Skip to content

Commit e34e98e

Browse files
Add getBytes() (#5672)
1 parent e0fe2b6 commit e34e98e

25 files changed

+732
-217
lines changed

.changeset/clever-eggs-relate.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@firebase/storage": minor
3+
"firebase": minor
4+
---
5+
6+
Adds `getBytes()`, `getStream()` and `getBlob()`, which allow direct file downloads from the SDK.

common/api-review/storage.api.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,15 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
7777
_getAuthToken(): Promise<string | null>;
7878
get host(): string;
7979
set host(host: string);
80+
// Warning: (ae-forgotten-export) The symbol "ConnectionType" needs to be exported by the entry point index.d.ts
8081
// Warning: (ae-forgotten-export) The symbol "RequestInfo" needs to be exported by the entry point index.d.ts
8182
// Warning: (ae-forgotten-export) The symbol "Connection" needs to be exported by the entry point index.d.ts
8283
// Warning: (ae-forgotten-export) The symbol "Request" needs to be exported by the entry point index.d.ts
8384
//
8485
// (undocumented)
85-
_makeRequest<T>(requestInfo: RequestInfo_2<T>, requestFactory: () => Connection, authToken: string | null, appCheckToken: string | null): Request_2<T>;
86+
_makeRequest<I extends ConnectionType, O>(requestInfo: RequestInfo_2<I, O>, requestFactory: () => Connection<I>, authToken: string | null, appCheckToken: string | null): Request_2<O>;
8687
// (undocumented)
87-
makeRequestWithTokens<T>(requestInfo: RequestInfo_2<T>, requestFactory: () => Connection): Promise<T>;
88+
makeRequestWithTokens<I extends ConnectionType, O>(requestInfo: RequestInfo_2<I, O>, requestFactory: () => Connection<I>): Promise<O>;
8889
_makeStorageReference(loc: _Location): _Reference;
8990
get maxOperationRetryTime(): number;
9091
set maxOperationRetryTime(time: number);
@@ -112,6 +113,12 @@ export interface FullMetadata extends UploadMetadata {
112113
updated: string;
113114
}
114115

116+
// @public
117+
export function getBlob(ref: StorageReference, maxDownloadSizeBytes?: number): Promise<Blob>;
118+
119+
// @public
120+
export function getBytes(ref: StorageReference, maxDownloadSizeBytes?: number): Promise<ArrayBuffer>;
121+
115122
// @internal (undocumented)
116123
export function _getChild(ref: StorageReference, childPath: string): _Reference;
117124

@@ -124,6 +131,9 @@ export function getMetadata(ref: StorageReference): Promise<FullMetadata>;
124131
// @public
125132
export function getStorage(app?: FirebaseApp, bucketUrl?: string): FirebaseStorage;
126133

134+
// @public
135+
export function getStream(ref: StorageReference, maxDownloadSizeBytes?: number): NodeJS.ReadableStream;
136+
127137
// Warning: (ae-forgotten-export) The symbol "StorageError" needs to be exported by the entry point index.d.ts
128138
//
129139
// @internal (undocumented)

packages/storage/.run/All Tests.run.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<env name="TS_NODE_CACHE" value="NO" />
1212
</envs>
1313
<ui>bdd</ui>
14-
<extra-mocha-options>--require ts-node/register/type-check --require index.node.ts</extra-mocha-options>
14+
<extra-mocha-options>--require ts-node/register/type-check --require src/index.node.ts</extra-mocha-options>
1515
<test-kind>PATTERN</test-kind>
1616
<test-pattern>test/{,!(browser)/**/}*.test.ts</test-pattern>
1717
<method v="2" />
1818
</configuration>
19-
</component>
19+
</component>

packages/storage/src/api.browser.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717

1818
import { StorageReference } from './public-types';
19+
import { Reference, getBlobInternal } from './reference';
20+
import { getModularInstance } from '@firebase/util';
1921

2022
/**
2123
* Downloads the data at the object's location. Returns an error if the object
@@ -28,17 +30,17 @@ import { StorageReference } from './public-types';
2830
* This API is not available in Node.
2931
*
3032
* @public
31-
* @param ref - StorageReference where data should be download.
33+
* @param ref - StorageReference where data should be downloaded.
3234
* @param maxDownloadSizeBytes - If set, the maximum allowed size in bytes to
3335
* retrieve.
3436
* @returns A Promise that resolves with a Blob containing the object's bytes
3537
*/
36-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
37-
function getBlob(
38+
export function getBlob(
3839
ref: StorageReference,
3940
maxDownloadSizeBytes?: number
4041
): Promise<Blob> {
41-
throw new Error('Not implemented');
42+
ref = getModularInstance(ref);
43+
return getBlobInternal(ref as Reference, maxDownloadSizeBytes);
4244
}
4345

4446
/**
@@ -48,19 +50,14 @@ function getBlob(
4850
* This API is only available in Node.
4951
*
5052
* @public
51-
* @param ref - StorageReference where data should be download.
53+
* @param ref - StorageReference where data should be downloaded.
5254
* @param maxDownloadSizeBytes - If set, the maximum allowed size in bytes to
5355
* retrieve.
5456
* @returns A stream with the object's data as bytes
5557
*/
56-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
57-
function getStream(
58+
export function getStream(
5859
ref: StorageReference,
5960
maxDownloadSizeBytes?: number
6061
): NodeJS.ReadableStream {
6162
throw new Error('getStream() is only supported by NodeJS builds');
6263
}
63-
64-
// TODO(getbytes): Export getBlob/getStream
65-
66-
export {};

packages/storage/src/api.node.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717

1818
import { StorageReference } from './public-types';
19+
import { Reference, getStreamInternal } from './reference';
20+
import { getModularInstance } from '@firebase/util';
1921

2022
/**
2123
* Downloads the data at the object's location. Returns an error if the object
@@ -28,13 +30,13 @@ import { StorageReference } from './public-types';
2830
* This API is not available in Node.
2931
*
3032
* @public
31-
* @param ref - StorageReference where data should be download.
33+
* @param ref - StorageReference where data should be downloaded.
3234
* @param maxDownloadSizeBytes - If set, the maximum allowed size in bytes to
3335
* retrieve.
3436
* @returns A Promise that resolves with a Blob containing the object's bytes
3537
*/
3638
// eslint-disable-next-line @typescript-eslint/no-unused-vars
37-
function getBlob(
39+
export function getBlob(
3840
ref: StorageReference,
3941
maxDownloadSizeBytes?: number
4042
): Promise<Blob> {
@@ -48,19 +50,15 @@ function getBlob(
4850
* This API is only available in Node.
4951
*
5052
* @public
51-
* @param ref - StorageReference where data should be download.
53+
* @param ref - StorageReference where data should be downloaded.
5254
* @param maxDownloadSizeBytes - If set, the maximum allowed size in bytes to
5355
* retrieve.
5456
* @returns A stream with the object's data as bytes
5557
*/
56-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
57-
function getStream(
58+
export function getStream(
5859
ref: StorageReference,
5960
maxDownloadSizeBytes?: number
6061
): NodeJS.ReadableStream {
61-
throw new Error('Not implemented');
62+
ref = getModularInstance(ref);
63+
return getStreamInternal(ref as Reference, maxDownloadSizeBytes);
6264
}
63-
64-
// TODO(getbytes): Export getBlob/getStream
65-
66-
export {};

packages/storage/src/api.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ import {
4747
getDownloadURL as getDownloadURLInternal,
4848
deleteObject as deleteObjectInternal,
4949
Reference,
50-
_getChild as _getChildInternal
50+
_getChild as _getChildInternal,
51+
getBytesInternal
5152
} from './reference';
5253
import { STORAGE_TYPE } from './constants';
5354
import { EmulatorMockTokenOptions, getModularInstance } from '@firebase/util';
@@ -76,6 +77,28 @@ export {
7677
} from './implementation/taskenums';
7778
export { StringFormat };
7879

80+
/**
81+
* Downloads the data at the object's location. Returns an error if the object
82+
* is not found.
83+
*
84+
* To use this functionality, you have to whitelist your app's origin in your
85+
* Cloud Storage bucket. See also
86+
* https://cloud.google.com/storage/docs/configuring-cors
87+
*
88+
* @public
89+
* @param ref - StorageReference where data should be downloaded.
90+
* @param maxDownloadSizeBytes - If set, the maximum allowed size in bytes to
91+
* retrieve.
92+
* @returns A Promise containing the object's bytes
93+
*/
94+
export function getBytes(
95+
ref: StorageReference,
96+
maxDownloadSizeBytes?: number
97+
): Promise<ArrayBuffer> {
98+
ref = getModularInstance(ref);
99+
return getBytesInternal(ref as Reference, maxDownloadSizeBytes);
100+
}
101+
79102
/**
80103
* Uploads data to this object's location.
81104
* The upload is not resumable.

packages/storage/src/implementation/connection.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,27 @@
1818
/** Network headers */
1919
export type Headers = Record<string, string>;
2020

21+
/** Response type exposed by the networking APIs. */
22+
export type ConnectionType =
23+
| string
24+
| ArrayBuffer
25+
| Blob
26+
| NodeJS.ReadableStream;
27+
2128
/**
2229
* A lightweight wrapper around XMLHttpRequest with a
2330
* goog.net.XhrIo-like interface.
31+
*
32+
* You can create a new connection by invoking `newTextConnection()`,
33+
* `newBytesConnection()` or `newStreamConnection()`.
2434
*/
25-
export interface Connection {
35+
export interface Connection<T extends ConnectionType> {
2636
/**
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.
37+
* Sends a request to the provided URL.
38+
*
39+
* This method never rejects its promise. In case of encountering an error,
40+
* it sets an error code internally which can be accessed by calling
41+
* getErrorCode() by callers.
2942
*/
3043
send(
3144
url: string,
@@ -38,7 +51,9 @@ export interface Connection {
3851

3952
getStatus(): number;
4053

41-
getResponseText(): string;
54+
getResponse(): T;
55+
56+
getErrorText(): string;
4257

4358
/**
4459
* Abort the request.

packages/storage/src/implementation/request.ts

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,12 @@
2020
* abstract representations.
2121
*/
2222

23-
import { start, stop, id as backoffId } from './backoff';
24-
import {
25-
StorageError,
26-
unknown,
27-
appDeleted,
28-
canceled,
29-
retryLimitExceeded
30-
} from './error';
31-
import { RequestHandler, RequestInfo } from './requestinfo';
23+
import { id as backoffId, start, stop } from './backoff';
24+
import { appDeleted, canceled, retryLimitExceeded, unknown } from './error';
25+
import { ErrorHandler, RequestHandler, RequestInfo } from './requestinfo';
3226
import { isJustDef } from './type';
3327
import { makeQueryString } from './url';
34-
import { Headers, Connection, ErrorCode } from './connection';
28+
import { Connection, ErrorCode, Headers, ConnectionType } from './connection';
3529

3630
export interface Request<T> {
3731
getPromise(): Promise<T>;
@@ -46,15 +40,23 @@ export interface Request<T> {
4640
cancel(appDelete?: boolean): void;
4741
}
4842

49-
class NetworkRequest<T> implements Request<T> {
50-
private pendingConnection_: Connection | null = null;
43+
/**
44+
* Handles network logic for all Storage Requests, including error reporting and
45+
* retries with backoff.
46+
*
47+
* @param I - the type of the backend's network response.
48+
* @param - O the output type used by the rest of the SDK. The conversion
49+
* happens in the specified `callback_`.
50+
*/
51+
class NetworkRequest<I extends ConnectionType, O> implements Request<O> {
52+
private pendingConnection_: Connection<I> | null = null;
5153
private backoffId_: backoffId | null = null;
52-
private resolve_!: (value?: T | PromiseLike<T>) => void;
54+
private resolve_!: (value?: O | PromiseLike<O>) => void;
5355
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5456
private reject_!: (reason?: any) => void;
5557
private canceled_: boolean = false;
5658
private appDelete_: boolean = false;
57-
promise_: Promise<T>;
59+
private promise_: Promise<O>;
5860

5961
constructor(
6062
private url_: string,
@@ -63,14 +65,14 @@ class NetworkRequest<T> implements Request<T> {
6365
private body_: string | Blob | Uint8Array | null,
6466
private successCodes_: number[],
6567
private additionalRetryCodes_: number[],
66-
private callback_: RequestHandler<string, T>,
67-
private errorCallback_: RequestHandler<StorageError, StorageError> | null,
68+
private callback_: RequestHandler<I, O>,
69+
private errorCallback_: ErrorHandler | null,
6870
private timeout_: number,
6971
private progressCallback_: ((p1: number, p2: number) => void) | null,
70-
private connectionFactory_: () => Connection
72+
private connectionFactory_: () => Connection<I>
7173
) {
7274
this.promise_ = new Promise((resolve, reject) => {
73-
this.resolve_ = resolve as (value?: T | PromiseLike<T>) => void;
75+
this.resolve_ = resolve as (value?: O | PromiseLike<O>) => void;
7476
this.reject_ = reject;
7577
this.start_();
7678
});
@@ -135,17 +137,14 @@ class NetworkRequest<T> implements Request<T> {
135137
*/
136138
const backoffDone: (
137139
requestWentThrough: boolean,
138-
status: RequestEndStatus
140+
status: RequestEndStatus<I>
139141
) => void = (requestWentThrough, status) => {
140142
const resolve = this.resolve_;
141143
const reject = this.reject_;
142-
const connection = status.connection as Connection;
144+
const connection = status.connection as Connection<I>;
143145
if (status.wasSuccessCode) {
144146
try {
145-
const result = this.callback_(
146-
connection,
147-
connection.getResponseText()
148-
);
147+
const result = this.callback_(connection, connection.getResponse());
149148
if (isJustDef(result)) {
150149
resolve(result);
151150
} else {
@@ -157,7 +156,7 @@ class NetworkRequest<T> implements Request<T> {
157156
} else {
158157
if (connection !== null) {
159158
const err = unknown();
160-
err.serverResponse = connection.getResponseText();
159+
err.serverResponse = connection.getErrorText();
161160
if (this.errorCallback_) {
162161
reject(this.errorCallback_(connection, err));
163162
} else {
@@ -182,7 +181,7 @@ class NetworkRequest<T> implements Request<T> {
182181
}
183182

184183
/** @inheritDoc */
185-
getPromise(): Promise<T> {
184+
getPromise(): Promise<O> {
186185
return this.promise_;
187186
}
188187

@@ -219,15 +218,15 @@ class NetworkRequest<T> implements Request<T> {
219218
* A collection of information about the result of a network request.
220219
* @param opt_canceled - Defaults to false.
221220
*/
222-
export class RequestEndStatus {
221+
export class RequestEndStatus<I extends ConnectionType> {
223222
/**
224223
* True if the request was canceled.
225224
*/
226225
canceled: boolean;
227226

228227
constructor(
229228
public wasSuccessCode: boolean,
230-
public connection: Connection | null,
229+
public connection: Connection<I> | null,
231230
canceled?: boolean
232231
) {
233232
this.canceled = !!canceled;
@@ -266,22 +265,22 @@ export function addAppCheckHeader_(
266265
}
267266
}
268267

269-
export function makeRequest<T>(
270-
requestInfo: RequestInfo<T>,
268+
export function makeRequest<I extends ConnectionType, O>(
269+
requestInfo: RequestInfo<I, O>,
271270
appId: string | null,
272271
authToken: string | null,
273272
appCheckToken: string | null,
274-
requestFactory: () => Connection,
273+
requestFactory: () => Connection<I>,
275274
firebaseVersion?: string
276-
): Request<T> {
275+
): Request<O> {
277276
const queryPart = makeQueryString(requestInfo.urlParams);
278277
const url = requestInfo.url + queryPart;
279278
const headers = Object.assign({}, requestInfo.headers);
280279
addGmpidHeader_(headers, appId);
281280
addAuthHeader_(headers, authToken);
282281
addVersionHeader_(headers, firebaseVersion);
283282
addAppCheckHeader_(headers, appCheckToken);
284-
return new NetworkRequest<T>(
283+
return new NetworkRequest<I, O>(
285284
url,
286285
requestInfo.method,
287286
headers,

0 commit comments

Comments
 (0)