import firebase from '@firebase/app'; import { Component } from '@firebase/component'; import { __awaiter, __generator, __spread, __values, __assign } from 'tslib'; import { ErrorFactory, FirebaseError } from '@firebase/util'; import { openDb } from 'idb'; var name = "@firebase/installations"; var version = "0.4.1"; /** * @license * Copyright 2019 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. */ var PENDING_TIMEOUT_MS = 10000; var PACKAGE_VERSION = "w:" + 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 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. */ var _a; var ERROR_DESCRIPTION_MAP = (_a = {}, _a["missing-app-config-values" /* MISSING_APP_CONFIG_VALUES */] = 'Missing App configuration value: "{$valueName}"', _a["not-registered" /* NOT_REGISTERED */] = 'Firebase Installation is not registered.', _a["installation-not-found" /* INSTALLATION_NOT_FOUND */] = 'Firebase Installation not found.', _a["request-failed" /* REQUEST_FAILED */] = '{$requestName} request failed with error "{$serverCode} {$serverStatus}: {$serverMessage}"', _a["app-offline" /* APP_OFFLINE */] = 'Could not process request. Application offline.', _a["delete-pending-registration" /* DELETE_PENDING_REGISTRATION */] = "Can't delete installation while there is a pending registration request.", _a); var ERROR_FACTORY = new 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 FirebaseError && error.code.includes("request-failed" /* REQUEST_FAILED */)); } /** * @license * Copyright 2019 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. */ function getInstallationsEndpoint(_a) { var projectId = _a.projectId; return INSTALLATIONS_API_URL + "/projects/" + projectId + "/installations"; } function extractAuthTokenInfoFromResponse(response) { return { token: response.token, requestStatus: 2 /* COMPLETED */, expiresIn: getExpiresInFromResponseExpiresIn(response.expiresIn), creationTime: Date.now() }; } function getErrorFromResponse(requestName, response) { return __awaiter(this, void 0, void 0, function () { var responseJson, errorData; return __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" /* 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 __awaiter(this, void 0, void 0, function () { var result; return __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 INTERNAL_AUTH_VERSION + " " + refreshToken; } /** * @license * Copyright 2019 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. */ function createInstallationRequest(appConfig, _a) { var fid = _a.fid; return __awaiter(this, void 0, void 0, function () { var endpoint, headers, body, request, response, responseValue, registeredInstallationEntry; return __generator(this, function (_b) { switch (_b.label) { case 0: endpoint = getInstallationsEndpoint(appConfig); headers = getHeaders(appConfig); 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 1: response = _b.sent(); if (!response.ok) return [3 /*break*/, 3]; return [4 /*yield*/, response.json()]; case 2: responseValue = _b.sent(); registeredInstallationEntry = { fid: responseValue.fid || fid, registrationStatus: 2 /* COMPLETED */, refreshToken: responseValue.refreshToken, authToken: extractAuthTokenInfoFromResponse(responseValue.authToken) }; return [2 /*return*/, registeredInstallationEntry]; case 3: return [4 /*yield*/, getErrorFromResponse('Create Installation', response)]; case 4: throw _b.sent(); } }); }); } /** * @license * Copyright 2019 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. */ /** Returns a promise that resolves after given time passes. */ function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } /** * @license * Copyright 2019 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. */ function bufferToBase64UrlSafe(array) { var b64 = btoa(String.fromCharCode.apply(String, __spread(array))); return b64.replace(/\+/g, '-').replace(/\//g, '_'); } /** * @license * Copyright 2019 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. */ 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 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. */ /** Returns a string key that can be used to identify the app. */ function getKey(appConfig) { return appConfig.appName + "!" + appConfig.appId; } /** * @license * Copyright 2019 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. */ 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 = __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 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. */ 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 = openDb(DATABASE_NAME, DATABASE_VERSION, function (upgradeDB) { // 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 (upgradeDB.oldVersion) { case 0: upgradeDB.createObjectStore(OBJECT_STORE_NAME); } }); } return dbPromise; } /** Assigns or overwrites the record for the given key with the given value. */ function set(appConfig, value) { return __awaiter(this, void 0, void 0, function () { var key, db, tx, objectStore, oldValue; return __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.complete]; 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 __awaiter(this, void 0, void 0, function () { var key, db, tx; return __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.complete]; 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 __awaiter(this, void 0, void 0, function () { var key, db, tx, store, oldValue, newValue; return __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.complete]; case 7: _a.sent(); if (newValue && (!oldValue || oldValue.fid !== newValue.fid)) { fidChanged(appConfig, newValue.fid); } return [2 /*return*/, newValue]; } }); }); } /** * @license * Copyright 2019 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. */ /** * Updates and returns the InstallationEntry from the database. * Also triggers a registration request if it is necessary and possible. */ function getInstallationEntry(appConfig) { return __awaiter(this, void 0, void 0, function () { var registrationPromise, installationEntry, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, update(appConfig, function (oldEntry) { var installationEntry = updateOrCreateInstallationEntry(oldEntry); var entryWithPromise = triggerRegistrationIfNecessary(appConfig, 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 /* 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(appConfig, installationEntry) { if (installationEntry.registrationStatus === 0 /* NOT_STARTED */) { if (!navigator.onLine) { // Registration required but app is offline. var registrationPromiseWithError = Promise.reject(ERROR_FACTORY.create("app-offline" /* APP_OFFLINE */)); return { installationEntry: installationEntry, registrationPromise: registrationPromiseWithError }; } // Try registering. Change status to IN_PROGRESS. var inProgressEntry = { fid: installationEntry.fid, registrationStatus: 1 /* IN_PROGRESS */, registrationTime: Date.now() }; var registrationPromise = registerInstallation(appConfig, inProgressEntry); return { installationEntry: inProgressEntry, registrationPromise: registrationPromise }; } else if (installationEntry.registrationStatus === 1 /* IN_PROGRESS */) { return { installationEntry: installationEntry, registrationPromise: waitUntilFidRegistration(appConfig) }; } else { return { installationEntry: installationEntry }; } } /** This will be executed only once for each new Firebase Installation. */ function registerInstallation(appConfig, installationEntry) { return __awaiter(this, void 0, void 0, function () { var registeredInstallationEntry, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 7]); return [4 /*yield*/, createInstallationRequest(appConfig, installationEntry)]; case 1: registeredInstallationEntry = _a.sent(); return [2 /*return*/, set(appConfig, registeredInstallationEntry)]; case 2: e_1 = _a.sent(); if (!(isServerError(e_1) && e_1.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(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(appConfig, { fid: installationEntry.fid, registrationStatus: 0 /* 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(appConfig) { return __awaiter(this, void 0, void 0, function () { var entry, _a, installationEntry, registrationPromise; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, updateInstallationRequest(appConfig)]; case 1: entry = _b.sent(); _b.label = 2; case 2: if (!(entry.registrationStatus === 1 /* 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(appConfig)]; case 4: entry = _b.sent(); return [3 /*break*/, 2]; case 5: if (!(entry.registrationStatus === 0 /* NOT_STARTED */)) return [3 /*break*/, 7]; return [4 /*yield*/, getInstallationEntry(appConfig)]; 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" /* INSTALLATION_NOT_FOUND */); } return clearTimedOutRequest(oldEntry); }); } function clearTimedOutRequest(entry) { if (hasInstallationRequestTimedOut(entry)) { return { fid: entry.fid, registrationStatus: 0 /* NOT_STARTED */ }; } return entry; } function hasInstallationRequestTimedOut(installationEntry) { return (installationEntry.registrationStatus === 1 /* IN_PROGRESS */ && installationEntry.registrationTime + PENDING_TIMEOUT_MS < Date.now()); } /** * @license * Copyright 2019 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. */ function generateAuthTokenRequest(_a, installationEntry) { var appConfig = _a.appConfig, platformLoggerProvider = _a.platformLoggerProvider; return __awaiter(this, void 0, void 0, function () { var endpoint, headers, platformLogger, body, request, response, responseValue, completedAuthToken; return __generator(this, function (_b) { switch (_b.label) { case 0: endpoint = getGenerateAuthTokenEndpoint(appConfig, installationEntry); headers = getHeadersWithAuth(appConfig, installationEntry); platformLogger = platformLoggerProvider.getImmediate({ optional: true }); if (platformLogger) { headers.append('x-firebase-client', platformLogger.getPlatformInfoString()); } body = { installation: { sdkVersion: PACKAGE_VERSION } }; request = { method: 'POST', headers: headers, body: JSON.stringify(body) }; return [4 /*yield*/, retryIfServerError(function () { return fetch(endpoint, request); })]; case 1: response = _b.sent(); if (!response.ok) return [3 /*break*/, 3]; return [4 /*yield*/, response.json()]; case 2: responseValue = _b.sent(); completedAuthToken = extractAuthTokenInfoFromResponse(responseValue); return [2 /*return*/, completedAuthToken]; case 3: return [4 /*yield*/, getErrorFromResponse('Generate Auth Token', response)]; case 4: throw _b.sent(); } }); }); } function getGenerateAuthTokenEndpoint(appConfig, _a) { var fid = _a.fid; return getInstallationsEndpoint(appConfig) + "/" + fid + "/authTokens:generate"; } /** * @license * Copyright 2019 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. */ /** * 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(dependencies, forceRefresh) { if (forceRefresh === void 0) { forceRefresh = false; } return __awaiter(this, void 0, void 0, function () { var tokenPromise, entry, authToken, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, update(dependencies.appConfig, function (oldEntry) { if (!isEntryRegistered(oldEntry)) { throw ERROR_FACTORY.create("not-registered" /* 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 /* IN_PROGRESS */) { // There already is a token request in progress. tokenPromise = waitUntilAuthTokenRequest(dependencies, forceRefresh); return oldEntry; } else { // No token or token expired. if (!navigator.onLine) { throw ERROR_FACTORY.create("app-offline" /* APP_OFFLINE */); } var inProgressEntry = makeAuthTokenRequestInProgressEntry(oldEntry); tokenPromise = fetchAuthTokenFromServer(dependencies, 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(dependencies, forceRefresh) { return __awaiter(this, void 0, void 0, function () { var entry, authToken; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, updateAuthTokenRequest(dependencies.appConfig)]; case 1: entry = _a.sent(); _a.label = 2; case 2: if (!(entry.authToken.requestStatus === 1 /* 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(dependencies.appConfig)]; case 4: entry = _a.sent(); return [3 /*break*/, 2]; case 5: authToken = entry.authToken; if (authToken.requestStatus === 0 /* NOT_STARTED */) { // The request timed out or failed in a different call. Try again. return [2 /*return*/, refreshAuthToken(dependencies, 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" /* NOT_REGISTERED */); } var oldAuthToken = oldEntry.authToken; if (hasAuthTokenRequestTimedOut(oldAuthToken)) { return __assign(__assign({}, oldEntry), { authToken: { requestStatus: 0 /* NOT_STARTED */ } }); } return oldEntry; }); } function fetchAuthTokenFromServer(dependencies, installationEntry) { return __awaiter(this, void 0, void 0, function () { var authToken, updatedInstallationEntry, e_1, updatedInstallationEntry; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 8]); return [4 /*yield*/, generateAuthTokenRequest(dependencies, installationEntry)]; case 1: authToken = _a.sent(); updatedInstallationEntry = __assign(__assign({}, installationEntry), { authToken: authToken }); return [4 /*yield*/, set(dependencies.appConfig, updatedInstallationEntry)]; case 2: _a.sent(); return [2 /*return*/, authToken]; case 3: e_1 = _a.sent(); if (!(isServerError(e_1) && (e_1.serverCode === 401 || e_1.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(dependencies.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 = __assign(__assign({}, installationEntry), { authToken: { requestStatus: 0 /* NOT_STARTED */ } }); return [4 /*yield*/, set(dependencies.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 /* COMPLETED */); } function isAuthTokenValid(authToken) { return (authToken.requestStatus === 2 /* 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 /* IN_PROGRESS */, requestTime: Date.now() }; return __assign(__assign({}, oldEntry), { authToken: inProgressAuthToken }); } function hasAuthTokenRequestTimedOut(authToken) { return (authToken.requestStatus === 1 /* IN_PROGRESS */ && authToken.requestTime + PENDING_TIMEOUT_MS < Date.now()); } /** * @license * Copyright 2019 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. */ function getId(dependencies) { return __awaiter(this, void 0, void 0, function () { var _a, installationEntry, registrationPromise; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, getInstallationEntry(dependencies.appConfig)]; 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(dependencies).catch(console.error); } return [2 /*return*/, installationEntry.fid]; } }); }); } /** * @license * Copyright 2019 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. */ function getToken(dependencies, forceRefresh) { if (forceRefresh === void 0) { forceRefresh = false; } return __awaiter(this, void 0, void 0, function () { var authToken; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, completeInstallationRegistration(dependencies.appConfig)]; case 1: _a.sent(); return [4 /*yield*/, refreshAuthToken(dependencies, forceRefresh)]; case 2: authToken = _a.sent(); return [2 /*return*/, authToken.token]; } }); }); } function completeInstallationRegistration(appConfig) { return __awaiter(this, void 0, void 0, function () { var registrationPromise; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getInstallationEntry(appConfig)]; 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 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. */ function deleteInstallationRequest(appConfig, installationEntry) { return __awaiter(this, void 0, void 0, function () { var endpoint, headers, request, response; return __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 getInstallationsEndpoint(appConfig) + "/" + fid; } /** * @license * Copyright 2019 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. */ function deleteInstallation(dependencies) { return __awaiter(this, void 0, void 0, function () { var appConfig, entry; return __generator(this, function (_a) { switch (_a.label) { case 0: appConfig = dependencies.appConfig; return [4 /*yield*/, update(appConfig, function (oldEntry) { if (oldEntry && oldEntry.registrationStatus === 0 /* 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 /* IN_PROGRESS */)) return [3 /*break*/, 2]; // Can't delete while trying to register. throw ERROR_FACTORY.create("delete-pending-registration" /* DELETE_PENDING_REGISTRATION */); case 2: if (!(entry.registrationStatus === 2 /* COMPLETED */)) return [3 /*break*/, 6]; if (!!navigator.onLine) return [3 /*break*/, 3]; throw ERROR_FACTORY.create("app-offline" /* 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 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. */ /** * Sets a new callback that will get called when Installation ID changes. * Returns an unsubscribe function that will remove the callback when called. */ function onIdChange(_a, callback) { var appConfig = _a.appConfig; addCallback(appConfig, callback); return function () { removeCallback(appConfig, callback); }; } /** * @license * Copyright 2019 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. */ 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 = __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" /* MISSING_APP_CONFIG_VALUES */, { valueName: valueName }); } /** * @license * Copyright 2019 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. */ function registerInstallations(instance) { var installationsName = 'installations'; instance.INTERNAL.registerComponent(new Component(installationsName, function (container) { var app = container.getProvider('app').getImmediate(); // Throws if app isn't configured properly. var appConfig = extractAppConfig(app); var platformLoggerProvider = container.getProvider('platform-logger'); var dependencies = { appConfig: appConfig, platformLoggerProvider: platformLoggerProvider }; var installations = { app: app, getId: function () { return getId(dependencies); }, getToken: function (forceRefresh) { return getToken(dependencies, forceRefresh); }, delete: function () { return deleteInstallation(dependencies); }, onIdChange: function (callback) { return onIdChange(dependencies, callback); } }; return installations; }, "PUBLIC" /* PUBLIC */)); instance.registerVersion(name, version); } registerInstallations(firebase); export { registerInstallations }; //# sourceMappingURL=index.esm.js.map