UNPKG

firebase-admin

Version:
216 lines (215 loc) 9.06 kB
/*! firebase-admin v13.3.0 */ "use strict"; /*! * @license * Copyright 2024 Google Inc. * * 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.FirebaseDataConnectError = exports.DATA_CONNECT_ERROR_CODE_MAPPING = exports.DataConnectApiClient = void 0; exports.useEmulator = useEmulator; const api_request_1 = require("../utils/api-request"); const error_1 = require("../utils/error"); const utils = require("../utils/index"); const validator = require("../utils/validator"); const API_VERSION = 'v1alpha'; /** The Firebase Data Connect backend base URL format. */ const FIREBASE_DATA_CONNECT_BASE_URL_FORMAT = 'https://firebasedataconnect.googleapis.com/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}'; /** Firebase Data Connect base URl format when using the Data Connect emultor. */ const FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT = 'http://{host}/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}'; const EXECUTE_GRAPH_QL_ENDPOINT = 'executeGraphql'; const EXECUTE_GRAPH_QL_READ_ENDPOINT = 'executeGraphqlRead'; const DATA_CONNECT_CONFIG_HEADERS = { 'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}` }; /** * Class that facilitates sending requests to the Firebase Data Connect backend API. * * @internal */ class DataConnectApiClient { constructor(connectorConfig, app) { this.connectorConfig = connectorConfig; this.app = app; if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseDataConnectError(exports.DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'First argument passed to getDataConnect() must be a valid Firebase app instance.'); } this.httpClient = new DataConnectHttpClient(app); } /** * Execute arbitrary GraphQL, including both read and write queries * * @param query - The GraphQL string to be executed. * @param options - GraphQL Options * @returns A promise that fulfills with a `ExecuteGraphqlResponse`. */ async executeGraphql(query, options) { return this.executeGraphqlHelper(query, EXECUTE_GRAPH_QL_ENDPOINT, options); } /** * Execute arbitrary read-only GraphQL queries * * @param query - The GraphQL (read-only) string to be executed. * @param options - GraphQL Options * @returns A promise that fulfills with a `ExecuteGraphqlResponse`. * @throws FirebaseDataConnectError */ async executeGraphqlRead(query, options) { return this.executeGraphqlHelper(query, EXECUTE_GRAPH_QL_READ_ENDPOINT, options); } async executeGraphqlHelper(query, endpoint, options) { if (!validator.isNonEmptyString(query)) { throw new FirebaseDataConnectError(exports.DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT, '`query` must be a non-empty string.'); } if (typeof options !== 'undefined') { if (!validator.isNonNullObject(options)) { throw new FirebaseDataConnectError(exports.DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'GraphqlOptions must be a non-null object'); } } const data = { query, ...(options?.variables && { variables: options?.variables }), ...(options?.operationName && { operationName: options?.operationName }), ...(options?.impersonate && { extensions: { impersonate: options?.impersonate } }), }; return this.getUrl(API_VERSION, this.connectorConfig.location, this.connectorConfig.serviceId, endpoint) .then(async (url) => { const request = { method: 'POST', url, headers: DATA_CONNECT_CONFIG_HEADERS, data, }; const resp = await this.httpClient.send(request); if (resp.data.errors && validator.isNonEmptyArray(resp.data.errors)) { const allMessages = resp.data.errors.map((error) => error.message).join(' '); throw new FirebaseDataConnectError(exports.DATA_CONNECT_ERROR_CODE_MAPPING.QUERY_ERROR, allMessages); } return Promise.resolve({ data: resp.data.data, }); }) .then((resp) => { return resp; }) .catch((err) => { throw this.toFirebaseError(err); }); } async getUrl(version, locationId, serviceId, endpointId) { return this.getProjectId() .then((projectId) => { const urlParams = { version, projectId, locationId, serviceId, endpointId }; let urlFormat; if (useEmulator()) { urlFormat = utils.formatString(FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT, { host: emulatorHost() }); } else { urlFormat = FIREBASE_DATA_CONNECT_BASE_URL_FORMAT; } return utils.formatString(urlFormat, urlParams); }); } getProjectId() { if (this.projectId) { return Promise.resolve(this.projectId); } return utils.findProjectId(this.app) .then((projectId) => { if (!validator.isNonEmptyString(projectId)) { throw new FirebaseDataConnectError(exports.DATA_CONNECT_ERROR_CODE_MAPPING.UNKNOWN, 'Failed to determine project ID. Initialize the ' + 'SDK with service account credentials or set project ID as an app option. ' + 'Alternatively, set the GOOGLE_CLOUD_PROJECT environment variable.'); } this.projectId = projectId; return projectId; }); } toFirebaseError(err) { if (err instanceof error_1.PrefixedFirebaseError) { return err; } const response = err.response; if (!response.isJson()) { return new FirebaseDataConnectError(exports.DATA_CONNECT_ERROR_CODE_MAPPING.UNKNOWN, `Unexpected response with status: ${response.status} and body: ${response.text}`); } const error = response.data.error || {}; let code = exports.DATA_CONNECT_ERROR_CODE_MAPPING.UNKNOWN; if (error.status && error.status in exports.DATA_CONNECT_ERROR_CODE_MAPPING) { code = exports.DATA_CONNECT_ERROR_CODE_MAPPING[error.status]; } const message = error.message || `Unknown server error: ${response.text}`; return new FirebaseDataConnectError(code, message); } } exports.DataConnectApiClient = DataConnectApiClient; /** * Data Connect-specific HTTP client which uses the special "owner" token * when communicating with the Data Connect Emulator. */ class DataConnectHttpClient extends api_request_1.AuthorizedHttpClient { getToken() { if (useEmulator()) { return Promise.resolve('owner'); } return super.getToken(); } } function emulatorHost() { return process.env.DATA_CONNECT_EMULATOR_HOST; } /** * When true the SDK should communicate with the Data Connect Emulator for all API * calls and also produce unsigned tokens. */ function useEmulator() { return !!emulatorHost(); } exports.DATA_CONNECT_ERROR_CODE_MAPPING = { ABORTED: 'aborted', INVALID_ARGUMENT: 'invalid-argument', INVALID_CREDENTIAL: 'invalid-credential', INTERNAL: 'internal-error', PERMISSION_DENIED: 'permission-denied', UNAUTHENTICATED: 'unauthenticated', NOT_FOUND: 'not-found', UNKNOWN: 'unknown-error', QUERY_ERROR: 'query-error', }; /** * Firebase Data Connect error code structure. This extends PrefixedFirebaseError. * * @param code - The error code. * @param message - The error message. * @constructor */ class FirebaseDataConnectError extends error_1.PrefixedFirebaseError { constructor(code, message) { super('data-connect', code, message); /* tslint:disable:max-line-length */ // Set the prototype explicitly. See the following link for more details: // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work /* tslint:enable:max-line-length */ this.__proto__ = FirebaseDataConnectError.prototype; } } exports.FirebaseDataConnectError = FirebaseDataConnectError;