UNPKG

26 kBJavaScriptView Raw
1import { _registerComponent, registerVersion, _getProvider, getApp } from '@firebase/app';
2import { FirebaseError, getModularInstance, getDefaultEmulatorHostnameAndPort } from '@firebase/util';
3import { Component } from '@firebase/component';
4
5/**
6 * @license
7 * Copyright 2017 Google LLC
8 *
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21const LONG_TYPE = 'type.googleapis.com/google.protobuf.Int64Value';
22const UNSIGNED_LONG_TYPE = 'type.googleapis.com/google.protobuf.UInt64Value';
23function mapValues(
24// { [k: string]: unknown } is no longer a wildcard assignment target after typescript 3.5
25// eslint-disable-next-line @typescript-eslint/no-explicit-any
26o, f) {
27 const result = {};
28 for (const key in o) {
29 if (o.hasOwnProperty(key)) {
30 result[key] = f(o[key]);
31 }
32 }
33 return result;
34}
35/**
36 * Takes data and encodes it in a JSON-friendly way, such that types such as
37 * Date are preserved.
38 * @internal
39 * @param data - Data to encode.
40 */
41function encode(data) {
42 if (data == null) {
43 return null;
44 }
45 if (data instanceof Number) {
46 data = data.valueOf();
47 }
48 if (typeof data === 'number' && isFinite(data)) {
49 // Any number in JS is safe to put directly in JSON and parse as a double
50 // without any loss of precision.
51 return data;
52 }
53 if (data === true || data === false) {
54 return data;
55 }
56 if (Object.prototype.toString.call(data) === '[object String]') {
57 return data;
58 }
59 if (data instanceof Date) {
60 return data.toISOString();
61 }
62 if (Array.isArray(data)) {
63 return data.map(x => encode(x));
64 }
65 if (typeof data === 'function' || typeof data === 'object') {
66 return mapValues(data, x => encode(x));
67 }
68 // If we got this far, the data is not encodable.
69 throw new Error('Data cannot be encoded in JSON: ' + data);
70}
71/**
72 * Takes data that's been encoded in a JSON-friendly form and returns a form
73 * with richer datatypes, such as Dates, etc.
74 * @internal
75 * @param json - JSON to convert.
76 */
77function decode(json) {
78 if (json == null) {
79 return json;
80 }
81 if (json['@type']) {
82 switch (json['@type']) {
83 case LONG_TYPE:
84 // Fall through and handle this the same as unsigned.
85 case UNSIGNED_LONG_TYPE: {
86 // Technically, this could work return a valid number for malformed
87 // data if there was a number followed by garbage. But it's just not
88 // worth all the extra code to detect that case.
89 const value = Number(json['value']);
90 if (isNaN(value)) {
91 throw new Error('Data cannot be decoded from JSON: ' + json);
92 }
93 return value;
94 }
95 default: {
96 throw new Error('Data cannot be decoded from JSON: ' + json);
97 }
98 }
99 }
100 if (Array.isArray(json)) {
101 return json.map(x => decode(x));
102 }
103 if (typeof json === 'function' || typeof json === 'object') {
104 return mapValues(json, x => decode(x));
105 }
106 // Anything else is safe to return.
107 return json;
108}
109
110/**
111 * @license
112 * Copyright 2020 Google LLC
113 *
114 * Licensed under the Apache License, Version 2.0 (the "License");
115 * you may not use this file except in compliance with the License.
116 * You may obtain a copy of the License at
117 *
118 * http://www.apache.org/licenses/LICENSE-2.0
119 *
120 * Unless required by applicable law or agreed to in writing, software
121 * distributed under the License is distributed on an "AS IS" BASIS,
122 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
123 * See the License for the specific language governing permissions and
124 * limitations under the License.
125 */
126/**
127 * Type constant for Firebase Functions.
128 */
129const FUNCTIONS_TYPE = 'functions';
130
131/**
132 * @license
133 * Copyright 2017 Google LLC
134 *
135 * Licensed under the Apache License, Version 2.0 (the "License");
136 * you may not use this file except in compliance with the License.
137 * You may obtain a copy of the License at
138 *
139 * http://www.apache.org/licenses/LICENSE-2.0
140 *
141 * Unless required by applicable law or agreed to in writing, software
142 * distributed under the License is distributed on an "AS IS" BASIS,
143 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
144 * See the License for the specific language governing permissions and
145 * limitations under the License.
146 */
147/**
148 * Standard error codes for different ways a request can fail, as defined by:
149 * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
150 *
151 * This map is used primarily to convert from a backend error code string to
152 * a client SDK error code string, and make sure it's in the supported set.
153 */
154const errorCodeMap = {
155 OK: 'ok',
156 CANCELLED: 'cancelled',
157 UNKNOWN: 'unknown',
158 INVALID_ARGUMENT: 'invalid-argument',
159 DEADLINE_EXCEEDED: 'deadline-exceeded',
160 NOT_FOUND: 'not-found',
161 ALREADY_EXISTS: 'already-exists',
162 PERMISSION_DENIED: 'permission-denied',
163 UNAUTHENTICATED: 'unauthenticated',
164 RESOURCE_EXHAUSTED: 'resource-exhausted',
165 FAILED_PRECONDITION: 'failed-precondition',
166 ABORTED: 'aborted',
167 OUT_OF_RANGE: 'out-of-range',
168 UNIMPLEMENTED: 'unimplemented',
169 INTERNAL: 'internal',
170 UNAVAILABLE: 'unavailable',
171 DATA_LOSS: 'data-loss'
172};
173/**
174 * An explicit error that can be thrown from a handler to send an error to the
175 * client that called the function.
176 */
177class FunctionsError extends FirebaseError {
178 constructor(
179 /**
180 * A standard error code that will be returned to the client. This also
181 * determines the HTTP status code of the response, as defined in code.proto.
182 */
183 code, message,
184 /**
185 * Extra data to be converted to JSON and included in the error response.
186 */
187 details) {
188 super(`${FUNCTIONS_TYPE}/${code}`, message || '');
189 this.details = details;
190 }
191}
192/**
193 * Takes an HTTP status code and returns the corresponding ErrorCode.
194 * This is the standard HTTP status code -> error mapping defined in:
195 * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
196 *
197 * @param status An HTTP status code.
198 * @return The corresponding ErrorCode, or ErrorCode.UNKNOWN if none.
199 */
200function codeForHTTPStatus(status) {
201 // Make sure any successful status is OK.
202 if (status >= 200 && status < 300) {
203 return 'ok';
204 }
205 switch (status) {
206 case 0:
207 // This can happen if the server returns 500.
208 return 'internal';
209 case 400:
210 return 'invalid-argument';
211 case 401:
212 return 'unauthenticated';
213 case 403:
214 return 'permission-denied';
215 case 404:
216 return 'not-found';
217 case 409:
218 return 'aborted';
219 case 429:
220 return 'resource-exhausted';
221 case 499:
222 return 'cancelled';
223 case 500:
224 return 'internal';
225 case 501:
226 return 'unimplemented';
227 case 503:
228 return 'unavailable';
229 case 504:
230 return 'deadline-exceeded';
231 }
232 return 'unknown';
233}
234/**
235 * Takes an HTTP response and returns the corresponding Error, if any.
236 */
237function _errorForResponse(status, bodyJSON) {
238 let code = codeForHTTPStatus(status);
239 // Start with reasonable defaults from the status code.
240 let description = code;
241 let details = undefined;
242 // Then look through the body for explicit details.
243 try {
244 const errorJSON = bodyJSON && bodyJSON.error;
245 if (errorJSON) {
246 const status = errorJSON.status;
247 if (typeof status === 'string') {
248 if (!errorCodeMap[status]) {
249 // They must've included an unknown error code in the body.
250 return new FunctionsError('internal', 'internal');
251 }
252 code = errorCodeMap[status];
253 // TODO(klimt): Add better default descriptions for error enums.
254 // The default description needs to be updated for the new code.
255 description = status;
256 }
257 const message = errorJSON.message;
258 if (typeof message === 'string') {
259 description = message;
260 }
261 details = errorJSON.details;
262 if (details !== undefined) {
263 details = decode(details);
264 }
265 }
266 }
267 catch (e) {
268 // If we couldn't parse explicit error data, that's fine.
269 }
270 if (code === 'ok') {
271 // Technically, there's an edge case where a developer could explicitly
272 // return an error code of OK, and we will treat it as success, but that
273 // seems reasonable.
274 return null;
275 }
276 return new FunctionsError(code, description, details);
277}
278
279/**
280 * @license
281 * Copyright 2017 Google LLC
282 *
283 * Licensed under the Apache License, Version 2.0 (the "License");
284 * you may not use this file except in compliance with the License.
285 * You may obtain a copy of the License at
286 *
287 * http://www.apache.org/licenses/LICENSE-2.0
288 *
289 * Unless required by applicable law or agreed to in writing, software
290 * distributed under the License is distributed on an "AS IS" BASIS,
291 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
292 * See the License for the specific language governing permissions and
293 * limitations under the License.
294 */
295/**
296 * Helper class to get metadata that should be included with a function call.
297 * @internal
298 */
299class ContextProvider {
300 constructor(authProvider, messagingProvider, appCheckProvider) {
301 this.auth = null;
302 this.messaging = null;
303 this.appCheck = null;
304 this.auth = authProvider.getImmediate({ optional: true });
305 this.messaging = messagingProvider.getImmediate({
306 optional: true
307 });
308 if (!this.auth) {
309 authProvider.get().then(auth => (this.auth = auth), () => {
310 /* get() never rejects */
311 });
312 }
313 if (!this.messaging) {
314 messagingProvider.get().then(messaging => (this.messaging = messaging), () => {
315 /* get() never rejects */
316 });
317 }
318 if (!this.appCheck) {
319 appCheckProvider.get().then(appCheck => (this.appCheck = appCheck), () => {
320 /* get() never rejects */
321 });
322 }
323 }
324 async getAuthToken() {
325 if (!this.auth) {
326 return undefined;
327 }
328 try {
329 const token = await this.auth.getToken();
330 return token === null || token === void 0 ? void 0 : token.accessToken;
331 }
332 catch (e) {
333 // If there's any error when trying to get the auth token, leave it off.
334 return undefined;
335 }
336 }
337 async getMessagingToken() {
338 if (!this.messaging ||
339 !('Notification' in self) ||
340 Notification.permission !== 'granted') {
341 return undefined;
342 }
343 try {
344 return await this.messaging.getToken();
345 }
346 catch (e) {
347 // We don't warn on this, because it usually means messaging isn't set up.
348 // console.warn('Failed to retrieve instance id token.', e);
349 // If there's any error when trying to get the token, leave it off.
350 return undefined;
351 }
352 }
353 async getAppCheckToken() {
354 if (this.appCheck) {
355 const result = await this.appCheck.getToken();
356 if (result.error) {
357 // Do not send the App Check header to the functions endpoint if
358 // there was an error from the App Check exchange endpoint. The App
359 // Check SDK will already have logged the error to console.
360 return null;
361 }
362 return result.token;
363 }
364 return null;
365 }
366 async getContext() {
367 const authToken = await this.getAuthToken();
368 const messagingToken = await this.getMessagingToken();
369 const appCheckToken = await this.getAppCheckToken();
370 return { authToken, messagingToken, appCheckToken };
371 }
372}
373
374/**
375 * @license
376 * Copyright 2017 Google LLC
377 *
378 * Licensed under the Apache License, Version 2.0 (the "License");
379 * you may not use this file except in compliance with the License.
380 * You may obtain a copy of the License at
381 *
382 * http://www.apache.org/licenses/LICENSE-2.0
383 *
384 * Unless required by applicable law or agreed to in writing, software
385 * distributed under the License is distributed on an "AS IS" BASIS,
386 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
387 * See the License for the specific language governing permissions and
388 * limitations under the License.
389 */
390const DEFAULT_REGION = 'us-central1';
391/**
392 * Returns a Promise that will be rejected after the given duration.
393 * The error will be of type FunctionsError.
394 *
395 * @param millis Number of milliseconds to wait before rejecting.
396 */
397function failAfter(millis) {
398 // Node timers and browser timers are fundamentally incompatible, but we
399 // don't care about the value here
400 // eslint-disable-next-line @typescript-eslint/no-explicit-any
401 let timer = null;
402 return {
403 promise: new Promise((_, reject) => {
404 timer = setTimeout(() => {
405 reject(new FunctionsError('deadline-exceeded', 'deadline-exceeded'));
406 }, millis);
407 }),
408 cancel: () => {
409 if (timer) {
410 clearTimeout(timer);
411 }
412 }
413 };
414}
415/**
416 * The main class for the Firebase Functions SDK.
417 * @internal
418 */
419class FunctionsService {
420 /**
421 * Creates a new Functions service for the given app.
422 * @param app - The FirebaseApp to use.
423 */
424 constructor(app, authProvider, messagingProvider, appCheckProvider, regionOrCustomDomain = DEFAULT_REGION, fetchImpl) {
425 this.app = app;
426 this.fetchImpl = fetchImpl;
427 this.emulatorOrigin = null;
428 this.contextProvider = new ContextProvider(authProvider, messagingProvider, appCheckProvider);
429 // Cancels all ongoing requests when resolved.
430 this.cancelAllRequests = new Promise(resolve => {
431 this.deleteService = () => {
432 return Promise.resolve(resolve());
433 };
434 });
435 // Resolve the region or custom domain overload by attempting to parse it.
436 try {
437 const url = new URL(regionOrCustomDomain);
438 this.customDomain = url.origin;
439 this.region = DEFAULT_REGION;
440 }
441 catch (e) {
442 this.customDomain = null;
443 this.region = regionOrCustomDomain;
444 }
445 }
446 _delete() {
447 return this.deleteService();
448 }
449 /**
450 * Returns the URL for a callable with the given name.
451 * @param name - The name of the callable.
452 * @internal
453 */
454 _url(name) {
455 const projectId = this.app.options.projectId;
456 if (this.emulatorOrigin !== null) {
457 const origin = this.emulatorOrigin;
458 return `${origin}/${projectId}/${this.region}/${name}`;
459 }
460 if (this.customDomain !== null) {
461 return `${this.customDomain}/${name}`;
462 }
463 return `https://${this.region}-${projectId}.cloudfunctions.net/${name}`;
464 }
465}
466/**
467 * Modify this instance to communicate with the Cloud Functions emulator.
468 *
469 * Note: this must be called before this instance has been used to do any operations.
470 *
471 * @param host The emulator host (ex: localhost)
472 * @param port The emulator port (ex: 5001)
473 * @public
474 */
475function connectFunctionsEmulator$1(functionsInstance, host, port) {
476 functionsInstance.emulatorOrigin = `http://${host}:${port}`;
477}
478/**
479 * Returns a reference to the callable https trigger with the given name.
480 * @param name - The name of the trigger.
481 * @public
482 */
483function httpsCallable$1(functionsInstance, name, options) {
484 return (data => {
485 return call(functionsInstance, name, data, options || {});
486 });
487}
488/**
489 * Returns a reference to the callable https trigger with the given url.
490 * @param url - The url of the trigger.
491 * @public
492 */
493function httpsCallableFromURL$1(functionsInstance, url, options) {
494 return (data => {
495 return callAtURL(functionsInstance, url, data, options || {});
496 });
497}
498/**
499 * Does an HTTP POST and returns the completed response.
500 * @param url The url to post to.
501 * @param body The JSON body of the post.
502 * @param headers The HTTP headers to include in the request.
503 * @return A Promise that will succeed when the request finishes.
504 */
505async function postJSON(url, body, headers, fetchImpl) {
506 headers['Content-Type'] = 'application/json';
507 let response;
508 try {
509 response = await fetchImpl(url, {
510 method: 'POST',
511 body: JSON.stringify(body),
512 headers
513 });
514 }
515 catch (e) {
516 // This could be an unhandled error on the backend, or it could be a
517 // network error. There's no way to know, since an unhandled error on the
518 // backend will fail to set the proper CORS header, and thus will be
519 // treated as a network error by fetch.
520 return {
521 status: 0,
522 json: null
523 };
524 }
525 let json = null;
526 try {
527 json = await response.json();
528 }
529 catch (e) {
530 // If we fail to parse JSON, it will fail the same as an empty body.
531 }
532 return {
533 status: response.status,
534 json
535 };
536}
537/**
538 * Calls a callable function asynchronously and returns the result.
539 * @param name The name of the callable trigger.
540 * @param data The data to pass as params to the function.s
541 */
542function call(functionsInstance, name, data, options) {
543 const url = functionsInstance._url(name);
544 return callAtURL(functionsInstance, url, data, options);
545}
546/**
547 * Calls a callable function asynchronously and returns the result.
548 * @param url The url of the callable trigger.
549 * @param data The data to pass as params to the function.s
550 */
551async function callAtURL(functionsInstance, url, data, options) {
552 // Encode any special types, such as dates, in the input data.
553 data = encode(data);
554 const body = { data };
555 // Add a header for the authToken.
556 const headers = {};
557 const context = await functionsInstance.contextProvider.getContext();
558 if (context.authToken) {
559 headers['Authorization'] = 'Bearer ' + context.authToken;
560 }
561 if (context.messagingToken) {
562 headers['Firebase-Instance-ID-Token'] = context.messagingToken;
563 }
564 if (context.appCheckToken !== null) {
565 headers['X-Firebase-AppCheck'] = context.appCheckToken;
566 }
567 // Default timeout to 70s, but let the options override it.
568 const timeout = options.timeout || 70000;
569 const failAfterHandle = failAfter(timeout);
570 const response = await Promise.race([
571 postJSON(url, body, headers, functionsInstance.fetchImpl),
572 failAfterHandle.promise,
573 functionsInstance.cancelAllRequests
574 ]);
575 // Always clear the failAfter timeout
576 failAfterHandle.cancel();
577 // If service was deleted, interrupted response throws an error.
578 if (!response) {
579 throw new FunctionsError('cancelled', 'Firebase Functions instance was deleted.');
580 }
581 // Check for an error status, regardless of http status.
582 const error = _errorForResponse(response.status, response.json);
583 if (error) {
584 throw error;
585 }
586 if (!response.json) {
587 throw new FunctionsError('internal', 'Response is not valid JSON object.');
588 }
589 let responseData = response.json.data;
590 // TODO(klimt): For right now, allow "result" instead of "data", for
591 // backwards compatibility.
592 if (typeof responseData === 'undefined') {
593 responseData = response.json.result;
594 }
595 if (typeof responseData === 'undefined') {
596 // Consider the response malformed.
597 throw new FunctionsError('internal', 'Response is missing data field.');
598 }
599 // Decode any special types, such as dates, in the returned data.
600 const decodedData = decode(responseData);
601 return { data: decodedData };
602}
603
604const name = "@firebase/functions";
605const version = "0.8.8";
606
607/**
608 * @license
609 * Copyright 2019 Google LLC
610 *
611 * Licensed under the Apache License, Version 2.0 (the "License");
612 * you may not use this file except in compliance with the License.
613 * You may obtain a copy of the License at
614 *
615 * http://www.apache.org/licenses/LICENSE-2.0
616 *
617 * Unless required by applicable law or agreed to in writing, software
618 * distributed under the License is distributed on an "AS IS" BASIS,
619 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
620 * See the License for the specific language governing permissions and
621 * limitations under the License.
622 */
623const AUTH_INTERNAL_NAME = 'auth-internal';
624const APP_CHECK_INTERNAL_NAME = 'app-check-internal';
625const MESSAGING_INTERNAL_NAME = 'messaging-internal';
626function registerFunctions(fetchImpl, variant) {
627 const factory = (container, { instanceIdentifier: regionOrCustomDomain }) => {
628 // Dependencies
629 const app = container.getProvider('app').getImmediate();
630 const authProvider = container.getProvider(AUTH_INTERNAL_NAME);
631 const messagingProvider = container.getProvider(MESSAGING_INTERNAL_NAME);
632 const appCheckProvider = container.getProvider(APP_CHECK_INTERNAL_NAME);
633 // eslint-disable-next-line @typescript-eslint/no-explicit-any
634 return new FunctionsService(app, authProvider, messagingProvider, appCheckProvider, regionOrCustomDomain, fetchImpl);
635 };
636 _registerComponent(new Component(FUNCTIONS_TYPE, factory, "PUBLIC" /* PUBLIC */).setMultipleInstances(true));
637 registerVersion(name, version, variant);
638 // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation
639 registerVersion(name, version, 'esm2017');
640}
641
642/**
643 * @license
644 * Copyright 2020 Google LLC
645 *
646 * Licensed under the Apache License, Version 2.0 (the "License");
647 * you may not use this file except in compliance with the License.
648 * You may obtain a copy of the License at
649 *
650 * http://www.apache.org/licenses/LICENSE-2.0
651 *
652 * Unless required by applicable law or agreed to in writing, software
653 * distributed under the License is distributed on an "AS IS" BASIS,
654 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
655 * See the License for the specific language governing permissions and
656 * limitations under the License.
657 */
658/**
659 * Returns a {@link Functions} instance for the given app.
660 * @param app - The {@link @firebase/app#FirebaseApp} to use.
661 * @param regionOrCustomDomain - one of:
662 * a) The region the callable functions are located in (ex: us-central1)
663 * b) A custom domain hosting the callable functions (ex: https://mydomain.com)
664 * @public
665 */
666function getFunctions(app = getApp(), regionOrCustomDomain = DEFAULT_REGION) {
667 // Dependencies
668 const functionsProvider = _getProvider(getModularInstance(app), FUNCTIONS_TYPE);
669 const functionsInstance = functionsProvider.getImmediate({
670 identifier: regionOrCustomDomain
671 });
672 const emulator = getDefaultEmulatorHostnameAndPort('functions');
673 if (emulator) {
674 connectFunctionsEmulator(functionsInstance, ...emulator);
675 }
676 return functionsInstance;
677}
678/**
679 * Modify this instance to communicate with the Cloud Functions emulator.
680 *
681 * Note: this must be called before this instance has been used to do any operations.
682 *
683 * @param host - The emulator host (ex: localhost)
684 * @param port - The emulator port (ex: 5001)
685 * @public
686 */
687function connectFunctionsEmulator(functionsInstance, host, port) {
688 connectFunctionsEmulator$1(getModularInstance(functionsInstance), host, port);
689}
690/**
691 * Returns a reference to the callable HTTPS trigger with the given name.
692 * @param name - The name of the trigger.
693 * @public
694 */
695function httpsCallable(functionsInstance, name, options) {
696 return httpsCallable$1(getModularInstance(functionsInstance), name, options);
697}
698/**
699 * Returns a reference to the callable HTTPS trigger with the specified url.
700 * @param url - The url of the trigger.
701 * @public
702 */
703function httpsCallableFromURL(functionsInstance, url, options) {
704 return httpsCallableFromURL$1(getModularInstance(functionsInstance), url, options);
705}
706
707/**
708 * Cloud Functions for Firebase
709 *
710 * @packageDocumentation
711 */
712registerFunctions(fetch.bind(self));
713
714export { connectFunctionsEmulator, getFunctions, httpsCallable, httpsCallableFromURL };
715//# sourceMappingURL=index.esm2017.js.map