Skip to content

Commit a7e00b9

Browse files
authored
Do not throw from connection.send in Node.js (#5578)
* do not throw from connection.send * Create curvy-peaches-deliver.md * add tests * formatting
1 parent b9235c6 commit a7e00b9

File tree

6 files changed

+53
-9
lines changed

6 files changed

+53
-9
lines changed

.changeset/curvy-peaches-deliver.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/storage": patch
3+
---
4+
5+
Do not throw from `connection.send` to enable retries in Node.js.

packages/storage/src/implementation/connection.ts

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export type Headers = Record<string, string>;
2323
* goog.net.XhrIo-like interface.
2424
*/
2525
export interface Connection {
26+
/**
27+
* This method should never reject. In case of encountering an error, set an error code internally which can be accessed
28+
* by calling getErrorCode() by callers.
29+
*/
2630
send(
2731
url: string,
2832
method: string,

packages/storage/src/implementation/request.ts

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class NetworkRequest<T> implements Request<T> {
106106
connection.addUploadProgressListener(progressListener);
107107
}
108108

109+
// connection.send() never rejects, so we don't need to have a error handler or use catch on the returned promise.
109110
// eslint-disable-next-line @typescript-eslint/no-floating-promises
110111
connection
111112
.send(this.url_, this.method_, this.body_, this.headers_)

packages/storage/src/platform/node/connection.ts

+11-9
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@
1717

1818
import { ErrorCode, Connection } from '../../implementation/connection';
1919
import { internalError } from '../../implementation/error';
20-
import nodeFetch from 'node-fetch';
21-
22-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23-
const fetch: typeof window.fetch = nodeFetch as any;
20+
import nodeFetch, { FetchError } from 'node-fetch';
2421

2522
/**
2623
* Network layer that works in Node.
@@ -34,6 +31,8 @@ export class FetchConnection implements Connection {
3431
private body_: string | undefined;
3532
private headers_: Headers | undefined;
3633
private sent_: boolean = false;
34+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
35+
private fetch_: typeof window.fetch = nodeFetch as any;
3736

3837
constructor() {
3938
this.errorCode_ = ErrorCode.NO_ERROR;
@@ -50,18 +49,21 @@ export class FetchConnection implements Connection {
5049
}
5150
this.sent_ = true;
5251

53-
return fetch(url, {
52+
return this.fetch_(url, {
5453
method,
5554
headers: headers || {},
5655
body
5756
})
5857
.then(resp => {
5958
this.headers_ = resp.headers;
6059
this.statusCode_ = resp.status;
61-
return resp.text();
62-
})
63-
.then(body => {
64-
this.body_ = body;
60+
return resp.text().then(body => {
61+
this.body_ = body;
62+
});
63+
}, (_err: FetchError) => {
64+
this.errorCode_ = ErrorCode.NETWORK_ERROR;
65+
// emulate XHR which sets status to 0 when encountering a network error
66+
this.statusCode_ = 0;
6567
});
6668
}
6769

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { expect } from "chai";
2+
import { SinonFakeXMLHttpRequest, useFakeXMLHttpRequest } from "sinon";
3+
import { ErrorCode } from "../../src/implementation/connection";
4+
import { XhrConnection } from "../../src/platform/browser/connection";
5+
6+
describe('Connections', () => {
7+
it('XhrConnection.send() should not reject on network errors', async () => {
8+
const fakeXHR = useFakeXMLHttpRequest();
9+
const connection = new XhrConnection();
10+
const sendPromise = connection.send('testurl', 'GET');
11+
// simulate a network error
12+
((connection as any).xhr_ as SinonFakeXMLHttpRequest).error();
13+
await sendPromise;
14+
expect(connection.getErrorCode()).to.equal(ErrorCode.NETWORK_ERROR);
15+
fakeXHR.restore();
16+
});
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { stub } from 'sinon';
2+
import { expect } from 'chai';
3+
import { ErrorCode } from '../../src/implementation/connection';
4+
import { FetchConnection } from '../../src/platform/node/connection';
5+
6+
describe('Connections', () => {
7+
it('FetchConnection.send() should not reject on network errors', async () => {
8+
const connection = new FetchConnection();
9+
10+
// need the casting here because fetch_ is a private member
11+
stub(connection as any, 'fetch_').rejects();
12+
await connection.send('testurl', 'GET');
13+
expect(connection.getErrorCode()).to.equal(ErrorCode.NETWORK_ERROR);
14+
});
15+
});

0 commit comments

Comments
 (0)