@mixd-id/web-scaffold 0.1.240411106 → 0.1.240411108
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.
- package/package.json +1 -1
- package/src/components/List.vue +1 -1
- package/src/utils/wss.js +161 -92
package/package.json
CHANGED
package/src/components/List.vue
CHANGED
package/src/utils/wss.js
CHANGED
|
@@ -63,6 +63,22 @@ const revertDataUrlObj = (obj) => {
|
|
|
63
63
|
return output
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Expected rejections when a redis client is torn down while commands are
|
|
67
|
+
// still queued (e.g. socket closes mid-subscribe) — safe to ignore.
|
|
68
|
+
const isClosedError = (e) =>
|
|
69
|
+
e && (e.message === 'Disconnects client' || e.message === 'The client is closed')
|
|
70
|
+
|
|
71
|
+
const reportError = (e) => {
|
|
72
|
+
if(isClosedError(e)) return
|
|
73
|
+
'$report' in process ? process.$report(e) : console.error(e)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const disconnectQuietly = (client) => {
|
|
77
|
+
if(client && client.isOpen){
|
|
78
|
+
client.disconnect().catch(reportError)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
class WSS extends EventEmitter2{
|
|
67
83
|
|
|
68
84
|
_instance
|
|
@@ -104,11 +120,23 @@ class WSS extends EventEmitter2{
|
|
|
104
120
|
port: process.env.REDIS_PORT ?? 6379
|
|
105
121
|
}
|
|
106
122
|
})
|
|
107
|
-
|
|
123
|
+
// Without an 'error' listener node-redis errors crash the process
|
|
124
|
+
this._client.on('error', reportError)
|
|
125
|
+
this._client.connect().catch(reportError)
|
|
126
|
+
|
|
127
|
+
this._instance.on('error', reportError)
|
|
108
128
|
|
|
109
129
|
this._instance.on('connection', async (socket, req) => {
|
|
110
130
|
socket.id = `${++this._connectionId}`
|
|
111
131
|
socket._retryCount = 0
|
|
132
|
+
socket.path = req.url
|
|
133
|
+
socket.headers = req.headers
|
|
134
|
+
|
|
135
|
+
// Protocol errors (e.g. invalid close codes) emit 'error'; without a
|
|
136
|
+
// listener they crash the process. ws closes the socket afterwards.
|
|
137
|
+
socket.on('error', reportError)
|
|
138
|
+
|
|
139
|
+
let closed = false
|
|
112
140
|
|
|
113
141
|
if(`${req.url}`.indexOf('debug-ws') > 0){
|
|
114
142
|
this._debugIds[socket.id] = {}
|
|
@@ -117,9 +145,17 @@ class WSS extends EventEmitter2{
|
|
|
117
145
|
let subscriber
|
|
118
146
|
|
|
119
147
|
const listener = (message) => {
|
|
120
|
-
|
|
148
|
+
let payload
|
|
149
|
+
try{
|
|
150
|
+
payload = JSON.parse(message)
|
|
151
|
+
}
|
|
152
|
+
catch(e){
|
|
153
|
+
return reportError(e)
|
|
154
|
+
}
|
|
155
|
+
const { model, event, items } = payload ?? {}
|
|
121
156
|
this.toBinaryData({ data:{ model, event, items } }, !!this._debugIds[socket.id])
|
|
122
157
|
.then(data => socket.send(data))
|
|
158
|
+
.catch(reportError)
|
|
123
159
|
}
|
|
124
160
|
|
|
125
161
|
socket.leave = (channel) => {
|
|
@@ -128,7 +164,7 @@ class WSS extends EventEmitter2{
|
|
|
128
164
|
}*/
|
|
129
165
|
|
|
130
166
|
if(subscriber){
|
|
131
|
-
subscriber.unsubscribe(channel, listener)
|
|
167
|
+
subscriber.unsubscribe(channel, listener).catch(reportError)
|
|
132
168
|
}
|
|
133
169
|
}
|
|
134
170
|
|
|
@@ -139,83 +175,25 @@ class WSS extends EventEmitter2{
|
|
|
139
175
|
socket.channels[channel] = 1*/
|
|
140
176
|
|
|
141
177
|
if(subscriber) {
|
|
142
|
-
subscriber.subscribe(channel, listener)
|
|
178
|
+
subscriber.subscribe(channel, listener).catch(reportError)
|
|
143
179
|
}
|
|
144
180
|
}
|
|
145
181
|
|
|
146
182
|
socket.to = (channel) => {
|
|
147
183
|
return {
|
|
148
184
|
emit: (model, event, items) => {
|
|
149
|
-
this.broadcast(channel, { model, event, items }).
|
|
185
|
+
this.broadcast(channel, { model, event, items }).catch(reportError)
|
|
150
186
|
}
|
|
151
187
|
}
|
|
152
188
|
}
|
|
153
189
|
|
|
154
|
-
socket.on('message',
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let obj
|
|
158
|
-
try{
|
|
159
|
-
obj = await this.fromBinaryData(binaryData, !!this._debugIds[socket.id]);
|
|
160
|
-
}
|
|
161
|
-
catch(e){
|
|
162
|
-
return socket.close(1002, e.message);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const { _requestId, path, params, ping, pong } = obj
|
|
166
|
-
|
|
167
|
-
if(pong){
|
|
168
|
-
delete socket.pinging
|
|
169
|
-
socket._retryCount = 0
|
|
170
|
-
}
|
|
171
|
-
else if(ping){
|
|
172
|
-
socket.send(await this.toBinaryData({ pong: 1 }, !!this._debugIds[socket.id]))
|
|
173
|
-
}
|
|
174
|
-
else{
|
|
175
|
-
let status = 200
|
|
176
|
-
let data
|
|
177
|
-
switch(path){
|
|
178
|
-
|
|
179
|
-
default:
|
|
180
|
-
try{
|
|
181
|
-
const arr = (await Promise.all(this._reqFn.map(fn => fn({ path, params }, socket))))
|
|
182
|
-
.filter(_ => _)
|
|
183
|
-
|
|
184
|
-
data = arr.length > 0 ? JSON.parse(JSON.stringify(arr.pop())) : data
|
|
185
|
-
}
|
|
186
|
-
catch(e){
|
|
187
|
-
|
|
188
|
-
if(e.response){
|
|
189
|
-
'$report' in process ? process.$report(e) : console.error(e.response.status, e.response.data)
|
|
190
|
-
}
|
|
191
|
-
else{
|
|
192
|
-
'$report' in process ? process.$report(e) : console.error(e)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
status = 500
|
|
196
|
-
data = {
|
|
197
|
-
name: e.name,
|
|
198
|
-
message: e.message,
|
|
199
|
-
errors: e.response ? e.response.data : e.errors,
|
|
200
|
-
stack: process.env.APP_DEBUG === 'true' ?
|
|
201
|
-
e.stack : undefined,
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
break
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
socket.send(await this.toBinaryData({
|
|
208
|
-
_requestId,
|
|
209
|
-
status,
|
|
210
|
-
data
|
|
211
|
-
}, !!this._debugIds[socket.id]))
|
|
212
|
-
}
|
|
190
|
+
socket.on('message', (binaryData) => {
|
|
191
|
+
this._onMessage(socket, binaryData).catch(reportError)
|
|
213
192
|
});
|
|
214
193
|
|
|
215
194
|
socket.on('close', (e) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
195
|
+
closed = true
|
|
196
|
+
disconnectQuietly(subscriber)
|
|
219
197
|
if(this._debugIds[socket.id]){
|
|
220
198
|
delete this._debugIds[socket.id]
|
|
221
199
|
}
|
|
@@ -232,45 +210,64 @@ class WSS extends EventEmitter2{
|
|
|
232
210
|
await fn(token, socket)
|
|
233
211
|
}
|
|
234
212
|
|
|
235
|
-
|
|
236
|
-
|
|
213
|
+
const client = this._client.duplicate()
|
|
214
|
+
client.on('error', reportError)
|
|
215
|
+
subscriber = client
|
|
216
|
+
await client.connect()
|
|
217
|
+
|
|
218
|
+
// Socket closed while authenticating/connecting: the close handler may
|
|
219
|
+
// have run before the client was open, so release it here.
|
|
220
|
+
if(closed){
|
|
221
|
+
disconnectQuietly(client)
|
|
222
|
+
return
|
|
223
|
+
}
|
|
237
224
|
|
|
238
225
|
socket.isAuth = true
|
|
239
226
|
socket.send(await this.toBinaryData({ auth:true }, !!this._debugIds[socket.id]))
|
|
240
227
|
}
|
|
241
228
|
catch(e){
|
|
229
|
+
disconnectQuietly(subscriber)
|
|
230
|
+
if(closed) return
|
|
242
231
|
return socket.close(1002, (e.message ?? '').substring(0, 123));
|
|
243
232
|
}
|
|
244
233
|
|
|
245
|
-
if(this._opt.ping !== false){
|
|
234
|
+
if(this._opt.ping !== false && !closed){
|
|
246
235
|
let pingTimer
|
|
247
236
|
|
|
237
|
+
const schedule = (fn, ms) => {
|
|
238
|
+
if(!closed) pingTimer = setTimeout(fn, ms)
|
|
239
|
+
}
|
|
240
|
+
|
|
248
241
|
const ping = async() => {
|
|
249
|
-
if(socket.readyState !== WebSocket.OPEN) return
|
|
242
|
+
if(closed || socket.readyState !== WebSocket.OPEN) return
|
|
250
243
|
|
|
251
|
-
|
|
244
|
+
try{
|
|
245
|
+
socket.send(await this.toBinaryData({ ping:1 }, !!this._debugIds[socket.id]))
|
|
246
|
+
}
|
|
247
|
+
catch(e){
|
|
248
|
+
return reportError(e)
|
|
249
|
+
}
|
|
252
250
|
socket.pinging = true
|
|
253
251
|
|
|
254
|
-
|
|
252
|
+
schedule(() => {
|
|
255
253
|
if(socket.pinging){
|
|
256
254
|
socket._retryCount++
|
|
257
255
|
|
|
258
256
|
if(socket._retryCount > 10){
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
257
|
+
// Peer is unresponsive, a close handshake would just hang;
|
|
258
|
+
// terminate so 'close' fires now and releases the subscriber.
|
|
259
|
+
socket.terminate()
|
|
263
260
|
}
|
|
264
261
|
else{
|
|
265
|
-
|
|
262
|
+
schedule(ping, 3000)
|
|
266
263
|
}
|
|
267
264
|
}
|
|
268
265
|
else{
|
|
269
|
-
|
|
266
|
+
schedule(ping, (Math.round(Math.random() * 10) % 5) * 5000)
|
|
270
267
|
}
|
|
271
268
|
}, 3000)
|
|
272
269
|
}
|
|
273
|
-
|
|
270
|
+
schedule(ping, (Math.round(Math.random() * 10) % 5) * 5000)
|
|
274
271
|
|
|
275
272
|
socket.on('close', () => {
|
|
276
273
|
clearTimeout(pingTimer)
|
|
@@ -279,6 +276,68 @@ class WSS extends EventEmitter2{
|
|
|
279
276
|
})
|
|
280
277
|
}
|
|
281
278
|
|
|
279
|
+
async _onMessage(socket, binaryData){
|
|
280
|
+
if(!socket.isAuth) return
|
|
281
|
+
|
|
282
|
+
let obj
|
|
283
|
+
try{
|
|
284
|
+
obj = await this.fromBinaryData(binaryData, !!this._debugIds[socket.id]);
|
|
285
|
+
if(typeof obj !== 'object' || obj === null) throw new Error('Invalid message')
|
|
286
|
+
}
|
|
287
|
+
catch(e){
|
|
288
|
+
return socket.close(1002, (e.message ?? '').substring(0, 123));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const { _requestId, path, params, ping, pong } = obj
|
|
292
|
+
|
|
293
|
+
if(pong){
|
|
294
|
+
delete socket.pinging
|
|
295
|
+
socket._retryCount = 0
|
|
296
|
+
}
|
|
297
|
+
else if(ping){
|
|
298
|
+
socket.send(await this.toBinaryData({ pong: 1 }, !!this._debugIds[socket.id]))
|
|
299
|
+
}
|
|
300
|
+
else{
|
|
301
|
+
let status = 200
|
|
302
|
+
let data
|
|
303
|
+
switch(path){
|
|
304
|
+
|
|
305
|
+
default:
|
|
306
|
+
try{
|
|
307
|
+
const arr = (await Promise.all(this._reqFn.map(fn => fn({ path, params }, socket))))
|
|
308
|
+
.filter(_ => _)
|
|
309
|
+
|
|
310
|
+
data = arr.length > 0 ? JSON.parse(JSON.stringify(arr.pop())) : data
|
|
311
|
+
}
|
|
312
|
+
catch(e){
|
|
313
|
+
|
|
314
|
+
if(e.response){
|
|
315
|
+
'$report' in process ? process.$report(e) : console.error(e.response.status, e.response.data)
|
|
316
|
+
}
|
|
317
|
+
else{
|
|
318
|
+
'$report' in process ? process.$report(e) : console.error(e)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
status = 500
|
|
322
|
+
data = {
|
|
323
|
+
name: e.name,
|
|
324
|
+
message: e.message,
|
|
325
|
+
errors: e.response ? e.response.data : e.errors,
|
|
326
|
+
stack: process.env.APP_DEBUG === 'true' ?
|
|
327
|
+
e.stack : undefined,
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
socket.send(await this.toBinaryData({
|
|
334
|
+
_requestId,
|
|
335
|
+
status,
|
|
336
|
+
data
|
|
337
|
+
}, !!this._debugIds[socket.id]))
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
282
341
|
stats(){
|
|
283
342
|
let authenticated = 0
|
|
284
343
|
this._instance.clients.forEach(socket => {
|
|
@@ -296,25 +355,34 @@ class WSS extends EventEmitter2{
|
|
|
296
355
|
|
|
297
356
|
clusterStats(){
|
|
298
357
|
return new Promise((resolve) => {
|
|
299
|
-
|
|
300
|
-
const
|
|
358
|
+
// Include own stats
|
|
359
|
+
const workers = { [process.pid]: this.stats() }
|
|
301
360
|
|
|
302
|
-
process.
|
|
303
|
-
|
|
361
|
+
if(!process.send){
|
|
362
|
+
return resolve(this._aggregateStats(workers))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Listener must be removed once done, otherwise every call leaks one
|
|
366
|
+
const finish = () => {
|
|
367
|
+
clearTimeout(timeout)
|
|
368
|
+
process.removeListener('message', onMessage)
|
|
369
|
+
resolve(this._aggregateStats(workers))
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const onMessage = (packet) => {
|
|
373
|
+
if(packet?.type === 'process:msg' && packet.data?.type === 'wss:stats:response'){
|
|
304
374
|
workers[packet.data.pid] = packet.data.stats
|
|
305
375
|
}
|
|
306
376
|
|
|
307
377
|
const expected = parseInt(process.env.instances || '0', 10)
|
|
308
378
|
if(expected > 0 && Object.keys(workers).length >= expected){
|
|
309
|
-
|
|
310
|
-
resolve(this._aggregateStats(workers))
|
|
379
|
+
finish()
|
|
311
380
|
}
|
|
312
|
-
}
|
|
381
|
+
}
|
|
313
382
|
|
|
383
|
+
const timeout = setTimeout(finish, 3000)
|
|
384
|
+
process.on('message', onMessage)
|
|
314
385
|
process.send({ type: 'process:msg', data: { type: 'wss:stats:request' } })
|
|
315
|
-
|
|
316
|
-
// Include own stats
|
|
317
|
-
workers[process.pid] = this.stats()
|
|
318
386
|
})
|
|
319
387
|
}
|
|
320
388
|
|
|
@@ -331,9 +399,10 @@ class WSS extends EventEmitter2{
|
|
|
331
399
|
}
|
|
332
400
|
|
|
333
401
|
initClusterStats(){
|
|
334
|
-
if(process.send){
|
|
402
|
+
if(process.send && !this._clusterStatsInit){
|
|
403
|
+
this._clusterStatsInit = true
|
|
335
404
|
process.on('message', (packet) => {
|
|
336
|
-
if(packet
|
|
405
|
+
if(packet?.type === 'process:msg' && packet.data?.type === 'wss:stats:request'){
|
|
337
406
|
process.send({
|
|
338
407
|
type: 'process:msg',
|
|
339
408
|
data: { type: 'wss:stats:response', pid: process.pid, stats: this.stats() }
|