firebase-functions 6.1.2 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -84,22 +84,27 @@ export interface CallableRequest<T = any> {
|
|
|
84
84
|
* The raw request handled by the callable.
|
|
85
85
|
*/
|
|
86
86
|
rawRequest: Request;
|
|
87
|
+
/**
|
|
88
|
+
* Whether this is a streaming request.
|
|
89
|
+
* Code can be optimized by not trying to generate a stream of chunks to
|
|
90
|
+
* call response.sendChunk on if request.acceptsStreaming is false.
|
|
91
|
+
* It is always safe, however, to call response.sendChunk as this will
|
|
92
|
+
* noop if acceptsStreaming is false.
|
|
93
|
+
*/
|
|
94
|
+
acceptsStreaming: boolean;
|
|
87
95
|
}
|
|
88
96
|
/**
|
|
89
|
-
* CallableProxyResponse
|
|
90
|
-
*
|
|
97
|
+
* CallableProxyResponse allows streaming response chunks and listening to signals
|
|
98
|
+
* triggered in events such as a disconnect.
|
|
91
99
|
*/
|
|
92
|
-
export interface
|
|
100
|
+
export interface CallableResponse<T = unknown> {
|
|
93
101
|
/**
|
|
94
102
|
* Writes a chunk of the response body to the client. This method can be called
|
|
95
103
|
* multiple times to stream data progressively.
|
|
104
|
+
* Returns a promise of whether the data was written. This can be false, for example,
|
|
105
|
+
* if the request was not a streaming request. Rejects if there is a network error.
|
|
96
106
|
*/
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Indicates whether the client has requested and can handle streaming responses.
|
|
100
|
-
* This should be checked before attempting to stream data to avoid compatibility issues.
|
|
101
|
-
*/
|
|
102
|
-
acceptsStreaming: boolean;
|
|
107
|
+
sendChunk: (chunk: T) => Promise<boolean>;
|
|
103
108
|
/**
|
|
104
109
|
* An AbortSignal that is triggered when the client disconnects or the
|
|
105
110
|
* request is terminated prematurely.
|
|
@@ -283,13 +283,9 @@ async function checkTokens(req, ctx, options) {
|
|
|
283
283
|
app: "INVALID",
|
|
284
284
|
auth: "INVALID",
|
|
285
285
|
};
|
|
286
|
-
await Promise.all([
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}),
|
|
290
|
-
Promise.resolve().then(async () => {
|
|
291
|
-
verifications.app = await checkAppCheckToken(req, ctx, options);
|
|
292
|
-
}),
|
|
286
|
+
[verifications.auth, verifications.app] = await Promise.all([
|
|
287
|
+
checkAuthToken(req, ctx),
|
|
288
|
+
checkAppCheckToken(req, ctx, options),
|
|
293
289
|
]);
|
|
294
290
|
const logPayload = {
|
|
295
291
|
verifications,
|
|
@@ -405,6 +401,7 @@ function encodeSSE(data) {
|
|
|
405
401
|
/** @internal */
|
|
406
402
|
function wrapOnCallHandler(options, handler, version) {
|
|
407
403
|
return async (req, res) => {
|
|
404
|
+
var _a;
|
|
408
405
|
const abortController = new AbortController();
|
|
409
406
|
let heartbeatInterval = null;
|
|
410
407
|
const heartbeatSeconds = options.heartbeatSeconds === undefined ? exports.DEFAULT_HEARTBEAT_SECONDS : options.heartbeatSeconds;
|
|
@@ -487,6 +484,12 @@ function wrapOnCallHandler(options, handler, version) {
|
|
|
487
484
|
throw new HttpsError("invalid-argument", "Unsupported Accept header 'text/event-stream'");
|
|
488
485
|
}
|
|
489
486
|
const data = decode(req.body.data);
|
|
487
|
+
if (options.authPolicy) {
|
|
488
|
+
const authorized = await options.authPolicy((_a = context.auth) !== null && _a !== void 0 ? _a : null, data);
|
|
489
|
+
if (!authorized) {
|
|
490
|
+
throw new HttpsError("permission-denied", "Permission Denied");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
490
493
|
let result;
|
|
491
494
|
if (version === "gcfv1") {
|
|
492
495
|
result = await handler(data, context);
|
|
@@ -495,26 +498,38 @@ function wrapOnCallHandler(options, handler, version) {
|
|
|
495
498
|
const arg = {
|
|
496
499
|
...context,
|
|
497
500
|
data,
|
|
501
|
+
acceptsStreaming,
|
|
498
502
|
};
|
|
499
503
|
const responseProxy = {
|
|
500
|
-
|
|
504
|
+
sendChunk(chunk) {
|
|
501
505
|
// if client doesn't accept sse-protocol, response.write() is no-op.
|
|
502
506
|
if (!acceptsStreaming) {
|
|
503
|
-
return false;
|
|
507
|
+
return Promise.resolve(false);
|
|
504
508
|
}
|
|
505
509
|
// if connection is already closed, response.write() is no-op.
|
|
506
510
|
if (abortController.signal.aborted) {
|
|
507
|
-
return false;
|
|
511
|
+
return Promise.resolve(false);
|
|
508
512
|
}
|
|
509
513
|
const formattedData = encodeSSE({ message: chunk });
|
|
510
|
-
|
|
514
|
+
let resolve;
|
|
515
|
+
let reject;
|
|
516
|
+
const p = new Promise((res, rej) => {
|
|
517
|
+
resolve = res;
|
|
518
|
+
reject = rej;
|
|
519
|
+
});
|
|
520
|
+
const wrote = res.write(formattedData, (error) => {
|
|
521
|
+
if (error) {
|
|
522
|
+
reject(error);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
resolve(wrote);
|
|
526
|
+
});
|
|
511
527
|
// Reset heartbeat timer after successful write
|
|
512
528
|
if (wrote && heartbeatInterval !== null && heartbeatSeconds > 0) {
|
|
513
529
|
scheduleHeartbeat();
|
|
514
530
|
}
|
|
515
|
-
return
|
|
531
|
+
return p;
|
|
516
532
|
},
|
|
517
|
-
acceptsStreaming,
|
|
518
533
|
signal: abortController.signal,
|
|
519
534
|
};
|
|
520
535
|
if (acceptsStreaming) {
|
package/lib/runtime/loader.js
CHANGED
|
@@ -40,8 +40,8 @@ async function loadModule(functionsDir) {
|
|
|
40
40
|
return require(path.resolve(absolutePath));
|
|
41
41
|
}
|
|
42
42
|
catch (e) {
|
|
43
|
-
if (e.code === "ERR_REQUIRE_ESM") {
|
|
44
|
-
// This is an ESM package!
|
|
43
|
+
if (e.code === "ERR_REQUIRE_ESM" || e.code === "ERR_REQUIRE_ASYNC_MODULE") {
|
|
44
|
+
// This is an ESM package, or one containing top-level awaits!
|
|
45
45
|
const modulePath = require.resolve(absolutePath);
|
|
46
46
|
// Resolve module path to file:// URL. Required for windows support.
|
|
47
47
|
const moduleURL = url.pathToFileURL(modulePath).href;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as express from "express";
|
|
2
2
|
import { ResetValue } from "../../common/options";
|
|
3
|
-
import { CallableRequest,
|
|
3
|
+
import { CallableRequest, CallableResponse, FunctionsErrorCode, HttpsError, Request, AuthData } from "../../common/providers/https";
|
|
4
4
|
import { ManifestEndpoint } from "../../runtime/manifest";
|
|
5
5
|
import { GlobalOptions, SupportedRegion } from "../options";
|
|
6
6
|
import { Expression } from "../../params";
|
|
@@ -101,7 +101,7 @@ export interface HttpsOptions extends Omit<GlobalOptions, "region" | "enforceApp
|
|
|
101
101
|
/**
|
|
102
102
|
* Options that can be set on a callable HTTPS function.
|
|
103
103
|
*/
|
|
104
|
-
export interface CallableOptions extends HttpsOptions {
|
|
104
|
+
export interface CallableOptions<T = any> extends HttpsOptions {
|
|
105
105
|
/**
|
|
106
106
|
* Determines whether Firebase AppCheck is enforced.
|
|
107
107
|
* When true, requests with invalid tokens autorespond with a 401
|
|
@@ -139,7 +139,22 @@ export interface CallableOptions extends HttpsOptions {
|
|
|
139
139
|
* Defaults to 30 seconds.
|
|
140
140
|
*/
|
|
141
141
|
heartbeatSeconds?: number | null;
|
|
142
|
+
/**
|
|
143
|
+
* Callback for whether a request is authorized.
|
|
144
|
+
*
|
|
145
|
+
* Designed to allow reusable auth policies to be passed as an options object. Two built-in reusable policies exist:
|
|
146
|
+
* isSignedIn and hasClaim.
|
|
147
|
+
*/
|
|
148
|
+
authPolicy?: (auth: AuthData | null, data: T) => boolean | Promise<boolean>;
|
|
142
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* An auth policy that requires a user to be signed in.
|
|
152
|
+
*/
|
|
153
|
+
export declare const isSignedIn: () => (auth: AuthData | null) => boolean;
|
|
154
|
+
/**
|
|
155
|
+
* An auth policy that requires a user to be both signed in and have a specific claim (optionally with a specific value)
|
|
156
|
+
*/
|
|
157
|
+
export declare const hasClaim: (claim: string, value?: string) => (auth: AuthData | null) => boolean;
|
|
143
158
|
/**
|
|
144
159
|
* Handles HTTPS requests.
|
|
145
160
|
*/
|
|
@@ -156,12 +171,16 @@ res: express.Response) => void | Promise<void>) & {
|
|
|
156
171
|
/**
|
|
157
172
|
* Creates a callable method for clients to call using a Firebase SDK.
|
|
158
173
|
*/
|
|
159
|
-
export interface CallableFunction<T, Return> extends HttpsFunction {
|
|
174
|
+
export interface CallableFunction<T, Return, Stream = unknown> extends HttpsFunction {
|
|
160
175
|
/** Executes the handler function with the provided data as input. Used for unit testing.
|
|
161
176
|
* @param data - An input for the handler function.
|
|
162
177
|
* @returns The output of the handler function.
|
|
163
178
|
*/
|
|
164
|
-
run(
|
|
179
|
+
run(request: CallableRequest<T>): Return;
|
|
180
|
+
stream(request: CallableRequest<T>, response: CallableResponse<Stream>): {
|
|
181
|
+
stream: AsyncIterator<Stream>;
|
|
182
|
+
output: Return;
|
|
183
|
+
};
|
|
165
184
|
}
|
|
166
185
|
/**
|
|
167
186
|
* Handles HTTPS requests.
|
|
@@ -182,10 +201,10 @@ export declare function onRequest(handler: (request: Request, response: express.
|
|
|
182
201
|
* @param handler - A function that takes a {@link https.CallableRequest}.
|
|
183
202
|
* @returns A function that you can export and deploy.
|
|
184
203
|
*/
|
|
185
|
-
export declare function onCall<T = any, Return = any | Promise<any
|
|
204
|
+
export declare function onCall<T = any, Return = any | Promise<any>, Stream = unknown>(opts: CallableOptions<T>, handler: (request: CallableRequest<T>, response?: CallableResponse<Stream>) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>, Stream>;
|
|
186
205
|
/**
|
|
187
206
|
* Declares a callable method for clients to call using a Firebase SDK.
|
|
188
207
|
* @param handler - A function that takes a {@link https.CallableRequest}.
|
|
189
208
|
* @returns A function that you can export and deploy.
|
|
190
209
|
*/
|
|
191
|
-
export declare function onCall<T = any, Return = any | Promise<any
|
|
210
|
+
export declare function onCall<T = any, Return = any | Promise<any>, Stream = unknown>(handler: (request: CallableRequest<T>, response?: CallableResponse<Stream>) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
// SOFTWARE.
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.onCall = exports.onRequest = exports.HttpsError = void 0;
|
|
24
|
+
exports.onCall = exports.onRequest = exports.hasClaim = exports.isSignedIn = exports.HttpsError = void 0;
|
|
25
25
|
/**
|
|
26
26
|
* Cloud functions to handle HTTPS request or callable RPCs.
|
|
27
27
|
* @packageDocumentation
|
|
@@ -35,6 +35,24 @@ Object.defineProperty(exports, "HttpsError", { enumerable: true, get: function (
|
|
|
35
35
|
const manifest_1 = require("../../runtime/manifest");
|
|
36
36
|
const options = require("../options");
|
|
37
37
|
const onInit_1 = require("../../common/onInit");
|
|
38
|
+
/**
|
|
39
|
+
* An auth policy that requires a user to be signed in.
|
|
40
|
+
*/
|
|
41
|
+
const isSignedIn = () => (auth) => !!auth;
|
|
42
|
+
exports.isSignedIn = isSignedIn;
|
|
43
|
+
/**
|
|
44
|
+
* An auth policy that requires a user to be both signed in and have a specific claim (optionally with a specific value)
|
|
45
|
+
*/
|
|
46
|
+
const hasClaim = (claim, value) => (auth) => {
|
|
47
|
+
if (!auth) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (!(claim in auth.token)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return !value || auth.token[claim] === value;
|
|
54
|
+
};
|
|
55
|
+
exports.hasClaim = hasClaim;
|
|
38
56
|
function onRequest(optsOrHandler, handler) {
|
|
39
57
|
let opts;
|
|
40
58
|
if (arguments.length === 1) {
|
|
@@ -131,12 +149,13 @@ function onCall(optsOrHandler, handler) {
|
|
|
131
149
|
origin = origin[0];
|
|
132
150
|
}
|
|
133
151
|
// fix the length of handler to make the call to handler consistent
|
|
134
|
-
const fixedLen = (req, resp) =>
|
|
152
|
+
const fixedLen = (req, resp) => handler(req, resp);
|
|
135
153
|
let func = (0, https_1.onCallHandler)({
|
|
136
154
|
cors: { origin, methods: "POST" },
|
|
137
155
|
enforceAppCheck: (_a = opts.enforceAppCheck) !== null && _a !== void 0 ? _a : options.getGlobalOptions().enforceAppCheck,
|
|
138
156
|
consumeAppCheckToken: opts.consumeAppCheckToken,
|
|
139
157
|
heartbeatSeconds: opts.heartbeatSeconds,
|
|
158
|
+
authPolicy: opts.authPolicy,
|
|
140
159
|
}, fixedLen, "gcfv2");
|
|
141
160
|
func = (0, trace_1.wrapTraceContext)((0, onInit_1.withInit)(func));
|
|
142
161
|
Object.defineProperty(func, "__trigger", {
|
|
@@ -175,7 +194,18 @@ function onCall(optsOrHandler, handler) {
|
|
|
175
194
|
},
|
|
176
195
|
callableTrigger: {},
|
|
177
196
|
};
|
|
197
|
+
// TODO: in the next major version, do auth/appcheck in these helper methods too.
|
|
178
198
|
func.run = (0, onInit_1.withInit)(handler);
|
|
199
|
+
func.stream = () => {
|
|
200
|
+
return {
|
|
201
|
+
stream: {
|
|
202
|
+
next() {
|
|
203
|
+
return Promise.reject("Coming soon");
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
output: Promise.reject("Coming soon"),
|
|
207
|
+
};
|
|
208
|
+
};
|
|
179
209
|
return func;
|
|
180
210
|
}
|
|
181
211
|
exports.onCall = onCall;
|