Skip to content

Commit 261f5d1

Browse files
Add getBytes() to Storage v9
1 parent 064e1ca commit 261f5d1

File tree

11 files changed

+136
-25
lines changed

11 files changed

+136
-25
lines changed

packages/storage/exp/api.ts

+15-1
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 '../src/reference';
5253
import { STORAGE_TYPE } from './constants';
5354
import { getModularInstance } from '@firebase/util';
@@ -62,6 +63,19 @@ export { UploadTask as _UploadTask } from '../src/task';
6263
export type { Reference as _Reference } from '../src/reference';
6364
export { FbsBlob as _FbsBlob } from '../src/implementation/blob';
6465

66+
/**
67+
* Download's the data at the object's location. Returns an error if the object
68+
* is not found.
69+
*
70+
* @public
71+
* @param ref - StorageReference where data should be download.
72+
* @returns A Promise containing the object's bytes
73+
*/
74+
export function getBytes(ref: StorageReference): Promise<ArrayBuffer> {
75+
ref = getModularInstance(ref);
76+
return getBytesInternal(ref as Reference);
77+
}
78+
6579
/**
6680
* Uploads data to this object's location.
6781
* The upload is not resumable.

packages/storage/src/implementation/connection.ts

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export interface Connection {
4040

4141
getResponseText(): string;
4242

43+
getResponseData(): ArrayBuffer;
44+
4345
/**
4446
* Abort the request.
4547
*/

packages/storage/src/implementation/request.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
canceled,
2929
retryLimitExceeded
3030
} from './error';
31-
import { RequestInfo } from './requestinfo';
31+
import {RequestHandler, RequestInfo} from './requestinfo';
3232
import { isJustDef } from './type';
3333
import { makeQueryString } from './url';
3434
import { Headers, Connection, ErrorCode } from './connection';
@@ -61,7 +61,7 @@ class NetworkRequest<T> implements Request<T> {
6161
private reject_!: (reason?: any) => void;
6262
private canceled_: boolean = false;
6363
private appDelete_: boolean = false;
64-
private callback_: (p1: Connection, p2: string) => T;
64+
private callback_: RequestHandler<T>;
6565
private errorCallback_:
6666
| ((p1: Connection, p2: FirebaseStorageError) => FirebaseStorageError)
6767
| null;
@@ -77,7 +77,7 @@ class NetworkRequest<T> implements Request<T> {
7777
body: string | Blob | Uint8Array | null,
7878
successCodes: number[],
7979
additionalRetryCodes: number[],
80-
callback: (p1: Connection, p2: string) => T,
80+
callback: RequestHandler<T>,
8181
errorCallback:
8282
| ((p1: Connection, p2: FirebaseStorageError) => FirebaseStorageError)
8383
| null,
@@ -169,7 +169,8 @@ class NetworkRequest<T> implements Request<T> {
169169
try {
170170
const result = self.callback_(
171171
connection,
172-
connection.getResponseText()
172+
connection.getResponseText(),
173+
connection.getResponseData()
173174
);
174175
if (isJustDef(result)) {
175176
resolve(result);

packages/storage/src/implementation/requestinfo.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export interface UrlParams {
2424
[name: string]: string | number;
2525
}
2626

27+
export type RequestHandler<T> = (connection: Connection, responseText: string, responseData:ArrayBuffer)=>T;
28+
2729
export class RequestInfo<T> {
2830
urlParams: UrlParams = {};
2931
headers: Headers = {};
@@ -51,7 +53,7 @@ export class RequestInfo<T> {
5153
* Note: The XhrIo passed to this function may be reused after this callback
5254
* returns. Do not keep a reference to it in any way.
5355
*/
54-
public handler: (p1: Connection, p2: string) => T,
55-
public timeout: number
56+
public handler: RequestHandler<T>,
57+
public timeout: number,
5658
) {}
5759
}

packages/storage/src/implementation/requests.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
toResourceString
4242
} from './metadata';
4343
import { fromResponseString } from './list';
44-
import { RequestInfo, UrlParams } from './requestinfo';
44+
import {RequestHandler, RequestInfo, UrlParams} from './requestinfo';
4545
import { isString } from './type';
4646
import { makeUrl } from './url';
4747
import { Connection } from './connection';
@@ -206,6 +206,28 @@ export function list(
206206
return requestInfo;
207207
}
208208

209+
export function getBytes(
210+
service: FirebaseStorageImpl,
211+
location: Location
212+
): RequestInfo<ArrayBuffer> {
213+
const urlPart = location.fullServerUrl();
214+
const url = makeUrl(urlPart, service.host) + '?alt=media';
215+
const method = 'GET';
216+
const timeout = service.maxOperationRetryTime;
217+
const requestInfo = new RequestInfo(
218+
url,
219+
method,
220+
getBytesHandler(),
221+
timeout
222+
);
223+
requestInfo.errorHandler = objectErrorHandler(location);
224+
return requestInfo;
225+
}
226+
227+
export function getBytesHandler(): RequestHandler<ArrayBuffer> {
228+
return (xhr: Connection,_: string, data: ArrayBuffer) => data;
229+
}
230+
209231
export function getDownloadUrl(
210232
service: FirebaseStorageImpl,
211233
location: Location,

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

+10
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ export class XhrConnection implements Connection {
113113
return this.xhr_.responseText;
114114
}
115115

116+
/**
117+
* @override
118+
*/
119+
getResponseData(): ArrayBuffer {
120+
if (!this.sent_) {
121+
throw internalError('cannot .getResponseData() before sending');
122+
}
123+
return this.xhr_.response;
124+
}
125+
116126
/**
117127
* Aborts the request.
118128
* @override

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const fetch: typeof window.fetch = nodeFetch as any;
3131
export class FetchConnection implements Connection {
3232
private errorCode_: ErrorCode;
3333
private statusCode_: number | undefined;
34-
private body_: string | undefined;
34+
private body_: ArrayBuffer | undefined;
3535
private headers_: Headers | undefined;
3636
private sent_: boolean = false;
3737

@@ -58,7 +58,7 @@ export class FetchConnection implements Connection {
5858
.then(resp => {
5959
this.headers_ = resp.headers;
6060
this.statusCode_ = resp.status;
61-
return resp.text();
61+
return resp.arrayBuffer();
6262
})
6363
.then(body => {
6464
this.body_ = body;
@@ -85,6 +85,15 @@ export class FetchConnection implements Connection {
8585
'cannot .getResponseText() before receiving response'
8686
);
8787
}
88+
return new TextDecoder().decode(this.body_);
89+
}
90+
91+
getResponseData(): ArrayBuffer {
92+
if (this.body_ === undefined) {
93+
throw internalError(
94+
'cannot .getResponseData() before receiving response'
95+
);
96+
}
8897
return this.body_;
8998
}
9099

packages/storage/src/reference.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import {
2929
updateMetadata as requestsUpdateMetadata,
3030
getDownloadUrl as requestsGetDownloadUrl,
3131
deleteObject as requestsDeleteObject,
32-
multipartUpload
32+
multipartUpload,
33+
getBytes
3334
} from './implementation/requests';
3435
import { ListOptions } from '../exp/public-types';
3536
import { StringFormat, dataFromString } from './implementation/string';
@@ -143,6 +144,18 @@ export class Reference {
143144
}
144145
}
145146

147+
/**
148+
* Download the bytes at the object's location.
149+
* @returns A Promise containing the downloaded bytes.
150+
*/
151+
export function getBytesInternal(ref: Reference): Promise<ArrayBuffer> {
152+
ref._throwIfRoot('getBytes');
153+
const requestInfo = getBytes(ref.storage, ref._location);
154+
return ref.storage
155+
.makeRequestWithTokens(requestInfo)
156+
.then(request => request.getPromise());
157+
}
158+
146159
/**
147160
* Uploads data to this object's location.
148161
* The upload is not resumable.

packages/storage/test/integration/integration.exp.test.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import {
2929
deleteObject,
3030
getMetadata,
3131
updateMetadata,
32-
listAll
32+
listAll,
33+
getBytes
3334
} from '../../exp/index';
3435

3536
import { use, expect } from 'chai';
@@ -71,6 +72,27 @@ describe('FirebaseStorage Exp', () => {
7172
expect(snap.metadata.timeCreated).to.exist;
7273
});
7374

75+
it('can get bytes', async () => {
76+
const reference = ref(storage, 'public/exp-bytes');
77+
await uploadBytes(reference, new Uint8Array([0, 1, 3, 128, 255]));
78+
const bytes = await getBytes(reference);
79+
expect(new Uint8Array(bytes)).to.deep.equal(new Uint8Array([0, 1, 3, 128, 255]));
80+
});
81+
82+
it('getBytes() throws for missing file', async () => {
83+
const reference = ref(storage, 'public/exp-bytes-missing');
84+
try {
85+
await getBytes(reference, );
86+
expect.fail();
87+
} catch (e) {
88+
expect(e.message).to.satisfy((v: string) =>
89+
v.match(
90+
/Object 'public\/exp-bytes-missing' does not exist/
91+
)
92+
);
93+
}
94+
});
95+
7496
it('can upload bytes (resumable)', async () => {
7597
const reference = ref(storage, 'public/exp-bytesresumable');
7698
const snap = await uploadBytesResumable(

packages/storage/test/unit/connection.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export class TestingConnection implements Connection {
4545
private sendHook: SendHook | null;
4646
private status: number;
4747
private responseText: string;
48+
private responseData: ArrayBuffer;
4849
private headers: Headers;
4950
private errorCode: ErrorCode;
5051

@@ -56,6 +57,7 @@ export class TestingConnection implements Connection {
5657
this.sendHook = sendHook;
5758
this.status = -1;
5859
this.responseText = '';
60+
this.responseData = new Uint8Array();
5961
this.headers = {};
6062
this.errorCode = ErrorCode.NO_ERROR;
6163
}
@@ -83,7 +85,7 @@ export class TestingConnection implements Connection {
8385

8486
simulateResponse(
8587
status: number,
86-
body: string,
88+
body: string|Uint8Array,
8789
headers: { [key: string]: string }
8890
): void {
8991
if (this.state !== State.SENT) {
@@ -94,7 +96,8 @@ export class TestingConnection implements Connection {
9496
}
9597

9698
this.status = status;
97-
this.responseText = body;
99+
this.responseText = typeof body === 'string'?body:'';
100+
this.responseData = typeof body === 'string'?new Uint8Array():body;
98101
this.headers = {};
99102
for (const [key, value] of Object.entries(headers)) {
100103
this.headers[key.toLowerCase()] = value.toString();
@@ -117,6 +120,10 @@ export class TestingConnection implements Connection {
117120
return this.responseText;
118121
}
119122

123+
getResponseData(): ArrayBuffer {
124+
return this.responseData;
125+
}
126+
120127
abort(): void {
121128
this.state = State.START;
122129
this.errorCode = ErrorCode.NO_ERROR;

0 commit comments

Comments
 (0)