index.esm2017.js 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662
  1. import { _getProvider, getApp, _registerComponent, registerVersion } from '@firebase/app';
  2. import { Component } from '@firebase/component';
  3. import { Deferred, ErrorFactory, isIndexedDBAvailable, uuidv4, getGlobal, base64, issuedAtTime, calculateBackoffMillis, getModularInstance } from '@firebase/util';
  4. import { Logger } from '@firebase/logger';
  5. /**
  6. * @license
  7. * Copyright 2020 Google LLC
  8. *
  9. * Licensed under the Apache License, Version 2.0 (the "License");
  10. * you may not use this file except in compliance with the License.
  11. * You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing, software
  16. * distributed under the License is distributed on an "AS IS" BASIS,
  17. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. * See the License for the specific language governing permissions and
  19. * limitations under the License.
  20. */
  21. const APP_CHECK_STATES = new Map();
  22. const DEFAULT_STATE = {
  23. activated: false,
  24. tokenObservers: []
  25. };
  26. const DEBUG_STATE = {
  27. initialized: false,
  28. enabled: false
  29. };
  30. /**
  31. * Gets a reference to the state object.
  32. */
  33. function getStateReference(app) {
  34. return APP_CHECK_STATES.get(app) || Object.assign({}, DEFAULT_STATE);
  35. }
  36. /**
  37. * Set once on initialization. The map should hold the same reference to the
  38. * same object until this entry is deleted.
  39. */
  40. function setInitialState(app, state) {
  41. APP_CHECK_STATES.set(app, state);
  42. return APP_CHECK_STATES.get(app);
  43. }
  44. function getDebugState() {
  45. return DEBUG_STATE;
  46. }
  47. /**
  48. * @license
  49. * Copyright 2020 Google LLC
  50. *
  51. * Licensed under the Apache License, Version 2.0 (the "License");
  52. * you may not use this file except in compliance with the License.
  53. * You may obtain a copy of the License at
  54. *
  55. * http://www.apache.org/licenses/LICENSE-2.0
  56. *
  57. * Unless required by applicable law or agreed to in writing, software
  58. * distributed under the License is distributed on an "AS IS" BASIS,
  59. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  60. * See the License for the specific language governing permissions and
  61. * limitations under the License.
  62. */
  63. const BASE_ENDPOINT = 'https://content-firebaseappcheck.googleapis.com/v1';
  64. const EXCHANGE_RECAPTCHA_TOKEN_METHOD = 'exchangeRecaptchaV3Token';
  65. const EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD = 'exchangeRecaptchaEnterpriseToken';
  66. const EXCHANGE_DEBUG_TOKEN_METHOD = 'exchangeDebugToken';
  67. const TOKEN_REFRESH_TIME = {
  68. /**
  69. * The offset time before token natural expiration to run the refresh.
  70. * This is currently 5 minutes.
  71. */
  72. OFFSET_DURATION: 5 * 60 * 1000,
  73. /**
  74. * This is the first retrial wait after an error. This is currently
  75. * 30 seconds.
  76. */
  77. RETRIAL_MIN_WAIT: 30 * 1000,
  78. /**
  79. * This is the maximum retrial wait, currently 16 minutes.
  80. */
  81. RETRIAL_MAX_WAIT: 16 * 60 * 1000
  82. };
  83. /**
  84. * One day in millis, for certain error code backoffs.
  85. */
  86. const ONE_DAY = 24 * 60 * 60 * 1000;
  87. /**
  88. * @license
  89. * Copyright 2020 Google LLC
  90. *
  91. * Licensed under the Apache License, Version 2.0 (the "License");
  92. * you may not use this file except in compliance with the License.
  93. * You may obtain a copy of the License at
  94. *
  95. * http://www.apache.org/licenses/LICENSE-2.0
  96. *
  97. * Unless required by applicable law or agreed to in writing, software
  98. * distributed under the License is distributed on an "AS IS" BASIS,
  99. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  100. * See the License for the specific language governing permissions and
  101. * limitations under the License.
  102. */
  103. /**
  104. * Port from auth proactiverefresh.js
  105. *
  106. */
  107. // TODO: move it to @firebase/util?
  108. // TODO: allow to config whether refresh should happen in the background
  109. class Refresher {
  110. constructor(operation, retryPolicy, getWaitDuration, lowerBound, upperBound) {
  111. this.operation = operation;
  112. this.retryPolicy = retryPolicy;
  113. this.getWaitDuration = getWaitDuration;
  114. this.lowerBound = lowerBound;
  115. this.upperBound = upperBound;
  116. this.pending = null;
  117. this.nextErrorWaitInterval = lowerBound;
  118. if (lowerBound > upperBound) {
  119. throw new Error('Proactive refresh lower bound greater than upper bound!');
  120. }
  121. }
  122. start() {
  123. this.nextErrorWaitInterval = this.lowerBound;
  124. this.process(true).catch(() => {
  125. /* we don't care about the result */
  126. });
  127. }
  128. stop() {
  129. if (this.pending) {
  130. this.pending.reject('cancelled');
  131. this.pending = null;
  132. }
  133. }
  134. isRunning() {
  135. return !!this.pending;
  136. }
  137. async process(hasSucceeded) {
  138. this.stop();
  139. try {
  140. this.pending = new Deferred();
  141. await sleep(this.getNextRun(hasSucceeded));
  142. // Why do we resolve a promise, then immediate wait for it?
  143. // We do it to make the promise chain cancellable.
  144. // We can call stop() which rejects the promise before the following line execute, which makes
  145. // the code jump to the catch block.
  146. // TODO: unit test this
  147. this.pending.resolve();
  148. await this.pending.promise;
  149. this.pending = new Deferred();
  150. await this.operation();
  151. this.pending.resolve();
  152. await this.pending.promise;
  153. this.process(true).catch(() => {
  154. /* we don't care about the result */
  155. });
  156. }
  157. catch (error) {
  158. if (this.retryPolicy(error)) {
  159. this.process(false).catch(() => {
  160. /* we don't care about the result */
  161. });
  162. }
  163. else {
  164. this.stop();
  165. }
  166. }
  167. }
  168. getNextRun(hasSucceeded) {
  169. if (hasSucceeded) {
  170. // If last operation succeeded, reset next error wait interval and return
  171. // the default wait duration.
  172. this.nextErrorWaitInterval = this.lowerBound;
  173. // Return typical wait duration interval after a successful operation.
  174. return this.getWaitDuration();
  175. }
  176. else {
  177. // Get next error wait interval.
  178. const currentErrorWaitInterval = this.nextErrorWaitInterval;
  179. // Double interval for next consecutive error.
  180. this.nextErrorWaitInterval *= 2;
  181. // Make sure next wait interval does not exceed the maximum upper bound.
  182. if (this.nextErrorWaitInterval > this.upperBound) {
  183. this.nextErrorWaitInterval = this.upperBound;
  184. }
  185. return currentErrorWaitInterval;
  186. }
  187. }
  188. }
  189. function sleep(ms) {
  190. return new Promise(resolve => {
  191. setTimeout(resolve, ms);
  192. });
  193. }
  194. /**
  195. * @license
  196. * Copyright 2020 Google LLC
  197. *
  198. * Licensed under the Apache License, Version 2.0 (the "License");
  199. * you may not use this file except in compliance with the License.
  200. * You may obtain a copy of the License at
  201. *
  202. * http://www.apache.org/licenses/LICENSE-2.0
  203. *
  204. * Unless required by applicable law or agreed to in writing, software
  205. * distributed under the License is distributed on an "AS IS" BASIS,
  206. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  207. * See the License for the specific language governing permissions and
  208. * limitations under the License.
  209. */
  210. const ERRORS = {
  211. ["already-initialized" /* AppCheckError.ALREADY_INITIALIZED */]: 'You have already called initializeAppCheck() for FirebaseApp {$appName} with ' +
  212. 'different options. To avoid this error, call initializeAppCheck() with the ' +
  213. 'same options as when it was originally called. This will return the ' +
  214. 'already initialized instance.',
  215. ["use-before-activation" /* AppCheckError.USE_BEFORE_ACTIVATION */]: 'App Check is being used before initializeAppCheck() is called for FirebaseApp {$appName}. ' +
  216. 'Call initializeAppCheck() before instantiating other Firebase services.',
  217. ["fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */]: 'Fetch failed to connect to a network. Check Internet connection. ' +
  218. 'Original error: {$originalErrorMessage}.',
  219. ["fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */]: 'Fetch client could not parse response.' +
  220. ' Original error: {$originalErrorMessage}.',
  221. ["fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */]: 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
  222. ["storage-open" /* AppCheckError.STORAGE_OPEN */]: 'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
  223. ["storage-get" /* AppCheckError.STORAGE_GET */]: 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
  224. ["storage-set" /* AppCheckError.STORAGE_WRITE */]: 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
  225. ["recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */]: 'ReCAPTCHA error.',
  226. ["throttled" /* AppCheckError.THROTTLED */]: `Requests throttled due to {$httpStatus} error. Attempts allowed again after {$time}`
  227. };
  228. const ERROR_FACTORY = new ErrorFactory('appCheck', 'AppCheck', ERRORS);
  229. /**
  230. * @license
  231. * Copyright 2020 Google LLC
  232. *
  233. * Licensed under the Apache License, Version 2.0 (the "License");
  234. * you may not use this file except in compliance with the License.
  235. * You may obtain a copy of the License at
  236. *
  237. * http://www.apache.org/licenses/LICENSE-2.0
  238. *
  239. * Unless required by applicable law or agreed to in writing, software
  240. * distributed under the License is distributed on an "AS IS" BASIS,
  241. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  242. * See the License for the specific language governing permissions and
  243. * limitations under the License.
  244. */
  245. function getRecaptcha(isEnterprise = false) {
  246. var _a;
  247. if (isEnterprise) {
  248. return (_a = self.grecaptcha) === null || _a === void 0 ? void 0 : _a.enterprise;
  249. }
  250. return self.grecaptcha;
  251. }
  252. function ensureActivated(app) {
  253. if (!getStateReference(app).activated) {
  254. throw ERROR_FACTORY.create("use-before-activation" /* AppCheckError.USE_BEFORE_ACTIVATION */, {
  255. appName: app.name
  256. });
  257. }
  258. }
  259. function getDurationString(durationInMillis) {
  260. const totalSeconds = Math.round(durationInMillis / 1000);
  261. const days = Math.floor(totalSeconds / (3600 * 24));
  262. const hours = Math.floor((totalSeconds - days * 3600 * 24) / 3600);
  263. const minutes = Math.floor((totalSeconds - days * 3600 * 24 - hours * 3600) / 60);
  264. const seconds = totalSeconds - days * 3600 * 24 - hours * 3600 - minutes * 60;
  265. let result = '';
  266. if (days) {
  267. result += pad(days) + 'd:';
  268. }
  269. if (hours) {
  270. result += pad(hours) + 'h:';
  271. }
  272. result += pad(minutes) + 'm:' + pad(seconds) + 's';
  273. return result;
  274. }
  275. function pad(value) {
  276. if (value === 0) {
  277. return '00';
  278. }
  279. return value >= 10 ? value.toString() : '0' + value;
  280. }
  281. /**
  282. * @license
  283. * Copyright 2020 Google LLC
  284. *
  285. * Licensed under the Apache License, Version 2.0 (the "License");
  286. * you may not use this file except in compliance with the License.
  287. * You may obtain a copy of the License at
  288. *
  289. * http://www.apache.org/licenses/LICENSE-2.0
  290. *
  291. * Unless required by applicable law or agreed to in writing, software
  292. * distributed under the License is distributed on an "AS IS" BASIS,
  293. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  294. * See the License for the specific language governing permissions and
  295. * limitations under the License.
  296. */
  297. async function exchangeToken({ url, body }, heartbeatServiceProvider) {
  298. const headers = {
  299. 'Content-Type': 'application/json'
  300. };
  301. // If heartbeat service exists, add heartbeat header string to the header.
  302. const heartbeatService = heartbeatServiceProvider.getImmediate({
  303. optional: true
  304. });
  305. if (heartbeatService) {
  306. const heartbeatsHeader = await heartbeatService.getHeartbeatsHeader();
  307. if (heartbeatsHeader) {
  308. headers['X-Firebase-Client'] = heartbeatsHeader;
  309. }
  310. }
  311. const options = {
  312. method: 'POST',
  313. body: JSON.stringify(body),
  314. headers
  315. };
  316. let response;
  317. try {
  318. response = await fetch(url, options);
  319. }
  320. catch (originalError) {
  321. throw ERROR_FACTORY.create("fetch-network-error" /* AppCheckError.FETCH_NETWORK_ERROR */, {
  322. originalErrorMessage: originalError === null || originalError === void 0 ? void 0 : originalError.message
  323. });
  324. }
  325. if (response.status !== 200) {
  326. throw ERROR_FACTORY.create("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */, {
  327. httpStatus: response.status
  328. });
  329. }
  330. let responseBody;
  331. try {
  332. // JSON parsing throws SyntaxError if the response body isn't a JSON string.
  333. responseBody = await response.json();
  334. }
  335. catch (originalError) {
  336. throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, {
  337. originalErrorMessage: originalError === null || originalError === void 0 ? void 0 : originalError.message
  338. });
  339. }
  340. // Protobuf duration format.
  341. // https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/Duration
  342. const match = responseBody.ttl.match(/^([\d.]+)(s)$/);
  343. if (!match || !match[2] || isNaN(Number(match[1]))) {
  344. throw ERROR_FACTORY.create("fetch-parse-error" /* AppCheckError.FETCH_PARSE_ERROR */, {
  345. originalErrorMessage: `ttl field (timeToLive) is not in standard Protobuf Duration ` +
  346. `format: ${responseBody.ttl}`
  347. });
  348. }
  349. const timeToLiveAsNumber = Number(match[1]) * 1000;
  350. const now = Date.now();
  351. return {
  352. token: responseBody.token,
  353. expireTimeMillis: now + timeToLiveAsNumber,
  354. issuedAtTimeMillis: now
  355. };
  356. }
  357. function getExchangeRecaptchaV3TokenRequest(app, reCAPTCHAToken) {
  358. const { projectId, appId, apiKey } = app.options;
  359. return {
  360. url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_TOKEN_METHOD}?key=${apiKey}`,
  361. body: {
  362. 'recaptcha_v3_token': reCAPTCHAToken
  363. }
  364. };
  365. }
  366. function getExchangeRecaptchaEnterpriseTokenRequest(app, reCAPTCHAToken) {
  367. const { projectId, appId, apiKey } = app.options;
  368. return {
  369. url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_RECAPTCHA_ENTERPRISE_TOKEN_METHOD}?key=${apiKey}`,
  370. body: {
  371. 'recaptcha_enterprise_token': reCAPTCHAToken
  372. }
  373. };
  374. }
  375. function getExchangeDebugTokenRequest(app, debugToken) {
  376. const { projectId, appId, apiKey } = app.options;
  377. return {
  378. url: `${BASE_ENDPOINT}/projects/${projectId}/apps/${appId}:${EXCHANGE_DEBUG_TOKEN_METHOD}?key=${apiKey}`,
  379. body: {
  380. // eslint-disable-next-line
  381. debug_token: debugToken
  382. }
  383. };
  384. }
  385. /**
  386. * @license
  387. * Copyright 2020 Google LLC
  388. *
  389. * Licensed under the Apache License, Version 2.0 (the "License");
  390. * you may not use this file except in compliance with the License.
  391. * You may obtain a copy of the License at
  392. *
  393. * http://www.apache.org/licenses/LICENSE-2.0
  394. *
  395. * Unless required by applicable law or agreed to in writing, software
  396. * distributed under the License is distributed on an "AS IS" BASIS,
  397. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  398. * See the License for the specific language governing permissions and
  399. * limitations under the License.
  400. */
  401. const DB_NAME = 'firebase-app-check-database';
  402. const DB_VERSION = 1;
  403. const STORE_NAME = 'firebase-app-check-store';
  404. const DEBUG_TOKEN_KEY = 'debug-token';
  405. let dbPromise = null;
  406. function getDBPromise() {
  407. if (dbPromise) {
  408. return dbPromise;
  409. }
  410. dbPromise = new Promise((resolve, reject) => {
  411. try {
  412. const request = indexedDB.open(DB_NAME, DB_VERSION);
  413. request.onsuccess = event => {
  414. resolve(event.target.result);
  415. };
  416. request.onerror = event => {
  417. var _a;
  418. reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, {
  419. originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message
  420. }));
  421. };
  422. request.onupgradeneeded = event => {
  423. const db = event.target.result;
  424. // We don't use 'break' in this switch statement, the fall-through
  425. // behavior is what we want, because if there are multiple versions between
  426. // the old version and the current version, we want ALL the migrations
  427. // that correspond to those versions to run, not only the last one.
  428. // eslint-disable-next-line default-case
  429. switch (event.oldVersion) {
  430. case 0:
  431. db.createObjectStore(STORE_NAME, {
  432. keyPath: 'compositeKey'
  433. });
  434. }
  435. };
  436. }
  437. catch (e) {
  438. reject(ERROR_FACTORY.create("storage-open" /* AppCheckError.STORAGE_OPEN */, {
  439. originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
  440. }));
  441. }
  442. });
  443. return dbPromise;
  444. }
  445. function readTokenFromIndexedDB(app) {
  446. return read(computeKey(app));
  447. }
  448. function writeTokenToIndexedDB(app, token) {
  449. return write(computeKey(app), token);
  450. }
  451. function writeDebugTokenToIndexedDB(token) {
  452. return write(DEBUG_TOKEN_KEY, token);
  453. }
  454. function readDebugTokenFromIndexedDB() {
  455. return read(DEBUG_TOKEN_KEY);
  456. }
  457. async function write(key, value) {
  458. const db = await getDBPromise();
  459. const transaction = db.transaction(STORE_NAME, 'readwrite');
  460. const store = transaction.objectStore(STORE_NAME);
  461. const request = store.put({
  462. compositeKey: key,
  463. value
  464. });
  465. return new Promise((resolve, reject) => {
  466. request.onsuccess = _event => {
  467. resolve();
  468. };
  469. transaction.onerror = event => {
  470. var _a;
  471. reject(ERROR_FACTORY.create("storage-set" /* AppCheckError.STORAGE_WRITE */, {
  472. originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message
  473. }));
  474. };
  475. });
  476. }
  477. async function read(key) {
  478. const db = await getDBPromise();
  479. const transaction = db.transaction(STORE_NAME, 'readonly');
  480. const store = transaction.objectStore(STORE_NAME);
  481. const request = store.get(key);
  482. return new Promise((resolve, reject) => {
  483. request.onsuccess = event => {
  484. const result = event.target.result;
  485. if (result) {
  486. resolve(result.value);
  487. }
  488. else {
  489. resolve(undefined);
  490. }
  491. };
  492. transaction.onerror = event => {
  493. var _a;
  494. reject(ERROR_FACTORY.create("storage-get" /* AppCheckError.STORAGE_GET */, {
  495. originalErrorMessage: (_a = event.target.error) === null || _a === void 0 ? void 0 : _a.message
  496. }));
  497. };
  498. });
  499. }
  500. function computeKey(app) {
  501. return `${app.options.appId}-${app.name}`;
  502. }
  503. /**
  504. * @license
  505. * Copyright 2020 Google LLC
  506. *
  507. * Licensed under the Apache License, Version 2.0 (the "License");
  508. * you may not use this file except in compliance with the License.
  509. * You may obtain a copy of the License at
  510. *
  511. * http://www.apache.org/licenses/LICENSE-2.0
  512. *
  513. * Unless required by applicable law or agreed to in writing, software
  514. * distributed under the License is distributed on an "AS IS" BASIS,
  515. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  516. * See the License for the specific language governing permissions and
  517. * limitations under the License.
  518. */
  519. const logger = new Logger('@firebase/app-check');
  520. /**
  521. * @license
  522. * Copyright 2020 Google LLC
  523. *
  524. * Licensed under the Apache License, Version 2.0 (the "License");
  525. * you may not use this file except in compliance with the License.
  526. * You may obtain a copy of the License at
  527. *
  528. * http://www.apache.org/licenses/LICENSE-2.0
  529. *
  530. * Unless required by applicable law or agreed to in writing, software
  531. * distributed under the License is distributed on an "AS IS" BASIS,
  532. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  533. * See the License for the specific language governing permissions and
  534. * limitations under the License.
  535. */
  536. /**
  537. * Always resolves. In case of an error reading from indexeddb, resolve with undefined
  538. */
  539. async function readTokenFromStorage(app) {
  540. if (isIndexedDBAvailable()) {
  541. let token = undefined;
  542. try {
  543. token = await readTokenFromIndexedDB(app);
  544. }
  545. catch (e) {
  546. // swallow the error and return undefined
  547. logger.warn(`Failed to read token from IndexedDB. Error: ${e}`);
  548. }
  549. return token;
  550. }
  551. return undefined;
  552. }
  553. /**
  554. * Always resolves. In case of an error writing to indexeddb, print a warning and resolve the promise
  555. */
  556. function writeTokenToStorage(app, token) {
  557. if (isIndexedDBAvailable()) {
  558. return writeTokenToIndexedDB(app, token).catch(e => {
  559. // swallow the error and resolve the promise
  560. logger.warn(`Failed to write token to IndexedDB. Error: ${e}`);
  561. });
  562. }
  563. return Promise.resolve();
  564. }
  565. async function readOrCreateDebugTokenFromStorage() {
  566. /**
  567. * Theoretically race condition can happen if we read, then write in 2 separate transactions.
  568. * But it won't happen here, because this function will be called exactly once.
  569. */
  570. let existingDebugToken = undefined;
  571. try {
  572. existingDebugToken = await readDebugTokenFromIndexedDB();
  573. }
  574. catch (_e) {
  575. // failed to read from indexeddb. We assume there is no existing debug token, and generate a new one.
  576. }
  577. if (!existingDebugToken) {
  578. // create a new debug token
  579. const newToken = uuidv4();
  580. // We don't need to block on writing to indexeddb
  581. // In case persistence failed, a new debug token will be generated everytime the page is refreshed.
  582. // It renders the debug token useless because you have to manually register(whitelist) the new token in the firebase console again and again.
  583. // If you see this error trying to use debug token, it probably means you are using a browser that doesn't support indexeddb.
  584. // You should switch to a different browser that supports indexeddb
  585. writeDebugTokenToIndexedDB(newToken).catch(e => logger.warn(`Failed to persist debug token to IndexedDB. Error: ${e}`));
  586. return newToken;
  587. }
  588. else {
  589. return existingDebugToken;
  590. }
  591. }
  592. /**
  593. * @license
  594. * Copyright 2020 Google LLC
  595. *
  596. * Licensed under the Apache License, Version 2.0 (the "License");
  597. * you may not use this file except in compliance with the License.
  598. * You may obtain a copy of the License at
  599. *
  600. * http://www.apache.org/licenses/LICENSE-2.0
  601. *
  602. * Unless required by applicable law or agreed to in writing, software
  603. * distributed under the License is distributed on an "AS IS" BASIS,
  604. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  605. * See the License for the specific language governing permissions and
  606. * limitations under the License.
  607. */
  608. function isDebugMode() {
  609. const debugState = getDebugState();
  610. return debugState.enabled;
  611. }
  612. async function getDebugToken() {
  613. const state = getDebugState();
  614. if (state.enabled && state.token) {
  615. return state.token.promise;
  616. }
  617. else {
  618. // should not happen!
  619. throw Error(`
  620. Can't get debug token in production mode.
  621. `);
  622. }
  623. }
  624. function initializeDebugMode() {
  625. const globals = getGlobal();
  626. const debugState = getDebugState();
  627. // Set to true if this function has been called, whether or not
  628. // it enabled debug mode.
  629. debugState.initialized = true;
  630. if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== 'string' &&
  631. globals.FIREBASE_APPCHECK_DEBUG_TOKEN !== true) {
  632. return;
  633. }
  634. debugState.enabled = true;
  635. const deferredToken = new Deferred();
  636. debugState.token = deferredToken;
  637. if (typeof globals.FIREBASE_APPCHECK_DEBUG_TOKEN === 'string') {
  638. deferredToken.resolve(globals.FIREBASE_APPCHECK_DEBUG_TOKEN);
  639. }
  640. else {
  641. deferredToken.resolve(readOrCreateDebugTokenFromStorage());
  642. }
  643. }
  644. /**
  645. * @license
  646. * Copyright 2020 Google LLC
  647. *
  648. * Licensed under the Apache License, Version 2.0 (the "License");
  649. * you may not use this file except in compliance with the License.
  650. * You may obtain a copy of the License at
  651. *
  652. * http://www.apache.org/licenses/LICENSE-2.0
  653. *
  654. * Unless required by applicable law or agreed to in writing, software
  655. * distributed under the License is distributed on an "AS IS" BASIS,
  656. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  657. * See the License for the specific language governing permissions and
  658. * limitations under the License.
  659. */
  660. // Initial hardcoded value agreed upon across platforms for initial launch.
  661. // Format left open for possible dynamic error values and other fields in the future.
  662. const defaultTokenErrorData = { error: 'UNKNOWN_ERROR' };
  663. /**
  664. * Stringify and base64 encode token error data.
  665. *
  666. * @param tokenError Error data, currently hardcoded.
  667. */
  668. function formatDummyToken(tokenErrorData) {
  669. return base64.encodeString(JSON.stringify(tokenErrorData),
  670. /* webSafe= */ false);
  671. }
  672. /**
  673. * This function always resolves.
  674. * The result will contain an error field if there is any error.
  675. * In case there is an error, the token field in the result will be populated with a dummy value
  676. */
  677. async function getToken$2(appCheck, forceRefresh = false) {
  678. const app = appCheck.app;
  679. ensureActivated(app);
  680. const state = getStateReference(app);
  681. /**
  682. * First check if there is a token in memory from a previous `getToken()` call.
  683. */
  684. let token = state.token;
  685. let error = undefined;
  686. /**
  687. * If an invalid token was found in memory, clear token from
  688. * memory and unset the local variable `token`.
  689. */
  690. if (token && !isValid(token)) {
  691. state.token = undefined;
  692. token = undefined;
  693. }
  694. /**
  695. * If there is no valid token in memory, try to load token from indexedDB.
  696. */
  697. if (!token) {
  698. // cachedTokenPromise contains the token found in IndexedDB or undefined if not found.
  699. const cachedToken = await state.cachedTokenPromise;
  700. if (cachedToken) {
  701. if (isValid(cachedToken)) {
  702. token = cachedToken;
  703. }
  704. else {
  705. // If there was an invalid token in the indexedDB cache, clear it.
  706. await writeTokenToStorage(app, undefined);
  707. }
  708. }
  709. }
  710. // Return the cached token (from either memory or indexedDB) if it's valid
  711. if (!forceRefresh && token && isValid(token)) {
  712. return {
  713. token: token.token
  714. };
  715. }
  716. // Only set to true if this `getToken()` call is making the actual
  717. // REST call to the exchange endpoint, versus waiting for an already
  718. // in-flight call (see debug and regular exchange endpoint paths below)
  719. let shouldCallListeners = false;
  720. /**
  721. * DEBUG MODE
  722. * If debug mode is set, and there is no cached token, fetch a new App
  723. * Check token using the debug token, and return it directly.
  724. */
  725. if (isDebugMode()) {
  726. // Avoid making another call to the exchange endpoint if one is in flight.
  727. if (!state.exchangeTokenPromise) {
  728. state.exchangeTokenPromise = exchangeToken(getExchangeDebugTokenRequest(app, await getDebugToken()), appCheck.heartbeatServiceProvider).finally(() => {
  729. // Clear promise when settled - either resolved or rejected.
  730. state.exchangeTokenPromise = undefined;
  731. });
  732. shouldCallListeners = true;
  733. }
  734. const tokenFromDebugExchange = await state.exchangeTokenPromise;
  735. // Write debug token to indexedDB.
  736. await writeTokenToStorage(app, tokenFromDebugExchange);
  737. // Write debug token to state.
  738. state.token = tokenFromDebugExchange;
  739. return { token: tokenFromDebugExchange.token };
  740. }
  741. /**
  742. * There are no valid tokens in memory or indexedDB and we are not in
  743. * debug mode.
  744. * Request a new token from the exchange endpoint.
  745. */
  746. try {
  747. // Avoid making another call to the exchange endpoint if one is in flight.
  748. if (!state.exchangeTokenPromise) {
  749. // state.provider is populated in initializeAppCheck()
  750. // ensureActivated() at the top of this function checks that
  751. // initializeAppCheck() has been called.
  752. state.exchangeTokenPromise = state.provider.getToken().finally(() => {
  753. // Clear promise when settled - either resolved or rejected.
  754. state.exchangeTokenPromise = undefined;
  755. });
  756. shouldCallListeners = true;
  757. }
  758. token = await getStateReference(app).exchangeTokenPromise;
  759. }
  760. catch (e) {
  761. if (e.code === `appCheck/${"throttled" /* AppCheckError.THROTTLED */}`) {
  762. // Warn if throttled, but do not treat it as an error.
  763. logger.warn(e.message);
  764. }
  765. else {
  766. // `getToken()` should never throw, but logging error text to console will aid debugging.
  767. logger.error(e);
  768. }
  769. // Always save error to be added to dummy token.
  770. error = e;
  771. }
  772. let interopTokenResult;
  773. if (!token) {
  774. // If token is undefined, there must be an error.
  775. // Return a dummy token along with the error.
  776. interopTokenResult = makeDummyTokenResult(error);
  777. }
  778. else if (error) {
  779. if (isValid(token)) {
  780. // It's also possible a valid token exists, but there's also an error.
  781. // (Such as if the token is almost expired, tries to refresh, and
  782. // the exchange request fails.)
  783. // We add a special error property here so that the refresher will
  784. // count this as a failed attempt and use the backoff instead of
  785. // retrying repeatedly with no delay, but any 3P listeners will not
  786. // be hindered in getting the still-valid token.
  787. interopTokenResult = {
  788. token: token.token,
  789. internalError: error
  790. };
  791. }
  792. else {
  793. // No invalid tokens should make it to this step. Memory and cached tokens
  794. // are checked. Other tokens are from fresh exchanges. But just in case.
  795. interopTokenResult = makeDummyTokenResult(error);
  796. }
  797. }
  798. else {
  799. interopTokenResult = {
  800. token: token.token
  801. };
  802. // write the new token to the memory state as well as the persistent storage.
  803. // Only do it if we got a valid new token
  804. state.token = token;
  805. await writeTokenToStorage(app, token);
  806. }
  807. if (shouldCallListeners) {
  808. notifyTokenListeners(app, interopTokenResult);
  809. }
  810. return interopTokenResult;
  811. }
  812. /**
  813. * Internal API for limited use tokens. Skips all FAC state and simply calls
  814. * the underlying provider.
  815. */
  816. async function getLimitedUseToken$1(appCheck) {
  817. const app = appCheck.app;
  818. ensureActivated(app);
  819. const { provider } = getStateReference(app);
  820. if (isDebugMode()) {
  821. const debugToken = await getDebugToken();
  822. const { token } = await exchangeToken(getExchangeDebugTokenRequest(app, debugToken), appCheck.heartbeatServiceProvider);
  823. return { token };
  824. }
  825. else {
  826. // provider is definitely valid since we ensure AppCheck was activated
  827. const { token } = await provider.getToken();
  828. return { token };
  829. }
  830. }
  831. function addTokenListener(appCheck, type, listener, onError) {
  832. const { app } = appCheck;
  833. const state = getStateReference(app);
  834. const tokenObserver = {
  835. next: listener,
  836. error: onError,
  837. type
  838. };
  839. state.tokenObservers = [...state.tokenObservers, tokenObserver];
  840. // Invoke the listener async immediately if there is a valid token
  841. // in memory.
  842. if (state.token && isValid(state.token)) {
  843. const validToken = state.token;
  844. Promise.resolve()
  845. .then(() => {
  846. listener({ token: validToken.token });
  847. initTokenRefresher(appCheck);
  848. })
  849. .catch(() => {
  850. /* we don't care about exceptions thrown in listeners */
  851. });
  852. }
  853. /**
  854. * Wait for any cached token promise to resolve before starting the token
  855. * refresher. The refresher checks to see if there is an existing token
  856. * in state and calls the exchange endpoint if not. We should first let the
  857. * IndexedDB check have a chance to populate state if it can.
  858. *
  859. * Listener call isn't needed here because cachedTokenPromise will call any
  860. * listeners that exist when it resolves.
  861. */
  862. // state.cachedTokenPromise is always populated in `activate()`.
  863. void state.cachedTokenPromise.then(() => initTokenRefresher(appCheck));
  864. }
  865. function removeTokenListener(app, listener) {
  866. const state = getStateReference(app);
  867. const newObservers = state.tokenObservers.filter(tokenObserver => tokenObserver.next !== listener);
  868. if (newObservers.length === 0 &&
  869. state.tokenRefresher &&
  870. state.tokenRefresher.isRunning()) {
  871. state.tokenRefresher.stop();
  872. }
  873. state.tokenObservers = newObservers;
  874. }
  875. /**
  876. * Logic to create and start refresher as needed.
  877. */
  878. function initTokenRefresher(appCheck) {
  879. const { app } = appCheck;
  880. const state = getStateReference(app);
  881. // Create the refresher but don't start it if `isTokenAutoRefreshEnabled`
  882. // is not true.
  883. let refresher = state.tokenRefresher;
  884. if (!refresher) {
  885. refresher = createTokenRefresher(appCheck);
  886. state.tokenRefresher = refresher;
  887. }
  888. if (!refresher.isRunning() && state.isTokenAutoRefreshEnabled) {
  889. refresher.start();
  890. }
  891. }
  892. function createTokenRefresher(appCheck) {
  893. const { app } = appCheck;
  894. return new Refresher(
  895. // Keep in mind when this fails for any reason other than the ones
  896. // for which we should retry, it will effectively stop the proactive refresh.
  897. async () => {
  898. const state = getStateReference(app);
  899. // If there is no token, we will try to load it from storage and use it
  900. // If there is a token, we force refresh it because we know it's going to expire soon
  901. let result;
  902. if (!state.token) {
  903. result = await getToken$2(appCheck);
  904. }
  905. else {
  906. result = await getToken$2(appCheck, true);
  907. }
  908. /**
  909. * getToken() always resolves. In case the result has an error field defined, it means
  910. * the operation failed, and we should retry.
  911. */
  912. if (result.error) {
  913. throw result.error;
  914. }
  915. /**
  916. * A special `internalError` field reflects that there was an error
  917. * getting a new token from the exchange endpoint, but there's still a
  918. * previous token that's valid for now and this should be passed to 2P/3P
  919. * requests for a token. But we want this callback (`this.operation` in
  920. * `Refresher`) to throw in order to kick off the Refresher's retry
  921. * backoff. (Setting `hasSucceeded` to false.)
  922. */
  923. if (result.internalError) {
  924. throw result.internalError;
  925. }
  926. }, () => {
  927. return true;
  928. }, () => {
  929. const state = getStateReference(app);
  930. if (state.token) {
  931. // issuedAtTime + (50% * total TTL) + 5 minutes
  932. let nextRefreshTimeMillis = state.token.issuedAtTimeMillis +
  933. (state.token.expireTimeMillis - state.token.issuedAtTimeMillis) *
  934. 0.5 +
  935. 5 * 60 * 1000;
  936. // Do not allow refresh time to be past (expireTime - 5 minutes)
  937. const latestAllowableRefresh = state.token.expireTimeMillis - 5 * 60 * 1000;
  938. nextRefreshTimeMillis = Math.min(nextRefreshTimeMillis, latestAllowableRefresh);
  939. return Math.max(0, nextRefreshTimeMillis - Date.now());
  940. }
  941. else {
  942. return 0;
  943. }
  944. }, TOKEN_REFRESH_TIME.RETRIAL_MIN_WAIT, TOKEN_REFRESH_TIME.RETRIAL_MAX_WAIT);
  945. }
  946. function notifyTokenListeners(app, token) {
  947. const observers = getStateReference(app).tokenObservers;
  948. for (const observer of observers) {
  949. try {
  950. if (observer.type === "EXTERNAL" /* ListenerType.EXTERNAL */ && token.error != null) {
  951. // If this listener was added by a 3P call, send any token error to
  952. // the supplied error handler. A 3P observer always has an error
  953. // handler.
  954. observer.error(token.error);
  955. }
  956. else {
  957. // If the token has no error field, always return the token.
  958. // If this is a 2P listener, return the token, whether or not it
  959. // has an error field.
  960. observer.next(token);
  961. }
  962. }
  963. catch (e) {
  964. // Errors in the listener function itself are always ignored.
  965. }
  966. }
  967. }
  968. function isValid(token) {
  969. return token.expireTimeMillis - Date.now() > 0;
  970. }
  971. function makeDummyTokenResult(error) {
  972. return {
  973. token: formatDummyToken(defaultTokenErrorData),
  974. error
  975. };
  976. }
  977. /**
  978. * @license
  979. * Copyright 2020 Google LLC
  980. *
  981. * Licensed under the Apache License, Version 2.0 (the "License");
  982. * you may not use this file except in compliance with the License.
  983. * You may obtain a copy of the License at
  984. *
  985. * http://www.apache.org/licenses/LICENSE-2.0
  986. *
  987. * Unless required by applicable law or agreed to in writing, software
  988. * distributed under the License is distributed on an "AS IS" BASIS,
  989. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  990. * See the License for the specific language governing permissions and
  991. * limitations under the License.
  992. */
  993. /**
  994. * AppCheck Service class.
  995. */
  996. class AppCheckService {
  997. constructor(app, heartbeatServiceProvider) {
  998. this.app = app;
  999. this.heartbeatServiceProvider = heartbeatServiceProvider;
  1000. }
  1001. _delete() {
  1002. const { tokenObservers } = getStateReference(this.app);
  1003. for (const tokenObserver of tokenObservers) {
  1004. removeTokenListener(this.app, tokenObserver.next);
  1005. }
  1006. return Promise.resolve();
  1007. }
  1008. }
  1009. function factory(app, heartbeatServiceProvider) {
  1010. return new AppCheckService(app, heartbeatServiceProvider);
  1011. }
  1012. function internalFactory(appCheck) {
  1013. return {
  1014. getToken: forceRefresh => getToken$2(appCheck, forceRefresh),
  1015. getLimitedUseToken: () => getLimitedUseToken$1(appCheck),
  1016. addTokenListener: listener => addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, listener),
  1017. removeTokenListener: listener => removeTokenListener(appCheck.app, listener)
  1018. };
  1019. }
  1020. const name = "@firebase/app-check";
  1021. const version = "0.8.0";
  1022. /**
  1023. * @license
  1024. * Copyright 2020 Google LLC
  1025. *
  1026. * Licensed under the Apache License, Version 2.0 (the "License");
  1027. * you may not use this file except in compliance with the License.
  1028. * You may obtain a copy of the License at
  1029. *
  1030. * http://www.apache.org/licenses/LICENSE-2.0
  1031. *
  1032. * Unless required by applicable law or agreed to in writing, software
  1033. * distributed under the License is distributed on an "AS IS" BASIS,
  1034. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1035. * See the License for the specific language governing permissions and
  1036. * limitations under the License.
  1037. */
  1038. const RECAPTCHA_URL = 'https://www.google.com/recaptcha/api.js';
  1039. const RECAPTCHA_ENTERPRISE_URL = 'https://www.google.com/recaptcha/enterprise.js';
  1040. function initializeV3(app, siteKey) {
  1041. const initialized = new Deferred();
  1042. const state = getStateReference(app);
  1043. state.reCAPTCHAState = { initialized };
  1044. const divId = makeDiv(app);
  1045. const grecaptcha = getRecaptcha(false);
  1046. if (!grecaptcha) {
  1047. loadReCAPTCHAV3Script(() => {
  1048. const grecaptcha = getRecaptcha(false);
  1049. if (!grecaptcha) {
  1050. // it shouldn't happen.
  1051. throw new Error('no recaptcha');
  1052. }
  1053. queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
  1054. });
  1055. }
  1056. else {
  1057. queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
  1058. }
  1059. return initialized.promise;
  1060. }
  1061. function initializeEnterprise(app, siteKey) {
  1062. const initialized = new Deferred();
  1063. const state = getStateReference(app);
  1064. state.reCAPTCHAState = { initialized };
  1065. const divId = makeDiv(app);
  1066. const grecaptcha = getRecaptcha(true);
  1067. if (!grecaptcha) {
  1068. loadReCAPTCHAEnterpriseScript(() => {
  1069. const grecaptcha = getRecaptcha(true);
  1070. if (!grecaptcha) {
  1071. // it shouldn't happen.
  1072. throw new Error('no recaptcha');
  1073. }
  1074. queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
  1075. });
  1076. }
  1077. else {
  1078. queueWidgetRender(app, siteKey, grecaptcha, divId, initialized);
  1079. }
  1080. return initialized.promise;
  1081. }
  1082. /**
  1083. * Add listener to render the widget and resolve the promise when
  1084. * the grecaptcha.ready() event fires.
  1085. */
  1086. function queueWidgetRender(app, siteKey, grecaptcha, container, initialized) {
  1087. grecaptcha.ready(() => {
  1088. // Invisible widgets allow us to set a different siteKey for each widget,
  1089. // so we use them to support multiple apps
  1090. renderInvisibleWidget(app, siteKey, grecaptcha, container);
  1091. initialized.resolve(grecaptcha);
  1092. });
  1093. }
  1094. /**
  1095. * Add invisible div to page.
  1096. */
  1097. function makeDiv(app) {
  1098. const divId = `fire_app_check_${app.name}`;
  1099. const invisibleDiv = document.createElement('div');
  1100. invisibleDiv.id = divId;
  1101. invisibleDiv.style.display = 'none';
  1102. document.body.appendChild(invisibleDiv);
  1103. return divId;
  1104. }
  1105. async function getToken$1(app) {
  1106. ensureActivated(app);
  1107. // ensureActivated() guarantees that reCAPTCHAState is set
  1108. const reCAPTCHAState = getStateReference(app).reCAPTCHAState;
  1109. const recaptcha = await reCAPTCHAState.initialized.promise;
  1110. return new Promise((resolve, _reject) => {
  1111. // Updated after initialization is complete.
  1112. const reCAPTCHAState = getStateReference(app).reCAPTCHAState;
  1113. recaptcha.ready(() => {
  1114. resolve(
  1115. // widgetId is guaranteed to be available if reCAPTCHAState.initialized.promise resolved.
  1116. recaptcha.execute(reCAPTCHAState.widgetId, {
  1117. action: 'fire_app_check'
  1118. }));
  1119. });
  1120. });
  1121. }
  1122. /**
  1123. *
  1124. * @param app
  1125. * @param container - Id of a HTML element.
  1126. */
  1127. function renderInvisibleWidget(app, siteKey, grecaptcha, container) {
  1128. const widgetId = grecaptcha.render(container, {
  1129. sitekey: siteKey,
  1130. size: 'invisible',
  1131. // Success callback - set state
  1132. callback: () => {
  1133. getStateReference(app).reCAPTCHAState.succeeded = true;
  1134. },
  1135. // Failure callback - set state
  1136. 'error-callback': () => {
  1137. getStateReference(app).reCAPTCHAState.succeeded = false;
  1138. }
  1139. });
  1140. const state = getStateReference(app);
  1141. state.reCAPTCHAState = Object.assign(Object.assign({}, state.reCAPTCHAState), { // state.reCAPTCHAState is set in the initialize()
  1142. widgetId });
  1143. }
  1144. function loadReCAPTCHAV3Script(onload) {
  1145. const script = document.createElement('script');
  1146. script.src = RECAPTCHA_URL;
  1147. script.onload = onload;
  1148. document.head.appendChild(script);
  1149. }
  1150. function loadReCAPTCHAEnterpriseScript(onload) {
  1151. const script = document.createElement('script');
  1152. script.src = RECAPTCHA_ENTERPRISE_URL;
  1153. script.onload = onload;
  1154. document.head.appendChild(script);
  1155. }
  1156. /**
  1157. * @license
  1158. * Copyright 2021 Google LLC
  1159. *
  1160. * Licensed under the Apache License, Version 2.0 (the "License");
  1161. * you may not use this file except in compliance with the License.
  1162. * You may obtain a copy of the License at
  1163. *
  1164. * http://www.apache.org/licenses/LICENSE-2.0
  1165. *
  1166. * Unless required by applicable law or agreed to in writing, software
  1167. * distributed under the License is distributed on an "AS IS" BASIS,
  1168. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1169. * See the License for the specific language governing permissions and
  1170. * limitations under the License.
  1171. */
  1172. /**
  1173. * App Check provider that can obtain a reCAPTCHA V3 token and exchange it
  1174. * for an App Check token.
  1175. *
  1176. * @public
  1177. */
  1178. class ReCaptchaV3Provider {
  1179. /**
  1180. * Create a ReCaptchaV3Provider instance.
  1181. * @param siteKey - ReCAPTCHA V3 siteKey.
  1182. */
  1183. constructor(_siteKey) {
  1184. this._siteKey = _siteKey;
  1185. /**
  1186. * Throttle requests on certain error codes to prevent too many retries
  1187. * in a short time.
  1188. */
  1189. this._throttleData = null;
  1190. }
  1191. /**
  1192. * Returns an App Check token.
  1193. * @internal
  1194. */
  1195. async getToken() {
  1196. var _a, _b, _c;
  1197. throwIfThrottled(this._throttleData);
  1198. // Top-level `getToken()` has already checked that App Check is initialized
  1199. // and therefore this._app and this._heartbeatServiceProvider are available.
  1200. const attestedClaimsToken = await getToken$1(this._app).catch(_e => {
  1201. // reCaptcha.execute() throws null which is not very descriptive.
  1202. throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
  1203. });
  1204. // Check if a failure state was set by the recaptcha "error-callback".
  1205. if (!((_a = getStateReference(this._app).reCAPTCHAState) === null || _a === void 0 ? void 0 : _a.succeeded)) {
  1206. throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
  1207. }
  1208. let result;
  1209. try {
  1210. result = await exchangeToken(getExchangeRecaptchaV3TokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider);
  1211. }
  1212. catch (e) {
  1213. if ((_b = e.code) === null || _b === void 0 ? void 0 : _b.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) {
  1214. this._throttleData = setBackoff(Number((_c = e.customData) === null || _c === void 0 ? void 0 : _c.httpStatus), this._throttleData);
  1215. throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, {
  1216. time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()),
  1217. httpStatus: this._throttleData.httpStatus
  1218. });
  1219. }
  1220. else {
  1221. throw e;
  1222. }
  1223. }
  1224. // If successful, clear throttle data.
  1225. this._throttleData = null;
  1226. return result;
  1227. }
  1228. /**
  1229. * @internal
  1230. */
  1231. initialize(app) {
  1232. this._app = app;
  1233. this._heartbeatServiceProvider = _getProvider(app, 'heartbeat');
  1234. initializeV3(app, this._siteKey).catch(() => {
  1235. /* we don't care about the initialization result */
  1236. });
  1237. }
  1238. /**
  1239. * @internal
  1240. */
  1241. isEqual(otherProvider) {
  1242. if (otherProvider instanceof ReCaptchaV3Provider) {
  1243. return this._siteKey === otherProvider._siteKey;
  1244. }
  1245. else {
  1246. return false;
  1247. }
  1248. }
  1249. }
  1250. /**
  1251. * App Check provider that can obtain a reCAPTCHA Enterprise token and exchange it
  1252. * for an App Check token.
  1253. *
  1254. * @public
  1255. */
  1256. class ReCaptchaEnterpriseProvider {
  1257. /**
  1258. * Create a ReCaptchaEnterpriseProvider instance.
  1259. * @param siteKey - reCAPTCHA Enterprise score-based site key.
  1260. */
  1261. constructor(_siteKey) {
  1262. this._siteKey = _siteKey;
  1263. /**
  1264. * Throttle requests on certain error codes to prevent too many retries
  1265. * in a short time.
  1266. */
  1267. this._throttleData = null;
  1268. }
  1269. /**
  1270. * Returns an App Check token.
  1271. * @internal
  1272. */
  1273. async getToken() {
  1274. var _a, _b, _c;
  1275. throwIfThrottled(this._throttleData);
  1276. // Top-level `getToken()` has already checked that App Check is initialized
  1277. // and therefore this._app and this._heartbeatServiceProvider are available.
  1278. const attestedClaimsToken = await getToken$1(this._app).catch(_e => {
  1279. // reCaptcha.execute() throws null which is not very descriptive.
  1280. throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
  1281. });
  1282. // Check if a failure state was set by the recaptcha "error-callback".
  1283. if (!((_a = getStateReference(this._app).reCAPTCHAState) === null || _a === void 0 ? void 0 : _a.succeeded)) {
  1284. throw ERROR_FACTORY.create("recaptcha-error" /* AppCheckError.RECAPTCHA_ERROR */);
  1285. }
  1286. let result;
  1287. try {
  1288. result = await exchangeToken(getExchangeRecaptchaEnterpriseTokenRequest(this._app, attestedClaimsToken), this._heartbeatServiceProvider);
  1289. }
  1290. catch (e) {
  1291. if ((_b = e.code) === null || _b === void 0 ? void 0 : _b.includes("fetch-status-error" /* AppCheckError.FETCH_STATUS_ERROR */)) {
  1292. this._throttleData = setBackoff(Number((_c = e.customData) === null || _c === void 0 ? void 0 : _c.httpStatus), this._throttleData);
  1293. throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, {
  1294. time: getDurationString(this._throttleData.allowRequestsAfter - Date.now()),
  1295. httpStatus: this._throttleData.httpStatus
  1296. });
  1297. }
  1298. else {
  1299. throw e;
  1300. }
  1301. }
  1302. // If successful, clear throttle data.
  1303. this._throttleData = null;
  1304. return result;
  1305. }
  1306. /**
  1307. * @internal
  1308. */
  1309. initialize(app) {
  1310. this._app = app;
  1311. this._heartbeatServiceProvider = _getProvider(app, 'heartbeat');
  1312. initializeEnterprise(app, this._siteKey).catch(() => {
  1313. /* we don't care about the initialization result */
  1314. });
  1315. }
  1316. /**
  1317. * @internal
  1318. */
  1319. isEqual(otherProvider) {
  1320. if (otherProvider instanceof ReCaptchaEnterpriseProvider) {
  1321. return this._siteKey === otherProvider._siteKey;
  1322. }
  1323. else {
  1324. return false;
  1325. }
  1326. }
  1327. }
  1328. /**
  1329. * Custom provider class.
  1330. * @public
  1331. */
  1332. class CustomProvider {
  1333. constructor(_customProviderOptions) {
  1334. this._customProviderOptions = _customProviderOptions;
  1335. }
  1336. /**
  1337. * @internal
  1338. */
  1339. async getToken() {
  1340. // custom provider
  1341. const customToken = await this._customProviderOptions.getToken();
  1342. // Try to extract IAT from custom token, in case this token is not
  1343. // being newly issued. JWT timestamps are in seconds since epoch.
  1344. const issuedAtTimeSeconds = issuedAtTime(customToken.token);
  1345. // Very basic validation, use current timestamp as IAT if JWT
  1346. // has no `iat` field or value is out of bounds.
  1347. const issuedAtTimeMillis = issuedAtTimeSeconds !== null &&
  1348. issuedAtTimeSeconds < Date.now() &&
  1349. issuedAtTimeSeconds > 0
  1350. ? issuedAtTimeSeconds * 1000
  1351. : Date.now();
  1352. return Object.assign(Object.assign({}, customToken), { issuedAtTimeMillis });
  1353. }
  1354. /**
  1355. * @internal
  1356. */
  1357. initialize(app) {
  1358. this._app = app;
  1359. }
  1360. /**
  1361. * @internal
  1362. */
  1363. isEqual(otherProvider) {
  1364. if (otherProvider instanceof CustomProvider) {
  1365. return (this._customProviderOptions.getToken.toString() ===
  1366. otherProvider._customProviderOptions.getToken.toString());
  1367. }
  1368. else {
  1369. return false;
  1370. }
  1371. }
  1372. }
  1373. /**
  1374. * Set throttle data to block requests until after a certain time
  1375. * depending on the failed request's status code.
  1376. * @param httpStatus - Status code of failed request.
  1377. * @param throttleData - `ThrottleData` object containing previous throttle
  1378. * data state.
  1379. * @returns Data about current throttle state and expiration time.
  1380. */
  1381. function setBackoff(httpStatus, throttleData) {
  1382. /**
  1383. * Block retries for 1 day for the following error codes:
  1384. *
  1385. * 404: Likely malformed URL.
  1386. *
  1387. * 403:
  1388. * - Attestation failed
  1389. * - Wrong API key
  1390. * - Project deleted
  1391. */
  1392. if (httpStatus === 404 || httpStatus === 403) {
  1393. return {
  1394. backoffCount: 1,
  1395. allowRequestsAfter: Date.now() + ONE_DAY,
  1396. httpStatus
  1397. };
  1398. }
  1399. else {
  1400. /**
  1401. * For all other error codes, the time when it is ok to retry again
  1402. * is based on exponential backoff.
  1403. */
  1404. const backoffCount = throttleData ? throttleData.backoffCount : 0;
  1405. const backoffMillis = calculateBackoffMillis(backoffCount, 1000, 2);
  1406. return {
  1407. backoffCount: backoffCount + 1,
  1408. allowRequestsAfter: Date.now() + backoffMillis,
  1409. httpStatus
  1410. };
  1411. }
  1412. }
  1413. function throwIfThrottled(throttleData) {
  1414. if (throttleData) {
  1415. if (Date.now() - throttleData.allowRequestsAfter <= 0) {
  1416. // If before, throw.
  1417. throw ERROR_FACTORY.create("throttled" /* AppCheckError.THROTTLED */, {
  1418. time: getDurationString(throttleData.allowRequestsAfter - Date.now()),
  1419. httpStatus: throttleData.httpStatus
  1420. });
  1421. }
  1422. }
  1423. }
  1424. /**
  1425. * @license
  1426. * Copyright 2020 Google LLC
  1427. *
  1428. * Licensed under the Apache License, Version 2.0 (the "License");
  1429. * you may not use this file except in compliance with the License.
  1430. * You may obtain a copy of the License at
  1431. *
  1432. * http://www.apache.org/licenses/LICENSE-2.0
  1433. *
  1434. * Unless required by applicable law or agreed to in writing, software
  1435. * distributed under the License is distributed on an "AS IS" BASIS,
  1436. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1437. * See the License for the specific language governing permissions and
  1438. * limitations under the License.
  1439. */
  1440. /**
  1441. * Activate App Check for the given app. Can be called only once per app.
  1442. * @param app - the {@link @firebase/app#FirebaseApp} to activate App Check for
  1443. * @param options - App Check initialization options
  1444. * @public
  1445. */
  1446. function initializeAppCheck(app = getApp(), options) {
  1447. app = getModularInstance(app);
  1448. const provider = _getProvider(app, 'app-check');
  1449. // Ensure initializeDebugMode() is only called once.
  1450. if (!getDebugState().initialized) {
  1451. initializeDebugMode();
  1452. }
  1453. // Log a message containing the debug token when `initializeAppCheck()`
  1454. // is called in debug mode.
  1455. if (isDebugMode()) {
  1456. // Do not block initialization to get the token for the message.
  1457. void getDebugToken().then(token =>
  1458. // Not using logger because I don't think we ever want this accidentally hidden.
  1459. console.log(`App Check debug token: ${token}. You will need to add it to your app's App Check settings in the Firebase console for it to work.`));
  1460. }
  1461. if (provider.isInitialized()) {
  1462. const existingInstance = provider.getImmediate();
  1463. const initialOptions = provider.getOptions();
  1464. if (initialOptions.isTokenAutoRefreshEnabled ===
  1465. options.isTokenAutoRefreshEnabled &&
  1466. initialOptions.provider.isEqual(options.provider)) {
  1467. return existingInstance;
  1468. }
  1469. else {
  1470. throw ERROR_FACTORY.create("already-initialized" /* AppCheckError.ALREADY_INITIALIZED */, {
  1471. appName: app.name
  1472. });
  1473. }
  1474. }
  1475. const appCheck = provider.initialize({ options });
  1476. _activate(app, options.provider, options.isTokenAutoRefreshEnabled);
  1477. // If isTokenAutoRefreshEnabled is false, do not send any requests to the
  1478. // exchange endpoint without an explicit call from the user either directly
  1479. // or through another Firebase library (storage, functions, etc.)
  1480. if (getStateReference(app).isTokenAutoRefreshEnabled) {
  1481. // Adding a listener will start the refresher and fetch a token if needed.
  1482. // This gets a token ready and prevents a delay when an internal library
  1483. // requests the token.
  1484. // Listener function does not need to do anything, its base functionality
  1485. // of calling getToken() already fetches token and writes it to memory/storage.
  1486. addTokenListener(appCheck, "INTERNAL" /* ListenerType.INTERNAL */, () => { });
  1487. }
  1488. return appCheck;
  1489. }
  1490. /**
  1491. * Activate App Check
  1492. * @param app - Firebase app to activate App Check for.
  1493. * @param provider - reCAPTCHA v3 provider or
  1494. * custom token provider.
  1495. * @param isTokenAutoRefreshEnabled - If true, the SDK automatically
  1496. * refreshes App Check tokens as needed. If undefined, defaults to the
  1497. * value of `app.automaticDataCollectionEnabled`, which defaults to
  1498. * false and can be set in the app config.
  1499. */
  1500. function _activate(app, provider, isTokenAutoRefreshEnabled) {
  1501. // Create an entry in the APP_CHECK_STATES map. Further changes should
  1502. // directly mutate this object.
  1503. const state = setInitialState(app, Object.assign({}, DEFAULT_STATE));
  1504. state.activated = true;
  1505. state.provider = provider; // Read cached token from storage if it exists and store it in memory.
  1506. state.cachedTokenPromise = readTokenFromStorage(app).then(cachedToken => {
  1507. if (cachedToken && isValid(cachedToken)) {
  1508. state.token = cachedToken;
  1509. // notify all listeners with the cached token
  1510. notifyTokenListeners(app, { token: cachedToken.token });
  1511. }
  1512. return cachedToken;
  1513. });
  1514. // Use value of global `automaticDataCollectionEnabled` (which
  1515. // itself defaults to false if not specified in config) if
  1516. // `isTokenAutoRefreshEnabled` param was not provided by user.
  1517. state.isTokenAutoRefreshEnabled =
  1518. isTokenAutoRefreshEnabled === undefined
  1519. ? app.automaticDataCollectionEnabled
  1520. : isTokenAutoRefreshEnabled;
  1521. state.provider.initialize(app);
  1522. }
  1523. /**
  1524. * Set whether App Check will automatically refresh tokens as needed.
  1525. *
  1526. * @param appCheckInstance - The App Check service instance.
  1527. * @param isTokenAutoRefreshEnabled - If true, the SDK automatically
  1528. * refreshes App Check tokens as needed. This overrides any value set
  1529. * during `initializeAppCheck()`.
  1530. * @public
  1531. */
  1532. function setTokenAutoRefreshEnabled(appCheckInstance, isTokenAutoRefreshEnabled) {
  1533. const app = appCheckInstance.app;
  1534. const state = getStateReference(app);
  1535. // This will exist if any product libraries have called
  1536. // `addTokenListener()`
  1537. if (state.tokenRefresher) {
  1538. if (isTokenAutoRefreshEnabled === true) {
  1539. state.tokenRefresher.start();
  1540. }
  1541. else {
  1542. state.tokenRefresher.stop();
  1543. }
  1544. }
  1545. state.isTokenAutoRefreshEnabled = isTokenAutoRefreshEnabled;
  1546. }
  1547. /**
  1548. * Get the current App Check token. Attaches to the most recent
  1549. * in-flight request if one is present. Returns null if no token
  1550. * is present and no token requests are in-flight.
  1551. *
  1552. * @param appCheckInstance - The App Check service instance.
  1553. * @param forceRefresh - If true, will always try to fetch a fresh token.
  1554. * If false, will use a cached token if found in storage.
  1555. * @public
  1556. */
  1557. async function getToken(appCheckInstance, forceRefresh) {
  1558. const result = await getToken$2(appCheckInstance, forceRefresh);
  1559. if (result.error) {
  1560. throw result.error;
  1561. }
  1562. return { token: result.token };
  1563. }
  1564. /**
  1565. * Requests a Firebase App Check token. This method should be used
  1566. * only if you need to authorize requests to a non-Firebase backend.
  1567. *
  1568. * Returns limited-use tokens that are intended for use with your
  1569. * non-Firebase backend endpoints that are protected with
  1570. * <a href="https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection">
  1571. * Replay Protection</a>. This method
  1572. * does not affect the token generation behavior of the
  1573. * #getAppCheckToken() method.
  1574. *
  1575. * @param appCheckInstance - The App Check service instance.
  1576. * @returns The limited use token.
  1577. * @public
  1578. */
  1579. function getLimitedUseToken(appCheckInstance) {
  1580. return getLimitedUseToken$1(appCheckInstance);
  1581. }
  1582. /**
  1583. * Wraps `addTokenListener`/`removeTokenListener` methods in an `Observer`
  1584. * pattern for public use.
  1585. */
  1586. function onTokenChanged(appCheckInstance, onNextOrObserver, onError,
  1587. /**
  1588. * NOTE: Although an `onCompletion` callback can be provided, it will
  1589. * never be called because the token stream is never-ending.
  1590. * It is added only for API consistency with the observer pattern, which
  1591. * we follow in JS APIs.
  1592. */
  1593. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  1594. onCompletion) {
  1595. let nextFn = () => { };
  1596. let errorFn = () => { };
  1597. if (onNextOrObserver.next != null) {
  1598. nextFn = onNextOrObserver.next.bind(onNextOrObserver);
  1599. }
  1600. else {
  1601. nextFn = onNextOrObserver;
  1602. }
  1603. if (onNextOrObserver.error != null) {
  1604. errorFn = onNextOrObserver.error.bind(onNextOrObserver);
  1605. }
  1606. else if (onError) {
  1607. errorFn = onError;
  1608. }
  1609. addTokenListener(appCheckInstance, "EXTERNAL" /* ListenerType.EXTERNAL */, nextFn, errorFn);
  1610. return () => removeTokenListener(appCheckInstance.app, nextFn);
  1611. }
  1612. /**
  1613. * Firebase App Check
  1614. *
  1615. * @packageDocumentation
  1616. */
  1617. const APP_CHECK_NAME = 'app-check';
  1618. const APP_CHECK_NAME_INTERNAL = 'app-check-internal';
  1619. function registerAppCheck() {
  1620. // The public interface
  1621. _registerComponent(new Component(APP_CHECK_NAME, container => {
  1622. // getImmediate for FirebaseApp will always succeed
  1623. const app = container.getProvider('app').getImmediate();
  1624. const heartbeatServiceProvider = container.getProvider('heartbeat');
  1625. return factory(app, heartbeatServiceProvider);
  1626. }, "PUBLIC" /* ComponentType.PUBLIC */)
  1627. .setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */)
  1628. /**
  1629. * Initialize app-check-internal after app-check is initialized to make AppCheck available to
  1630. * other Firebase SDKs
  1631. */
  1632. .setInstanceCreatedCallback((container, _identifier, _appcheckService) => {
  1633. container.getProvider(APP_CHECK_NAME_INTERNAL).initialize();
  1634. }));
  1635. // The internal interface used by other Firebase products
  1636. _registerComponent(new Component(APP_CHECK_NAME_INTERNAL, container => {
  1637. const appCheck = container.getProvider('app-check').getImmediate();
  1638. return internalFactory(appCheck);
  1639. }, "PUBLIC" /* ComponentType.PUBLIC */).setInstantiationMode("EXPLICIT" /* InstantiationMode.EXPLICIT */));
  1640. registerVersion(name, version);
  1641. }
  1642. registerAppCheck();
  1643. export { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider, getLimitedUseToken, getToken, initializeAppCheck, onTokenChanged, setTokenAutoRefreshEnabled };
  1644. //# sourceMappingURL=index.esm2017.js.map