Skip to content

Commit be56a3e

Browse files
satya164facebook-github-bot
authored andcommitted
Implement Blob support for XMLHttpRequest
Summary: This PR is a followup to #11417 and should be merged after that one is merged. 1. Add support for creating blobs from strings, not just other blobs 1. Add the `File` constructor which is a superset of `Blob` 1. Add the `FileReader` API which can be used to read blobs as strings or data url (base64) 1. Add support for uploading and downloading blobs via `XMLHttpRequest` and `fetch` 1. Add ability to download local files on Android so you can do `fetch(uri).then(res => res.blob())` to get a blob for a local file (iOS already supported this) 1. Clone the repo https://github.com/expo/react-native-blob-test 1. Change the `package.json` and update `react-native` dependency to point to this branch, then run `npm install` 1. Run the `server.js` file with `node server.js` 1. Open the `index.common.js` file and replace `localhost` with your computer's IP address 1. Start the packager with `yarn start` and run the app on your device If everything went well, all tests should pass, and you should see a screen like this: ![screen shot 2017-06-08 at 7 53 08 pm](https://user-images.githubusercontent.com/1174278/26936407-435bbce2-4c8c-11e7-9ae3-eb104e46961e.png)! Pull to rerun all tests or tap on specific test to re-run it [GENERAL] [FEATURE] [Blob] - Implement blob support for XMLHttpRequest Closes #11573 Reviewed By: shergin Differential Revision: D6082054 Pulled By: hramos fbshipit-source-id: cc9c174fdefdfaf6e5d9fd7b300120a01a50e8c1
1 parent 3fc33bb commit be56a3e

40 files changed

+2056
-382
lines changed

Libraries/Blob/Blob.js

+41-53
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,12 @@
88
*
99
* @providesModule Blob
1010
* @flow
11+
* @format
1112
*/
1213

1314
'use strict';
1415

15-
const invariant = require('fbjs/lib/invariant');
16-
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
17-
* found when Flow v0.54 was deployed. To see the error delete this comment and
18-
* run Flow. */
19-
const uuid = require('uuid');
20-
21-
const { BlobModule } = require('NativeModules');
22-
23-
import type { BlobProps } from 'BlobTypes';
16+
import type {BlobData, BlobOptions} from 'BlobTypes';
2417

2518
/**
2619
* Opaque JS representation of some binary data in native.
@@ -60,61 +53,39 @@ import type { BlobProps } from 'BlobTypes';
6053
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
6154
*/
6255
class Blob {
63-
/**
64-
* Size of the data contained in the Blob object, in bytes.
65-
*/
66-
size: number;
67-
/*
68-
* String indicating the MIME type of the data contained in the Blob.
69-
* If the type is unknown, this string is empty.
70-
*/
71-
type: string;
72-
73-
/*
74-
* Unique id to identify the blob on native side (non-standard)
75-
*/
76-
blobId: string;
77-
/*
78-
* Offset to indicate part of blob, used when sliced (non-standard)
79-
*/
80-
offset: number;
81-
82-
/**
83-
* Construct blob instance from blob data from native.
84-
* Used internally by modules like XHR, WebSocket, etc.
85-
*/
86-
static create(props: BlobProps): Blob {
87-
return Object.assign(Object.create(Blob.prototype), props);
88-
}
56+
_data: ?BlobData;
8957

9058
/**
9159
* Constructor for JS consumers.
9260
* Currently we only support creating Blobs from other Blobs.
9361
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
9462
*/
95-
constructor(parts: Array<Blob>, options: any) {
96-
const blobId = uuid();
97-
let size = 0;
98-
parts.forEach((part) => {
99-
invariant(part instanceof Blob, 'Can currently only create a Blob from other Blobs');
100-
size += part.size;
101-
});
102-
BlobModule.createFromParts(parts, blobId);
103-
return Blob.create({
104-
blobId,
105-
offset: 0,
106-
size,
107-
});
63+
constructor(parts: Array<Blob | string> = [], options?: BlobOptions) {
64+
const BlobManager = require('BlobManager');
65+
this.data = BlobManager.createFromParts(parts, options).data;
10866
}
10967

11068
/*
11169
* This method is used to create a new Blob object containing
11270
* the data in the specified range of bytes of the source Blob.
11371
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
11472
*/
73+
set data(data: ?BlobData) {
74+
this._data = data;
75+
}
76+
77+
get data(): BlobData {
78+
if (!this._data) {
79+
throw new Error('Blob has been closed and is no longer available');
80+
}
81+
82+
return this._data;
83+
}
84+
11585
slice(start?: number, end?: number): Blob {
116-
let offset = this.offset;
117-
let size = this.size;
86+
const BlobManager = require('BlobManager');
87+
let {offset, size} = this.data;
88+
11889
if (typeof start === 'number') {
11990
if (start > size) {
12091
start = size;
@@ -129,8 +100,8 @@ class Blob {
129100
size = end - start;
130101
}
131102
}
132-
return Blob.create({
133-
blobId: this.blobId,
103+
return BlobManager.createFromOptions({
104+
blobId: this.data.blobId,
134105
offset,
135106
size,
136107
});
@@ -149,7 +120,24 @@ class Blob {
149120
* `new Blob([blob, ...])` actually copies the data in memory.
150121
*/
151122
close() {
152-
BlobModule.release(this.blobId);
123+
const BlobManager = require('BlobManager');
124+
BlobManager.release(this.data.blobId);
125+
this.data = null;
126+
}
127+
128+
/**
129+
* Size of the data contained in the Blob object, in bytes.
130+
*/
131+
get size(): number {
132+
return this.data.size;
133+
}
134+
135+
/*
136+
* String indicating the MIME type of the data contained in the Blob.
137+
* If the type is unknown, this string is empty.
138+
*/
139+
get type(): string {
140+
return this.data.type || '';
153141
}
154142
}
155143

Libraries/Blob/BlobManager.js

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule BlobManager
10+
* @flow
11+
* @format
12+
*/
13+
14+
'use strict';
15+
16+
const Blob = require('Blob');
17+
const BlobRegistry = require('BlobRegistry');
18+
const {BlobModule} = require('NativeModules');
19+
20+
import type {BlobData, BlobOptions} from 'BlobTypes';
21+
22+
/*eslint-disable no-bitwise */
23+
/*eslint-disable eqeqeq */
24+
25+
/**
26+
* Based on the rfc4122-compliant solution posted at
27+
* http://stackoverflow.com/questions/105034
28+
*/
29+
function uuidv4(): string {
30+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
31+
const r = (Math.random() * 16) | 0,
32+
v = c == 'x' ? r : (r & 0x3) | 0x8;
33+
return v.toString(16);
34+
});
35+
}
36+
37+
/**
38+
* Module to manage blobs. Wrapper around the native blob module.
39+
*/
40+
class BlobManager {
41+
/**
42+
* If the native blob module is available.
43+
*/
44+
static isAvailable = !!BlobModule;
45+
46+
/**
47+
* Create blob from existing array of blobs.
48+
*/
49+
static createFromParts(
50+
parts: Array<Blob | string>,
51+
options?: BlobOptions,
52+
): Blob {
53+
const blobId = uuidv4();
54+
const items = parts.map(part => {
55+
if (
56+
part instanceof ArrayBuffer ||
57+
(global.ArrayBufferView && part instanceof global.ArrayBufferView)
58+
) {
59+
throw new Error(
60+
"Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported",
61+
);
62+
}
63+
if (part instanceof Blob) {
64+
return {
65+
data: part.data,
66+
type: 'blob',
67+
};
68+
} else {
69+
return {
70+
data: String(part),
71+
type: 'string',
72+
};
73+
}
74+
});
75+
const size = items.reduce((acc, curr) => {
76+
if (curr.type === 'string') {
77+
return acc + global.unescape(encodeURI(curr.data)).length;
78+
} else {
79+
return acc + curr.data.size;
80+
}
81+
}, 0);
82+
83+
BlobModule.createFromParts(items, blobId);
84+
85+
return BlobManager.createFromOptions({
86+
blobId,
87+
offset: 0,
88+
size,
89+
type: options ? options.type : '',
90+
lastModified: options ? options.lastModified : Date.now(),
91+
});
92+
}
93+
94+
/**
95+
* Create blob instance from blob data from native.
96+
* Used internally by modules like XHR, WebSocket, etc.
97+
*/
98+
static createFromOptions(options: BlobData): Blob {
99+
BlobRegistry.register(options.blobId);
100+
return Object.assign(Object.create(Blob.prototype), {data: options});
101+
}
102+
103+
/**
104+
* Deallocate resources for a blob.
105+
*/
106+
static release(blobId: string): void {
107+
BlobRegistry.unregister(blobId);
108+
if (BlobRegistry.has(blobId)) {
109+
return;
110+
}
111+
BlobModule.release(blobId);
112+
}
113+
114+
/**
115+
* Inject the blob content handler in the networking module to support blob
116+
* requests and responses.
117+
*/
118+
static addNetworkingHandler(): void {
119+
BlobModule.addNetworkingHandler();
120+
}
121+
122+
/**
123+
* Indicate the websocket should return a blob for incoming binary
124+
* messages.
125+
*/
126+
static addWebSocketHandler(socketId: number): void {
127+
BlobModule.addWebSocketHandler(socketId);
128+
}
129+
130+
/**
131+
* Indicate the websocket should no longer return a blob for incoming
132+
* binary messages.
133+
*/
134+
static removeWebSocketHandler(socketId: number): void {
135+
BlobModule.removeWebSocketHandler(socketId);
136+
}
137+
138+
/**
139+
* Send a blob message to a websocket.
140+
*/
141+
static sendOverSocket(blob: Blob, socketId: number): void {
142+
BlobModule.sendOverSocket(blob.data, socketId);
143+
}
144+
}
145+
146+
module.exports = BlobManager;

Libraries/Blob/BlobRegistry.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule BlobRegistry
10+
* @flow
11+
* @format
12+
*/
13+
14+
const registry: {[key: string]: number} = {};
15+
16+
const register = (id: string) => {
17+
if (registry[id]) {
18+
registry[id]++;
19+
} else {
20+
registry[id] = 1;
21+
}
22+
};
23+
24+
const unregister = (id: string) => {
25+
if (registry[id]) {
26+
registry[id]--;
27+
if (registry[id] <= 0) {
28+
delete registry[id];
29+
}
30+
}
31+
};
32+
33+
const has = (id: string) => {
34+
return registry[id] && registry[id] > 0;
35+
};
36+
37+
module.exports = {
38+
register,
39+
unregister,
40+
has,
41+
};

Libraries/Blob/BlobTypes.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@
88
*
99
* @providesModule BlobTypes
1010
* @flow
11+
* @format
1112
*/
1213

1314
'use strict';
1415

15-
export type BlobProps = {
16+
export type BlobData = {
1617
blobId: string,
1718
offset: number,
1819
size: number,
20+
name?: string,
1921
type?: string,
22+
lastModified?: number,
2023
};
2124

22-
export type FileProps = BlobProps & {
23-
name: string,
25+
export type BlobOptions = {
26+
type: string,
2427
lastModified: number,
2528
};

0 commit comments

Comments
 (0)