1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | 'use strict';
|
12 |
|
13 | const Blob = require('Blob');
|
14 | const EventTarget = require('event-target-shim');
|
15 | const NativeEventEmitter = require('NativeEventEmitter');
|
16 | const BlobManager = require('BlobManager');
|
17 | const NativeModules = require('NativeModules');
|
18 | const Platform = require('Platform');
|
19 | const WebSocketEvent = require('WebSocketEvent');
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | const base64 = require('base64-js');
|
25 | const binaryToBase64 = require('binaryToBase64');
|
26 | const invariant = require('invariant');
|
27 |
|
28 | const {WebSocketModule} = NativeModules;
|
29 |
|
30 | import type EventSubscription from 'EventSubscription';
|
31 |
|
32 | type ArrayBufferView =
|
33 | | Int8Array
|
34 | | Uint8Array
|
35 | | Uint8ClampedArray
|
36 | | Int16Array
|
37 | | Uint16Array
|
38 | | Int32Array
|
39 | | Uint32Array
|
40 | | Float32Array
|
41 | | Float64Array
|
42 | | DataView;
|
43 |
|
44 | type BinaryType = 'blob' | 'arraybuffer';
|
45 |
|
46 | const CONNECTING = 0;
|
47 | const OPEN = 1;
|
48 | const CLOSING = 2;
|
49 | const CLOSED = 3;
|
50 |
|
51 | const CLOSE_NORMAL = 1000;
|
52 |
|
53 | const WEBSOCKET_EVENTS = ['close', 'error', 'message', 'open'];
|
54 |
|
55 | let nextWebSocketId = 0;
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | class WebSocket extends EventTarget(...WEBSOCKET_EVENTS) {
|
64 | static CONNECTING = CONNECTING;
|
65 | static OPEN = OPEN;
|
66 | static CLOSING = CLOSING;
|
67 | static CLOSED = CLOSED;
|
68 |
|
69 | CONNECTING: number = CONNECTING;
|
70 | OPEN: number = OPEN;
|
71 | CLOSING: number = CLOSING;
|
72 | CLOSED: number = CLOSED;
|
73 |
|
74 | _socketId: number;
|
75 | _eventEmitter: NativeEventEmitter;
|
76 | _subscriptions: Array<EventSubscription>;
|
77 | _binaryType: ?BinaryType;
|
78 |
|
79 | onclose: ?Function;
|
80 | onerror: ?Function;
|
81 | onmessage: ?Function;
|
82 | onopen: ?Function;
|
83 |
|
84 | bufferedAmount: number;
|
85 | extension: ?string;
|
86 | protocol: ?string;
|
87 | readyState: number = CONNECTING;
|
88 | url: ?string;
|
89 |
|
90 |
|
91 |
|
92 | static isAvailable: boolean = !!WebSocketModule;
|
93 |
|
94 | constructor(
|
95 | url: string,
|
96 | protocols: ?string | ?Array<string>,
|
97 | options: ?{headers?: {origin?: string}},
|
98 | ) {
|
99 | super();
|
100 | if (typeof protocols === 'string') {
|
101 | protocols = [protocols];
|
102 | }
|
103 |
|
104 | const {headers = {}, ...unrecognized} = options || {};
|
105 |
|
106 |
|
107 | |
108 |
|
109 |
|
110 | if (unrecognized && typeof unrecognized.origin === 'string') {
|
111 | console.warn(
|
112 | 'Specifying `origin` as a WebSocket connection option is deprecated. Include it under `headers` instead.',
|
113 | );
|
114 | |
115 |
|
116 |
|
117 | headers.origin = unrecognized.origin;
|
118 | |
119 |
|
120 |
|
121 | delete unrecognized.origin;
|
122 | }
|
123 |
|
124 |
|
125 | if (Object.keys(unrecognized).length > 0) {
|
126 | console.warn(
|
127 | 'Unrecognized WebSocket connection option(s) `' +
|
128 | Object.keys(unrecognized).join('`, `') +
|
129 | '`. ' +
|
130 | 'Did you mean to put these under `headers`?',
|
131 | );
|
132 | }
|
133 |
|
134 | if (!Array.isArray(protocols)) {
|
135 | protocols = null;
|
136 | }
|
137 |
|
138 | if (!WebSocket.isAvailable) {
|
139 | throw new Error(
|
140 | 'Cannot initialize WebSocket module. ' +
|
141 | 'Native module WebSocketModule is missing.',
|
142 | );
|
143 | }
|
144 |
|
145 | this._eventEmitter = new NativeEventEmitter(WebSocketModule);
|
146 | this._socketId = nextWebSocketId++;
|
147 | this._registerEvents();
|
148 | WebSocketModule.connect(
|
149 | url,
|
150 | protocols,
|
151 | {headers},
|
152 | this._socketId,
|
153 | );
|
154 | }
|
155 |
|
156 | get binaryType(): ?BinaryType {
|
157 | return this._binaryType;
|
158 | }
|
159 |
|
160 | set binaryType(binaryType: BinaryType): void {
|
161 | if (binaryType !== 'blob' && binaryType !== 'arraybuffer') {
|
162 | throw new Error("binaryType must be either 'blob' or 'arraybuffer'");
|
163 | }
|
164 | if (this._binaryType === 'blob' || binaryType === 'blob') {
|
165 | invariant(
|
166 | BlobManager.isAvailable,
|
167 | 'Native module BlobModule is required for blob support',
|
168 | );
|
169 | if (binaryType === 'blob') {
|
170 | BlobManager.addWebSocketHandler(this._socketId);
|
171 | } else {
|
172 | BlobManager.removeWebSocketHandler(this._socketId);
|
173 | }
|
174 | }
|
175 | this._binaryType = binaryType;
|
176 | }
|
177 |
|
178 | close(code?: number, reason?: string): void {
|
179 | if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) {
|
180 | return;
|
181 | }
|
182 |
|
183 | this.readyState = this.CLOSING;
|
184 | this._close(code, reason);
|
185 | }
|
186 |
|
187 | send(data: string | ArrayBuffer | ArrayBufferView | Blob): void {
|
188 | if (this.readyState === this.CONNECTING) {
|
189 | throw new Error('INVALID_STATE_ERR');
|
190 | }
|
191 |
|
192 | if (data instanceof Blob) {
|
193 | invariant(
|
194 | BlobManager.isAvailable,
|
195 | 'Native module BlobModule is required for blob support',
|
196 | );
|
197 | BlobManager.sendOverSocket(data, this._socketId);
|
198 | return;
|
199 | }
|
200 |
|
201 | if (typeof data === 'string') {
|
202 | WebSocketModule.send(data, this._socketId);
|
203 | return;
|
204 | }
|
205 |
|
206 | if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
207 | WebSocketModule.sendBinary(binaryToBase64(data), this._socketId);
|
208 | return;
|
209 | }
|
210 |
|
211 | throw new Error('Unsupported data type');
|
212 | }
|
213 |
|
214 | ping(): void {
|
215 | if (this.readyState === this.CONNECTING) {
|
216 | throw new Error('INVALID_STATE_ERR');
|
217 | }
|
218 |
|
219 | WebSocketModule.ping(this._socketId);
|
220 | }
|
221 |
|
222 | _close(code?: number, reason?: string): void {
|
223 | if (Platform.OS === 'android') {
|
224 |
|
225 | const statusCode = typeof code === 'number' ? code : CLOSE_NORMAL;
|
226 | const closeReason = typeof reason === 'string' ? reason : '';
|
227 | WebSocketModule.close(statusCode, closeReason, this._socketId);
|
228 | } else {
|
229 | WebSocketModule.close(this._socketId);
|
230 | }
|
231 |
|
232 | if (BlobManager.isAvailable && this._binaryType === 'blob') {
|
233 | BlobManager.removeWebSocketHandler(this._socketId);
|
234 | }
|
235 | }
|
236 |
|
237 | _unregisterEvents(): void {
|
238 | this._subscriptions.forEach(e => e.remove());
|
239 | this._subscriptions = [];
|
240 | }
|
241 |
|
242 | _registerEvents(): void {
|
243 | this._subscriptions = [
|
244 | this._eventEmitter.addListener('websocketMessage', ev => {
|
245 | if (ev.id !== this._socketId) {
|
246 | return;
|
247 | }
|
248 | let data = ev.data;
|
249 | switch (ev.type) {
|
250 | case 'binary':
|
251 | data = base64.toByteArray(ev.data).buffer;
|
252 | break;
|
253 | case 'blob':
|
254 | data = BlobManager.createFromOptions(ev.data);
|
255 | break;
|
256 | }
|
257 | this.dispatchEvent(new WebSocketEvent('message', {data}));
|
258 | }),
|
259 | this._eventEmitter.addListener('websocketOpen', ev => {
|
260 | if (ev.id !== this._socketId) {
|
261 | return;
|
262 | }
|
263 | this.readyState = this.OPEN;
|
264 | this.dispatchEvent(new WebSocketEvent('open'));
|
265 | }),
|
266 | this._eventEmitter.addListener('websocketClosed', ev => {
|
267 | if (ev.id !== this._socketId) {
|
268 | return;
|
269 | }
|
270 | this.readyState = this.CLOSED;
|
271 | this.dispatchEvent(
|
272 | new WebSocketEvent('close', {
|
273 | code: ev.code,
|
274 | reason: ev.reason,
|
275 | }),
|
276 | );
|
277 | this._unregisterEvents();
|
278 | this.close();
|
279 | }),
|
280 | this._eventEmitter.addListener('websocketFailed', ev => {
|
281 | if (ev.id !== this._socketId) {
|
282 | return;
|
283 | }
|
284 | this.readyState = this.CLOSED;
|
285 | this.dispatchEvent(
|
286 | new WebSocketEvent('error', {
|
287 | message: ev.message,
|
288 | }),
|
289 | );
|
290 | this.dispatchEvent(
|
291 | new WebSocketEvent('close', {
|
292 | message: ev.message,
|
293 | }),
|
294 | );
|
295 | this._unregisterEvents();
|
296 | this.close();
|
297 | }),
|
298 | ];
|
299 | }
|
300 | }
|
301 |
|
302 | module.exports = WebSocket;
|