first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeWebSocketModule';
import NativeWebSocketModule from '../../src/private/specs_DEPRECATED/modules/NativeWebSocketModule';
export default NativeWebSocketModule;

View File

@@ -0,0 +1,37 @@
/*
* 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.
*/
#import <React/RCTDefines.h>
#if RCT_DEV // Only supported in dev mode
@class RCTReconnectingWebSocket;
@protocol RCTReconnectingWebSocketDelegate
- (void)reconnectingWebSocketDidOpen:(RCTReconnectingWebSocket *)webSocket;
- (void)reconnectingWebSocket:(RCTReconnectingWebSocket *)webSocket didReceiveMessage:(id)message;
/** Sent when the socket has closed due to error or clean shutdown. An automatic reconnect will start shortly. */
- (void)reconnectingWebSocketDidClose:(RCTReconnectingWebSocket *)webSocket;
@end
@interface RCTReconnectingWebSocket : NSObject
/** Delegate will be messaged on the given queue (required). */
- (instancetype)initWithURL:(NSURL *)url queue:(dispatch_queue_t)queue;
@property (nonatomic, weak) id<RCTReconnectingWebSocketDelegate> delegate;
- (void)send:(id)data;
- (void)start;
- (void)stop;
- (instancetype)initWithURL:(NSURL *)url __deprecated_msg("Use initWithURL:queue: instead");
/** @brief Must be set before -start to have effect */
@property (nonatomic, strong) dispatch_queue_t delegateDispatchQueue __deprecated_msg("Use initWithURL:queue: instead");
@end
#endif

View File

@@ -0,0 +1,109 @@
/*
* 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.
*/
#import <React/RCTReconnectingWebSocket.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <SocketRocket/SRWebSocket.h>
#if RCT_DEV // Only supported in dev mode
@interface RCTReconnectingWebSocket () <SRWebSocketDelegate>
@end
@implementation RCTReconnectingWebSocket {
NSURL *_url;
SRWebSocket *_socket;
BOOL _stopped;
}
- (instancetype)initWithURL:(NSURL *)url queue:(dispatch_queue_t)queue
{
if (self = [super init]) {
_url = url;
_delegateDispatchQueue = queue;
}
return self;
}
- (instancetype)initWithURL:(NSURL *)url
{
return [self initWithURL:url queue:dispatch_get_main_queue()];
}
- (void)send:(id)data
{
[_socket sendData:data error:nil];
}
- (void)start
{
[self stop];
_stopped = NO;
_socket = [[SRWebSocket alloc] initWithURL:_url];
_socket.delegate = self;
[_socket setDelegateDispatchQueue:_delegateDispatchQueue];
[_socket open];
}
- (void)stop
{
_stopped = YES;
_socket.delegate = nil;
[_socket closeWithCode:1000 reason:@"Invalidated"];
_socket = nil;
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
[_delegate reconnectingWebSocket:self didReceiveMessage:message];
}
- (void)reconnect
{
if (_stopped) {
return;
}
__weak SRWebSocket *socket = _socket;
__weak __typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf start];
if (!socket) {
[weakSelf reconnect];
}
});
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{
[_delegate reconnectingWebSocketDidOpen:self];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error
{
[_delegate reconnectingWebSocketDidClose:self];
if ([error code] != ECONNREFUSED) {
[self reconnect];
}
}
- (void)webSocket:(SRWebSocket *)webSocket
didCloseWithCode:(NSInteger)code
reason:(NSString *)reason
wasClean:(BOOL)wasClean
{
[_delegate reconnectingWebSocketDidClose:self];
[self reconnect];
}
@end
#endif

View File

@@ -0,0 +1,325 @@
/**
* 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.
*
* @flow
* @format
*/
import type {EventCallback} from '../../src/private/webapis/dom/events/EventTarget';
import type {BlobData} from '../Blob/BlobTypes';
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import Event from '../../src/private/webapis/dom/events/Event';
import {
getEventHandlerAttribute,
setEventHandlerAttribute,
} from '../../src/private/webapis/dom/events/EventHandlerAttributes';
import EventTarget from '../../src/private/webapis/dom/events/EventTarget';
import MessageEvent from '../../src/private/webapis/html/events/MessageEvent';
import CloseEvent from '../../src/private/webapis/websockets/events/CloseEvent';
import Blob from '../Blob/Blob';
import BlobManager from '../Blob/BlobManager';
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import binaryToBase64 from '../Utilities/binaryToBase64';
import Platform from '../Utilities/Platform';
import NativeWebSocketModule from './NativeWebSocketModule';
import base64 from 'base64-js';
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;
// Abnormal closure where no code is provided in a control frame
// https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
const CLOSE_ABNORMAL = 1006;
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 {
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;
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 || {};
// 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 MessageEvent('message', {data}));
}),
this._eventEmitter.addListener('websocketOpen', ev => {
if (ev.id !== this._socketId) {
return;
}
this.readyState = this.OPEN;
this.protocol = ev.protocol;
this.dispatchEvent(new Event('open'));
}),
this._eventEmitter.addListener('websocketClosed', ev => {
if (ev.id !== this._socketId) {
return;
}
this.readyState = this.CLOSED;
this.dispatchEvent(
new CloseEvent('close', {
code: ev.code,
reason: ev.reason,
// TODO: missing `wasClean` (exposed on iOS as `clean` but missing on Android)
}),
);
this._unregisterEvents();
this.close();
}),
this._eventEmitter.addListener('websocketFailed', ev => {
if (ev.id !== this._socketId) {
return;
}
this.readyState = this.CLOSED;
this.dispatchEvent(new Event('error'));
this.dispatchEvent(
new CloseEvent('close', {
code: CLOSE_ABNORMAL,
reason: ev.message,
// TODO: Expose `wasClean`
}),
);
this._unregisterEvents();
this.close();
}),
];
}
get onclose(): EventCallback | null {
return getEventHandlerAttribute(this, 'close');
}
set onclose(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'close', listener);
}
get onerror(): EventCallback | null {
return getEventHandlerAttribute(this, 'error');
}
set onerror(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'error', listener);
}
get onmessage(): EventCallback | null {
return getEventHandlerAttribute(this, 'message');
}
set onmessage(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'message', listener);
}
get onopen(): EventCallback | null {
return getEventHandlerAttribute(this, 'open');
}
set onopen(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'open', listener);
}
}
export default WebSocket;

View File

@@ -0,0 +1,277 @@
/**
* 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.
*
* @flow strict-local
* @format
*/
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import Platform from '../Utilities/Platform';
import NativeWebSocketModule from './NativeWebSocketModule';
import base64 from 'base64-js';
const originalRCTWebSocketConnect = NativeWebSocketModule.connect;
const originalRCTWebSocketSend = NativeWebSocketModule.send;
const originalRCTWebSocketSendBinary = NativeWebSocketModule.sendBinary;
const originalRCTWebSocketClose = NativeWebSocketModule.close;
let eventEmitter;
let subscriptions;
let closeCallback;
let sendCallback;
let connectCallback;
let onOpenCallback;
let onMessageCallback;
let onErrorCallback;
let onCloseCallback;
let isInterceptorEnabled = false;
/**
* A network interceptor which monkey-patches RCTWebSocketModule methods
* to gather all websocket network requests/responses, in order to show
* their information in the React Native inspector development tool.
*/
const WebSocketInterceptor = {
/**
* Invoked when RCTWebSocketModule.close(...) is called.
* The callback is invoked with the following parameters:
* - `'code'` - The status code explaining why the connection was closed,
* - `'reason'` - The reason explaining why the connection was closed,
* - `'socketId'` - The id of the socket.
*/
setCloseCallback(callback: ?(number | null, string | null, number) => void) {
closeCallback = callback;
},
/**
* Invoked when RCTWebSocketModule.send(...) or sendBinary(...) is called.
* The callback is invoked with the following parameters:
* - `'data'` - The data sent,
* - `'socketId'` - The id of the socket.
*/
setSendCallback(callback: ?(string | ArrayBuffer, number) => void) {
sendCallback = callback;
},
/**
* Invoked when RCTWebSocketModule.connect(...) is called.
* The callback is invoked with the following parameters:
* - `'url'` - The url of the socket,
* - `'protocols'` - The protocols of the socket,
* - `'options'` - The options of the socket,
* - `'socketId'` - The id of the socket.
*/
setConnectCallback(
callback: ?(string, Array<string> | null, Array<string>, number) => void,
) {
connectCallback = callback;
},
/**
* Invoked when event "websocketOpen" happens.
* The callback is invoked with the following parameters:
* - `'socketId'` - The id of the socket.
*/
setOnOpenCallback(callback: ?(number) => void) {
onOpenCallback = callback;
},
/**
* Invoked when event "websocketMessage" happens.
* The callback is invoked with the following parameters:
* - `'data'` - The data received,
* - `'socketId'` - The id of the socket.
*/
setOnMessageCallback(callback: ?(string | ArrayBuffer, number) => void) {
onMessageCallback = callback;
},
/**
* Invoked when event "websocketFailed" happens.
* The callback is invoked with the following parameters:
* - `'message'` - The error message,
* - `'socketId'` - The id of the socket.
*/
setOnErrorCallback(callback: ?({message: string}, number) => void) {
onErrorCallback = callback;
},
/**
* Invoked when event "websocketClosed" happens.
* The callback is invoked with the following parameters:
* - `'code'` - The status code explaining why the connection was closed,
* - `'reason'` - The reason explaining why the connection was closed,
* - `'socketId'` - The id of the socket.
*/
setOnCloseCallback(
callback: ?({code: number, reason: ?string}, number) => void,
) {
onCloseCallback = callback;
},
isInterceptorEnabled(): boolean {
return isInterceptorEnabled;
},
_unregisterEvents() {
subscriptions.forEach(e => e.remove());
subscriptions = [];
},
/**
* Add listeners to the RCTWebSocketModule events to intercept them.
*/
_registerEvents() {
subscriptions = [
// $FlowFixMe[incompatible-type]
eventEmitter.addListener('websocketMessage', ev => {
if (onMessageCallback) {
onMessageCallback(
ev.type === 'binary'
? WebSocketInterceptor._arrayBufferToString(ev.data)
: ev.data,
ev.id,
);
}
}),
// $FlowFixMe[incompatible-type]
eventEmitter.addListener('websocketOpen', ev => {
if (onOpenCallback) {
onOpenCallback(ev.id);
}
}),
// $FlowFixMe[incompatible-type]
eventEmitter.addListener('websocketClosed', ev => {
if (onCloseCallback) {
onCloseCallback({code: ev.code, reason: ev.reason}, ev.id);
}
}),
// $FlowFixMe[incompatible-type]
eventEmitter.addListener('websocketFailed', ev => {
if (onErrorCallback) {
onErrorCallback({message: ev.message}, ev.id);
}
}),
];
},
enableInterception() {
if (isInterceptorEnabled) {
return;
}
// $FlowFixMe[underconstrained-implicit-instantiation]
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,
);
WebSocketInterceptor._registerEvents();
// Override `connect` method for all RCTWebSocketModule requests
// to intercept the request url, protocols, options and socketId,
// then pass them through the `connectCallback`.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
NativeWebSocketModule.connect = function (
url: string,
protocols: Array<string> | null,
options: $FlowFixMe,
socketId: number,
) {
if (connectCallback) {
connectCallback(url, protocols, options, socketId);
}
originalRCTWebSocketConnect.apply(this, arguments);
};
// Override `send` method for all RCTWebSocketModule requests to intercept
// the data sent, then pass them through the `sendCallback`.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
NativeWebSocketModule.send = function (data, socketId) {
if (sendCallback) {
sendCallback(data, socketId);
}
originalRCTWebSocketSend.apply(this, arguments);
};
// Override `sendBinary` method for all RCTWebSocketModule requests to
// intercept the data sent, then pass them through the `sendCallback`.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
NativeWebSocketModule.sendBinary = function (data, socketId) {
if (sendCallback) {
sendCallback(WebSocketInterceptor._arrayBufferToString(data), socketId);
}
originalRCTWebSocketSendBinary.apply(this, arguments);
};
// Override `close` method for all RCTWebSocketModule requests to intercept
// the close information, then pass them through the `closeCallback`.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
NativeWebSocketModule.close = function () {
if (closeCallback) {
if (arguments.length === 3) {
closeCallback(arguments[0], arguments[1], arguments[2]);
} else {
closeCallback(null, null, arguments[0]);
}
}
originalRCTWebSocketClose.apply(this, arguments);
};
isInterceptorEnabled = true;
},
_arrayBufferToString(data: string): ArrayBuffer | string {
const value = base64.toByteArray(data).buffer;
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
if (value === undefined || value === null) {
return '(no value)';
}
if (
typeof ArrayBuffer !== 'undefined' &&
typeof Uint8Array !== 'undefined' &&
value instanceof ArrayBuffer
) {
return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`;
}
return value;
},
// Unpatch RCTWebSocketModule methods and remove the callbacks.
disableInterception() {
if (!isInterceptorEnabled) {
return;
}
isInterceptorEnabled = false;
// $FlowFixMe[cannot-write]
NativeWebSocketModule.send = originalRCTWebSocketSend;
// $FlowFixMe[cannot-write]
NativeWebSocketModule.sendBinary = originalRCTWebSocketSendBinary;
// $FlowFixMe[cannot-write]
NativeWebSocketModule.close = originalRCTWebSocketClose;
// $FlowFixMe[cannot-write]
NativeWebSocketModule.connect = originalRCTWebSocketConnect;
connectCallback = null;
closeCallback = null;
sendCallback = null;
onOpenCallback = null;
onMessageCallback = null;
onCloseCallback = null;
onErrorCallback = null;
WebSocketInterceptor._unregisterEvents();
},
};
export default WebSocketInterceptor;