1 | import d from 'debug';
|
2 | import WebSocketPlus, {
|
3 | OPEN,
|
4 | DISCONNECT,
|
5 | RECONNECT,
|
6 | RETRY,
|
7 | SCHEDULE,
|
8 | OFFLINE,
|
9 | ONLINE,
|
10 | ERROR,
|
11 | MESSAGE,
|
12 | } from './websocket-plus';
|
13 | import { createError } from './error';
|
14 | import { GenericCommand, CommandType } from '../proto/message';
|
15 | import { trim, isWeapp } from './utils';
|
16 |
|
17 | const debug = d('LC:Connection');
|
18 |
|
19 | const COMMAND_TIMEOUT = 20000;
|
20 |
|
21 | const EXPIRE = Symbol('expire');
|
22 |
|
23 | export {
|
24 | OPEN,
|
25 | DISCONNECT,
|
26 | RECONNECT,
|
27 | RETRY,
|
28 | SCHEDULE,
|
29 | OFFLINE,
|
30 | ONLINE,
|
31 | ERROR,
|
32 | MESSAGE,
|
33 | EXPIRE,
|
34 | };
|
35 |
|
36 | export default class Connection extends WebSocketPlus {
|
37 | constructor(getUrl, { format, version }) {
|
38 | debug('initializing Connection');
|
39 | const protocolString = `lc.${format}.${version}`;
|
40 | if (!isWeapp) {
|
41 | super(getUrl, protocolString);
|
42 | } else {
|
43 | super(
|
44 | getUrl().then(urls =>
|
45 | urls.map(
|
46 | url =>
|
47 | `${url}${
|
48 | url.indexOf('?') === -1 ? '?' : '&'
|
49 | }subprotocol=${encodeURIComponent(protocolString)}`
|
50 | )
|
51 | )
|
52 | );
|
53 | }
|
54 | this._protocalFormat = format;
|
55 | this._commands = {};
|
56 | this._serialId = 0;
|
57 | }
|
58 |
|
59 | async send(command, waitingForRespond = true) {
|
60 | let serialId;
|
61 | if (waitingForRespond) {
|
62 | this._serialId += 1;
|
63 | serialId = this._serialId;
|
64 | command.i = serialId;
|
65 | }
|
66 | if (debug.enabled) debug('↑ %O sent', trim(command));
|
67 |
|
68 | let message;
|
69 | if (this._protocalFormat === 'proto2base64') {
|
70 | message = command.toBase64();
|
71 | } else if (command.toArrayBuffer) {
|
72 | message = command.toArrayBuffer();
|
73 | }
|
74 | if (!message) {
|
75 | throw new TypeError(`${command} is not a GenericCommand`);
|
76 | }
|
77 |
|
78 | super.send(message);
|
79 |
|
80 | if (!waitingForRespond) return undefined;
|
81 | return new Promise((resolve, reject) => {
|
82 | this._commands[serialId] = {
|
83 | resolve,
|
84 | reject,
|
85 | timeout: setTimeout(() => {
|
86 | if (this._commands[serialId]) {
|
87 | if (debug.enabled) debug('✗ %O timeout', trim(command));
|
88 | reject(
|
89 | createError({
|
90 | error: `Command Timeout [cmd:${command.cmd} op:${command.op}]`,
|
91 | name: 'COMMAND_TIMEOUT',
|
92 | })
|
93 | );
|
94 | delete this._commands[serialId];
|
95 | }
|
96 | }, COMMAND_TIMEOUT),
|
97 | };
|
98 | });
|
99 | }
|
100 |
|
101 | handleMessage(msg) {
|
102 | let message;
|
103 | try {
|
104 | message = GenericCommand.decode(msg);
|
105 | if (debug.enabled) debug('↓ %O received', trim(message));
|
106 | } catch (e) {
|
107 | console.warn('Decode message failed:', e.message, msg);
|
108 | return;
|
109 | }
|
110 | const serialId = message.i;
|
111 | if (serialId) {
|
112 | if (this._commands[serialId]) {
|
113 | clearTimeout(this._commands[serialId].timeout);
|
114 | if (message.cmd === CommandType.error) {
|
115 | this._commands[serialId].reject(createError(message.errorMessage));
|
116 | } else {
|
117 | this._commands[serialId].resolve(message);
|
118 | }
|
119 | delete this._commands[serialId];
|
120 | } else {
|
121 | console.warn(`Unexpected command received with serialId [${serialId}],
|
122 | which have timed out or never been requested.`);
|
123 | }
|
124 | } else {
|
125 | switch (message.cmd) {
|
126 | case CommandType.error: {
|
127 | this.emit(ERROR, createError(message.errorMessage));
|
128 | return;
|
129 | }
|
130 | case CommandType.goaway: {
|
131 | this.emit(EXPIRE);
|
132 | return;
|
133 | }
|
134 | default: {
|
135 | this.emit(MESSAGE, message);
|
136 | }
|
137 | }
|
138 | }
|
139 | }
|
140 |
|
141 | ping() {
|
142 | return this.send(
|
143 | new GenericCommand({
|
144 | cmd: CommandType.echo,
|
145 | })
|
146 | ).catch(error => debug('ping failed:', error));
|
147 | }
|
148 | }
|