@logux/server 0.16.3 → 0.17.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/base-server/index.d.ts +65 -39
- package/base-server/index.js +174 -754
- package/context/index.d.ts +13 -1
- package/context/index.js +4 -2
- package/create-reporter/index.js +6 -0
- package/filtered-node/index.js +38 -1
- package/index.d.ts +5 -0
- package/index.js +1 -0
- package/package.json +4 -5
- package/postgres-store/index.d.ts +3 -1
- package/postgres-store/index.js +74 -30
- package/processing/index.js +560 -0
- package/processing-store/index.d.ts +51 -0
- package/processing-store/index.js +44 -0
- package/publishing/index.js +157 -0
- package/queue-manager/index.js +39 -0
- package/server-client/index.d.ts +0 -9
- package/server-client/index.js +25 -134
- package/url-pattern/index.js +9 -3
package/base-server/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { LoguxNotFoundError } from '@logux/actions'
|
|
2
1
|
import { Log, MemoryStore, parseId, ServerConnection } from '@logux/core'
|
|
3
2
|
import { createNanoEvents } from 'nanoevents'
|
|
4
3
|
import { nanoid } from 'nanoid'
|
|
@@ -8,42 +7,34 @@ import { fileURLToPath } from 'node:url'
|
|
|
8
7
|
import { WebSocketServer } from 'ws'
|
|
9
8
|
|
|
10
9
|
import { addHttpPages } from '../add-http-pages/index.js'
|
|
11
|
-
import { Context } from '../context/index.js'
|
|
12
10
|
import { createHttpServer } from '../create-http-server/index.js'
|
|
11
|
+
import {
|
|
12
|
+
ActionRunner,
|
|
13
|
+
getRegexType,
|
|
14
|
+
normalizeChannel,
|
|
15
|
+
normalizeType,
|
|
16
|
+
wasNot403
|
|
17
|
+
} from '../processing/index.js'
|
|
18
|
+
import { Publisher } from '../publishing/index.js'
|
|
13
19
|
import { ServerClient } from '../server-client/index.js'
|
|
14
20
|
import { createPattern } from '../url-pattern/index.js'
|
|
15
21
|
|
|
16
|
-
const SKIP_PROCESS = Symbol('skipProcess')
|
|
17
22
|
const RESEND_META = ['channels', 'users', 'clients', 'nodes']
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (settled > 0) {
|
|
35
|
-
for (let client of app.connected.values()) {
|
|
36
|
-
if (!targets.has(client) && client.node.lastAddedCache < settled) {
|
|
37
|
-
client.node.lastAddedCache = settled
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
targets.clear()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function maxAdded(settled, meta) {
|
|
45
|
-
if (typeof meta.added === 'undefined' || meta.added < settled) return settled
|
|
46
|
-
return meta.added
|
|
24
|
+
// The reports and the debug messages of the failures, which have a reason
|
|
25
|
+
const FAILURES = {
|
|
26
|
+
denied: (action, meta) => [
|
|
27
|
+
{ actionId: meta.id },
|
|
28
|
+
`Action "${meta.id}" was denied`
|
|
29
|
+
],
|
|
30
|
+
unknownType: (action, meta) => [
|
|
31
|
+
{ actionId: meta.id, type: action.type },
|
|
32
|
+
`Action with unknown type ${action.type}`
|
|
33
|
+
],
|
|
34
|
+
wrongChannel: (action, meta) => [
|
|
35
|
+
{ actionId: meta.id, channel: action.channel },
|
|
36
|
+
`Wrong channel name ${action.channel}`
|
|
37
|
+
]
|
|
47
38
|
}
|
|
48
39
|
|
|
49
40
|
function optionError(msg) {
|
|
@@ -53,68 +44,7 @@ function optionError(msg) {
|
|
|
53
44
|
throw error
|
|
54
45
|
}
|
|
55
46
|
|
|
56
|
-
export
|
|
57
|
-
try {
|
|
58
|
-
await cb()
|
|
59
|
-
return true
|
|
60
|
-
} catch (e) {
|
|
61
|
-
if (e.name === 'ResponseError' && e.statusCode === 403) {
|
|
62
|
-
return false
|
|
63
|
-
}
|
|
64
|
-
throw e
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function normalizeTypeCallbacks(name, callbacks) {
|
|
69
|
-
if (callbacks && callbacks.accessAndProcess) {
|
|
70
|
-
callbacks.access = (ctx, ...args) => {
|
|
71
|
-
return wasNot403(async () => {
|
|
72
|
-
await callbacks.accessAndProcess(ctx, ...args)
|
|
73
|
-
ctx[SKIP_PROCESS] = true
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
callbacks.process = async (ctx, ...args) => {
|
|
77
|
-
if (!ctx[SKIP_PROCESS]) await callbacks.accessAndProcess(ctx, ...args)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (!callbacks || !callbacks.access) {
|
|
81
|
-
throw new Error(`${name} must have access callback`)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function normalizeChannelCallbacks(pattern, callbacks) {
|
|
86
|
-
if (callbacks && callbacks.accessAndLoad) {
|
|
87
|
-
callbacks.access = (ctx, ...args) => {
|
|
88
|
-
return wasNot403(async () => {
|
|
89
|
-
try {
|
|
90
|
-
ctx.data.load = await callbacks.accessAndLoad(ctx, ...args)
|
|
91
|
-
} catch (e) {
|
|
92
|
-
if (e.name === 'LoguxNotFoundError') {
|
|
93
|
-
ctx.data.notFound = true
|
|
94
|
-
} else if (e.name === 'ResponseError' && e.statusCode === 404) {
|
|
95
|
-
ctx.data.notFound = true
|
|
96
|
-
} else {
|
|
97
|
-
throw e
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
callbacks.load = ctx => {
|
|
103
|
-
if (ctx.data.notFound) {
|
|
104
|
-
throw new LoguxNotFoundError()
|
|
105
|
-
} else {
|
|
106
|
-
return ctx.data.load
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (!callbacks || !callbacks.access) {
|
|
111
|
-
throw new Error(`Channel ${pattern} must have access callback`)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function subscriberFilterId(action) {
|
|
116
|
-
return JSON.stringify(action.filter || {})
|
|
117
|
-
}
|
|
47
|
+
export { wasNot403 }
|
|
118
48
|
|
|
119
49
|
export class BaseServer {
|
|
120
50
|
constructor(opts = {}) {
|
|
@@ -164,36 +94,20 @@ export class BaseServer {
|
|
|
164
94
|
}
|
|
165
95
|
|
|
166
96
|
this.logger = console
|
|
167
|
-
|
|
168
|
-
this.contexts = new WeakMap()
|
|
169
97
|
this.log = log
|
|
170
98
|
|
|
171
99
|
let cleaned = {}
|
|
172
100
|
|
|
173
101
|
this.on('preadd', (action, meta) => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
meta.
|
|
180
|
-
|
|
181
|
-
if (meta.id.split(' ')[1] === this.nodeId) {
|
|
182
|
-
if (!meta.subprotocol) {
|
|
183
|
-
meta.subprotocol = this.options.subprotocol
|
|
184
|
-
}
|
|
185
|
-
if (
|
|
186
|
-
!isLogux &&
|
|
187
|
-
!this.types[action.type] &&
|
|
188
|
-
!this.getRegexProcessor(action.type)
|
|
189
|
-
) {
|
|
190
|
-
meta.status = 'processed'
|
|
191
|
-
}
|
|
102
|
+
this.prepareMeta(meta)
|
|
103
|
+
// TODO: Remove the warning in next major
|
|
104
|
+
let old = ['channel', 'client', 'node', 'user'].find(key => key in meta)
|
|
105
|
+
if (old) {
|
|
106
|
+
let message = `Replace meta.${old} with meta.${old}s`
|
|
107
|
+
this.logger.warn({ actionId: meta.id }, message)
|
|
108
|
+
this.debugActionError(meta, message)
|
|
192
109
|
}
|
|
193
|
-
this.replaceResendShortcuts(meta)
|
|
194
110
|
})
|
|
195
|
-
// Reports are per action, `clean` for a reason-less action is emitted
|
|
196
|
-
// by the log before `batch` event
|
|
197
111
|
this.on('add', (action, meta) => {
|
|
198
112
|
if (meta.reasons.length === 0) {
|
|
199
113
|
cleaned[meta.id] = true
|
|
@@ -202,55 +116,6 @@ export class BaseServer {
|
|
|
202
116
|
this.emitter.emit('report', 'add', { action, meta })
|
|
203
117
|
}
|
|
204
118
|
})
|
|
205
|
-
this.on('batch', entries => {
|
|
206
|
-
let start = Date.now()
|
|
207
|
-
let ready = []
|
|
208
|
-
|
|
209
|
-
for (let [action, meta] of entries) {
|
|
210
|
-
if (this.destroying && !this.actionToQueue.has(meta.id)) {
|
|
211
|
-
continue
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (action.type === 'logux/subscribe') {
|
|
215
|
-
if (meta.server === this.nodeId) {
|
|
216
|
-
void this.subscribeAction(action, meta, start)
|
|
217
|
-
}
|
|
218
|
-
continue
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (action.type === 'logux/unsubscribe') {
|
|
222
|
-
if (meta.server === this.nodeId) {
|
|
223
|
-
this.unsubscribeAction(action, meta)
|
|
224
|
-
}
|
|
225
|
-
continue
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
let processor = this.getProcessor(action.type)
|
|
229
|
-
if (processor && processor.resend && meta.status === 'waiting') {
|
|
230
|
-
// Resending is asynchronous, so the action can’t wait for others
|
|
231
|
-
void this.resendAction(action, meta, processor, start)
|
|
232
|
-
continue
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (this.isUseless(action, meta)) {
|
|
236
|
-
this.emitter.emit('report', 'useless', { action, meta })
|
|
237
|
-
}
|
|
238
|
-
ready.push([action, meta, processor])
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (ready.length === 0) return
|
|
242
|
-
let runProcessors = () => {
|
|
243
|
-
for (let [action, meta, processor] of ready) {
|
|
244
|
-
this.runProcessor(action, meta, processor, start)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// Actions are processed only after they were sent to the clients
|
|
248
|
-
void Promise.resolve(this.sendBatch(ready)).then(runProcessors, error => {
|
|
249
|
-
// Do not stop action processing on a broken channel filter
|
|
250
|
-
runProcessors()
|
|
251
|
-
throw error
|
|
252
|
-
})
|
|
253
|
-
})
|
|
254
119
|
this.on('clean', (action, meta) => {
|
|
255
120
|
if (cleaned[meta.id]) {
|
|
256
121
|
delete cleaned[meta.id]
|
|
@@ -312,7 +177,6 @@ export class BaseServer {
|
|
|
312
177
|
this.userIds = new Map()
|
|
313
178
|
this.types = {}
|
|
314
179
|
this.regexTypes = new Map()
|
|
315
|
-
this.processing = 0
|
|
316
180
|
|
|
317
181
|
this.lastClient = 0
|
|
318
182
|
|
|
@@ -320,108 +184,54 @@ export class BaseServer {
|
|
|
320
184
|
this.subscribers = {}
|
|
321
185
|
|
|
322
186
|
this.authAttempts = {}
|
|
323
|
-
this.unknownTypes = {}
|
|
324
|
-
this.wrongChannels = {}
|
|
325
187
|
|
|
326
188
|
this.timeouts = {}
|
|
327
189
|
this.lastTimeout = 0
|
|
328
190
|
|
|
329
|
-
this.
|
|
330
|
-
this.
|
|
331
|
-
this.actionToQueue = new Map()
|
|
332
|
-
this.queueTimers = new Map()
|
|
191
|
+
this.publisher = new Publisher(this)
|
|
192
|
+
this.runner = new ActionRunner(this)
|
|
333
193
|
|
|
334
|
-
this.
|
|
194
|
+
this.on('batch', entries => {
|
|
195
|
+
for (let [action, meta] of entries) {
|
|
196
|
+
if (this.isUseless(action, meta)) {
|
|
197
|
+
this.emitter.emit('report', 'useless', { action, meta })
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
this.publisher.track(entries).catch(e => {
|
|
201
|
+
this.emitter.emit('error', e)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
this.httpListeners = []
|
|
335
206
|
this.httpAllListeners = []
|
|
207
|
+
this.httpNotFoundListener = undefined
|
|
336
208
|
addHttpPages(this)
|
|
337
209
|
|
|
338
210
|
this.listenNotes = {}
|
|
339
211
|
|
|
340
|
-
let end = (actionId, queue, queueKey, ...args) => {
|
|
341
|
-
clearTimeout(this.queueTimers.get(actionId))
|
|
342
|
-
this.queueTimers.delete(actionId)
|
|
343
|
-
this.actionToQueue.delete(actionId)
|
|
344
|
-
if (queue.length() === 0) {
|
|
345
|
-
this.queues.delete(queueKey)
|
|
346
|
-
}
|
|
347
|
-
queue.next(...args)
|
|
348
|
-
}
|
|
349
|
-
let undoRemainingTasks = queue => {
|
|
350
|
-
let remainingTasks = queue.getQueue()
|
|
351
|
-
if (remainingTasks) {
|
|
352
|
-
for (let task of remainingTasks) {
|
|
353
|
-
this.undo(task.action, task.meta, 'error')
|
|
354
|
-
this.actionToQueue.delete(task.meta.id)
|
|
355
|
-
// The core waits for it to answer `synced`
|
|
356
|
-
task.onReceiveResolve(false)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
queue.killAndDrain()
|
|
360
|
-
}
|
|
361
|
-
this.releaseQueue = (actionId, error) => {
|
|
362
|
-
let queueKey = this.actionToQueue.get(actionId)
|
|
363
|
-
if (!queueKey) return
|
|
364
|
-
let queue = this.queues.get(queueKey)
|
|
365
|
-
if (error) undoRemainingTasks(queue)
|
|
366
|
-
end(actionId, queue, queueKey, error ?? null)
|
|
367
|
-
}
|
|
368
|
-
this.on('error', (e, action, meta) => {
|
|
369
|
-
if (meta) this.releaseQueue(meta.id, e)
|
|
370
|
-
})
|
|
371
|
-
this.on('processed', (action, meta) => {
|
|
372
|
-
if (action.type === 'logux/undo') {
|
|
373
|
-
let queueKey = this.actionToQueue.get(action.id)
|
|
374
|
-
if (queueKey) {
|
|
375
|
-
let queue = this.queues.get(queueKey)
|
|
376
|
-
undoRemainingTasks(queue)
|
|
377
|
-
end(action.id, queue, queueKey, null, meta)
|
|
378
|
-
}
|
|
379
|
-
} else if (action.type === 'logux/processed') {
|
|
380
|
-
let queueKey = this.actionToQueue.get(action.id)
|
|
381
|
-
if (queueKey) {
|
|
382
|
-
let queue = this.queues.get(queueKey)
|
|
383
|
-
end(action.id, queue, queueKey, null, meta)
|
|
384
|
-
}
|
|
385
|
-
} else if (
|
|
386
|
-
action.type !== 'logux/subscribed' &&
|
|
387
|
-
action.type !== 'logux/unsubscribed'
|
|
388
|
-
) {
|
|
389
|
-
let queueKey = this.actionToQueue.get(meta.id)
|
|
390
|
-
if (queueKey) {
|
|
391
|
-
let queue = this.queues.get(queueKey)
|
|
392
|
-
end(meta.id, queue, queueKey, null, meta)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
})
|
|
396
|
-
|
|
397
212
|
this.unbind.push(() => {
|
|
398
213
|
for (let i of this.connected.values()) i.destroy()
|
|
399
214
|
for (let i in this.timeouts) {
|
|
400
215
|
clearTimeout(this.timeouts[i])
|
|
401
216
|
}
|
|
402
217
|
})
|
|
403
|
-
this.unbind.push(() => {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
})
|
|
411
|
-
}
|
|
412
|
-
})
|
|
413
|
-
})
|
|
414
|
-
this.unbind.push(() => {
|
|
415
|
-
return Promise.allSettled(
|
|
416
|
-
[...this.queues.values()].map(queue => {
|
|
417
|
-
return new Promise(resolve => {
|
|
418
|
-
queue.drain = resolve
|
|
419
|
-
})
|
|
218
|
+
this.unbind.push(async () => {
|
|
219
|
+
await this.runner.waitForTasks()
|
|
220
|
+
await this.publisher.settled()
|
|
221
|
+
// The callbacks, which exceeded `queueTimeout`, can still be running
|
|
222
|
+
if (this.runner.detached.size > 0) {
|
|
223
|
+
this.emitter.emit('report', 'destroyDetached', {
|
|
224
|
+
actions: this.runner.detached.size
|
|
420
225
|
})
|
|
421
|
-
|
|
226
|
+
}
|
|
422
227
|
})
|
|
423
228
|
}
|
|
424
229
|
|
|
230
|
+
handleClient(ws, req) {
|
|
231
|
+
ws.upgradeReq = req
|
|
232
|
+
this.addClient(new ServerConnection(ws))
|
|
233
|
+
}
|
|
234
|
+
|
|
425
235
|
addClient(connection) {
|
|
426
236
|
this.lastClient += 1
|
|
427
237
|
let key = this.lastClient.toString()
|
|
@@ -434,50 +244,20 @@ export class BaseServer {
|
|
|
434
244
|
this.authenticator = authenticator
|
|
435
245
|
}
|
|
436
246
|
|
|
437
|
-
buildUndo(action, meta, reason, extra) {
|
|
438
|
-
let undoMeta = { status: 'processed' }
|
|
439
|
-
|
|
440
|
-
if (meta.users) undoMeta.users = meta.users.slice(0)
|
|
441
|
-
if (meta.nodes) undoMeta.nodes = meta.nodes.slice(0)
|
|
442
|
-
if (meta.clients) undoMeta.clients = meta.clients.slice(0)
|
|
443
|
-
if (meta.reasons) undoMeta.reasons = meta.reasons.slice(0)
|
|
444
|
-
if (meta.channels) undoMeta.channels = meta.channels.slice(0)
|
|
445
|
-
if (meta.excludeClients) {
|
|
446
|
-
undoMeta.excludeClients = meta.excludeClients.slice(0)
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
let undoAction = {
|
|
450
|
-
...extra,
|
|
451
|
-
action,
|
|
452
|
-
id: meta.id,
|
|
453
|
-
reason,
|
|
454
|
-
type: 'logux/undo'
|
|
455
|
-
}
|
|
456
|
-
return [undoAction, undoMeta]
|
|
457
|
-
}
|
|
458
|
-
|
|
459
247
|
channel(pattern, callbacks, options = {}) {
|
|
460
|
-
|
|
461
|
-
|
|
248
|
+
let channel = normalizeChannel(
|
|
249
|
+
`Channel ${pattern}`,
|
|
250
|
+
callbacks,
|
|
251
|
+
options.queue || 'main'
|
|
252
|
+
)
|
|
462
253
|
if (typeof pattern === 'string') {
|
|
463
254
|
channel.pattern = createPattern(pattern)
|
|
464
255
|
} else {
|
|
465
256
|
channel.regexp = pattern
|
|
466
257
|
}
|
|
467
|
-
|
|
468
|
-
channel.queueName = options.queue || 'main'
|
|
469
258
|
this.channels.push(channel)
|
|
470
259
|
}
|
|
471
260
|
|
|
472
|
-
createContext(action, meta) {
|
|
473
|
-
let context = this.contexts.get(action)
|
|
474
|
-
if (!context) {
|
|
475
|
-
context = new Context(this, meta)
|
|
476
|
-
this.contexts.set(action, context)
|
|
477
|
-
}
|
|
478
|
-
return context
|
|
479
|
-
}
|
|
480
|
-
|
|
481
261
|
debugActionError(meta, msg) {
|
|
482
262
|
if (this.env === 'development') {
|
|
483
263
|
let clientId = parseId(meta.id).clientId
|
|
@@ -497,12 +277,6 @@ export class BaseServer {
|
|
|
497
277
|
}
|
|
498
278
|
}
|
|
499
279
|
|
|
500
|
-
denyAction(action, meta) {
|
|
501
|
-
this.emitter.emit('report', 'denied', { actionId: meta.id })
|
|
502
|
-
this.undo(action, meta, 'denied')
|
|
503
|
-
this.debugActionError(meta, `Action "${meta.id}" was denied`)
|
|
504
|
-
}
|
|
505
|
-
|
|
506
280
|
destroy() {
|
|
507
281
|
this.destroying = true
|
|
508
282
|
this.emitter.emit('report', 'destroy')
|
|
@@ -515,37 +289,6 @@ export class BaseServer {
|
|
|
515
289
|
return client.drain()
|
|
516
290
|
}
|
|
517
291
|
|
|
518
|
-
finally(processor, ctx, action, meta) {
|
|
519
|
-
this.contexts.delete(action)
|
|
520
|
-
if (processor && processor.finally) {
|
|
521
|
-
try {
|
|
522
|
-
processor.finally(ctx, action, meta)
|
|
523
|
-
} catch (err) {
|
|
524
|
-
this.emitter.emit('error', err, action, meta)
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
getProcessor(type) {
|
|
530
|
-
return (
|
|
531
|
-
this.types[type] || this.getRegexProcessor(type) || this.otherProcessor
|
|
532
|
-
)
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
getRegexProcessor(type) {
|
|
536
|
-
for (let regexp of this.regexTypes.keys()) {
|
|
537
|
-
if (type.match(regexp) !== null) {
|
|
538
|
-
return this.regexTypes.get(regexp)
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return undefined
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
handleClient(ws, req) {
|
|
545
|
-
ws.upgradeReq = req
|
|
546
|
-
this.addClient(new ServerConnection(ws))
|
|
547
|
-
}
|
|
548
|
-
|
|
549
292
|
http(method, url, listener) {
|
|
550
293
|
if (this.options.disableHttpServer) {
|
|
551
294
|
throw new Error(
|
|
@@ -555,31 +298,26 @@ export class BaseServer {
|
|
|
555
298
|
if (!url) {
|
|
556
299
|
this.httpAllListeners.push(method)
|
|
557
300
|
} else {
|
|
558
|
-
|
|
301
|
+
let route = { listener, match: createPattern(url), method, url }
|
|
302
|
+
let same = this.httpListeners.findIndex(i => {
|
|
303
|
+
return i.method === method && i.url === url
|
|
304
|
+
})
|
|
305
|
+
if (same === -1) {
|
|
306
|
+
this.httpListeners.push(route)
|
|
307
|
+
} else {
|
|
308
|
+
this.httpListeners[same] = route
|
|
309
|
+
}
|
|
559
310
|
}
|
|
560
311
|
}
|
|
561
312
|
|
|
562
|
-
|
|
563
|
-
this.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
})
|
|
569
|
-
if (parseId(meta.id).userId !== 'server') {
|
|
570
|
-
this.undo(action, meta, 'unknownType')
|
|
313
|
+
httpNotFound(listener) {
|
|
314
|
+
if (this.options.disableHttpServer) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
'`server.httpNotFound()` can not be called when ' +
|
|
317
|
+
'`disableHttpServer` enabled'
|
|
318
|
+
)
|
|
571
319
|
}
|
|
572
|
-
this.
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
internalWrongChannel(action, meta) {
|
|
576
|
-
this.contexts.delete(action)
|
|
577
|
-
this.emitter.emit('report', 'wrongChannel', {
|
|
578
|
-
actionId: meta.id,
|
|
579
|
-
channel: action.channel
|
|
580
|
-
})
|
|
581
|
-
this.undo(action, meta, 'wrongChannel')
|
|
582
|
-
this.debugActionError(meta, `Wrong channel name ${action.channel}`)
|
|
320
|
+
this.httpNotFoundListener = listener
|
|
583
321
|
}
|
|
584
322
|
|
|
585
323
|
isBruteforce(ip) {
|
|
@@ -588,14 +326,10 @@ export class BaseServer {
|
|
|
588
326
|
}
|
|
589
327
|
|
|
590
328
|
isUseless(action, meta) {
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
) {
|
|
596
|
-
return false
|
|
597
|
-
}
|
|
598
|
-
for (let i of ['channels', 'nodes', 'clients', 'users']) {
|
|
329
|
+
if (action.type.startsWith('logux/')) return false
|
|
330
|
+
if (this.otherProcessor) return false
|
|
331
|
+
if (this.types[action.type] || getRegexType(this, action.type)) return false
|
|
332
|
+
for (let i of RESEND_META) {
|
|
599
333
|
if (Array.isArray(meta[i]) && meta[i].length > 0) return false
|
|
600
334
|
}
|
|
601
335
|
return true
|
|
@@ -664,37 +398,6 @@ export class BaseServer {
|
|
|
664
398
|
})
|
|
665
399
|
}
|
|
666
400
|
|
|
667
|
-
/**
|
|
668
|
-
* The client re-sent the action, which the log already has: the server
|
|
669
|
-
* stored it, but the client did not get the answer. Answer again
|
|
670
|
-
* and release the queue of the client.
|
|
671
|
-
*/
|
|
672
|
-
async resolveDuplicate(action, meta) {
|
|
673
|
-
let [stored, storedMeta] = await this.log.byId(meta.id)
|
|
674
|
-
this.emitter.emit('report', 'duplicate', {
|
|
675
|
-
actionId: meta.id,
|
|
676
|
-
status: storedMeta ? storedMeta.status : undefined
|
|
677
|
-
})
|
|
678
|
-
if (stored && storedMeta.status === 'processed') {
|
|
679
|
-
// The `processed` event of the answer releases the queue
|
|
680
|
-
this.markAsProcessed(storedMeta)
|
|
681
|
-
} else {
|
|
682
|
-
// Still processing or already answered by `logux/undo`
|
|
683
|
-
this.releaseQueue(meta.id)
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
markAsProcessed(meta) {
|
|
688
|
-
this.log.changeMeta(meta.id, { status: 'processed' })
|
|
689
|
-
let data = parseId(meta.id)
|
|
690
|
-
if (data.userId !== 'server') {
|
|
691
|
-
this.log.add(
|
|
692
|
-
{ id: meta.id, type: 'logux/processed' },
|
|
693
|
-
{ clients: [data.clientId], status: 'processed' }
|
|
694
|
-
)
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
401
|
on(event, listener) {
|
|
699
402
|
if (
|
|
700
403
|
event === 'preadd' ||
|
|
@@ -709,11 +412,10 @@ export class BaseServer {
|
|
|
709
412
|
}
|
|
710
413
|
|
|
711
414
|
otherChannel(callbacks) {
|
|
712
|
-
normalizeChannelCallbacks('Unknown channel', callbacks)
|
|
713
415
|
if (this.otherSubscriber) {
|
|
714
416
|
throw new Error('Callbacks for unknown channel are already defined')
|
|
715
417
|
}
|
|
716
|
-
let channel =
|
|
418
|
+
let channel = normalizeChannel('Unknown channel', callbacks, 'main')
|
|
717
419
|
channel.pattern = name => [name]
|
|
718
420
|
this.otherSubscriber = channel
|
|
719
421
|
}
|
|
@@ -722,75 +424,30 @@ export class BaseServer {
|
|
|
722
424
|
if (this.otherProcessor) {
|
|
723
425
|
throw new Error('Callbacks for unknown types are already defined')
|
|
724
426
|
}
|
|
725
|
-
|
|
726
|
-
this.otherProcessor = callbacks
|
|
427
|
+
this.otherProcessor = normalizeType('Unknown type', callbacks, 'main')
|
|
727
428
|
}
|
|
728
429
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
if (
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
delete this.subscribers[action.channel]
|
|
745
|
-
}
|
|
430
|
+
// The callbacks need the ID, time, server and subprotocol
|
|
431
|
+
prepareMeta(meta, client) {
|
|
432
|
+
if (typeof meta.id === 'undefined') {
|
|
433
|
+
meta.id = this.log.generateId()
|
|
434
|
+
if (typeof meta.time === 'undefined') meta.time = this.log.lastTime
|
|
435
|
+
} else if (typeof meta.time === 'undefined') {
|
|
436
|
+
meta.time = this.log.now()
|
|
437
|
+
}
|
|
438
|
+
if (typeof meta.reasons === 'undefined') meta.reasons = []
|
|
439
|
+
if (!meta.server) meta.server = this.nodeId
|
|
440
|
+
if (!meta.subprotocol) {
|
|
441
|
+
if (meta.id.split(' ')[1] === this.nodeId) {
|
|
442
|
+
meta.subprotocol = this.options.subprotocol
|
|
443
|
+
} else if (client) {
|
|
444
|
+
meta.subprotocol = client.node.remoteSubprotocol
|
|
746
445
|
}
|
|
747
446
|
}
|
|
748
|
-
this.emitter.emit('unsubscribed', action, meta, clientNodeId)
|
|
749
|
-
this.emitter.emit('report', 'unsubscribed', {
|
|
750
|
-
actionId: meta.id,
|
|
751
|
-
channel: action.channel
|
|
752
|
-
})
|
|
753
447
|
}
|
|
754
448
|
|
|
755
449
|
process(action, meta = {}) {
|
|
756
|
-
return
|
|
757
|
-
let unbindError = this.on('error', (e, errorAction) => {
|
|
758
|
-
if (errorAction === action) {
|
|
759
|
-
unbindError()
|
|
760
|
-
unbindProcessed()
|
|
761
|
-
reject(e)
|
|
762
|
-
}
|
|
763
|
-
})
|
|
764
|
-
let unbindProcessed = this.on('processed', (processed, processedMeta) => {
|
|
765
|
-
if (processed === action) {
|
|
766
|
-
unbindError()
|
|
767
|
-
unbindProcessed()
|
|
768
|
-
resolve(processedMeta)
|
|
769
|
-
}
|
|
770
|
-
})
|
|
771
|
-
this.log.add(action, meta)
|
|
772
|
-
})
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
async processAction(processor, action, meta, start) {
|
|
776
|
-
let ctx = this.createContext(action, meta)
|
|
777
|
-
|
|
778
|
-
let latency
|
|
779
|
-
this.processing += 1
|
|
780
|
-
try {
|
|
781
|
-
await processor.process(ctx, action, meta)
|
|
782
|
-
latency = Date.now() - start
|
|
783
|
-
this.markAsProcessed(meta)
|
|
784
|
-
} catch (e) {
|
|
785
|
-
this.log.changeMeta(meta.id, { status: 'error' })
|
|
786
|
-
this.undo(action, meta, 'error')
|
|
787
|
-
this.emitter.emit('error', e, action, meta)
|
|
788
|
-
} finally {
|
|
789
|
-
this.finally(processor, ctx, action, meta)
|
|
790
|
-
}
|
|
791
|
-
if (typeof latency === 'undefined') latency = Date.now() - start
|
|
792
|
-
this.processing -= 1
|
|
793
|
-
this.emitter.emit('processed', action, meta, latency)
|
|
450
|
+
return this.runner.submit({ action, meta, trusted: true })
|
|
794
451
|
}
|
|
795
452
|
|
|
796
453
|
async processHttp(req, res) {
|
|
@@ -799,24 +456,46 @@ export class BaseServer {
|
|
|
799
456
|
urlString = decodeURIComponent(urlString)
|
|
800
457
|
}
|
|
801
458
|
let reqUrl = new URL(urlString, 'http://localhost')
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
if (result === true) {
|
|
809
|
-
processed = true
|
|
810
|
-
break
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
if (!processed) {
|
|
814
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
815
|
-
res.end('Not found\n')
|
|
459
|
+
|
|
460
|
+
// Listeners are checked in the order they were added, like in Express
|
|
461
|
+
for (let route of this.httpListeners) {
|
|
462
|
+
if (route.method === req.method && route.match(reqUrl.pathname)) {
|
|
463
|
+
await route.listener(req, res)
|
|
464
|
+
return
|
|
816
465
|
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
for (let listener of this.httpAllListeners) {
|
|
469
|
+
let result = await listener(req, res)
|
|
470
|
+
if (result === true) return
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (this.httpNotFoundListener) {
|
|
474
|
+
await this.httpNotFoundListener(req, res)
|
|
817
475
|
} else {
|
|
818
|
-
|
|
476
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
477
|
+
res.end('Not found\n')
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// `clientId` differs from the action’s author only for the actions,
|
|
482
|
+
// which were sent with a foreign ID
|
|
483
|
+
publishUndo(action, meta, reason, extra, clientId) {
|
|
484
|
+
let undoMeta = {}
|
|
485
|
+
if (meta.users) undoMeta.users = meta.users.slice(0)
|
|
486
|
+
if (meta.nodes) undoMeta.nodes = meta.nodes.slice(0)
|
|
487
|
+
if (meta.reasons) undoMeta.reasons = meta.reasons.slice(0)
|
|
488
|
+
if (meta.channels) undoMeta.channels = meta.channels.slice(0)
|
|
489
|
+
if (meta.excludeClients) {
|
|
490
|
+
undoMeta.excludeClients = meta.excludeClients.slice(0)
|
|
819
491
|
}
|
|
492
|
+
undoMeta.clients = (meta.clients || []).concat([
|
|
493
|
+
clientId || parseId(meta.id).clientId
|
|
494
|
+
])
|
|
495
|
+
return this.log.add(
|
|
496
|
+
{ ...extra, action, id: meta.id, reason, type: 'logux/undo' },
|
|
497
|
+
undoMeta
|
|
498
|
+
)
|
|
820
499
|
}
|
|
821
500
|
|
|
822
501
|
rememberBadAuth(ip) {
|
|
@@ -830,172 +509,8 @@ export class BaseServer {
|
|
|
830
509
|
}, 3000)
|
|
831
510
|
}
|
|
832
511
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
meta.channels = [meta.channel]
|
|
836
|
-
delete meta.channel
|
|
837
|
-
}
|
|
838
|
-
if (meta.user) {
|
|
839
|
-
meta.users = [meta.user]
|
|
840
|
-
delete meta.user
|
|
841
|
-
}
|
|
842
|
-
if (meta.client) {
|
|
843
|
-
meta.clients = [meta.client]
|
|
844
|
-
delete meta.client
|
|
845
|
-
}
|
|
846
|
-
if (meta.node) {
|
|
847
|
-
meta.nodes = [meta.node]
|
|
848
|
-
delete meta.node
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
async resendAction(action, meta, processor, start) {
|
|
853
|
-
let ctx = this.createContext(action, meta)
|
|
854
|
-
let resend
|
|
855
|
-
try {
|
|
856
|
-
resend = await processor.resend(ctx, action, meta)
|
|
857
|
-
} catch (e) {
|
|
858
|
-
this.undo(action, meta, 'error')
|
|
859
|
-
this.emitter.emit('error', e, action, meta)
|
|
860
|
-
this.finally(processor, ctx, action, meta)
|
|
861
|
-
return
|
|
862
|
-
}
|
|
863
|
-
if (resend) {
|
|
864
|
-
if (typeof resend === 'string') {
|
|
865
|
-
resend = { channels: [resend] }
|
|
866
|
-
} else if (Array.isArray(resend)) {
|
|
867
|
-
resend = { channels: resend }
|
|
868
|
-
} else {
|
|
869
|
-
this.replaceResendShortcuts(resend)
|
|
870
|
-
}
|
|
871
|
-
let diff = {}
|
|
872
|
-
for (let i of RESEND_META) {
|
|
873
|
-
if (resend[i]) diff[i] = resend[i]
|
|
874
|
-
}
|
|
875
|
-
await this.log.changeMeta(meta.id, diff)
|
|
876
|
-
meta = { ...meta, ...diff }
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
await this.sendAction(action, meta)
|
|
880
|
-
this.runProcessor(action, meta, processor, start)
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
resolveTargets(action, meta, targets) {
|
|
884
|
-
let from = parseId(meta.id).clientId
|
|
885
|
-
let ignoreClients = new Set(meta.excludeClients || [])
|
|
886
|
-
ignoreClients.add(from)
|
|
887
|
-
|
|
888
|
-
if (meta.nodes) {
|
|
889
|
-
for (let id of meta.nodes) {
|
|
890
|
-
let client = this.nodeIds.get(id)
|
|
891
|
-
if (client) {
|
|
892
|
-
ignoreClients.add(client.clientId)
|
|
893
|
-
addTarget(targets, client, action, meta)
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (meta.clients) {
|
|
899
|
-
for (let id of meta.clients) {
|
|
900
|
-
if (this.clientIds.has(id)) {
|
|
901
|
-
let client = this.clientIds.get(id)
|
|
902
|
-
ignoreClients.add(client.clientId)
|
|
903
|
-
addTarget(targets, client, action, meta)
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
if (meta.users) {
|
|
909
|
-
for (let userId of meta.users) {
|
|
910
|
-
let users = this.userIds.get(userId)
|
|
911
|
-
if (users) {
|
|
912
|
-
for (let client of users) {
|
|
913
|
-
if (!ignoreClients.has(client.clientId)) {
|
|
914
|
-
ignoreClients.add(client.clientId)
|
|
915
|
-
addTarget(targets, client, action, meta)
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
if (!meta.channels) return undefined
|
|
923
|
-
|
|
924
|
-
let waiting = []
|
|
925
|
-
for (let channel of meta.channels) {
|
|
926
|
-
if (this.subscribers[channel]) {
|
|
927
|
-
for (let nodeId in this.subscribers[channel]) {
|
|
928
|
-
let clientId = parseId(nodeId).clientId
|
|
929
|
-
if (!ignoreClients.has(clientId)) {
|
|
930
|
-
let subscriber = this.subscribers[channel][nodeId]
|
|
931
|
-
if (subscriber) {
|
|
932
|
-
let ctx = this.createContext(action, meta)
|
|
933
|
-
let client = this.clientIds.get(clientId)
|
|
934
|
-
for (let filter of Object.values(subscriber.filters)) {
|
|
935
|
-
if (typeof filter === 'function') {
|
|
936
|
-
waiting.push(
|
|
937
|
-
Promise.resolve(filter(ctx, action, meta)).then(result => {
|
|
938
|
-
if (result && client && !ignoreClients.has(clientId)) {
|
|
939
|
-
ignoreClients.add(clientId)
|
|
940
|
-
addTarget(targets, client, action, meta)
|
|
941
|
-
}
|
|
942
|
-
})
|
|
943
|
-
)
|
|
944
|
-
} else if (filter && client) {
|
|
945
|
-
ignoreClients.add(clientId)
|
|
946
|
-
addTarget(targets, client, action, meta)
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
return waiting.length > 0 ? Promise.all(waiting) : undefined
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
runProcessor(action, meta, processor, start) {
|
|
958
|
-
if (meta.status === 'waiting') {
|
|
959
|
-
if (!processor) {
|
|
960
|
-
this.internalUnknownType(action, meta)
|
|
961
|
-
return
|
|
962
|
-
}
|
|
963
|
-
if (processor.process) {
|
|
964
|
-
void this.processAction(processor, action, meta, start)
|
|
965
|
-
} else {
|
|
966
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
967
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
968
|
-
this.markAsProcessed(meta)
|
|
969
|
-
}
|
|
970
|
-
} else {
|
|
971
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
972
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
async sendAction(action, meta) {
|
|
977
|
-
let targets = new Map()
|
|
978
|
-
let waiting = this.resolveTargets(action, meta, targets)
|
|
979
|
-
if (waiting) await waiting
|
|
980
|
-
sendTargets(this, targets, maxAdded(0, meta))
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
sendBatch(entries) {
|
|
984
|
-
let targets = new Map()
|
|
985
|
-
let waiting = []
|
|
986
|
-
let settled = 0
|
|
987
|
-
for (let [action, meta] of entries) {
|
|
988
|
-
settled = maxAdded(settled, meta)
|
|
989
|
-
let promise = this.resolveTargets(action, meta, targets)
|
|
990
|
-
if (promise) waiting.push(promise)
|
|
991
|
-
}
|
|
992
|
-
if (waiting.length === 0) {
|
|
993
|
-
sendTargets(this, targets, settled)
|
|
994
|
-
return undefined
|
|
995
|
-
}
|
|
996
|
-
return Promise.all(waiting).then(() => {
|
|
997
|
-
sendTargets(this, targets, settled)
|
|
998
|
-
})
|
|
512
|
+
sendWithoutProcess(action, meta) {
|
|
513
|
+
return this.publisher.send([[action, meta]])
|
|
999
514
|
}
|
|
1000
515
|
|
|
1001
516
|
sendOnConnect(loader) {
|
|
@@ -1011,163 +526,68 @@ export class BaseServer {
|
|
|
1011
526
|
}, ms)
|
|
1012
527
|
}
|
|
1013
528
|
|
|
1014
|
-
subscribe(nodeId, channel) {
|
|
529
|
+
async subscribe(nodeId, channel) {
|
|
1015
530
|
if (channel === '__proto__' || nodeId === '__proto__') return
|
|
1016
531
|
if (!this.subscribers[channel] || !this.subscribers[channel][nodeId]) {
|
|
1017
532
|
if (!this.subscribers[channel]) {
|
|
1018
533
|
this.subscribers[channel] = {}
|
|
1019
534
|
}
|
|
1020
535
|
this.subscribers[channel][nodeId] = { filters: { '{}': true } }
|
|
1021
|
-
this.log.add(
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
async subscribeAction(action, meta, start) {
|
|
1026
|
-
if (typeof action.channel !== 'string' || action.channel === '__proto__') {
|
|
1027
|
-
this.wrongChannel(action, meta)
|
|
1028
|
-
return
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
let channels = this.channels
|
|
1032
|
-
if (this.otherSubscriber) {
|
|
1033
|
-
channels = this.channels.concat([this.otherSubscriber])
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
let match
|
|
1037
|
-
for (let channel of channels) {
|
|
1038
|
-
if (channel.pattern) {
|
|
1039
|
-
match = channel.pattern(action.channel)
|
|
1040
|
-
} else {
|
|
1041
|
-
match = action.channel.match(channel.regexp)
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
let subscribed = false
|
|
1045
|
-
if (match) {
|
|
1046
|
-
let ctx = this.createContext(action, meta)
|
|
1047
|
-
if (ctx.nodeId === '__proto__') return
|
|
1048
|
-
ctx.params = match
|
|
1049
|
-
try {
|
|
1050
|
-
let access = await channel.access(ctx, action, meta)
|
|
1051
|
-
if (this.wrongChannels[meta.id]) {
|
|
1052
|
-
delete this.wrongChannels[meta.id]
|
|
1053
|
-
return
|
|
1054
|
-
}
|
|
1055
|
-
if (!access) {
|
|
1056
|
-
this.denyAction(action, meta)
|
|
1057
|
-
return
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
let client = this.clientIds.get(ctx.clientId)
|
|
1061
|
-
if (!client) {
|
|
1062
|
-
this.emitter.emit('subscriptionCancelled')
|
|
1063
|
-
return
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
let filterId = subscriberFilterId(action)
|
|
1067
|
-
let filters = { [filterId]: true }
|
|
1068
|
-
|
|
1069
|
-
if (channel.filter) {
|
|
1070
|
-
let filter = await channel.filter(ctx, action, meta)
|
|
1071
|
-
filters = { [filterId]: filter }
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
this.emitter.emit('report', 'subscribed', {
|
|
1075
|
-
actionId: meta.id,
|
|
1076
|
-
channel: action.channel
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
if (!this.subscribers[action.channel]) {
|
|
1080
|
-
this.subscribers[action.channel] = {}
|
|
1081
|
-
this.emitter.emit('subscribing', action, meta)
|
|
1082
|
-
}
|
|
1083
|
-
let subscriber = this.subscribers[action.channel][ctx.nodeId]
|
|
1084
|
-
if (subscriber) {
|
|
1085
|
-
filters = { ...subscriber.filters, ...filters }
|
|
1086
|
-
}
|
|
1087
|
-
this.subscribers[action.channel][ctx.nodeId] = {
|
|
1088
|
-
filters,
|
|
1089
|
-
unsubscribe: channel.unsubscribe
|
|
1090
|
-
? (unsubscribeAction, unsubscribeMeta) =>
|
|
1091
|
-
channel.unsubscribe(ctx, unsubscribeAction, unsubscribeMeta)
|
|
1092
|
-
: undefined
|
|
1093
|
-
}
|
|
1094
|
-
subscribed = true
|
|
1095
|
-
|
|
1096
|
-
if (channel.load) {
|
|
1097
|
-
// Actions from a single `load()` are sent in a single message
|
|
1098
|
-
let sendBack = await channel.load(ctx, action, meta)
|
|
1099
|
-
if (sendBack) await ctx.sendBack(sendBack)
|
|
1100
|
-
}
|
|
1101
|
-
this.emitter.emit('subscribed', action, meta, Date.now() - start)
|
|
1102
|
-
this.markAsProcessed(meta)
|
|
1103
|
-
} catch (e) {
|
|
1104
|
-
if (e.name === 'LoguxNotFoundError') {
|
|
1105
|
-
this.undo(action, meta, 'notFound')
|
|
1106
|
-
} else {
|
|
1107
|
-
this.emitter.emit('error', e, action, meta)
|
|
1108
|
-
this.undo(action, meta, 'error')
|
|
1109
|
-
}
|
|
1110
|
-
if (subscribed) {
|
|
1111
|
-
this.unsubscribe(action, meta)
|
|
1112
|
-
}
|
|
1113
|
-
} finally {
|
|
1114
|
-
this.finally(channel, ctx, action, meta)
|
|
536
|
+
await this.log.add(
|
|
537
|
+
{ channel, type: 'logux/subscribed' },
|
|
538
|
+
{
|
|
539
|
+
nodes: [nodeId]
|
|
1115
540
|
}
|
|
1116
|
-
|
|
1117
|
-
}
|
|
541
|
+
)
|
|
1118
542
|
}
|
|
1119
|
-
|
|
1120
|
-
if (!match) this.wrongChannel(action, meta)
|
|
1121
543
|
}
|
|
1122
544
|
|
|
1123
545
|
type(name, callbacks, options = {}) {
|
|
1124
|
-
let queue = options.queue || 'main'
|
|
1125
|
-
this.typeToQueue.set(name, queue)
|
|
1126
|
-
|
|
1127
546
|
if (typeof name === 'function') name = name.type
|
|
1128
|
-
|
|
547
|
+
let handler = normalizeType(
|
|
548
|
+
`Action type ${name}`,
|
|
549
|
+
callbacks,
|
|
550
|
+
options.queue || 'main'
|
|
551
|
+
)
|
|
1129
552
|
|
|
1130
553
|
if (name instanceof RegExp) {
|
|
1131
|
-
this.regexTypes.set(name,
|
|
554
|
+
this.regexTypes.set(name, handler)
|
|
1132
555
|
} else {
|
|
1133
556
|
if (this.types[name]) {
|
|
1134
557
|
throw new Error(`Action type ${name} was already defined`)
|
|
1135
558
|
}
|
|
1136
|
-
this.types[name] =
|
|
559
|
+
this.types[name] = handler
|
|
1137
560
|
}
|
|
1138
561
|
}
|
|
1139
562
|
|
|
1140
|
-
|
|
1141
|
-
let
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
unknownType(action, meta) {
|
|
1148
|
-
this.internalUnknownType(action, meta)
|
|
1149
|
-
this.unknownTypes[meta.id] = true
|
|
563
|
+
reportFailure(reason, action, meta) {
|
|
564
|
+
let failure = FAILURES[reason]
|
|
565
|
+
if (!failure) return
|
|
566
|
+
let [details, message] = failure(action, meta)
|
|
567
|
+
this.emitter.emit('report', reason, details)
|
|
568
|
+
this.debugActionError(meta, message)
|
|
1150
569
|
}
|
|
1151
570
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
this.
|
|
571
|
+
undo(action, meta, reason = 'error', extra = {}) {
|
|
572
|
+
this.reportFailure(reason, action, meta)
|
|
573
|
+
let task = this.runner.byId.get(meta.id)
|
|
574
|
+
if (task && !task.finished) {
|
|
575
|
+
// The task answers the client with this outcome only once
|
|
576
|
+
task.settle({ extra, reason, type: 'undo' })
|
|
577
|
+
return Promise.resolve()
|
|
578
|
+
}
|
|
579
|
+
return this.publishUndo(action, meta, reason, extra)
|
|
1155
580
|
}
|
|
1156
581
|
|
|
1157
|
-
|
|
1158
|
-
if (
|
|
1159
|
-
this.
|
|
1160
|
-
return
|
|
582
|
+
unknownType(action, meta) {
|
|
583
|
+
if (parseId(meta.id).userId === 'server') {
|
|
584
|
+
this.reportFailure('unknownType', action, meta)
|
|
585
|
+
return Promise.resolve()
|
|
1161
586
|
}
|
|
1162
|
-
|
|
1163
|
-
this.unsubscribe(action, meta)
|
|
1164
|
-
|
|
1165
|
-
this.markAsProcessed(meta)
|
|
1166
|
-
this.contexts.delete(action)
|
|
587
|
+
return this.undo(action, meta, 'unknownType')
|
|
1167
588
|
}
|
|
1168
589
|
|
|
1169
590
|
wrongChannel(action, meta) {
|
|
1170
|
-
this.
|
|
1171
|
-
this.wrongChannels[meta.id] = true
|
|
591
|
+
return this.undo(action, meta, 'wrongChannel')
|
|
1172
592
|
}
|
|
1173
593
|
}
|