firebase-functions 6.4.0 → 6.6.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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "description": "Use this library to build serverless functions for event triggers and HTTP using Cloud Functions for Firebase",
3
+ "mcpServers": {
4
+ "firebase": {
5
+ "command": "npx",
6
+ "args": ["-y", "firebase-tools@latest", "experimental:mcp"]
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,69 @@
1
+ # Cloud Functions for Firebase setup guide
2
+
3
+ This guide provides a step-by-step process for setting up Cloud Functions, tailored for coding agents.
4
+
5
+ ## 1. Create a 2nd-gen HTTP function
6
+
7
+ Replace the contents of `src/index.ts` (or `index.js`) with the following code to create a simple, modern v2 HTTP endpoint.
8
+
9
+ ```typescript
10
+ import { onRequest } from "firebase-functions/https";
11
+ import { onDocumentCreated } from "firebase-functions/firestore";
12
+ import * as logger from "firebase-functions/logger";
13
+ import { defineString, defineInt } from "firebase-functions/params";
14
+
15
+ // Configurable parameters
16
+ const scaleLimit = defineInt("MAX_INSTANCES", { default: 1 });
17
+ const greeting = defineString("GREETING", { default: "Hello" });
18
+
19
+ export const helloWorld = onRequest({ maxInstances: scaleLimit }, async (request, response) => {
20
+ logger.info("Request received!", request);
21
+ response.send(`${greeting.value()} from Firebase!`);
22
+ });
23
+
24
+ export const newDoc = onDocumentCreated(
25
+ { maxInstances: scaleLimit },
26
+ "/words/{wordId}",
27
+ async (event) => {
28
+ logger.info("New word!", event.data.data());
29
+ }
30
+ );
31
+ ```
32
+
33
+ **Key points for the agent:**
34
+
35
+ - Use `logger` for structured logging.
36
+ - Use `defineString`, `defineInt`, `defineSecret` for environment configuration instead of `functions.config()`.
37
+ - Recommend a `maxInstances` setting of 1 for all functions until correct behavior can be verified.
38
+
39
+ ## 2. Build TypeScript
40
+
41
+ Compile your TypeScript code to JavaScript.
42
+
43
+ ```bash
44
+ npm run build
45
+ ```
46
+
47
+ ## 3. Local Development and Testing
48
+
49
+ Use the Firebase Emulators to test your function locally before deploying.
50
+
51
+ A human should run the following command in a separate terminal window to start the emulators:
52
+
53
+ ```bash
54
+ # Start the functions emulator
55
+ firebase emulators:start --only functions
56
+ ```
57
+
58
+ A human can then interact with the function at the local URL provided by the emulator.
59
+
60
+ ## 4. Deploy to Firebase
61
+
62
+ Once testing is complete, deploy the function to your Firebase project.
63
+
64
+ ```bash
65
+ # Deploy only the functions
66
+ firebase deploy --only functions
67
+ ```
68
+
69
+ The agent will be prompted to set any parameters defined with `defineString` or other `define` functions that do not have a default value.
@@ -0,0 +1,178 @@
1
+ # Upgrading a 1st gen to 2nd gen
2
+
3
+ This guide provides a step-by-step process for migrating a single Cloud Function from 1st to 2nd generation. Migrate functions one-by-one. Run both generations side-by-side before deleting the 1st gen function.
4
+
5
+ ## 1. Identify a 1st-gen function to upgrade
6
+
7
+ Find all 1st-gen functions in the directory. 1st-gen functions used a namespaced API like this:
8
+
9
+ **Before (1st Gen):**
10
+
11
+ ```typescript
12
+ import * as functions from "firebase-functions";
13
+
14
+ export const webhook = functions.https.onRequest((request, response) => {
15
+ // ...
16
+ });
17
+ ```
18
+
19
+ Sometimes, they'll explicitly import from the `firebase-functions/v1` subpackage, but not always.
20
+
21
+ Ask the human to pick a **single** function to upgrade from the list of 1st gen functions you found.
22
+
23
+ ## 2. Update Dependencies
24
+
25
+ Ensure your `firebase-functions` and `firebase-admin` SDKs are up-to-date, and you are using a recent version of the Firebase CLI.
26
+
27
+ ## 3. Modify Imports
28
+
29
+ Update your import statements to use the top-level modules.
30
+
31
+ **After (2nd Gen):**
32
+
33
+ ```typescript
34
+ import { onRequest } from "firebase-functions/https";
35
+ ```
36
+
37
+ ## 4. Update Trigger Definition
38
+
39
+ The SDK is now more modular. Update your trigger definition accordingly.
40
+
41
+ **After (2nd Gen):**
42
+
43
+ ```typescript
44
+ export const webhook = onRequest((request, response) => {
45
+ // ...
46
+ });
47
+ ```
48
+
49
+ Here are other examples of trigger changes:
50
+
51
+ ### Callable Triggers
52
+
53
+ **Before (1st Gen):**
54
+
55
+ ```typescript
56
+ export const getprofile = functions.https.onCall((data, context) => {
57
+ // ...
58
+ });
59
+ ```
60
+
61
+ **After (2nd Gen):**
62
+
63
+ ```typescript
64
+ import { onCall } from "firebase-functions/https";
65
+
66
+ export const getprofile = onCall((request) => {
67
+ // ...
68
+ });
69
+ ```
70
+
71
+ ### Background Triggers (Pub/Sub)
72
+
73
+ **Before (1st Gen):**
74
+
75
+ ```typescript
76
+ export const hellopubsub = functions.pubsub.topic("topic-name").onPublish((message) => {
77
+ // ...
78
+ });
79
+ ```
80
+
81
+ **After (2nd Gen):**
82
+
83
+ ```typescript
84
+ import { onMessagePublished } from "firebase-functions/pubsub";
85
+
86
+ export const hellopubsub = onMessagePublished("topic-name", (event) => {
87
+ // ...
88
+ });
89
+ ```
90
+
91
+ ## 5. Use Parameterized Configuration
92
+
93
+ Migrate from `functions.config()` to the new `params` module for environment configuration.
94
+
95
+ **Before (`.runtimeconfig.json`):**
96
+
97
+ ```json
98
+ {
99
+ "someservice": {
100
+ "key": "somesecret"
101
+ }
102
+ }
103
+ ```
104
+
105
+ **And in code (1st Gen):**
106
+
107
+ ```typescript
108
+ const SKEY = functions.config().someservice.key;
109
+ ```
110
+
111
+ **After (2nd Gen):**
112
+ Define params in your code and set their values during deployment.
113
+
114
+ **In `index.ts`:**
115
+
116
+ ```typescript
117
+ import { defineString } from "firebase-functions/params";
118
+
119
+ const SOMESERVICE_KEY = defineString("SOMESERVICE_KEY");
120
+ ```
121
+
122
+ Use `SOMESERVICE_KEY.value()` to access the value. For secrets like API keys, use `defineSecret`.
123
+
124
+ **In `index.ts`:**
125
+
126
+ ```typescript
127
+ import { defineSecret } from "firebase-functions/params";
128
+
129
+ const SOMESERVICE_KEY = defineSecret("SOMESERVICE_KEY");
130
+ ```
131
+
132
+ The human will be prompted to set the value on deployment. The value will be stored securely in Cloud Secret Manager.
133
+
134
+ ## 6. Update Runtime Options
135
+
136
+ Runtime options are now set directly within the function definition.
137
+
138
+ **Before (1st Gen):**
139
+
140
+ ```typescript
141
+ export const func = functions
142
+ .runWith({
143
+ // Keep 5 instances warm
144
+ minInstances: 5,
145
+ })
146
+ .https.onRequest((request, response) => {
147
+ // ...
148
+ });
149
+ ```
150
+
151
+ **After (2nd Gen):**
152
+
153
+ ```typescript
154
+ import { onRequest } from "firebase-functions/https";
155
+
156
+ export const func = onRequest(
157
+ {
158
+ // Keep 5 instances warm
159
+ minInstances: 5,
160
+ },
161
+ (request, response) => {
162
+ // ...
163
+ }
164
+ );
165
+ ```
166
+
167
+ ## 7. Traffic Migration
168
+
169
+ A human should follow these steps to migrate safely:
170
+
171
+ > To migrate traffic safely:
172
+ >
173
+ > 1. Rename your new 2nd gen function with a different name.
174
+ > 2. Comment out any existing `minInstances` or `maxInstances` config in the new 2nd gen function and instead set `maxInstances` to `1` while testing.
175
+ > 3. Deploy it alongside the old 1st gen function.
176
+ > 4. Gradually introduce traffic to the new function (e.g., via client-side changes or by calling it from the 1st gen function).
177
+ > 5. As traffic ramps up to the new 2nd gen function, scale it up by adding back the original `minInstances` and `maxInstances` settings to the 2nd gen function. Reduce the `minInstances` and `maxInstances` settings for the 1st gen function as traffic decreases.
178
+ > 6. The 1st gen function can be deleted once it has stopped receiving traffic.
@@ -0,0 +1,116 @@
1
+ ## Key Guidelines
2
+
3
+ - Always use 2nd-gen functions for new development.
4
+ - Use 1st-gen functions _only_ for Analytics and basic Auth triggers, since those aren't supported by 2nd gen.
5
+ - Use `firebase-functions` SDK version 6.0.0 and above
6
+ - Use top-level imports (e.g., `firebase-functions/https`). These are 2nd gen by default. If 1st gen is required (Analytics or basic Auth triggers), import from the `firebase-functions/v1` import path.
7
+
8
+ ## Configuration: Use Secret Params for API Keys
9
+
10
+ For sensitive information like API keys (e.g., for LLMs, payment providers, etc.), **always** use `defineSecret`. This stores the value securely in Cloud Secret Manager.
11
+
12
+ ```typescript
13
+ import { onRequest } from "firebase-functions/https";
14
+ import { logger } from "firebase-functions/logger";
15
+ import { defineString, defineSecret } from "firebase-functions/params";
16
+
17
+ // Securely define an LLM API key
18
+ const LLM_API_KEY = defineSecret("LLM_API_KEY");
19
+
20
+ // Example function that uses the secret
21
+ export const callLlm = onRequest({ secrets: [LLM_API_KEY] }, async (req, res) => {
22
+ const apiKey = LLM_API_KEY.value();
23
+
24
+ // Use the apiKey to make a call to the LLM service
25
+ logger.info("Calling LLM with API key.");
26
+
27
+ // insert code here to call LLM...
28
+
29
+ res.send("LLM API call initiated.");
30
+ });
31
+ ```
32
+
33
+ The CLI will prompt for secret's value at deploy time. Alternatively, a human can set the secret using the Firebase CLI command:
34
+
35
+ ```bash
36
+ firebase functions:secrets:set <SECRET_NAME>
37
+ ```
38
+
39
+ If you see an API key being accessed with `functions.config` in existing functions code, offer to upgrade to params.
40
+
41
+ ## Use the Firebase Admin SDK
42
+
43
+ To interact with Firebase services like Firestore, Auth, or RTDB from within your functions, you need to initialize the Firebase Admin SDK. Call `initializeApp` without any arguments so that Application Default Credentials are used.
44
+
45
+ 1. **Install the SDK:**
46
+
47
+ ```bash
48
+ npm i firebase-admin
49
+ ```
50
+
51
+ 2. **Initialize in your code:**
52
+
53
+ ```typescript
54
+ import * as admin from "firebase-admin";
55
+ import { onInit } from "firebase-functions";
56
+
57
+ onInit(() => {
58
+ admin.initializeApp();
59
+ });
60
+ ```
61
+
62
+ This should be done once at the top level of your `index.ts` file.
63
+
64
+ ## Common Imports
65
+
66
+ ```typescript
67
+ import { onRequest, onCall, onCallGenkit } from "firebase-functions/https";
68
+ import { onDocumentUpdated } from "firebase-functions/firestore";
69
+ import { onNewFatalIssuePublished } from "firebase-functions/alerts/crashlytics";
70
+ import { onValueWritten } from "firebase-functions/database";
71
+ import { onSchedule } from "firebase-functions/scheduler";
72
+ const { onTaskDispatched } = require("firebase-functions/tasks");
73
+ import { onObjectFinalized } from "firebase-functions/storage";
74
+ import { onMessagePublished } from "firebase-functions/pubsub";
75
+ import { beforeUserSignedIn } from "firebase-functions/identity";
76
+ import { onTestMatrixCompleted } from "firebase-functions/testLab";
77
+ import { logger, onInit } from "firebase-functions";
78
+ import { defineString, defineSecret } from "firebase-functions/params";
79
+ ```
80
+
81
+ A human can find code samples for these triggers in the [functions-samples repository](https://github.com/firebase/functions-samples/tree/main/Node).
82
+
83
+ ## 1st-gen Functions (Legacy Triggers)
84
+
85
+ Use the `firebase-functions/v1` import for Analytics and basic Auth triggers. These aren't supported in 2nd gen.
86
+
87
+ ```typescript
88
+ import * as functionsV1 from "firebase-functions/v1";
89
+
90
+ // v1 Analytics trigger
91
+ export const onPurchase = functionsV1.analytics.event("purchase").onLog(async (event) => {
92
+ logger.info("Purchase event", { value: event.params?.value });
93
+ });
94
+
95
+ // v1 Auth trigger
96
+ export const onUserCreate = functionsV1.auth.user().onCreate(async (user) => {
97
+ logger.info("User created", { uid: user.uid });
98
+ });
99
+ ```
100
+
101
+ ## Development Commands
102
+
103
+ ```bash
104
+ # Install dependencies
105
+ npm install
106
+
107
+ # Compile TypeScript
108
+ npm run build
109
+
110
+ # Run emulators for local development
111
+ # This is a long-running command. A human can run this command themselves to start the emulators:
112
+ firebase emulators:start --only functions
113
+
114
+ # Deploy functions
115
+ firebase deploy --only functions
116
+ ```
package/README.md CHANGED
@@ -10,12 +10,12 @@ Learn more about the Firebase SDK for Cloud Functions in the [Firebase documenta
10
10
 
11
11
  Here are some resources to get help:
12
12
 
13
- - Start with the quickstart: https://firebase.google.com/docs/functions/write-firebase-functions
14
- - Go through the guide: https://firebase.google.com/docs/functions/
15
- - Read the full API reference: https://firebase.google.com/docs/reference/functions/
16
- - Browse some examples: https://github.com/firebase/functions-samples
13
+ - [Start with the quickstart](https://firebase.google.com/docs/functions/write-firebase-functions)
14
+ - [Go through the guides](https://firebase.google.com/docs/functions/)
15
+ - [Read the full API reference](https://firebase.google.com/docs/reference/functions/2nd-gen/node/firebase-functions)
16
+ - [Browse some examples](https://github.com/firebase/functions-samples)
17
17
 
18
- If the official documentation doesn't help, try asking through our official support channels: https://firebase.google.com/support/
18
+ If the official documentation doesn't help, try asking through our [official support channels](https://firebase.google.com/support/)
19
19
 
20
20
  _Please avoid double posting across multiple channels!_
21
21
 
@@ -104,7 +104,7 @@ class DataSnapshot {
104
104
  }
105
105
  if (parts.length) {
106
106
  for (const part of parts) {
107
- if (source[part] === undefined) {
107
+ if (typeof source === "undefined" || source === null) {
108
108
  return null;
109
109
  }
110
110
  source = source[part];
@@ -36,8 +36,12 @@ export interface AppCheckData {
36
36
  * The interface for Auth tokens verified in Callable functions
37
37
  */
38
38
  export interface AuthData {
39
+ /** The user's uid from the request's ID token. */
39
40
  uid: string;
41
+ /** The decoded claims of the ID token after verification. */
40
42
  token: DecodedIdToken;
43
+ /** The raw ID token as parsed from the header. */
44
+ rawToken: string;
41
45
  }
42
46
  /**
43
47
  * The interface for metadata for the API as passed to the handler.
@@ -330,6 +330,7 @@ async function checkAuthToken(req, ctx) {
330
330
  ctx.auth = {
331
331
  uid: authToken.uid,
332
332
  token: authToken,
333
+ rawToken: idToken,
333
334
  };
334
335
  return "VALID";
335
336
  }
@@ -46,6 +46,7 @@ export interface RateLimits {
46
46
  export interface AuthData {
47
47
  uid: string;
48
48
  token: DecodedIdToken;
49
+ rawToken: string;
49
50
  }
50
51
  /** Metadata about a call to a Task Queue function. */
51
52
  export interface TaskContext {
@@ -67,6 +67,7 @@ function onDispatchHandler(handler) {
67
67
  context.auth = {
68
68
  uid: authToken.uid,
69
69
  token: authToken,
70
+ rawToken: token,
70
71
  };
71
72
  }
72
73
  const data = https.decode(req.body.data);
@@ -26,19 +26,19 @@ const util_1 = require("util");
26
26
  const trace_1 = require("../common/trace");
27
27
  const common_1 = require("./common");
28
28
  /** @internal */
29
- function removeCircular(obj, refs = []) {
29
+ function removeCircular(obj, refs = new Set()) {
30
30
  if (typeof obj !== "object" || !obj) {
31
31
  return obj;
32
32
  }
33
33
  // If the object defines its own toJSON, prefer that.
34
- if (obj.toJSON) {
34
+ if (obj.toJSON && typeof obj.toJSON === "function") {
35
35
  return obj.toJSON();
36
36
  }
37
- if (refs.includes(obj)) {
37
+ if (refs.has(obj)) {
38
38
  return "[Circular]";
39
39
  }
40
40
  else {
41
- refs.push(obj);
41
+ refs.add(obj);
42
42
  }
43
43
  let returnObj;
44
44
  if (Array.isArray(obj)) {
@@ -48,14 +48,24 @@ function removeCircular(obj, refs = []) {
48
48
  returnObj = {};
49
49
  }
50
50
  for (const k in obj) {
51
- if (refs.includes(obj[k])) {
52
- returnObj[k] = "[Circular]";
51
+ if (obj.hasOwnProperty(k)) {
52
+ try {
53
+ if (refs.has(obj[k])) {
54
+ returnObj[k] = "[Circular]";
55
+ }
56
+ else {
57
+ returnObj[k] = removeCircular(obj[k], refs);
58
+ }
59
+ }
60
+ catch {
61
+ returnObj[k] = "[Error - cannot serialize]";
62
+ }
53
63
  }
54
64
  else {
55
- returnObj[k] = removeCircular(obj[k], refs);
65
+ returnObj[k] = "[Error - defined in the prototype but missing in the object]";
56
66
  }
57
67
  }
58
- refs.pop();
68
+ refs.delete(obj);
59
69
  return returnObj;
60
70
  }
61
71
  /**
@@ -2,10 +2,10 @@
2
2
  * @hidden
3
3
  * @alpha
4
4
  */
5
- import { BooleanParam, Expression, IntParam, Param, ParamOptions, SecretParam, StringParam, ListParam } from "./types";
5
+ import { BooleanParam, Expression, IntParam, Param, ParamOptions, SecretParam, JsonSecretParam, StringParam, ListParam } from "./types";
6
6
  export { BUCKET_PICKER, TextInput, SelectInput, SelectOptions, MultiSelectInput, select, multiSelect, } from "./types";
7
7
  export { ParamOptions, Expression };
8
- type SecretOrExpr = Param<any> | SecretParam;
8
+ type SecretOrExpr = Param<any> | SecretParam | JsonSecretParam<any>;
9
9
  export declare const declaredParams: SecretOrExpr[];
10
10
  /**
11
11
  * A built-in parameter that resolves to the default RTDB database URL associated
@@ -37,6 +37,19 @@ export declare const storageBucket: Param<string>;
37
37
  * @returns A parameter with a `string` return type for `.value`.
38
38
  */
39
39
  export declare function defineSecret(name: string): SecretParam;
40
+ /**
41
+ * Declares a secret parameter that retrieves a structured JSON object in Cloud Secret Manager.
42
+ * This is useful for managing groups of related configuration values, such as all settings
43
+ * for a third-party API, as a single unit.
44
+ *
45
+ * The secret value must be a valid JSON string. At runtime, the value will be automatically parsed
46
+ * and returned as a JavaScript object. If the value is not set or is not valid JSON, an error will be thrown.
47
+ *
48
+ * @param name The name of the environment variable to use to load the parameter.
49
+ * @returns A parameter whose `.value()` method returns the parsed JSON object.
50
+ * ```
51
+ */
52
+ export declare function defineJsonSecret<T = any>(name: string): JsonSecretParam<T>;
40
53
  /**
41
54
  * Declare a string parameter.
42
55
  *
@@ -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.defineList = exports.defineFloat = exports.defineInt = exports.defineBoolean = exports.defineString = exports.defineSecret = exports.storageBucket = exports.gcloudProject = exports.projectID = exports.databaseURL = exports.clearParams = exports.declaredParams = exports.Expression = exports.multiSelect = exports.select = exports.BUCKET_PICKER = void 0;
24
+ exports.defineList = exports.defineFloat = exports.defineInt = exports.defineBoolean = exports.defineString = exports.defineJsonSecret = exports.defineSecret = exports.storageBucket = exports.gcloudProject = exports.projectID = exports.databaseURL = exports.clearParams = exports.declaredParams = exports.Expression = exports.multiSelect = exports.select = exports.BUCKET_PICKER = void 0;
25
25
  /**
26
26
  * @hidden
27
27
  * @alpha
@@ -89,6 +89,24 @@ function defineSecret(name) {
89
89
  return param;
90
90
  }
91
91
  exports.defineSecret = defineSecret;
92
+ /**
93
+ * Declares a secret parameter that retrieves a structured JSON object in Cloud Secret Manager.
94
+ * This is useful for managing groups of related configuration values, such as all settings
95
+ * for a third-party API, as a single unit.
96
+ *
97
+ * The secret value must be a valid JSON string. At runtime, the value will be automatically parsed
98
+ * and returned as a JavaScript object. If the value is not set or is not valid JSON, an error will be thrown.
99
+ *
100
+ * @param name The name of the environment variable to use to load the parameter.
101
+ * @returns A parameter whose `.value()` method returns the parsed JSON object.
102
+ * ```
103
+ */
104
+ function defineJsonSecret(name) {
105
+ const param = new types_1.JsonSecretParam(name);
106
+ registerParam(param);
107
+ return param;
108
+ }
109
+ exports.defineJsonSecret = defineJsonSecret;
92
110
  /**
93
111
  * Declare a string parameter.
94
112
  *
@@ -114,6 +114,8 @@ export type ParamSpec<T extends string | number | boolean | string[]> = {
114
114
  description?: string;
115
115
  /** The way in which the Firebase CLI will prompt for the value of this parameter. Defaults to a TextInput. */
116
116
  input?: ParamInput<T>;
117
+ /** Optional format annotation for additional type information (e.g., "json" for JSON-encoded secrets). */
118
+ format?: string;
117
119
  };
118
120
  /**
119
121
  * Representation of parameters for the stack over the wire.
@@ -130,6 +132,7 @@ export type WireParamSpec<T extends string | number | boolean | string[]> = {
130
132
  description?: string;
131
133
  type: ParamValueType;
132
134
  input?: ParamInput<T>;
135
+ format?: string;
133
136
  };
134
137
  /** Configuration options which can be used to customize the prompting behavior of a parameter. */
135
138
  export type ParamOptions<T extends string | number | boolean | string[]> = Omit<ParamSpec<T>, "name" | "type">;
@@ -177,6 +180,20 @@ export declare class SecretParam {
177
180
  /** Returns the secret's value at runtime. Throws an error if accessed during deployment. */
178
181
  value(): string;
179
182
  }
183
+ /**
184
+ * A parametrized object whose value is stored as a JSON string in Cloud Secret Manager.
185
+ * This is useful for managing groups of related configuration values, such as all settings
186
+ * for a third-party API, as a single unit. Supply instances of JsonSecretParam to the
187
+ * secrets array while defining a Function to make their values accessible during execution
188
+ * of that Function.
189
+ */
190
+ export declare class JsonSecretParam<T = any> {
191
+ static type: ParamValueType;
192
+ name: string;
193
+ constructor(name: string);
194
+ /** Returns the secret's parsed JSON value at runtime. Throws an error if accessed during deployment, if the secret is not set, or if the value is not valid JSON. */
195
+ value(): T;
196
+ }
180
197
  /**
181
198
  * A parametrized value of String type that will be read from .env files
182
199
  * if present, or prompted for by the CLI if missing.
@@ -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.ListParam = exports.BooleanParam = exports.FloatParam = exports.IntParam = exports.InternalExpression = exports.StringParam = exports.SecretParam = exports.Param = exports.BUCKET_PICKER = exports.multiSelect = exports.select = exports.CompareExpression = exports.TernaryExpression = exports.Expression = void 0;
24
+ exports.ListParam = exports.BooleanParam = exports.FloatParam = exports.IntParam = exports.InternalExpression = exports.StringParam = exports.JsonSecretParam = exports.SecretParam = exports.Param = exports.BUCKET_PICKER = exports.multiSelect = exports.select = exports.CompareExpression = exports.TernaryExpression = exports.Expression = void 0;
25
25
  const logger = require("../logger");
26
26
  /*
27
27
  * A CEL expression which can be evaluated during function deployment, and
@@ -293,6 +293,48 @@ class SecretParam {
293
293
  }
294
294
  exports.SecretParam = SecretParam;
295
295
  SecretParam.type = "secret";
296
+ /**
297
+ * A parametrized object whose value is stored as a JSON string in Cloud Secret Manager.
298
+ * This is useful for managing groups of related configuration values, such as all settings
299
+ * for a third-party API, as a single unit. Supply instances of JsonSecretParam to the
300
+ * secrets array while defining a Function to make their values accessible during execution
301
+ * of that Function.
302
+ */
303
+ class JsonSecretParam {
304
+ constructor(name) {
305
+ this.name = name;
306
+ }
307
+ /** @internal */
308
+ runtimeValue() {
309
+ const val = process.env[this.name];
310
+ if (val === undefined) {
311
+ throw new Error(`No value found for secret parameter "${this.name}". A function can only access a secret if you include the secret in the function's dependency array.`);
312
+ }
313
+ try {
314
+ return JSON.parse(val);
315
+ }
316
+ catch (error) {
317
+ throw new Error(`"${this.name}" could not be parsed as JSON. Please verify its value in Secret Manager. Details: ${error}`);
318
+ }
319
+ }
320
+ /** @internal */
321
+ toSpec() {
322
+ return {
323
+ type: "secret",
324
+ name: this.name,
325
+ format: "json",
326
+ };
327
+ }
328
+ /** Returns the secret's parsed JSON value at runtime. Throws an error if accessed during deployment, if the secret is not set, or if the value is not valid JSON. */
329
+ value() {
330
+ if (process.env.FUNCTIONS_CONTROL_API === "true") {
331
+ throw new Error(`Cannot access the value of secret "${this.name}" during function deployment. Secret values are only available at runtime.`);
332
+ }
333
+ return this.runtimeValue();
334
+ }
335
+ }
336
+ exports.JsonSecretParam = JsonSecretParam;
337
+ JsonSecretParam.type = "secret";
296
338
  /**
297
339
  * A parametrized value of String type that will be read from .env files
298
340
  * if present, or prompted for by the CLI if missing.
@@ -57,6 +57,8 @@ export interface EventContext<Params = Record<string, string>> {
57
57
  auth?: {
58
58
  uid: string;
59
59
  token: EventContextAuthToken;
60
+ /** If available, the unparsed ID token. */
61
+ rawToken?: string;
60
62
  };
61
63
  /**
62
64
  * The level of permissions for a user.
@@ -104,6 +104,19 @@ class AnalyticsEvent {
104
104
  }
105
105
  }
106
106
  exports.AnalyticsEvent = AnalyticsEvent;
107
+ function isValidUserProperty(property) {
108
+ if (property == null || typeof property !== "object" || !("value" in property)) {
109
+ return false;
110
+ }
111
+ const { value } = property;
112
+ if (value == null) {
113
+ return false;
114
+ }
115
+ if (typeof value === "object" && Object.keys(value).length === 0) {
116
+ return false;
117
+ }
118
+ return true;
119
+ }
107
120
  /**
108
121
  * Interface representing the user who triggered the events.
109
122
  */
@@ -116,7 +129,9 @@ class UserDimensions {
116
129
  copyTimestampToString(wireFormat, this, "firstOpenTimestampMicros", "firstOpenTime");
117
130
  this.userProperties = {}; // With no entries in the wire format, present an empty (as opposed to absent) map.
118
131
  copyField(wireFormat, this, "userProperties", (r) => {
119
- const entries = Object.entries(r).map(([k, v]) => [k, new UserPropertyValue(v)]);
132
+ const entries = Object.entries(r)
133
+ .filter(([, v]) => isValidUserProperty(v))
134
+ .map(([k, v]) => [k, new UserPropertyValue(v)]);
120
135
  return Object.fromEntries(entries);
121
136
  });
122
137
  copyField(wireFormat, this, "bundleInfo", (r) => new ExportBundleInfo(r));
@@ -203,8 +218,19 @@ function mapKeys(obj, transform) {
203
218
  // 'unwrapValue' method just below.
204
219
  /** @hidden */
205
220
  function unwrapValueAsString(wrapped) {
206
- const key = Object.keys(wrapped)[0];
207
- return wrapped[key].toString();
221
+ if (!wrapped || typeof wrapped !== "object") {
222
+ return "";
223
+ }
224
+ const keys = Object.keys(wrapped);
225
+ if (keys.length === 0) {
226
+ return "";
227
+ }
228
+ const key = keys[0];
229
+ const value = wrapped[key];
230
+ if (value === null || value === undefined) {
231
+ return "";
232
+ }
233
+ return value.toString();
208
234
  }
209
235
  // Ditto as the method above, but returning the values in the idiomatic JavaScript type (string for strings,
210
236
  // number for numbers):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-functions",
3
- "version": "6.4.0",
3
+ "version": "6.6.0",
4
4
  "description": "Firebase SDK for Cloud Functions",
5
5
  "keywords": [
6
6
  "firebase",
@@ -19,6 +19,7 @@
19
19
  "license": "MIT",
20
20
  "author": "Firebase Team",
21
21
  "files": [
22
+ ".guides",
22
23
  "lib",
23
24
  "protos"
24
25
  ],
@@ -301,7 +302,6 @@
301
302
  "eslint-plugin-prettier": "^4.0.0",
302
303
  "firebase-admin": "^13.0.0",
303
304
  "genkit": "^1.0.0-rc.4",
304
- "js-yaml": "^3.13.1",
305
305
  "jsdom": "^16.2.1",
306
306
  "jsonwebtoken": "^9.0.0",
307
307
  "jwk-to-pem": "^2.0.5",
@@ -317,6 +317,7 @@
317
317
  "sinon": "^9.2.4",
318
318
  "ts-node": "^10.4.0",
319
319
  "typescript": "^4.3.5",
320
+ "yaml": "^2.8.1",
320
321
  "yargs": "^15.3.1"
321
322
  },
322
323
  "peerDependencies": {