mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
20d9d3aa6a
Summary:
We are working on making the empty object literal `{}` have the type `{}` - i.e. exact empty object - rather than being unsealed.
Some manual fixes, in particular to React Native code, which is used and can be synced to other repos (e.g. WWW).
With these changes, error diff in Xplat is down to ~1990 errors
Note that after I roll out `exact_empty_objects`, I'll codemod all the `{...null}` (the only way to get an exact empty object currently) back to `{}`
Changelog: [Internal]
Reviewed By: SamChou19815
Differential Revision: D36142838
fbshipit-source-id: 054caf370db230f42a4c5f5706c88979ef246537
290 lines
8.5 KiB
JavaScript
290 lines
8.5 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
* @flow
|
|
*/
|
|
|
|
import Blob from '../Blob/Blob';
|
|
import type {BlobData} from '../Blob/BlobTypes';
|
|
import BlobManager from '../Blob/BlobManager';
|
|
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
|
|
import binaryToBase64 from '../Utilities/binaryToBase64';
|
|
import Platform from '../Utilities/Platform';
|
|
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
|
|
import NativeWebSocketModule from './NativeWebSocketModule';
|
|
import WebSocketEvent from './WebSocketEvent';
|
|
import base64 from 'base64-js';
|
|
import EventTarget from 'event-target-shim';
|
|
import invariant from 'invariant';
|
|
|
|
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;
|
|
const CLOSED = 3;
|
|
|
|
const CLOSE_NORMAL = 1000;
|
|
|
|
const WEBSOCKET_EVENTS = ['close', 'error', 'message', 'open'];
|
|
|
|
let nextWebSocketId = 0;
|
|
|
|
type WebSocketEventDefinitions = {
|
|
websocketOpen: [{id: number, protocol: string}],
|
|
websocketClosed: [{id: number, code: number, reason: string}],
|
|
websocketMessage: [
|
|
| {type: 'binary', id: number, data: string}
|
|
| {type: 'text', id: number, data: string}
|
|
| {type: 'blob', id: number, data: BlobData},
|
|
],
|
|
websocketFailed: [{id: number, message: string}],
|
|
};
|
|
|
|
/**
|
|
* Browser-compatible WebSockets implementation.
|
|
*
|
|
* See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
|
* See https://github.com/websockets/ws
|
|
*/
|
|
class WebSocket extends (EventTarget(...WEBSOCKET_EVENTS): any) {
|
|
static CONNECTING: number = CONNECTING;
|
|
static OPEN: number = OPEN;
|
|
static CLOSING: number = CLOSING;
|
|
static CLOSED: number = CLOSED;
|
|
|
|
CONNECTING: number = CONNECTING;
|
|
OPEN: number = OPEN;
|
|
CLOSING: number = CLOSING;
|
|
CLOSED: number = CLOSED;
|
|
|
|
_socketId: number;
|
|
_eventEmitter: NativeEventEmitter<WebSocketEventDefinitions>;
|
|
_subscriptions: Array<EventSubscription>;
|
|
_binaryType: ?BinaryType;
|
|
|
|
onclose: ?Function;
|
|
onerror: ?Function;
|
|
onmessage: ?Function;
|
|
onopen: ?Function;
|
|
|
|
bufferedAmount: number;
|
|
extension: ?string;
|
|
protocol: ?string;
|
|
readyState: number = CONNECTING;
|
|
url: ?string;
|
|
|
|
constructor(
|
|
url: string,
|
|
protocols: ?string | ?Array<string>,
|
|
options: ?{headers?: {origin?: string, ...}, ...},
|
|
) {
|
|
super();
|
|
this.url = url;
|
|
if (typeof protocols === 'string') {
|
|
protocols = [protocols];
|
|
}
|
|
|
|
const {headers = {}, ...unrecognized} = options || {...null};
|
|
|
|
// Preserve deprecated backwards compatibility for the 'origin' option
|
|
// $FlowFixMe[prop-missing]
|
|
if (unrecognized && typeof unrecognized.origin === 'string') {
|
|
console.warn(
|
|
'Specifying `origin` as a WebSocket connection option is deprecated. Include it under `headers` instead.',
|
|
);
|
|
/* $FlowFixMe[prop-missing] (>=0.54.0 site=react_native_fb,react_native_
|
|
* oss) This comment suppresses an error found when Flow v0.54 was
|
|
* deployed. To see the error delete this comment and run Flow. */
|
|
headers.origin = unrecognized.origin;
|
|
/* $FlowFixMe[prop-missing] (>=0.54.0 site=react_native_fb,react_native_
|
|
* oss) This comment suppresses an error found when Flow v0.54 was
|
|
* deployed. To see the error delete this comment and run Flow. */
|
|
delete unrecognized.origin;
|
|
}
|
|
|
|
// Warn about and discard anything else
|
|
if (Object.keys(unrecognized).length > 0) {
|
|
console.warn(
|
|
'Unrecognized WebSocket connection option(s) `' +
|
|
Object.keys(unrecognized).join('`, `') +
|
|
'`. ' +
|
|
'Did you mean to put these under `headers`?',
|
|
);
|
|
}
|
|
|
|
if (!Array.isArray(protocols)) {
|
|
protocols = null;
|
|
}
|
|
|
|
this._eventEmitter = new NativeEventEmitter(
|
|
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
|
|
// If you want to use the native module on other platforms, please remove this condition and test its behavior
|
|
Platform.OS !== 'ios' ? null : NativeWebSocketModule,
|
|
);
|
|
this._socketId = nextWebSocketId++;
|
|
this._registerEvents();
|
|
NativeWebSocketModule.connect(url, protocols, {headers}, 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') {
|
|
invariant(
|
|
BlobManager.isAvailable,
|
|
'Native module BlobModule is required for blob support',
|
|
);
|
|
if (binaryType === 'blob') {
|
|
BlobManager.addWebSocketHandler(this._socketId);
|
|
} else {
|
|
BlobManager.removeWebSocketHandler(this._socketId);
|
|
}
|
|
}
|
|
this._binaryType = binaryType;
|
|
}
|
|
|
|
close(code?: number, reason?: string): void {
|
|
if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
|
|
return;
|
|
}
|
|
|
|
this.readyState = this.CLOSING;
|
|
this._close(code, reason);
|
|
}
|
|
|
|
send(data: string | ArrayBuffer | ArrayBufferView | Blob): void {
|
|
if (this.readyState === this.CONNECTING) {
|
|
throw new Error('INVALID_STATE_ERR');
|
|
}
|
|
|
|
if (data instanceof Blob) {
|
|
invariant(
|
|
BlobManager.isAvailable,
|
|
'Native module BlobModule is required for blob support',
|
|
);
|
|
BlobManager.sendOverSocket(data, this._socketId);
|
|
return;
|
|
}
|
|
|
|
if (typeof data === 'string') {
|
|
NativeWebSocketModule.send(data, this._socketId);
|
|
return;
|
|
}
|
|
|
|
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
|
NativeWebSocketModule.sendBinary(binaryToBase64(data), this._socketId);
|
|
return;
|
|
}
|
|
|
|
throw new Error('Unsupported data type');
|
|
}
|
|
|
|
ping(): void {
|
|
if (this.readyState === this.CONNECTING) {
|
|
throw new Error('INVALID_STATE_ERR');
|
|
}
|
|
|
|
NativeWebSocketModule.ping(this._socketId);
|
|
}
|
|
|
|
_close(code?: number, reason?: string): void {
|
|
// 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 : '';
|
|
NativeWebSocketModule.close(statusCode, closeReason, this._socketId);
|
|
|
|
if (BlobManager.isAvailable && this._binaryType === 'blob') {
|
|
BlobManager.removeWebSocketHandler(this._socketId);
|
|
}
|
|
}
|
|
|
|
_unregisterEvents(): void {
|
|
this._subscriptions.forEach(e => e.remove());
|
|
this._subscriptions = [];
|
|
}
|
|
|
|
_registerEvents(): void {
|
|
this._subscriptions = [
|
|
this._eventEmitter.addListener('websocketMessage', ev => {
|
|
if (ev.id !== this._socketId) {
|
|
return;
|
|
}
|
|
let data: Blob | BlobData | ArrayBuffer | string = ev.data;
|
|
switch (ev.type) {
|
|
case 'binary':
|
|
data = base64.toByteArray(ev.data).buffer;
|
|
break;
|
|
case 'blob':
|
|
data = BlobManager.createFromOptions(ev.data);
|
|
break;
|
|
}
|
|
this.dispatchEvent(new WebSocketEvent('message', {data}));
|
|
}),
|
|
this._eventEmitter.addListener('websocketOpen', ev => {
|
|
if (ev.id !== this._socketId) {
|
|
return;
|
|
}
|
|
this.readyState = this.OPEN;
|
|
this.protocol = ev.protocol;
|
|
this.dispatchEvent(new WebSocketEvent('open'));
|
|
}),
|
|
this._eventEmitter.addListener('websocketClosed', ev => {
|
|
if (ev.id !== this._socketId) {
|
|
return;
|
|
}
|
|
this.readyState = this.CLOSED;
|
|
this.dispatchEvent(
|
|
new WebSocketEvent('close', {
|
|
code: ev.code,
|
|
reason: ev.reason,
|
|
}),
|
|
);
|
|
this._unregisterEvents();
|
|
this.close();
|
|
}),
|
|
this._eventEmitter.addListener('websocketFailed', ev => {
|
|
if (ev.id !== this._socketId) {
|
|
return;
|
|
}
|
|
this.readyState = this.CLOSED;
|
|
this.dispatchEvent(
|
|
new WebSocketEvent('error', {
|
|
message: ev.message,
|
|
}),
|
|
);
|
|
this.dispatchEvent(
|
|
new WebSocketEvent('close', {
|
|
message: ev.message,
|
|
}),
|
|
);
|
|
this._unregisterEvents();
|
|
this.close();
|
|
}),
|
|
];
|
|
}
|
|
}
|
|
|
|
module.exports = WebSocket;
|