Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

socket.js 21KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. 'use strict';
  2. /* global Blob, FileReader */
  3. const assert = require('bsert');
  4. const EventEmitter = require('events');
  5. const WebSocket = require('./backend').Client;
  6. const Packet = require('./packet');
  7. const Frame = require('./frame');
  8. const util = require('./util');
  9. const Parser = require('./parser');
  10. const codes = require('./codes');
  11. const blacklist = require('./blacklist');
  12. /**
  13. * Socket
  14. */
  15. class Socket extends EventEmitter {
  16. constructor() {
  17. super();
  18. this.server = null;
  19. this.ws = null;
  20. this.protocol = '';
  21. this.url = 'ws://127.0.0.1:80/socket.io/?transport=websocket';
  22. this.ssl = false;
  23. this.host = '127.0.0.1';
  24. this.port = 80;
  25. this.inbound = false;
  26. this.handshake = false;
  27. this.opened = false;
  28. this.connected = false;
  29. this.challenge = false;
  30. this.destroyed = false;
  31. this.reconnection = true;
  32. this.time = 0;
  33. this.sequence = 0;
  34. this.pingInterval = 25000;
  35. this.pingTimeout = 60000;
  36. this.lastPing = 0;
  37. this.parser = new Parser();
  38. this.binary = false;
  39. this.packet = null;
  40. this.timer = null;
  41. this.jobs = new Map();
  42. this.hooks = new Map();
  43. this.channels = new Set();
  44. this.events = new EventEmitter();
  45. this.buffer = [];
  46. // Unused.
  47. this.admin = false;
  48. this.auth = false;
  49. }
  50. accept(server, req, socket, ws) {
  51. assert(!this.ws, 'Cannot accept twice.');
  52. assert(server);
  53. assert(req);
  54. assert(socket);
  55. assert(socket.remoteAddress);
  56. assert(socket.remotePort != null);
  57. assert(ws);
  58. let proto = 'ws';
  59. let host = socket.remoteAddress;
  60. let port = socket.remotePort;
  61. if (socket.encrypted)
  62. proto = 'wss';
  63. if (host.indexOf(':') !== -1)
  64. host = `[${host}]`;
  65. if (!port)
  66. port = 0;
  67. this.server = server;
  68. this.binary = req.url.indexOf('b64=1') === -1;
  69. this.url = `${proto}://${host}:${port}/socket.io/?transport=websocket`;
  70. this.ssl = proto === 'wss';
  71. this.host = socket.remoteAddress;
  72. this.port = socket.remotePort;
  73. this.inbound = true;
  74. this.ws = ws;
  75. this.init();
  76. return this;
  77. }
  78. connect(port, host, ssl, protocols) {
  79. assert(!this.ws, 'Cannot connect twice.');
  80. if (typeof port === 'string') {
  81. protocols = host;
  82. [port, host, ssl] = util.parseURL(port);
  83. }
  84. let proto = 'ws';
  85. if (ssl)
  86. proto = 'wss';
  87. if (!host)
  88. host = '127.0.0.1';
  89. assert(typeof host === 'string');
  90. assert((port & 0xffff) === port, 'Must pass a port.');
  91. assert(!ssl || typeof ssl === 'boolean');
  92. assert(!protocols || Array.isArray(protocols));
  93. let hostname = host;
  94. if (host.indexOf(':') !== -1 && host[0] !== '[')
  95. hostname = `[${host}]`;
  96. const path = '/socket.io';
  97. const qs = '?transport=websocket';
  98. const url = `${proto}://${hostname}:${port}${path}/${qs}`;
  99. this.binary = true;
  100. this.url = url;
  101. this.ssl = ssl;
  102. this.host = host;
  103. this.port = port;
  104. this.inbound = false;
  105. this.ws = new WebSocket(url, protocols);
  106. this.init();
  107. return this;
  108. }
  109. init() {
  110. this.protocol = this.ws.protocol;
  111. this.time = Date.now();
  112. this.observe();
  113. this.parser.on('error', (err) => {
  114. this.emit('error', err);
  115. });
  116. this.parser.on('frame', async (frame) => {
  117. try {
  118. await this.handleFrame(frame);
  119. } catch (e) {
  120. this.emit('error', e);
  121. }
  122. });
  123. this.start();
  124. }
  125. observe() {
  126. const ws = this.ws;
  127. assert(ws);
  128. ws.binaryType = 'arraybuffer';
  129. ws.onopen = async () => {
  130. await this.onOpen();
  131. };
  132. ws.onmessage = async (event) => {
  133. await this.onMessage(event);
  134. };
  135. ws.onerror = async (event) => {
  136. await this.onError(event);
  137. };
  138. ws.onclose = async (event) => {
  139. await this.onClose(event);
  140. };
  141. }
  142. async onOpen() {
  143. if (this.destroyed)
  144. return;
  145. if (!this.inbound)
  146. return;
  147. assert(!this.opened);
  148. assert(!this.connected);
  149. assert(!this.handshake);
  150. this.opened = true;
  151. this.handshake = true;
  152. await this.emitAsync('open');
  153. this.sendHandshake();
  154. this.connected = true;
  155. await this.emitAsync('connect');
  156. this.sendConnect();
  157. }
  158. async emitAsync(event, ...args) {
  159. const handlers = this.listeners(event);
  160. for (const handler of handlers) {
  161. try {
  162. await handler(...args);
  163. } catch (e) {
  164. this.emit('error', e);
  165. }
  166. }
  167. }
  168. async onMessage(event) {
  169. if (this.destroyed)
  170. return;
  171. let data;
  172. try {
  173. data = await readBinary(event.data);
  174. } catch (e) {
  175. this.emit('error', e);
  176. return;
  177. }
  178. // Textual frame.
  179. if (typeof data === 'string') {
  180. this.parser.feedString(data);
  181. return;
  182. }
  183. // Binary frame.
  184. this.parser.feedBinary(data);
  185. }
  186. async onError(event) {
  187. if (this.destroyed)
  188. return;
  189. this.emit('error', new Error(event.message));
  190. if (this.inbound) {
  191. this.destroy();
  192. return;
  193. }
  194. this.close();
  195. }
  196. async onClose(event) {
  197. if (this.destroyed)
  198. return;
  199. if (event.code === 1000 || event.code === 1001) {
  200. if (!this.connected)
  201. this.emit('error', new Error('Could not connect.'));
  202. if (this.inbound) {
  203. this.destroy();
  204. return;
  205. }
  206. this.close();
  207. return;
  208. }
  209. const code = codes[event.code] || 'UNKNOWN_CODE';
  210. const reason = event.reason || 'Unknown reason';
  211. const msg = `Websocket Closed: ${reason} (code=${code}).`;
  212. const err = new Error(msg);
  213. err.reason = event.reason || '';
  214. err.code = event.code || 0;
  215. this.emit('error', err);
  216. if (this.inbound) {
  217. this.destroy();
  218. return;
  219. }
  220. if (!this.reconnection) {
  221. this.destroy();
  222. return;
  223. }
  224. this.close();
  225. }
  226. close() {
  227. if (this.destroyed)
  228. return;
  229. this.time = Date.now();
  230. this.packet = null;
  231. this.handshake = false;
  232. this.connected = false;
  233. this.challenge = false;
  234. this.sequence = 0;
  235. this.lastPing = 0;
  236. for (const [id, job] of this.jobs) {
  237. this.jobs.delete(id);
  238. job.reject(new Error('Job timed out.'));
  239. }
  240. assert(this.ws);
  241. this.ws.onopen = () => {};
  242. this.ws.onmessage = () => {};
  243. this.ws.onerror = () => {};
  244. this.ws.onclose = () => {};
  245. this.ws.close();
  246. this.emitAsync('disconnect');
  247. }
  248. error(msg) {
  249. if (this.destroyed)
  250. return;
  251. this.emit('error', new Error(msg));
  252. }
  253. destroy() {
  254. if (this.destroyed)
  255. return;
  256. this.close();
  257. this.stop();
  258. this.opened = false;
  259. this.destroyed = true;
  260. this.buffer.length = 0;
  261. this.emitAsync('close');
  262. this.removeAllListeners();
  263. this.on('error', () => {});
  264. }
  265. send(frame) {
  266. if (this.destroyed)
  267. return;
  268. assert(this.ws);
  269. if (frame.binary && this.binary)
  270. this.ws.send(frame.toRaw());
  271. else
  272. this.ws.send(frame.toString());
  273. }
  274. reconnect() {
  275. assert(!this.inbound);
  276. this.close();
  277. this.ws = new WebSocket(this.url);
  278. this.time = Date.now();
  279. this.observe();
  280. }
  281. start() {
  282. assert(this.ws);
  283. assert(this.timer == null);
  284. this.timer = setInterval(() => this.stall(), 5000);
  285. }
  286. stop() {
  287. if (this.timer != null) {
  288. clearInterval(this.timer);
  289. this.timer = null;
  290. }
  291. }
  292. stall() {
  293. const now = Date.now();
  294. assert(this.ws);
  295. if (!this.connected) {
  296. if (now - this.time > 10000) {
  297. if (this.inbound || !this.reconnection) {
  298. this.error('Timed out waiting for connection.');
  299. this.destroy();
  300. return;
  301. }
  302. this.error('Timed out waiting for connection. Reconnecting...');
  303. this.reconnect();
  304. return;
  305. }
  306. return;
  307. }
  308. for (const [id, job] of this.jobs) {
  309. if (now - job.time > 600000) {
  310. this.jobs.delete(id);
  311. job.reject(new Error('Job timed out.'));
  312. }
  313. }
  314. if (!this.inbound && !this.challenge) {
  315. this.challenge = true;
  316. this.lastPing = now;
  317. this.sendPing();
  318. return;
  319. }
  320. if (!this.inbound && now - this.lastPing > this.pingTimeout) {
  321. this.error('Connection is stalling (ping).');
  322. if (this.inbound) {
  323. this.destroy();
  324. return;
  325. }
  326. this.close();
  327. return;
  328. }
  329. }
  330. /*
  331. * Frames
  332. */
  333. async handleFrame(frame) {
  334. if (this.destroyed)
  335. return undefined;
  336. switch (frame.type) {
  337. case Frame.types.OPEN:
  338. return this.handleOpen(frame);
  339. case Frame.types.CLOSE:
  340. return this.handleClose(frame);
  341. case Frame.types.PING:
  342. return this.handlePing(frame);
  343. case Frame.types.PONG:
  344. return this.handlePong(frame);
  345. case Frame.types.MESSAGE:
  346. return this.handleMessage(frame);
  347. case Frame.types.UPGRADE:
  348. return this.handleUpgrade(frame);
  349. case Frame.types.NOOP:
  350. return this.handleNoop(frame);
  351. default: {
  352. throw new Error('Unknown frame.');
  353. }
  354. }
  355. }
  356. async handleOpen(frame) {
  357. if (this.inbound)
  358. throw new Error('Inbound socket sent an open frame.');
  359. if (frame.binary)
  360. throw new Error('Received a binary open frame.');
  361. if (this.handshake)
  362. throw new Error('Duplicate open frame.');
  363. const json = JSON.parse(frame.data);
  364. enforce(json && typeof json === 'object', 'open', 'object');
  365. const {pingInterval, pingTimeout} = json;
  366. enforce((pingInterval >>> 0) === pingInterval, 'interval', 'uint32');
  367. enforce((pingTimeout >>> 0) === pingTimeout, 'timeout', 'uint32');
  368. this.pingInterval = pingInterval;
  369. this.pingTimeout = pingTimeout;
  370. this.handshake = true;
  371. if (!this.opened) {
  372. this.opened = true;
  373. await this.emitAsync('open');
  374. }
  375. }
  376. async handleClose(frame) {
  377. if (this.inbound)
  378. throw new Error('Inbound socket sent a close frame.');
  379. this.close();
  380. }
  381. async handlePing() {
  382. if (!this.inbound)
  383. throw new Error('Outbound socket sent a ping frame.');
  384. this.sendPong();
  385. }
  386. async handlePong() {
  387. if (this.inbound)
  388. throw new Error('Inbound socket sent a pong frame.');
  389. if (!this.challenge) {
  390. this.error('Remote node sent bad pong.');
  391. this.destroy();
  392. return;
  393. }
  394. this.challenge = false;
  395. }
  396. async handleMessage(frame) {
  397. if (this.packet) {
  398. const packet = this.packet;
  399. if (!frame.binary)
  400. throw new Error('Received non-binary frame as attachment.');
  401. packet.buffers.push(frame.data);
  402. if (packet.buffers.length === packet.attachments) {
  403. this.packet = null;
  404. return this.handlePacket(packet);
  405. }
  406. return undefined;
  407. }
  408. if (frame.binary)
  409. throw new Error('Received binary frame as a message.');
  410. const packet = Packet.fromString(frame.data);
  411. if (packet.attachments > 0) {
  412. this.packet = packet;
  413. return undefined;
  414. }
  415. return this.handlePacket(packet);
  416. }
  417. async handleUpgrade(frame) {
  418. if (!this.inbound)
  419. throw new Error('Outbound socket sent an upgrade frame.');
  420. throw new Error('Cannot upgrade from websocket.');
  421. }
  422. async handleNoop(frame) {
  423. ;
  424. }
  425. sendFrame(type, data, binary) {
  426. this.send(new Frame(type, data, binary));
  427. }
  428. sendOpen(data) {
  429. this.sendFrame(Frame.types.OPEN, data, false);
  430. }
  431. sendClose(data) {
  432. this.sendFrame(Frame.types.CLOSE, data, false);
  433. }
  434. sendPing(data) {
  435. this.sendFrame(Frame.types.PING, data, false);
  436. }
  437. sendPong(data) {
  438. this.sendFrame(Frame.types.PONG, data, false);
  439. }
  440. sendMessage(data) {
  441. this.sendFrame(Frame.types.MESSAGE, data, false);
  442. }
  443. sendBinary(data) {
  444. this.sendFrame(Frame.types.MESSAGE, data, true);
  445. }
  446. sendHandshake() {
  447. const handshake = JSON.stringify({
  448. sid: '00000000000000000000',
  449. upgrades: [],
  450. pingInterval: this.pingInterval,
  451. pingTimeout: this.pingTimeout
  452. });
  453. this.sendOpen(handshake);
  454. }
  455. /*
  456. * Packets
  457. */
  458. async handlePacket(packet) {
  459. if (this.destroyed)
  460. return undefined;
  461. switch (packet.type) {
  462. case Packet.types.CONNECT: {
  463. return this.handleConnect();
  464. }
  465. case Packet.types.DISCONNECT: {
  466. return this.handleDisconnect();
  467. }
  468. case Packet.types.EVENT:
  469. case Packet.types.BINARY_EVENT: {
  470. const args = packet.getData();
  471. enforce(Array.isArray(args), 'args', 'array');
  472. enforce(args.length > 0, 'args', 'array');
  473. enforce(typeof args[0] === 'string', 'event', 'string');
  474. if (packet.id !== -1)
  475. return this.handleCall(packet.id, args);
  476. return this.handleEvent(args);
  477. }
  478. case Packet.types.ACK:
  479. case Packet.types.BINARY_ACK: {
  480. enforce(packet.id !== -1, 'id', 'uint32');
  481. const json = packet.getData();
  482. enforce(json == null || Array.isArray(json), 'args', 'array');
  483. let err = null;
  484. let result = null;
  485. if (json && json.length > 0)
  486. err = json[0];
  487. if (json && json.length > 1)
  488. result = json[1];
  489. if (result == null)
  490. result = null;
  491. if (err) {
  492. enforce(typeof err === 'object', 'error', 'object');
  493. return this.handleError(packet.id, err);
  494. }
  495. return this.handleAck(packet.id, result);
  496. }
  497. case Packet.types.ERROR: {
  498. const err = packet.getData();
  499. enforce(err && typeof err === 'object', 'error', 'object');
  500. return this.handleError(-1, err);
  501. }
  502. default: {
  503. throw new Error('Unknown packet.');
  504. }
  505. }
  506. }
  507. async handleConnect() {
  508. if (this.inbound)
  509. throw new Error('Inbound socket sent connect packet.');
  510. this.connected = true;
  511. await this.emitAsync('connect');
  512. for (const packet of this.buffer)
  513. this.sendPacket(packet);
  514. this.buffer.length = 0;
  515. }
  516. async handleDisconnect() {
  517. this.close();
  518. }
  519. async handleEvent(args) {
  520. try {
  521. const event = args[0];
  522. if (blacklist.hasOwnProperty(event))
  523. throw new Error(`Cannot emit blacklisted event: ${event}.`);
  524. this.events.emit(...args);
  525. } catch (e) {
  526. this.emit('error', e);
  527. this.sendError(-1, e);
  528. }
  529. }
  530. async handleCall(id, args) {
  531. let result;
  532. try {
  533. const event = args.shift();
  534. if (blacklist.hasOwnProperty(event))
  535. throw new Error(`Cannot emit blacklisted event: ${event}.`);
  536. const handler = this.hooks.get(event);
  537. if (!handler)
  538. throw new Error(`Call not found: ${event}.`);
  539. result = await handler(...args);
  540. } catch (e) {
  541. this.emit('error', e);
  542. this.sendError(id, e);
  543. return;
  544. }
  545. if (result == null)
  546. result = null;
  547. this.sendAck(id, result);
  548. }
  549. async handleAck(id, data) {
  550. const job = this.jobs.get(id);
  551. if (!job)
  552. throw new Error(`Job not found for ${id}.`);
  553. this.jobs.delete(id);
  554. job.resolve(data);
  555. }
  556. async handleError(id, err) {
  557. const msg = castMsg(err.message);
  558. const name = castString(err.name);
  559. const type = castString(err.type);
  560. const code = castCode(err.code);
  561. if (id === -1) {
  562. const e = new Error(msg);
  563. e.name = name;
  564. e.type = type;
  565. e.code = code;
  566. this.emit('error', e);
  567. return;
  568. }
  569. const job = this.jobs.get(id);
  570. if (!job)
  571. throw new Error(`Job not found for ${id}.`);
  572. this.jobs.delete(id);
  573. const e = new Error(msg);
  574. e.name = name;
  575. e.type = type;
  576. e.code = code;
  577. job.reject(e);
  578. }
  579. sendPacket(packet) {
  580. this.sendMessage(packet.toString());
  581. for (const data of packet.buffers)
  582. this.sendBinary(data);
  583. }
  584. sendConnect() {
  585. this.sendPacket(new Packet(Packet.types.CONNECT));
  586. }
  587. sendDisconnect() {
  588. this.sendPacket(new Packet(Packet.types.DISCONNECT));
  589. }
  590. sendEvent(data) {
  591. const packet = new Packet();
  592. packet.type = Packet.types.EVENT;
  593. packet.setData(data);
  594. if (!this.connected) {
  595. this.buffer.push(packet);
  596. return;
  597. }
  598. this.sendPacket(packet);
  599. }
  600. sendCall(id, data) {
  601. const packet = new Packet();
  602. packet.type = Packet.types.EVENT;
  603. packet.id = id;
  604. packet.setData(data);
  605. if (!this.connected) {
  606. this.buffer.push(packet);
  607. return;
  608. }
  609. this.sendPacket(packet);
  610. }
  611. sendAck(id, data) {
  612. const packet = new Packet();
  613. packet.type = Packet.types.ACK;
  614. packet.id = id;
  615. packet.setData([null, data]);
  616. this.sendPacket(packet);
  617. }
  618. sendError(id, err) {
  619. const message = castMsg(err.message);
  620. const name = castString(err.name);
  621. const type = castString(err.type);
  622. const code = castCode(err.code);
  623. if (id === -1) {
  624. const packet = new Packet();
  625. packet.type = Packet.types.ERROR;
  626. packet.setData({ message, name, type, code });
  627. this.sendPacket(packet);
  628. return;
  629. }
  630. const packet = new Packet();
  631. packet.type = Packet.types.ACK;
  632. packet.id = id;
  633. packet.setData([{ message, name, type, code }]);
  634. this.sendPacket(packet);
  635. }
  636. /*
  637. * API
  638. */
  639. bind(event, handler) {
  640. enforce(typeof event === 'string', 'event', 'string');
  641. enforce(typeof handler === 'function', 'handler', 'function');
  642. assert(!blacklist.hasOwnProperty(event), 'Blacklisted event.');
  643. this.events.on(event, handler);
  644. }
  645. unbind(event, handler) {
  646. enforce(typeof event === 'string', 'event', 'string');
  647. enforce(typeof handler === 'function', 'handler', 'function');
  648. assert(!blacklist.hasOwnProperty(event), 'Blacklisted event.');
  649. this.events.removeListener(event, handler);
  650. }
  651. fire(...args) {
  652. enforce(args.length > 0, 'event', 'string');
  653. enforce(typeof args[0] === 'string', 'event', 'string');
  654. this.sendEvent(args);
  655. }
  656. hook(event, handler) {
  657. enforce(typeof event === 'string', 'event', 'string');
  658. enforce(typeof handler === 'function', 'handler', 'function');
  659. assert(!this.hooks.has(event), 'Hook already bound.');
  660. assert(!blacklist.hasOwnProperty(event), 'Blacklisted event.');
  661. this.hooks.set(event, handler);
  662. }
  663. unhook(event) {
  664. enforce(typeof event === 'string', 'event', 'string');
  665. assert(!blacklist.hasOwnProperty(event), 'Blacklisted event.');
  666. this.hooks.delete(event);
  667. }
  668. call(...args) {
  669. enforce(args.length > 0, 'event', 'string');
  670. enforce(typeof args[0] === 'string', 'event', 'string');
  671. const id = this.sequence;
  672. this.sequence += 1;
  673. this.sequence >>>= 0;
  674. assert(!this.jobs.has(id), 'ID collision.');
  675. this.sendCall(id, args);
  676. return new Promise((resolve, reject) => {
  677. this.jobs.set(id, new Job(resolve, reject, Date.now()));
  678. });
  679. }
  680. channel(name) {
  681. return this.channels.has(name);
  682. }
  683. join(name) {
  684. if (!this.server)
  685. return false;
  686. return this.server.join(this, name);
  687. }
  688. leave(name) {
  689. if (!this.server)
  690. return false;
  691. return this.server.leave(this, name);
  692. }
  693. static accept(server, req, socket, ws) {
  694. return new this().accept(server, req, socket, ws);
  695. }
  696. static connect(port, host, ssl, protocols) {
  697. return new this().connect(port, host, ssl, protocols);
  698. }
  699. }
  700. /*
  701. * Helpers
  702. */
  703. class Job {
  704. constructor(resolve, reject, time) {
  705. this.resolve = resolve;
  706. this.reject = reject;
  707. this.time = time;
  708. }
  709. }
  710. function castCode(code) {
  711. if (code !== null
  712. && typeof code !== 'number'
  713. && typeof code !== 'string') {
  714. return null;
  715. }
  716. return code;
  717. }
  718. function castMsg(msg) {
  719. if (typeof msg !== 'string')
  720. return 'No message.';
  721. return msg;
  722. }
  723. function castString(type) {
  724. if (typeof type !== 'string')
  725. return null;
  726. return type;
  727. }
  728. function enforce(value, name, type) {
  729. if (!value) {
  730. const err = new TypeError(`'${name}' must be a(n) ${type}.`);
  731. if (Error.captureStackTrace)
  732. Error.captureStackTrace(err, enforce);
  733. throw err;
  734. }
  735. }
  736. function readBinary(data) {
  737. return new Promise((resolve, reject) => {
  738. if (typeof data === 'string') {
  739. resolve(data);
  740. return;
  741. }
  742. if (!data || typeof data !== 'object') {
  743. reject(new Error('Bad data object.'));
  744. return;
  745. }
  746. if (Buffer.isBuffer(data)) {
  747. resolve(data);
  748. return;
  749. }
  750. if (data instanceof ArrayBuffer) {
  751. const result = Buffer.from(data);
  752. resolve(result);
  753. return;
  754. }
  755. if (data.buffer instanceof ArrayBuffer) {
  756. const result = Buffer.from(data.buffer,
  757. data.byteOffset,
  758. data.byteLength);
  759. resolve(result);
  760. return;
  761. }
  762. if (typeof Blob !== 'undefined' && Blob) {
  763. if (data instanceof Blob) {
  764. const reader = new FileReader();
  765. reader.onloadend = () => {
  766. const result = Buffer.from(reader.result);
  767. resolve(result);
  768. };
  769. reader.readAsArrayBuffer(data);
  770. return;
  771. }
  772. }
  773. reject(new Error('Bad data object.'));
  774. });
  775. }
  776. /*
  777. * Expose
  778. */
  779. module.exports = Socket;