UNPKG

18.4 kBJavaScriptView Raw
1"use strict";
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.
23Object.defineProperty(exports, "__esModule", { value: true });
24exports.FunctionBuilder = exports.runWith = exports.region = void 0;
25const options_1 = require("../common/options");
26const types_1 = require("../params/types");
27const function_configuration_1 = require("./function-configuration");
28const analytics = require("./providers/analytics");
29const auth = require("./providers/auth");
30const database = require("./providers/database");
31const firestore = require("./providers/firestore");
32const https = require("./providers/https");
33const pubsub = require("./providers/pubsub");
34const remoteConfig = require("./providers/remoteConfig");
35const storage = require("./providers/storage");
36const tasks = require("./providers/tasks");
37const 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 */
43function 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}
135function 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 */
152function 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 */
166function region(...regions) {
167 if (assertRegionsAreValid(regions)) {
168 return new FunctionBuilder({ regions });
169 }
170}
171exports.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 */
190function runWith(runtimeOptions) {
191 if (assertRuntimeOptionsValid(runtimeOptions)) {
192 return new FunctionBuilder(runtimeOptions);
193 }
194}
195exports.runWith = runWith;
196class 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}
383exports.FunctionBuilder = FunctionBuilder;