firebase-functions 6.1.0 → 6.1.1

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.
@@ -85,6 +85,14 @@ export interface CallableRequest<T = any> {
85
85
  */
86
86
  rawRequest: Request;
87
87
  }
88
+ /**
89
+ * CallableProxyResponse exposes subset of express.Response object
90
+ * to allow writing partial, streaming responses back to the client.
91
+ */
92
+ export interface CallableProxyResponse {
93
+ write: express.Response["write"];
94
+ acceptsStreaming: boolean;
95
+ }
88
96
  /**
89
97
  * The set of Firebase Functions status codes. The codes are the same at the
90
98
  * ones exposed by {@link https://github.com/grpc/grpc/blob/master/doc/statuscodes.md | gRPC}.
@@ -385,8 +385,8 @@ async function checkAppCheckToken(req, ctx, options) {
385
385
  }
386
386
  }
387
387
  /** @internal */
388
- function onCallHandler(options, handler) {
389
- const wrapped = wrapOnCallHandler(options, handler);
388
+ function onCallHandler(options, handler, version) {
389
+ const wrapped = wrapOnCallHandler(options, handler, version);
390
390
  return (req, res) => {
391
391
  return new Promise((resolve) => {
392
392
  res.on("finish", resolve);
@@ -397,8 +397,11 @@ function onCallHandler(options, handler) {
397
397
  };
398
398
  }
399
399
  exports.onCallHandler = onCallHandler;
400
+ function encodeSSE(data) {
401
+ return `data: ${JSON.stringify(data)}\n`;
402
+ }
400
403
  /** @internal */
401
- function wrapOnCallHandler(options, handler) {
404
+ function wrapOnCallHandler(options, handler, version) {
402
405
  return async (req, res) => {
403
406
  try {
404
407
  if (!isValidRequest(req)) {
@@ -413,7 +416,7 @@ function wrapOnCallHandler(options, handler) {
413
416
  // The original monkey-patched code lived in the functionsEmulatorRuntime
414
417
  // (link: https://github.com/firebase/firebase-tools/blob/accea7abda3cc9fa6bb91368e4895faf95281c60/src/emulator/functionsEmulatorRuntime.ts#L480)
415
418
  // and was not compatible with how monorepos separate out packages (see https://github.com/firebase/firebase-tools/issues/5210).
416
- if ((0, debug_1.isDebugFeatureEnabled)("skipTokenVerification") && handler.length === 2) {
419
+ if ((0, debug_1.isDebugFeatureEnabled)("skipTokenVerification") && version === "gcfv1") {
417
420
  const authContext = context.rawRequest.header(exports.CALLABLE_AUTH_HEADER);
418
421
  if (authContext) {
419
422
  logger.debug("Callable functions auth override", {
@@ -452,9 +455,14 @@ function wrapOnCallHandler(options, handler) {
452
455
  // pushes with FCM. In that case, the FCM APIs will validate the token.
453
456
  context.instanceIdToken = req.header("Firebase-Instance-ID-Token");
454
457
  }
458
+ const acceptsStreaming = req.header("accept") === "text/event-stream";
459
+ if (acceptsStreaming && version === "gcfv1") {
460
+ // streaming responses are not supported in v1 callable
461
+ throw new HttpsError("invalid-argument", "Unsupported Accept header 'text/event-stream'");
462
+ }
455
463
  const data = decode(req.body.data);
456
464
  let result;
457
- if (handler.length === 2) {
465
+ if (version === "gcfv1") {
458
466
  result = await handler(data, context);
459
467
  }
460
468
  else {
@@ -462,15 +470,36 @@ function wrapOnCallHandler(options, handler) {
462
470
  ...context,
463
471
  data,
464
472
  };
473
+ // TODO: set up optional heartbeat
474
+ const responseProxy = {
475
+ write(chunk) {
476
+ if (acceptsStreaming) {
477
+ const formattedData = encodeSSE({ message: chunk });
478
+ return res.write(formattedData);
479
+ }
480
+ // if client doesn't accept sse-protocol, response.write() is no-op.
481
+ },
482
+ acceptsStreaming,
483
+ };
484
+ if (acceptsStreaming) {
485
+ // SSE always responds with 200
486
+ res.status(200);
487
+ }
465
488
  // For some reason the type system isn't picking up that the handler
466
489
  // is a one argument function.
467
- result = await handler(arg);
490
+ result = await handler(arg, responseProxy);
468
491
  }
469
492
  // Encode the result as JSON to preserve types like Dates.
470
493
  result = encode(result);
471
494
  // If there was some result, encode it in the body.
472
495
  const responseBody = { result };
473
- res.status(200).send(responseBody);
496
+ if (acceptsStreaming) {
497
+ res.write(encodeSSE(responseBody));
498
+ res.end();
499
+ }
500
+ else {
501
+ res.status(200).send(responseBody);
502
+ }
474
503
  }
475
504
  catch (err) {
476
505
  let httpErr = err;
@@ -481,7 +510,12 @@ function wrapOnCallHandler(options, handler) {
481
510
  }
482
511
  const { status } = httpErr.httpErrorCode;
483
512
  const body = { error: httpErr.toJSON() };
484
- res.status(status).send(body);
513
+ if (version === "gcfv2" && req.header("accept") === "text/event-stream") {
514
+ res.send(encodeSSE(body));
515
+ }
516
+ else {
517
+ res.status(status).send(body);
518
+ }
485
519
  }
486
520
  };
487
521
  }
@@ -70,9 +70,8 @@ function _onRequestWithOptions(handler, options) {
70
70
  exports._onRequestWithOptions = _onRequestWithOptions;
71
71
  /** @internal */
72
72
  function _onCallWithOptions(handler, options) {
73
- // onCallHandler sniffs the function length of the passed-in callback
74
- // and the user could have only tried to listen to data. Wrap their handler
75
- // in another handler to avoid accidentally triggering the v2 API
73
+ // fix the length of handler to make the call to handler consistent
74
+ // in the onCallHandler
76
75
  const fixedLen = (data, context) => {
77
76
  return (0, onInit_1.withInit)(handler)(data, context);
78
77
  };
@@ -80,7 +79,7 @@ function _onCallWithOptions(handler, options) {
80
79
  enforceAppCheck: options.enforceAppCheck,
81
80
  consumeAppCheckToken: options.consumeAppCheckToken,
82
81
  cors: { origin: true, methods: "POST" },
83
- }, fixedLen));
82
+ }, fixedLen, "gcfv1"));
84
83
  func.__trigger = {
85
84
  labels: {},
86
85
  ...(0, cloud_functions_1.optionsToTrigger)(options),
@@ -1,6 +1,6 @@
1
1
  import * as express from "express";
2
2
  import { ResetValue } from "../../common/options";
3
- import { CallableRequest, FunctionsErrorCode, HttpsError, Request } from "../../common/providers/https";
3
+ import { CallableRequest, CallableProxyResponse, FunctionsErrorCode, HttpsError, Request } from "../../common/providers/https";
4
4
  import { ManifestEndpoint } from "../../runtime/manifest";
5
5
  import { GlobalOptions, SupportedRegion } from "../options";
6
6
  import { Expression } from "../../params";
@@ -175,10 +175,10 @@ export declare function onRequest(handler: (request: Request, response: express.
175
175
  * @param handler - A function that takes a {@link https.CallableRequest}.
176
176
  * @returns A function that you can export and deploy.
177
177
  */
178
- export declare function onCall<T = any, Return = any | Promise<any>>(opts: CallableOptions, handler: (request: CallableRequest<T>) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
178
+ export declare function onCall<T = any, Return = any | Promise<any>>(opts: CallableOptions, handler: (request: CallableRequest<T>, response?: CallableProxyResponse) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
179
179
  /**
180
180
  * Declares a callable method for clients to call using a Firebase SDK.
181
181
  * @param handler - A function that takes a {@link https.CallableRequest}.
182
182
  * @returns A function that you can export and deploy.
183
183
  */
184
- export declare function onCall<T = any, Return = any | Promise<any>>(handler: (request: CallableRequest<T>) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
184
+ export declare function onCall<T = any, Return = any | Promise<any>>(handler: (request: CallableRequest<T>, response?: CallableProxyResponse) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
@@ -130,15 +130,14 @@ function onCall(optsOrHandler, handler) {
130
130
  if (Array.isArray(origin) && origin.length === 1) {
131
131
  origin = origin[0];
132
132
  }
133
- // onCallHandler sniffs the function length to determine which API to present.
134
- // fix the length to prevent api versions from being mismatched.
135
- const fixedLen = (req) => (0, onInit_1.withInit)(handler)(req);
133
+ // fix the length of handler to make the call to handler consistent
134
+ const fixedLen = (req, resp) => (0, onInit_1.withInit)(handler)(req, resp);
136
135
  let func = (0, https_1.onCallHandler)({
137
136
  cors: { origin, methods: "POST" },
138
137
  enforceAppCheck: (_a = opts.enforceAppCheck) !== null && _a !== void 0 ? _a : options.getGlobalOptions().enforceAppCheck,
139
138
  consumeAppCheckToken: opts.consumeAppCheckToken,
140
- }, fixedLen);
141
- func = (0, trace_1.wrapTraceContext)(func);
139
+ }, fixedLen, "gcfv2");
140
+ func = (0, trace_1.wrapTraceContext)((0, onInit_1.withInit)(func));
142
141
  Object.defineProperty(func, "__trigger", {
143
142
  get: () => {
144
143
  const baseOpts = options.optionsToTriggerAnnotations(options.getGlobalOptions());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-functions",
3
- "version": "6.1.0",
3
+ "version": "6.1.1",
4
4
  "description": "Firebase SDK for Cloud Functions",
5
5
  "keywords": [
6
6
  "firebase",
@@ -299,7 +299,7 @@
299
299
  "eslint-config-prettier": "^8.3.0",
300
300
  "eslint-plugin-jsdoc": "^39.2.9",
301
301
  "eslint-plugin-prettier": "^4.0.0",
302
- "firebase-admin": "^12.1.0",
302
+ "firebase-admin": "^13.0.0",
303
303
  "js-yaml": "^3.13.1",
304
304
  "jsdom": "^16.2.1",
305
305
  "jsonwebtoken": "^9.0.0",
@@ -319,7 +319,7 @@
319
319
  "yargs": "^15.3.1"
320
320
  },
321
321
  "peerDependencies": {
322
- "firebase-admin": "^11.10.0 || ^12.0.0"
322
+ "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0"
323
323
  },
324
324
  "engines": {
325
325
  "node": ">=14.10.0"