firebase-functions 6.5.0 → 7.0.0-rc.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.
Files changed (157) hide show
  1. package/lib/_virtual/rolldown_runtime.js +34 -0
  2. package/lib/bin/firebase-functions.js +78 -103
  3. package/lib/common/app.js +35 -55
  4. package/lib/common/change.js +54 -75
  5. package/lib/common/config.js +41 -41
  6. package/lib/common/debug.js +23 -47
  7. package/lib/common/encoding.js +59 -82
  8. package/lib/common/onInit.js +26 -28
  9. package/lib/common/options.js +22 -42
  10. package/lib/common/params.d.ts +6 -6
  11. package/lib/common/params.js +0 -23
  12. package/lib/common/providers/database.js +270 -300
  13. package/lib/common/providers/firestore.js +66 -92
  14. package/lib/common/providers/https.d.ts +0 -1
  15. package/lib/common/providers/https.js +537 -539
  16. package/lib/common/providers/identity.js +393 -444
  17. package/lib/common/providers/tasks.js +64 -98
  18. package/lib/common/timezone.js +544 -542
  19. package/lib/common/trace.d.ts +0 -1
  20. package/lib/common/trace.js +63 -55
  21. package/lib/common/utilities/assertions.d.ts +11 -0
  22. package/lib/common/utilities/assertions.js +18 -0
  23. package/lib/common/utilities/encoder.js +20 -37
  24. package/lib/common/utilities/path-pattern.js +106 -132
  25. package/lib/common/utilities/path.js +28 -27
  26. package/lib/common/utilities/utils.js +23 -45
  27. package/lib/esm/_virtual/rolldown_runtime.mjs +16 -0
  28. package/lib/esm/bin/firebase-functions.mjs +91 -0
  29. package/lib/esm/common/app.mjs +39 -0
  30. package/lib/esm/common/change.mjs +57 -0
  31. package/lib/esm/common/config.mjs +45 -0
  32. package/lib/esm/common/debug.mjs +28 -0
  33. package/lib/esm/common/encoding.mjs +69 -0
  34. package/lib/esm/common/onInit.mjs +33 -0
  35. package/lib/esm/common/options.mjs +22 -0
  36. package/lib/esm/common/params.mjs +1 -0
  37. package/lib/esm/common/providers/database.mjs +269 -0
  38. package/lib/esm/common/providers/firestore.mjs +78 -0
  39. package/lib/esm/common/providers/https.mjs +573 -0
  40. package/lib/esm/common/providers/identity.mjs +428 -0
  41. package/lib/esm/common/providers/tasks.mjs +67 -0
  42. package/lib/esm/common/timezone.mjs +544 -0
  43. package/lib/esm/common/trace.mjs +73 -0
  44. package/lib/esm/common/utilities/assertions.mjs +17 -0
  45. package/lib/esm/common/utilities/encoder.mjs +21 -0
  46. package/lib/esm/common/utilities/path-pattern.mjs +116 -0
  47. package/lib/esm/common/utilities/path.mjs +35 -0
  48. package/lib/esm/common/utilities/utils.mjs +29 -0
  49. package/lib/esm/function-configuration.mjs +1 -0
  50. package/lib/esm/logger/common.mjs +23 -0
  51. package/lib/esm/logger/compat.mjs +25 -0
  52. package/lib/esm/logger/index.mjs +131 -0
  53. package/lib/esm/params/index.mjs +160 -0
  54. package/lib/esm/params/types.mjs +400 -0
  55. package/lib/esm/runtime/loader.mjs +132 -0
  56. package/lib/esm/runtime/manifest.mjs +134 -0
  57. package/lib/esm/types/global.d.mjs +1 -0
  58. package/lib/esm/v1/cloud-functions.mjs +206 -0
  59. package/lib/esm/v1/config.mjs +14 -0
  60. package/lib/esm/v1/function-builder.mjs +252 -0
  61. package/lib/esm/v1/function-configuration.mjs +72 -0
  62. package/lib/esm/v1/index.mjs +27 -0
  63. package/lib/esm/v1/providers/analytics.mjs +212 -0
  64. package/lib/esm/v1/providers/auth.mjs +156 -0
  65. package/lib/esm/v1/providers/database.mjs +243 -0
  66. package/lib/esm/v1/providers/firestore.mjs +131 -0
  67. package/lib/esm/v1/providers/https.mjs +82 -0
  68. package/lib/esm/v1/providers/pubsub.mjs +175 -0
  69. package/lib/esm/v1/providers/remoteConfig.mjs +64 -0
  70. package/lib/esm/v1/providers/storage.mjs +163 -0
  71. package/lib/esm/v1/providers/tasks.mjs +63 -0
  72. package/lib/esm/v1/providers/testLab.mjs +94 -0
  73. package/lib/esm/v2/core.mjs +4 -0
  74. package/lib/esm/v2/index.mjs +28 -0
  75. package/lib/esm/v2/options.mjs +102 -0
  76. package/lib/esm/v2/providers/alerts/alerts.mjs +85 -0
  77. package/lib/esm/v2/providers/alerts/appDistribution.mjs +75 -0
  78. package/lib/esm/v2/providers/alerts/billing.mjs +51 -0
  79. package/lib/esm/v2/providers/alerts/crashlytics.mjs +122 -0
  80. package/lib/esm/v2/providers/alerts/index.mjs +22 -0
  81. package/lib/esm/v2/providers/alerts/performance.mjs +66 -0
  82. package/lib/esm/v2/providers/database.mjs +197 -0
  83. package/lib/esm/v2/providers/dataconnect.mjs +130 -0
  84. package/lib/esm/v2/providers/eventarc.mjs +51 -0
  85. package/lib/esm/v2/providers/firestore.mjs +294 -0
  86. package/lib/esm/v2/providers/https.mjs +210 -0
  87. package/lib/esm/v2/providers/identity.mjs +103 -0
  88. package/lib/esm/v2/providers/pubsub.mjs +148 -0
  89. package/lib/esm/v2/providers/remoteConfig.mjs +52 -0
  90. package/lib/esm/v2/providers/scheduler.mjs +84 -0
  91. package/lib/esm/v2/providers/storage.mjs +155 -0
  92. package/lib/esm/v2/providers/tasks.mjs +65 -0
  93. package/lib/esm/v2/providers/testLab.mjs +53 -0
  94. package/lib/esm/v2/trace.mjs +20 -0
  95. package/lib/function-configuration.d.ts +0 -0
  96. package/lib/function-configuration.js +0 -0
  97. package/lib/logger/common.js +21 -41
  98. package/lib/logger/compat.js +18 -33
  99. package/lib/logger/index.js +119 -130
  100. package/lib/params/index.d.ts +19 -4
  101. package/lib/params/index.js +153 -129
  102. package/lib/params/types.d.ts +17 -0
  103. package/lib/params/types.js +390 -382
  104. package/lib/runtime/loader.js +114 -148
  105. package/lib/runtime/manifest.js +106 -126
  106. package/lib/types/global.d.js +0 -0
  107. package/lib/v1/cloud-functions.d.ts +2 -2
  108. package/lib/v1/cloud-functions.js +193 -241
  109. package/lib/v1/config.d.ts +4 -7
  110. package/lib/v1/config.js +13 -75
  111. package/lib/v1/function-builder.js +239 -368
  112. package/lib/v1/function-configuration.js +70 -63
  113. package/lib/v1/index.js +118 -73
  114. package/lib/v1/providers/analytics.js +189 -210
  115. package/lib/v1/providers/auth.d.ts +2 -1
  116. package/lib/v1/providers/auth.js +159 -164
  117. package/lib/v1/providers/database.js +237 -242
  118. package/lib/v1/providers/firestore.js +131 -130
  119. package/lib/v1/providers/https.d.ts +2 -1
  120. package/lib/v1/providers/https.js +79 -86
  121. package/lib/v1/providers/pubsub.js +175 -172
  122. package/lib/v1/providers/remoteConfig.js +64 -68
  123. package/lib/v1/providers/storage.js +161 -163
  124. package/lib/v1/providers/tasks.d.ts +1 -1
  125. package/lib/v1/providers/tasks.js +65 -80
  126. package/lib/v1/providers/testLab.js +94 -94
  127. package/lib/v2/core.d.ts +1 -1
  128. package/lib/v2/core.js +5 -32
  129. package/lib/v2/index.d.ts +6 -3
  130. package/lib/v2/index.js +123 -75
  131. package/lib/v2/options.js +88 -114
  132. package/lib/v2/providers/alerts/alerts.js +76 -95
  133. package/lib/v2/providers/alerts/appDistribution.js +73 -78
  134. package/lib/v2/providers/alerts/billing.js +49 -53
  135. package/lib/v2/providers/alerts/crashlytics.js +110 -102
  136. package/lib/v2/providers/alerts/index.js +56 -53
  137. package/lib/v2/providers/alerts/performance.js +64 -74
  138. package/lib/v2/providers/database.js +177 -180
  139. package/lib/v2/providers/dataconnect.d.ts +95 -0
  140. package/lib/v2/providers/dataconnect.js +137 -0
  141. package/lib/v2/providers/eventarc.js +55 -77
  142. package/lib/v2/providers/firestore.js +262 -260
  143. package/lib/v2/providers/https.d.ts +3 -2
  144. package/lib/v2/providers/https.js +210 -247
  145. package/lib/v2/providers/identity.d.ts +2 -1
  146. package/lib/v2/providers/identity.js +96 -105
  147. package/lib/v2/providers/pubsub.js +149 -167
  148. package/lib/v2/providers/remoteConfig.js +54 -63
  149. package/lib/v2/providers/scheduler.js +84 -96
  150. package/lib/v2/providers/storage.js +147 -162
  151. package/lib/v2/providers/tasks.d.ts +1 -1
  152. package/lib/v2/providers/tasks.js +68 -95
  153. package/lib/v2/providers/testLab.js +55 -64
  154. package/lib/v2/trace.js +18 -19
  155. package/package.json +290 -226
  156. package/protos/compiledFirestore.mjs +3512 -0
  157. package/protos/update.sh +28 -7
@@ -0,0 +1,78 @@
1
+ import { error, warn } from "../../logger/index.mjs";
2
+ import { getApp } from "../app.mjs";
3
+ import { dateToTimestampProto } from "../utilities/encoder.mjs";
4
+ import * as firestore from "firebase-admin/firestore";
5
+ import { google } from "../../../../protos/compiledFirestore.mjs";
6
+
7
+ //#region src/common/providers/firestore.ts
8
+ /** static-complied protobufs */
9
+ const DocumentEventData = google.events.cloud.firestore.v1.DocumentEventData;
10
+ let firestoreInstance;
11
+ /** @hidden */
12
+ function _getValueProto(data, resource, valueFieldName) {
13
+ const value = data?.[valueFieldName];
14
+ if (typeof value === "undefined" || value === null || typeof value === "object" && !Object.keys(value).length) {
15
+ return resource;
16
+ }
17
+ const proto = {
18
+ fields: value?.fields || {},
19
+ createTime: dateToTimestampProto(value?.createTime),
20
+ updateTime: dateToTimestampProto(value?.updateTime),
21
+ name: value?.name || resource
22
+ };
23
+ return proto;
24
+ }
25
+ /** @internal */
26
+ function createSnapshotFromProtobuf(data, path, databaseId) {
27
+ if (!firestoreInstance) {
28
+ firestoreInstance = firestore.getFirestore(getApp(), databaseId);
29
+ }
30
+ try {
31
+ const dataBuffer = Buffer.from(data);
32
+ const firestoreDecoded = DocumentEventData.decode(dataBuffer);
33
+ return firestoreInstance.snapshot_(firestoreDecoded.value || path, null, "protobufJS");
34
+ } catch (err) {
35
+ error("Failed to decode protobuf and create a snapshot.");
36
+ throw err;
37
+ }
38
+ }
39
+ /** @internal */
40
+ function createBeforeSnapshotFromProtobuf(data, path, databaseId) {
41
+ if (!firestoreInstance) {
42
+ firestoreInstance = firestore.getFirestore(getApp(), databaseId);
43
+ }
44
+ try {
45
+ const dataBuffer = Buffer.from(data);
46
+ const firestoreDecoded = DocumentEventData.decode(dataBuffer);
47
+ return firestoreInstance.snapshot_(firestoreDecoded.oldValue || path, null, "protobufJS");
48
+ } catch (err) {
49
+ error("Failed to decode protobuf and create a before snapshot.");
50
+ throw err;
51
+ }
52
+ }
53
+ /** @internal */
54
+ function createSnapshotFromJson(data, source, createTime, updateTime, databaseId) {
55
+ if (!firestoreInstance) {
56
+ firestoreInstance = databaseId ? firestore.getFirestore(getApp(), databaseId) : firestore.getFirestore(getApp());
57
+ }
58
+ const valueProto = _getValueProto(data, source, "value");
59
+ let timeString = createTime || updateTime;
60
+ if (!timeString) {
61
+ warn("Snapshot has no readTime. Using now()");
62
+ timeString = new Date().toISOString();
63
+ }
64
+ const readTime = dateToTimestampProto(timeString);
65
+ return firestoreInstance.snapshot_(valueProto, readTime, "json");
66
+ }
67
+ /** @internal */
68
+ function createBeforeSnapshotFromJson(data, source, createTime, updateTime, databaseId) {
69
+ if (!firestoreInstance) {
70
+ firestoreInstance = databaseId ? firestore.getFirestore(getApp(), databaseId) : firestore.getFirestore(getApp());
71
+ }
72
+ const oldValueProto = _getValueProto(data, source, "oldValue");
73
+ const oldReadTime = dateToTimestampProto(createTime || updateTime);
74
+ return firestoreInstance.snapshot_(oldValueProto, oldReadTime, "json");
75
+ }
76
+
77
+ //#endregion
78
+ export { createBeforeSnapshotFromJson, createBeforeSnapshotFromProtobuf, createSnapshotFromJson, createSnapshotFromProtobuf };
@@ -0,0 +1,573 @@
1
+ import { debug, error, warn } from "../../logger/index.mjs";
2
+ import { getApp } from "../app.mjs";
3
+ import { isDebugFeatureEnabled } from "../debug.mjs";
4
+ import { getAuth } from "firebase-admin/auth";
5
+ import cors from "cors";
6
+ import { getAppCheck } from "firebase-admin/app-check";
7
+
8
+ //#region src/common/providers/https.ts
9
+ const JWT_REGEX = /^[a-zA-Z0-9\-_=]+?\.[a-zA-Z0-9\-_=]+?\.([a-zA-Z0-9\-_=]+)?$/;
10
+ /** @internal */
11
+ const CALLABLE_AUTH_HEADER = "x-callable-context-auth";
12
+ /** @internal */
13
+ const ORIGINAL_AUTH_HEADER = "x-original-auth";
14
+ /** @internal */
15
+ const DEFAULT_HEARTBEAT_SECONDS = 30;
16
+ /**
17
+ * Standard error codes and HTTP statuses for different ways a request can fail,
18
+ * as defined by:
19
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
20
+ *
21
+ * This map is used primarily to convert from a client error code string to
22
+ * to the HTTP format error code string and status, and make sure it's in the
23
+ * supported set.
24
+ */
25
+ const errorCodeMap = {
26
+ ok: {
27
+ canonicalName: "OK",
28
+ status: 200
29
+ },
30
+ cancelled: {
31
+ canonicalName: "CANCELLED",
32
+ status: 499
33
+ },
34
+ unknown: {
35
+ canonicalName: "UNKNOWN",
36
+ status: 500
37
+ },
38
+ "invalid-argument": {
39
+ canonicalName: "INVALID_ARGUMENT",
40
+ status: 400
41
+ },
42
+ "deadline-exceeded": {
43
+ canonicalName: "DEADLINE_EXCEEDED",
44
+ status: 504
45
+ },
46
+ "not-found": {
47
+ canonicalName: "NOT_FOUND",
48
+ status: 404
49
+ },
50
+ "already-exists": {
51
+ canonicalName: "ALREADY_EXISTS",
52
+ status: 409
53
+ },
54
+ "permission-denied": {
55
+ canonicalName: "PERMISSION_DENIED",
56
+ status: 403
57
+ },
58
+ unauthenticated: {
59
+ canonicalName: "UNAUTHENTICATED",
60
+ status: 401
61
+ },
62
+ "resource-exhausted": {
63
+ canonicalName: "RESOURCE_EXHAUSTED",
64
+ status: 429
65
+ },
66
+ "failed-precondition": {
67
+ canonicalName: "FAILED_PRECONDITION",
68
+ status: 400
69
+ },
70
+ aborted: {
71
+ canonicalName: "ABORTED",
72
+ status: 409
73
+ },
74
+ "out-of-range": {
75
+ canonicalName: "OUT_OF_RANGE",
76
+ status: 400
77
+ },
78
+ unimplemented: {
79
+ canonicalName: "UNIMPLEMENTED",
80
+ status: 501
81
+ },
82
+ internal: {
83
+ canonicalName: "INTERNAL",
84
+ status: 500
85
+ },
86
+ unavailable: {
87
+ canonicalName: "UNAVAILABLE",
88
+ status: 503
89
+ },
90
+ "data-loss": {
91
+ canonicalName: "DATA_LOSS",
92
+ status: 500
93
+ }
94
+ };
95
+ /**
96
+ * An explicit error that can be thrown from a handler to send an error to the
97
+ * client that called the function.
98
+ */
99
+ var HttpsError = class extends Error {
100
+ constructor(code, message, details) {
101
+ super(message);
102
+ if (code in errorCodeMap === false) {
103
+ throw new Error(`Unknown error code: ${code}.`);
104
+ }
105
+ this.code = code;
106
+ this.details = details;
107
+ this.httpErrorCode = errorCodeMap[code];
108
+ }
109
+ /**
110
+ * Returns a JSON-serializable representation of this object.
111
+ */
112
+ toJSON() {
113
+ const { details, httpErrorCode: { canonicalName: status }, message } = this;
114
+ return {
115
+ ...details === undefined ? {} : { details },
116
+ message,
117
+ status
118
+ };
119
+ }
120
+ };
121
+ /** @hidden */
122
+ function isValidRequest(req) {
123
+ if (!req.body) {
124
+ warn("Request is missing body.");
125
+ return false;
126
+ }
127
+ if (req.method !== "POST") {
128
+ warn("Request has invalid method.", req.method);
129
+ return false;
130
+ }
131
+ let contentType = (req.header("Content-Type") || "").toLowerCase();
132
+ const semiColon = contentType.indexOf(";");
133
+ if (semiColon >= 0) {
134
+ contentType = contentType.slice(0, semiColon).trim();
135
+ }
136
+ if (contentType !== "application/json") {
137
+ warn("Request has incorrect Content-Type.", contentType);
138
+ return false;
139
+ }
140
+ if (typeof req.body.data === "undefined") {
141
+ warn("Request body is missing data.", req.body);
142
+ return false;
143
+ }
144
+ const extraKeys = Object.keys(req.body).filter((field) => field !== "data");
145
+ if (extraKeys.length !== 0) {
146
+ warn("Request body has extra fields: ", extraKeys.join(", "));
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ /** @hidden */
152
+ const LONG_TYPE = "type.googleapis.com/google.protobuf.Int64Value";
153
+ /** @hidden */
154
+ const UNSIGNED_LONG_TYPE = "type.googleapis.com/google.protobuf.UInt64Value";
155
+ /**
156
+ * Encodes arbitrary data in our special format for JSON.
157
+ * This is exposed only for testing.
158
+ */
159
+ /** @hidden */
160
+ function encode(data) {
161
+ if (data === null || typeof data === "undefined") {
162
+ return null;
163
+ }
164
+ if (data instanceof Number) {
165
+ data = data.valueOf();
166
+ }
167
+ if (Number.isFinite(data)) {
168
+ return data;
169
+ }
170
+ if (typeof data === "boolean") {
171
+ return data;
172
+ }
173
+ if (typeof data === "string") {
174
+ return data;
175
+ }
176
+ if (Array.isArray(data)) {
177
+ return data.map(encode);
178
+ }
179
+ if (typeof data === "object" || typeof data === "function") {
180
+ const obj = {};
181
+ for (const [k, v] of Object.entries(data)) {
182
+ obj[k] = encode(v);
183
+ }
184
+ return obj;
185
+ }
186
+ error("Data cannot be encoded in JSON.", data);
187
+ throw new Error(`Data cannot be encoded in JSON: ${data}`);
188
+ }
189
+ /**
190
+ * Decodes our special format for JSON into native types.
191
+ * This is exposed only for testing.
192
+ */
193
+ /** @hidden */
194
+ function decode(data) {
195
+ if (data === null) {
196
+ return data;
197
+ }
198
+ if (data["@type"]) {
199
+ switch (data["@type"]) {
200
+ case LONG_TYPE:
201
+ case UNSIGNED_LONG_TYPE: {
202
+ const value = parseFloat(data.value);
203
+ if (isNaN(value)) {
204
+ error("Data cannot be decoded from JSON.", data);
205
+ throw new Error(`Data cannot be decoded from JSON: ${data}`);
206
+ }
207
+ return value;
208
+ }
209
+ default: {
210
+ error("Data cannot be decoded from JSON.", data);
211
+ throw new Error(`Data cannot be decoded from JSON: ${data}`);
212
+ }
213
+ }
214
+ }
215
+ if (Array.isArray(data)) {
216
+ return data.map(decode);
217
+ }
218
+ if (typeof data === "object") {
219
+ const obj = {};
220
+ for (const [k, v] of Object.entries(data)) {
221
+ obj[k] = decode(v);
222
+ }
223
+ return obj;
224
+ }
225
+ return data;
226
+ }
227
+ /** @internal */
228
+ function unsafeDecodeToken(token) {
229
+ if (!JWT_REGEX.test(token)) {
230
+ return {};
231
+ }
232
+ const components = token.split(".").map((s) => Buffer.from(s, "base64").toString());
233
+ let payload = components[1];
234
+ if (typeof payload === "string") {
235
+ try {
236
+ const obj = JSON.parse(payload);
237
+ if (typeof obj === "object") {
238
+ payload = obj;
239
+ }
240
+ } catch (_e) {}
241
+ }
242
+ return payload;
243
+ }
244
+ /**
245
+ * Decode, but not verify, a Auth ID token.
246
+ *
247
+ * Do not use in production. Token should always be verified using the Admin SDK.
248
+ *
249
+ * This is exposed only for testing.
250
+ */
251
+ /** @internal */
252
+ function unsafeDecodeIdToken(token) {
253
+ const decoded = unsafeDecodeToken(token);
254
+ decoded.uid = decoded.sub;
255
+ return decoded;
256
+ }
257
+ /**
258
+ * Decode, but not verify, an App Check token.
259
+ *
260
+ * Do not use in production. Token should always be verified using the Admin SDK.
261
+ *
262
+ * This is exposed only for testing.
263
+ */
264
+ /** @internal */
265
+ function unsafeDecodeAppCheckToken(token) {
266
+ const decoded = unsafeDecodeToken(token);
267
+ decoded.app_id = decoded.sub;
268
+ return decoded;
269
+ }
270
+ /**
271
+ * Check and verify tokens included in the requests. Once verified, tokens
272
+ * are injected into the callable context.
273
+ *
274
+ * @param {Request} req - Request sent to the Callable function.
275
+ * @param {CallableContext} ctx - Context to be sent to callable function handler.
276
+ * @returns {CallableTokenStatus} Status of the token verifications.
277
+ */
278
+ /** @internal */
279
+ async function checkTokens(req, ctx, options) {
280
+ const verifications = {
281
+ app: "INVALID",
282
+ auth: "INVALID"
283
+ };
284
+ [verifications.auth, verifications.app] = await Promise.all([checkAuthToken(req, ctx), checkAppCheckToken(req, ctx, options)]);
285
+ const logPayload = {
286
+ verifications,
287
+ "logging.googleapis.com/labels": { "firebase-log-type": "callable-request-verification" }
288
+ };
289
+ const errs = [];
290
+ if (verifications.app === "INVALID") {
291
+ errs.push("AppCheck token was rejected.");
292
+ }
293
+ if (verifications.auth === "INVALID") {
294
+ errs.push("Auth token was rejected.");
295
+ }
296
+ if (errs.length === 0) {
297
+ debug("Callable request verification passed", logPayload);
298
+ } else {
299
+ warn(`Callable request verification failed: ${errs.join(" ")}`, logPayload);
300
+ }
301
+ return verifications;
302
+ }
303
+ /** @interanl */
304
+ async function checkAuthToken(req, ctx) {
305
+ const authorization = req.header("Authorization");
306
+ if (!authorization) {
307
+ return "MISSING";
308
+ }
309
+ const match = authorization.match(/^Bearer (.*)$/i);
310
+ if (!match) {
311
+ return "INVALID";
312
+ }
313
+ const idToken = match[1];
314
+ try {
315
+ let authToken;
316
+ if (isDebugFeatureEnabled("skipTokenVerification")) {
317
+ authToken = unsafeDecodeIdToken(idToken);
318
+ } else {
319
+ authToken = await getAuth(getApp()).verifyIdToken(idToken);
320
+ }
321
+ ctx.auth = {
322
+ uid: authToken.uid,
323
+ token: authToken,
324
+ rawToken: idToken
325
+ };
326
+ return "VALID";
327
+ } catch (err) {
328
+ warn("Failed to validate auth token.", err);
329
+ return "INVALID";
330
+ }
331
+ }
332
+ /** @internal */
333
+ async function checkAppCheckToken(req, ctx, options) {
334
+ const appCheckToken = req.header("X-Firebase-AppCheck");
335
+ if (!appCheckToken) {
336
+ return "MISSING";
337
+ }
338
+ try {
339
+ let appCheckData;
340
+ if (isDebugFeatureEnabled("skipTokenVerification")) {
341
+ const decodedToken = unsafeDecodeAppCheckToken(appCheckToken);
342
+ appCheckData = {
343
+ appId: decodedToken.app_id,
344
+ token: decodedToken
345
+ };
346
+ if (options.consumeAppCheckToken) {
347
+ appCheckData.alreadyConsumed = false;
348
+ }
349
+ } else {
350
+ const appCheck = getAppCheck(getApp());
351
+ if (options.consumeAppCheckToken) {
352
+ if (appCheck.verifyToken?.length === 1) {
353
+ const errorMsg = "Unsupported version of the Admin SDK." + " App Check token will not be consumed." + " Please upgrade the firebase-admin to the latest version.";
354
+ error(errorMsg);
355
+ throw new HttpsError("internal", "Internal Error");
356
+ }
357
+ appCheckData = await getAppCheck(getApp()).verifyToken(appCheckToken, { consume: true });
358
+ } else {
359
+ appCheckData = await getAppCheck(getApp()).verifyToken(appCheckToken);
360
+ }
361
+ }
362
+ ctx.app = appCheckData;
363
+ return "VALID";
364
+ } catch (err) {
365
+ warn("Failed to validate AppCheck token.", err);
366
+ if (err instanceof HttpsError) {
367
+ throw err;
368
+ }
369
+ return "INVALID";
370
+ }
371
+ }
372
+ /** @internal */
373
+ function onCallHandler(options, handler, version) {
374
+ const wrapped = wrapOnCallHandler(options, handler, version);
375
+ return (req, res) => {
376
+ return new Promise((resolve) => {
377
+ res.on("finish", resolve);
378
+ cors(options.cors)(req, res, () => {
379
+ resolve(wrapped(req, res));
380
+ });
381
+ });
382
+ };
383
+ }
384
+ function encodeSSE(data) {
385
+ return `data: ${JSON.stringify(data)}\n\n`;
386
+ }
387
+ /** @internal */
388
+ function wrapOnCallHandler(options, handler, version) {
389
+ return async (req, res) => {
390
+ const abortController = new AbortController();
391
+ let heartbeatInterval = null;
392
+ const heartbeatSeconds = options.heartbeatSeconds === undefined ? DEFAULT_HEARTBEAT_SECONDS : options.heartbeatSeconds;
393
+ const clearScheduledHeartbeat = () => {
394
+ if (heartbeatInterval) {
395
+ clearTimeout(heartbeatInterval);
396
+ heartbeatInterval = null;
397
+ }
398
+ };
399
+ const scheduleHeartbeat = () => {
400
+ clearScheduledHeartbeat();
401
+ if (!abortController.signal.aborted) {
402
+ heartbeatInterval = setTimeout(() => {
403
+ if (!abortController.signal.aborted) {
404
+ res.write(": ping\n\n");
405
+ scheduleHeartbeat();
406
+ }
407
+ }, heartbeatSeconds * 1e3);
408
+ }
409
+ };
410
+ res.on("close", () => {
411
+ clearScheduledHeartbeat();
412
+ abortController.abort();
413
+ });
414
+ try {
415
+ if (!isValidRequest(req)) {
416
+ error("Invalid request, unable to process.");
417
+ throw new HttpsError("invalid-argument", "Bad Request");
418
+ }
419
+ const context = { rawRequest: req };
420
+ if (isDebugFeatureEnabled("skipTokenVerification") && version === "gcfv1") {
421
+ const authContext = context.rawRequest.header(CALLABLE_AUTH_HEADER);
422
+ if (authContext) {
423
+ debug("Callable functions auth override", {
424
+ key: CALLABLE_AUTH_HEADER,
425
+ value: authContext
426
+ });
427
+ context.auth = JSON.parse(decodeURIComponent(authContext));
428
+ delete context.rawRequest.headers[CALLABLE_AUTH_HEADER];
429
+ }
430
+ const originalAuth = context.rawRequest.header(ORIGINAL_AUTH_HEADER);
431
+ if (originalAuth) {
432
+ context.rawRequest.headers["authorization"] = originalAuth;
433
+ delete context.rawRequest.headers[ORIGINAL_AUTH_HEADER];
434
+ }
435
+ }
436
+ const tokenStatus = await checkTokens(req, context, options);
437
+ if (tokenStatus.auth === "INVALID") {
438
+ throw new HttpsError("unauthenticated", "Unauthenticated");
439
+ }
440
+ if (tokenStatus.app === "INVALID") {
441
+ if (options.enforceAppCheck) {
442
+ throw new HttpsError("unauthenticated", "Unauthenticated");
443
+ } else {
444
+ warn("Allowing request with invalid AppCheck token because enforcement is disabled");
445
+ }
446
+ }
447
+ if (tokenStatus.app === "MISSING" && options.enforceAppCheck) {
448
+ throw new HttpsError("unauthenticated", "Unauthenticated");
449
+ }
450
+ const instanceId = req.header("Firebase-Instance-ID-Token");
451
+ if (instanceId) {
452
+ context.instanceIdToken = req.header("Firebase-Instance-ID-Token");
453
+ }
454
+ const acceptsStreaming = req.header("accept") === "text/event-stream";
455
+ if (acceptsStreaming && version === "gcfv1") {
456
+ throw new HttpsError("invalid-argument", "Unsupported Accept header 'text/event-stream'");
457
+ }
458
+ const data = decode(req.body.data);
459
+ if (options.authPolicy) {
460
+ const authorized = await options.authPolicy(context.auth ?? null, data);
461
+ if (!authorized) {
462
+ throw new HttpsError("permission-denied", "Permission Denied");
463
+ }
464
+ }
465
+ let result;
466
+ if (version === "gcfv1") {
467
+ result = await handler(data, context);
468
+ } else {
469
+ const arg = {
470
+ ...context,
471
+ data,
472
+ acceptsStreaming
473
+ };
474
+ const responseProxy = {
475
+ sendChunk(chunk) {
476
+ if (!acceptsStreaming) {
477
+ return Promise.resolve(false);
478
+ }
479
+ if (abortController.signal.aborted) {
480
+ return Promise.resolve(false);
481
+ }
482
+ const formattedData = encodeSSE({ message: chunk });
483
+ let resolve;
484
+ let reject;
485
+ const p = new Promise((res$1, rej) => {
486
+ resolve = res$1;
487
+ reject = rej;
488
+ });
489
+ const wrote = res.write(formattedData, (error$1) => {
490
+ if (error$1) {
491
+ reject(error$1);
492
+ return;
493
+ }
494
+ resolve(wrote);
495
+ });
496
+ if (wrote && heartbeatInterval !== null && heartbeatSeconds > 0) {
497
+ scheduleHeartbeat();
498
+ }
499
+ return p;
500
+ },
501
+ signal: abortController.signal
502
+ };
503
+ if (acceptsStreaming) {
504
+ res.status(200);
505
+ if (heartbeatSeconds !== null && heartbeatSeconds > 0) {
506
+ scheduleHeartbeat();
507
+ }
508
+ }
509
+ result = await handler(arg, responseProxy);
510
+ clearScheduledHeartbeat();
511
+ }
512
+ if (!abortController.signal.aborted) {
513
+ result = encode(result);
514
+ const responseBody = { result };
515
+ if (acceptsStreaming) {
516
+ res.write(encodeSSE(responseBody));
517
+ res.end();
518
+ } else {
519
+ res.status(200).send(responseBody);
520
+ }
521
+ } else {
522
+ res.end();
523
+ }
524
+ } catch (err) {
525
+ if (!abortController.signal.aborted) {
526
+ let httpErr = err;
527
+ if (!(err instanceof HttpsError)) {
528
+ error("Unhandled error", err);
529
+ httpErr = new HttpsError("internal", "INTERNAL");
530
+ }
531
+ const { status } = httpErr.httpErrorCode;
532
+ const body = { error: httpErr.toJSON() };
533
+ if (version === "gcfv2" && req.header("accept") === "text/event-stream") {
534
+ res.write(encodeSSE(body));
535
+ res.end();
536
+ } else {
537
+ res.status(status).send(body);
538
+ }
539
+ } else {
540
+ res.end();
541
+ }
542
+ } finally {
543
+ clearScheduledHeartbeat();
544
+ }
545
+ };
546
+ }
547
+ /**
548
+ * Wraps an HTTP handler with a safety net for unhandled errors.
549
+ *
550
+ * This wrapper catches both synchronous errors and rejected Promises from `async` handlers.
551
+ * Without this, an unhandled error in an `async` handler would cause the request to hang
552
+ * until the platform timeout, as Express (v4) does not await handlers.
553
+ *
554
+ * It logs the error and returns a 500 Internal Server Error to the client if the response
555
+ * headers have not yet been sent.
556
+ *
557
+ * @internal
558
+ */
559
+ function withErrorHandler(handler) {
560
+ return async (req, res) => {
561
+ try {
562
+ await handler(req, res);
563
+ } catch (err) {
564
+ error("Unhandled error", err);
565
+ if (!res.headersSent) {
566
+ res.status(500).send("Internal Server Error");
567
+ }
568
+ }
569
+ };
570
+ }
571
+
572
+ //#endregion
573
+ export { CALLABLE_AUTH_HEADER, DEFAULT_HEARTBEAT_SECONDS, HttpsError, ORIGINAL_AUTH_HEADER, checkAuthToken, decode, encode, isValidRequest, onCallHandler, unsafeDecodeAppCheckToken, unsafeDecodeIdToken, unsafeDecodeToken, withErrorHandler };