@logux/server 0.13.1 → 0.15.0
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/LICENSE +1 -1
- package/README.md +18 -41
- package/{base-server → add-http-pages}/hello.html +34 -26
- package/add-http-pages/index.js +24 -0
- package/add-sync-map/index.d.ts +1 -3
- package/add-sync-map/index.js +4 -10
- package/base-server/index.d.ts +175 -119
- package/base-server/index.js +272 -184
- package/context/index.d.ts +70 -61
- package/context/index.js +33 -20
- package/create-reporter/index.js +80 -93
- package/human-formatter/index.js +60 -85
- package/human-formatter/utils.js +14 -0
- package/index.d.ts +1 -1
- package/index.js +1 -9
- package/options-loader/index.js +46 -14
- package/options-loader/index.test.js +3 -3
- package/options-loader/test.env +2 -0
- package/package.json +21 -32
- package/request/index.d.ts +1 -99
- package/request/index.js +2 -40
- package/server/index.d.ts +31 -40
- package/server/index.js +26 -22
- package/server-client/index.d.ts +21 -12
- package/server-client/index.js +56 -11
- package/test-client/index.d.ts +24 -7
- package/test-client/index.js +8 -8
- package/test-server/index.d.ts +27 -14
- package/test-server/index.js +25 -5
- package/url-pattern/index.js +28 -0
- package/bind-backend-proxy/index.js +0 -325
- package/bind-control-server/index.js +0 -137
- package/request/test.test.js +0 -134
package/base-server/index.js
CHANGED
|
@@ -2,21 +2,38 @@ import { LoguxNotFoundError } from '@logux/actions'
|
|
|
2
2
|
import { Log, MemoryStore, parseId, ServerConnection } from '@logux/core'
|
|
3
3
|
import { createNanoEvents } from 'nanoevents'
|
|
4
4
|
import { nanoid } from 'nanoid'
|
|
5
|
-
import {
|
|
5
|
+
import { readFile } from 'node:fs/promises'
|
|
6
6
|
import { dirname, join } from 'node:path'
|
|
7
7
|
import { fileURLToPath } from 'node:url'
|
|
8
|
-
import UrlPattern from 'url-pattern'
|
|
9
8
|
import { WebSocketServer } from 'ws'
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
import { bindControlServer } from '../bind-control-server/index.js'
|
|
10
|
+
import { addHttpPages } from '../add-http-pages/index.js'
|
|
13
11
|
import { Context } from '../context/index.js'
|
|
14
12
|
import { createHttpServer } from '../create-http-server/index.js'
|
|
15
13
|
import { ServerClient } from '../server-client/index.js'
|
|
14
|
+
import { createPattern } from '../url-pattern/index.js'
|
|
16
15
|
|
|
17
16
|
const SKIP_PROCESS = Symbol('skipProcess')
|
|
18
17
|
const RESEND_META = ['channels', 'users', 'clients', 'nodes']
|
|
19
18
|
|
|
19
|
+
function addTarget(targets, client, action, meta) {
|
|
20
|
+
let entries = targets.get(client)
|
|
21
|
+
if (entries) {
|
|
22
|
+
entries.push([action, meta])
|
|
23
|
+
} else {
|
|
24
|
+
targets.set(client, [[action, meta]])
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sendTargets(targets) {
|
|
29
|
+
for (let [client, entries] of targets) {
|
|
30
|
+
let sending = client.node.onAdd(entries)
|
|
31
|
+
// Only `ServerClient` tracks sending for `Server#drain()`
|
|
32
|
+
if (client.track) client.track(sending)
|
|
33
|
+
}
|
|
34
|
+
targets.clear()
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
function optionError(msg) {
|
|
21
38
|
let error = new Error(msg)
|
|
22
39
|
error.logux = true
|
|
@@ -24,17 +41,6 @@ function optionError(msg) {
|
|
|
24
41
|
throw error
|
|
25
42
|
}
|
|
26
43
|
|
|
27
|
-
let helloCache
|
|
28
|
-
async function readHello() {
|
|
29
|
-
if (!helloCache) {
|
|
30
|
-
let hello = await fs.readFile(
|
|
31
|
-
join(fileURLToPath(import.meta.url), '..', 'hello.html')
|
|
32
|
-
)
|
|
33
|
-
helloCache = hello.toString()
|
|
34
|
-
}
|
|
35
|
-
return helloCache
|
|
36
|
-
}
|
|
37
|
-
|
|
38
44
|
export async function wasNot403(cb) {
|
|
39
45
|
try {
|
|
40
46
|
await cb()
|
|
@@ -103,17 +109,11 @@ export class BaseServer {
|
|
|
103
109
|
this.options = opts
|
|
104
110
|
this.env = this.options.env || process.env.NODE_ENV || 'development'
|
|
105
111
|
|
|
106
|
-
if (
|
|
107
|
-
typeof this.options.subprotocol === 'undefined' &&
|
|
108
|
-
typeof this.options.backend === 'undefined'
|
|
109
|
-
) {
|
|
112
|
+
if (typeof this.options.subprotocol === 'undefined') {
|
|
110
113
|
throw optionError('Missed `subprotocol` option in server constructor')
|
|
111
114
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
typeof this.options.backend === 'undefined'
|
|
115
|
-
) {
|
|
116
|
-
throw optionError('Missed `supports` option in server constructor')
|
|
115
|
+
if (typeof this.options.minSubprotocol === 'undefined') {
|
|
116
|
+
throw optionError('Missed `minSubprotocol` option in server constructor')
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
if (this.options.key && !this.options.cert) {
|
|
@@ -128,14 +128,16 @@ export class BaseServer {
|
|
|
128
128
|
if (!this.options.host) this.options.host = '127.0.0.1'
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
this.nodeId = `server:${this.options.id || nanoid(
|
|
131
|
+
this.nodeId = `server:${this.options.id || nanoid(6)}`
|
|
132
132
|
|
|
133
133
|
if (this.options.fileUrl) {
|
|
134
134
|
this.options.root = dirname(fileURLToPath(this.options.fileUrl))
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
this.options.root = this.options.root || process.cwd()
|
|
138
|
-
|
|
138
|
+
if (typeof this.options.port === 'string') {
|
|
139
|
+
this.options.port = parseInt(this.options.port, 10)
|
|
140
|
+
}
|
|
139
141
|
|
|
140
142
|
let store = this.options.store || new MemoryStore()
|
|
141
143
|
|
|
@@ -167,7 +169,6 @@ export class BaseServer {
|
|
|
167
169
|
}
|
|
168
170
|
if (
|
|
169
171
|
!isLogux &&
|
|
170
|
-
!this.options.backend &&
|
|
171
172
|
!this.types[action.type] &&
|
|
172
173
|
!this.getRegexProcessor(action.type)
|
|
173
174
|
) {
|
|
@@ -176,89 +177,64 @@ export class BaseServer {
|
|
|
176
177
|
}
|
|
177
178
|
this.replaceResendShortcuts(meta)
|
|
178
179
|
})
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
// Reports are per action, `clean` for a reason-less action is emitted
|
|
181
|
+
// by the log before `batch` event
|
|
182
|
+
this.on('add', (action, meta) => {
|
|
181
183
|
if (meta.reasons.length === 0) {
|
|
182
184
|
cleaned[meta.id] = true
|
|
183
185
|
this.emitter.emit('report', 'addClean', { action, meta })
|
|
184
186
|
} else {
|
|
185
187
|
this.emitter.emit('report', 'add', { action, meta })
|
|
186
188
|
}
|
|
189
|
+
})
|
|
190
|
+
this.on('batch', entries => {
|
|
191
|
+
let start = Date.now()
|
|
192
|
+
let ready = []
|
|
187
193
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (action.type === 'logux/subscribe') {
|
|
193
|
-
if (meta.server === this.nodeId) {
|
|
194
|
-
this.subscribeAction(action, meta, start)
|
|
194
|
+
for (let [action, meta] of entries) {
|
|
195
|
+
if (this.destroying && !this.actionToQueue.has(meta.id)) {
|
|
196
|
+
continue
|
|
195
197
|
}
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
if (action.type === 'logux/subscribe') {
|
|
200
|
+
if (meta.server === this.nodeId) {
|
|
201
|
+
void this.subscribeAction(action, meta, start)
|
|
202
|
+
}
|
|
203
|
+
continue
|
|
202
204
|
}
|
|
203
|
-
return
|
|
204
|
-
}
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let resend
|
|
210
|
-
try {
|
|
211
|
-
resend = await processor.resend(ctx, action, meta)
|
|
212
|
-
} catch (e) {
|
|
213
|
-
this.undo(action, meta, 'error')
|
|
214
|
-
this.emitter.emit('error', e, action, meta)
|
|
215
|
-
this.finally(processor, ctx, action, meta)
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
if (resend) {
|
|
219
|
-
if (typeof resend === 'string') {
|
|
220
|
-
resend = { channels: [resend] }
|
|
221
|
-
} else if (Array.isArray(resend)) {
|
|
222
|
-
resend = { channels: resend }
|
|
223
|
-
} else {
|
|
224
|
-
this.replaceResendShortcuts(resend)
|
|
206
|
+
if (action.type === 'logux/unsubscribe') {
|
|
207
|
+
if (meta.server === this.nodeId) {
|
|
208
|
+
this.unsubscribeAction(action, meta)
|
|
225
209
|
}
|
|
226
|
-
|
|
227
|
-
for (let i of RESEND_META) {
|
|
228
|
-
if (resend[i]) diff[i] = resend[i]
|
|
229
|
-
}
|
|
230
|
-
await this.log.changeMeta(meta.id, diff)
|
|
231
|
-
meta = { ...meta, ...diff }
|
|
210
|
+
continue
|
|
232
211
|
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (this.isUseless(action, meta)) {
|
|
236
|
-
this.emitter.emit('report', 'useless', { action, meta })
|
|
237
|
-
}
|
|
238
212
|
|
|
239
|
-
|
|
213
|
+
let processor = this.getProcessor(action.type)
|
|
214
|
+
if (processor && processor.resend && meta.status === 'waiting') {
|
|
215
|
+
// Resending is asynchronous, so the action can’t wait for others
|
|
216
|
+
void this.resendAction(action, meta, processor, start)
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
240
219
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
this.internalUnknownType(action, meta)
|
|
244
|
-
return
|
|
220
|
+
if (this.isUseless(action, meta)) {
|
|
221
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
245
222
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
action,
|
|
254
|
-
meta
|
|
255
|
-
)
|
|
256
|
-
this.markAsProcessed(meta)
|
|
223
|
+
ready.push([action, meta, processor])
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (ready.length === 0) return
|
|
227
|
+
let runProcessors = () => {
|
|
228
|
+
for (let [action, meta, processor] of ready) {
|
|
229
|
+
this.runProcessor(action, meta, processor, start)
|
|
257
230
|
}
|
|
258
|
-
} else {
|
|
259
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
260
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
261
231
|
}
|
|
232
|
+
// Actions are processed only after they were sent to the clients
|
|
233
|
+
void Promise.resolve(this.sendBatch(ready)).then(runProcessors, error => {
|
|
234
|
+
// Do not stop action processing on a broken channel filter
|
|
235
|
+
runProcessors()
|
|
236
|
+
throw error
|
|
237
|
+
})
|
|
262
238
|
})
|
|
263
239
|
this.on('clean', (action, meta) => {
|
|
264
240
|
if (cleaned[meta.id]) {
|
|
@@ -339,27 +315,11 @@ export class BaseServer {
|
|
|
339
315
|
this.queues = new Map()
|
|
340
316
|
this.actionToQueue = new Map()
|
|
341
317
|
|
|
342
|
-
this.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return {
|
|
346
|
-
body: await readHello(),
|
|
347
|
-
headers: { 'Content-Type': 'text/html' }
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
safe: true
|
|
351
|
-
},
|
|
352
|
-
'GET /health': {
|
|
353
|
-
request: () => ({
|
|
354
|
-
body: 'Logux Server: OK\n',
|
|
355
|
-
headers: { 'Content-Type': 'text/plain' }
|
|
356
|
-
}),
|
|
357
|
-
safe: true
|
|
358
|
-
}
|
|
359
|
-
}
|
|
318
|
+
this.httpListeners = {}
|
|
319
|
+
this.httpAllListeners = []
|
|
320
|
+
addHttpPages(this)
|
|
360
321
|
|
|
361
322
|
this.listenNotes = {}
|
|
362
|
-
bindBackendProxy(this)
|
|
363
323
|
|
|
364
324
|
let end = (actionId, queue, queueKey, ...args) => {
|
|
365
325
|
this.actionToQueue.delete(actionId)
|
|
@@ -478,9 +438,7 @@ export class BaseServer {
|
|
|
478
438
|
normalizeChannelCallbacks(`Channel ${pattern}`, callbacks)
|
|
479
439
|
let channel = Object.assign({}, callbacks)
|
|
480
440
|
if (typeof pattern === 'string') {
|
|
481
|
-
channel.pattern =
|
|
482
|
-
segmentValueCharset: '^/'
|
|
483
|
-
})
|
|
441
|
+
channel.pattern = createPattern(pattern)
|
|
484
442
|
} else {
|
|
485
443
|
channel.regexp = pattern
|
|
486
444
|
}
|
|
@@ -529,6 +487,12 @@ export class BaseServer {
|
|
|
529
487
|
return Promise.all(this.unbind.map(i => i()))
|
|
530
488
|
}
|
|
531
489
|
|
|
490
|
+
drain(clientId) {
|
|
491
|
+
let client = this.clientIds.get(clientId)
|
|
492
|
+
if (!client) return Promise.resolve(false)
|
|
493
|
+
return client.drain()
|
|
494
|
+
}
|
|
495
|
+
|
|
532
496
|
finally(processor, ctx, action, meta) {
|
|
533
497
|
this.contexts.delete(action)
|
|
534
498
|
if (processor && processor.finally) {
|
|
@@ -555,13 +519,22 @@ export class BaseServer {
|
|
|
555
519
|
return undefined
|
|
556
520
|
}
|
|
557
521
|
|
|
558
|
-
|
|
522
|
+
handleClient(ws, req) {
|
|
523
|
+
ws.upgradeReq = req
|
|
524
|
+
this.addClient(new ServerConnection(ws))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
http(method, url, listener) {
|
|
559
528
|
if (this.options.disableHttpServer) {
|
|
560
529
|
throw new Error(
|
|
561
530
|
'`server.http()` can not be called when `disableHttpServer` enabled'
|
|
562
531
|
)
|
|
563
532
|
}
|
|
564
|
-
|
|
533
|
+
if (!url) {
|
|
534
|
+
this.httpAllListeners.push(method)
|
|
535
|
+
} else {
|
|
536
|
+
this.httpListeners[`${method} ${url}`] = listener
|
|
537
|
+
}
|
|
565
538
|
}
|
|
566
539
|
|
|
567
540
|
internalUnknownType(action, meta) {
|
|
@@ -618,50 +591,54 @@ export class BaseServer {
|
|
|
618
591
|
this.httpServer.listen(this.options.port, this.options.host, resolve)
|
|
619
592
|
})
|
|
620
593
|
}
|
|
621
|
-
bindControlServer(this, this.httpListener)
|
|
622
594
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
595
|
+
let processing = 0
|
|
596
|
+
let waiting
|
|
597
|
+
this.unbind.push(() => {
|
|
598
|
+
return new Promise(resolve => {
|
|
599
|
+
let end = () => {
|
|
600
|
+
this.ws.close(resolve)
|
|
601
|
+
this.httpServer.close()
|
|
602
|
+
}
|
|
603
|
+
if (processing === 0) {
|
|
604
|
+
end()
|
|
605
|
+
} else {
|
|
606
|
+
waiting = end
|
|
607
|
+
}
|
|
608
|
+
})
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
if (!this.options.disableHttpServer) {
|
|
612
|
+
this.httpServer.on('request', async (req, res) => {
|
|
613
|
+
if (this.destroying) {
|
|
614
|
+
res.writeHead(503, { 'Content-Type': 'text/plain' })
|
|
615
|
+
res.end('The server is shutting down\n')
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
processing += 1
|
|
619
|
+
await this.processHttp(req, res)
|
|
620
|
+
processing -= 1
|
|
621
|
+
if (processing === 0 && waiting) waiting()
|
|
622
|
+
})
|
|
638
623
|
}
|
|
639
624
|
|
|
640
625
|
let pkg = JSON.parse(
|
|
641
|
-
await
|
|
642
|
-
join(fileURLToPath(import.meta.url), '..', '..', 'package.json')
|
|
643
|
-
)
|
|
626
|
+
await readFile(join(import.meta.dirname, '..', 'package.json'))
|
|
644
627
|
)
|
|
645
628
|
|
|
646
|
-
this.ws.on('connection', (ws, req) =>
|
|
647
|
-
ws.upgradeReq = req
|
|
648
|
-
this.addClient(new ServerConnection(ws))
|
|
649
|
-
})
|
|
629
|
+
this.ws.on('connection', (ws, req) => this.handleClient(ws, req))
|
|
650
630
|
this.emitter.emit('report', 'listen', {
|
|
651
|
-
backend: this.options.backend,
|
|
652
631
|
cert: !!this.options.cert,
|
|
653
|
-
controlMask: this.options.controlMask,
|
|
654
|
-
controlSecret: this.options.controlSecret,
|
|
655
632
|
environment: this.env,
|
|
656
633
|
host: this.options.host,
|
|
657
634
|
loguxServer: pkg.version,
|
|
635
|
+
minSubprotocol: this.options.minSubprotocol,
|
|
658
636
|
nodeId: this.nodeId,
|
|
659
637
|
notes: this.listenNotes,
|
|
660
638
|
port: this.options.port,
|
|
661
639
|
redis: this.options.redis,
|
|
662
640
|
server: !!this.options.server,
|
|
663
|
-
subprotocol: this.options.subprotocol
|
|
664
|
-
supports: this.options.supports
|
|
641
|
+
subprotocol: this.options.subprotocol
|
|
665
642
|
})
|
|
666
643
|
}
|
|
667
644
|
|
|
@@ -677,7 +654,12 @@ export class BaseServer {
|
|
|
677
654
|
}
|
|
678
655
|
|
|
679
656
|
on(event, listener) {
|
|
680
|
-
if (
|
|
657
|
+
if (
|
|
658
|
+
event === 'preadd' ||
|
|
659
|
+
event === 'add' ||
|
|
660
|
+
event === 'batch' ||
|
|
661
|
+
event === 'clean'
|
|
662
|
+
) {
|
|
681
663
|
return this.log.emitter.on(event, listener)
|
|
682
664
|
} else {
|
|
683
665
|
return this.emitter.on(event, listener)
|
|
@@ -690,11 +672,7 @@ export class BaseServer {
|
|
|
690
672
|
throw new Error('Callbacks for unknown channel are already defined')
|
|
691
673
|
}
|
|
692
674
|
let channel = Object.assign({}, callbacks)
|
|
693
|
-
channel.pattern =
|
|
694
|
-
match(name) {
|
|
695
|
-
return [name]
|
|
696
|
-
}
|
|
697
|
-
}
|
|
675
|
+
channel.pattern = name => [name]
|
|
698
676
|
this.otherSubscriber = channel
|
|
699
677
|
}
|
|
700
678
|
|
|
@@ -707,6 +685,7 @@ export class BaseServer {
|
|
|
707
685
|
}
|
|
708
686
|
|
|
709
687
|
performUnsubscribe(clientNodeId, action, meta) {
|
|
688
|
+
if (action.channel === '__proto__' || clientNodeId === '__proto__') return
|
|
710
689
|
if (this.subscribers[action.channel]) {
|
|
711
690
|
let subscriber = this.subscribers[action.channel][clientNodeId]
|
|
712
691
|
if (subscriber) {
|
|
@@ -772,6 +751,32 @@ export class BaseServer {
|
|
|
772
751
|
this.emitter.emit('processed', action, meta, latency)
|
|
773
752
|
}
|
|
774
753
|
|
|
754
|
+
async processHttp(req, res) {
|
|
755
|
+
let urlString = req.url
|
|
756
|
+
if (/^\/\w+%3F/.test(urlString)) {
|
|
757
|
+
urlString = decodeURIComponent(urlString)
|
|
758
|
+
}
|
|
759
|
+
let reqUrl = new URL(urlString, 'http://localhost')
|
|
760
|
+
let rule = this.httpListeners[req.method + ' ' + reqUrl.pathname]
|
|
761
|
+
|
|
762
|
+
if (!rule) {
|
|
763
|
+
let processed = false
|
|
764
|
+
for (let listener of this.httpAllListeners) {
|
|
765
|
+
let result = await listener(req, res)
|
|
766
|
+
if (result === true) {
|
|
767
|
+
processed = true
|
|
768
|
+
break
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (!processed) {
|
|
772
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
773
|
+
res.end('Not found\n')
|
|
774
|
+
}
|
|
775
|
+
} else {
|
|
776
|
+
await rule(req, res)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
775
780
|
rememberBadAuth(ip) {
|
|
776
781
|
this.authAttempts[ip] = (this.authAttempts[ip] || 0) + 1
|
|
777
782
|
this.setTimeout(() => {
|
|
@@ -802,7 +807,42 @@ export class BaseServer {
|
|
|
802
807
|
}
|
|
803
808
|
}
|
|
804
809
|
|
|
805
|
-
async
|
|
810
|
+
async resendAction(action, meta, processor, start) {
|
|
811
|
+
let ctx = this.createContext(action, meta)
|
|
812
|
+
let resend
|
|
813
|
+
try {
|
|
814
|
+
resend = await processor.resend(ctx, action, meta)
|
|
815
|
+
} catch (e) {
|
|
816
|
+
this.undo(action, meta, 'error')
|
|
817
|
+
this.emitter.emit('error', e, action, meta)
|
|
818
|
+
this.finally(processor, ctx, action, meta)
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
if (resend) {
|
|
822
|
+
if (typeof resend === 'string') {
|
|
823
|
+
resend = { channels: [resend] }
|
|
824
|
+
} else if (Array.isArray(resend)) {
|
|
825
|
+
resend = { channels: resend }
|
|
826
|
+
} else {
|
|
827
|
+
this.replaceResendShortcuts(resend)
|
|
828
|
+
}
|
|
829
|
+
let diff = {}
|
|
830
|
+
for (let i of RESEND_META) {
|
|
831
|
+
if (resend[i]) diff[i] = resend[i]
|
|
832
|
+
}
|
|
833
|
+
await this.log.changeMeta(meta.id, diff)
|
|
834
|
+
meta = { ...meta, ...diff }
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (this.isUseless(action, meta)) {
|
|
838
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
await this.sendAction(action, meta)
|
|
842
|
+
this.runProcessor(action, meta, processor, start)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
resolveTargets(action, meta, targets) {
|
|
806
846
|
let from = parseId(meta.id).clientId
|
|
807
847
|
let ignoreClients = new Set(meta.excludeClients || [])
|
|
808
848
|
ignoreClients.add(from)
|
|
@@ -812,7 +852,7 @@ export class BaseServer {
|
|
|
812
852
|
let client = this.nodeIds.get(id)
|
|
813
853
|
if (client) {
|
|
814
854
|
ignoreClients.add(client.clientId)
|
|
815
|
-
client
|
|
855
|
+
addTarget(targets, client, action, meta)
|
|
816
856
|
}
|
|
817
857
|
}
|
|
818
858
|
}
|
|
@@ -822,7 +862,7 @@ export class BaseServer {
|
|
|
822
862
|
if (this.clientIds.has(id)) {
|
|
823
863
|
let client = this.clientIds.get(id)
|
|
824
864
|
ignoreClients.add(client.clientId)
|
|
825
|
-
client
|
|
865
|
+
addTarget(targets, client, action, meta)
|
|
826
866
|
}
|
|
827
867
|
}
|
|
828
868
|
}
|
|
@@ -834,32 +874,38 @@ export class BaseServer {
|
|
|
834
874
|
for (let client of users) {
|
|
835
875
|
if (!ignoreClients.has(client.clientId)) {
|
|
836
876
|
ignoreClients.add(client.clientId)
|
|
837
|
-
client
|
|
877
|
+
addTarget(targets, client, action, meta)
|
|
838
878
|
}
|
|
839
879
|
}
|
|
840
880
|
}
|
|
841
881
|
}
|
|
842
882
|
}
|
|
843
883
|
|
|
844
|
-
if (meta.channels)
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
884
|
+
if (!meta.channels) return undefined
|
|
885
|
+
|
|
886
|
+
let waiting = []
|
|
887
|
+
for (let channel of meta.channels) {
|
|
888
|
+
if (this.subscribers[channel]) {
|
|
889
|
+
for (let nodeId in this.subscribers[channel]) {
|
|
890
|
+
let clientId = parseId(nodeId).clientId
|
|
891
|
+
if (!ignoreClients.has(clientId)) {
|
|
892
|
+
let subscriber = this.subscribers[channel][nodeId]
|
|
893
|
+
if (subscriber) {
|
|
894
|
+
let ctx = this.createContext(action, meta)
|
|
895
|
+
let client = this.clientIds.get(clientId)
|
|
896
|
+
for (let filter of Object.values(subscriber.filters)) {
|
|
897
|
+
if (typeof filter === 'function') {
|
|
898
|
+
waiting.push(
|
|
899
|
+
Promise.resolve(filter(ctx, action, meta)).then(result => {
|
|
900
|
+
if (result && client && !ignoreClients.has(clientId)) {
|
|
901
|
+
ignoreClients.add(clientId)
|
|
902
|
+
addTarget(targets, client, action, meta)
|
|
903
|
+
}
|
|
904
|
+
})
|
|
905
|
+
)
|
|
906
|
+
} else if (filter && client) {
|
|
907
|
+
ignoreClients.add(clientId)
|
|
908
|
+
addTarget(targets, client, action, meta)
|
|
863
909
|
}
|
|
864
910
|
}
|
|
865
911
|
}
|
|
@@ -867,6 +913,53 @@ export class BaseServer {
|
|
|
867
913
|
}
|
|
868
914
|
}
|
|
869
915
|
}
|
|
916
|
+
return waiting.length > 0 ? Promise.all(waiting) : undefined
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
runProcessor(action, meta, processor, start) {
|
|
920
|
+
if (meta.status === 'waiting') {
|
|
921
|
+
if (!processor) {
|
|
922
|
+
this.internalUnknownType(action, meta)
|
|
923
|
+
return
|
|
924
|
+
}
|
|
925
|
+
if (processor.process) {
|
|
926
|
+
void this.processAction(processor, action, meta, start)
|
|
927
|
+
} else {
|
|
928
|
+
this.emitter.emit('processed', action, meta, 0)
|
|
929
|
+
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
930
|
+
this.markAsProcessed(meta)
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
this.emitter.emit('processed', action, meta, 0)
|
|
934
|
+
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
async sendAction(action, meta) {
|
|
939
|
+
let targets = new Map()
|
|
940
|
+
let waiting = this.resolveTargets(action, meta, targets)
|
|
941
|
+
if (waiting) await waiting
|
|
942
|
+
sendTargets(targets)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
sendBatch(entries) {
|
|
946
|
+
let targets = new Map()
|
|
947
|
+
let waiting = []
|
|
948
|
+
for (let [action, meta] of entries) {
|
|
949
|
+
let promise = this.resolveTargets(action, meta, targets)
|
|
950
|
+
if (promise) waiting.push(promise)
|
|
951
|
+
}
|
|
952
|
+
if (waiting.length === 0) {
|
|
953
|
+
sendTargets(targets)
|
|
954
|
+
return undefined
|
|
955
|
+
}
|
|
956
|
+
return Promise.all(waiting).then(() => {
|
|
957
|
+
sendTargets(targets)
|
|
958
|
+
})
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
sendOnConnect(loader) {
|
|
962
|
+
this.connectLoader = loader
|
|
870
963
|
}
|
|
871
964
|
|
|
872
965
|
setTimeout(callback, ms) {
|
|
@@ -879,6 +972,7 @@ export class BaseServer {
|
|
|
879
972
|
}
|
|
880
973
|
|
|
881
974
|
subscribe(nodeId, channel) {
|
|
975
|
+
if (channel === '__proto__' || nodeId === '__proto__') return
|
|
882
976
|
if (!this.subscribers[channel] || !this.subscribers[channel][nodeId]) {
|
|
883
977
|
if (!this.subscribers[channel]) {
|
|
884
978
|
this.subscribers[channel] = {}
|
|
@@ -889,7 +983,7 @@ export class BaseServer {
|
|
|
889
983
|
}
|
|
890
984
|
|
|
891
985
|
async subscribeAction(action, meta, start) {
|
|
892
|
-
if (typeof action.channel !== 'string') {
|
|
986
|
+
if (typeof action.channel !== 'string' || action.channel === '__proto__') {
|
|
893
987
|
this.wrongChannel(action, meta)
|
|
894
988
|
return
|
|
895
989
|
}
|
|
@@ -902,7 +996,7 @@ export class BaseServer {
|
|
|
902
996
|
let match
|
|
903
997
|
for (let channel of channels) {
|
|
904
998
|
if (channel.pattern) {
|
|
905
|
-
match = channel.pattern
|
|
999
|
+
match = channel.pattern(action.channel)
|
|
906
1000
|
} else {
|
|
907
1001
|
match = action.channel.match(channel.regexp)
|
|
908
1002
|
}
|
|
@@ -910,6 +1004,7 @@ export class BaseServer {
|
|
|
910
1004
|
let subscribed = false
|
|
911
1005
|
if (match) {
|
|
912
1006
|
let ctx = this.createContext(action, meta)
|
|
1007
|
+
if (ctx.nodeId === '__proto__') return
|
|
913
1008
|
ctx.params = match
|
|
914
1009
|
try {
|
|
915
1010
|
let access = await channel.access(ctx, action, meta)
|
|
@@ -959,16 +1054,9 @@ export class BaseServer {
|
|
|
959
1054
|
subscribed = true
|
|
960
1055
|
|
|
961
1056
|
if (channel.load) {
|
|
1057
|
+
// Actions from a single `load()` are sent in a single message
|
|
962
1058
|
let sendBack = await channel.load(ctx, action, meta)
|
|
963
|
-
if (
|
|
964
|
-
await Promise.all(
|
|
965
|
-
sendBack.map(i => {
|
|
966
|
-
return Array.isArray(i) ? ctx.sendBack(...i) : ctx.sendBack(i)
|
|
967
|
-
})
|
|
968
|
-
)
|
|
969
|
-
} else if (sendBack) {
|
|
970
|
-
await ctx.sendBack(sendBack)
|
|
971
|
-
}
|
|
1059
|
+
if (sendBack) await ctx.sendBack(sendBack)
|
|
972
1060
|
}
|
|
973
1061
|
this.emitter.emit('subscribed', action, meta, Date.now() - start)
|
|
974
1062
|
this.markAsProcessed(meta)
|