firebase-functions 6.3.2 → 6.5.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.
- package/.guides/config.json +9 -0
- package/.guides/setup.md +69 -0
- package/.guides/upgrade.md +178 -0
- package/.guides/usage.md +116 -0
- package/README.md +5 -5
- package/lib/bin/firebase-functions.js +65 -17
- package/lib/common/providers/database.js +4 -1
- package/lib/common/providers/https.d.ts +4 -0
- package/lib/common/providers/https.js +1 -0
- package/lib/common/providers/tasks.d.ts +1 -0
- package/lib/common/providers/tasks.js +1 -0
- package/lib/common/trace.d.ts +3 -0
- package/lib/common/trace.js +0 -1
- package/lib/logger/index.js +18 -7
- package/lib/v1/cloud-functions.d.ts +25 -1
- package/lib/v2/index.d.ts +1 -0
- package/lib/v2/index.js +3 -1
- package/lib/v2/options.d.ts +14 -3
- package/lib/v2/providers/https.d.ts +4 -4
- package/lib/v2/providers/https.js +15 -2
- package/lib/v2/providers/tasks.js +0 -1
- package/package.json +3 -2
package/.guides/setup.md
ADDED
|
@@ -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.
|
package/.guides/usage.md
ADDED
|
@@ -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
|
|
14
|
-
- Go through the
|
|
15
|
-
- Read the full API reference
|
|
16
|
-
- Browse some examples
|
|
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
|
|
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
|
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
// SOFTWARE.
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
const express = require("express");
|
|
26
|
+
const fs = require("fs/promises");
|
|
27
|
+
const path = require("path");
|
|
26
28
|
const loader_1 = require("../runtime/loader");
|
|
27
29
|
const manifest_1 = require("../runtime/manifest");
|
|
28
30
|
function printUsageAndExit() {
|
|
@@ -42,30 +44,76 @@ if (args.length > 1) {
|
|
|
42
44
|
}
|
|
43
45
|
functionsDir = args[0];
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
const app = express();
|
|
47
|
-
function handleQuitquitquit(req, res) {
|
|
47
|
+
function handleQuitquitquit(req, res, server) {
|
|
48
48
|
res.send("ok");
|
|
49
49
|
server.close();
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
if (process.env.FUNCTIONS_MANIFEST_OUTPUT_PATH) {
|
|
52
|
+
void (async () => {
|
|
53
|
+
var _a;
|
|
54
|
+
const outputPath = process.env.FUNCTIONS_MANIFEST_OUTPUT_PATH;
|
|
55
55
|
try {
|
|
56
|
+
// Validate the output path
|
|
57
|
+
const dir = path.dirname(outputPath);
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(dir, fs.constants.W_OK);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
console.error(`Error: Cannot write to directory '${dir}': ${e instanceof Error ? e.message : String(e)}`);
|
|
63
|
+
console.error("Please ensure the directory exists and you have write permissions.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
56
66
|
const stack = await (0, loader_1.loadStack)(functionsDir);
|
|
57
|
-
|
|
58
|
-
|
|
67
|
+
const wireFormat = (0, manifest_1.stackToWire)(stack);
|
|
68
|
+
await fs.writeFile(outputPath, JSON.stringify(wireFormat, null, 2));
|
|
69
|
+
process.exit(0);
|
|
59
70
|
}
|
|
60
71
|
catch (e) {
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
if (e.code === "ENOENT") {
|
|
73
|
+
console.error(`Error: Directory '${path.dirname(outputPath)}' does not exist.`);
|
|
74
|
+
console.error("Please create the directory or specify a valid path.");
|
|
75
|
+
}
|
|
76
|
+
else if (e.code === "EACCES") {
|
|
77
|
+
console.error(`Error: Permission denied writing to '${outputPath}'.`);
|
|
78
|
+
console.error("Please check file permissions or choose a different location.");
|
|
79
|
+
}
|
|
80
|
+
else if ((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes("Failed to generate manifest")) {
|
|
81
|
+
console.error(e.message);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.error(`Failed to generate manifest from function source: ${e instanceof Error ? e.message : String(e)}`);
|
|
85
|
+
}
|
|
86
|
+
if (e instanceof Error && e.stack) {
|
|
87
|
+
console.error(e.stack);
|
|
88
|
+
}
|
|
89
|
+
process.exit(1);
|
|
63
90
|
}
|
|
64
|
-
});
|
|
91
|
+
})();
|
|
65
92
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
else {
|
|
94
|
+
let server = undefined;
|
|
95
|
+
const app = express();
|
|
96
|
+
app.get("/__/quitquitquit", (req, res) => handleQuitquitquit(req, res, server));
|
|
97
|
+
app.post("/__/quitquitquit", (req, res) => handleQuitquitquit(req, res, server));
|
|
98
|
+
if (process.env.FUNCTIONS_CONTROL_API === "true") {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
100
|
+
app.get("/__/functions.yaml", async (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const stack = await (0, loader_1.loadStack)(functionsDir);
|
|
103
|
+
res.setHeader("content-type", "text/yaml");
|
|
104
|
+
res.send(JSON.stringify((0, manifest_1.stackToWire)(stack)));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
console.error(e);
|
|
108
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
109
|
+
res.status(400).send(`Failed to generate manifest from function source: ${errorMessage}`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
let port = 8080;
|
|
114
|
+
if (process.env.PORT) {
|
|
115
|
+
port = Number.parseInt(process.env.PORT);
|
|
116
|
+
}
|
|
117
|
+
console.log("Serving at port", port);
|
|
118
|
+
server = app.listen(port);
|
|
69
119
|
}
|
|
70
|
-
console.log("Serving at port", port);
|
|
71
|
-
server = app.listen(port);
|
|
@@ -99,9 +99,12 @@ class DataSnapshot {
|
|
|
99
99
|
val() {
|
|
100
100
|
const parts = (0, path_1.pathParts)(this._childPath);
|
|
101
101
|
let source = this._data;
|
|
102
|
+
if (source === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
102
105
|
if (parts.length) {
|
|
103
106
|
for (const part of parts) {
|
|
104
|
-
if (source
|
|
107
|
+
if (typeof source === "undefined" || source === null) {
|
|
105
108
|
return null;
|
|
106
109
|
}
|
|
107
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.
|
package/lib/common/trace.d.ts
CHANGED
package/lib/common/trace.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractTraceContext = exports.traceContext = void 0;
|
|
4
4
|
const async_hooks_1 = require("async_hooks");
|
|
5
|
-
/* @internal */
|
|
6
5
|
exports.traceContext = new async_hooks_1.AsyncLocalStorage();
|
|
7
6
|
/**
|
|
8
7
|
* A regex to match the Cloud Trace header.
|
package/lib/logger/index.js
CHANGED
|
@@ -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.
|
|
37
|
+
if (refs.has(obj)) {
|
|
38
38
|
return "[Circular]";
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
41
|
-
refs.
|
|
41
|
+
refs.add(obj);
|
|
42
42
|
}
|
|
43
43
|
let returnObj;
|
|
44
44
|
if (Array.isArray(obj)) {
|
|
@@ -48,13 +48,24 @@ function removeCircular(obj, refs = []) {
|
|
|
48
48
|
returnObj = {};
|
|
49
49
|
}
|
|
50
50
|
for (const k in obj) {
|
|
51
|
-
if (
|
|
52
|
-
|
|
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] =
|
|
65
|
+
returnObj[k] = "[Error - defined in the prototype but missing in the object]";
|
|
56
66
|
}
|
|
57
67
|
}
|
|
68
|
+
refs.delete(obj);
|
|
58
69
|
return returnObj;
|
|
59
70
|
}
|
|
60
71
|
/**
|
|
@@ -55,8 +55,10 @@ export interface EventContext<Params = Record<string, string>> {
|
|
|
55
55
|
* does not exist.
|
|
56
56
|
*/
|
|
57
57
|
auth?: {
|
|
58
|
-
token: object;
|
|
59
58
|
uid: string;
|
|
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.
|
|
@@ -152,6 +154,28 @@ export interface EventContext<Params = Record<string, string>> {
|
|
|
152
154
|
*/
|
|
153
155
|
timestamp: string;
|
|
154
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* https://firebase.google.com/docs/reference/security/database#authtoken
|
|
159
|
+
*/
|
|
160
|
+
export interface EventContextAuthToken {
|
|
161
|
+
iss: string;
|
|
162
|
+
aud: string;
|
|
163
|
+
auth_time: number;
|
|
164
|
+
iat: number;
|
|
165
|
+
exp: number;
|
|
166
|
+
sub: string;
|
|
167
|
+
email?: string;
|
|
168
|
+
email_verified?: boolean;
|
|
169
|
+
phone_number?: string;
|
|
170
|
+
name?: string;
|
|
171
|
+
firebase?: {
|
|
172
|
+
identities?: {
|
|
173
|
+
[key: string]: string[];
|
|
174
|
+
};
|
|
175
|
+
sign_in_provider?: string;
|
|
176
|
+
tenant?: string;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
155
179
|
/**
|
|
156
180
|
* Resource is a standard format for defining a resource
|
|
157
181
|
* (google.rpc.context.AttributeContext.Resource). In Cloud Functions, it is the
|
package/lib/v2/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { alerts, database, storage, https, identity, pubsub, logger, tasks, even
|
|
|
22
22
|
export { setGlobalOptions, GlobalOptions, SupportedRegion, MemoryOption, VpcEgressSetting, IngressSetting, EventHandlerOptions, } from "./options";
|
|
23
23
|
export { CloudFunction, CloudEvent, ParamsOf, onInit } from "./core";
|
|
24
24
|
export { Change } from "../common/change";
|
|
25
|
+
export { traceContext } from "../common/trace";
|
|
25
26
|
import * as params from "../params";
|
|
26
27
|
export { params };
|
|
27
28
|
export { config } from "../v1/config";
|
package/lib/v2/index.js
CHANGED
|
@@ -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.app = exports.config = exports.params = exports.Change = exports.onInit = exports.setGlobalOptions = exports.firestore = exports.testLab = exports.remoteConfig = exports.scheduler = exports.eventarc = exports.tasks = exports.logger = exports.pubsub = exports.identity = exports.https = exports.storage = exports.database = exports.alerts = void 0;
|
|
24
|
+
exports.app = exports.config = exports.params = exports.traceContext = exports.Change = exports.onInit = exports.setGlobalOptions = exports.firestore = exports.testLab = exports.remoteConfig = exports.scheduler = exports.eventarc = exports.tasks = exports.logger = exports.pubsub = exports.identity = exports.https = exports.storage = exports.database = exports.alerts = void 0;
|
|
25
25
|
/**
|
|
26
26
|
* The 2nd gen API for Cloud Functions for Firebase.
|
|
27
27
|
* This SDK supports deep imports. For example, the namespace
|
|
@@ -61,6 +61,8 @@ var core_1 = require("./core");
|
|
|
61
61
|
Object.defineProperty(exports, "onInit", { enumerable: true, get: function () { return core_1.onInit; } });
|
|
62
62
|
var change_1 = require("../common/change");
|
|
63
63
|
Object.defineProperty(exports, "Change", { enumerable: true, get: function () { return change_1.Change; } });
|
|
64
|
+
var trace_1 = require("../common/trace");
|
|
65
|
+
Object.defineProperty(exports, "traceContext", { enumerable: true, get: function () { return trace_1.traceContext; } });
|
|
64
66
|
// NOTE: Equivalent to `export * as params from "../params"` but api-extractor doesn't support that syntax.
|
|
65
67
|
const params = require("../params");
|
|
66
68
|
exports.params = params;
|
package/lib/v2/options.d.ts
CHANGED
|
@@ -134,11 +134,22 @@ export declare function setGlobalOptions(options: GlobalOptions): void;
|
|
|
134
134
|
* Additional fields that can be set on any event-handling function.
|
|
135
135
|
*/
|
|
136
136
|
export interface EventHandlerOptions extends Omit<GlobalOptions, "enforceAppCheck"> {
|
|
137
|
-
/** Type of the event.
|
|
137
|
+
/** Type of the event. */
|
|
138
138
|
eventType?: string;
|
|
139
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* Filters events based on exact matches on the CloudEvents attributes.
|
|
141
|
+
*
|
|
142
|
+
* Each key-value pair represents an attribute name and its required value for exact matching.
|
|
143
|
+
* Events must match all specified filters to trigger the function.
|
|
144
|
+
*/
|
|
140
145
|
eventFilters?: Record<string, string | Expression<string>>;
|
|
141
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Filters events based on path pattern matching on the CloudEvents attributes.
|
|
148
|
+
*
|
|
149
|
+
* Similar to eventFilters, but supports wildcard patterns for flexible matching where `*` matches
|
|
150
|
+
* any single path segment, `**` matches zero or more path segments, and `{param}` captures a path segment
|
|
151
|
+
* as a parameter
|
|
152
|
+
*/
|
|
142
153
|
eventFilterPathPatterns?: Record<string, string | Expression<string>>;
|
|
143
154
|
/** Whether failed executions should be delivered again. */
|
|
144
155
|
retry?: boolean | Expression<boolean> | ResetValue;
|
|
@@ -22,7 +22,7 @@ export interface HttpsOptions extends Omit<GlobalOptions, "region" | "enforceApp
|
|
|
22
22
|
* If this is an `Array`, allows requests from domains matching at least one entry of the array.
|
|
23
23
|
* Defaults to true for {@link https.CallableFunction} and false otherwise.
|
|
24
24
|
*/
|
|
25
|
-
cors?: string | boolean | RegExp | Array<string | RegExp>;
|
|
25
|
+
cors?: string | Expression<string> | Expression<string[]> | boolean | RegExp | Array<string | RegExp>;
|
|
26
26
|
/**
|
|
27
27
|
* Amount of memory to allocate to a function.
|
|
28
28
|
*/
|
|
@@ -140,12 +140,12 @@ export interface CallableOptions<T = any> extends HttpsOptions {
|
|
|
140
140
|
*/
|
|
141
141
|
heartbeatSeconds?: number | null;
|
|
142
142
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* Callback for whether a request is authorized.
|
|
143
|
+
* (Deprecated) Callback for whether a request is authorized.
|
|
146
144
|
*
|
|
147
145
|
* Designed to allow reusable auth policies to be passed as an options object. Two built-in reusable policies exist:
|
|
148
146
|
* isSignedIn and hasClaim.
|
|
147
|
+
*
|
|
148
|
+
* @deprecated
|
|
149
149
|
*/
|
|
150
150
|
authPolicy?: (auth: AuthData | null, data: T) => boolean | Promise<boolean>;
|
|
151
151
|
}
|
|
@@ -33,6 +33,7 @@ const debug_1 = require("../../common/debug");
|
|
|
33
33
|
const https_1 = require("../../common/providers/https");
|
|
34
34
|
Object.defineProperty(exports, "HttpsError", { enumerable: true, get: function () { return https_1.HttpsError; } });
|
|
35
35
|
const manifest_1 = require("../../runtime/manifest");
|
|
36
|
+
const params_1 = require("../../params");
|
|
36
37
|
const options = require("../options");
|
|
37
38
|
const onInit_1 = require("../../common/onInit");
|
|
38
39
|
const logger = require("../../logger");
|
|
@@ -68,7 +69,7 @@ function onRequest(optsOrHandler, handler) {
|
|
|
68
69
|
opts = optsOrHandler;
|
|
69
70
|
}
|
|
70
71
|
if ((0, debug_1.isDebugFeatureEnabled)("enableCors") || "cors" in opts) {
|
|
71
|
-
let origin = opts.cors;
|
|
72
|
+
let origin = opts.cors instanceof params_1.Expression ? opts.cors.value() : opts.cors;
|
|
72
73
|
if ((0, debug_1.isDebugFeatureEnabled)("enableCors")) {
|
|
73
74
|
// Respect `cors: false` to turn off cors even if debug feature is enabled.
|
|
74
75
|
origin = opts.cors === false ? false : true;
|
|
@@ -146,7 +147,19 @@ function onCall(optsOrHandler, handler) {
|
|
|
146
147
|
else {
|
|
147
148
|
opts = optsOrHandler;
|
|
148
149
|
}
|
|
149
|
-
let
|
|
150
|
+
let cors;
|
|
151
|
+
if ("cors" in opts) {
|
|
152
|
+
if (opts.cors instanceof params_1.Expression) {
|
|
153
|
+
cors = opts.cors.value();
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
cors = opts.cors;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
cors = true;
|
|
161
|
+
}
|
|
162
|
+
let origin = (0, debug_1.isDebugFeatureEnabled)("enableCors") ? true : cors;
|
|
150
163
|
// Arrays cause the access-control-allow-origin header to be dynamic based
|
|
151
164
|
// on the origin header of the request. If there is only one element in the
|
|
152
165
|
// array, this is unnecessary.
|
|
@@ -86,7 +86,6 @@ function onTaskDispatched(optsOrHandler, handler) {
|
|
|
86
86
|
(0, encoding_1.copyIfPresent)(func.__endpoint.taskQueueTrigger.rateLimits, opts.rateLimits, "maxConcurrentDispatches", "maxDispatchesPerSecond");
|
|
87
87
|
(0, encoding_1.convertIfPresent)(func.__endpoint.taskQueueTrigger, options.getGlobalOptions(), "invoker", "invoker", encoding_1.convertInvoker);
|
|
88
88
|
(0, encoding_1.convertIfPresent)(func.__endpoint.taskQueueTrigger, opts, "invoker", "invoker", encoding_1.convertInvoker);
|
|
89
|
-
(0, encoding_1.copyIfPresent)(func.__endpoint.taskQueueTrigger, opts, "retry", "retry");
|
|
90
89
|
func.__requiredAPIs = [
|
|
91
90
|
{
|
|
92
91
|
api: "cloudtasks.googleapis.com",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-functions",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.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": {
|