Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 25.2k
Implement Blob support for XMLHttpRequest#11573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
642d451be07fa9e8ea388553f20eaaba70522ebafb781e9e6109a4652746628a60605dd4b62b9600217ab1614c18ad6369968a7b860f8ebbf56c66b7c0d8b2d62da3039feed91a6060a8880eb999306498b6fd3fd0efa54c2fa7ff5c2dd2c5fc2acbe644c2197988018564ec871b69ed1479086d9d3295ccf580e6159d5b0dbe167cc9a425ed2b250f92899035a37b6adc5318e70fb7e37a02691aa1f848e98f9545d014a21ff09bcd6ede7b53698975b4f1ed3a36ae03165ca3a28ad1a977635ebe159e1de53b1ec88828fb3496ac243471368b86e20cdd7e84953bfddcbc644c7a928da1554028d150000408ab236ed3f35e0c1ce2481c45d215ce5494fe9ad568cf2f15886d56239bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -8,19 +8,12 @@ | ||
| * | ||
| * @providesModule Blob | ||
| * @flow | ||
| * @format | ||
| */ | ||
| 'use strict'; | ||
| const invariant = require('fbjs/lib/invariant'); | ||
| /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error | ||
| * found when Flow v0.54 was deployed. To see the error delete this comment and | ||
| * run Flow. */ | ||
| const uuid = require('uuid'); | ||
| const { BlobModule } = require('NativeModules'); | ||
| import type { BlobProps } from 'BlobTypes'; | ||
| import type {BlobData, BlobOptions} from 'BlobTypes'; | ||
| /** | ||
| * Opaque JS representation of some binary data in native. | ||
| @@ -60,61 +53,39 @@ import type { BlobProps } from 'BlobTypes'; | ||
| * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob | ||
| */ | ||
| class Blob { | ||
| /** | ||
| * Size of the data contained in the Blob object, in bytes. | ||
| */ | ||
| size: number; | ||
| /* | ||
| * String indicating the MIME type of the data contained in the Blob. | ||
| * If the type is unknown, this string is empty. | ||
| */ | ||
| type: string; | ||
| /* | ||
| * Unique id to identify the blob on native side (non-standard) | ||
| */ | ||
| blobId: string; | ||
| /* | ||
| * Offset to indicate part of blob, used when sliced (non-standard) | ||
| */ | ||
| offset: number; | ||
| /** | ||
| * Construct blob instance from blob data from native. | ||
| * Used internally by modules like XHR, WebSocket, etc. | ||
| */ | ||
| static create(props: BlobProps): Blob { | ||
| return Object.assign(Object.create(Blob.prototype), props); | ||
| } | ||
| _data: ?BlobData; | ||
| /** | ||
| * Constructor for JS consumers. | ||
| * Currently we only support creating Blobs from other Blobs. | ||
| * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob | ||
| */ | ||
| constructor(parts: Array<Blob>, options: any) { | ||
| const blobId = uuid(); | ||
| let size = 0; | ||
| parts.forEach((part) => { | ||
| invariant(part instanceof Blob, 'Can currently only create a Blob from other Blobs'); | ||
| size += part.size; | ||
| }); | ||
| BlobModule.createFromParts(parts, blobId); | ||
| return Blob.create({ | ||
| blobId, | ||
| offset: 0, | ||
| size, | ||
| }); | ||
| constructor(parts: Array<Blob | string> = [], options?: BlobOptions) { | ||
| const BlobManager = require('BlobManager'); | ||
| this.data = BlobManager.createFromParts(parts, options).data; | ||
| } | ||
| /* | ||
| * This method is used to create a new Blob object containing | ||
| * the data in the specified range of bytes of the source Blob. | ||
| * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice | ||
| */ | ||
| set data(data: ?BlobData) { | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any reason for making Also, these methods cause the docblock for ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO doesn't make sense to expose both | ||
| this._data = data; | ||
| } | ||
| get data(): BlobData { | ||
| if (!this._data) { | ||
| throw new Error('Blob has been closed and is no longer available'); | ||
| } | ||
| return this._data; | ||
| } | ||
| slice(start?: number, end?: number): Blob { | ||
| let offset = this.offset; | ||
| let size = this.size; | ||
| const BlobManager = require('BlobManager'); | ||
| let {offset, size} = this.data; | ||
| if (typeof start === 'number') { | ||
| if (start > size) { | ||
| start = size; | ||
| @@ -129,8 +100,8 @@ class Blob { | ||
| size = end - start; | ||
| } | ||
| } | ||
| return Blob.create({ | ||
| blobId: this.blobId, | ||
| return BlobManager.createFromOptions({ | ||
| blobId: this.data.blobId, | ||
| offset, | ||
| size, | ||
| }); | ||
| @@ -149,7 +120,24 @@ class Blob { | ||
| * `new Blob([blob, ...])` actually copies the data in memory. | ||
| */ | ||
| close() { | ||
| BlobModule.release(this.blobId); | ||
| const BlobManager = require('BlobManager'); | ||
| BlobManager.release(this.data.blobId); | ||
| this.data = null; | ||
| } | ||
| /** | ||
| * Size of the data contained in the Blob object, in bytes. | ||
| */ | ||
| get size(): number { | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we remove the ability to mutate a ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are read-only properties according to the | ||
| return this.data.size; | ||
| } | ||
| /* | ||
| * String indicating the MIME type of the data contained in the Blob. | ||
| * If the type is unknown, this string is empty. | ||
| */ | ||
| get type(): string { | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. | ||
| return this.data.type || ''; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| /** | ||
| * Copyright (c) 2013-present, Facebook, Inc. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. An additional grant | ||
| * of patent rights can be found in the PATENTS file in the same directory. | ||
| * | ||
| * @providesModule BlobManager | ||
| * @flow | ||
| * @format | ||
| */ | ||
| 'use strict'; | ||
| const uuid = require('uuid/v4'); | ||
| const Blob = require('Blob'); | ||
| const BlobRegistry = require('BlobRegistry'); | ||
| const {BlobModule} = require('NativeModules'); | ||
| import type {BlobData, BlobOptions} from 'BlobTypes'; | ||
| /** | ||
| * Module to manage blobs. Wrapper around the native blob module. | ||
| */ | ||
| class BlobManager { | ||
| /** | ||
| * If the native blob module is available. | ||
| */ | ||
| static isAvailable = !!BlobModule; | ||
| /** | ||
| * Create blob from existing array of blobs. | ||
| */ | ||
| static createFromParts( | ||
| parts: Array<Blob | string>, | ||
| options?: BlobOptions, | ||
| ): Blob { | ||
| const blobId = uuid(); | ||
| const items = parts.map(part => { | ||
| if ( | ||
| part instanceof ArrayBuffer || | ||
| (global.ArrayBufferView && part instanceof global.ArrayBufferView) | ||
| ) { | ||
| throw new Error( | ||
| "Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported", | ||
| ); | ||
| } | ||
| if (part instanceof Blob) { | ||
| return { | ||
| data: part.data, | ||
| type: 'blob', | ||
| }; | ||
| } else { | ||
| return { | ||
| data: String(part), | ||
| type: 'string', | ||
| }; | ||
| } | ||
| }); | ||
| const size = items.reduce((acc, curr) => { | ||
| if (curr.type === 'string') { | ||
| return acc + global.unescape(encodeURI(curr.data)).length; | ||
| } else { | ||
| return acc + curr.data.size; | ||
| } | ||
| }, 0); | ||
| BlobModule.createFromParts(items, blobId); | ||
| return BlobManager.createFromOptions({ | ||
| blobId, | ||
| offset: 0, | ||
| size, | ||
| type: options ? options.type : '', | ||
| lastModified: options ? options.lastModified : Date.now(), | ||
| }); | ||
| } | ||
| /** | ||
| * Create blob instance from blob data from native. | ||
| * Used internally by modules like XHR, WebSocket, etc. | ||
| */ | ||
| static createFromOptions(options: BlobData): Blob { | ||
| BlobRegistry.register(options.blobId); | ||
| return Object.assign(Object.create(Blob.prototype), {data: options}); | ||
| } | ||
| /** | ||
| * Deallocate resources for a blob. | ||
| */ | ||
| static release(blobId: string): void { | ||
| BlobRegistry.unregister(blobId); | ||
| if (BlobRegistry.has(blobId)) { | ||
| return; | ||
| } | ||
| BlobModule.release(blobId); | ||
| } | ||
| /** | ||
| * Inject the blob content handler in the networking module to support blob | ||
| * requests and responses. | ||
| */ | ||
| static addNetworkingHandler(): void { | ||
| BlobModule.addNetworkingHandler(); | ||
| } | ||
| /** | ||
| * Indicate the websocket should return a blob for incoming binary | ||
| * messages. | ||
| */ | ||
| static addWebSocketHandler(socketId: number): void { | ||
| BlobModule.addWebSocketHandler(socketId); | ||
| } | ||
| /** | ||
| * Indicate the websocket should no longer return a blob for incoming | ||
| * binary messages. | ||
| */ | ||
| static removeWebSocketHandler(socketId: number): void { | ||
| BlobModule.removeWebSocketHandler(socketId); | ||
| } | ||
| /** | ||
| * Send a blob message to a websocket. | ||
| */ | ||
| static sendOverSocket(blob: Blob, socketId: number): void { | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. Did you consider making | ||
| BlobModule.sendOverSocket(blob.data, socketId); | ||
| } | ||
| } | ||
| module.exports = BlobManager; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /** | ||
| * Copyright (c) 2013-present, Facebook, Inc. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. An additional grant | ||
| * of patent rights can be found in the PATENTS file in the same directory. | ||
| * | ||
| * @providesModule BlobRegistry | ||
| * @flow | ||
| * @format | ||
| */ | ||
| const registry: {[key: string]: number} = {}; | ||
| const register = (id: string) => { | ||
| if (registry[id]) { | ||
| registry[id]++; | ||
| } else { | ||
| registry[id] = 1; | ||
| } | ||
| }; | ||
| const unregister = (id: string) => { | ||
| if (registry[id]) { | ||
| registry[id]--; | ||
| if (registry[id] <= 0) { | ||
| delete registry[id]; | ||
| } | ||
| } | ||
| }; | ||
| const has = (id: string) => { | ||
| return registry[id] && registry[id] > 0; | ||
| }; | ||
| module.exports = { | ||
| register, | ||
| unregister, | ||
| has, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /** | ||
| * Copyright (c) 2013-present, Facebook, Inc. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. An additional grant | ||
| * of patent rights can be found in the PATENTS file in the same directory. | ||
| * | ||
| * @providesModule File | ||
| * @flow | ||
| * @format | ||
| */ | ||
| 'use strict'; | ||
| const Blob = require('Blob'); | ||
| const invariant = require('fbjs/lib/invariant'); | ||
| import type {BlobOptions} from 'BlobTypes'; | ||
| /** | ||
| * The File interface provides information about files. | ||
| */ | ||
| class File extends Blob { | ||
| /** | ||
| * Constructor for JS consumers. | ||
| */ | ||
| constructor( | ||
| parts: Array<Blob | string>, | ||
| name: string, | ||
| options?: BlobOptions, | ||
| ) { | ||
| invariant( | ||
| parts != null && name != null, | ||
| 'Failed to construct `File`: Must pass both `parts` and `name` arguments.', | ||
| ); | ||
| super(parts, options); | ||
| this.data.name = name; | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mutating | ||
| } | ||
| /** | ||
| * Name of the file. | ||
| */ | ||
| get name(): string { | ||
| invariant(this.data.name != null, 'Files must have a name set.'); | ||
| return this.data.name; | ||
| } | ||
| /* | ||
| * Last modified time of the file. | ||
| */ | ||
| get lastModified(): number { | ||
| return this.data.lastModified || 0; | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we set ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Kureev native side needs to pass this info when it's possible, but we can probably set it to current time from native if we cannot get the time. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on what I see as an outcome of ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto, this should just be an invariant that | ||
| } | ||
| } | ||
| module.exports = File; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To follow conventions of the repository (especially since initializing
BlobManagerdoes not have side effects), can you hoist these inlined require statements into the module scope?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not possible due to circular dependency between the
BlobandBlobManagermodules