-
Notifications
You must be signed in to change notification settings - Fork 24.2k
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
[BREAKING on iOS] Add blob implementation with WebSocket integration #11417
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/** | ||
* 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 Blob | ||
* @flow | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const uuid = require('uuid'); | ||
const invariant = require('fbjs/lib/invariant'); | ||
const { BlobModule } = require('NativeModules'); | ||
|
||
import type { BlobProps } from './BlobTypes'; | ||
|
||
/** | ||
* Opaque JS representation of some binary data in native. | ||
* | ||
* The API is modeled after the W3C Blob API, with one caveat | ||
* regarding explicit deallocation. Refer to the `close()` | ||
* method for further details. | ||
* | ||
* Example usage in a React component: | ||
* | ||
* class WebSocketImage extends React.Component { | ||
* state = {blob: null}; | ||
* componentDidMount() { | ||
* let ws = this.ws = new WebSocket(...); | ||
* ws.binaryType = 'blob'; | ||
* ws.onmessage = (event) => { | ||
* if (this.state.blob) { | ||
* this.state.blob.close(); | ||
* } | ||
* this.setState({blob: event.data}); | ||
* }; | ||
* } | ||
* componentUnmount() { | ||
* if (this.state.blob) { | ||
* this.state.blob.close(); | ||
* } | ||
* this.ws.close(); | ||
* } | ||
* render() { | ||
* if (!this.state.blob) { | ||
* return <View />; | ||
* } | ||
* return <Image source={{uri: URL.createObjectURL(this.state.blob)}} />; | ||
* } | ||
* } | ||
* | ||
* 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); | ||
} | ||
|
||
/** | ||
* 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) { | ||
let blobId = uuid.v4(); | ||
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, | ||
}); | ||
} | ||
|
||
/* | ||
* 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 | ||
*/ | ||
slice(start?: number, end?: number): Blob { | ||
let offset = this.offset; | ||
let size = this.size; | ||
if (typeof start === 'number') { | ||
if (start > size) { | ||
start = size; | ||
} | ||
offset += start; | ||
size -= start; | ||
|
||
if (typeof end === 'number') { | ||
if (end < 0) { | ||
end = this.size + end; | ||
} | ||
size = end - start; | ||
} | ||
} | ||
return Blob.create({ | ||
blobId: this.blobId, | ||
offset, | ||
size, | ||
}); | ||
} | ||
|
||
/** | ||
* This method is in the standard, but not actually implemented by | ||
* any browsers at this point. It's important for how Blobs work in | ||
* React Native, however, since we cannot de-allocate resources automatically, | ||
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. You could hook into JSC's finalizer callback to clean this up, but we currently don't have a good cross-platform approach to creating custom JS objects. It also would be an issue with the Websocket executor. |
||
* so consumers need to explicitly de-allocate them. | ||
* | ||
* Note that the semantics around Blobs created via `blob.slice()` | ||
* and `new Blob([blob])` are different. `blob.slice()` creates a | ||
* new *view* onto the same binary data, so calling `close()` on any | ||
* of those views is enough to deallocate the data, whereas | ||
* `new Blob([blob, ...])` actually copies the data in memory. | ||
*/ | ||
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. Good comment! |
||
close() { | ||
BlobModule.release(this.blobId); | ||
} | ||
} | ||
|
||
module.exports = Blob; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* 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. | ||
* | ||
* @flow | ||
*/ | ||
|
||
export type BlobProps = { | ||
blobId: string; | ||
offset: number; | ||
size: number; | ||
type?: string; | ||
}; | ||
|
||
export type FileProps = BlobProps & { | ||
name: string; | ||
lastModified: number; | ||
}; |
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.
Would it make sense to throw if it's not a number?
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.
Don't remember if it throws on web. Will have to check and ensure we match the behaviour.
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.
Not throwing here because browsers don't throw in case of invalid type.