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 6 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
7 changes: 7 additions & 0 deletions .changeset/new-bugs-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@firebase/functions-compat": patch
"@firebase/functions": patch
"firebase-size-analysis": patch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

firebase-size-analysis can be removed, it's in the ignored changesets list. Actually, the change to that file seems unneeded (see below) so also for that reason.

---

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;
}


```
23 changes: 20 additions & 3 deletions e2e/sample-apps/modular.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,28 @@ async function authLogout(app) {
*/
async function callFunctions(app) {
console.log('[FUNCTIONS] start');
const functions = getFunctions(app);
const callTest = httpsCallable(functions, 'callTest');
let functions = getFunctions(app);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is functions reassigned?

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 given name.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"given name" => "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
5 changes: 3 additions & 2 deletions repo-scripts/size-analysis/bundle-definitions/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
"path": "functions",
"imports": [
"getFunctions",
"httpsCallable"
"httpsCallable",
"httpsCallableFromURL"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need to be added here, this represents a typical bundle of imports for a use case and it's not a common case that a developer would use httpsCallable and httpsCallableFromURL in the same app. In any case, since httpsCallable wraps the core code of httpsCallableFromURL, the bundle size should be about the same.

]
}
]
}
]
}
]
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still thinks there's a whitespace change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno why my formatter keeps on insisting on this. Edited in vim to avoid stripping the newline.