Skip to content

Commit 926709f

Browse files
Merge main into release
2 parents 62c4c98 + 6d6ce81 commit 926709f

File tree

14 files changed

+164
-110
lines changed

14 files changed

+164
-110
lines changed

.changeset/itchy-boxes-try.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'firebase': minor
3+
'@firebase/storage': minor
4+
---
5+
6+
Migrate from the Node to Web ReadableStream interface

.changeset/large-games-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/app': patch
3+
---
4+
5+
Prevent heartbeats methods from throwing - warn instead.

common/api-review/storage.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export function getMetadata(ref: StorageReference): Promise<FullMetadata>;
132132
export function getStorage(app?: FirebaseApp, bucketUrl?: string): FirebaseStorage;
133133

134134
// @public
135-
export function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): NodeJS.ReadableStream;
135+
export function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): ReadableStream;
136136

137137
// @internal (undocumented)
138138
export function _invalidArgument(message: string): StorageError;

docs-devsite/storage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ This API is only available in Node.
279279
<b>Signature:</b>
280280

281281
```typescript
282-
export declare function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): NodeJS.ReadableStream;
282+
export declare function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): ReadableStream;
283283
```
284284

285285
#### Parameters
@@ -291,7 +291,7 @@ export declare function getStream(ref: StorageReference, maxDownloadSizeBytes?:
291291

292292
<b>Returns:</b>
293293

294-
NodeJS.ReadableStream
294+
ReadableStream
295295

296296
A stream with the object's data as bytes
297297

packages/app/src/heartbeatService.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,26 @@ describe('HeartbeatServiceImpl', () => {
146146
const emptyHeaders = await heartbeatService.getHeartbeatsHeader();
147147
expect(emptyHeaders).to.equal('');
148148
});
149+
it(`triggerHeartbeat() doesn't throw even if code errors`, async () => {
150+
//@ts-expect-error Ensure this doesn't match
151+
heartbeatService._heartbeatsCache?.lastSentHeartbeatDate = 50;
152+
//@ts-expect-error Ensure you can't .push() to this
153+
heartbeatService._heartbeatsCache.heartbeats = 50;
154+
const warnStub = stub(console, 'warn');
155+
await heartbeatService.triggerHeartbeat();
156+
expect(warnStub).to.be.called;
157+
expect(warnStub.args[0][1].message).to.include('heartbeats');
158+
warnStub.restore();
159+
});
160+
it(`getHeartbeatsHeader() doesn't throw even if code errors`, async () => {
161+
//@ts-expect-error Ensure you can't .push() to this
162+
heartbeatService._heartbeatsCache.heartbeats = 50;
163+
const warnStub = stub(console, 'warn');
164+
await heartbeatService.getHeartbeatsHeader();
165+
expect(warnStub).to.be.called;
166+
expect(warnStub.args[0][1].message).to.include('heartbeats');
167+
warnStub.restore();
168+
});
149169
});
150170
describe('If IndexedDB has entries', () => {
151171
let heartbeatService: HeartbeatServiceImpl;

packages/app/src/heartbeatService.ts

Lines changed: 73 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
HeartbeatStorage,
3434
SingleDateHeartbeat
3535
} from './types';
36+
import { logger } from './logger';
3637

3738
const MAX_HEADER_BYTES = 1024;
3839
// 30 days
@@ -80,43 +81,47 @@ export class HeartbeatServiceImpl implements HeartbeatService {
8081
* already logged, subsequent calls to this function in the same day will be ignored.
8182
*/
8283
async triggerHeartbeat(): Promise<void> {
83-
const platformLogger = this.container
84-
.getProvider('platform-logger')
85-
.getImmediate();
84+
try {
85+
const platformLogger = this.container
86+
.getProvider('platform-logger')
87+
.getImmediate();
8688

87-
// This is the "Firebase user agent" string from the platform logger
88-
// service, not the browser user agent.
89-
const agent = platformLogger.getPlatformInfoString();
90-
const date = getUTCDateString();
91-
if (this._heartbeatsCache?.heartbeats == null) {
92-
this._heartbeatsCache = await this._heartbeatsCachePromise;
93-
// If we failed to construct a heartbeats cache, then return immediately.
89+
// This is the "Firebase user agent" string from the platform logger
90+
// service, not the browser user agent.
91+
const agent = platformLogger.getPlatformInfoString();
92+
const date = getUTCDateString();
93+
console.log('heartbeats', this._heartbeatsCache?.heartbeats);
9494
if (this._heartbeatsCache?.heartbeats == null) {
95+
this._heartbeatsCache = await this._heartbeatsCachePromise;
96+
// If we failed to construct a heartbeats cache, then return immediately.
97+
if (this._heartbeatsCache?.heartbeats == null) {
98+
return;
99+
}
100+
}
101+
// Do not store a heartbeat if one is already stored for this day
102+
// or if a header has already been sent today.
103+
if (
104+
this._heartbeatsCache.lastSentHeartbeatDate === date ||
105+
this._heartbeatsCache.heartbeats.some(
106+
singleDateHeartbeat => singleDateHeartbeat.date === date
107+
)
108+
) {
95109
return;
110+
} else {
111+
// There is no entry for this date. Create one.
112+
this._heartbeatsCache.heartbeats.push({ date, agent });
96113
}
114+
// Remove entries older than 30 days.
115+
this._heartbeatsCache.heartbeats =
116+
this._heartbeatsCache.heartbeats.filter(singleDateHeartbeat => {
117+
const hbTimestamp = new Date(singleDateHeartbeat.date).valueOf();
118+
const now = Date.now();
119+
return now - hbTimestamp <= STORED_HEARTBEAT_RETENTION_MAX_MILLIS;
120+
});
121+
return this._storage.overwrite(this._heartbeatsCache);
122+
} catch (e) {
123+
logger.warn(e);
97124
}
98-
// Do not store a heartbeat if one is already stored for this day
99-
// or if a header has already been sent today.
100-
if (
101-
this._heartbeatsCache.lastSentHeartbeatDate === date ||
102-
this._heartbeatsCache.heartbeats.some(
103-
singleDateHeartbeat => singleDateHeartbeat.date === date
104-
)
105-
) {
106-
return;
107-
} else {
108-
// There is no entry for this date. Create one.
109-
this._heartbeatsCache.heartbeats.push({ date, agent });
110-
}
111-
// Remove entries older than 30 days.
112-
this._heartbeatsCache.heartbeats = this._heartbeatsCache.heartbeats.filter(
113-
singleDateHeartbeat => {
114-
const hbTimestamp = new Date(singleDateHeartbeat.date).valueOf();
115-
const now = Date.now();
116-
return now - hbTimestamp <= STORED_HEARTBEAT_RETENTION_MAX_MILLIS;
117-
}
118-
);
119-
return this._storage.overwrite(this._heartbeatsCache);
120125
}
121126

122127
/**
@@ -127,39 +132,44 @@ export class HeartbeatServiceImpl implements HeartbeatService {
127132
* returns an empty string.
128133
*/
129134
async getHeartbeatsHeader(): Promise<string> {
130-
if (this._heartbeatsCache === null) {
131-
await this._heartbeatsCachePromise;
132-
}
133-
// If it's still null or the array is empty, there is no data to send.
134-
if (
135-
this._heartbeatsCache?.heartbeats == null ||
136-
this._heartbeatsCache.heartbeats.length === 0
137-
) {
135+
try {
136+
if (this._heartbeatsCache === null) {
137+
await this._heartbeatsCachePromise;
138+
}
139+
// If it's still null or the array is empty, there is no data to send.
140+
if (
141+
this._heartbeatsCache?.heartbeats == null ||
142+
this._heartbeatsCache.heartbeats.length === 0
143+
) {
144+
return '';
145+
}
146+
const date = getUTCDateString();
147+
// Extract as many heartbeats from the cache as will fit under the size limit.
148+
const { heartbeatsToSend, unsentEntries } = extractHeartbeatsForHeader(
149+
this._heartbeatsCache.heartbeats
150+
);
151+
const headerString = base64urlEncodeWithoutPadding(
152+
JSON.stringify({ version: 2, heartbeats: heartbeatsToSend })
153+
);
154+
// Store last sent date to prevent another being logged/sent for the same day.
155+
this._heartbeatsCache.lastSentHeartbeatDate = date;
156+
if (unsentEntries.length > 0) {
157+
// Store any unsent entries if they exist.
158+
this._heartbeatsCache.heartbeats = unsentEntries;
159+
// This seems more likely than emptying the array (below) to lead to some odd state
160+
// since the cache isn't empty and this will be called again on the next request,
161+
// and is probably safest if we await it.
162+
await this._storage.overwrite(this._heartbeatsCache);
163+
} else {
164+
this._heartbeatsCache.heartbeats = [];
165+
// Do not wait for this, to reduce latency.
166+
void this._storage.overwrite(this._heartbeatsCache);
167+
}
168+
return headerString;
169+
} catch (e) {
170+
logger.warn(e);
138171
return '';
139172
}
140-
const date = getUTCDateString();
141-
// Extract as many heartbeats from the cache as will fit under the size limit.
142-
const { heartbeatsToSend, unsentEntries } = extractHeartbeatsForHeader(
143-
this._heartbeatsCache.heartbeats
144-
);
145-
const headerString = base64urlEncodeWithoutPadding(
146-
JSON.stringify({ version: 2, heartbeats: heartbeatsToSend })
147-
);
148-
// Store last sent date to prevent another being logged/sent for the same day.
149-
this._heartbeatsCache.lastSentHeartbeatDate = date;
150-
if (unsentEntries.length > 0) {
151-
// Store any unsent entries if they exist.
152-
this._heartbeatsCache.heartbeats = unsentEntries;
153-
// This seems more likely than emptying the array (below) to lead to some odd state
154-
// since the cache isn't empty and this will be called again on the next request,
155-
// and is probably safest if we await it.
156-
await this._storage.overwrite(this._heartbeatsCache);
157-
} else {
158-
this._heartbeatsCache.heartbeats = [];
159-
// Do not wait for this, to reduce latency.
160-
void this._storage.overwrite(this._heartbeatsCache);
161-
}
162-
return headerString;
163173
}
164174
}
165175

packages/storage/src/api.browser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ export function getBlob(
5858
export function getStream(
5959
ref: StorageReference,
6060
maxDownloadSizeBytes?: number
61-
): NodeJS.ReadableStream {
61+
): ReadableStream {
6262
throw new Error('getStream() is only supported by NodeJS builds');
6363
}

packages/storage/src/api.node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function getBlob(
5858
export function getStream(
5959
ref: StorageReference,
6060
maxDownloadSizeBytes?: number
61-
): NodeJS.ReadableStream {
61+
): ReadableStream {
6262
ref = getModularInstance(ref);
6363
return getStreamInternal(ref as Reference, maxDownloadSizeBytes);
6464
}

packages/storage/src/implementation/connection.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
1817
/** Network headers */
1918
export type Headers = Record<string, string>;
2019

@@ -23,7 +22,7 @@ export type ConnectionType =
2322
| string
2423
| ArrayBuffer
2524
| Blob
26-
| NodeJS.ReadableStream;
25+
| ReadableStream<Uint8Array>;
2726

2827
/**
2928
* A lightweight wrapper around XMLHttpRequest with a

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function newBlobConnection(): Connection<Blob> {
171171
return new XhrBlobConnection();
172172
}
173173

174-
export function newStreamConnection(): Connection<NodeJS.ReadableStream> {
174+
export function newStreamConnection(): Connection<ReadableStream> {
175175
throw new Error('Streams are only supported on Node');
176176
}
177177

0 commit comments

Comments
 (0)