'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var app = require('@firebase/app'); var component = require('@firebase/component'); var tslib = require('tslib'); var util = require('@firebase/util'); var idb = require('idb'); var name = "@firebase/installations"; var version = "0.6.5"; /** * @license * Copyright 2019 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. */ var PENDING_TIMEOUT_MS = 10000; var PACKAGE_VERSION = "w:".concat(version); var INTERNAL_AUTH_VERSION = 'FIS_v2'; var INSTALLATIONS_API_URL = 'https://firebaseinstallations.googleapis.com/v1'; var TOKEN_EXPIRATION_BUFFER = 60 * 60 * 1000; // One hour var SERVICE = 'installations'; var SERVICE_NAME = 'Installations'; /** * @license * Copyright 2019 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. */ var _a; var ERROR_DESCRIPTION_MAP = (_a = {}, _a["missing-app-config-values" /* ErrorCode.MISSING_APP_CONFIG_VALUES */] = 'Missing App configuration value: "{$valueName}"', _a["not-registered" /* ErrorCode.NOT_REGISTERED */] = 'Firebase Installation is not registered.', _a["installation-not-found" /* ErrorCode.INSTALLATION_NOT_FOUND */] = 'Firebase Installation not found.', _a["request-failed" /* ErrorCode.REQUEST_FAILED */] = '{$requestName} request failed with error "{$serverCode} {$serverStatus}: {$serverMessage}"', _a["app-offline" /* ErrorCode.APP_OFFLINE */] = 'Could not process request. Application offline.', _a["delete-pending-registration" /* ErrorCode.DELETE_PENDING_REGISTRATION */] = "Can't delete installation while there is a pending registration request.", _a); var ERROR_FACTORY = new util.ErrorFactory(SERVICE, SERVICE_NAME, ERROR_DESCRIPTION_MAP); /** Returns true if error is a FirebaseError that is based on an error from the server. */ function isServerError(error) { return (error instanceof util.FirebaseError && error.code.includes("request-failed" /* ErrorCode.REQUEST_FAILED */)); } /** * @license * Copyright 2019 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. */ function getInstallationsEndpoint(_a) { var projectId = _a.projectId; return "".concat(INSTALLATIONS_API_URL, "/projects/").concat(projectId, "/installations"); } function extractAuthTokenInfoFromResponse(response) { return { token: response.token, requestStatus: 2 /* RequestStatus.COMPLETED */, expiresIn: getExpiresInFromResponseExpiresIn(response.expiresIn), creationTime: Date.now() }; } function getErrorFromResponse(requestName, response) { return tslib.__awaiter(this, void 0, void 0, function () { var responseJson, errorData; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, response.json()]; case 1: responseJson = _a.sent(); errorData = responseJson.error; return [2 /*return*/, ERROR_FACTORY.create("request-failed" /* ErrorCode.REQUEST_FAILED */, { requestName: requestName, serverCode: errorData.code, serverMessage: errorData.message, serverStatus: errorData.status })]; } }); }); } function getHeaders(_a) { var apiKey = _a.apiKey; return new Headers({ 'Content-Type': 'application/json', Accept: 'application/json', 'x-goog-api-key': apiKey }); } function getHeadersWithAuth(appConfig, _a) { var refreshToken = _a.refreshToken; var headers = getHeaders(appConfig); headers.append('Authorization', getAuthorizationHeader(refreshToken)); return headers; } /** * Calls the passed in fetch wrapper and returns the response. * If the returned response has a status of 5xx, re-runs the function once and * returns the response. */ function retryIfServerError(fn) { return tslib.__awaiter(this, void 0, void 0, function () { var result; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, fn()]; case 1: result = _a.sent(); if (result.status >= 500 && result.status < 600) { // Internal Server Error. Retry request. return [2 /*return*/, fn()]; } return [2 /*return*/, result]; } }); }); } function getExpiresInFromResponseExpiresIn(responseExpiresIn) { // This works because the server will never respond with fractions of a second. return Number(responseExpiresIn.replace('s', '000')); } function getAuthorizationHeader(refreshToken) { return "".concat(INTERNAL_AUTH_VERSION, " ").concat(refreshToken); } /** * @license * Copyright 2019 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. */ function createInstallationRequest(_a, _b) { var appConfig = _a.appConfig, heartbeatServiceProvider = _a.heartbeatServiceProvider; var fid = _b.fid; return tslib.__awaiter(this, void 0, void 0, function () { var endpoint, headers, heartbeatService, heartbeatsHeader, body, request, response, responseValue, registeredInstallationEntry; return tslib.__generator(this, function (_c) { switch (_c.label) { case 0: endpoint = getInstallationsEndpoint(appConfig); headers = getHeaders(appConfig); heartbeatService = heartbeatServiceProvider.getImmediate({ optional: true }); if (!heartbeatService) return [3 /*break*/, 2]; return [4 /*yield*/, heartbeatService.getHeartbeatsHeader()]; case 1: heartbeatsHeader = _c.sent(); if (heartbeatsHeader) { headers.append('x-firebase-client', heartbeatsHeader); } _c.label = 2; case 2: body = { fid: fid, authVersion: INTERNAL_AUTH_VERSION, appId: appConfig.appId, sdkVersion: PACKAGE_VERSION }; request = { method: 'POST', headers: headers, body: JSON.stringify(body) }; return [4 /*yield*/, retryIfServerError(function () { return fetch(endpoint, request); })]; case 3: response = _c.sent(); if (!response.ok) return [3 /*break*/, 5]; return [4 /*yield*/, response.json()]; case 4: responseValue = _c.sent(); registeredInstallationEntry = { fid: responseValue.fid || fid, registrationStatus: 2 /* RequestStatus.COMPLETED */, refreshToken: responseValue.refreshToken, authToken: extractAuthTokenInfoFromResponse(responseValue.authToken) }; return [2 /*return*/, registeredInstallationEntry]; case 5: return [4 /*yield*/, getErrorFromResponse('Create Installation', response)]; case 6: throw _c.sent(); } }); }); } /** * @license * Copyright 2019 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. */ /** Returns a promise that resolves after given time passes. */ function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } /** * @license * Copyright 2019 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. */ function bufferToBase64UrlSafe(array) { var b64 = btoa(String.fromCharCode.apply(String, tslib.__spreadArray([], tslib.__read(array), false))); return b64.replace(/\+/g, '-').replace(/\//g, '_'); } /** * @license * Copyright 2019 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. */ var VALID_FID_PATTERN = /^[cdef][\w-]{21}$/; var INVALID_FID = ''; /** * Generates a new FID using random values from Web Crypto API. * Returns an empty string if FID generation fails for any reason. */ function generateFid() { try { // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 // bytes. our implementation generates a 17 byte array instead. var fidByteArray = new Uint8Array(17); var crypto_1 = self.crypto || self.msCrypto; crypto_1.getRandomValues(fidByteArray); // Replace the first 4 random bits with the constant FID header of 0b0111. fidByteArray[0] = 112 + (fidByteArray[0] % 16); var fid = encode(fidByteArray); return VALID_FID_PATTERN.test(fid) ? fid : INVALID_FID; } catch (_a) { // FID generation errored return INVALID_FID; } } /** Converts a FID Uint8Array to a base64 string representation. */ function encode(fidByteArray) { var b64String = bufferToBase64UrlSafe(fidByteArray); // Remove the 23rd character that was added because of the extra 4 bits at the // end of our 17 byte array, and the '=' padding. return b64String.substr(0, 22); } /** * @license * Copyright 2019 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. */ /** Returns a string key that can be used to identify the app. */ function getKey(appConfig) { return "".concat(appConfig.appName, "!").concat(appConfig.appId); } /** * @license * Copyright 2019 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. */ var fidChangeCallbacks = new Map(); /** * Calls the onIdChange callbacks with the new FID value, and broadcasts the * change to other tabs. */ function fidChanged(appConfig, fid) { var key = getKey(appConfig); callFidChangeCallbacks(key, fid); broadcastFidChange(key, fid); } function addCallback(appConfig, callback) { // Open the broadcast channel if it's not already open, // to be able to listen to change events from other tabs. getBroadcastChannel(); var key = getKey(appConfig); var callbackSet = fidChangeCallbacks.get(key); if (!callbackSet) { callbackSet = new Set(); fidChangeCallbacks.set(key, callbackSet); } callbackSet.add(callback); } function removeCallback(appConfig, callback) { var key = getKey(appConfig); var callbackSet = fidChangeCallbacks.get(key); if (!callbackSet) { return; } callbackSet.delete(callback); if (callbackSet.size === 0) { fidChangeCallbacks.delete(key); } // Close broadcast channel if there are no more callbacks. closeBroadcastChannel(); } function callFidChangeCallbacks(key, fid) { var e_1, _a; var callbacks = fidChangeCallbacks.get(key); if (!callbacks) { return; } try { for (var callbacks_1 = tslib.__values(callbacks), callbacks_1_1 = callbacks_1.next(); !callbacks_1_1.done; callbacks_1_1 = callbacks_1.next()) { var callback = callbacks_1_1.value; callback(fid); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (callbacks_1_1 && !callbacks_1_1.done && (_a = callbacks_1.return)) _a.call(callbacks_1); } finally { if (e_1) throw e_1.error; } } } function broadcastFidChange(key, fid) { var channel = getBroadcastChannel(); if (channel) { channel.postMessage({ key: key, fid: fid }); } closeBroadcastChannel(); } var broadcastChannel = null; /** Opens and returns a BroadcastChannel if it is supported by the browser. */ function getBroadcastChannel() { if (!broadcastChannel && 'BroadcastChannel' in self) { broadcastChannel = new BroadcastChannel('[Firebase] FID Change'); broadcastChannel.onmessage = function (e) { callFidChangeCallbacks(e.data.key, e.data.fid); }; } return broadcastChannel; } function closeBroadcastChannel() { if (fidChangeCallbacks.size === 0 && broadcastChannel) { broadcastChannel.close(); broadcastChannel = null; } } /** * @license * Copyright 2019 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. */ var DATABASE_NAME = 'firebase-installations-database'; var DATABASE_VERSION = 1; var OBJECT_STORE_NAME = 'firebase-installations-store'; var dbPromise = null; function getDbPromise() { if (!dbPromise) { dbPromise = idb.openDB(DATABASE_NAME, DATABASE_VERSION, { upgrade: function (db, oldVersion) { // We don't use 'break' in this switch statement, the fall-through // behavior is what we want, because if there are multiple versions between // the old version and the current version, we want ALL the migrations // that correspond to those versions to run, not only the last one. // eslint-disable-next-line default-case switch (oldVersion) { case 0: db.createObjectStore(OBJECT_STORE_NAME); } } }); } return dbPromise; } /** Assigns or overwrites the record for the given key with the given value. */ function set(appConfig, value) { return tslib.__awaiter(this, void 0, void 0, function () { var key, db, tx, objectStore, oldValue; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: key = getKey(appConfig); return [4 /*yield*/, getDbPromise()]; case 1: db = _a.sent(); tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); objectStore = tx.objectStore(OBJECT_STORE_NAME); return [4 /*yield*/, objectStore.get(key)]; case 2: oldValue = (_a.sent()); return [4 /*yield*/, objectStore.put(value, key)]; case 3: _a.sent(); return [4 /*yield*/, tx.done]; case 4: _a.sent(); if (!oldValue || oldValue.fid !== value.fid) { fidChanged(appConfig, value.fid); } return [2 /*return*/, value]; } }); }); } /** Removes record(s) from the objectStore that match the given key. */ function remove(appConfig) { return tslib.__awaiter(this, void 0, void 0, function () { var key, db, tx; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: key = getKey(appConfig); return [4 /*yield*/, getDbPromise()]; case 1: db = _a.sent(); tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); return [4 /*yield*/, tx.objectStore(OBJECT_STORE_NAME).delete(key)]; case 2: _a.sent(); return [4 /*yield*/, tx.done]; case 3: _a.sent(); return [2 /*return*/]; } }); }); } /** * Atomically updates a record with the result of updateFn, which gets * called with the current value. If newValue is undefined, the record is * deleted instead. * @return Updated value */ function update(appConfig, updateFn) { return tslib.__awaiter(this, void 0, void 0, function () { var key, db, tx, store, oldValue, newValue; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: key = getKey(appConfig); return [4 /*yield*/, getDbPromise()]; case 1: db = _a.sent(); tx = db.transaction(OBJECT_STORE_NAME, 'readwrite'); store = tx.objectStore(OBJECT_STORE_NAME); return [4 /*yield*/, store.get(key)]; case 2: oldValue = (_a.sent()); newValue = updateFn(oldValue); if (!(newValue === undefined)) return [3 /*break*/, 4]; return [4 /*yield*/, store.delete(key)]; case 3: _a.sent(); return [3 /*break*/, 6]; case 4: return [4 /*yield*/, store.put(newValue, key)]; case 5: _a.sent(); _a.label = 6; case 6: return [4 /*yield*/, tx.done]; case 7: _a.sent(); if (newValue && (!oldValue || oldValue.fid !== newValue.fid)) { fidChanged(appConfig, newValue.fid); } return [2 /*return*/, newValue]; } }); }); } /** * @license * Copyright 2019 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. */ /** * Updates and returns the InstallationEntry from the database. * Also triggers a registration request if it is necessary and possible. */ function getInstallationEntry(installations) { return tslib.__awaiter(this, void 0, void 0, function () { var registrationPromise, installationEntry; var _a; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, update(installations.appConfig, function (oldEntry) { var installationEntry = updateOrCreateInstallationEntry(oldEntry); var entryWithPromise = triggerRegistrationIfNecessary(installations, installationEntry); registrationPromise = entryWithPromise.registrationPromise; return entryWithPromise.installationEntry; })]; case 1: installationEntry = _b.sent(); if (!(installationEntry.fid === INVALID_FID)) return [3 /*break*/, 3]; _a = {}; return [4 /*yield*/, registrationPromise]; case 2: // FID generation failed. Waiting for the FID from the server. return [2 /*return*/, (_a.installationEntry = _b.sent(), _a)]; case 3: return [2 /*return*/, { installationEntry: installationEntry, registrationPromise: registrationPromise }]; } }); }); } /** * Creates a new Installation Entry if one does not exist. * Also clears timed out pending requests. */ function updateOrCreateInstallationEntry(oldEntry) { var entry = oldEntry || { fid: generateFid(), registrationStatus: 0 /* RequestStatus.NOT_STARTED */ }; return clearTimedOutRequest(entry); } /** * If the Firebase Installation is not registered yet, this will trigger the * registration and return an InProgressInstallationEntry. * * If registrationPromise does not exist, the installationEntry is guaranteed * to be registered. */ function triggerRegistrationIfNecessary(installations, installationEntry) { if (installationEntry.registrationStatus === 0 /* RequestStatus.NOT_STARTED */) { if (!navigator.onLine) { // Registration required but app is offline. var registrationPromiseWithError = Promise.reject(ERROR_FACTORY.create("app-offline" /* ErrorCode.APP_OFFLINE */)); return { installationEntry: installationEntry, registrationPromise: registrationPromiseWithError }; } // Try registering. Change status to IN_PROGRESS. var inProgressEntry = { fid: installationEntry.fid, registrationStatus: 1 /* RequestStatus.IN_PROGRESS */, registrationTime: Date.now() }; var registrationPromise = registerInstallation(installations, inProgressEntry); return { installationEntry: inProgressEntry, registrationPromise: registrationPromise }; } else if (installationEntry.registrationStatus === 1 /* RequestStatus.IN_PROGRESS */) { return { installationEntry: installationEntry, registrationPromise: waitUntilFidRegistration(installations) }; } else { return { installationEntry: installationEntry }; } } /** This will be executed only once for each new Firebase Installation. */ function registerInstallation(installations, installationEntry) { return tslib.__awaiter(this, void 0, void 0, function () { var registeredInstallationEntry, e_1; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 7]); return [4 /*yield*/, createInstallationRequest(installations, installationEntry)]; case 1: registeredInstallationEntry = _a.sent(); return [2 /*return*/, set(installations.appConfig, registeredInstallationEntry)]; case 2: e_1 = _a.sent(); if (!(isServerError(e_1) && e_1.customData.serverCode === 409)) return [3 /*break*/, 4]; // Server returned a "FID can not be used" error. // Generate a new ID next time. return [4 /*yield*/, remove(installations.appConfig)]; case 3: // Server returned a "FID can not be used" error. // Generate a new ID next time. _a.sent(); return [3 /*break*/, 6]; case 4: // Registration failed. Set FID as not registered. return [4 /*yield*/, set(installations.appConfig, { fid: installationEntry.fid, registrationStatus: 0 /* RequestStatus.NOT_STARTED */ })]; case 5: // Registration failed. Set FID as not registered. _a.sent(); _a.label = 6; case 6: throw e_1; case 7: return [2 /*return*/]; } }); }); } /** Call if FID registration is pending in another request. */ function waitUntilFidRegistration(installations) { return tslib.__awaiter(this, void 0, void 0, function () { var entry, _a, installationEntry, registrationPromise; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, updateInstallationRequest(installations.appConfig)]; case 1: entry = _b.sent(); _b.label = 2; case 2: if (!(entry.registrationStatus === 1 /* RequestStatus.IN_PROGRESS */)) return [3 /*break*/, 5]; // createInstallation request still in progress. return [4 /*yield*/, sleep(100)]; case 3: // createInstallation request still in progress. _b.sent(); return [4 /*yield*/, updateInstallationRequest(installations.appConfig)]; case 4: entry = _b.sent(); return [3 /*break*/, 2]; case 5: if (!(entry.registrationStatus === 0 /* RequestStatus.NOT_STARTED */)) return [3 /*break*/, 7]; return [4 /*yield*/, getInstallationEntry(installations)]; case 6: _a = _b.sent(), installationEntry = _a.installationEntry, registrationPromise = _a.registrationPromise; if (registrationPromise) { return [2 /*return*/, registrationPromise]; } else { // if there is no registrationPromise, entry is registered. return [2 /*return*/, installationEntry]; } case 7: return [2 /*return*/, entry]; } }); }); } /** * Called only if there is a CreateInstallation request in progress. * * Updates the InstallationEntry in the DB based on the status of the * CreateInstallation request. * * Returns the updated InstallationEntry. */ function updateInstallationRequest(appConfig) { return update(appConfig, function (oldEntry) { if (!oldEntry) { throw ERROR_FACTORY.create("installation-not-found" /* ErrorCode.INSTALLATION_NOT_FOUND */); } return clearTimedOutRequest(oldEntry); }); } function clearTimedOutRequest(entry) { if (hasInstallationRequestTimedOut(entry)) { return { fid: entry.fid, registrationStatus: 0 /* RequestStatus.NOT_STARTED */ }; } return entry; } function hasInstallationRequestTimedOut(installationEntry) { return (installationEntry.registrationStatus === 1 /* RequestStatus.IN_PROGRESS */ && installationEntry.registrationTime + PENDING_TIMEOUT_MS < Date.now()); } /** * @license * Copyright 2019 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. */ function generateAuthTokenRequest(_a, installationEntry) { var appConfig = _a.appConfig, heartbeatServiceProvider = _a.heartbeatServiceProvider; return tslib.__awaiter(this, void 0, void 0, function () { var endpoint, headers, heartbeatService, heartbeatsHeader, body, request, response, responseValue, completedAuthToken; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: endpoint = getGenerateAuthTokenEndpoint(appConfig, installationEntry); headers = getHeadersWithAuth(appConfig, installationEntry); heartbeatService = heartbeatServiceProvider.getImmediate({ optional: true }); if (!heartbeatService) return [3 /*break*/, 2]; return [4 /*yield*/, heartbeatService.getHeartbeatsHeader()]; case 1: heartbeatsHeader = _b.sent(); if (heartbeatsHeader) { headers.append('x-firebase-client', heartbeatsHeader); } _b.label = 2; case 2: body = { installation: { sdkVersion: PACKAGE_VERSION, appId: appConfig.appId } }; request = { method: 'POST', headers: headers, body: JSON.stringify(body) }; return [4 /*yield*/, retryIfServerError(function () { return fetch(endpoint, request); })]; case 3: response = _b.sent(); if (!response.ok) return [3 /*break*/, 5]; return [4 /*yield*/, response.json()]; case 4: responseValue = _b.sent(); completedAuthToken = extractAuthTokenInfoFromResponse(responseValue); return [2 /*return*/, completedAuthToken]; case 5: return [4 /*yield*/, getErrorFromResponse('Generate Auth Token', response)]; case 6: throw _b.sent(); } }); }); } function getGenerateAuthTokenEndpoint(appConfig, _a) { var fid = _a.fid; return "".concat(getInstallationsEndpoint(appConfig), "/").concat(fid, "/authTokens:generate"); } /** * @license * Copyright 2019 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. */ /** * Returns a valid authentication token for the installation. Generates a new * token if one doesn't exist, is expired or about to expire. * * Should only be called if the Firebase Installation is registered. */ function refreshAuthToken(installations, forceRefresh) { if (forceRefresh === void 0) { forceRefresh = false; } return tslib.__awaiter(this, void 0, void 0, function () { var tokenPromise, entry, authToken, _a; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, update(installations.appConfig, function (oldEntry) { if (!isEntryRegistered(oldEntry)) { throw ERROR_FACTORY.create("not-registered" /* ErrorCode.NOT_REGISTERED */); } var oldAuthToken = oldEntry.authToken; if (!forceRefresh && isAuthTokenValid(oldAuthToken)) { // There is a valid token in the DB. return oldEntry; } else if (oldAuthToken.requestStatus === 1 /* RequestStatus.IN_PROGRESS */) { // There already is a token request in progress. tokenPromise = waitUntilAuthTokenRequest(installations, forceRefresh); return oldEntry; } else { // No token or token expired. if (!navigator.onLine) { throw ERROR_FACTORY.create("app-offline" /* ErrorCode.APP_OFFLINE */); } var inProgressEntry = makeAuthTokenRequestInProgressEntry(oldEntry); tokenPromise = fetchAuthTokenFromServer(installations, inProgressEntry); return inProgressEntry; } })]; case 1: entry = _b.sent(); if (!tokenPromise) return [3 /*break*/, 3]; return [4 /*yield*/, tokenPromise]; case 2: _a = _b.sent(); return [3 /*break*/, 4]; case 3: _a = entry.authToken; _b.label = 4; case 4: authToken = _a; return [2 /*return*/, authToken]; } }); }); } /** * Call only if FID is registered and Auth Token request is in progress. * * Waits until the current pending request finishes. If the request times out, * tries once in this thread as well. */ function waitUntilAuthTokenRequest(installations, forceRefresh) { return tslib.__awaiter(this, void 0, void 0, function () { var entry, authToken; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, updateAuthTokenRequest(installations.appConfig)]; case 1: entry = _a.sent(); _a.label = 2; case 2: if (!(entry.authToken.requestStatus === 1 /* RequestStatus.IN_PROGRESS */)) return [3 /*break*/, 5]; // generateAuthToken still in progress. return [4 /*yield*/, sleep(100)]; case 3: // generateAuthToken still in progress. _a.sent(); return [4 /*yield*/, updateAuthTokenRequest(installations.appConfig)]; case 4: entry = _a.sent(); return [3 /*break*/, 2]; case 5: authToken = entry.authToken; if (authToken.requestStatus === 0 /* RequestStatus.NOT_STARTED */) { // The request timed out or failed in a different call. Try again. return [2 /*return*/, refreshAuthToken(installations, forceRefresh)]; } else { return [2 /*return*/, authToken]; } } }); }); } /** * Called only if there is a GenerateAuthToken request in progress. * * Updates the InstallationEntry in the DB based on the status of the * GenerateAuthToken request. * * Returns the updated InstallationEntry. */ function updateAuthTokenRequest(appConfig) { return update(appConfig, function (oldEntry) { if (!isEntryRegistered(oldEntry)) { throw ERROR_FACTORY.create("not-registered" /* ErrorCode.NOT_REGISTERED */); } var oldAuthToken = oldEntry.authToken; if (hasAuthTokenRequestTimedOut(oldAuthToken)) { return tslib.__assign(tslib.__assign({}, oldEntry), { authToken: { requestStatus: 0 /* RequestStatus.NOT_STARTED */ } }); } return oldEntry; }); } function fetchAuthTokenFromServer(installations, installationEntry) { return tslib.__awaiter(this, void 0, void 0, function () { var authToken, updatedInstallationEntry, e_1, updatedInstallationEntry; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 8]); return [4 /*yield*/, generateAuthTokenRequest(installations, installationEntry)]; case 1: authToken = _a.sent(); updatedInstallationEntry = tslib.__assign(tslib.__assign({}, installationEntry), { authToken: authToken }); return [4 /*yield*/, set(installations.appConfig, updatedInstallationEntry)]; case 2: _a.sent(); return [2 /*return*/, authToken]; case 3: e_1 = _a.sent(); if (!(isServerError(e_1) && (e_1.customData.serverCode === 401 || e_1.customData.serverCode === 404))) return [3 /*break*/, 5]; // Server returned a "FID not found" or a "Invalid authentication" error. // Generate a new ID next time. return [4 /*yield*/, remove(installations.appConfig)]; case 4: // Server returned a "FID not found" or a "Invalid authentication" error. // Generate a new ID next time. _a.sent(); return [3 /*break*/, 7]; case 5: updatedInstallationEntry = tslib.__assign(tslib.__assign({}, installationEntry), { authToken: { requestStatus: 0 /* RequestStatus.NOT_STARTED */ } }); return [4 /*yield*/, set(installations.appConfig, updatedInstallationEntry)]; case 6: _a.sent(); _a.label = 7; case 7: throw e_1; case 8: return [2 /*return*/]; } }); }); } function isEntryRegistered(installationEntry) { return (installationEntry !== undefined && installationEntry.registrationStatus === 2 /* RequestStatus.COMPLETED */); } function isAuthTokenValid(authToken) { return (authToken.requestStatus === 2 /* RequestStatus.COMPLETED */ && !isAuthTokenExpired(authToken)); } function isAuthTokenExpired(authToken) { var now = Date.now(); return (now < authToken.creationTime || authToken.creationTime + authToken.expiresIn < now + TOKEN_EXPIRATION_BUFFER); } /** Returns an updated InstallationEntry with an InProgressAuthToken. */ function makeAuthTokenRequestInProgressEntry(oldEntry) { var inProgressAuthToken = { requestStatus: 1 /* RequestStatus.IN_PROGRESS */, requestTime: Date.now() }; return tslib.__assign(tslib.__assign({}, oldEntry), { authToken: inProgressAuthToken }); } function hasAuthTokenRequestTimedOut(authToken) { return (authToken.requestStatus === 1 /* RequestStatus.IN_PROGRESS */ && authToken.requestTime + PENDING_TIMEOUT_MS < Date.now()); } /** * @license * Copyright 2019 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. */ /** * Creates a Firebase Installation if there isn't one for the app and * returns the Installation ID. * @param installations - The `Installations` instance. * * @public */ function getId(installations) { return tslib.__awaiter(this, void 0, void 0, function () { var installationsImpl, _a, installationEntry, registrationPromise; return tslib.__generator(this, function (_b) { switch (_b.label) { case 0: installationsImpl = installations; return [4 /*yield*/, getInstallationEntry(installationsImpl)]; case 1: _a = _b.sent(), installationEntry = _a.installationEntry, registrationPromise = _a.registrationPromise; if (registrationPromise) { registrationPromise.catch(console.error); } else { // If the installation is already registered, update the authentication // token if needed. refreshAuthToken(installationsImpl).catch(console.error); } return [2 /*return*/, installationEntry.fid]; } }); }); } /** * @license * Copyright 2019 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. */ /** * Returns a Firebase Installations auth token, identifying the current * Firebase Installation. * @param installations - The `Installations` instance. * @param forceRefresh - Force refresh regardless of token expiration. * * @public */ function getToken(installations, forceRefresh) { if (forceRefresh === void 0) { forceRefresh = false; } return tslib.__awaiter(this, void 0, void 0, function () { var installationsImpl, authToken; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: installationsImpl = installations; return [4 /*yield*/, completeInstallationRegistration(installationsImpl)]; case 1: _a.sent(); return [4 /*yield*/, refreshAuthToken(installationsImpl, forceRefresh)]; case 2: authToken = _a.sent(); return [2 /*return*/, authToken.token]; } }); }); } function completeInstallationRegistration(installations) { return tslib.__awaiter(this, void 0, void 0, function () { var registrationPromise; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getInstallationEntry(installations)]; case 1: registrationPromise = (_a.sent()).registrationPromise; if (!registrationPromise) return [3 /*break*/, 3]; // A createInstallation request is in progress. Wait until it finishes. return [4 /*yield*/, registrationPromise]; case 2: // A createInstallation request is in progress. Wait until it finishes. _a.sent(); _a.label = 3; case 3: return [2 /*return*/]; } }); }); } /** * @license * Copyright 2019 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. */ function deleteInstallationRequest(appConfig, installationEntry) { return tslib.__awaiter(this, void 0, void 0, function () { var endpoint, headers, request, response; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: endpoint = getDeleteEndpoint(appConfig, installationEntry); headers = getHeadersWithAuth(appConfig, installationEntry); request = { method: 'DELETE', headers: headers }; return [4 /*yield*/, retryIfServerError(function () { return fetch(endpoint, request); })]; case 1: response = _a.sent(); if (!!response.ok) return [3 /*break*/, 3]; return [4 /*yield*/, getErrorFromResponse('Delete Installation', response)]; case 2: throw _a.sent(); case 3: return [2 /*return*/]; } }); }); } function getDeleteEndpoint(appConfig, _a) { var fid = _a.fid; return "".concat(getInstallationsEndpoint(appConfig), "/").concat(fid); } /** * @license * Copyright 2019 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. */ /** * Deletes the Firebase Installation and all associated data. * @param installations - The `Installations` instance. * * @public */ function deleteInstallations(installations) { return tslib.__awaiter(this, void 0, void 0, function () { var appConfig, entry; return tslib.__generator(this, function (_a) { switch (_a.label) { case 0: appConfig = installations.appConfig; return [4 /*yield*/, update(appConfig, function (oldEntry) { if (oldEntry && oldEntry.registrationStatus === 0 /* RequestStatus.NOT_STARTED */) { // Delete the unregistered entry without sending a deleteInstallation request. return undefined; } return oldEntry; })]; case 1: entry = _a.sent(); if (!entry) return [3 /*break*/, 6]; if (!(entry.registrationStatus === 1 /* RequestStatus.IN_PROGRESS */)) return [3 /*break*/, 2]; // Can't delete while trying to register. throw ERROR_FACTORY.create("delete-pending-registration" /* ErrorCode.DELETE_PENDING_REGISTRATION */); case 2: if (!(entry.registrationStatus === 2 /* RequestStatus.COMPLETED */)) return [3 /*break*/, 6]; if (!!navigator.onLine) return [3 /*break*/, 3]; throw ERROR_FACTORY.create("app-offline" /* ErrorCode.APP_OFFLINE */); case 3: return [4 /*yield*/, deleteInstallationRequest(appConfig, entry)]; case 4: _a.sent(); return [4 /*yield*/, remove(appConfig)]; case 5: _a.sent(); _a.label = 6; case 6: return [2 /*return*/]; } }); }); } /** * @license * Copyright 2019 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. */ /** * Sets a new callback that will get called when Installation ID changes. * Returns an unsubscribe function that will remove the callback when called. * @param installations - The `Installations` instance. * @param callback - The callback function that is invoked when FID changes. * @returns A function that can be called to unsubscribe. * * @public */ function onIdChange(installations, callback) { var appConfig = installations.appConfig; addCallback(appConfig, callback); return function () { removeCallback(appConfig, callback); }; } /** * @license * Copyright 2020 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. */ /** * Returns an instance of {@link Installations} associated with the given * {@link @firebase/app#FirebaseApp} instance. * @param app - The {@link @firebase/app#FirebaseApp} instance. * * @public */ function getInstallations(app$1) { if (app$1 === void 0) { app$1 = app.getApp(); } var installationsImpl = app._getProvider(app$1, 'installations').getImmediate(); return installationsImpl; } /** * @license * Copyright 2019 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. */ function extractAppConfig(app) { var e_1, _a; if (!app || !app.options) { throw getMissingValueError('App Configuration'); } if (!app.name) { throw getMissingValueError('App Name'); } // Required app config keys var configKeys = [ 'projectId', 'apiKey', 'appId' ]; try { for (var configKeys_1 = tslib.__values(configKeys), configKeys_1_1 = configKeys_1.next(); !configKeys_1_1.done; configKeys_1_1 = configKeys_1.next()) { var keyName = configKeys_1_1.value; if (!app.options[keyName]) { throw getMissingValueError(keyName); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (configKeys_1_1 && !configKeys_1_1.done && (_a = configKeys_1.return)) _a.call(configKeys_1); } finally { if (e_1) throw e_1.error; } } return { appName: app.name, projectId: app.options.projectId, apiKey: app.options.apiKey, appId: app.options.appId }; } function getMissingValueError(valueName) { return ERROR_FACTORY.create("missing-app-config-values" /* ErrorCode.MISSING_APP_CONFIG_VALUES */, { valueName: valueName }); } /** * @license * Copyright 2020 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. */ var INSTALLATIONS_NAME = 'installations'; var INSTALLATIONS_NAME_INTERNAL = 'installations-internal'; var publicFactory = function (container) { var app$1 = container.getProvider('app').getImmediate(); // Throws if app isn't configured properly. var appConfig = extractAppConfig(app$1); var heartbeatServiceProvider = app._getProvider(app$1, 'heartbeat'); var installationsImpl = { app: app$1, appConfig: appConfig, heartbeatServiceProvider: heartbeatServiceProvider, _delete: function () { return Promise.resolve(); } }; return installationsImpl; }; var internalFactory = function (container) { var app$1 = container.getProvider('app').getImmediate(); // Internal FIS instance relies on public FIS instance. var installations = app._getProvider(app$1, INSTALLATIONS_NAME).getImmediate(); var installationsInternal = { getId: function () { return getId(installations); }, getToken: function (forceRefresh) { return getToken(installations, forceRefresh); } }; return installationsInternal; }; function registerInstallations() { app._registerComponent(new component.Component(INSTALLATIONS_NAME, publicFactory, "PUBLIC" /* ComponentType.PUBLIC */)); app._registerComponent(new component.Component(INSTALLATIONS_NAME_INTERNAL, internalFactory, "PRIVATE" /* ComponentType.PRIVATE */)); } /** * The Firebase Installations Web SDK. * This SDK does not work in a Node.js environment. * * @packageDocumentation */ registerInstallations(); app.registerVersion(name, version); // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation app.registerVersion(name, version, 'cjs5'); exports.deleteInstallations = deleteInstallations; exports.getId = getId; exports.getInstallations = getInstallations; exports.getToken = getToken; exports.onIdChange = onIdChange; //# sourceMappingURL=index.cjs.js.map