Skip to content

Commit 8b301ba

Browse files
committed
Add network timeout cancellation to core network code
1 parent 37c1a01 commit 8b301ba

File tree

2 files changed

+43
-18
lines changed

2 files changed

+43
-18
lines changed

packages-exp/auth-exp/src/api/index.test.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import { assert, expect, use } from 'chai';
1919
import * as chaiAsPromised from 'chai-as-promised';
20+
import * as sinon from 'sinon';
2021
import { useFakeTimers } from 'sinon';
22+
import * as sinonChai from 'sinon-chai';
2123

2224
import { FirebaseError } from '@firebase/util';
2325

@@ -27,15 +29,11 @@ import * as mockFetch from '../../test/helpers/mock_fetch';
2729
import { AuthErrorCode } from '../core/errors';
2830
import { ConfigInternal } from '../model/auth';
2931
import {
30-
_getFinalTarget,
31-
_performApiRequest,
32-
DEFAULT_API_TIMEOUT_MS,
33-
Endpoint,
34-
HttpHeader,
35-
HttpMethod
32+
_getFinalTarget, _performApiRequest, DEFAULT_API_TIMEOUT_MS, Endpoint, HttpHeader, HttpMethod
3633
} from './';
3734
import { ServerError } from './errors';
3835

36+
use(sinonChai);
3937
use(chaiAsPromised);
4038

4139
describe('api/_performApiRequest', () => {
@@ -227,6 +225,22 @@ describe('api/_performApiRequest', () => {
227225
clock.restore();
228226
});
229227

228+
it('should clear the network timeout on success', async () => {
229+
const clock = useFakeTimers();
230+
sinon.spy(clock, 'clearTimeout');
231+
mockFetch.setUp();
232+
mockEndpoint(Endpoint.SIGN_UP, {});
233+
const promise = _performApiRequest(
234+
auth,
235+
HttpMethod.POST,
236+
Endpoint.SIGN_UP,
237+
request
238+
);
239+
await promise;
240+
expect(clock.clearTimeout).to.have.been.called;
241+
clock.restore();
242+
});
243+
230244
it('should handle network failure', async () => {
231245
mockFetch.setUpWithOverride(() => {
232246
return new Promise<never>((_, reject) =>

packages-exp/auth-exp/src/api/index.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717

1818
import { FirebaseError, querystring } from '@firebase/util';
1919

20-
import {
21-
AUTH_ERROR_FACTORY,
22-
AuthErrorCode,
23-
NamedErrorParams
24-
} from '../core/errors';
20+
import { AUTH_ERROR_FACTORY, AuthErrorCode, NamedErrorParams } from '../core/errors';
2521
import { fail } from '../core/util/assert';
2622
import { Delay } from '../core/util/delay';
2723
import { _emulatorUrl } from '../core/util/emulator';
@@ -120,11 +116,16 @@ export async function _performFetchWithErrorHandling<V>(
120116
(auth as Auth)._canInitEmulator = false;
121117
const errorMap = { ...SERVER_ERROR_MAP, ...customErrorMap };
122118
try {
119+
const networkTimeout = new NetworkTimeout<Response>(auth.name);
123120
const response: Response = await Promise.race<Promise<Response>>([
124121
fetchFn(),
125-
makeNetworkTimeout(auth.name)
122+
networkTimeout.promise
126123
]);
127124

125+
// If we've reached this point, the fetch succeeded and the networkTimeout
126+
// didn't throw; clear the network timeout delay so that Node won't hang
127+
networkTimeout.clearNetworkTimeout();
128+
128129
const json = await response.json();
129130
if ('needConfirmation' in json) {
130131
throw makeTaggedError(auth, AuthErrorCode.NEED_CONFIRMATION, json);
@@ -201,16 +202,26 @@ export function _getFinalTarget(
201202
return _emulatorUrl(auth.config, base);
202203
}
203204

204-
function makeNetworkTimeout<T>(appName: string): Promise<T> {
205-
return new Promise((_, reject) =>
206-
setTimeout(() => {
205+
class NetworkTimeout<T> {
206+
// Node timers and browser timers are fundamentally incompatible, but we
207+
// don't care about the value here
208+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
209+
private timer: any|null = null;
210+
readonly promise = new Promise<T>((_, reject) => {
211+
this.timer = setTimeout(() => {
207212
return reject(
208213
AUTH_ERROR_FACTORY.create(AuthErrorCode.TIMEOUT, {
209-
appName
214+
appName: this.appName
210215
})
211216
);
212-
}, DEFAULT_API_TIMEOUT_MS.get())
213-
);
217+
}, DEFAULT_API_TIMEOUT_MS.get());
218+
});
219+
220+
clearNetworkTimeout():void {
221+
clearTimeout(this.timer);
222+
}
223+
224+
constructor(private readonly appName: string) {}
214225
}
215226

216227
interface PotentialResponse extends IdTokenResponse {

0 commit comments

Comments
 (0)