lupine.api 1.1.57 → 1.1.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +3 -3
  2. package/admin/admin-about.tsx +12 -16
  3. package/admin/admin-config.tsx +47 -44
  4. package/admin/admin-css.tsx +3 -3
  5. package/admin/admin-db.tsx +75 -75
  6. package/admin/admin-frame-helper.tsx +364 -364
  7. package/admin/admin-frame.tsx +164 -164
  8. package/admin/admin-index.tsx +65 -65
  9. package/admin/admin-login.tsx +111 -111
  10. package/admin/admin-menu-edit.tsx +637 -637
  11. package/admin/admin-menu-list.tsx +87 -87
  12. package/admin/admin-page-edit.tsx +564 -564
  13. package/admin/admin-page-list.tsx +83 -83
  14. package/admin/admin-performance.tsx +28 -28
  15. package/admin/admin-release.tsx +427 -404
  16. package/admin/admin-resources.tsx +382 -382
  17. package/admin/admin-shell.tsx +89 -89
  18. package/admin/admin-table-data.tsx +146 -146
  19. package/admin/admin-table-list.tsx +230 -230
  20. package/admin/admin-test-animations.tsx +395 -395
  21. package/admin/admin-test-component.tsx +823 -808
  22. package/admin/admin-test-edit.tsx +319 -319
  23. package/admin/admin-test-themes.tsx +56 -56
  24. package/admin/admin-tokens.tsx +338 -338
  25. package/admin/design/admin-design.tsx +174 -174
  26. package/admin/design/block-grid.tsx +36 -36
  27. package/admin/design/block-grid1.tsx +21 -21
  28. package/admin/design/block-paragraph.tsx +19 -19
  29. package/admin/design/block-title.tsx +19 -19
  30. package/admin/design/design-block-box.tsx +140 -140
  31. package/admin/design/drag-data.tsx +24 -24
  32. package/admin/index.ts +9 -9
  33. package/admin/package.json +15 -15
  34. package/admin/tsconfig.json +127 -127
  35. package/dev/copy-folder.js +32 -32
  36. package/dev/cp-index-html.js +69 -69
  37. package/dev/file-utils.js +12 -12
  38. package/dev/index.js +18 -19
  39. package/dev/package.json +12 -12
  40. package/dev/plugin-ifelse.js +168 -168
  41. package/dev/plugin-ifelse.test.js +37 -37
  42. package/dev/run-cmd.js +14 -14
  43. package/dev/send-request.js +12 -12
  44. package/package.json +55 -55
  45. package/src/admin-api/admin-api-helper.ts +210 -205
  46. package/src/admin-api/admin-api.ts +65 -65
  47. package/src/admin-api/admin-auth.ts +152 -146
  48. package/src/admin-api/admin-config.ts +94 -84
  49. package/src/admin-api/admin-csv.ts +94 -94
  50. package/src/admin-api/admin-db.ts +269 -269
  51. package/src/admin-api/admin-menu.ts +135 -135
  52. package/src/admin-api/admin-page.ts +135 -135
  53. package/src/admin-api/admin-performance.ts +128 -128
  54. package/src/admin-api/admin-release.ts +703 -700
  55. package/src/admin-api/admin-resources.ts +318 -318
  56. package/src/admin-api/admin-token-helper.ts +82 -79
  57. package/src/admin-api/admin-tokens.ts +90 -90
  58. package/src/admin-api/index.ts +2 -2
  59. package/src/admin-api/web-config-api.ts +19 -19
  60. package/src/api/api-cache.ts +103 -103
  61. package/src/api/api-helper.ts +44 -44
  62. package/src/api/api-module.ts +67 -60
  63. package/src/api/api-router.ts +177 -177
  64. package/src/api/api-shared-storage.ts +64 -64
  65. package/src/api/async-storage.ts +5 -5
  66. package/src/api/debug-service.ts +56 -56
  67. package/src/api/encode-html.ts +27 -27
  68. package/src/api/handle-status.ts +75 -75
  69. package/src/api/index.ts +15 -16
  70. package/src/api/mini-web-socket.ts +270 -270
  71. package/src/api/server-content-type.ts +82 -82
  72. package/src/api/server-render.ts +235 -215
  73. package/src/api/shell-service.ts +74 -74
  74. package/src/api/simple-storage.ts +80 -80
  75. package/src/api/static-server.ts +128 -125
  76. package/src/api/to-client-delivery.ts +26 -26
  77. package/src/app/app-cache.ts +55 -55
  78. package/src/app/app-helper.ts +62 -62
  79. package/src/app/app-message.ts +109 -109
  80. package/src/app/app-shared-storage.ts +363 -363
  81. package/src/app/app-start.ts +136 -136
  82. package/src/app/cleanup-exit.ts +16 -16
  83. package/src/app/host-to-path.ts +38 -38
  84. package/src/app/index.ts +11 -11
  85. package/src/app/process-dev-requests.ts +130 -130
  86. package/src/app/web-listener.ts +294 -294
  87. package/src/app/web-processor.ts +47 -42
  88. package/src/app/web-server.ts +100 -100
  89. package/src/common-js/web-env.js +104 -104
  90. package/src/index.ts +7 -7
  91. package/src/lang/api-lang-en.ts +26 -26
  92. package/src/lang/api-lang-zh-cn.ts +27 -27
  93. package/src/lang/index.ts +2 -2
  94. package/src/lang/lang-helper.ts +76 -76
  95. package/src/lang/lang-props.ts +6 -6
  96. package/src/lib/db/db-helper.ts +23 -23
  97. package/src/lib/db/db-mysql.ts +249 -250
  98. package/src/lib/db/db-sqlite.ts +101 -101
  99. package/src/lib/db/db.spec.ts +28 -28
  100. package/src/lib/db/db.ts +325 -325
  101. package/src/lib/db/index.ts +5 -5
  102. package/src/lib/index.ts +3 -3
  103. package/src/lib/logger.spec.ts +214 -214
  104. package/src/lib/logger.ts +281 -281
  105. package/src/lib/runtime-require.ts +37 -37
  106. package/src/lib/utils/cookie-util.ts +34 -34
  107. package/src/lib/utils/crypto.ts +58 -58
  108. package/src/lib/utils/date-utils.ts +317 -317
  109. package/src/lib/utils/deep-merge.ts +37 -37
  110. package/src/lib/utils/delay.ts +12 -12
  111. package/src/lib/utils/file-setting.ts +55 -55
  112. package/src/lib/utils/format-bytes.ts +11 -11
  113. package/src/lib/utils/fs-utils.ts +158 -158
  114. package/src/lib/utils/get-env.ts +27 -27
  115. package/src/lib/utils/index.ts +12 -12
  116. package/src/lib/utils/is-type.ts +48 -48
  117. package/src/lib/utils/load-env.ts +14 -14
  118. package/src/lib/utils/pad.ts +6 -6
  119. package/src/models/api-base.ts +5 -5
  120. package/src/models/api-module-props.ts +10 -11
  121. package/src/models/api-router-props.ts +26 -26
  122. package/src/models/app-cache-props.ts +33 -33
  123. package/src/models/app-data-props.ts +10 -10
  124. package/src/models/app-helper-props.ts +6 -6
  125. package/src/models/app-shared-storage-props.ts +38 -38
  126. package/src/models/app-start-props.ts +18 -18
  127. package/src/models/async-storage-props.ts +13 -13
  128. package/src/models/db-config.ts +30 -30
  129. package/src/models/host-to-path-props.ts +12 -12
  130. package/src/models/index.ts +16 -16
  131. package/src/models/json-object.ts +8 -8
  132. package/src/models/locals-props.ts +36 -36
  133. package/src/models/logger-props.ts +84 -84
  134. package/src/models/simple-storage-props.ts +13 -14
  135. package/src/models/to-client-delivery-props.ts +6 -6
  136. package/tsconfig.json +115 -115
  137. package/dev/plugin-gen-versions.js +0 -20
@@ -1,270 +1,270 @@
1
- import { IncomingMessage } from 'http';
2
- import { Duplex } from 'stream';
3
- import crypto from 'crypto';
4
-
5
- // https://github.com/ErickWendel/websockets-with-nodejs-from-scratch/blob/main/nodejs-raw-websocket/server.mjs
6
- // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
7
- // https://github.com/websockets/ws/blob/d343a0cf7bba29a4e14217cb010446bec8fdf444/lib/receiver.js
8
-
9
- // This is a specification specific to WebSocket protocol
10
- const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
11
-
12
- const MASK_KEY_BYTES_LENGTH = 4;
13
- // opcodes
14
- const OPCODE_CONTINUATION = 0x0;
15
- const OPCODE_TEXT = 0x1;
16
- const OPCODE_BINARY = 0x2;
17
- const OPCODE_CLOSE = 0x8;
18
- const OPCODE_PING = 0x9;
19
- const OPCODE_PONG = 0xa;
20
-
21
- // This is only used in debug mode (no clusters)
22
- export type MiniWebSocketMsgProps = (msg: string, socket: Duplex) => void;
23
- export type MiniWebSocketCloseProps = (socket: Duplex, status: string) => void;
24
- export class MiniWebSocket {
25
- clientRefreshFlag = Date.now();
26
- clients = new Set<Duplex>();
27
- private _onMessage: MiniWebSocketMsgProps;
28
- private _onClose?: MiniWebSocketCloseProps;
29
-
30
- constructor(onMessage: MiniWebSocketMsgProps, onClose?: MiniWebSocketCloseProps) {
31
- this._onMessage = onMessage;
32
- this._onClose = onClose;
33
- }
34
-
35
- public handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer) {
36
- const key = req.headers['sec-websocket-key'];
37
- const upgrade = (req.headers.upgrade || '').toString().toLowerCase();
38
- const connection = (req.headers.connection || '').toString().toLowerCase();
39
-
40
- if (!key || upgrade !== 'websocket' || !connection.includes('upgrade')) {
41
- socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
42
- socket.destroy();
43
- return;
44
- }
45
-
46
- const cleanup = (socket: Duplex, status: string) => {
47
- if (this.clients.has(socket)) this.clients.delete(socket);
48
- if (!socket.destroyed) socket.destroy();
49
- if (this._onClose) this._onClose(socket, status);
50
- };
51
- this.clients.add(socket);
52
- socket.on('close', () => cleanup(socket, 'close'));
53
- socket.on('error', () => cleanup(socket, 'error'));
54
- socket.on('end', () => cleanup(socket, 'end'));
55
- socket.write(this.handleHeaders(key));
56
-
57
- // buffer for cumulative data, parse frame; first append head (possible residual data)
58
- let buffer = head && head.length ? Buffer.from(head) : Buffer.alloc(0);
59
- const onData = (chunk: Buffer) => {
60
- buffer = Buffer.concat([buffer, chunk]);
61
- // try to parse as many complete frames as possible
62
- parseFrames();
63
- };
64
-
65
- const parseFrames = () => {
66
- while (true) {
67
- if (buffer.length < 2) return;
68
-
69
- const firstByte = buffer[0];
70
- const secondByte = buffer[1];
71
-
72
- const fin = (firstByte & 0x80) !== 0; // FIN bit
73
- const rsv = firstByte & 0x70; // must be 0
74
- const opcode = firstByte & 0x0f;
75
-
76
- const masked = (secondByte & 0x80) !== 0; // must be 1
77
- let payloadLen = secondByte & 0x7f;
78
-
79
- let offset = 2;
80
-
81
- // illegal case: RSV set bits; or client not mask
82
- if (rsv !== 0 || !masked) {
83
- this.sendClose(socket, 1002); // protocol error
84
- cleanup(socket, 'protocol error');
85
- return;
86
- }
87
-
88
- // only accept single frame (FIN=1) simple message, ignore fragmented/continuation frame
89
- if (!fin && opcode !== OPCODE_CONTINUATION) {
90
- this.sendClose(socket, 1003); // unsupported data
91
- cleanup(socket, 'unsupported opcode');
92
- return;
93
- }
94
-
95
- if (payloadLen === 126) {
96
- if (buffer.length < offset + 2) return;
97
- payloadLen = buffer.readUInt16BE(offset);
98
- offset += 2;
99
- } else if (payloadLen === 127) {
100
- // read 64-bit length: we only take low 32 bits (enough for your scene)
101
- if (buffer.length < offset + 8) return;
102
- // ignore high 32
103
- payloadLen = buffer.readUInt32BE(offset + 4);
104
- offset += 8;
105
- }
106
-
107
- const needed = offset + MASK_KEY_BYTES_LENGTH + payloadLen;
108
- if (buffer.length < needed) return;
109
-
110
- const maskKey = buffer.slice(offset, offset + MASK_KEY_BYTES_LENGTH);
111
- offset += MASK_KEY_BYTES_LENGTH;
112
-
113
- let payload = buffer.slice(offset, offset + payloadLen);
114
- // unmask
115
- for (let i = 0; i < payload.length; i++) {
116
- payload[i] ^= maskKey[i % MASK_KEY_BYTES_LENGTH];
117
- }
118
-
119
- // consume this frame
120
- buffer = buffer.slice(needed);
121
-
122
- switch (opcode) {
123
- case OPCODE_TEXT: {
124
- const text = payload.toString('utf8');
125
- try {
126
- this._onMessage(text, socket);
127
- } catch {
128
- // ignore user callback error
129
- }
130
- break;
131
- }
132
- case OPCODE_BINARY: {
133
- // don't support binary, close
134
- this.sendClose(socket, 1003); // unsupported data
135
- cleanup(socket, 'unsupported binary');
136
- return;
137
- }
138
- case OPCODE_PING: {
139
- // reply Pong, with same payload
140
- this.safeWrite(socket, this.encodeFrame(payload, OPCODE_PONG));
141
- break;
142
- }
143
- case OPCODE_PONG: {
144
- // ignore
145
- break;
146
- }
147
- case OPCODE_CLOSE: {
148
- // parse status code
149
- let code = 1000;
150
- if (payload.length >= 2) {
151
- code = payload.readUInt16BE(0);
152
- }
153
- // reply Close, then end
154
- this.sendClose(socket, code);
155
- setTimeout(() => {
156
- try {
157
- socket.end();
158
- } catch {}
159
- cleanup(socket, 'close');
160
- }, 20);
161
- return; // end loop
162
- }
163
- case OPCODE_CONTINUATION: {
164
- // don't support continuation, close
165
- this.sendClose(socket, 1003);
166
- cleanup(socket, 'unsupported continuation');
167
- return;
168
- }
169
- default: {
170
- // unknown opcode
171
- this.sendClose(socket, 1002);
172
- cleanup(socket, 'unknown opcode');
173
- return;
174
- }
175
- }
176
- }
177
- };
178
- socket.on('data', onData);
179
- }
180
-
181
- private handleHeaders(key: string) {
182
- const acceptKey = crypto
183
- .createHash('sha1')
184
- .update(key + WEBSOCKET_GUID)
185
- .digest('base64');
186
- const headers = [
187
- 'HTTP/1.1 101 Switching Protocols',
188
- // 'Content-Type: text/html',
189
- 'Upgrade: websocket',
190
- 'Connection: Upgrade',
191
- `Sec-WebSocket-Accept: ${acceptKey}`,
192
- '\r\n',
193
- ];
194
- return headers.join('\r\n');
195
- }
196
-
197
- public broadcast(msg: string) {
198
- const payload = Buffer.from(msg);
199
- const frame = this.encodeFrame(payload, OPCODE_TEXT);
200
- for (const socket of this.clients) {
201
- this.safeWrite(socket, frame);
202
- }
203
- }
204
- public sendMessage(socket: Duplex, msg: string) {
205
- const payload = Buffer.from(msg);
206
- this.safeWrite(socket, this.encodeFrame(payload, OPCODE_TEXT));
207
- }
208
-
209
- encodeFrame(payload: Buffer, opcode: number) {
210
- const len = payload.length;
211
-
212
- // 0x80 === 128 in binary
213
- // FIN=1 + opcode
214
- if (len <= 125) {
215
- const header = Buffer.from([0x80 | opcode, len]);
216
- return Buffer.concat([header, payload]);
217
- }
218
-
219
- if (len < 0x10000) {
220
- // alloc 4 bytes
221
- // [0] - 128 + 1 - 10000001 fin + opcode
222
- // [1] - 126 + 0 - payload length marker + mask indicator
223
- // [2] 0 - content length
224
- // [3] 113 - content length
225
- // [ 4 - ..] - the message itself
226
- const header = Buffer.allocUnsafe(4);
227
- header[0] = 0x80 | opcode;
228
- header[1] = 126;
229
- header.writeUInt16BE(len, 2);
230
- return Buffer.concat([header, payload]);
231
- }
232
-
233
- // 64-bit length:write high 32 bits as 0, low 32 bits as len (enough for your scene)
234
- const header = Buffer.allocUnsafe(10);
235
- header[0] = 0x80 | opcode;
236
- header[1] = 127;
237
- header.writeUInt32BE(0, 2); // high 32
238
- header.writeUInt32BE(len, 6); // low 32
239
- return Buffer.concat([header, payload]);
240
- }
241
-
242
- sendClose(socket: Duplex, code = 1000) {
243
- const payload = Buffer.allocUnsafe(2);
244
- payload.writeUInt16BE(code, 0);
245
- this.safeWrite(socket, this.encodeFrame(payload, OPCODE_CLOSE));
246
- }
247
-
248
- safeWrite(socket: Duplex, data: Buffer) {
249
- try {
250
- if (!socket.destroyed) socket.write(data);
251
- } catch {
252
- // ignore
253
- }
254
- }
255
-
256
- close(socket: Duplex, code = 1000) {
257
- this.sendClose(socket, code);
258
- setTimeout(() => {
259
- try {
260
- socket.end();
261
- } catch {}
262
- }, 20);
263
- }
264
-
265
- closeAll(code = 1000) {
266
- for (const s of this.clients) {
267
- this.close(s, code);
268
- }
269
- }
270
- }
1
+ import { IncomingMessage } from 'http';
2
+ import { Duplex } from 'stream';
3
+ import crypto from 'crypto';
4
+
5
+ // https://github.com/ErickWendel/websockets-with-nodejs-from-scratch/blob/main/nodejs-raw-websocket/server.mjs
6
+ // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
7
+ // https://github.com/websockets/ws/blob/d343a0cf7bba29a4e14217cb010446bec8fdf444/lib/receiver.js
8
+
9
+ // This is a specification specific to WebSocket protocol
10
+ const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
11
+
12
+ const MASK_KEY_BYTES_LENGTH = 4;
13
+ // opcodes
14
+ const OPCODE_CONTINUATION = 0x0;
15
+ const OPCODE_TEXT = 0x1;
16
+ const OPCODE_BINARY = 0x2;
17
+ const OPCODE_CLOSE = 0x8;
18
+ const OPCODE_PING = 0x9;
19
+ const OPCODE_PONG = 0xa;
20
+
21
+ // This is only used in debug mode (no clusters)
22
+ export type MiniWebSocketMsgProps = (msg: string, socket: Duplex) => void;
23
+ export type MiniWebSocketCloseProps = (socket: Duplex, status: string) => void;
24
+ export class MiniWebSocket {
25
+ clientRefreshFlag = Date.now();
26
+ clients = new Set<Duplex>();
27
+ private _onMessage: MiniWebSocketMsgProps;
28
+ private _onClose?: MiniWebSocketCloseProps;
29
+
30
+ constructor(onMessage: MiniWebSocketMsgProps, onClose?: MiniWebSocketCloseProps) {
31
+ this._onMessage = onMessage;
32
+ this._onClose = onClose;
33
+ }
34
+
35
+ public handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer) {
36
+ const key = req.headers['sec-websocket-key'];
37
+ const upgrade = (req.headers.upgrade || '').toString().toLowerCase();
38
+ const connection = (req.headers.connection || '').toString().toLowerCase();
39
+
40
+ if (!key || upgrade !== 'websocket' || !connection.includes('upgrade')) {
41
+ socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
42
+ socket.destroy();
43
+ return;
44
+ }
45
+
46
+ const cleanup = (socket: Duplex, status: string) => {
47
+ if (this.clients.has(socket)) this.clients.delete(socket);
48
+ if (!socket.destroyed) socket.destroy();
49
+ if (this._onClose) this._onClose(socket, status);
50
+ };
51
+ this.clients.add(socket);
52
+ socket.on('close', () => cleanup(socket, 'close'));
53
+ socket.on('error', () => cleanup(socket, 'error'));
54
+ socket.on('end', () => cleanup(socket, 'end'));
55
+ socket.write(this.handleHeaders(key));
56
+
57
+ // buffer for cumulative data, parse frame; first append head (possible residual data)
58
+ let buffer = head && head.length ? Buffer.from(head) : Buffer.alloc(0);
59
+ const onData = (chunk: Buffer) => {
60
+ buffer = Buffer.concat([buffer, chunk]);
61
+ // try to parse as many complete frames as possible
62
+ parseFrames();
63
+ };
64
+
65
+ const parseFrames = () => {
66
+ while (true) {
67
+ if (buffer.length < 2) return;
68
+
69
+ const firstByte = buffer[0];
70
+ const secondByte = buffer[1];
71
+
72
+ const fin = (firstByte & 0x80) !== 0; // FIN bit
73
+ const rsv = firstByte & 0x70; // must be 0
74
+ const opcode = firstByte & 0x0f;
75
+
76
+ const masked = (secondByte & 0x80) !== 0; // must be 1
77
+ let payloadLen = secondByte & 0x7f;
78
+
79
+ let offset = 2;
80
+
81
+ // illegal case: RSV set bits; or client not mask
82
+ if (rsv !== 0 || !masked) {
83
+ this.sendClose(socket, 1002); // protocol error
84
+ cleanup(socket, 'protocol error');
85
+ return;
86
+ }
87
+
88
+ // only accept single frame (FIN=1) simple message, ignore fragmented/continuation frame
89
+ if (!fin && opcode !== OPCODE_CONTINUATION) {
90
+ this.sendClose(socket, 1003); // unsupported data
91
+ cleanup(socket, 'unsupported opcode');
92
+ return;
93
+ }
94
+
95
+ if (payloadLen === 126) {
96
+ if (buffer.length < offset + 2) return;
97
+ payloadLen = buffer.readUInt16BE(offset);
98
+ offset += 2;
99
+ } else if (payloadLen === 127) {
100
+ // read 64-bit length: we only take low 32 bits (enough for your scene)
101
+ if (buffer.length < offset + 8) return;
102
+ // ignore high 32
103
+ payloadLen = buffer.readUInt32BE(offset + 4);
104
+ offset += 8;
105
+ }
106
+
107
+ const needed = offset + MASK_KEY_BYTES_LENGTH + payloadLen;
108
+ if (buffer.length < needed) return;
109
+
110
+ const maskKey = buffer.slice(offset, offset + MASK_KEY_BYTES_LENGTH);
111
+ offset += MASK_KEY_BYTES_LENGTH;
112
+
113
+ let payload = buffer.slice(offset, offset + payloadLen);
114
+ // unmask
115
+ for (let i = 0; i < payload.length; i++) {
116
+ payload[i] ^= maskKey[i % MASK_KEY_BYTES_LENGTH];
117
+ }
118
+
119
+ // consume this frame
120
+ buffer = buffer.slice(needed);
121
+
122
+ switch (opcode) {
123
+ case OPCODE_TEXT: {
124
+ const text = payload.toString('utf8');
125
+ try {
126
+ this._onMessage(text, socket);
127
+ } catch {
128
+ // ignore user callback error
129
+ }
130
+ break;
131
+ }
132
+ case OPCODE_BINARY: {
133
+ // don't support binary, close
134
+ this.sendClose(socket, 1003); // unsupported data
135
+ cleanup(socket, 'unsupported binary');
136
+ return;
137
+ }
138
+ case OPCODE_PING: {
139
+ // reply Pong, with same payload
140
+ this.safeWrite(socket, this.encodeFrame(payload, OPCODE_PONG));
141
+ break;
142
+ }
143
+ case OPCODE_PONG: {
144
+ // ignore
145
+ break;
146
+ }
147
+ case OPCODE_CLOSE: {
148
+ // parse status code
149
+ let code = 1000;
150
+ if (payload.length >= 2) {
151
+ code = payload.readUInt16BE(0);
152
+ }
153
+ // reply Close, then end
154
+ this.sendClose(socket, code);
155
+ setTimeout(() => {
156
+ try {
157
+ socket.end();
158
+ } catch {}
159
+ cleanup(socket, 'close');
160
+ }, 20);
161
+ return; // end loop
162
+ }
163
+ case OPCODE_CONTINUATION: {
164
+ // don't support continuation, close
165
+ this.sendClose(socket, 1003);
166
+ cleanup(socket, 'unsupported continuation');
167
+ return;
168
+ }
169
+ default: {
170
+ // unknown opcode
171
+ this.sendClose(socket, 1002);
172
+ cleanup(socket, 'unknown opcode');
173
+ return;
174
+ }
175
+ }
176
+ }
177
+ };
178
+ socket.on('data', onData);
179
+ }
180
+
181
+ private handleHeaders(key: string) {
182
+ const acceptKey = crypto
183
+ .createHash('sha1')
184
+ .update(key + WEBSOCKET_GUID)
185
+ .digest('base64');
186
+ const headers = [
187
+ 'HTTP/1.1 101 Switching Protocols',
188
+ // 'Content-Type: text/html',
189
+ 'Upgrade: websocket',
190
+ 'Connection: Upgrade',
191
+ `Sec-WebSocket-Accept: ${acceptKey}`,
192
+ '\r\n',
193
+ ];
194
+ return headers.join('\r\n');
195
+ }
196
+
197
+ public broadcast(msg: string) {
198
+ const payload = Buffer.from(msg);
199
+ const frame = this.encodeFrame(payload, OPCODE_TEXT);
200
+ for (const socket of this.clients) {
201
+ this.safeWrite(socket, frame);
202
+ }
203
+ }
204
+ public sendMessage(socket: Duplex, msg: string) {
205
+ const payload = Buffer.from(msg);
206
+ this.safeWrite(socket, this.encodeFrame(payload, OPCODE_TEXT));
207
+ }
208
+
209
+ encodeFrame(payload: Buffer, opcode: number) {
210
+ const len = payload.length;
211
+
212
+ // 0x80 === 128 in binary
213
+ // FIN=1 + opcode
214
+ if (len <= 125) {
215
+ const header = Buffer.from([0x80 | opcode, len]);
216
+ return Buffer.concat([header, payload]);
217
+ }
218
+
219
+ if (len < 0x10000) {
220
+ // alloc 4 bytes
221
+ // [0] - 128 + 1 - 10000001 fin + opcode
222
+ // [1] - 126 + 0 - payload length marker + mask indicator
223
+ // [2] 0 - content length
224
+ // [3] 113 - content length
225
+ // [ 4 - ..] - the message itself
226
+ const header = Buffer.allocUnsafe(4);
227
+ header[0] = 0x80 | opcode;
228
+ header[1] = 126;
229
+ header.writeUInt16BE(len, 2);
230
+ return Buffer.concat([header, payload]);
231
+ }
232
+
233
+ // 64-bit length:write high 32 bits as 0, low 32 bits as len (enough for your scene)
234
+ const header = Buffer.allocUnsafe(10);
235
+ header[0] = 0x80 | opcode;
236
+ header[1] = 127;
237
+ header.writeUInt32BE(0, 2); // high 32
238
+ header.writeUInt32BE(len, 6); // low 32
239
+ return Buffer.concat([header, payload]);
240
+ }
241
+
242
+ sendClose(socket: Duplex, code = 1000) {
243
+ const payload = Buffer.allocUnsafe(2);
244
+ payload.writeUInt16BE(code, 0);
245
+ this.safeWrite(socket, this.encodeFrame(payload, OPCODE_CLOSE));
246
+ }
247
+
248
+ safeWrite(socket: Duplex, data: Buffer) {
249
+ try {
250
+ if (!socket.destroyed) socket.write(data);
251
+ } catch {
252
+ // ignore
253
+ }
254
+ }
255
+
256
+ close(socket: Duplex, code = 1000) {
257
+ this.sendClose(socket, code);
258
+ setTimeout(() => {
259
+ try {
260
+ socket.end();
261
+ } catch {}
262
+ }, 20);
263
+ }
264
+
265
+ closeAll(code = 1000) {
266
+ for (const s of this.clients) {
267
+ this.close(s, code);
268
+ }
269
+ }
270
+ }