1844 lines
77 KiB
JavaScript
1844 lines
77 KiB
JavaScript
|
import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app';
|
||
|
import { Component } from '@firebase/component';
|
||
|
import { __assign, __awaiter, __generator, __spreadArray } from 'tslib';
|
||
|
import { Deferred, ErrorFactory, isIndexedDBAvailable, uuidv4, getGlobal, base64, issuedAtTime, calculateBackoffMillis, getModularInstance } from '@firebase/util';
|
||
|
import { Logger } from '@firebase/logger';
|
||
|
|
||
|
/**
|
||
|
* @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 APP_CHECK_STATES = new Map();
|
||
|
var DEFAULT_STATE = {
|
||
|
activated: false,
|
||
|
tokenObservers: []
|
||
|
};
|
||
|
var DEBUG_STATE = {
|
||
|
initialized: false,
|
||
|
enabled: false
|
||
|
};
|
||
|
/**
|
||
|
* Gets a reference to the state object.
|
||
|
*/
|
||
|
function getStateReference(app) {
|
||
|
return APP_CHECK_STATES.get(app) || __assign({}, DEFAULT_STATE);
|
||
|
}
|
||
|
/**
|
||
|
* Set once on initialization. The map should hold the same reference to the
|
||
|
* same object until this entry is deleted.
|
||
|
*/
|
||
|
function setInitialState(app, state) {
|
||
|
APP_CHECK_STATES.set(app, state);
|
||
|
return APP_CHECK_STATES.get(app);
|
||
|
}
|
||
|
function getDebugState() {
|
||
|
return DEBUG_STATE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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 BASE_ENDPOINT = 'https://content-firebaseappcheck.googleapis.com/v1';
|
||
|
var EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaV3Token';
|
||
|
var EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = 'exchangeRecaptchaEnterpriseToken';
|
||
|
var EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken';
|
||
|
var TOKEN_REFRESH_TIME = {
|
||
|
/**
|
||
|
* The offset time before token natural expiration to run the refresh.
|
||
|
* This is currently 5 minutes.
|
||
|
*/
|
||
|
OFFSET_DURATION: 5 * 60 * 1000,
|
||
|
/**
|
||
|
* This is the first retrial wait after an error. This is currently
|
||
|
* 30 seconds.
|
||
|
*/
|
||
|
RETRIAL_MIN_WAIT: 30 * 1000,
|
||
|
/**
|
||
|
* This is the maximum retrial wait, currently 16 minutes.
|
||
|
*/
|
||
|
RETRIAL_MAX_WAIT: 16 * 60 * 1000
|
||
|
};
|
||
|
/**
|
||
|
* One day in millis, for certain error code backoffs.
|
||
|
*/
|
||
|
var ONE_DAY = 24 * 60 * 60 * 1000;
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
/**
|
||
|
* Port from auth proactiverefresh.js
|
||
|
*
|
||
|
*/
|
||
|
// TODO: move it to @firebase/util?
|
||
|
// TODO: allow to config whether refresh should happen in the background
|
||
|
var Refresher = /** @class */ (function () {
|
||
|
function Refresher(operation, retryPolicy, getWaitDuration, lowerBound, upperBound) {
|
||
|
this.operation = operation;
|
||
|
this.retryPolicy = retryPolicy;
|
||
|
this.getWaitDuration = getWaitDuration;
|
||
|
this.lowerBound = lowerBound;
|
||
|
this.upperBound = upperBound;
|
||
|
this.pending = null;
|
||
|
this.nextErrorWaitInterval = lowerBound;
|
||
|
if (lowerBound > upperBound) {
|
||
|
throw new Error('Proactive refresh lower bound greater than upper bound!');
|
||
|
}
|
||
|
}
|
||
|
Refresher.prototype.start = function () {
|
||
|
this.nextErrorWaitInterval = this.lowerBound;
|
||
|
this.process(true).catch(function () {
|
||
|
/* we don't care about the result */
|
||
|
});
|
||
|
};
|
||
|
Refresher.prototype.stop = function () {
|
||
|
if (this.pending) {
|
||
|
this.pending.reject('cancelled');
|
||
|
this.pending = null;
|
||
|
}
|
||
|
};
|
||
|
Refresher.prototype.isRunning = function () {
|
||
|
return !!this.pending;
|
||
|
};
|
||
|
Refresher.prototype.process = function (hasSucceeded) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var error_1;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0:
|
||
|
this.stop();
|
||
|
_a.label = 1;
|
||
|
case 1:
|
||
|
_a.trys.push([1, 6, , 7]);
|
||
|
this.pending = new Deferred();
|
||
|
this.pending.promise.catch(function (_e) {
|
||
|
/* ignore */
|
||
|
});
|
||
|
return [4 /*yield*/, sleep(this.getNextRun(hasSucceeded))];
|
||
|
case 2:
|
||
|
_a.sent();
|
||
|
// Why do we resolve a promise, then immediate wait for it?
|
||
|
// We do it to make the promise chain cancellable.
|
||
|
// We can call stop() which rejects the promise before the following line execute, which makes
|
||
|
// the code jump to the catch block.
|
||
|
// TODO: unit test this
|
||
|
this.pending.resolve();
|
||
|
return [4 /*yield*/, this.pending.promise];
|
||
|
case 3:
|
||
|
_a.sent();
|
||
|
this.pending = new Deferred();
|
||
|
this.pending.promise.catch(function (_e) {
|
||
|
/* ignore */
|
||
|
});
|
||
|
return [4 /*yield*/, this.operation()];
|
||
|
case 4:
|
||
|
_a.sent();
|
||
|
this.pending.resolve();
|
||
|
return [4 /*yield*/, this.pending.promise];
|
||
|
case 5:
|
||
|
_a.sent();
|
||
|
this.process(true).catch(function () {
|
||
|
/* we don't care about the result */
|
||
|
});
|
||
|
return [3 /*break*/, 7];
|
||
|
case 6:
|
||
|
error_1 = _a.sent();
|
||
|
if (this.retryPolicy(error_1)) {
|
||
|
this.process(false).catch(function () {
|
||
|
/* we don't care about the result */
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
this.stop();
|
||
|
}
|
||
|
return [3 /*break*/, 7];
|
||
|
case 7: return [2 /*return*/];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
Refresher.prototype.getNextRun = function (hasSucceeded) {
|
||
|
if (hasSucceeded) {
|
||
|
// If last operation succeeded, reset next error wait interval and return
|
||
|
// the default wait duration.
|
||
|
this.nextErrorWaitInterval = this.lowerBound;
|
||
|
// Return typical wait duration interval after a successful operation.
|
||
|
return this.getWaitDuration();
|
||
|
}
|
||
|
else {
|
||
|
// Get next error wait interval.
|
||
|
var currentErrorWaitInterval = this.nextErrorWaitInterval;
|
||
|
// Double interval for next consecutive error.
|
||
|
this.nextErrorWaitInterval *= 2;
|
||
|
// Make sure next wait interval does not exceed the maximum upper bound.
|
||
|
if (this.nextErrorWaitInterval > this.upperBound) {
|
||
|
this.nextErrorWaitInterval = this.upperBound;
|
||
|
}
|
||
|
return currentErrorWaitInterval;
|
||
|
}
|
||
|
};
|
||
|
return Refresher;
|
||
|
}());
|
||
|
function sleep(ms) {
|
||
|
return new Promise(function (resolve) {
|
||
|
setTimeout(resolve, ms);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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 _a;
|
||
|
var ERRORS = (_a = {},
|
||
|
_a["already-initialized" /* AppCheckError.ALREADY_INITIALIZED */] = 'You have already called initializeAppCheck() for FirebaseApp {$appName} with ' +
|
||
|
'different options. To avoid this error, call initializeAppCheck() with the ' +
|
||
|
'same options as when it was originally called. This will return the ' +
|
||
|
'already initialized instance.',
|
||
|
_a["use-before-activation" /* AppCheckError.USE_BEFORE_ACTIVATION */] = 'App Check is being used before initializeAppCheck() is called for FirebaseApp {$appName}. ' +
|
||
|
'Call initializeAppCheck() before instantiating other Firebase services.',
|
||
|
_a["fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */] = 'Fetch failed to connect to a network. Check Internet connection. ' +
|
||
|
'Original error: {$originalErrorMessage}.',
|
||
|
_a["fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */] = 'Fetch client could not parse response.' +
|
||
|
' Original error: {$originalErrorMessage}.',
|
||
|
_a["fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */] = 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
|
||
|
_a["storage-open" /* AppCheckError.STORAGE_OPEN */] = 'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
|
||
|
_a["storage-get" /* AppCheckError.STORAGE_GET */] = 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
|
||
|
_a["storage-set" /* AppCheckError.STORAGE_WRITE */] = 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
|
||
|
_a["recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */] = 'ReCAPTCHA error.',
|
||
|
_a["throttled" /* AppCheckError.THROTTLED */] = "Requests throttled due to {$httpStatus} error. Attempts allowed again after {$time}",
|
||
|
_a);
|
||
|
var ERROR_FACTORY = new ErrorFactory('appCheck', 'AppCheck', ERRORS);
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
function getRecaptcha(isEnterprise) {
|
||
|
var _a;
|
||
|
if (isEnterprise === void 0) { isEnterprise = false; }
|
||
|
if (isEnterprise) {
|
||
|
return (_a = self.grecaptcha) === null || _a === void 0 ? void 0 : _a.enterprise;
|
||
|
}
|
||
|
return self.grecaptcha;
|
||
|
}
|
||
|
function ensureActivated(app) {
|
||
|
if (!getStateReference(app).activated) {
|
||
|
throw ERROR_FACTORY.create("use-before-activation" /* AppCheckError.USE_BEFORE_ACTIVATION */, {
|
||
|
appName: app.name
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
function getDurationString(durationInMillis) {
|
||
|
var totalSeconds = Math.round(durationInMillis / 1000);
|
||
|
var days = Math.floor(totalSeconds / (3600 * 24));
|
||
|
var hours = Math.floor((totalSeconds - days * 3600 * 24) / 3600);
|
||
|
var minutes = Math.floor((totalSeconds - days * 3600 * 24 - hours * 3600) / 60);
|
||
|
var seconds = totalSeconds - days * 3600 * 24 - hours * 3600 - minutes * 60;
|
||
|
var result = '';
|
||
|
if (days) {
|
||
|
result += pad(days) + 'd:';
|
||
|
}
|
||
|
if (hours) {
|
||
|
result += pad(hours) + 'h:';
|
||
|
}
|
||
|
result += pad(minutes) + 'm:' + pad(seconds) + 's';
|
||
|
return result;
|
||
|
}
|
||
|
function pad(value) {
|
||
|
if (value === 0) {
|
||
|
return '00';
|
||
|
}
|
||
|
return value >= 10 ? value.toString() : '0' + value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
function exchangeToken(_a, heartbeatServiceProvider) {
|
||
|
var url = _a.url, body = _a.body;
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var headers, heartbeatService, heartbeatsHeader, options, response, originalError_1, responseBody, originalError_2, match, timeToLiveAsNumber, now;
|
||
|
return __generator(this, function (_b) {
|
||
|
switch (_b.label) {
|
||
|
case 0:
|
||
|
headers = {
|
||
|
'Content-Type': 'application/json'
|
||
|
};
|
||
|
heartbeatService = heartbeatServiceProvider.getImmediate({
|
||
|
optional: true
|
||
|
});
|
||
|
if (!heartbeatService) return [3 /*break*/, 2];
|
||
|
return [4 /*yield*/, heartbeatService.getHeartbeatsHeader()];
|
||
|
case 1:
|
||
|
heartbeatsHeader = _b.sent();
|
||
|
if (heartbeatsHeader) {
|
||
|
headers['X-Firebase-Client'] = heartbeatsHeader;
|
||
|
}
|
||
|
_b.label = 2;
|
||
|
case 2:
|
||
|
options = {
|
||
|
method: 'POST',
|
||
|
body: JSON.stringify(body),
|
||
|
headers: headers
|
||
|
};
|
||
|
_b.label = 3;
|
||
|
case 3:
|
||
|
_b.trys.push([3, 5, , 6]);
|
||
|
return [4 /*yield*/, fetch(url, options)];
|
||
|
case 4:
|
||
|
response = _b.sent();
|
||
|
return [3 /*break*/, 6];
|
||
|
case 5:
|
||
|
originalError_1 = _b.sent();
|
||
|
throw ERROR_FACTORY.create("fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */, {
|
||
|
originalErrorMessage: originalError_1 === null || originalError_1 === void 0 ? void 0 : originalError_1.message
|
||
|
});
|
||
|
case 6:
|
||
|
if (response.status !== 200) {
|
||
|
throw ERROR_FACTORY.create("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */, {
|
||
|
httpStatus: response.status
|
||
|
});
|
||
|
}
|
||
|
_b.label = 7;
|
||
|
case 7:
|
||
|
_b.trys.push([7, 9, , 10]);
|
||
|
return [4 /*yield*/, response.json()];
|
||
|
case 8:
|
||
|
// JSON parsing throws SyntaxError if the response body isn't a JSON string.
|
||
|
responseBody = _b.sent();
|
||
|
return [3 /*break*/, 10];
|
||
|
case 9:
|
||
|
originalError_2 = _b.sent();
|
||
|
throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, {
|
||
|
originalErrorMessage: originalError_2 === null || originalError_2 === void 0 ? void 0 : originalError_2.message
|
||
|
});
|
||
|
case 10:
|
||
|
match = responseBody.ttl.match(/^([\d.]+)(s)$/);
|
||
|
if (!match || !match[2] || isNaN(Number(match[1]))) {
|
||
|
throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, {
|
||
|
originalErrorMessage: "ttl field (timeToLive) is not in standard Protobuf Duration " +
|
||
|
"format: ".concat(responseBody.ttl)
|
||
|
});
|
||
|
}
|
||
|
timeToLiveAsNumber = Number(match[1]) * 1000;
|
||
|
now = Date.now();
|
||
|
return [2 /*return*/, {
|
||
|
token: responseBody.token,
|
||
|
expireTimeMillis: now + timeToLiveAsNumber,
|
||
|
issuedAtTimeMillis: now
|
||
|
}];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
function getExchangeRecaptchaV3TokenRequest(app, reCAPTCHAToken) {
|
||
|
var _a = app.options, projectId = _a.projectId, appId = _a.appId, apiKey = _a.apiKey;
|
||
|
return {
|
||
|
url: "".concat(BASE_ENDPOINT, "/projects/").concat(projectId, "/apps/").concat(appId, ":").concat(EXCHANGE_RECAPTCHA_TOKEN_METHOD, "?key=").concat(apiKey),
|
||
|
body: {
|
||
|
'recaptcha_v3_token': reCAPTCHAToken
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function getExchangeRecaptchaEnterpriseTokenRequest(app, reCAPTCHAToken) {
|
||
|
var _a = app.options, projectId = _a.projectId, appId = _a.appId, apiKey = _a.apiKey;
|
||
|
return {
|
||
|
url: "".concat(BASE_ENDPOINT, "/projects/").concat(projectId, "/apps/").concat(appId, ":").concat(EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD, "?key=").concat(apiKey),
|
||
|
body: {
|
||
|
'recaptcha_enterprise_token': reCAPTCHAToken
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
function getExchangeDebugTokenRequest(app, debugToken) {
|
||
|
var _a = app.options, projectId = _a.projectId, appId = _a.appId, apiKey = _a.apiKey;
|
||
|
return {
|
||
|
url: "".concat(BASE_ENDPOINT, "/projects/").concat(projectId, "/apps/").concat(appId, ":").concat(EXCHANGE_DEBUG_TOKEN_METHOD, "?key=").concat(apiKey),
|
||
|
body: {
|
||
|
// eslint-disable-next-line
|
||
|
debug_token: debugToken
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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 DB_NAME = 'firebase-app-check-database';
|
||
|
var DB_VERSION = 1;
|
||
|
var STORE_NAME = 'firebase-app-check-store';
|
||
|
var DEBUG_TOKEN_KEY = 'debug-token';
|
||
|
var dbPromise = null;
|
||
|
function getDBPromise() {
|
||
|
if (dbPromise) {
|
||
|
return dbPromise;
|
||
|
}
|
||
|
dbPromise = new Promise(function (resolve, reject) {
|
||
|
try {
|
||
|
var request = indexedDB.open(DB_NAME, DB_VERSION);
|
||
|
request.onsuccess = function (event) {
|
||
|
resolve(event.target.result);
|
||
|
};
|
||
|
request.onerror = function (event) {
|
||
|
var _a;
|
||
|
reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, {
|
||
|
originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message
|
||
|
}));
|
||
|
};
|
||
|
request.onupgradeneeded = function (event) {
|
||
|
var db = event.target.result;
|
||
|
// 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 (event.oldVersion) {
|
||
|
case 0:
|
||
|
db.createObjectStore(STORE_NAME, {
|
||
|
keyPath: 'compositeKey'
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
catch (e) {
|
||
|
reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, {
|
||
|
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
||
|
}));
|
||
|
}
|
||
|
});
|
||
|
return dbPromise;
|
||
|
}
|
||
|
function readTokenFromIndexedDB(app) {
|
||
|
return read(computeKey(app));
|
||
|
}
|
||
|
function writeTokenToIndexedDB(app, token) {
|
||
|
return write(computeKey(app), token);
|
||
|
}
|
||
|
function writeDebugTokenToIndexedDB(token) {
|
||
|
return write(DEBUG_TOKEN_KEY, token);
|
||
|
}
|
||
|
function readDebugTokenFromIndexedDB() {
|
||
|
return read(DEBUG_TOKEN_KEY);
|
||
|
}
|
||
|
function write(key, value) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var db, transaction, store, request;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0: return [4 /*yield*/, getDBPromise()];
|
||
|
case 1:
|
||
|
db = _a.sent();
|
||
|
transaction = db.transaction(STORE_NAME, 'readwrite');
|
||
|
store = transaction.objectStore(STORE_NAME);
|
||
|
request = store.put({
|
||
|
compositeKey: key,
|
||
|
value: value
|
||
|
});
|
||
|
return [2 /*return*/, new Promise(function (resolve, reject) {
|
||
|
request.onsuccess = function (_event) {
|
||
|
resolve();
|
||
|
};
|
||
|
transaction.onerror = function (event) {
|
||
|
var _a;
|
||
|
reject(ERROR_FACTORY.create("storage-set" /* AppCheckError.STORAGE_WRITE */, {
|
||
|
originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message
|
||
|
}));
|
||
|
};
|
||
|
})];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
function read(key) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var db, transaction, store, request;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0: return [4 /*yield*/, getDBPromise()];
|
||
|
case 1:
|
||
|
db = _a.sent();
|
||
|
transaction = db.transaction(STORE_NAME, 'readonly');
|
||
|
store = transaction.objectStore(STORE_NAME);
|
||
|
request = store.get(key);
|
||
|
return [2 /*return*/, new Promise(function (resolve, reject) {
|
||
|
request.onsuccess = function (event) {
|
||
|
var result = event.target.result;
|
||
|
if (result) {
|
||
|
resolve(result.value);
|
||
|
}
|
||
|
else {
|
||
|
resolve(undefined);
|
||
|
}
|
||
|
};
|
||
|
transaction.onerror = function (event) {
|
||
|
var _a;
|
||
|
reject(ERROR_FACTORY.create("storage-get" /* AppCheckError.STORAGE_GET */, {
|
||
|
originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message
|
||
|
}));
|
||
|
};
|
||
|
})];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
function computeKey(app) {
|
||
|
return "".concat(app.options.appId, "-").concat(app.name);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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 logger = new Logger('@firebase/app-check');
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
/**
|
||
|
* Always resolves. In case of an error reading from indexeddb, resolve with undefined
|
||
|
*/
|
||
|
function readTokenFromStorage(app) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var token, e_1;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0:
|
||
|
if (!isIndexedDBAvailable()) return [3 /*break*/, 5];
|
||
|
token = undefined;
|
||
|
_a.label = 1;
|
||
|
case 1:
|
||
|
_a.trys.push([1, 3, , 4]);
|
||
|
return [4 /*yield*/, readTokenFromIndexedDB(app)];
|
||
|
case 2:
|
||
|
token = _a.sent();
|
||
|
return [3 /*break*/, 4];
|
||
|
case 3:
|
||
|
e_1 = _a.sent();
|
||
|
// swallow the error and return undefined
|
||
|
logger.warn("Failed to read token from IndexedDB. Error: ".concat(e_1));
|
||
|
return [3 /*break*/, 4];
|
||
|
case 4: return [2 /*return*/, token];
|
||
|
case 5: return [2 /*return*/, undefined];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Always resolves. In case of an error writing to indexeddb, print a warning and resolve the promise
|
||
|
*/
|
||
|
function writeTokenToStorage(app, token) {
|
||
|
if (isIndexedDBAvailable()) {
|
||
|
return writeTokenToIndexedDB(app, token).catch(function (e) {
|
||
|
// swallow the error and resolve the promise
|
||
|
logger.warn("Failed to write token to IndexedDB. Error: ".concat(e));
|
||
|
});
|
||
|
}
|
||
|
return Promise.resolve();
|
||
|
}
|
||
|
function readOrCreateDebugTokenFromStorage() {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var existingDebugToken, newToken;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0:
|
||
|
existingDebugToken = undefined;
|
||
|
_a.label = 1;
|
||
|
case 1:
|
||
|
_a.trys.push([1, 3, , 4]);
|
||
|
return [4 /*yield*/, readDebugTokenFromIndexedDB()];
|
||
|
case 2:
|
||
|
existingDebugToken = _a.sent();
|
||
|
return [3 /*break*/, 4];
|
||
|
case 3:
|
||
|
_a.sent();
|
||
|
return [3 /*break*/, 4];
|
||
|
case 4:
|
||
|
if (!existingDebugToken) {
|
||
|
newToken = uuidv4();
|
||
|
// We don't need to block on writing to indexeddb
|
||
|
// In case persistence failed, a new debug token will be generated everytime the page is refreshed.
|
||
|
// It renders the debug token useless because you have to manually register(whitelist) the new token in the firebase console again and again.
|
||
|
// If you see this error trying to use debug token, it probably means you are using a browser that doesn't support indexeddb.
|
||
|
// You should switch to a different browser that supports indexeddb
|
||
|
writeDebugTokenToIndexedDB(newToken).catch(function (e) {
|
||
|
return logger.warn("Failed to persist debug token to IndexedDB. Error: ".concat(e));
|
||
|
});
|
||
|
return [2 /*return*/, newToken];
|
||
|
}
|
||
|
else {
|
||
|
return [2 /*return*/, existingDebugToken];
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
function isDebugMode() {
|
||
|
var debugState = getDebugState();
|
||
|
return debugState.enabled;
|
||
|
}
|
||
|
function getDebugToken() {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var state;
|
||
|
return __generator(this, function (_a) {
|
||
|
state = getDebugState();
|
||
|
if (state.enabled && state.token) {
|
||
|
return [2 /*return*/, state.token.promise];
|
||
|
}
|
||
|
else {
|
||
|
// should not happen!
|
||
|
throw Error("\n Can't get debug token in production mode.\n ");
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
function initializeDebugMode() {
|
||
|
var globals = getGlobal();
|
||
|
var debugState = getDebugState();
|
||
|
// Set to true if this function has been called, whether or not
|
||
|
// it enabled debug mode.
|
||
|
debugState.initialized = true;
|
||
|
if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== 'string' &&
|
||
|
globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== true) {
|
||
|
return;
|
||
|
}
|
||
|
debugState.enabled = true;
|
||
|
var deferredToken = new Deferred();
|
||
|
debugState.token = deferredToken;
|
||
|
if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN === 'string') {
|
||
|
deferredToken.resolve(globals.FIREBASE_APPCHECK_DEBUG_TOKEN);
|
||
|
}
|
||
|
else {
|
||
|
deferredToken.resolve(readOrCreateDebugTokenFromStorage());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
// Initial hardcoded value agreed upon across platforms for initial launch.
|
||
|
// Format left open for possible dynamic error values and other fields in the future.
|
||
|
var defaultTokenErrorData = { error: 'UNKNOWN_ERROR' };
|
||
|
/**
|
||
|
* Stringify and base64 encode token error data.
|
||
|
*
|
||
|
* @param tokenError Error data, currently hardcoded.
|
||
|
*/
|
||
|
function formatDummyToken(tokenErrorData) {
|
||
|
return base64.encodeString(JSON.stringify(tokenErrorData),
|
||
|
/* webSafe= */ false);
|
||
|
}
|
||
|
/**
|
||
|
* This function always resolves.
|
||
|
* The result will contain an error field if there is any error.
|
||
|
* In case there is an error, the token field in the result will be populated with a dummy value
|
||
|
*/
|
||
|
function getToken$2(appCheck, forceRefresh) {
|
||
|
if (forceRefresh === void 0) { forceRefresh = false; }
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var app, state, token, error, cachedToken, shouldCallListeners, _a, _b, _c, _d, tokenFromDebugExchange, e_1, interopTokenResult;
|
||
|
return __generator(this, function (_e) {
|
||
|
switch (_e.label) {
|
||
|
case 0:
|
||
|
app = appCheck.app;
|
||
|
ensureActivated(app);
|
||
|
state = getStateReference(app);
|
||
|
token = state.token;
|
||
|
error = undefined;
|
||
|
/**
|
||
|
* If an invalid token was found in memory, clear token from
|
||
|
* memory and unset the local variable `token`.
|
||
|
*/
|
||
|
if (token && !isValid(token)) {
|
||
|
state.token = undefined;
|
||
|
token = undefined;
|
||
|
}
|
||
|
if (!!token) return [3 /*break*/, 4];
|
||
|
return [4 /*yield*/, state.cachedTokenPromise];
|
||
|
case 1:
|
||
|
cachedToken = _e.sent();
|
||
|
if (!cachedToken) return [3 /*break*/, 4];
|
||
|
if (!isValid(cachedToken)) return [3 /*break*/, 2];
|
||
|
token = cachedToken;
|
||
|
return [3 /*break*/, 4];
|
||
|
case 2:
|
||
|
// If there was an invalid token in the indexedDB cache, clear it.
|
||
|
return [4 /*yield*/, writeTokenToStorage(app, undefined)];
|
||
|
case 3:
|
||
|
// If there was an invalid token in the indexedDB cache, clear it.
|
||
|
_e.sent();
|
||
|
_e.label = 4;
|
||
|
case 4:
|
||
|
// Return the cached token (from either memory or indexedDB) if it's valid
|
||
|
if (!forceRefresh && token && isValid(token)) {
|
||
|
return [2 /*return*/, {
|
||
|
token: token.token
|
||
|
}];
|
||
|
}
|
||
|
shouldCallListeners = false;
|
||
|
if (!isDebugMode()) return [3 /*break*/, 9];
|
||
|
if (!!state.exchangeTokenPromise) return [3 /*break*/, 6];
|
||
|
_a = state;
|
||
|
_b = exchangeToken;
|
||
|
_c = getExchangeDebugTokenRequest;
|
||
|
_d = [app];
|
||
|
return [4 /*yield*/, getDebugToken()];
|
||
|
case 5:
|
||
|
_a.exchangeTokenPromise = _b.apply(void 0, [_c.apply(void 0, _d.concat([_e.sent()])),
|
||
|
appCheck.heartbeatServiceProvider]).finally(function () {
|
||
|
// Clear promise when settled - either resolved or rejected.
|
||
|
state.exchangeTokenPromise = undefined;
|
||
|
});
|
||
|
shouldCallListeners = true;
|
||
|
_e.label = 6;
|
||
|
case 6: return [4 /*yield*/, state.exchangeTokenPromise];
|
||
|
case 7:
|
||
|
tokenFromDebugExchange = _e.sent();
|
||
|
// Write debug token to indexedDB.
|
||
|
return [4 /*yield*/, writeTokenToStorage(app, tokenFromDebugExchange)];
|
||
|
case 8:
|
||
|
// Write debug token to indexedDB.
|
||
|
_e.sent();
|
||
|
// Write debug token to state.
|
||
|
state.token = tokenFromDebugExchange;
|
||
|
return [2 /*return*/, { token: tokenFromDebugExchange.token }];
|
||
|
case 9:
|
||
|
_e.trys.push([9, 11, , 12]);
|
||
|
// Avoid making another call to the exchange endpoint if one is in flight.
|
||
|
if (!state.exchangeTokenPromise) {
|
||
|
// state.provider is populated in initializeAppCheck()
|
||
|
// ensureActivated() at the top of this function checks that
|
||
|
// initializeAppCheck() has been called.
|
||
|
state.exchangeTokenPromise = state.provider.getToken().finally(function () {
|
||
|
// Clear promise when settled - either resolved or rejected.
|
||
|
state.exchangeTokenPromise = undefined;
|
||
|
});
|
||
|
shouldCallListeners = true;
|
||
|
}
|
||
|
return [4 /*yield*/, getStateReference(app).exchangeTokenPromise];
|
||
|
case 10:
|
||
|
token = _e.sent();
|
||
|
return [3 /*break*/, 12];
|
||
|
case 11:
|
||
|
e_1 = _e.sent();
|
||
|
if (e_1.code === "appCheck/".concat("throttled" /* AppCheckError.THROTTLED */)) {
|
||
|
// Warn if throttled, but do not treat it as an error.
|
||
|
logger.warn(e_1.message);
|
||
|
}
|
||
|
else {
|
||
|
// `getToken()` should never throw, but logging error text to console will aid debugging.
|
||
|
logger.error(e_1);
|
||
|
}
|
||
|
// Always save error to be added to dummy token.
|
||
|
error = e_1;
|
||
|
return [3 /*break*/, 12];
|
||
|
case 12:
|
||
|
if (!!token) return [3 /*break*/, 13];
|
||
|
// If token is undefined, there must be an error.
|
||
|
// Return a dummy token along with the error.
|
||
|
interopTokenResult = makeDummyTokenResult(error);
|
||
|
return [3 /*break*/, 16];
|
||
|
case 13:
|
||
|
if (!error) return [3 /*break*/, 14];
|
||
|
if (isValid(token)) {
|
||
|
// It's also possible a valid token exists, but there's also an error.
|
||
|
// (Such as if the token is almost expired, tries to refresh, and
|
||
|
// the exchange request fails.)
|
||
|
// We add a special error property here so that the refresher will
|
||
|
// count this as a failed attempt and use the backoff instead of
|
||
|
// retrying repeatedly with no delay, but any 3P listeners will not
|
||
|
// be hindered in getting the still-valid token.
|
||
|
interopTokenResult = {
|
||
|
token: token.token,
|
||
|
internalError: error
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
// No invalid tokens should make it to this step. Memory and cached tokens
|
||
|
// are checked. Other tokens are from fresh exchanges. But just in case.
|
||
|
interopTokenResult = makeDummyTokenResult(error);
|
||
|
}
|
||
|
return [3 /*break*/, 16];
|
||
|
case 14:
|
||
|
interopTokenResult = {
|
||
|
token: token.token
|
||
|
};
|
||
|
// write the new token to the memory state as well as the persistent storage.
|
||
|
// Only do it if we got a valid new token
|
||
|
state.token = token;
|
||
|
return [4 /*yield*/, writeTokenToStorage(app, token)];
|
||
|
case 15:
|
||
|
_e.sent();
|
||
|
_e.label = 16;
|
||
|
case 16:
|
||
|
if (shouldCallListeners) {
|
||
|
notifyTokenListeners(app, interopTokenResult);
|
||
|
}
|
||
|
return [2 /*return*/, interopTokenResult];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Internal API for limited use tokens. Skips all FAC state and simply calls
|
||
|
* the underlying provider.
|
||
|
*/
|
||
|
function getLimitedUseToken$1(appCheck) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var app, provider, debugToken, token, token;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0:
|
||
|
app = appCheck.app;
|
||
|
ensureActivated(app);
|
||
|
provider = getStateReference(app).provider;
|
||
|
if (!isDebugMode()) return [3 /*break*/, 3];
|
||
|
return [4 /*yield*/, getDebugToken()];
|
||
|
case 1:
|
||
|
debugToken = _a.sent();
|
||
|
return [4 /*yield*/, exchangeToken(getExchangeDebugTokenRequest(app, debugToken), appCheck.heartbeatServiceProvider)];
|
||
|
case 2:
|
||
|
token = (_a.sent()).token;
|
||
|
return [2 /*return*/, { token: token }];
|
||
|
case 3: return [4 /*yield*/, provider.getToken()];
|
||
|
case 4:
|
||
|
token = (_a.sent()).token;
|
||
|
return [2 /*return*/, { token: token }];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
function addTokenListener(appCheck, type, listener, onError) {
|
||
|
var app = appCheck.app;
|
||
|
var state = getStateReference(app);
|
||
|
var tokenObserver = {
|
||
|
next: listener,
|
||
|
error: onError,
|
||
|
type: type
|
||
|
};
|
||
|
state.tokenObservers = __spreadArray(__spreadArray([], state.tokenObservers, true), [tokenObserver], false);
|
||
|
// Invoke the listener async immediately if there is a valid token
|
||
|
// in memory.
|
||
|
if (state.token && isValid(state.token)) {
|
||
|
var validToken_1 = state.token;
|
||
|
Promise.resolve()
|
||
|
.then(function () {
|
||
|
listener({ token: validToken_1.token });
|
||
|
initTokenRefresher(appCheck);
|
||
|
})
|
||
|
.catch(function () {
|
||
|
/* we don't care about exceptions thrown in listeners */
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Wait for any cached token promise to resolve before starting the token
|
||
|
* refresher. The refresher checks to see if there is an existing token
|
||
|
* in state and calls the exchange endpoint if not. We should first let the
|
||
|
* IndexedDB check have a chance to populate state if it can.
|
||
|
*
|
||
|
* Listener call isn't needed here because cachedTokenPromise will call any
|
||
|
* listeners that exist when it resolves.
|
||
|
*/
|
||
|
// state.cachedTokenPromise is always populated in `activate()`.
|
||
|
void state.cachedTokenPromise.then(function () { return initTokenRefresher(appCheck); });
|
||
|
}
|
||
|
function removeTokenListener(app, listener) {
|
||
|
var state = getStateReference(app);
|
||
|
var newObservers = state.tokenObservers.filter(function (tokenObserver) { return tokenObserver.next !== listener; });
|
||
|
if (newObservers.length === 0 &&
|
||
|
state.tokenRefresher &&
|
||
|
state.tokenRefresher.isRunning()) {
|
||
|
state.tokenRefresher.stop();
|
||
|
}
|
||
|
state.tokenObservers = newObservers;
|
||
|
}
|
||
|
/**
|
||
|
* Logic to create and start refresher as needed.
|
||
|
*/
|
||
|
function initTokenRefresher(appCheck) {
|
||
|
var app = appCheck.app;
|
||
|
var state = getStateReference(app);
|
||
|
// Create the refresher but don't start it if `isTokenAutoRefreshEnabled`
|
||
|
// is not true.
|
||
|
var refresher = state.tokenRefresher;
|
||
|
if (!refresher) {
|
||
|
refresher = createTokenRefresher(appCheck);
|
||
|
state.tokenRefresher = refresher;
|
||
|
}
|
||
|
if (!refresher.isRunning() && state.isTokenAutoRefreshEnabled) {
|
||
|
refresher.start();
|
||
|
}
|
||
|
}
|
||
|
function createTokenRefresher(appCheck) {
|
||
|
var _this = this;
|
||
|
var app = appCheck.app;
|
||
|
return new Refresher(
|
||
|
// Keep in mind when this fails for any reason other than the ones
|
||
|
// for which we should retry, it will effectively stop the proactive refresh.
|
||
|
function () { return __awaiter(_this, void 0, void 0, function () {
|
||
|
var state, result;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0:
|
||
|
state = getStateReference(app);
|
||
|
if (!!state.token) return [3 /*break*/, 2];
|
||
|
return [4 /*yield*/, getToken$2(appCheck)];
|
||
|
case 1:
|
||
|
result = _a.sent();
|
||
|
return [3 /*break*/, 4];
|
||
|
case 2: return [4 /*yield*/, getToken$2(appCheck, true)];
|
||
|
case 3:
|
||
|
result = _a.sent();
|
||
|
_a.label = 4;
|
||
|
case 4:
|
||
|
/**
|
||
|
* getToken() always resolves. In case the result has an error field defined, it means
|
||
|
* the operation failed, and we should retry.
|
||
|
*/
|
||
|
if (result.error) {
|
||
|
throw result.error;
|
||
|
}
|
||
|
/**
|
||
|
* A special `internalError` field reflects that there was an error
|
||
|
* getting a new token from the exchange endpoint, but there's still a
|
||
|
* previous token that's valid for now and this should be passed to 2P/3P
|
||
|
* requests for a token. But we want this callback (`this.operation` in
|
||
|
* `Refresher`) to throw in order to kick off the Refresher's retry
|
||
|
* backoff. (Setting `hasSucceeded` to false.)
|
||
|
*/
|
||
|
if (result.internalError) {
|
||
|
throw result.internalError;
|
||
|
}
|
||
|
return [2 /*return*/];
|
||
|
}
|
||
|
});
|
||
|
}); }, function () {
|
||
|
return true;
|
||
|
}, function () {
|
||
|
var state = getStateReference(app);
|
||
|
if (state.token) {
|
||
|
// issuedAtTime + (50% * total TTL) + 5 minutes
|
||
|
var nextRefreshTimeMillis = state.token.issuedAtTimeMillis +
|
||
|
(state.token.expireTimeMillis - state.token.issuedAtTimeMillis) *
|
||
|
0.5 +
|
||
|
5 * 60 * 1000;
|
||
|
// Do not allow refresh time to be past (expireTime - 5 minutes)
|
||
|
var latestAllowableRefresh = state.token.expireTimeMillis - 5 * 60 * 1000;
|
||
|
nextRefreshTimeMillis = Math.min(nextRefreshTimeMillis, latestAllowableRefresh);
|
||
|
return Math.max(0, nextRefreshTimeMillis - Date.now());
|
||
|
}
|
||
|
else {
|
||
|
return 0;
|
||
|
}
|
||
|
}, TOKEN_REFRESH_TIME.RETRIAL_MIN_WAIT, TOKEN_REFRESH_TIME.RETRIAL_MAX_WAIT);
|
||
|
}
|
||
|
function notifyTokenListeners(app, token) {
|
||
|
var observers = getStateReference(app).tokenObservers;
|
||
|
for (var _i = 0, observers_1 = observers; _i < observers_1.length; _i++) {
|
||
|
var observer = observers_1[_i];
|
||
|
try {
|
||
|
if (observer.type === "EXTERNAL" /* ListenerType.EXTERNAL */ && token.error != null) {
|
||
|
// If this listener was added by a 3P call, send any token error to
|
||
|
// the supplied error handler. A 3P observer always has an error
|
||
|
// handler.
|
||
|
observer.error(token.error);
|
||
|
}
|
||
|
else {
|
||
|
// If the token has no error field, always return the token.
|
||
|
// If this is a 2P listener, return the token, whether or not it
|
||
|
// has an error field.
|
||
|
observer.next(token);
|
||
|
}
|
||
|
}
|
||
|
catch (e) {
|
||
|
// Errors in the listener function itself are always ignored.
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
function isValid(token) {
|
||
|
return token.expireTimeMillis - Date.now() > 0;
|
||
|
}
|
||
|
function makeDummyTokenResult(error) {
|
||
|
return {
|
||
|
token: formatDummyToken(defaultTokenErrorData),
|
||
|
error: error
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
/**
|
||
|
* AppCheck Service class.
|
||
|
*/
|
||
|
var AppCheckService = /** @class */ (function () {
|
||
|
function AppCheckService(app, heartbeatServiceProvider) {
|
||
|
this.app = app;
|
||
|
this.heartbeatServiceProvider = heartbeatServiceProvider;
|
||
|
}
|
||
|
AppCheckService.prototype._delete = function () {
|
||
|
var tokenObservers = getStateReference(this.app).tokenObservers;
|
||
|
for (var _i = 0, tokenObservers_1 = tokenObservers; _i < tokenObservers_1.length; _i++) {
|
||
|
var tokenObserver = tokenObservers_1[_i];
|
||
|
removeTokenListener(this.app, tokenObserver.next);
|
||
|
}
|
||
|
return Promise.resolve();
|
||
|
};
|
||
|
return AppCheckService;
|
||
|
}());
|
||
|
function factory(app, heartbeatServiceProvider) {
|
||
|
return new AppCheckService(app, heartbeatServiceProvider);
|
||
|
}
|
||
|
function internalFactory(appCheck) {
|
||
|
return {
|
||
|
getToken: function (forceRefresh) { return getToken$2(appCheck, forceRefresh); },
|
||
|
getLimitedUseToken: function () { return getLimitedUseToken$1(appCheck); },
|
||
|
addTokenListener: function (listener) {
|
||
|
return addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, listener);
|
||
|
},
|
||
|
removeTokenListener: function (listener) { return removeTokenListener(appCheck.app, listener); }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
var name = "@firebase/app-check";
|
||
|
var version = "0.8.2";
|
||
|
|
||
|
/**
|
||
|
* @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 RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js';
|
||
|
var RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js';
|
||
|
function initializeV3(app, siteKey) {
|
||
|
var initialized = new Deferred();
|
||
|
var state = getStateReference(app);
|
||
|
state.reCAPTCHAState = { initialized: initialized };
|
||
|
var divId = makeDiv(app);
|
||
|
var grecaptcha = getRecaptcha(false);
|
||
|
if (!grecaptcha) {
|
||
|
loadReCAPTCHAV3Script(function () {
|
||
|
var grecaptcha = getRecaptcha(false);
|
||
|
if (!grecaptcha) {
|
||
|
// it shouldn't happen.
|
||
|
throw new Error('no recaptcha');
|
||
|
}
|
||
|
queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
|
||
|
}
|
||
|
return initialized.promise;
|
||
|
}
|
||
|
function initializeEnterprise(app, siteKey) {
|
||
|
var initialized = new Deferred();
|
||
|
var state = getStateReference(app);
|
||
|
state.reCAPTCHAState = { initialized: initialized };
|
||
|
var divId = makeDiv(app);
|
||
|
var grecaptcha = getRecaptcha(true);
|
||
|
if (!grecaptcha) {
|
||
|
loadReCAPTCHAEnterpriseScript(function () {
|
||
|
var grecaptcha = getRecaptcha(true);
|
||
|
if (!grecaptcha) {
|
||
|
// it shouldn't happen.
|
||
|
throw new Error('no recaptcha');
|
||
|
}
|
||
|
queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
|
||
|
}
|
||
|
return initialized.promise;
|
||
|
}
|
||
|
/**
|
||
|
* Add listener to render the widget and resolve the promise when
|
||
|
* the grecaptcha.ready() event fires.
|
||
|
*/
|
||
|
function queueWidgetRender(app, siteKey, grecaptcha, container, initialized) {
|
||
|
grecaptcha.ready(function () {
|
||
|
// Invisible widgets allow us to set a different siteKey for each widget,
|
||
|
// so we use them to support multiple apps
|
||
|
renderInvisibleWidget(app, siteKey, grecaptcha, container);
|
||
|
initialized.resolve(grecaptcha);
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Add invisible div to page.
|
||
|
*/
|
||
|
function makeDiv(app) {
|
||
|
var divId = "fire_app_check_".concat(app.name);
|
||
|
var invisibleDiv = document.createElement('div');
|
||
|
invisibleDiv.id = divId;
|
||
|
invisibleDiv.style.display = 'none';
|
||
|
document.body.appendChild(invisibleDiv);
|
||
|
return divId;
|
||
|
}
|
||
|
function getToken$1(app) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var reCAPTCHAState, recaptcha;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0:
|
||
|
ensureActivated(app);
|
||
|
reCAPTCHAState = getStateReference(app).reCAPTCHAState;
|
||
|
return [4 /*yield*/, reCAPTCHAState.initialized.promise];
|
||
|
case 1:
|
||
|
recaptcha = _a.sent();
|
||
|
return [2 /*return*/, new Promise(function (resolve, _reject) {
|
||
|
// Updated after initialization is complete.
|
||
|
var reCAPTCHAState = getStateReference(app).reCAPTCHAState;
|
||
|
recaptcha.ready(function () {
|
||
|
resolve(
|
||
|
// widgetId is guaranteed to be available if reCAPTCHAState.initialized.promise resolved.
|
||
|
recaptcha.execute(reCAPTCHAState.widgetId, {
|
||
|
action: 'fire_app_check'
|
||
|
}));
|
||
|
});
|
||
|
})];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
*
|
||
|
* @param app
|
||
|
* @param container - Id of a HTML element.
|
||
|
*/
|
||
|
function renderInvisibleWidget(app, siteKey, grecaptcha, container) {
|
||
|
var widgetId = grecaptcha.render(container, {
|
||
|
sitekey: siteKey,
|
||
|
size: 'invisible',
|
||
|
// Success callback - set state
|
||
|
callback: function () {
|
||
|
getStateReference(app).reCAPTCHAState.succeeded = true;
|
||
|
},
|
||
|
// Failure callback - set state
|
||
|
'error-callback': function () {
|
||
|
getStateReference(app).reCAPTCHAState.succeeded = false;
|
||
|
}
|
||
|
});
|
||
|
var state = getStateReference(app);
|
||
|
state.reCAPTCHAState = __assign(__assign({}, state.reCAPTCHAState), { // state.reCAPTCHAState is set in the initialize()
|
||
|
widgetId: widgetId });
|
||
|
}
|
||
|
function loadReCAPTCHAV3Script(onload) {
|
||
|
var script = document.createElement('script');
|
||
|
script.src = RECAPTCHA_URL;
|
||
|
script.onload = onload;
|
||
|
document.head.appendChild(script);
|
||
|
}
|
||
|
function loadReCAPTCHAEnterpriseScript(onload) {
|
||
|
var script = document.createElement('script');
|
||
|
script.src = RECAPTCHA_ENTERPRISE_URL;
|
||
|
script.onload = onload;
|
||
|
document.head.appendChild(script);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @license
|
||
|
* Copyright 2021 Google LLC
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
/**
|
||
|
* App Check provider that can obtain a reCAPTCHA V3 token and exchange it
|
||
|
* for an App Check token.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
var ReCaptchaV3Provider = /** @class */ (function () {
|
||
|
/**
|
||
|
* Create a ReCaptchaV3Provider instance.
|
||
|
* @param siteKey - ReCAPTCHA V3 siteKey.
|
||
|
*/
|
||
|
function ReCaptchaV3Provider(_siteKey) {
|
||
|
this._siteKey = _siteKey;
|
||
|
/**
|
||
|
* Throttle requests on certain error codes to prevent too many retries
|
||
|
* in a short time.
|
||
|
*/
|
||
|
this._throttleData = null;
|
||
|
}
|
||
|
/**
|
||
|
* Returns an App Check token.
|
||
|
* @internal
|
||
|
*/
|
||
|
ReCaptchaV3Provider.prototype.getToken = function () {
|
||
|
var _a, _b, _c;
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var attestedClaimsToken, result, e_1;
|
||
|
return __generator(this, function (_d) {
|
||
|
switch (_d.label) {
|
||
|
case 0:
|
||
|
throwIfThrottled(this._throttleData);
|
||
|
return [4 /*yield*/, getToken$1(this._app).catch(function (_e) {
|
||
|
// reCaptcha.execute() throws null which is not very descriptive.
|
||
|
throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
|
||
|
})];
|
||
|
case 1:
|
||
|
attestedClaimsToken = _d.sent();
|
||
|
// Check if a failure state was set by the recaptcha "error-callback".
|
||
|
if (!((_a = getStateReference(this._app).reCAPTCHAState) === null || _a === void 0 ? void 0 : _a.succeeded)) {
|
||
|
throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
|
||
|
}
|
||
|
_d.label = 2;
|
||
|
case 2:
|
||
|
_d.trys.push([2, 4, , 5]);
|
||
|
return [4 /*yield*/, exchangeToken(getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider)];
|
||
|
case 3:
|
||
|
result = _d.sent();
|
||
|
return [3 /*break*/, 5];
|
||
|
case 4:
|
||
|
e_1 = _d.sent();
|
||
|
if ((_b = e_1.code) === null || _b === void 0 ? void 0 : _b.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) {
|
||
|
this._throttleData = setBackoff(Number((_c = e_1.customData) === null || _c === void 0 ? void 0 : _c.httpStatus), this._throttleData);
|
||
|
throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, {
|
||
|
time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()),
|
||
|
httpStatus: this._throttleData.httpStatus
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
throw e_1;
|
||
|
}
|
||
|
case 5:
|
||
|
// If successful, clear throttle data.
|
||
|
this._throttleData = null;
|
||
|
return [2 /*return*/, result];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
ReCaptchaV3Provider.prototype.initialize = function (app) {
|
||
|
this._app = app;
|
||
|
this._heartbeatServiceProvider = _getProvider(app, 'heartbeat');
|
||
|
initializeV3(app, this._siteKey).catch(function () {
|
||
|
/* we don't care about the initialization result */
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
ReCaptchaV3Provider.prototype.isEqual = function (otherProvider) {
|
||
|
if (otherProvider instanceof ReCaptchaV3Provider) {
|
||
|
return this._siteKey === otherProvider._siteKey;
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
return ReCaptchaV3Provider;
|
||
|
}());
|
||
|
/**
|
||
|
* App Check provider that can obtain a reCAPTCHA Enterprise token and exchange it
|
||
|
* for an App Check token.
|
||
|
*
|
||
|
* @public
|
||
|
*/
|
||
|
var ReCaptchaEnterpriseProvider = /** @class */ (function () {
|
||
|
/**
|
||
|
* Create a ReCaptchaEnterpriseProvider instance.
|
||
|
* @param siteKey - reCAPTCHA Enterprise score-based site key.
|
||
|
*/
|
||
|
function ReCaptchaEnterpriseProvider(_siteKey) {
|
||
|
this._siteKey = _siteKey;
|
||
|
/**
|
||
|
* Throttle requests on certain error codes to prevent too many retries
|
||
|
* in a short time.
|
||
|
*/
|
||
|
this._throttleData = null;
|
||
|
}
|
||
|
/**
|
||
|
* Returns an App Check token.
|
||
|
* @internal
|
||
|
*/
|
||
|
ReCaptchaEnterpriseProvider.prototype.getToken = function () {
|
||
|
var _a, _b, _c;
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var attestedClaimsToken, result, e_2;
|
||
|
return __generator(this, function (_d) {
|
||
|
switch (_d.label) {
|
||
|
case 0:
|
||
|
throwIfThrottled(this._throttleData);
|
||
|
return [4 /*yield*/, getToken$1(this._app).catch(function (_e) {
|
||
|
// reCaptcha.execute() throws null which is not very descriptive.
|
||
|
throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
|
||
|
})];
|
||
|
case 1:
|
||
|
attestedClaimsToken = _d.sent();
|
||
|
// Check if a failure state was set by the recaptcha "error-callback".
|
||
|
if (!((_a = getStateReference(this._app).reCAPTCHAState) === null || _a === void 0 ? void 0 : _a.succeeded)) {
|
||
|
throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
|
||
|
}
|
||
|
_d.label = 2;
|
||
|
case 2:
|
||
|
_d.trys.push([2, 4, , 5]);
|
||
|
return [4 /*yield*/, exchangeToken(getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider)];
|
||
|
case 3:
|
||
|
result = _d.sent();
|
||
|
return [3 /*break*/, 5];
|
||
|
case 4:
|
||
|
e_2 = _d.sent();
|
||
|
if ((_b = e_2.code) === null || _b === void 0 ? void 0 : _b.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) {
|
||
|
this._throttleData = setBackoff(Number((_c = e_2.customData) === null || _c === void 0 ? void 0 : _c.httpStatus), this._throttleData);
|
||
|
throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, {
|
||
|
time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()),
|
||
|
httpStatus: this._throttleData.httpStatus
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
throw e_2;
|
||
|
}
|
||
|
case 5:
|
||
|
// If successful, clear throttle data.
|
||
|
this._throttleData = null;
|
||
|
return [2 /*return*/, result];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
ReCaptchaEnterpriseProvider.prototype.initialize = function (app) {
|
||
|
this._app = app;
|
||
|
this._heartbeatServiceProvider = _getProvider(app, 'heartbeat');
|
||
|
initializeEnterprise(app, this._siteKey).catch(function () {
|
||
|
/* we don't care about the initialization result */
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
ReCaptchaEnterpriseProvider.prototype.isEqual = function (otherProvider) {
|
||
|
if (otherProvider instanceof ReCaptchaEnterpriseProvider) {
|
||
|
return this._siteKey === otherProvider._siteKey;
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
return ReCaptchaEnterpriseProvider;
|
||
|
}());
|
||
|
/**
|
||
|
* Custom provider class.
|
||
|
* @public
|
||
|
*/
|
||
|
var CustomProvider = /** @class */ (function () {
|
||
|
function CustomProvider(_customProviderOptions) {
|
||
|
this._customProviderOptions = _customProviderOptions;
|
||
|
}
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
CustomProvider.prototype.getToken = function () {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var customToken, issuedAtTimeSeconds, issuedAtTimeMillis;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0: return [4 /*yield*/, this._customProviderOptions.getToken()];
|
||
|
case 1:
|
||
|
customToken = _a.sent();
|
||
|
issuedAtTimeSeconds = issuedAtTime(customToken.token);
|
||
|
issuedAtTimeMillis = issuedAtTimeSeconds !== null &&
|
||
|
issuedAtTimeSeconds < Date.now() &&
|
||
|
issuedAtTimeSeconds > 0
|
||
|
? issuedAtTimeSeconds * 1000
|
||
|
: Date.now();
|
||
|
return [2 /*return*/, __assign(__assign({}, customToken), { issuedAtTimeMillis: issuedAtTimeMillis })];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
CustomProvider.prototype.initialize = function (app) {
|
||
|
this._app = app;
|
||
|
};
|
||
|
/**
|
||
|
* @internal
|
||
|
*/
|
||
|
CustomProvider.prototype.isEqual = function (otherProvider) {
|
||
|
if (otherProvider instanceof CustomProvider) {
|
||
|
return (this._customProviderOptions.getToken.toString() ===
|
||
|
otherProvider._customProviderOptions.getToken.toString());
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
return CustomProvider;
|
||
|
}());
|
||
|
/**
|
||
|
* Set throttle data to block requests until after a certain time
|
||
|
* depending on the failed request's status code.
|
||
|
* @param httpStatus - Status code of failed request.
|
||
|
* @param throttleData - `ThrottleData` object containing previous throttle
|
||
|
* data state.
|
||
|
* @returns Data about current throttle state and expiration time.
|
||
|
*/
|
||
|
function setBackoff(httpStatus, throttleData) {
|
||
|
/**
|
||
|
* Block retries for 1 day for the following error codes:
|
||
|
*
|
||
|
* 404: Likely malformed URL.
|
||
|
*
|
||
|
* 403:
|
||
|
* - Attestation failed
|
||
|
* - Wrong API key
|
||
|
* - Project deleted
|
||
|
*/
|
||
|
if (httpStatus === 404 || httpStatus === 403) {
|
||
|
return {
|
||
|
backoffCount: 1,
|
||
|
allowRequestsAfter: Date.now() + ONE_DAY,
|
||
|
httpStatus: httpStatus
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
/**
|
||
|
* For all other error codes, the time when it is ok to retry again
|
||
|
* is based on exponential backoff.
|
||
|
*/
|
||
|
var backoffCount = throttleData ? throttleData.backoffCount : 0;
|
||
|
var backoffMillis = calculateBackoffMillis(backoffCount, 1000, 2);
|
||
|
return {
|
||
|
backoffCount: backoffCount + 1,
|
||
|
allowRequestsAfter: Date.now() + backoffMillis,
|
||
|
httpStatus: httpStatus
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
function throwIfThrottled(throttleData) {
|
||
|
if (throttleData) {
|
||
|
if (Date.now() - throttleData.allowRequestsAfter <= 0) {
|
||
|
// If before, throw.
|
||
|
throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, {
|
||
|
time: getDurationString(throttleData.allowRequestsAfter - Date.now()),
|
||
|
httpStatus: throttleData.httpStatus
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*/
|
||
|
/**
|
||
|
* Activate App Check for the given app. Can be called only once per app.
|
||
|
* @param app - the {@link @firebase/app#FirebaseApp} to activate App Check for
|
||
|
* @param options - App Check initialization options
|
||
|
* @public
|
||
|
*/
|
||
|
function initializeAppCheck(app, options) {
|
||
|
if (app === void 0) { app = getApp(); }
|
||
|
app = getModularInstance(app);
|
||
|
var provider = _getProvider(app, 'app-check');
|
||
|
// Ensure initializeDebugMode() is only called once.
|
||
|
if (!getDebugState().initialized) {
|
||
|
initializeDebugMode();
|
||
|
}
|
||
|
// Log a message containing the debug token when `initializeAppCheck()`
|
||
|
// is called in debug mode.
|
||
|
if (isDebugMode()) {
|
||
|
// Do not block initialization to get the token for the message.
|
||
|
void getDebugToken().then(function (token) {
|
||
|
// Not using logger because I don't think we ever want this accidentally hidden.
|
||
|
return console.log("App Check debug token: ".concat(token, ". You will need to add it to your app's App Check settings in the Firebase console for it to work."));
|
||
|
});
|
||
|
}
|
||
|
if (provider.isInitialized()) {
|
||
|
var existingInstance = provider.getImmediate();
|
||
|
var initialOptions = provider.getOptions();
|
||
|
if (initialOptions.isTokenAutoRefreshEnabled ===
|
||
|
options.isTokenAutoRefreshEnabled &&
|
||
|
initialOptions.provider.isEqual(options.provider)) {
|
||
|
return existingInstance;
|
||
|
}
|
||
|
else {
|
||
|
throw ERROR_FACTORY.create("already-initialized" /* AppCheckError.ALREADY_INITIALIZED */, {
|
||
|
appName: app.name
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
var appCheck = provider.initialize({ options: options });
|
||
|
_activate(app, options.provider, options.isTokenAutoRefreshEnabled);
|
||
|
// If isTokenAutoRefreshEnabled is false, do not send any requests to the
|
||
|
// exchange endpoint without an explicit call from the user either directly
|
||
|
// or through another Firebase library (storage, functions, etc.)
|
||
|
if (getStateReference(app).isTokenAutoRefreshEnabled) {
|
||
|
// Adding a listener will start the refresher and fetch a token if needed.
|
||
|
// This gets a token ready and prevents a delay when an internal library
|
||
|
// requests the token.
|
||
|
// Listener function does not need to do anything, its base functionality
|
||
|
// of calling getToken() already fetches token and writes it to memory/storage.
|
||
|
addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, function () { });
|
||
|
}
|
||
|
return appCheck;
|
||
|
}
|
||
|
/**
|
||
|
* Activate App Check
|
||
|
* @param app - Firebase app to activate App Check for.
|
||
|
* @param provider - reCAPTCHA v3 provider or
|
||
|
* custom token provider.
|
||
|
* @param isTokenAutoRefreshEnabled - If true, the SDK automatically
|
||
|
* refreshes App Check tokens as needed. If undefined, defaults to the
|
||
|
* value of `app.automaticDataCollectionEnabled`, which defaults to
|
||
|
* false and can be set in the app config.
|
||
|
*/
|
||
|
function _activate(app, provider, isTokenAutoRefreshEnabled) {
|
||
|
// Create an entry in the APP_CHECK_STATES map. Further changes should
|
||
|
// directly mutate this object.
|
||
|
var state = setInitialState(app, __assign({}, DEFAULT_STATE));
|
||
|
state.activated = true;
|
||
|
state.provider = provider; // Read cached token from storage if it exists and store it in memory.
|
||
|
state.cachedTokenPromise = readTokenFromStorage(app).then(function (cachedToken) {
|
||
|
if (cachedToken && isValid(cachedToken)) {
|
||
|
state.token = cachedToken;
|
||
|
// notify all listeners with the cached token
|
||
|
notifyTokenListeners(app, { token: cachedToken.token });
|
||
|
}
|
||
|
return cachedToken;
|
||
|
});
|
||
|
// Use value of global `automaticDataCollectionEnabled` (which
|
||
|
// itself defaults to false if not specified in config) if
|
||
|
// `isTokenAutoRefreshEnabled` param was not provided by user.
|
||
|
state.isTokenAutoRefreshEnabled =
|
||
|
isTokenAutoRefreshEnabled === undefined
|
||
|
? app.automaticDataCollectionEnabled
|
||
|
: isTokenAutoRefreshEnabled;
|
||
|
state.provider.initialize(app);
|
||
|
}
|
||
|
/**
|
||
|
* Set whether App Check will automatically refresh tokens as needed.
|
||
|
*
|
||
|
* @param appCheckInstance - The App Check service instance.
|
||
|
* @param isTokenAutoRefreshEnabled - If true, the SDK automatically
|
||
|
* refreshes App Check tokens as needed. This overrides any value set
|
||
|
* during `initializeAppCheck()`.
|
||
|
* @public
|
||
|
*/
|
||
|
function setTokenAutoRefreshEnabled(appCheckInstance, isTokenAutoRefreshEnabled) {
|
||
|
var app = appCheckInstance.app;
|
||
|
var state = getStateReference(app);
|
||
|
// This will exist if any product libraries have called
|
||
|
// `addTokenListener()`
|
||
|
if (state.tokenRefresher) {
|
||
|
if (isTokenAutoRefreshEnabled === true) {
|
||
|
state.tokenRefresher.start();
|
||
|
}
|
||
|
else {
|
||
|
state.tokenRefresher.stop();
|
||
|
}
|
||
|
}
|
||
|
state.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled;
|
||
|
}
|
||
|
/**
|
||
|
* Get the current App Check token. Attaches to the most recent
|
||
|
* in-flight request if one is present. Returns null if no token
|
||
|
* is present and no token requests are in-flight.
|
||
|
*
|
||
|
* @param appCheckInstance - The App Check service instance.
|
||
|
* @param forceRefresh - If true, will always try to fetch a fresh token.
|
||
|
* If false, will use a cached token if found in storage.
|
||
|
* @public
|
||
|
*/
|
||
|
function getToken(appCheckInstance, forceRefresh) {
|
||
|
return __awaiter(this, void 0, void 0, function () {
|
||
|
var result;
|
||
|
return __generator(this, function (_a) {
|
||
|
switch (_a.label) {
|
||
|
case 0: return [4 /*yield*/, getToken$2(appCheckInstance, forceRefresh)];
|
||
|
case 1:
|
||
|
result = _a.sent();
|
||
|
if (result.error) {
|
||
|
throw result.error;
|
||
|
}
|
||
|
return [2 /*return*/, { token: result.token }];
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Requests a Firebase App Check token. This method should be used
|
||
|
* only if you need to authorize requests to a non-Firebase backend.
|
||
|
*
|
||
|
* Returns limited-use tokens that are intended for use with your
|
||
|
* non-Firebase backend endpoints that are protected with
|
||
|
* <a href="https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection">
|
||
|
* Replay Protection</a>. This method
|
||
|
* does not affect the token generation behavior of the
|
||
|
* #getAppCheckToken() method.
|
||
|
*
|
||
|
* @param appCheckInstance - The App Check service instance.
|
||
|
* @returns The limited use token.
|
||
|
* @public
|
||
|
*/
|
||
|
function getLimitedUseToken(appCheckInstance) {
|
||
|
return getLimitedUseToken$1(appCheckInstance);
|
||
|
}
|
||
|
/**
|
||
|
* Wraps `addTokenListener`/`removeTokenListener` methods in an `Observer`
|
||
|
* pattern for public use.
|
||
|
*/
|
||
|
function onTokenChanged(appCheckInstance, onNextOrObserver, onError,
|
||
|
/**
|
||
|
* NOTE: Although an `onCompletion` callback can be provided, it will
|
||
|
* never be called because the token stream is never-ending.
|
||
|
* It is added only for API consistency with the observer pattern, which
|
||
|
* we follow in JS APIs.
|
||
|
*/
|
||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||
|
onCompletion) {
|
||
|
var nextFn = function () { };
|
||
|
var errorFn = function () { };
|
||
|
if (onNextOrObserver.next != null) {
|
||
|
nextFn = onNextOrObserver.next.bind(onNextOrObserver);
|
||
|
}
|
||
|
else {
|
||
|
nextFn = onNextOrObserver;
|
||
|
}
|
||
|
if (onNextOrObserver.error != null) {
|
||
|
errorFn = onNextOrObserver.error.bind(onNextOrObserver);
|
||
|
}
|
||
|
else if (onError) {
|
||
|
errorFn = onError;
|
||
|
}
|
||
|
addTokenListener(appCheckInstance, "EXTERNAL" /* ListenerType.EXTERNAL */, nextFn, errorFn);
|
||
|
return function () { return removeTokenListener(appCheckInstance.app, nextFn); };
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The Firebase App Check Web SDK.
|
||
|
*
|
||
|
* @remarks
|
||
|
* Firebase App Check does not work in a Node.js environment using `ReCaptchaV3Provider` or
|
||
|
* `ReCaptchaEnterpriseProvider`, but can be used in Node.js if you use
|
||
|
* `CustomProvider` and write your own attestation method.
|
||
|
*
|
||
|
* @packageDocumentation
|
||
|
*/
|
||
|
var APP_CHECK_NAME = 'app-check';
|
||
|
var APP_CHECK_NAME_INTERNAL = 'app-check-internal';
|
||
|
function registerAppCheck() {
|
||
|
// The public interface
|
||
|
_registerComponent(new Component(APP_CHECK_NAME, function (container) {
|
||
|
// getImmediate for FirebaseApp will always succeed
|
||
|
var app = container.getProvider('app').getImmediate();
|
||
|
var heartbeatServiceProvider = container.getProvider('heartbeat');
|
||
|
return factory(app, heartbeatServiceProvider);
|
||
|
}, "PUBLIC" /* ComponentType.PUBLIC */)
|
||
|
.setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */)
|
||
|
/**
|
||
|
* Initialize app-check-internal after app-check is initialized to make AppCheck available to
|
||
|
* other Firebase SDKs
|
||
|
*/
|
||
|
.setInstanceCreatedCallback(function (container, _identifier, _appcheckService) {
|
||
|
container.getProvider(APP_CHECK_NAME_INTERNAL).initialize();
|
||
|
}));
|
||
|
// The internal interface used by other Firebase products
|
||
|
_registerComponent(new Component(APP_CHECK_NAME_INTERNAL, function (container) {
|
||
|
var appCheck = container.getProvider('app-check').getImmediate();
|
||
|
return internalFactory(appCheck);
|
||
|
}, "PUBLIC" /* ComponentType.PUBLIC */).setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */));
|
||
|
registerVersion(name, version);
|
||
|
}
|
||
|
registerAppCheck();
|
||
|
|
||
|
export { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider, getLimitedUseToken, getToken, initializeAppCheck, onTokenChanged, setTokenAutoRefreshEnabled };
|
||
|
//# sourceMappingURL=index.esm.js.map
|