Skip to content

Commit f681482

Browse files
authored
Catch errors in debug token exchange logic (#8792)
1 parent 884cbd7 commit f681482

File tree

3 files changed

+57
-17
lines changed

3 files changed

+57
-17
lines changed

.changeset/tough-kiwis-smile.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/app-check': patch
3+
---
4+
5+
Fixed a bug that caused an error to be thrown when the debug exchange failed.

packages/app-check/src/internal-api.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,29 @@ describe('internal api', () => {
176176
errorStub.restore();
177177
});
178178

179+
it('resolves with a dummy token and an error if failed to get a token in debug mode', async () => {
180+
const errorStub = stub(console, 'error');
181+
window.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
182+
const appCheck = initializeAppCheck(app, {
183+
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
184+
});
185+
186+
const error = new Error('oops, something went wrong');
187+
stub(client, 'exchangeToken').returns(Promise.reject(error));
188+
189+
const token = await getToken(appCheck as AppCheckService);
190+
191+
expect(token).to.deep.equal({
192+
token: formatDummyToken(defaultTokenErrorData),
193+
error
194+
});
195+
expect(errorStub.args[0][1].message).to.include(
196+
'oops, something went wrong'
197+
);
198+
delete window.FIREBASE_APPCHECK_DEBUG_TOKEN;
199+
errorStub.restore();
200+
});
201+
179202
it('resolves with a dummy token and an error if recaptcha failed', async () => {
180203
const errorStub = stub(console, 'error');
181204
const appCheck = initializeAppCheck(app, {

packages/app-check/src/internal-api.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -116,24 +116,36 @@ export async function getToken(
116116
* Check token using the debug token, and return it directly.
117117
*/
118118
if (isDebugMode()) {
119-
// Avoid making another call to the exchange endpoint if one is in flight.
120-
if (!state.exchangeTokenPromise) {
121-
state.exchangeTokenPromise = exchangeToken(
122-
getExchangeDebugTokenRequest(app, await getDebugToken()),
123-
appCheck.heartbeatServiceProvider
124-
).finally(() => {
125-
// Clear promise when settled - either resolved or rejected.
126-
state.exchangeTokenPromise = undefined;
127-
});
128-
shouldCallListeners = true;
119+
try {
120+
// Avoid making another call to the exchange endpoint if one is in flight.
121+
if (!state.exchangeTokenPromise) {
122+
state.exchangeTokenPromise = exchangeToken(
123+
getExchangeDebugTokenRequest(app, await getDebugToken()),
124+
appCheck.heartbeatServiceProvider
125+
).finally(() => {
126+
// Clear promise when settled - either resolved or rejected.
127+
state.exchangeTokenPromise = undefined;
128+
});
129+
shouldCallListeners = true;
130+
}
131+
const tokenFromDebugExchange: AppCheckTokenInternal =
132+
await state.exchangeTokenPromise;
133+
// Write debug token to indexedDB.
134+
await writeTokenToStorage(app, tokenFromDebugExchange);
135+
// Write debug token to state.
136+
state.token = tokenFromDebugExchange;
137+
return { token: tokenFromDebugExchange.token };
138+
} catch (e) {
139+
if ((e as FirebaseError).code === `appCheck/${AppCheckError.THROTTLED}`) {
140+
// Warn if throttled, but do not treat it as an error.
141+
logger.warn((e as FirebaseError).message);
142+
} else {
143+
// `getToken()` should never throw, but logging error text to console will aid debugging.
144+
logger.error(e);
145+
}
146+
// Return dummy token and error
147+
return makeDummyTokenResult(e as FirebaseError);
129148
}
130-
const tokenFromDebugExchange: AppCheckTokenInternal =
131-
await state.exchangeTokenPromise;
132-
// Write debug token to indexedDB.
133-
await writeTokenToStorage(app, tokenFromDebugExchange);
134-
// Write debug token to state.
135-
state.token = tokenFromDebugExchange;
136-
return { token: tokenFromDebugExchange.token };
137149
}
138150

139151
/**

0 commit comments

Comments
 (0)