1 | ;
|
2 | // The MIT License (MIT)
|
3 | //
|
4 | // Copyright (c) 2017 Firebase
|
5 | //
|
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
|
7 | // of this software and associated documentation files (the "Software"), to deal
|
8 | // in the Software without restriction, including without limitation the rights
|
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10 | // copies of the Software, and to permit persons to whom the Software is
|
11 | // furnished to do so, subject to the following conditions:
|
12 | //
|
13 | // The above copyright notice and this permission notice shall be included in all
|
14 | // copies or substantial portions of the Software.
|
15 | //
|
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22 | // SOFTWARE.
|
23 | Object.defineProperty(exports, "__esModule", { value: true });
|
24 | exports.FunctionBuilder = exports.runWith = exports.region = void 0;
|
25 | const options_1 = require("../common/options");
|
26 | const types_1 = require("../params/types");
|
27 | const function_configuration_1 = require("./function-configuration");
|
28 | const analytics = require("./providers/analytics");
|
29 | const auth = require("./providers/auth");
|
30 | const database = require("./providers/database");
|
31 | const firestore = require("./providers/firestore");
|
32 | const https = require("./providers/https");
|
33 | const pubsub = require("./providers/pubsub");
|
34 | const remoteConfig = require("./providers/remoteConfig");
|
35 | const storage = require("./providers/storage");
|
36 | const tasks = require("./providers/tasks");
|
37 | const testLab = require("./providers/testLab");
|
38 | /**
|
39 | * Assert that the runtime options passed in are valid.
|
40 | * @param runtimeOptions object containing memory and timeout information.
|
41 | * @throws { Error } Memory and TimeoutSeconds values must be valid.
|
42 | */
|
43 | function assertRuntimeOptionsValid(runtimeOptions) {
|
44 | const mem = runtimeOptions.memory;
|
45 | if (mem && typeof mem !== "object" && !function_configuration_1.VALID_MEMORY_OPTIONS.includes(mem)) {
|
46 | throw new Error(`The only valid memory allocation values are: ${function_configuration_1.VALID_MEMORY_OPTIONS.join(", ")}`);
|
47 | }
|
48 | if (typeof runtimeOptions.timeoutSeconds === "number" &&
|
49 | (runtimeOptions.timeoutSeconds > function_configuration_1.MAX_TIMEOUT_SECONDS || runtimeOptions.timeoutSeconds < 0)) {
|
50 | throw new Error(`TimeoutSeconds must be between 0 and ${function_configuration_1.MAX_TIMEOUT_SECONDS}`);
|
51 | }
|
52 | if (runtimeOptions.ingressSettings &&
|
53 | !(runtimeOptions.ingressSettings instanceof options_1.ResetValue) &&
|
54 | !function_configuration_1.INGRESS_SETTINGS_OPTIONS.includes(runtimeOptions.ingressSettings)) {
|
55 | throw new Error(`The only valid ingressSettings values are: ${function_configuration_1.INGRESS_SETTINGS_OPTIONS.join(",")}`);
|
56 | }
|
57 | if (runtimeOptions.vpcConnectorEgressSettings &&
|
58 | !(runtimeOptions.vpcConnectorEgressSettings instanceof options_1.ResetValue) &&
|
59 | !function_configuration_1.VPC_EGRESS_SETTINGS_OPTIONS.includes(runtimeOptions.vpcConnectorEgressSettings)) {
|
60 | throw new Error(`The only valid vpcConnectorEgressSettings values are: ${function_configuration_1.VPC_EGRESS_SETTINGS_OPTIONS.join(",")}`);
|
61 | }
|
62 | validateFailurePolicy(runtimeOptions.failurePolicy);
|
63 | const serviceAccount = runtimeOptions.serviceAccount;
|
64 | if (serviceAccount &&
|
65 | !(serviceAccount === "default" ||
|
66 | serviceAccount instanceof options_1.ResetValue ||
|
67 | serviceAccount instanceof types_1.Expression ||
|
68 | serviceAccount.includes("@"))) {
|
69 | throw new Error(`serviceAccount must be set to 'default', a string expression, a service account email, or '{serviceAccountName}@'`);
|
70 | }
|
71 | if (runtimeOptions.labels) {
|
72 | // Labels must follow the rules listed in
|
73 | // https://cloud.google.com/resource-manager/docs/creating-managing-labels#requirements
|
74 | if (Object.keys(runtimeOptions.labels).length > function_configuration_1.MAX_NUMBER_USER_LABELS) {
|
75 | throw new Error(`A function must not have more than ${function_configuration_1.MAX_NUMBER_USER_LABELS} user-defined labels.`);
|
76 | }
|
77 | // We reserve the 'deployment' and 'firebase' namespaces for future feature development.
|
78 | const reservedKeys = Object.keys(runtimeOptions.labels).filter((key) => key.startsWith("deployment") || key.startsWith("firebase"));
|
79 | if (reservedKeys.length) {
|
80 | throw new Error(`Invalid labels: ${reservedKeys.join(", ")}. Labels may not start with reserved names 'deployment' or 'firebase'`);
|
81 | }
|
82 | const invalidLengthKeys = Object.keys(runtimeOptions.labels).filter((key) => key.length < 1 || key.length > 63);
|
83 | if (invalidLengthKeys.length > 0) {
|
84 | throw new Error(`Invalid labels: ${invalidLengthKeys.join(", ")}. Label keys must be between 1 and 63 characters in length.`);
|
85 | }
|
86 | const invalidLengthValues = Object.values(runtimeOptions.labels).filter((value) => value.length > 63);
|
87 | if (invalidLengthValues.length > 0) {
|
88 | throw new Error(`Invalid labels: ${invalidLengthValues.join(", ")}. Label values must be less than 64 charcters.`);
|
89 | }
|
90 | // Keys can contain lowercase letters, foreign characters, numbers, _ or -. They must start with a letter.
|
91 | const validKeyPattern = /^[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}$/u;
|
92 | const invalidKeys = Object.keys(runtimeOptions.labels).filter((key) => !validKeyPattern.test(key));
|
93 | if (invalidKeys.length > 0) {
|
94 | throw new Error(`Invalid labels: ${invalidKeys.join(", ")}. Label keys can only contain lowercase letters, international characters, numbers, _ or -, and must start with a letter.`);
|
95 | }
|
96 | // Values can contain lowercase letters, foreign characters, numbers, _ or -.
|
97 | const validValuePattern = /^[\p{Ll}\p{Lo}\p{N}_-]{0,63}$/u;
|
98 | const invalidValues = Object.values(runtimeOptions.labels).filter((value) => !validValuePattern.test(value));
|
99 | if (invalidValues.length > 0) {
|
100 | throw new Error(`Invalid labels: ${invalidValues.join(", ")}. Label values can only contain lowercase letters, international characters, numbers, _ or -.`);
|
101 | }
|
102 | }
|
103 | if (typeof runtimeOptions.invoker === "string" && runtimeOptions.invoker.length === 0) {
|
104 | throw new Error("Invalid service account for function invoker, must be a non-empty string");
|
105 | }
|
106 | if (runtimeOptions.invoker !== undefined && Array.isArray(runtimeOptions.invoker)) {
|
107 | if (runtimeOptions.invoker.length === 0) {
|
108 | throw new Error("Invalid invoker array, must contain at least 1 service account entry");
|
109 | }
|
110 | for (const serviceAccount of runtimeOptions.invoker) {
|
111 | if (serviceAccount.length === 0) {
|
112 | throw new Error("Invalid invoker array, a service account must be a non-empty string");
|
113 | }
|
114 | if (serviceAccount === "public") {
|
115 | throw new Error("Invalid invoker array, a service account cannot be set to the 'public' identifier");
|
116 | }
|
117 | if (serviceAccount === "private") {
|
118 | throw new Error("Invalid invoker array, a service account cannot be set to the 'private' identifier");
|
119 | }
|
120 | }
|
121 | }
|
122 | if (runtimeOptions.secrets !== undefined) {
|
123 | const invalidSecrets = runtimeOptions.secrets.filter((s) => !/^[A-Za-z\d\-_]+$/.test(s instanceof types_1.SecretParam ? s.name : s));
|
124 | if (invalidSecrets.length > 0) {
|
125 | throw new Error(`Invalid secrets: ${invalidSecrets.join(",")}. ` +
|
126 | "Secret must be configured using the resource id (e.g. API_KEY)");
|
127 | }
|
128 | }
|
129 | if ("allowInvalidAppCheckToken" in runtimeOptions) {
|
130 | throw new Error('runWith option "allowInvalidAppCheckToken" has been inverted and ' +
|
131 | 'renamed "enforceAppCheck"');
|
132 | }
|
133 | return true;
|
134 | }
|
135 | function validateFailurePolicy(policy) {
|
136 | if (typeof policy === "boolean" || typeof policy === "undefined") {
|
137 | return;
|
138 | }
|
139 | if (typeof policy !== "object") {
|
140 | throw new Error(`failurePolicy must be a boolean or an object.`);
|
141 | }
|
142 | const retry = policy.retry;
|
143 | if (typeof retry !== "object" || Object.keys(retry).length) {
|
144 | throw new Error("failurePolicy.retry must be an empty object.");
|
145 | }
|
146 | }
|
147 | /**
|
148 | * Assert regions specified are valid.
|
149 | * @param regions list of regions.
|
150 | * @throws { Error } Regions must be in list of supported regions.
|
151 | */
|
152 | function assertRegionsAreValid(regions) {
|
153 | if (!regions.length) {
|
154 | throw new Error("You must specify at least one region");
|
155 | }
|
156 | return true;
|
157 | }
|
158 | /**
|
159 | * Configure the regions that the function is deployed to.
|
160 | * @param regions One of more region strings.
|
161 | * @example
|
162 | * functions.region('us-east1')
|
163 | * @example
|
164 | * functions.region('us-east1', 'us-central1')
|
165 | */
|
166 | function region(...regions) {
|
167 | if (assertRegionsAreValid(regions)) {
|
168 | return new FunctionBuilder({ regions });
|
169 | }
|
170 | }
|
171 | exports.region = region;
|
172 | /**
|
173 | * Configure runtime options for the function.
|
174 | * @param runtimeOptions Object with optional fields:
|
175 | * 1. `memory`: amount of memory to allocate to the function, possible values
|
176 | * are: '128MB', '256MB', '512MB', '1GB', '2GB', '4GB', and '8GB'.
|
177 | * 2. `timeoutSeconds`: timeout for the function in seconds, possible values are
|
178 | * 0 to 540.
|
179 | * 3. `failurePolicy`: failure policy of the function, with boolean `true` being
|
180 | * equivalent to providing an empty retry object.
|
181 | * 4. `vpcConnector`: id of a VPC connector in same project and region.
|
182 | * 5. `vpcConnectorEgressSettings`: when a vpcConnector is set, control which
|
183 | * egress traffic is sent through the vpcConnector.
|
184 | * 6. `serviceAccount`: Specific service account for the function.
|
185 | * 7. `ingressSettings`: ingress settings for the function, which control where a HTTPS
|
186 | * function can be called from.
|
187 | *
|
188 | * Value must not be null.
|
189 | */
|
190 | function runWith(runtimeOptions) {
|
191 | if (assertRuntimeOptionsValid(runtimeOptions)) {
|
192 | return new FunctionBuilder(runtimeOptions);
|
193 | }
|
194 | }
|
195 | exports.runWith = runWith;
|
196 | class FunctionBuilder {
|
197 | constructor(options) {
|
198 | this.options = options;
|
199 | }
|
200 | /**
|
201 | * Configure the regions that the function is deployed to.
|
202 | * @param regions One or more region strings.
|
203 | * @example
|
204 | * functions.region('us-east1')
|
205 | * @example
|
206 | * functions.region('us-east1', 'us-central1')
|
207 | */
|
208 | region(...regions) {
|
209 | if (assertRegionsAreValid(regions)) {
|
210 | this.options.regions = regions;
|
211 | return this;
|
212 | }
|
213 | }
|
214 | /**
|
215 | * Configure runtime options for the function.
|
216 | * @param runtimeOptions Object with optional fields:
|
217 | * 1. `memory`: amount of memory to allocate to the function, possible values
|
218 | * are: '128MB', '256MB', '512MB', '1GB', '2GB', '4GB', and '8GB'.
|
219 | * 2. `timeoutSeconds`: timeout for the function in seconds, possible values are
|
220 | * 0 to 540.
|
221 | * 3. `failurePolicy`: failure policy of the function, with boolean `true` being
|
222 | * equivalent to providing an empty retry object.
|
223 | * 4. `vpcConnector`: id of a VPC connector in the same project and region
|
224 | * 5. `vpcConnectorEgressSettings`: when a `vpcConnector` is set, control which
|
225 | * egress traffic is sent through the `vpcConnector`.
|
226 | *
|
227 | * Value must not be null.
|
228 | */
|
229 | runWith(runtimeOptions) {
|
230 | if (assertRuntimeOptionsValid(runtimeOptions)) {
|
231 | this.options = {
|
232 | ...this.options,
|
233 | ...runtimeOptions,
|
234 | };
|
235 | return this;
|
236 | }
|
237 | }
|
238 | get https() {
|
239 | if (this.options.failurePolicy !== undefined) {
|
240 | console.warn("RuntimeOptions.failurePolicy is not supported in https functions.");
|
241 | }
|
242 | return {
|
243 | /**
|
244 | * Handle HTTP requests.
|
245 | * @param handler A function that takes a request and response object,
|
246 | * same signature as an Express app.
|
247 | */
|
248 | onRequest: (handler) => https._onRequestWithOptions(handler, this.options),
|
249 | /**
|
250 | * Declares a callable method for clients to call using a Firebase SDK.
|
251 | * @param handler A method that takes a data and context and returns a value.
|
252 | */
|
253 | onCall: (handler) => https._onCallWithOptions(handler, this.options),
|
254 | };
|
255 | }
|
256 | get tasks() {
|
257 | return {
|
258 | /**
|
259 | * Declares a task queue function for clients to call using a Firebase Admin SDK.
|
260 | * @param options Configurations for the task queue function.
|
261 | */
|
262 | /** @hidden */
|
263 | taskQueue: (options) => {
|
264 | return new tasks.TaskQueueBuilder(options, this.options);
|
265 | },
|
266 | };
|
267 | }
|
268 | get database() {
|
269 | return {
|
270 | /**
|
271 | * Selects a database instance that will trigger the function. If omitted,
|
272 | * will pick the default database for your project.
|
273 | * @param instance The Realtime Database instance to use.
|
274 | */
|
275 | instance: (instance) => database._instanceWithOptions(instance, this.options),
|
276 | /**
|
277 | * Select Firebase Realtime Database Reference to listen to.
|
278 | *
|
279 | * This method behaves very similarly to the method of the same name in
|
280 | * the client and Admin Firebase SDKs. Any change to the Database that
|
281 | * affects the data at or below the provided `path` will fire an event in
|
282 | * Cloud Functions.
|
283 | *
|
284 | * There are three important differences between listening to a Realtime
|
285 | * Database event in Cloud Functions and using the Realtime Database in
|
286 | * the client and Admin SDKs:
|
287 | * 1. Cloud Functions allows wildcards in the `path` name. Any `path`
|
288 | * component in curly brackets (`{}`) is a wildcard that matches all
|
289 | * strings. The value that matched a certain invocation of a Cloud
|
290 | * Function is returned as part of the `context.params` object. For
|
291 | * example, `ref("messages/{messageId}")` matches changes at
|
292 | * `/messages/message1` or `/messages/message2`, resulting in
|
293 | * `context.params.messageId` being set to `"message1"` or
|
294 | * `"message2"`, respectively.
|
295 | * 2. Cloud Functions do not fire an event for data that already existed
|
296 | * before the Cloud Function was deployed.
|
297 | * 3. Cloud Function events have access to more information, including
|
298 | * information about the user who triggered the Cloud Function.
|
299 | * @param ref Path of the database to listen to.
|
300 | */
|
301 | ref: (path) => database._refWithOptions(path, this.options),
|
302 | };
|
303 | }
|
304 | get firestore() {
|
305 | return {
|
306 | /**
|
307 | * Select the Firestore document to listen to for events.
|
308 | * @param path Full database path to listen to. This includes the name of
|
309 | * the collection that the document is a part of. For example, if the
|
310 | * collection is named "users" and the document is named "Ada", then the
|
311 | * path is "/users/Ada".
|
312 | */
|
313 | document: (path) => firestore._documentWithOptions(path, this.options),
|
314 | /** @hidden */
|
315 | namespace: (namespace) => firestore._namespaceWithOptions(namespace, this.options),
|
316 | /** @hidden */
|
317 | database: (database) => firestore._databaseWithOptions(database, this.options),
|
318 | };
|
319 | }
|
320 | get analytics() {
|
321 | return {
|
322 | /**
|
323 | * Select analytics events to listen to for events.
|
324 | * @param analyticsEventType Name of the analytics event type.
|
325 | */
|
326 | event: (analyticsEventType) => analytics._eventWithOptions(analyticsEventType, this.options),
|
327 | };
|
328 | }
|
329 | get remoteConfig() {
|
330 | return {
|
331 | /**
|
332 | * Handle all updates (including rollbacks) that affect a Remote Config
|
333 | * project.
|
334 | * @param handler A function that takes the updated Remote Config template
|
335 | * version metadata as an argument.
|
336 | */
|
337 | onUpdate: (handler) => remoteConfig._onUpdateWithOptions(handler, this.options),
|
338 | };
|
339 | }
|
340 | get storage() {
|
341 | return {
|
342 | /**
|
343 | * The optional bucket function allows you to choose which buckets' events
|
344 | * to handle. This step can be bypassed by calling object() directly,
|
345 | * which will use the default Cloud Storage for Firebase bucket.
|
346 | * @param bucket Name of the Google Cloud Storage bucket to listen to.
|
347 | */
|
348 | bucket: (bucket) => storage._bucketWithOptions(this.options, bucket),
|
349 | /**
|
350 | * Handle events related to Cloud Storage objects.
|
351 | */
|
352 | object: () => storage._objectWithOptions(this.options),
|
353 | };
|
354 | }
|
355 | get pubsub() {
|
356 | return {
|
357 | /**
|
358 | * Select Cloud Pub/Sub topic to listen to.
|
359 | * @param topic Name of Pub/Sub topic, must belong to the same project as
|
360 | * the function.
|
361 | */
|
362 | topic: (topic) => pubsub._topicWithOptions(topic, this.options),
|
363 | schedule: (schedule) => pubsub._scheduleWithOptions(schedule, this.options),
|
364 | };
|
365 | }
|
366 | get auth() {
|
367 | return {
|
368 | /**
|
369 | * Handle events related to Firebase authentication users.
|
370 | */
|
371 | user: (userOptions) => auth._userWithOptions(this.options, userOptions),
|
372 | };
|
373 | }
|
374 | get testLab() {
|
375 | return {
|
376 | /**
|
377 | * Handle events related to Test Lab test matrices.
|
378 | */
|
379 | testMatrix: () => testLab._testMatrixWithOpts(this.options),
|
380 | };
|
381 | }
|
382 | }
|
383 | exports.FunctionBuilder = FunctionBuilder;
|