Skip to content

Add httpsCallableFromURL #6162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/new-bugs-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@firebase/functions-compat": patch
"@firebase/functions": patch
---

Add httpsCallableFromURL
101 changes: 52 additions & 49 deletions common/api-review/functions.api.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,52 @@
## API Report File for "@firebase/functions"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { FirebaseApp } from '@firebase/app';
import { FirebaseError } from '@firebase/util';

// @public
export function connectFunctionsEmulator(functionsInstance: Functions, host: string, port: number): void;

// @public
export interface Functions {
app: FirebaseApp;
customDomain: string | null;
region: string;
}

// @public
export interface FunctionsError extends FirebaseError {
readonly code: FunctionsErrorCode;
readonly details?: unknown;
}

// @public
export type FunctionsErrorCode = 'ok' | 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated';

// @public
export function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions;

// @public
export type HttpsCallable<RequestData = unknown, ResponseData = unknown> = (data?: RequestData | null) => Promise<HttpsCallableResult<ResponseData>>;

// @public
export function httpsCallable<RequestData = unknown, ResponseData = unknown>(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable<RequestData, ResponseData>;

// @public
export interface HttpsCallableOptions {
timeout?: number;
}

// @public
export interface HttpsCallableResult<ResponseData = unknown> {
readonly data: ResponseData;
}


```
## API Report File for "@firebase/functions"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { FirebaseApp } from '@firebase/app';
import { FirebaseError } from '@firebase/util';

// @public
export function connectFunctionsEmulator(functionsInstance: Functions, host: string, port: number): void;

// @public
export interface Functions {
app: FirebaseApp;
customDomain: string | null;
region: string;
}

// @public
export interface FunctionsError extends FirebaseError {
readonly code: FunctionsErrorCode;
readonly details?: unknown;
}

// @public
export type FunctionsErrorCode = 'ok' | 'cancelled' | 'unknown' | 'invalid-argument' | 'deadline-exceeded' | 'not-found' | 'already-exists' | 'permission-denied' | 'resource-exhausted' | 'failed-precondition' | 'aborted' | 'out-of-range' | 'unimplemented' | 'internal' | 'unavailable' | 'data-loss' | 'unauthenticated';

// @public
export function getFunctions(app?: FirebaseApp, regionOrCustomDomain?: string): Functions;

// @public
export type HttpsCallable<RequestData = unknown, ResponseData = unknown> = (data?: RequestData | null) => Promise<HttpsCallableResult<ResponseData>>;

// @public
export function httpsCallable<RequestData = unknown, ResponseData = unknown>(functionsInstance: Functions, name: string, options?: HttpsCallableOptions): HttpsCallable<RequestData, ResponseData>;

// @public
export function httpsCallableFromURL<RequestData = unknown, ResponseData = unknown>(functionsInstance: Functions, url: string, options?: HttpsCallableOptions): HttpsCallable<RequestData, ResponseData>;

// @public
export interface HttpsCallableOptions {
timeout?: number;
}

// @public
export interface HttpsCallableResult<ResponseData = unknown> {
readonly data: ResponseData;
}


```
21 changes: 19 additions & 2 deletions e2e/sample-apps/modular.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,27 @@ async function authLogout(app) {
async function callFunctions(app) {
console.log('[FUNCTIONS] start');
const functions = getFunctions(app);
const callTest = httpsCallable(functions, 'callTest');
let callTest = httpsCallable(functions, 'callTest');
try {
const result = await callTest({ data: 'blah' });
console.log('[FUNCTIONS] result:', result.data);
console.log('[FUNCTIONS] result (by name):', result.data);
} catch (e) {
if (e.message.includes('Unauthenticated')) {
console.warn(
'Functions blocked by App Check. ' +
'Activate app check with a live sitekey to allow Functions calls'
);
} else {
throw e;
}
}
callTest = httpsCallableByUrl(
functions,
`https://us-central-${app.options.projectId}.cloudfunctions.net/callTest`
);
try {
const result = await callTest({ data: 'blah' });
console.log('[FUNCTIONS] result (by URL):', result.data);
} catch (e) {
if (e.message.includes('Unauthenticated')) {
console.warn(
Expand Down
16 changes: 15 additions & 1 deletion e2e/tests/modular.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ import {
Firestore,
initializeFirestore
} from 'firebase/firestore';
import { Functions, getFunctions, httpsCallable } from 'firebase/functions';
import {
Functions,
getFunctions,
httpsCallable,
httpsCallableFromURL
} from 'firebase/functions';
import { getMessaging } from 'firebase/messaging';
import {
FirebasePerformance,
Expand Down Expand Up @@ -144,6 +149,15 @@ describe('MODULAR', () => {
expect(result.data.word).to.equal('hellooo');
// This takes a while. Extend timeout past default (2000)
}).timeout(5000);
it('httpsCallableFromURL()', async () => {
const callTest = httpsCallableFromURL<{ data: string }, { word: string }>(
functions,
`https://us-central1-${app.options.projectId}.cloudfunctions.net/callTest`
);
const result = await callTest({ data: 'blah' });
expect(result.data.word).to.equal('hellooo');
// This takes a while. Extend timeout past default (2000)
}).timeout(5000);
});

describe('STORAGE', async () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/functions-compat/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { FirebaseFunctions, HttpsCallable } from '@firebase/functions-types';
import {
httpsCallable as httpsCallableExp,
httpsCallableFromURL as httpsCallableFromURLExp,
connectFunctionsEmulator as useFunctionsEmulatorExp,
HttpsCallableOptions,
Functions as FunctionsServiceExp
Expand Down Expand Up @@ -47,6 +48,12 @@ export class FunctionsService implements FirebaseFunctions, _FirebaseService {
httpsCallable(name: string, options?: HttpsCallableOptions): HttpsCallable {
return httpsCallableExp(this._delegate, name, options);
}
httpsCallableFromURL(
url: string,
options?: HttpsCallableOptions
): HttpsCallable {
return httpsCallableFromURLExp(this._delegate, url, options);
}
/**
* Deprecated in pre-modularized repo, does not exist in modularized
* functions package, need to convert to "host" and "port" args that
Expand Down
23 changes: 22 additions & 1 deletion packages/functions/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
FunctionsService,
DEFAULT_REGION,
connectFunctionsEmulator as _connectFunctionsEmulator,
httpsCallable as _httpsCallable
httpsCallable as _httpsCallable,
httpsCallableFromURL as _httpsCallableFromURL
} from './service';
import { getModularInstance } from '@firebase/util';

Expand Down Expand Up @@ -90,3 +91,23 @@ export function httpsCallable<RequestData = unknown, ResponseData = unknown>(
options
);
}

/**
* Returns a reference to the callable HTTPS trigger with the specified url.
* @param url - The url of the trigger.
* @public
*/
export function httpsCallableFromURL<
RequestData = unknown,
ResponseData = unknown
>(
functionsInstance: Functions,
url: string,
options?: HttpsCallableOptions
): HttpsCallable<RequestData, ResponseData> {
return _httpsCallableFromURL<RequestData, ResponseData>(
getModularInstance<FunctionsService>(functionsInstance as FunctionsService),
url,
options
);
}
30 changes: 29 additions & 1 deletion packages/functions/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,21 @@ export function httpsCallable<RequestData, ResponseData>(
}) as HttpsCallable<RequestData, ResponseData>;
}

/**
* Returns a reference to the callable https trigger with the given url.
* @param url - The url of the trigger.
* @public
*/
export function httpsCallableFromURL<RequestData, ResponseData>(
functionsInstance: FunctionsService,
url: string,
options?: HttpsCallableOptions
): HttpsCallable<RequestData, ResponseData> {
return (data => {
return callAtURL(functionsInstance, url, data, options || {});
}) as HttpsCallable<RequestData, ResponseData>;
}

/**
* Does an HTTP POST and returns the completed response.
* @param url The url to post to.
Expand Down Expand Up @@ -235,14 +250,27 @@ async function postJSON(
* @param name The name of the callable trigger.
* @param data The data to pass as params to the function.s
*/
async function call(
function call(
functionsInstance: FunctionsService,
name: string,
data: unknown,
options: HttpsCallableOptions
): Promise<HttpsCallableResult> {
const url = functionsInstance._url(name);
return callAtURL(functionsInstance, url, data, options);
}

/**
* Calls a callable function asynchronously and returns the result.
* @param url The url of the callable trigger.
* @param data The data to pass as params to the function.s
*/
async function callAtURL(
functionsInstance: FunctionsService,
url: string,
data: unknown,
options: HttpsCallableOptions
): Promise<HttpsCallableResult> {
// Encode any special types, such as dates, in the input data.
data = encode(data);
const body = { data };
Expand Down