Add blob implementation with WebSocket integration

Summary:
This is the first PR from a series of PRs grabbou and me will make to add blob support to React Native. The next PR will include blob support for XMLHttpRequest.

I'd like to get this merged with minimal changes to preserve the attribution. My next PR can contain bigger changes.

Blobs are used to transfer binary data between server and client. Currently React Native lacks a way to deal with binary data. The only thing that comes close is uploading files through a URI.

Current workarounds to transfer binary data includes encoding and decoding them to base64 and and transferring them as string, which is not ideal, since it increases the payload size and the whole payload needs to be sent via the bridge every time changes are made.

The PR adds a way to deal with blobs via a new native module. The blob is constructed on the native side and the data never needs to pass through the bridge. Currently the only way to create a blob is to receive a blob from the server via websocket.

The PR is largely a direct port of https://github.com/silklabs/silk/tree/master/react-native-blobs by philikon into RN (with changes to integrate with RN), and attributed as such.

> **Note:** This is a breaking change for all people running iOS without CocoaPods. You will have to manually add `RCTBlob.xcodeproj` to your `Libraries` and then, add it to Build Phases. Just follow the process of manual linking. We'll also need to document this process in the release notes.

Related discussion - https://github.com/facebook/react-native/issues/11103

- `Image` can't show image when `URL.createObjectURL` is used with large images on Android

The websocket integration can be tested via a simple server,

```js
const fs = require('fs');
const http = require('http');

const WebSocketServer = require('ws').Server;

const wss = new WebSocketServer({
  server: http.createServer().listen(7232),
});

wss.on('connection', (ws) => {
  ws.on('message', (d) => {
    console.log(d);
  });

  ws.send(fs.readFileSync('./some-file'));
});
```

Then on the client,

```js
var ws = new WebSocket('ws://localhost:7232');

ws.binaryType = 'blob';

ws.onerror = (error) => {
  console.error(error);
};

ws.onmessage = (e) => {
  console.log(e.data);
  ws.send(e.data);
};
```

cc brentvatne ide
Closes https://github.com/facebook/react-native/pull/11417

Reviewed By: sahrens

Differential Revision: D5188484

Pulled By: javache

fbshipit-source-id: 6afcbc4d19aa7a27b0dc9d52701ba400e7d7e98f
This commit is contained in:
Philipp von Weitershausen 2017-07-26 08:12:12 -07:00 committed by Facebook Github Bot
parent 91493f6b9d
commit ed903099b4
26 changed files with 1800 additions and 307 deletions

153
Libraries/Blob/Blob.js Normal file
View File

@ -0,0 +1,153 @@
/**
* 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 invariant = require('fbjs/lib/invariant');
const uuid = require('uuid');
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) {
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,
});
}
/*
* 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,
* 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.
*/
close() {
BlobModule.release(this.blobId);
}
}
module.exports = Blob;

View File

@ -0,0 +1,25 @@
/**
* 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 BlobTypes
* @flow
*/
'use strict';
export type BlobProps = {
blobId: string,
offset: number,
size: number,
type?: string,
};
export type FileProps = BlobProps & {
name: string,
lastModified: number,
};

View File

@ -0,0 +1,344 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */; };
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */; };
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */ = {isa = PBXBuildFile; fileRef = AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
358F4ED51D1E81A9004DF814 /* Copy Headers */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = include/RCTBlob;
dstSubfolderSpec = 16;
files = (
AD08711A1E2162C8007D136D /* RCTBlobManager.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
};
AD0871121E215B16007D136D /* Copy Headers */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = include/RCTBlob;
dstSubfolderSpec = 16;
files = (
AD0871131E215B28007D136D /* RCTBlobManager.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBlob.a; sourceTree = BUILT_PRODUCTS_DIR; };
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBlobManager.h; sourceTree = "<group>"; };
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBlobManager.m; sourceTree = "<group>"; };
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTBlob-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
358F4ECE1D1E81A9004DF814 = {
isa = PBXGroup;
children = (
AD9A43C11DFC7126008DC588 /* RCTBlobManager.h */,
AD9A43C21DFC7126008DC588 /* RCTBlobManager.m */,
358F4ED81D1E81A9004DF814 /* Products */,
);
sourceTree = "<group>";
};
358F4ED81D1E81A9004DF814 /* Products */ = {
isa = PBXGroup;
children = (
358F4ED71D1E81A9004DF814 /* libRCTBlob.a */,
ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
AD0871151E215EB7007D136D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
AD0871161E215EC9007D136D /* RCTBlobManager.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
AD0871171E215ECC007D136D /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
AD0871181E215ED1007D136D /* RCTBlobManager.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
358F4ED61D1E81A9004DF814 /* RCTBlob */ = {
isa = PBXNativeTarget;
buildConfigurationList = 358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */;
buildPhases = (
AD0871151E215EB7007D136D /* Headers */,
358F4ED51D1E81A9004DF814 /* Copy Headers */,
358F4ED31D1E81A9004DF814 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = RCTBlob;
productName = SLKBlobs;
productReference = 358F4ED71D1E81A9004DF814 /* libRCTBlob.a */;
productType = "com.apple.product-type.library.static";
};
ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */;
buildPhases = (
AD0871171E215ECC007D136D /* Headers */,
AD0871121E215B16007D136D /* Copy Headers */,
ADD01A641E09402E00F6D226 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = "RCTBlob-tvOS";
productName = "RCTBlob-tvOS";
productReference = ADD01A681E09402E00F6D226 /* libRCTBlob-tvOS.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
358F4ECF1D1E81A9004DF814 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0730;
ORGANIZATIONNAME = "Silk Labs";
TargetAttributes = {
358F4ED61D1E81A9004DF814 = {
CreatedOnToolsVersion = 7.3;
};
ADD01A671E09402E00F6D226 = {
CreatedOnToolsVersion = 8.2;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 358F4ECE1D1E81A9004DF814;
productRefGroup = 358F4ED81D1E81A9004DF814 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
358F4ED61D1E81A9004DF814 /* RCTBlob */,
ADD01A671E09402E00F6D226 /* RCTBlob-tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
358F4ED31D1E81A9004DF814 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
AD9A43C31DFC7126008DC588 /* RCTBlobManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
ADD01A641E09402E00F6D226 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
ADD01A711E09404A00F6D226 /* RCTBlobManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
358F4EDE1D1E81A9004DF814 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
};
name = Debug;
};
358F4EDF1D1E81A9004DF814 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
358F4EE11D1E81A9004DF814 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
358F4EE21D1E81A9004DF814 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
ADD01A6F1E09402E00F6D226 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
TVOS_DEPLOYMENT_TARGET = 10.1;
};
name = Debug;
};
ADD01A701E09402E00F6D226 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
TVOS_DEPLOYMENT_TARGET = 10.1;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
358F4ED21D1E81A9004DF814 /* Build configuration list for PBXProject "RCTBlob" */ = {
isa = XCConfigurationList;
buildConfigurations = (
358F4EDE1D1E81A9004DF814 /* Debug */,
358F4EDF1D1E81A9004DF814 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
358F4EE01D1E81A9004DF814 /* Build configuration list for PBXNativeTarget "RCTBlob" */ = {
isa = XCConfigurationList;
buildConfigurations = (
358F4EE11D1E81A9004DF814 /* Debug */,
358F4EE21D1E81A9004DF814 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
ADD01A6E1E09402E00F6D226 /* Build configuration list for PBXNativeTarget "RCTBlob-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
ADD01A6F1E09402E00F6D226 /* Debug */,
ADD01A701E09402E00F6D226 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 358F4ECF1D1E81A9004DF814 /* Project object */;
}

16
Libraries/Blob/RCTBlobManager.h Executable file
View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-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.
*/
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTURLRequestHandler.h>
@interface RCTBlobManager : NSObject <RCTBridgeModule, RCTURLRequestHandler>
@end

213
Libraries/Blob/RCTBlobManager.m Executable file
View File

@ -0,0 +1,213 @@
/**
* Copyright (c) 2015-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.
*/
#import "RCTBlobManager.h"
#import <React/RCTConvert.h>
#import <React/RCTWebSocketModule.h>
static NSString *const kBlobUriScheme = @"blob";
@interface _RCTBlobContentHandler : NSObject <RCTWebSocketContentHandler>
- (instancetype)initWithBlobManager:(RCTBlobManager *)blobManager;
@end
@implementation RCTBlobManager
{
NSMutableDictionary<NSString *, NSData *> *_blobs;
_RCTBlobContentHandler *_contentHandler;
NSOperationQueue *_queue;
}
RCT_EXPORT_MODULE(BlobModule)
@synthesize bridge = _bridge;
- (NSDictionary<NSString *, id> *)constantsToExport
{
return @{
@"BLOB_URI_SCHEME": kBlobUriScheme,
@"BLOB_URI_HOST": [NSNull null],
};
}
- (dispatch_queue_t)methodQueue
{
return [[_bridge webSocketModule] methodQueue];
}
- (NSString *)store:(NSData *)data
{
NSString *blobId = [NSUUID UUID].UUIDString;
[self store:data withId:blobId];
return blobId;
}
- (void)store:(NSData *)data withId:(NSString *)blobId
{
if (!_blobs) {
_blobs = [NSMutableDictionary new];
}
_blobs[blobId] = data;
}
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
{
NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
return [self resolve:blobId
offset:offset ? [offset integerValue] : 0
size:size ? [size integerValue] : -1];
}
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
{
NSData *data = _blobs[blobId];
if (!data) {
return nil;
}
if (offset != 0 || (size != -1 && size != data.length)) {
data = [data subdataWithRange:NSMakeRange(offset, size)];
}
return data;
}
RCT_EXPORT_METHOD(enableBlobSupport:(nonnull NSNumber *)socketID)
{
if (!_contentHandler) {
_contentHandler = [[_RCTBlobContentHandler alloc] initWithBlobManager:self];
}
[[_bridge webSocketModule] setContentHandler:_contentHandler forSocketID:socketID];
}
RCT_EXPORT_METHOD(disableBlobSupport:(nonnull NSNumber *)socketID)
{
[[_bridge webSocketModule] setContentHandler:nil forSocketID:socketID];
}
RCT_EXPORT_METHOD(sendBlob:(NSDictionary *)blob socketID:(nonnull NSNumber *)socketID)
{
[[_bridge webSocketModule] sendData:[self resolve:blob] forSocketID:socketID];
}
RCT_EXPORT_METHOD(createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId)
{
NSMutableData *data = [NSMutableData new];
for (NSDictionary<NSString *, id> *part in parts) {
NSData *partData = [self resolve:part];
[data appendData:partData];
}
[self store:data withId:blobId];
}
RCT_EXPORT_METHOD(release:(NSString *)blobId)
{
[_blobs removeObjectForKey:blobId];
}
#pragma mark - RCTURLRequestHandler methods
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:kBlobUriScheme] == NSOrderedSame;
}
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_queue) {
_queue = [NSOperationQueue new];
_queue.maxConcurrentOperationCount = 2;
}
__weak __block NSBlockOperation *weakOp;
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:nil
expectedContentLength:-1
textEncodingName:nil];
[delegate URLRequest:weakOp didReceiveResponse:response];
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:request.URL resolvingAgainstBaseURL:NO];
NSString *blobId = components.path;
NSInteger offset = 0;
NSInteger size = -1;
if (components.queryItems) {
for (NSURLQueryItem *queryItem in components.queryItems) {
if ([queryItem.name isEqualToString:@"offset"]) {
offset = [queryItem.value integerValue];
}
if ([queryItem.name isEqualToString:@"size"]) {
size = [queryItem.value integerValue];
}
}
}
NSData *data;
if (blobId) {
data = [self resolve:blobId offset:offset size:size];
}
NSError *error;
if (data) {
[delegate URLRequest:weakOp didReceiveData:data];
} else {
error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
}
[delegate URLRequest:weakOp didCompleteWithError:error];
}];
weakOp = op;
[_queue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
[op cancel];
}
@end
@implementation _RCTBlobContentHandler {
__weak RCTBlobManager *_blobManager;
}
- (instancetype)initWithBlobManager:(RCTBlobManager *)blobManager
{
if (self = [super init]) {
_blobManager = blobManager;
}
return self;
}
- (id)processMessage:(id)message forSocketID:(NSNumber *)socketID withType:(NSString *__autoreleasing _Nonnull *)type
{
if (![message isKindOfClass:[NSData class]]) {
*type = @"text";
return message;
}
*type = @"blob";
return @{
@"blobId": [_blobManager store:message],
@"offset": @0,
@"size": @(((NSData *)message).length),
};
}
@end

69
Libraries/Blob/URL.js Normal file
View File

@ -0,0 +1,69 @@
/**
* 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 URL
* @flow
*/
'use strict';
const Blob = require('Blob');
const { BlobModule } = require('NativeModules');
let BLOB_URL_PREFIX = null;
if (typeof BlobModule.BLOB_URI_SCHEME === 'string') {
BLOB_URL_PREFIX = BlobModule.BLOB_URI_SCHEME + ':';
if (typeof BlobModule.BLOB_URI_HOST === 'string') {
BLOB_URL_PREFIX += `//${BlobModule.BLOB_URI_HOST}/`;
}
}
/**
* To allow Blobs be accessed via `content://` URIs,
* you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
*
* ```xml
* <manifest>
* <application>
* <provider
* android:name="com.facebook.react.modules.blob.BlobProvider"
* android:authorities="@string/blob_provider_authority"
* android:exported="false"
* />
* </application>
* </manifest>
* ```
* And then define the `blob_provider_authority` string in `res/values/strings.xml`.
* Use a dotted name that's entirely unique to your app:
*
* ```xml
* <resources>
* <string name="blob_provider_authority">your.app.package.blobs</string>
* </resources>
* ```
*/
class URL {
constructor() {
throw new Error('Creating BlobURL objects is not supported yet.');
}
static createObjectURL(blob: Blob) {
if (BLOB_URL_PREFIX === null) {
throw new Error('Cannot create URL for blob!');
}
return `${BLOB_URL_PREFIX}${blob.blobId}?offset=${blob.offset}&size=${blob.size}`;
}
static revokeObjectURL(url: string) {
// Do nothing.
}
}
module.exports = URL;

View File

@ -170,6 +170,8 @@ polyfillGlobal('Headers', () => require('fetch').Headers);
polyfillGlobal('Request', () => require('fetch').Request);
polyfillGlobal('Response', () => require('fetch').Response);
polyfillGlobal('WebSocket', () => require('WebSocket'));
polyfillGlobal('Blob', () => require('Blob'));
polyfillGlobal('URL', () => require('URL'));
// Set up alert
if (!global.alert) {

View File

@ -9,6 +9,28 @@
#import <React/RCTEventEmitter.h>
@interface RCTWebSocketModule : RCTEventEmitter
NS_ASSUME_NONNULL_BEGIN
@protocol RCTWebSocketContentHandler <NSObject>
- (id)processMessage:(id __nullable)message forSocketID:(NSNumber *)socketID
withType:(NSString *__nonnull __autoreleasing *__nonnull)type;
@end
@interface RCTWebSocketModule : RCTEventEmitter
// Register a custom handler for a specific websocket. The handler will be strongly held by the WebSocketModule.
- (void)setContentHandler:(id<RCTWebSocketContentHandler> __nullable)handler forSocketID:(NSNumber *)socketID;
- (void)sendData:(NSData *)data forSocketID:(nonnull NSNumber *)socketID;
@end
@interface RCTBridge (RCTWebSocketModule)
- (RCTWebSocketModule *)webSocketModule;
@end
NS_ASSUME_NONNULL_END

View File

@ -36,11 +36,15 @@
@implementation RCTWebSocketModule
{
NSMutableDictionary<NSNumber *, RCTSRWebSocket *> *_sockets;
NSMutableDictionary<NSNumber *, RCTSRWebSocket *> *_sockets;
NSMutableDictionary<NSNumber *, id> *_contentHandlers;
}
RCT_EXPORT_MODULE()
// Used by RCTBlobModule
@synthesize methodQueue = _methodQueue;
- (NSArray *)supportedEvents
{
return @[@"websocketMessage",
@ -88,15 +92,19 @@ RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols headers:(N
[webSocket open];
}
RCT_EXPORT_METHOD(send:(NSString *)message socketID:(nonnull NSNumber *)socketID)
RCT_EXPORT_METHOD(send:(NSString *)message forSocketID:(nonnull NSNumber *)socketID)
{
[_sockets[socketID] send:message];
}
RCT_EXPORT_METHOD(sendBinary:(NSString *)base64String socketID:(nonnull NSNumber *)socketID)
RCT_EXPORT_METHOD(sendBinary:(NSString *)base64String forSocketID:(nonnull NSNumber *)socketID)
{
NSData *message = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
[_sockets[socketID] send:message];
[self sendData:[[NSData alloc] initWithBase64EncodedString:base64String options:0] forSocketID:socketID];
}
- (void)sendData:(NSData *)data forSocketID:(nonnull NSNumber *)socketID
{
[_sockets[socketID] send:data];
}
RCT_EXPORT_METHOD(ping:(nonnull NSNumber *)socketID)
@ -110,14 +118,36 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber *)socketID)
[_sockets removeObjectForKey:socketID];
}
- (void)setContentHandler:(id<RCTWebSocketContentHandler>)handler forSocketID:(NSString *)socketID
{
if (!_contentHandlers) {
_contentHandlers = [NSMutableDictionary new];
}
_contentHandlers[socketID] = handler;
}
#pragma mark - RCTSRWebSocketDelegate methods
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
{
BOOL binary = [message isKindOfClass:[NSData class]];
NSString *type;
NSNumber *socketID = [webSocket reactTag];
id contentHandler = _contentHandlers[socketID];
if (contentHandler) {
message = [contentHandler processMessage:message forSocketID:socketID withType:&type];
} else {
if ([message isKindOfClass:[NSData class]]) {
type = @"binary";
message = [message base64EncodedStringWithOptions:0];
} else {
type = @"text";
}
}
[self sendEventWithName:@"websocketMessage" body:@{
@"data": binary ? [message base64EncodedStringWithOptions:0] : message,
@"type": binary ? @"binary" : @"text",
@"data": message,
@"type": type,
@"id": webSocket.reactTag
}];
}
@ -131,21 +161,36 @@ RCT_EXPORT_METHOD(close:(nonnull NSNumber *)socketID)
- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
{
NSNumber *socketID = [webSocket reactTag];
_contentHandlers[socketID] = nil;
[self sendEventWithName:@"websocketFailed" body:@{
@"message":error.localizedDescription,
@"id": webSocket.reactTag
@"message": error.localizedDescription,
@"id": socketID
}];
}
- (void)webSocket:(RCTSRWebSocket *)webSocket didCloseWithCode:(NSInteger)code
reason:(NSString *)reason wasClean:(BOOL)wasClean
- (void)webSocket:(RCTSRWebSocket *)webSocket
didCloseWithCode:(NSInteger)code
reason:(NSString *)reason
wasClean:(BOOL)wasClean
{
NSNumber *socketID = [webSocket reactTag];
_contentHandlers[socketID] = nil;
[self sendEventWithName:@"websocketClosed" body:@{
@"code": @(code),
@"reason": RCTNullIfNil(reason),
@"clean": @(wasClean),
@"id": webSocket.reactTag
@"id": socketID
}];
}
@end
@implementation RCTBridge (RCTWebSocketModule)
- (RCTWebSocketModule *)webSocketModule
{
return [self moduleForClass:[RCTWebSocketModule class]];
}
@end

View File

@ -8,11 +8,24 @@
*/
#import <React/RCTDefines.h>
#import <React/RCTWebSocketObserverProtocol.h>
#if RCT_DEV // Only supported in dev mode
@interface RCTWebSocketObserver : NSObject <RCTWebSocketObserver>
@protocol RCTWebSocketObserverDelegate
- (void)didReceiveWebSocketMessage:(NSDictionary<NSString *, id> *)message;
@end
@interface RCTWebSocketObserver : NSObject
- (instancetype)initWithURL:(NSURL *)url;
@property (nonatomic, weak) id<RCTWebSocketObserverDelegate> delegate;
- (void)start;
- (void)stop;
@end
#endif

View File

@ -11,17 +11,35 @@
*/
'use strict';
const NativeEventEmitter = require('NativeEventEmitter');
const Platform = require('Platform');
const RCTWebSocketModule = require('NativeModules').WebSocketModule;
const WebSocketEvent = require('WebSocketEvent');
const binaryToBase64 = require('binaryToBase64');
const Blob = require('Blob');
const EventTarget = require('event-target-shim');
const NativeEventEmitter = require('NativeEventEmitter');
const NativeModules = require('NativeModules');
const Platform = require('Platform');
const WebSocketEvent = require('WebSocketEvent');
const base64 = require('base64-js');
const binaryToBase64 = require('binaryToBase64');
const invariant = require('fbjs/lib/invariant');
const {WebSocketModule} = NativeModules;
import type EventSubscription from 'EventSubscription';
type ArrayBufferView =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array
| DataView
type BinaryType = 'blob' | 'arraybuffer'
const CONNECTING = 0;
const OPEN = 1;
const CLOSING = 2;
@ -58,22 +76,22 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
_socketId: number;
_eventEmitter: NativeEventEmitter;
_subscriptions: Array<EventSubscription>;
_binaryType: ?BinaryType;
onclose: ?Function;
onerror: ?Function;
onmessage: ?Function;
onopen: ?Function;
binaryType: ?string;
bufferedAmount: number;
extension: ?string;
protocol: ?string;
readyState: number = CONNECTING;
url: ?string;
// This module depends on the native `RCTWebSocketModule` module. If you don't include it,
// This module depends on the native `WebSocketModule` module. If you don't include it,
// `WebSocket.isAvailable` will return `false`, and WebSocket constructor will throw an error
static isAvailable: boolean = !!RCTWebSocketModule;
static isAvailable: boolean = !!WebSocketModule;
constructor(url: string, protocols: ?string | ?Array<string>, options: ?{origin?: string}) {
super();
@ -87,13 +105,35 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
if (!WebSocket.isAvailable) {
throw new Error('Cannot initialize WebSocket module. ' +
'Native module RCTWebSocketModule is missing.');
'Native module WebSocketModule is missing.');
}
this._eventEmitter = new NativeEventEmitter(RCTWebSocketModule);
this._eventEmitter = new NativeEventEmitter(WebSocketModule);
this._socketId = nextWebSocketId++;
this._registerEvents();
RCTWebSocketModule.connect(url, protocols, options, this._socketId);
WebSocketModule.connect(url, protocols, options, this._socketId);
}
get binaryType(): ?BinaryType {
return this._binaryType;
}
set binaryType(binaryType: BinaryType): void {
if (binaryType !== 'blob' && binaryType !== 'arraybuffer') {
throw new Error('binaryType must be either \'blob\' or \'arraybuffer\'');
}
if (this._binaryType === 'blob' || binaryType === 'blob') {
const BlobModule = NativeModules.BlobModule;
invariant(BlobModule, 'Native module BlobModule is required for blob support');
if (BlobModule) {
if (binaryType === 'blob') {
BlobModule.enableBlobSupport(this._socketId);
} else {
BlobModule.disableBlobSupport(this._socketId);
}
}
}
this._binaryType = binaryType;
}
close(code?: number, reason?: string): void {
@ -106,18 +146,25 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
this._close(code, reason);
}
send(data: string | ArrayBuffer | $ArrayBufferView): void {
send(data: string | ArrayBuffer | ArrayBufferView | Blob): void {
if (this.readyState === this.CONNECTING) {
throw new Error('INVALID_STATE_ERR');
}
if (data instanceof Blob) {
const BlobModule = NativeModules.BlobModule;
invariant(BlobModule, 'Native module BlobModule is required for blob support');
BlobModule.sendBlob(data, this._socketId);
return;
}
if (typeof data === 'string') {
RCTWebSocketModule.send(data, this._socketId);
WebSocketModule.send(data, this._socketId);
return;
}
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
RCTWebSocketModule.sendBinary(binaryToBase64(data), this._socketId);
WebSocketModule.sendBinary(binaryToBase64(data), this._socketId);
return;
}
@ -129,7 +176,7 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
throw new Error('INVALID_STATE_ERR');
}
RCTWebSocketModule.ping(this._socketId);
WebSocketModule.ping(this._socketId);
}
_close(code?: number, reason?: string): void {
@ -137,9 +184,9 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
// See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
const statusCode = typeof code === 'number' ? code : CLOSE_NORMAL;
const closeReason = typeof reason === 'string' ? reason : '';
RCTWebSocketModule.close(statusCode, closeReason, this._socketId);
WebSocketModule.close(statusCode, closeReason, this._socketId);
} else {
RCTWebSocketModule.close(this._socketId);
WebSocketModule.close(this._socketId);
}
}
@ -154,9 +201,16 @@ class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
if (ev.id !== this._socketId) {
return;
}
this.dispatchEvent(new WebSocketEvent('message', {
data: (ev.type === 'binary') ? base64.toByteArray(ev.data).buffer : ev.data
}));
let data = ev.data;
switch (ev.type) {
case 'binary':
data = base64.toByteArray(ev.data).buffer;
break;
case 'blob':
data = Blob.create(ev.data);
break;
}
this.dispatchEvent(new WebSocketEvent('message', { data }));
}),
this._eventEmitter.addListener('websocketOpen', ev => {
if (ev.id !== this._socketId) {

View File

@ -37,7 +37,6 @@
1497CFB01B21F5E400C1F8F2 /* RCTFontTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA81B21F5E400C1F8F2 /* RCTFontTests.m */; };
1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */; };
1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */; };
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
14B6DA821B276C5900BF4DD1 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58005BEE1ABA80530062E044 /* libRCTTest.a */; };
14D6D7111B220EB3001FB087 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14D6D7101B220EB3001FB087 /* libOCMock.a */; };
14D6D71E1B2222EF001FB087 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
@ -106,6 +105,7 @@
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
39AA31A41DC1DFDC000F7EBB /* RCTUnicodeDecodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 39AA31A31DC1DFDC000F7EBB /* RCTUnicodeDecodeTests.m */; };
3D05746D1DE6008900184BB4 /* libRCTPushNotification-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D05746C1DE6008900184BB4 /* libRCTPushNotification-tvOS.a */; };
3D0E379D1F1CC77200DCAC9F /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
3D13F8481D6F6AF900E69E0E /* ImageInBundle.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D13F8441D6F6AF200E69E0E /* ImageInBundle.png */; };
3D13F84A1D6F6AFD00E69E0E /* OtherImages.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3D13F8451D6F6AF200E69E0E /* OtherImages.xcassets */; };
3D299BAF1D33EBFA00FA1057 /* RCTLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D299BAE1D33EBFA00FA1057 /* RCTLoggingTests.m */; };
@ -114,6 +114,8 @@
3D56F9F11D6F6E9B00F53A06 /* RNTesterBundle.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3D13F83E1D6F6AE000E69E0E /* RNTesterBundle.bundle */; };
3DB99D0C1BA0340600302749 /* RNTesterIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* RNTesterIntegrationTests.m */; };
3DD981D61D33C6FB007DC7BE /* RNTesterUnitTestsBundle.js in Resources */ = {isa = PBXBuildFile; fileRef = 3DD981D51D33C6FB007DC7BE /* RNTesterUnitTestsBundle.js */; };
52C11BBB1EEACA7100C1A058 /* libRCTBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5281CA511EEAC9A700AC40CD /* libRCTBlob.a */; };
52C11BE11EEACA7800C1A058 /* libRCTBlob-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5281CA531EEAC9A700AC40CD /* libRCTBlob-tvOS.a */; };
68FF44381CF6111500720EFD /* RCTBundleURLProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */; };
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
@ -392,6 +394,20 @@
remoteGlobalIDString = 139D7E881E25C6D100323FB7;
remoteInfo = "double-conversion";
};
5281CA501EEAC9A700AC40CD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5281CA4B1EEAC9A700AC40CD /* RCTBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 358F4ED71D1E81A9004DF814;
remoteInfo = RCTBlob;
};
5281CA521EEAC9A700AC40CD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5281CA4B1EEAC9A700AC40CD /* RCTBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = ADD01A681E09402E00F6D226;
remoteInfo = "RCTBlob-tvOS";
};
58005BED1ABA80530062E044 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */;
@ -489,6 +505,7 @@
3D2AFAF41D646CF80089D1A3 /* legacy_image@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "legacy_image@2x.png"; path = "RNTester/legacy_image@2x.png"; sourceTree = "<group>"; };
3DB99D0B1BA0340600302749 /* RNTesterIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNTesterIntegrationTests.m; sourceTree = "<group>"; };
3DD981D51D33C6FB007DC7BE /* RNTesterUnitTestsBundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = RNTesterUnitTestsBundle.js; sourceTree = "<group>"; };
5281CA4B1EEAC9A700AC40CD /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = ../Libraries/Blob/RCTBlob.xcodeproj; sourceTree = "<group>"; };
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBundleURLProviderTests.m; sourceTree = "<group>"; };
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
@ -526,8 +543,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3D0E379D1F1CC77200DCAC9F /* libReact.a in Frameworks */,
52C11BBB1EEACA7100C1A058 /* libRCTBlob.a in Frameworks */,
2D66FF8F1ECA406D00F0A767 /* libART.a in Frameworks */,
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */,
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */,
13E501F11D07A84A005F35D8 /* libRCTAnimation.a in Frameworks */,
@ -564,6 +582,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
52C11BE11EEACA7800C1A058 /* libRCTBlob-tvOS.a in Frameworks */,
2D66FF901ECA407E00F0A767 /* libART-tvOS.a in Frameworks */,
2DD323EA1DA2DE3F000FE1B8 /* libReact.a in Frameworks */,
2DD323E31DA2DE3F000FE1B8 /* libRCTAnimation.a in Frameworks */,
@ -598,6 +617,7 @@
1316A21D1AA397F400C0188E /* Libraries */ = {
isa = PBXGroup;
children = (
5281CA4B1EEAC9A700AC40CD /* RCTBlob.xcodeproj */,
2D66FF5F1ECA405900F0A767 /* ART.xcodeproj */,
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */,
14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */,
@ -875,6 +895,15 @@
path = RNTester/RNTesterBundle;
sourceTree = "<group>";
};
5281CA4C1EEAC9A700AC40CD /* Products */ = {
isa = PBXGroup;
children = (
5281CA511EEAC9A700AC40CD /* libRCTBlob.a */,
5281CA531EEAC9A700AC40CD /* libRCTBlob-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
};
58005BE51ABA80530062E044 /* Products */ = {
isa = PBXGroup;
children = (
@ -1117,6 +1146,10 @@
ProductGroup = 13E5019D1D07A502005F35D8 /* Products */;
ProjectRef = 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = 5281CA4C1EEAC9A700AC40CD /* Products */;
ProjectRef = 5281CA4B1EEAC9A700AC40CD /* RCTBlob.xcodeproj */;
},
{
ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */;
ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */;
@ -1418,6 +1451,20 @@
remoteRef = 3D507F431EBC88B700B56834 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5281CA511EEAC9A700AC40CD /* libRCTBlob.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTBlob.a;
remoteRef = 5281CA501EEAC9A700AC40CD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5281CA531EEAC9A700AC40CD /* libRCTBlob-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libRCTBlob-tvOS.a";
remoteRef = 5281CA521EEAC9A700AC40CD /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
58005BEE1ABA80530062E044 /* libRCTTest.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@ -6,8 +6,8 @@
* 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
* @providesModule WebSocketExample
* @format
*/
'use strict';
@ -16,23 +16,19 @@
const React = require('react');
const ReactNative = require('react-native');
const {
Image,
PixelRatio,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
ScrollView,
View,
} = ReactNative;
const DEFAULT_WS_URL = 'ws://localhost:5555/';
const DEFAULT_HTTP_URL = 'http://localhost:5556/';
const WS_EVENTS = [
'close',
'error',
'message',
'open',
];
const WS_EVENTS = ['close', 'error', 'message', 'open'];
const WS_STATES = [
/* 0 */ 'CONNECTING',
/* 1 */ 'OPEN',
@ -42,7 +38,11 @@ const WS_STATES = [
class Button extends React.Component {
render(): React.Element<any> {
const label = <Text style={styles.buttonLabel}>{this.props.label}</Text>;
const label = (
<Text style={styles.buttonLabel}>
{this.props.label}
</Text>
);
if (this.props.disabled) {
return (
<View style={[styles.button, styles.disabledButton]}>
@ -51,9 +51,7 @@ class Button extends React.Component {
);
}
return (
<TouchableOpacity
onPress={this.props.onPress}
style={styles.button}>
<TouchableOpacity onPress={this.props.onPress} style={styles.button}>
{label}
</TouchableOpacity>
);
@ -64,39 +62,84 @@ class Row extends React.Component {
render(): React.Element<any> {
return (
<View style={styles.row}>
<Text>{this.props.label}</Text>
<Text>{this.props.value}</Text>
<Text>
{this.props.label}
</Text>
{this.props.value
? <Text>
{this.props.value}
</Text>
: null}
{this.props.children}
</View>
);
}
}
class WebSocketImage extends React.Component {
ws: ?WebSocket = null;
state: {blob: ?Blob} = {blob: null};
componentDidMount() {
let ws = (this.ws = new WebSocket(this.props.url));
ws.binaryType = 'blob';
ws.onmessage = event => {
if (event.data instanceof Blob) {
const blob = event.data;
if (this.state.blob) {
this.state.blob.close();
}
this.setState({blob});
}
};
ws.onopen = event => {
ws.send('getImage');
};
}
componentUnmount() {
if (this.state.blob) {
this.state.blob.close();
}
this.ws && this.ws.close();
}
render() {
if (!this.state.blob) {
return <View />;
}
return (
<Image
source={{uri: URL.createObjectURL(this.state.blob)}}
style={{width: 50, height: 50}}
/>
);
}
}
function showValue(value) {
if (value === undefined || value === null) {
return '(no value)';
}
console.log('typeof Uint8Array', typeof Uint8Array);
if (typeof ArrayBuffer !== 'undefined' &&
typeof Uint8Array !== 'undefined' &&
value instanceof ArrayBuffer) {
if (
typeof ArrayBuffer !== 'undefined' &&
typeof Uint8Array !== 'undefined' &&
value instanceof ArrayBuffer
) {
return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`;
}
return value;
}
type State = {
url: string;
httpUrl: string;
fetchStatus: ?string;
socket: ?WebSocket;
socketState: ?number;
lastSocketEvent: ?string;
lastMessage: ?string | ?ArrayBuffer;
outgoingMessage: string;
url: string,
httpUrl: string,
fetchStatus: ?string,
socket: ?WebSocket,
socketState: ?number,
lastSocketEvent: ?string,
lastMessage: ?string | ?ArrayBuffer,
outgoingMessage: string,
};
class WebSocketExample extends React.Component<any, any, State> {
static title = 'WebSocket';
static description = 'WebSocket API';
@ -127,10 +170,7 @@ class WebSocketExample extends React.Component<any, any, State> {
this.state.socket.close();
};
// Ideally this would be a MessageEvent, but Flow's definition
// doesn't inherit from Event, so it's 'any' for now.
// See https://github.com/facebook/flow/issues/1654.
_onSocketEvent = (event: any) => {
_onSocketEvent = (event: MessageEvent) => {
const state: any = {
socketState: event.target.readyState,
lastSocketEvent: event.type,
@ -153,7 +193,7 @@ class WebSocketExample extends React.Component<any, any, State> {
this.setState({
fetchStatus: 'fetching',
});
fetch(this.state.httpUrl).then((response) => {
fetch(this.state.httpUrl).then(response => {
if (response.status >= 200 && response.status < 400) {
this.setState({
fetchStatus: 'OK',
@ -163,9 +203,11 @@ class WebSocketExample extends React.Component<any, any, State> {
};
_sendBinary = () => {
if (!this.state.socket ||
typeof ArrayBuffer === 'undefined' ||
typeof Uint8Array === 'undefined') {
if (
!this.state.socket ||
typeof ArrayBuffer === 'undefined' ||
typeof Uint8Array === 'undefined'
) {
return;
}
const {outgoingMessage} = this.state;
@ -180,9 +222,8 @@ class WebSocketExample extends React.Component<any, any, State> {
render(): React.Element<any> {
const socketState = WS_STATES[this.state.socketState || -1];
const canConnect =
!this.state.socket ||
this.state.socket.readyState >= WebSocket.CLOSING;
const canSend = !!this.state.socket;
!this.state.socket || this.state.socket.readyState >= WebSocket.CLOSING;
const canSend = socketState === 'OPEN';
return (
<ScrollView style={styles.container}>
<View style={styles.note}>
@ -191,10 +232,7 @@ class WebSocketExample extends React.Component<any, any, State> {
./RNTester/js/websocket_test_server.js
</Text>
</View>
<Row
label="Current WebSocket state"
value={showValue(socketState)}
/>
<Row label="Current WebSocket state" value={showValue(socketState)} />
<Row
label="Last WebSocket event"
value={showValue(this.state.lastSocketEvent)}
@ -203,11 +241,14 @@ class WebSocketExample extends React.Component<any, any, State> {
label="Last message received"
value={showValue(this.state.lastMessage)}
/>
<Row label="Last image received">
{canSend ? <WebSocketImage url={this.state.url} /> : null}
</Row>
<TextInput
style={styles.textInput}
autoCorrect={false}
placeholder="Server URL..."
onChangeText={(url) => this.setState({url})}
onChangeText={url => this.setState({url})}
value={this.state.url}
/>
<View style={styles.buttonRow}>
@ -226,7 +267,7 @@ class WebSocketExample extends React.Component<any, any, State> {
style={styles.textInput}
autoCorrect={false}
placeholder="Type message here..."
onChangeText={(outgoingMessage) => this.setState({outgoingMessage})}
onChangeText={outgoingMessage => this.setState({outgoingMessage})}
value={this.state.outgoingMessage}
/>
<View style={styles.buttonRow}>
@ -244,14 +285,14 @@ class WebSocketExample extends React.Component<any, any, State> {
<View style={styles.note}>
<Text>To start the HTTP test server:</Text>
<Text style={styles.monospace}>
./RNTester/http_test_server.js
./RNTester/js/http_test_server.js
</Text>
</View>
<TextInput
style={styles.textInput}
autoCorrect={false}
placeholder="HTTP URL..."
onChangeText={(httpUrl) => this.setState({httpUrl})}
onChangeText={httpUrl => this.setState({httpUrl})}
value={this.state.httpUrl}
/>
<View style={styles.buttonRow}>
@ -263,13 +304,14 @@ class WebSocketExample extends React.Component<any, any, State> {
</View>
<View style={styles.note}>
<Text>
{this.state.fetchStatus === 'OK' ? 'Done. Check your WS server console to see if the next WS requests include the cookie (should be "wstest=OK")' : '-'}
{this.state.fetchStatus === 'OK'
? 'Done. Check your WS server console to see if the next WS requests include the cookie (should be "wstest=OK")'
: '-'}
</Text>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({

0
RNTester/js/http_test_server.js Normal file → Executable file
View File

7
RNTester/js/websocket_test_server.js Normal file → Executable file
View File

@ -16,6 +16,9 @@
const WebSocket = require('ws');
const fs = require('fs');
const path = require('path');
console.log(`\
Test server for WebSocketExample
@ -33,6 +36,10 @@ server.on('connection', (ws) => {
if (respondWithBinary) {
message = new Buffer(message);
}
if (message === 'getImage') {
message = fs.readFileSync(path.resolve(__dirname, 'flux@3x.png'));
}
console.log('Replying with:', message);
ws.send(message);
});

View File

@ -148,9 +148,15 @@ Pod::Spec.new do |s|
ss.header_dir = "RCTAnimation"
end
s.subspec "RCTBlob" do |ss|
ss.dependency "React/Core"
ss.source_files = "Libraries/Blob/*.{h,m}"
ss.preserve_paths = "Libraries/Blob/*.js"
end
s.subspec "RCTCameraRoll" do |ss|
ss.dependency "React/Core"
ss.dependency "React/RCTImage"
ss.dependency 'React/RCTImage'
ss.source_files = "Libraries/CameraRoll/*.{h,m}"
end
@ -192,6 +198,7 @@ Pod::Spec.new do |s|
s.subspec "RCTWebSocket" do |ss|
ss.dependency "React/Core"
ss.dependency "React/RCTBlob"
ss.source_files = "Libraries/WebSocket/*.{h,m}"
end

View File

@ -19,7 +19,7 @@
#import <React/RCTReconnectingWebSocket.h>
#import <React/RCTSRWebSocket.h>
#import <React/RCTUtils.h>
#import <React/RCTWebSocketObserverProtocol.h>
#import <React/RCTWebSocketObserver.h>
#import "RCTPackagerConnectionBridgeConfig.h"
#import "RCTReloadPackagerMethod.h"

View File

@ -352,6 +352,12 @@
3D0B842C1EC0B4EA00B2BD8E /* RCTTVView.m in Sources */ = {isa = PBXBuildFile; fileRef = 130443D71E401AD800D93A67 /* RCTTVView.m */; };
3D0B842F1EC0B51200B2BD8E /* RCTTVNavigationEventEmitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D0B842D1EC0B51200B2BD8E /* RCTTVNavigationEventEmitter.h */; };
3D0B84301EC0B51200B2BD8E /* RCTTVNavigationEventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D0B842E1EC0B51200B2BD8E /* RCTTVNavigationEventEmitter.m */; };
3D0E378A1F1CC40000DCAC9F /* RCTWebSocketModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D0E37891F1CC40000DCAC9F /* RCTWebSocketModule.h */; };
3D0E378C1F1CC58C00DCAC9F /* RCTWebSocketObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D0E378B1F1CC58C00DCAC9F /* RCTWebSocketObserver.h */; };
3D0E378D1F1CC58F00DCAC9F /* RCTWebSocketObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D0E378B1F1CC58C00DCAC9F /* RCTWebSocketObserver.h */; };
3D0E378E1F1CC59100DCAC9F /* RCTWebSocketModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D0E37891F1CC40000DCAC9F /* RCTWebSocketModule.h */; };
3D0E378F1F1CC5CF00DCAC9F /* RCTWebSocketModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3D0E37891F1CC40000DCAC9F /* RCTWebSocketModule.h */; };
3D0E37901F1CC5E100DCAC9F /* RCTWebSocketModule.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3D0E37891F1CC40000DCAC9F /* RCTWebSocketModule.h */; };
3D1E68DB1CABD13900DD7465 /* RCTDisplayLink.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */; };
3D302F1E1DF8265A00D6DDAE /* JavaScriptCore.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3D7A27DC1DE32541002E3F95 /* JavaScriptCore.h */; };
3D302F1F1DF8265A00D6DDAE /* JSCWrapper.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3D7A27DE1DE32541002E3F95 /* JSCWrapper.h */; };
@ -399,7 +405,6 @@
3D302F4D1DF828F800D6DDAE /* RCTURLRequestDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */; };
3D302F4E1DF828F800D6DDAE /* RCTURLRequestHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */; };
3D302F4F1DF828F800D6DDAE /* RCTUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */; };
3D302F501DF828F800D6DDAE /* RCTWebSocketObserverProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DB910701C74B21600838BBE /* RCTWebSocketObserverProtocol.h */; };
3D302F541DF828F800D6DDAE /* RCTJSCSamplingProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 369123DF1DDC75850095B341 /* RCTJSCSamplingProfiler.h */; };
3D302F551DF828F800D6DDAE /* RCTAccessibilityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */; };
3D302F561DF828F800D6DDAE /* RCTAlertManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */; };
@ -604,7 +609,6 @@
3D80D9481DF6FA890028D040 /* RCTURLRequestDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */; };
3D80D9491DF6FA890028D040 /* RCTURLRequestHandler.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */; };
3D80D94A1DF6FA890028D040 /* RCTUtils.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */; };
3D80D94B1DF6FA890028D040 /* RCTWebSocketObserverProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3DB910701C74B21600838BBE /* RCTWebSocketObserverProtocol.h */; };
3D80D94F1DF6FA890028D040 /* RCTJSCSamplingProfiler.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 369123DF1DDC75850095B341 /* RCTJSCSamplingProfiler.h */; };
3D80D9501DF6FA890028D040 /* RCTAccessibilityManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */; };
3D80D9511DF6FA890028D040 /* RCTAlertManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */; };
@ -718,7 +722,6 @@
3D80DA421DF820620028D040 /* RCTURLRequestDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */; };
3D80DA431DF820620028D040 /* RCTURLRequestHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */; };
3D80DA441DF820620028D040 /* RCTUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */; };
3D80DA451DF820620028D040 /* RCTWebSocketObserverProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DB910701C74B21600838BBE /* RCTWebSocketObserverProtocol.h */; };
3D80DA491DF820620028D040 /* RCTJSCSamplingProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 369123DF1DDC75850095B341 /* RCTJSCSamplingProfiler.h */; };
3D80DA4A1DF820620028D040 /* RCTAccessibilityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */; };
3D80DA4B1DF820620028D040 /* RCTAlertManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */; };
@ -856,7 +859,6 @@
3DA981E31E5B0F29004F2374 /* RCTURLRequestDelegate.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */; };
3DA981E41E5B0F29004F2374 /* RCTURLRequestHandler.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */; };
3DA981E51E5B0F29004F2374 /* RCTUtils.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */; };
3DA981E61E5B0F29004F2374 /* RCTWebSocketObserverProtocol.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3DB910701C74B21600838BBE /* RCTWebSocketObserverProtocol.h */; };
3DA981E91E5B0F7F004F2374 /* RCTJSCSamplingProfiler.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 369123DF1DDC75850095B341 /* RCTJSCSamplingProfiler.h */; };
3DA981EA1E5B0F7F004F2374 /* RCTAccessibilityManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */; };
3DA981EB1E5B0F7F004F2374 /* RCTAlertManager.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 13B07FE71A69327A00A75B9A /* RCTAlertManager.h */; };
@ -1191,6 +1193,7 @@
dstPath = include/React;
dstSubfolderSpec = 16;
files = (
3D0E37901F1CC5E100DCAC9F /* RCTWebSocketModule.h in Copy Headers */,
5960C1BF1F0804F50066FD5B /* RCTLayoutAnimation.h in Copy Headers */,
5960C1C01F0804F50066FD5B /* RCTLayoutAnimationGroup.h in Copy Headers */,
C6827DFC1EF1801B00D66BEF /* RCTJSEnvironment.h in Copy Headers */,
@ -1314,7 +1317,6 @@
3DA981E31E5B0F29004F2374 /* RCTURLRequestDelegate.h in Copy Headers */,
3DA981E41E5B0F29004F2374 /* RCTURLRequestHandler.h in Copy Headers */,
3DA981E51E5B0F29004F2374 /* RCTUtils.h in Copy Headers */,
3DA981E61E5B0F29004F2374 /* RCTWebSocketObserverProtocol.h in Copy Headers */,
);
name = "Copy Headers";
runOnlyForDeploymentPostprocessing = 0;
@ -1412,6 +1414,7 @@
dstPath = include/React;
dstSubfolderSpec = 16;
files = (
3D0E378F1F1CC5CF00DCAC9F /* RCTWebSocketModule.h in Copy Headers */,
5960C1BD1F0804DF0066FD5B /* RCTLayoutAnimation.h in Copy Headers */,
5960C1BE1F0804DF0066FD5B /* RCTLayoutAnimationGroup.h in Copy Headers */,
C6827DFB1EF1800E00D66BEF /* RCTJSEnvironment.h in Copy Headers */,
@ -1462,7 +1465,6 @@
3D80D9481DF6FA890028D040 /* RCTURLRequestDelegate.h in Copy Headers */,
3D80D9491DF6FA890028D040 /* RCTURLRequestHandler.h in Copy Headers */,
3D80D94A1DF6FA890028D040 /* RCTUtils.h in Copy Headers */,
3D80D94B1DF6FA890028D040 /* RCTWebSocketObserverProtocol.h in Copy Headers */,
3D80D94F1DF6FA890028D040 /* RCTJSCSamplingProfiler.h in Copy Headers */,
3D80D9501DF6FA890028D040 /* RCTAccessibilityManager.h in Copy Headers */,
3D80D9511DF6FA890028D040 /* RCTAlertManager.h in Copy Headers */,
@ -1846,6 +1848,8 @@
3D0B84291EC0B49400B2BD8E /* RCTTVRemoteHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTVRemoteHandler.m; sourceTree = "<group>"; };
3D0B842D1EC0B51200B2BD8E /* RCTTVNavigationEventEmitter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTVNavigationEventEmitter.h; sourceTree = "<group>"; };
3D0B842E1EC0B51200B2BD8E /* RCTTVNavigationEventEmitter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTVNavigationEventEmitter.m; sourceTree = "<group>"; };
3D0E37891F1CC40000DCAC9F /* RCTWebSocketModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTWebSocketModule.h; path = WebSocket/RCTWebSocketModule.h; sourceTree = "<group>"; };
3D0E378B1F1CC58C00DCAC9F /* RCTWebSocketObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTWebSocketObserver.h; path = WebSocket/RCTWebSocketObserver.h; sourceTree = "<group>"; };
3D1E68D81CABD13900DD7465 /* RCTDisplayLink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDisplayLink.h; sourceTree = "<group>"; };
3D1E68D91CABD13900DD7465 /* RCTDisplayLink.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDisplayLink.m; sourceTree = "<group>"; };
3D1FA07A1DE4F2EA00E03CC6 /* RCTNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworking.h; sourceTree = "<group>"; };
@ -1931,7 +1935,6 @@
3D92B10B1E0369AD0018521A /* Unicode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Unicode.h; sourceTree = "<group>"; };
3D92B10C1E0369AD0018521A /* Value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Value.cpp; sourceTree = "<group>"; };
3D92B10D1E0369AD0018521A /* Value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Value.h; sourceTree = "<group>"; };
3DB910701C74B21600838BBE /* RCTWebSocketObserverProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketObserverProtocol.h; sourceTree = "<group>"; };
3DCC92BA1E94458B00EF89A8 /* YGEnums.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = YGEnums.c; sourceTree = "<group>"; };
3DF1BE801F26576400068F1A /* JSCTracing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSCTracing.cpp; sourceTree = "<group>"; };
3DF1BE811F26576400068F1A /* JSCTracing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSCTracing.h; sourceTree = "<group>"; };
@ -2478,6 +2481,8 @@
3D7BFD2A1EA8E3D3008DFB7A /* WebSocket */ = {
isa = PBXGroup;
children = (
3D0E378B1F1CC58C00DCAC9F /* RCTWebSocketObserver.h */,
3D0E37891F1CC40000DCAC9F /* RCTWebSocketModule.h */,
3D7BFD2B1EA8E3FA008DFB7A /* RCTReconnectingWebSocket.h */,
3D7BFD2C1EA8E3FA008DFB7A /* RCTSRWebSocket.h */,
);
@ -2628,7 +2633,6 @@
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
3DB910701C74B21600838BBE /* RCTWebSocketObserverProtocol.h */,
);
path = Base;
sourceTree = "<group>";
@ -2716,6 +2720,7 @@
3D7AA9C51E548CDB001955CF /* NSDataBigString.h in Headers */,
5960C1BA1F0804A00066FD5B /* RCTLayoutAnimationGroup.h in Headers */,
13134C991E296B2A00B9F3CB /* RCTCxxMethod.h in Headers */,
3D0E378D1F1CC58F00DCAC9F /* RCTWebSocketObserver.h in Headers */,
3D302F471DF828F800D6DDAE /* RCTPlatform.h in Headers */,
13134C951E296B2A00B9F3CB /* RCTObjcExecutor.h in Headers */,
590D7BFE1EBD458B00D8A370 /* RCTShadowView+Layout.h in Headers */,
@ -2741,6 +2746,7 @@
3D302F2E1DF828F800D6DDAE /* RCTBridgeDelegate.h in Headers */,
3D302F2F1DF828F800D6DDAE /* RCTBridgeMethod.h in Headers */,
130E3D8A1E6A083600ACE484 /* RCTDevSettings.h in Headers */,
3D0E378E1F1CC59100DCAC9F /* RCTWebSocketModule.h in Headers */,
3D302F301DF828F800D6DDAE /* RCTBridgeModule.h in Headers */,
3D302F311DF828F800D6DDAE /* RCTBundleURLProvider.h in Headers */,
3D302F321DF828F800D6DDAE /* RCTConvert.h in Headers */,
@ -2777,7 +2783,6 @@
3D302F4D1DF828F800D6DDAE /* RCTURLRequestDelegate.h in Headers */,
3D302F4E1DF828F800D6DDAE /* RCTURLRequestHandler.h in Headers */,
3D302F4F1DF828F800D6DDAE /* RCTUtils.h in Headers */,
3D302F501DF828F800D6DDAE /* RCTWebSocketObserverProtocol.h in Headers */,
3D302F541DF828F800D6DDAE /* RCTJSCSamplingProfiler.h in Headers */,
3D302F551DF828F800D6DDAE /* RCTAccessibilityManager.h in Headers */,
3D302F561DF828F800D6DDAE /* RCTAlertManager.h in Headers */,
@ -3060,12 +3065,12 @@
3D80DA421DF820620028D040 /* RCTURLRequestDelegate.h in Headers */,
3D80DA431DF820620028D040 /* RCTURLRequestHandler.h in Headers */,
3D80DA441DF820620028D040 /* RCTUtils.h in Headers */,
3D80DA451DF820620028D040 /* RCTWebSocketObserverProtocol.h in Headers */,
13134C981E296B2A00B9F3CB /* RCTCxxMethod.h in Headers */,
3D80DA491DF820620028D040 /* RCTJSCSamplingProfiler.h in Headers */,
3D80DA4A1DF820620028D040 /* RCTAccessibilityManager.h in Headers */,
3D80DA4B1DF820620028D040 /* RCTAlertManager.h in Headers */,
3D80DA4C1DF820620028D040 /* RCTAppState.h in Headers */,
3D0E378C1F1CC58C00DCAC9F /* RCTWebSocketObserver.h in Headers */,
3D80DA4D1DF820620028D040 /* RCTAsyncLocalStorage.h in Headers */,
3D80DA4E1DF820620028D040 /* RCTClipboard.h in Headers */,
3D80DA4F1DF820620028D040 /* RCTDevLoadingView.h in Headers */,
@ -3092,6 +3097,7 @@
C6194AB01EF156280034D062 /* RCTPackagerConnectionConfig.h in Headers */,
CF2731C01E7B8DE40044CA4F /* RCTDeviceInfo.h in Headers */,
3D80DA611DF820620028D040 /* RCTAnimationType.h in Headers */,
3D0E378A1F1CC40000DCAC9F /* RCTWebSocketModule.h in Headers */,
3D80DA621DF820620028D040 /* RCTAutoInsetsProtocol.h in Headers */,
3D80DA631DF820620028D040 /* RCTBorderDrawing.h in Headers */,
3D80DA641DF820620028D040 /* RCTBorderStyle.h in Headers */,

View File

@ -0,0 +1,22 @@
include_defs("//ReactAndroid/DEFS")
android_library(
name = "blob",
srcs = glob(["**/*.java"]),
visibility = [
"PUBLIC",
],
deps = [
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
react_native_dep("third-party/android/support-annotations:android-support-annotations"),
react_native_dep("third-party/android/support/v4:lib-support-v4"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_dep("third-party/java/okio:okio"),
react_native_target("java/com/facebook/react:react"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/modules/websocket:websocket"),
],
)

View File

@ -0,0 +1,180 @@
/**
* Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
*
* <p>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.
*/
package com.facebook.react.modules.blob;
import android.content.res.Resources;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.websocket.WebSocketModule;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import okio.ByteString;
@ReactModule(name = BlobModule.NAME)
public class BlobModule extends ReactContextBaseJavaModule {
protected static final String NAME = "BlobModule";
private final Map<String, byte[]> mBlobs = new HashMap<>();
protected final WebSocketModule.ContentHandler mContentHandler =
new WebSocketModule.ContentHandler() {
@Override
public void onMessage(String text, WritableMap params) {
params.putString("data", text);
}
@Override
public void onMessage(ByteString bytes, WritableMap params) {
byte[] data = bytes.toByteArray();
WritableMap blob = Arguments.createMap();
blob.putString("blobId", store(data));
blob.putInt("offset", 0);
blob.putInt("size", data.length);
params.putMap("data", blob);
params.putString("type", "blob");
}
};
public BlobModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
@Nullable
public Map getConstants() {
// The application can register BlobProvider as a ContentProvider so that blobs are resolvable.
// If it does, it needs to tell us what authority was used via this string resource.
Resources resources = getReactApplicationContext().getResources();
String packageName = getReactApplicationContext().getPackageName();
int resourceId = resources.getIdentifier("blob_provider_authority", "string", packageName);
if (resourceId == 0) {
return null;
}
return MapBuilder.of(
"BLOB_URI_SCHEME", "content", "BLOB_URI_HOST", resources.getString(resourceId));
}
public String store(byte[] data) {
String blobId = UUID.randomUUID().toString();
store(data, blobId);
return blobId;
}
public void store(byte[] data, String blobId) {
mBlobs.put(blobId, data);
}
public void remove(String blobId) {
mBlobs.remove(blobId);
}
@Nullable
public byte[] resolve(Uri uri) {
String blobId = uri.getLastPathSegment();
int offset = 0;
int size = -1;
String offsetParam = uri.getQueryParameter("offset");
if (offsetParam != null) {
offset = Integer.parseInt(offsetParam, 10);
}
String sizeParam = uri.getQueryParameter("size");
if (sizeParam != null) {
size = Integer.parseInt(sizeParam, 10);
}
return resolve(blobId, offset, size);
}
@Nullable
public byte[] resolve(String blobId, int offset, int size) {
byte[] data = mBlobs.get(blobId);
if (data == null) {
return null;
}
if (size == -1) {
size = data.length - offset;
}
if (offset > 0) {
data = Arrays.copyOfRange(data, offset, offset + size);
}
return data;
}
@Nullable
public byte[] resolve(ReadableMap blob) {
return resolve(blob.getString("blobId"), blob.getInt("offset"), blob.getInt("size"));
}
private WebSocketModule getWebSocketModule() {
return getReactApplicationContext().getNativeModule(WebSocketModule.class);
}
@ReactMethod
public void enableBlobSupport(final int id) {
getWebSocketModule().setContentHandler(id, mContentHandler);
}
@ReactMethod
public void disableBlobSupport(final int id) {
getWebSocketModule().setContentHandler(id, null);
}
@ReactMethod
public void sendBlob(ReadableMap blob, int id) {
byte[] data = resolve(blob.getString("blobId"), blob.getInt("offset"), blob.getInt("size"));
if (data != null) {
getWebSocketModule().sendBinary(ByteString.of(data), id);
} else {
getWebSocketModule().sendBinary((ByteString) null, id);
}
}
@ReactMethod
public void createFromParts(ReadableArray parts, String blobId) {
int totalBlobSize = 0;
ArrayList<ReadableMap> partList = new ArrayList<>(parts.size());
for (int i = 0; i < parts.size(); i++) {
ReadableMap part = parts.getMap(i);
totalBlobSize += part.getInt("size");
partList.add(i, part);
}
ByteBuffer buffer = ByteBuffer.allocate(totalBlobSize);
for (ReadableMap part : partList) {
buffer.put(resolve(part));
}
store(buffer.array(), blobId);
}
@ReactMethod
public void release(String blobId) {
remove(blobId);
}
}

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
*
* <p>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.
*/
package com.facebook.react.modules.blob;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.support.annotation.Nullable;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.ReactContext;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
public final class BlobProvider extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public @Nullable Cursor query(
Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public @Nullable String getType(Uri uri) {
return null;
}
@Override
public @Nullable Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if (!mode.equals("r")) {
throw new FileNotFoundException("Cannot open " + uri.toString() + " in mode '" + mode + "'");
}
BlobModule blobModule = null;
Context context = getContext().getApplicationContext();
if (context instanceof ReactApplication) {
ReactNativeHost host = ((ReactApplication) context).getReactNativeHost();
ReactContext reactContext = host.getReactInstanceManager().getCurrentReactContext();
blobModule = reactContext.getNativeModule(BlobModule.class);
}
if (blobModule == null) {
throw new RuntimeException("No blob module associated with BlobProvider");
}
byte[] data = blobModule.resolve(uri);
if (data == null) {
throw new FileNotFoundException("Cannot open " + uri.toString() + ", blob not found.");
}
ParcelFileDescriptor[] pipe;
try {
pipe = ParcelFileDescriptor.createPipe();
} catch (IOException exception) {
return null;
}
ParcelFileDescriptor readSide = pipe[0];
ParcelFileDescriptor writeSide = pipe[1];
OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide);
try {
outputStream.write(data);
outputStream.close();
} catch (IOException exception) {
return null;
}
return readSide;
}
}

View File

@ -9,16 +9,6 @@
package com.facebook.react.modules.websocket;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
@ -34,7 +24,14 @@ import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.network.ForwardingCookieHandler;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@ -43,9 +40,16 @@ import okhttp3.WebSocketListener;
import okio.ByteString;
@ReactModule(name = "WebSocketModule", hasConstants = false)
public class WebSocketModule extends ReactContextBaseJavaModule {
public final class WebSocketModule extends ReactContextBaseJavaModule {
public interface ContentHandler {
void onMessage(String text, WritableMap params);
void onMessage(ByteString byteString, WritableMap params);
}
private final Map<Integer, WebSocket> mWebSocketConnections = new HashMap<>();
private final Map<Integer, ContentHandler> mContentHandlers = new HashMap<>();
private ReactContext mReactContext;
private ForwardingCookieHandler mCookieHandler;
@ -67,6 +71,14 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
return "WebSocketModule";
}
public void setContentHandler(final int id, final ContentHandler contentHandler) {
if (contentHandler != null) {
mContentHandlers.put(id, contentHandler);
} else {
mContentHandlers.remove(id);
}
}
@ReactMethod
public void connect(
final String url,
@ -79,9 +91,7 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
.readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
.build();
Request.Builder builder = new Request.Builder()
.tag(id)
.url(url);
Request.Builder builder = new Request.Builder().tag(id).url(url);
String cookie = getCookie(url);
if (cookie != null) {
@ -124,49 +134,65 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
}
}
client.newWebSocket(builder.build(), new WebSocketListener() {
client.newWebSocket(
builder.build(),
new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
mWebSocketConnections.put(id, webSocket);
WritableMap params = Arguments.createMap();
params.putInt("id", id);
sendEvent("websocketOpen", params);
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
mWebSocketConnections.put(id, webSocket);
WritableMap params = Arguments.createMap();
params.putInt("id", id);
sendEvent("websocketOpen", params);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
WritableMap params = Arguments.createMap();
params.putInt("id", id);
params.putInt("code", code);
params.putString("reason", reason);
sendEvent("websocketClosed", params);
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
WritableMap params = Arguments.createMap();
params.putInt("id", id);
params.putInt("code", code);
params.putString("reason", reason);
sendEvent("websocketClosed", params);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
notifyWebSocketFailed(id, t.getMessage());
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
notifyWebSocketFailed(id, t.getMessage());
}
@Override
public void onMessage(WebSocket webSocket, String text) {
WritableMap params = Arguments.createMap();
params.putInt("id", id);
params.putString("data", text);
params.putString("type", "text");
sendEvent("websocketMessage", params);
}
@Override
public void onMessage(WebSocket webSocket, String text) {
WritableMap params = Arguments.createMap();
params.putInt("id", id);
params.putString("type", "text");
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
String text = bytes.base64();
WritableMap params = Arguments.createMap();
params.putInt("id", id);
params.putString("data", text);
params.putString("type", "binary");
sendEvent("websocketMessage", params);
}
});
ContentHandler contentHandler = mContentHandlers.get(id);
if (contentHandler != null) {
contentHandler.onMessage(text, params);
} else {
params.putString("data", text);
}
sendEvent("websocketMessage", params);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
WritableMap params = Arguments.createMap();
params.putInt("id", id);
params.putString("type", "binary");
ContentHandler contentHandler = mContentHandlers.get(id);
if (contentHandler != null) {
contentHandler.onMessage(bytes, params);
} else {
String text = bytes.base64();
params.putString("data", text);
}
sendEvent("websocketMessage", params);
}
});
// Trigger shutdown of the dispatcher's executor so this process can exit cleanly
client.dispatcher().executorService().shutdown();
@ -183,6 +209,7 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
try {
client.close(code, reason);
mWebSocketConnections.remove(id);
mContentHandlers.remove(id);
} catch (Exception e) {
FLog.e(
ReactConstants.TAG,
@ -219,6 +246,19 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
}
}
public void sendBinary(ByteString byteString, int id) {
WebSocket client = mWebSocketConnections.get(id);
if (client == null) {
// This is a programmer error
throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
}
try {
client.send(byteString);
} catch (Exception e) {
notifyWebSocketFailed(id, e.getMessage());
}
}
@ReactMethod
public void ping(int id) {
WebSocket client = mWebSocketConnections.get(id);
@ -243,10 +283,9 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
/**
* Get the default HTTP(S) origin for a specific WebSocket URI
*
* @param String uri
* @param uri
* @return A string of the endpoint converted to HTTP protocol (http[s]://host[:port])
*/
private static String getDefaultOrigin(String uri) {
try {
String defaultOrigin;
@ -280,7 +319,7 @@ public class WebSocketModule extends ReactContextBaseJavaModule {
/**
* Get the cookie for a specific domain
*
* @param String uri
* @param uri
* @return The cookie header or null if none is set
*/
private String getCookie(String uri) {

View File

@ -23,6 +23,7 @@ android_library(
react_native_target("java/com/facebook/react/module/model:model"),
react_native_target("java/com/facebook/react/modules/accessibilityinfo:accessibilityinfo"),
react_native_target("java/com/facebook/react/modules/appstate:appstate"),
react_native_target("java/com/facebook/react/modules/blob:blob"),
react_native_target("java/com/facebook/react/modules/camera:camera"),
react_native_target("java/com/facebook/react/modules/clipboard:clipboard"),
react_native_target("java/com/facebook/react/modules/core:core"),

View File

@ -13,7 +13,6 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.facebook.react.LazyReactPackage;
import com.facebook.react.animated.NativeAnimatedModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
@ -30,6 +29,7 @@ import com.facebook.react.flat.RCTVirtualTextManager;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.modules.accessibilityinfo.AccessibilityInfoModule;
import com.facebook.react.modules.appstate.AppStateModule;
import com.facebook.react.modules.blob.BlobModule;
import com.facebook.react.modules.camera.CameraRollManager;
import com.facebook.react.modules.camera.ImageEditingManager;
import com.facebook.react.modules.camera.ImageStoreManager;
@ -74,12 +74,10 @@ import com.facebook.react.views.toolbar.ReactToolbarManager;
import com.facebook.react.views.view.ReactViewManager;
import com.facebook.react.views.viewpager.ReactViewPagerManager;
import com.facebook.react.views.webview.ReactWebViewManager;
import javax.inject.Provider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.inject.Provider;
/**
* Package defining basic modules and view managers.
@ -101,150 +99,207 @@ public class MainReactPackage extends LazyReactPackage {
@Override
public List<ModuleSpec> getNativeModules(final ReactApplicationContext context) {
return Arrays.asList(
new ModuleSpec(AccessibilityInfoModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AccessibilityInfoModule(context);
}
}),
new ModuleSpec(AppStateModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AppStateModule(context);
}
}),
new ModuleSpec(AsyncStorageModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AsyncStorageModule(context);
}
}),
new ModuleSpec(CameraRollManager.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new CameraRollManager(context);
}
}),
new ModuleSpec(ClipboardModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ClipboardModule(context);
}
}),
new ModuleSpec(DatePickerDialogModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new DatePickerDialogModule(context);
}
}),
new ModuleSpec(DialogModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new DialogModule(context);
}
}),
new ModuleSpec(FrescoModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FrescoModule(context, true, mConfig != null ? mConfig.getFrescoConfig() : null);
}
}),
new ModuleSpec(I18nManagerModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new I18nManagerModule(context);
}
}),
new ModuleSpec(ImageEditingManager.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ImageEditingManager(context);
}
}),
new ModuleSpec(ImageLoaderModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ImageLoaderModule(context);
}
}),
new ModuleSpec(ImageStoreManager.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ImageStoreManager(context);
}
}),
new ModuleSpec(IntentModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new IntentModule(context);
}
}),
new ModuleSpec(LocationModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new LocationModule(context);
}
}),
new ModuleSpec(NativeAnimatedModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new NativeAnimatedModule(context);
}
}),
new ModuleSpec(NetworkingModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new NetworkingModule(context);
}
}),
new ModuleSpec(NetInfoModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new NetInfoModule(context);
}
}),
new ModuleSpec(PermissionsModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new PermissionsModule(context);
}
}),
new ModuleSpec(ShareModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ShareModule(context);
}
}),
new ModuleSpec(StatusBarModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new StatusBarModule(context);
}
}),
new ModuleSpec(TimePickerDialogModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new TimePickerDialogModule(context);
}
}),
new ModuleSpec(ToastModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ToastModule(context);
}
}),
new ModuleSpec(VibrationModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new VibrationModule(context);
}
}),
new ModuleSpec(WebSocketModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new WebSocketModule(context);
}
}));
new ModuleSpec(
AccessibilityInfoModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AccessibilityInfoModule(context);
}
}),
new ModuleSpec(
AppStateModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AppStateModule(context);
}
}),
new ModuleSpec(
BlobModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new BlobModule(context);
}
}),
new ModuleSpec(
AsyncStorageModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new AsyncStorageModule(context);
}
}),
new ModuleSpec(
CameraRollManager.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new CameraRollManager(context);
}
}),
new ModuleSpec(
ClipboardModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ClipboardModule(context);
}
}),
new ModuleSpec(
DatePickerDialogModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new DatePickerDialogModule(context);
}
}),
new ModuleSpec(
DialogModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new DialogModule(context);
}
}),
new ModuleSpec(
FrescoModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FrescoModule(
context, true, mConfig != null ? mConfig.getFrescoConfig() : null);
}
}),
new ModuleSpec(
I18nManagerModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new I18nManagerModule(context);
}
}),
new ModuleSpec(
ImageEditingManager.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ImageEditingManager(context);
}
}),
new ModuleSpec(
ImageLoaderModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ImageLoaderModule(context);
}
}),
new ModuleSpec(
ImageStoreManager.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ImageStoreManager(context);
}
}),
new ModuleSpec(
IntentModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new IntentModule(context);
}
}),
new ModuleSpec(
LocationModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new LocationModule(context);
}
}),
new ModuleSpec(
NativeAnimatedModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new NativeAnimatedModule(context);
}
}),
new ModuleSpec(
NetworkingModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new NetworkingModule(context);
}
}),
new ModuleSpec(
NetInfoModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new NetInfoModule(context);
}
}),
new ModuleSpec(
PermissionsModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new PermissionsModule(context);
}
}),
new ModuleSpec(
ShareModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ShareModule(context);
}
}),
new ModuleSpec(
StatusBarModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new StatusBarModule(context);
}
}),
new ModuleSpec(
TimePickerDialogModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new TimePickerDialogModule(context);
}
}),
new ModuleSpec(
ToastModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new ToastModule(context);
}
}),
new ModuleSpec(
VibrationModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new VibrationModule(context);
}
}),
new ModuleSpec(
WebSocketModule.class,
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new WebSocketModule(context);
}
}));
}
@Override

View File

@ -36,6 +36,7 @@
2DCD954D1E0B4F2C00145EB5 /* HelloWorldTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* HelloWorldTests.m */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -228,6 +229,13 @@
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 358F4ED71D1E81A9004DF814;
remoteInfo = RCTBlob;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@ -255,6 +263,7 @@
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTBlob.xcodeproj; path = "../node_modules/react-native/Libraries/Blob/RCTBlob.xcodeproj"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -270,6 +279,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
ADBDB9381DFEBF1600ED6528 /* libRCTBlob.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
146834051AC3E58100842450 /* libReact.a in Frameworks */,
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
@ -411,6 +422,7 @@
3DAD3EAB1DF850E9000B6D8A /* libcxxreact.a */,
3DAD3EAD1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EAF1DF850E9000B6D8A /* libjschelpers.a */,
3DAD3EA31DF850E9000B6D8A /* libReact-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@ -439,6 +451,7 @@
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */,
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
@ -483,6 +496,14 @@
name = Products;
sourceTree = "<group>";
};
ADBDB9201DFEBF0600ED6528 /* Products */ = {
isa = PBXGroup;
children = (
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -602,6 +623,10 @@
ProductGroup = 5E91572E1DD0AC6500FF2AA8 /* Products */;
ProjectRef = 5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */;
},
{
ProductGroup = ADBDB9201DFEBF0600ED6528 /* Products */;
ProjectRef = ADBDB91F1DFEBF0600ED6528 /* RCTBlob.xcodeproj */;
},
{
ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
@ -748,10 +773,10 @@
remoteRef = 3DAD3E981DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
3DAD3EA31DF850E9000B6D8A /* libReact.a */ = {
3DAD3EA31DF850E9000B6D8A /* libReact-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libReact.a;
path = "libReact-tvOS.a";
remoteRef = 3DAD3EA21DF850E9000B6D8A /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@ -825,6 +850,13 @@
remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
ADBDB9271DFEBF0700ED6528 /* libRCTBlob.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTBlob.a;
remoteRef = ADBDB9261DFEBF0700ED6528 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */