diff --git a/.changeset/tough-kiwis-smile.md b/.changeset/tough-kiwis-smile.md new file mode 100644 index 00000000000..47e6d4caff8 --- /dev/null +++ b/.changeset/tough-kiwis-smile.md @@ -0,0 +1,5 @@ +--- +'@firebase/app-check': patch +--- + +Fixed a bug that caused an error to be thrown when the debug exchange failed. diff --git a/packages/app-check/src/internal-api.test.ts b/packages/app-check/src/internal-api.test.ts index 360ec3a026a..1e43a5e7e21 100644 --- a/packages/app-check/src/internal-api.test.ts +++ b/packages/app-check/src/internal-api.test.ts @@ -176,6 +176,29 @@ describe('internal api', () => { errorStub.restore(); }); + it('resolves with a dummy token and an error if failed to get a token in debug mode', async () => { + const errorStub = stub(console, 'error'); + window.FIREBASE_APPCHECK_DEBUG_TOKEN = true; + const appCheck = initializeAppCheck(app, { + provider: new ReCaptchaV3Provider(FAKE_SITE_KEY) + }); + + const error = new Error('oops, something went wrong'); + stub(client, 'exchangeToken').returns(Promise.reject(error)); + + const token = await getToken(appCheck as AppCheckService); + + expect(token).to.deep.equal({ + token: formatDummyToken(defaultTokenErrorData), + error + }); + expect(errorStub.args[0][1].message).to.include( + 'oops, something went wrong' + ); + delete window.FIREBASE_APPCHECK_DEBUG_TOKEN; + errorStub.restore(); + }); + it('resolves with a dummy token and an error if recaptcha failed', async () => { const errorStub = stub(console, 'error'); const appCheck = initializeAppCheck(app, { diff --git a/packages/app-check/src/internal-api.ts b/packages/app-check/src/internal-api.ts index 728f2ca5e68..4eb3953614a 100644 --- a/packages/app-check/src/internal-api.ts +++ b/packages/app-check/src/internal-api.ts @@ -116,24 +116,36 @@ export async function getToken( * Check token using the debug token, and return it directly. */ if (isDebugMode()) { - // Avoid making another call to the exchange endpoint if one is in flight. - if (!state.exchangeTokenPromise) { - state.exchangeTokenPromise = exchangeToken( - getExchangeDebugTokenRequest(app, await getDebugToken()), - appCheck.heartbeatServiceProvider - ).finally(() => { - // Clear promise when settled - either resolved or rejected. - state.exchangeTokenPromise = undefined; - }); - shouldCallListeners = true; + try { + // Avoid making another call to the exchange endpoint if one is in flight. + if (!state.exchangeTokenPromise) { + state.exchangeTokenPromise = exchangeToken( + getExchangeDebugTokenRequest(app, await getDebugToken()), + appCheck.heartbeatServiceProvider + ).finally(() => { + // Clear promise when settled - either resolved or rejected. + state.exchangeTokenPromise = undefined; + }); + shouldCallListeners = true; + } + const tokenFromDebugExchange: AppCheckTokenInternal = + await state.exchangeTokenPromise; + // Write debug token to indexedDB. + await writeTokenToStorage(app, tokenFromDebugExchange); + // Write debug token to state. + state.token = tokenFromDebugExchange; + return { token: tokenFromDebugExchange.token }; + } catch (e) { + if ((e as FirebaseError).code === `appCheck/${AppCheckError.THROTTLED}`) { + // Warn if throttled, but do not treat it as an error. + logger.warn((e as FirebaseError).message); + } else { + // `getToken()` should never throw, but logging error text to console will aid debugging. + logger.error(e); + } + // Return dummy token and error + return makeDummyTokenResult(e as FirebaseError); } - const tokenFromDebugExchange: AppCheckTokenInternal = - await state.exchangeTokenPromise; - // Write debug token to indexedDB. - await writeTokenToStorage(app, tokenFromDebugExchange); - // Write debug token to state. - state.token = tokenFromDebugExchange; - return { token: tokenFromDebugExchange.token }; } /**