firebase-admin
Version:
Firebase admin SDK for Node.js
227 lines (226 loc) • 10.4 kB
JavaScript
/*! firebase-admin v14.2.0 */
"use strict";
/*!
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskQueue = exports.Functions = void 0;
const functions_api_client_internal_1 = require("./functions-api-client-internal");
const error_1 = require("./error");
const validator = require("../utils/validator");
/**
* The Firebase `Functions` service interface.
*/
class Functions {
/**
* @param app - The app for this `Functions` service.
* @constructor
* @internal
*/
constructor(app) {
this.app = app;
this.client = new functions_api_client_internal_1.FunctionsApiClient(app);
}
taskQueue(functionName, extensionIdOrScope = { scope: 'current' }) {
const scope = typeof extensionIdOrScope === 'string'
? (extensionIdOrScope === ''
? { scope: 'current' }
: { scope: 'extensionOrKit', instance: extensionIdOrScope })
: extensionIdOrScope;
return new TaskQueue(functionName, this.client, scope);
}
}
exports.Functions = Functions;
/**
* The `TaskQueue` interface.
*/
class TaskQueue {
/**
* @param functionName - The name of the function.
* @param client - The `FunctionsApiClient` instance.
* @param extensionIdOrScope - Optional canonical ID of the extension or `FunctionScope`.
* @constructor
* @internal
*/
constructor(functionName, client, extensionIdOrScope = { scope: 'current' }) {
this.functionName = functionName;
this.client = client;
if (!validator.isNonEmptyString(functionName)) {
throw new error_1.FirebaseFunctionsError({
code: 'invalid-argument',
message: '`functionName` must be a non-empty string.'
});
}
if (!validator.isNonNullObject(client) || !('enqueue' in client)) {
throw new error_1.FirebaseFunctionsError({
code: 'invalid-argument',
message: 'Must provide a valid FunctionsApiClient instance to create a new TaskQueue.'
});
}
if (typeof extensionIdOrScope === 'string') {
this.scope = extensionIdOrScope === ''
? { scope: 'current' }
: { scope: 'extensionOrKit', instance: extensionIdOrScope };
}
else if (validator.isNonNullObject(extensionIdOrScope)) {
const scope = extensionIdOrScope.scope;
if (scope === 'current' || scope === 'global') {
this.scope = extensionIdOrScope;
}
else if (scope === 'extension' || scope === 'kit' || scope === 'extensionOrKit') {
const instance = extensionIdOrScope.instance;
if (!validator.isNonEmptyString(instance)) {
throw new error_1.FirebaseFunctionsError({
code: 'invalid-argument',
message: `\`instance\` must be a non-empty string for scope "${scope}".`
});
}
this.scope = extensionIdOrScope;
}
else {
// TODO: Update error message to include 'kit' when kit type becomes public.
throw new error_1.FirebaseFunctionsError({
code: 'invalid-argument',
message: '`scope` must be one of "current", "global", or "extension".'
});
}
}
else {
throw new error_1.FirebaseFunctionsError({
code: 'invalid-argument',
message: '`extensionIdOrScope` must be a string or a FunctionScope object.'
});
}
if (this.scope.scope === 'extensionOrKit') {
const instance = this.scope.instance;
const extInstanceId = process.env.EXT_INSTANCE_ID;
const kitInstanceId = process.env.FIREBASE_KIT_INSTANCE_ID;
if (validator.isNonEmptyString(extInstanceId) && extInstanceId === instance) {
this.scope.scope = 'extension';
console.warn('Targeting your own extension no longer requires a second parameter. ' +
`Please change the call taskQueue('${functionName}', '${instance}') to taskQueue('${functionName}')`);
}
else if (validator.isNonEmptyString(kitInstanceId) && kitInstanceId === instance) {
this.scope.scope = 'kit';
console.warn('Targeting your own extension or kit no longer requires a second parameter, ' +
'which can have performance implications. Please change the call ' +
`taskQueue('${functionName}', '${instance}') to taskQueue('${functionName}') ` +
`or taskQueue('${functionName}', { scope: "current" })`);
}
}
}
/**
* Creates a task and adds it to the queue. Tasks cannot be updated after creation.
* This action requires `cloudtasks.tasks.create` IAM permission on the service account.
*
* @param data - The data payload of the task.
* @param opts - Optional options when enqueuing a new task.
* @returns A promise that resolves when the task has successfully been added to the queue.
*/
async enqueue(data, opts) {
if (this.scope.scope !== 'extensionOrKit') {
await this.client.enqueue(data, this.functionName, this.scope, opts);
return;
}
// Note: The extensionOrKit scope and the associated fallback logic exist purely
// to support migration from legacy extension ID string parameters, and can be
// removed in the next major version release after extensions turndown.
try {
await this.client.enqueue(data, this.functionName, this.scope, opts);
}
catch (err) {
const isNotFound = err.code === 'not-found' ||
(err instanceof error_1.FirebaseFunctionsError && err.httpResponse?.status === 404);
if (isNotFound) {
const tempKitScope = { scope: 'kit', instance: this.scope.instance };
await this.client.enqueue(data, this.functionName, tempKitScope, opts);
// Only upgrade the stateful scope to kit if the retry request succeeds
this.scope.scope = 'kit';
this.logFallbackWarning(this.functionName, this.scope.instance);
return;
}
throw err;
}
}
/**
* Deletes an enqueued task if it has not yet completed.
* @param id - the ID of the task, relative to this queue.
* @returns A promise that resolves when the task has been deleted.
*/
async delete(id) {
if (this.scope.scope !== 'extensionOrKit') {
try {
await this.client.delete(id, this.functionName, this.scope);
}
catch (err) {
const isNotFound = err.code === 'not-found' ||
(err instanceof error_1.FirebaseFunctionsError && err.httpResponse?.status === 404);
if (isNotFound) {
return;
}
throw err;
}
return;
}
// Note: The extensionOrKit scope and the associated fallback logic exist purely
// to support migration from legacy extension ID string parameters, and can be
// removed in the next major version release after extensions turndown.
try {
await this.client.delete(id, this.functionName, this.scope);
}
catch (err) {
const isNotFound = err.code === 'not-found' ||
(err instanceof error_1.FirebaseFunctionsError && err.httpResponse?.status === 404);
if (isNotFound) {
try {
const tempKitScope = { scope: 'kit', instance: this.scope.instance };
await this.client.delete(id, this.functionName, tempKitScope);
// Only upgrade the stateful scope to kit if the retry request succeeds
this.scope.scope = 'kit';
this.logFallbackWarning(this.functionName, this.scope.instance);
}
catch (retryErr) {
const isRetryNotFound = retryErr.code === 'not-found' ||
(retryErr instanceof error_1.FirebaseFunctionsError && retryErr.httpResponse?.status === 404);
if (isRetryNotFound) {
// Ignore if both fail with not-found
return;
}
throw retryErr;
}
return;
}
throw err;
}
}
logFallbackWarning(functionName, instance) {
const kitInstanceId = process.env.FIREBASE_KIT_INSTANCE_ID;
if (validator.isNonEmptyString(kitInstanceId) && kitInstanceId === instance) {
// Note: It is OK to warn about kits here because targeting a kit requires the kit to be deployed first.
console.warn('Targeting your own extension or kit no longer requires a second parameter, ' +
'which can have performance implications. Please change the call ' +
`taskQueue('${functionName}', '${instance}') to taskQueue('${functionName}') ` +
`or taskQueue('${functionName}', { scope: "current" })`);
}
else {
console.warn(`Targeting kit ${instance} with the legacy extensions API, ` +
'which has performance implications. Please change the call ' +
`taskQueue('${functionName}', '${instance}') to ` +
`taskQueue('${functionName}', { scope: "kit", instance: '${instance}' })`);
}
}
}
exports.TaskQueue = TaskQueue;