@logux/server 0.16.2 → 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 +72 -38
- package/base-server/index.js +177 -727
- package/context/index.d.ts +13 -1
- package/context/index.js +4 -2
- package/create-reporter/index.js +8 -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 -114
- package/url-pattern/index.js +9 -3
- package/vite.config.ts +0 -25
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,73 +44,15 @@ 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 = {}) {
|
|
121
51
|
this.options = opts
|
|
122
52
|
this.env = this.options.env || process.env.NODE_ENV || 'development'
|
|
53
|
+
if (typeof this.options.queueTimeout === 'undefined') {
|
|
54
|
+
this.options.queueTimeout = 5 * 60 * 1000
|
|
55
|
+
}
|
|
123
56
|
|
|
124
57
|
if (typeof this.options.subprotocol === 'undefined') {
|
|
125
58
|
throw optionError('Missed `subprotocol` option in server constructor')
|
|
@@ -161,36 +94,20 @@ export class BaseServer {
|
|
|
161
94
|
}
|
|
162
95
|
|
|
163
96
|
this.logger = console
|
|
164
|
-
|
|
165
|
-
this.contexts = new WeakMap()
|
|
166
97
|
this.log = log
|
|
167
98
|
|
|
168
99
|
let cleaned = {}
|
|
169
100
|
|
|
170
101
|
this.on('preadd', (action, meta) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
meta.
|
|
177
|
-
|
|
178
|
-
if (meta.id.split(' ')[1] === this.nodeId) {
|
|
179
|
-
if (!meta.subprotocol) {
|
|
180
|
-
meta.subprotocol = this.options.subprotocol
|
|
181
|
-
}
|
|
182
|
-
if (
|
|
183
|
-
!isLogux &&
|
|
184
|
-
!this.types[action.type] &&
|
|
185
|
-
!this.getRegexProcessor(action.type)
|
|
186
|
-
) {
|
|
187
|
-
meta.status = 'processed'
|
|
188
|
-
}
|
|
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)
|
|
189
109
|
}
|
|
190
|
-
this.replaceResendShortcuts(meta)
|
|
191
110
|
})
|
|
192
|
-
// Reports are per action, `clean` for a reason-less action is emitted
|
|
193
|
-
// by the log before `batch` event
|
|
194
111
|
this.on('add', (action, meta) => {
|
|
195
112
|
if (meta.reasons.length === 0) {
|
|
196
113
|
cleaned[meta.id] = true
|
|
@@ -199,55 +116,6 @@ export class BaseServer {
|
|
|
199
116
|
this.emitter.emit('report', 'add', { action, meta })
|
|
200
117
|
}
|
|
201
118
|
})
|
|
202
|
-
this.on('batch', entries => {
|
|
203
|
-
let start = Date.now()
|
|
204
|
-
let ready = []
|
|
205
|
-
|
|
206
|
-
for (let [action, meta] of entries) {
|
|
207
|
-
if (this.destroying && !this.actionToQueue.has(meta.id)) {
|
|
208
|
-
continue
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (action.type === 'logux/subscribe') {
|
|
212
|
-
if (meta.server === this.nodeId) {
|
|
213
|
-
void this.subscribeAction(action, meta, start)
|
|
214
|
-
}
|
|
215
|
-
continue
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (action.type === 'logux/unsubscribe') {
|
|
219
|
-
if (meta.server === this.nodeId) {
|
|
220
|
-
this.unsubscribeAction(action, meta)
|
|
221
|
-
}
|
|
222
|
-
continue
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
let processor = this.getProcessor(action.type)
|
|
226
|
-
if (processor && processor.resend && meta.status === 'waiting') {
|
|
227
|
-
// Resending is asynchronous, so the action can’t wait for others
|
|
228
|
-
void this.resendAction(action, meta, processor, start)
|
|
229
|
-
continue
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (this.isUseless(action, meta)) {
|
|
233
|
-
this.emitter.emit('report', 'useless', { action, meta })
|
|
234
|
-
}
|
|
235
|
-
ready.push([action, meta, processor])
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (ready.length === 0) return
|
|
239
|
-
let runProcessors = () => {
|
|
240
|
-
for (let [action, meta, processor] of ready) {
|
|
241
|
-
this.runProcessor(action, meta, processor, start)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// Actions are processed only after they were sent to the clients
|
|
245
|
-
void Promise.resolve(this.sendBatch(ready)).then(runProcessors, error => {
|
|
246
|
-
// Do not stop action processing on a broken channel filter
|
|
247
|
-
runProcessors()
|
|
248
|
-
throw error
|
|
249
|
-
})
|
|
250
|
-
})
|
|
251
119
|
this.on('clean', (action, meta) => {
|
|
252
120
|
if (cleaned[meta.id]) {
|
|
253
121
|
delete cleaned[meta.id]
|
|
@@ -309,7 +177,6 @@ export class BaseServer {
|
|
|
309
177
|
this.userIds = new Map()
|
|
310
178
|
this.types = {}
|
|
311
179
|
this.regexTypes = new Map()
|
|
312
|
-
this.processing = 0
|
|
313
180
|
|
|
314
181
|
this.lastClient = 0
|
|
315
182
|
|
|
@@ -317,101 +184,54 @@ export class BaseServer {
|
|
|
317
184
|
this.subscribers = {}
|
|
318
185
|
|
|
319
186
|
this.authAttempts = {}
|
|
320
|
-
this.unknownTypes = {}
|
|
321
|
-
this.wrongChannels = {}
|
|
322
187
|
|
|
323
188
|
this.timeouts = {}
|
|
324
189
|
this.lastTimeout = 0
|
|
325
190
|
|
|
326
|
-
this.
|
|
327
|
-
this.
|
|
328
|
-
this.actionToQueue = new Map()
|
|
191
|
+
this.publisher = new Publisher(this)
|
|
192
|
+
this.runner = new ActionRunner(this)
|
|
329
193
|
|
|
330
|
-
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 = []
|
|
331
206
|
this.httpAllListeners = []
|
|
207
|
+
this.httpNotFoundListener = undefined
|
|
332
208
|
addHttpPages(this)
|
|
333
209
|
|
|
334
210
|
this.listenNotes = {}
|
|
335
211
|
|
|
336
|
-
let end = (actionId, queue, queueKey, ...args) => {
|
|
337
|
-
this.actionToQueue.delete(actionId)
|
|
338
|
-
if (queue.length() === 0) {
|
|
339
|
-
this.queues.delete(queueKey)
|
|
340
|
-
}
|
|
341
|
-
queue.next(...args)
|
|
342
|
-
}
|
|
343
|
-
let undoRemainingTasks = queue => {
|
|
344
|
-
let remainingTasks = queue.getQueue()
|
|
345
|
-
if (remainingTasks) {
|
|
346
|
-
for (let task of remainingTasks) {
|
|
347
|
-
this.undo(task.action, task.meta, 'error')
|
|
348
|
-
this.actionToQueue.delete(task.meta.id)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
queue.killAndDrain()
|
|
352
|
-
}
|
|
353
|
-
this.on('error', (e, action, meta) => {
|
|
354
|
-
let queueKey = this.actionToQueue.get(meta?.id)
|
|
355
|
-
if (queueKey) {
|
|
356
|
-
let queue = this.queues.get(queueKey)
|
|
357
|
-
undoRemainingTasks(queue)
|
|
358
|
-
end(meta.id, queue, queueKey, e)
|
|
359
|
-
}
|
|
360
|
-
})
|
|
361
|
-
this.on('processed', (action, meta) => {
|
|
362
|
-
if (action.type === 'logux/undo') {
|
|
363
|
-
let queueKey = this.actionToQueue.get(action.id)
|
|
364
|
-
if (queueKey) {
|
|
365
|
-
let queue = this.queues.get(queueKey)
|
|
366
|
-
undoRemainingTasks(queue)
|
|
367
|
-
end(action.id, queue, queueKey, null, meta)
|
|
368
|
-
}
|
|
369
|
-
} else if (action.type === 'logux/processed') {
|
|
370
|
-
let queueKey = this.actionToQueue.get(action.id)
|
|
371
|
-
if (queueKey) {
|
|
372
|
-
let queue = this.queues.get(queueKey)
|
|
373
|
-
end(action.id, queue, queueKey, null, meta)
|
|
374
|
-
}
|
|
375
|
-
} else if (
|
|
376
|
-
action.type !== 'logux/subscribed' &&
|
|
377
|
-
action.type !== 'logux/unsubscribed'
|
|
378
|
-
) {
|
|
379
|
-
let queueKey = this.actionToQueue.get(meta.id)
|
|
380
|
-
if (queueKey) {
|
|
381
|
-
let queue = this.queues.get(queueKey)
|
|
382
|
-
end(meta.id, queue, queueKey, null, meta)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
})
|
|
386
|
-
|
|
387
212
|
this.unbind.push(() => {
|
|
388
213
|
for (let i of this.connected.values()) i.destroy()
|
|
389
214
|
for (let i in this.timeouts) {
|
|
390
215
|
clearTimeout(this.timeouts[i])
|
|
391
216
|
}
|
|
392
217
|
})
|
|
393
|
-
this.unbind.push(() => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
})
|
|
403
|
-
})
|
|
404
|
-
this.unbind.push(() => {
|
|
405
|
-
return Promise.allSettled(
|
|
406
|
-
[...this.queues.values()].map(queue => {
|
|
407
|
-
return new Promise(resolve => {
|
|
408
|
-
queue.drain = resolve
|
|
409
|
-
})
|
|
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
|
|
410
225
|
})
|
|
411
|
-
|
|
226
|
+
}
|
|
412
227
|
})
|
|
413
228
|
}
|
|
414
229
|
|
|
230
|
+
handleClient(ws, req) {
|
|
231
|
+
ws.upgradeReq = req
|
|
232
|
+
this.addClient(new ServerConnection(ws))
|
|
233
|
+
}
|
|
234
|
+
|
|
415
235
|
addClient(connection) {
|
|
416
236
|
this.lastClient += 1
|
|
417
237
|
let key = this.lastClient.toString()
|
|
@@ -424,50 +244,20 @@ export class BaseServer {
|
|
|
424
244
|
this.authenticator = authenticator
|
|
425
245
|
}
|
|
426
246
|
|
|
427
|
-
buildUndo(action, meta, reason, extra) {
|
|
428
|
-
let undoMeta = { status: 'processed' }
|
|
429
|
-
|
|
430
|
-
if (meta.users) undoMeta.users = meta.users.slice(0)
|
|
431
|
-
if (meta.nodes) undoMeta.nodes = meta.nodes.slice(0)
|
|
432
|
-
if (meta.clients) undoMeta.clients = meta.clients.slice(0)
|
|
433
|
-
if (meta.reasons) undoMeta.reasons = meta.reasons.slice(0)
|
|
434
|
-
if (meta.channels) undoMeta.channels = meta.channels.slice(0)
|
|
435
|
-
if (meta.excludeClients) {
|
|
436
|
-
undoMeta.excludeClients = meta.excludeClients.slice(0)
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
let undoAction = {
|
|
440
|
-
...extra,
|
|
441
|
-
action,
|
|
442
|
-
id: meta.id,
|
|
443
|
-
reason,
|
|
444
|
-
type: 'logux/undo'
|
|
445
|
-
}
|
|
446
|
-
return [undoAction, undoMeta]
|
|
447
|
-
}
|
|
448
|
-
|
|
449
247
|
channel(pattern, callbacks, options = {}) {
|
|
450
|
-
|
|
451
|
-
|
|
248
|
+
let channel = normalizeChannel(
|
|
249
|
+
`Channel ${pattern}`,
|
|
250
|
+
callbacks,
|
|
251
|
+
options.queue || 'main'
|
|
252
|
+
)
|
|
452
253
|
if (typeof pattern === 'string') {
|
|
453
254
|
channel.pattern = createPattern(pattern)
|
|
454
255
|
} else {
|
|
455
256
|
channel.regexp = pattern
|
|
456
257
|
}
|
|
457
|
-
|
|
458
|
-
channel.queueName = options.queue || 'main'
|
|
459
258
|
this.channels.push(channel)
|
|
460
259
|
}
|
|
461
260
|
|
|
462
|
-
createContext(action, meta) {
|
|
463
|
-
let context = this.contexts.get(action)
|
|
464
|
-
if (!context) {
|
|
465
|
-
context = new Context(this, meta)
|
|
466
|
-
this.contexts.set(action, context)
|
|
467
|
-
}
|
|
468
|
-
return context
|
|
469
|
-
}
|
|
470
|
-
|
|
471
261
|
debugActionError(meta, msg) {
|
|
472
262
|
if (this.env === 'development') {
|
|
473
263
|
let clientId = parseId(meta.id).clientId
|
|
@@ -487,12 +277,6 @@ export class BaseServer {
|
|
|
487
277
|
}
|
|
488
278
|
}
|
|
489
279
|
|
|
490
|
-
denyAction(action, meta) {
|
|
491
|
-
this.emitter.emit('report', 'denied', { actionId: meta.id })
|
|
492
|
-
this.undo(action, meta, 'denied')
|
|
493
|
-
this.debugActionError(meta, `Action "${meta.id}" was denied`)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
280
|
destroy() {
|
|
497
281
|
this.destroying = true
|
|
498
282
|
this.emitter.emit('report', 'destroy')
|
|
@@ -505,37 +289,6 @@ export class BaseServer {
|
|
|
505
289
|
return client.drain()
|
|
506
290
|
}
|
|
507
291
|
|
|
508
|
-
finally(processor, ctx, action, meta) {
|
|
509
|
-
this.contexts.delete(action)
|
|
510
|
-
if (processor && processor.finally) {
|
|
511
|
-
try {
|
|
512
|
-
processor.finally(ctx, action, meta)
|
|
513
|
-
} catch (err) {
|
|
514
|
-
this.emitter.emit('error', err, action, meta)
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
getProcessor(type) {
|
|
520
|
-
return (
|
|
521
|
-
this.types[type] || this.getRegexProcessor(type) || this.otherProcessor
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
getRegexProcessor(type) {
|
|
526
|
-
for (let regexp of this.regexTypes.keys()) {
|
|
527
|
-
if (type.match(regexp) !== null) {
|
|
528
|
-
return this.regexTypes.get(regexp)
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return undefined
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
handleClient(ws, req) {
|
|
535
|
-
ws.upgradeReq = req
|
|
536
|
-
this.addClient(new ServerConnection(ws))
|
|
537
|
-
}
|
|
538
|
-
|
|
539
292
|
http(method, url, listener) {
|
|
540
293
|
if (this.options.disableHttpServer) {
|
|
541
294
|
throw new Error(
|
|
@@ -545,31 +298,26 @@ export class BaseServer {
|
|
|
545
298
|
if (!url) {
|
|
546
299
|
this.httpAllListeners.push(method)
|
|
547
300
|
} else {
|
|
548
|
-
|
|
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
|
+
}
|
|
549
310
|
}
|
|
550
311
|
}
|
|
551
312
|
|
|
552
|
-
|
|
553
|
-
this.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
})
|
|
559
|
-
if (parseId(meta.id).userId !== 'server') {
|
|
560
|
-
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
|
+
)
|
|
561
319
|
}
|
|
562
|
-
this.
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
internalWrongChannel(action, meta) {
|
|
566
|
-
this.contexts.delete(action)
|
|
567
|
-
this.emitter.emit('report', 'wrongChannel', {
|
|
568
|
-
actionId: meta.id,
|
|
569
|
-
channel: action.channel
|
|
570
|
-
})
|
|
571
|
-
this.undo(action, meta, 'wrongChannel')
|
|
572
|
-
this.debugActionError(meta, `Wrong channel name ${action.channel}`)
|
|
320
|
+
this.httpNotFoundListener = listener
|
|
573
321
|
}
|
|
574
322
|
|
|
575
323
|
isBruteforce(ip) {
|
|
@@ -578,14 +326,10 @@ export class BaseServer {
|
|
|
578
326
|
}
|
|
579
327
|
|
|
580
328
|
isUseless(action, meta) {
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
) {
|
|
586
|
-
return false
|
|
587
|
-
}
|
|
588
|
-
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) {
|
|
589
333
|
if (Array.isArray(meta[i]) && meta[i].length > 0) return false
|
|
590
334
|
}
|
|
591
335
|
return true
|
|
@@ -654,17 +398,6 @@ export class BaseServer {
|
|
|
654
398
|
})
|
|
655
399
|
}
|
|
656
400
|
|
|
657
|
-
markAsProcessed(meta) {
|
|
658
|
-
this.log.changeMeta(meta.id, { status: 'processed' })
|
|
659
|
-
let data = parseId(meta.id)
|
|
660
|
-
if (data.userId !== 'server') {
|
|
661
|
-
this.log.add(
|
|
662
|
-
{ id: meta.id, type: 'logux/processed' },
|
|
663
|
-
{ clients: [data.clientId], status: 'processed' }
|
|
664
|
-
)
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
401
|
on(event, listener) {
|
|
669
402
|
if (
|
|
670
403
|
event === 'preadd' ||
|
|
@@ -679,11 +412,10 @@ export class BaseServer {
|
|
|
679
412
|
}
|
|
680
413
|
|
|
681
414
|
otherChannel(callbacks) {
|
|
682
|
-
normalizeChannelCallbacks('Unknown channel', callbacks)
|
|
683
415
|
if (this.otherSubscriber) {
|
|
684
416
|
throw new Error('Callbacks for unknown channel are already defined')
|
|
685
417
|
}
|
|
686
|
-
let channel =
|
|
418
|
+
let channel = normalizeChannel('Unknown channel', callbacks, 'main')
|
|
687
419
|
channel.pattern = name => [name]
|
|
688
420
|
this.otherSubscriber = channel
|
|
689
421
|
}
|
|
@@ -692,75 +424,30 @@ export class BaseServer {
|
|
|
692
424
|
if (this.otherProcessor) {
|
|
693
425
|
throw new Error('Callbacks for unknown types are already defined')
|
|
694
426
|
}
|
|
695
|
-
|
|
696
|
-
this.otherProcessor = callbacks
|
|
427
|
+
this.otherProcessor = normalizeType('Unknown type', callbacks, 'main')
|
|
697
428
|
}
|
|
698
429
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
delete this.subscribers[action.channel]
|
|
715
|
-
}
|
|
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
|
|
716
445
|
}
|
|
717
446
|
}
|
|
718
|
-
this.emitter.emit('unsubscribed', action, meta, clientNodeId)
|
|
719
|
-
this.emitter.emit('report', 'unsubscribed', {
|
|
720
|
-
actionId: meta.id,
|
|
721
|
-
channel: action.channel
|
|
722
|
-
})
|
|
723
447
|
}
|
|
724
448
|
|
|
725
449
|
process(action, meta = {}) {
|
|
726
|
-
return
|
|
727
|
-
let unbindError = this.on('error', (e, errorAction) => {
|
|
728
|
-
if (errorAction === action) {
|
|
729
|
-
unbindError()
|
|
730
|
-
unbindProcessed()
|
|
731
|
-
reject(e)
|
|
732
|
-
}
|
|
733
|
-
})
|
|
734
|
-
let unbindProcessed = this.on('processed', (processed, processedMeta) => {
|
|
735
|
-
if (processed === action) {
|
|
736
|
-
unbindError()
|
|
737
|
-
unbindProcessed()
|
|
738
|
-
resolve(processedMeta)
|
|
739
|
-
}
|
|
740
|
-
})
|
|
741
|
-
this.log.add(action, meta)
|
|
742
|
-
})
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
async processAction(processor, action, meta, start) {
|
|
746
|
-
let ctx = this.createContext(action, meta)
|
|
747
|
-
|
|
748
|
-
let latency
|
|
749
|
-
this.processing += 1
|
|
750
|
-
try {
|
|
751
|
-
await processor.process(ctx, action, meta)
|
|
752
|
-
latency = Date.now() - start
|
|
753
|
-
this.markAsProcessed(meta)
|
|
754
|
-
} catch (e) {
|
|
755
|
-
this.log.changeMeta(meta.id, { status: 'error' })
|
|
756
|
-
this.undo(action, meta, 'error')
|
|
757
|
-
this.emitter.emit('error', e, action, meta)
|
|
758
|
-
} finally {
|
|
759
|
-
this.finally(processor, ctx, action, meta)
|
|
760
|
-
}
|
|
761
|
-
if (typeof latency === 'undefined') latency = Date.now() - start
|
|
762
|
-
this.processing -= 1
|
|
763
|
-
this.emitter.emit('processed', action, meta, latency)
|
|
450
|
+
return this.runner.submit({ action, meta, trusted: true })
|
|
764
451
|
}
|
|
765
452
|
|
|
766
453
|
async processHttp(req, res) {
|
|
@@ -769,24 +456,46 @@ export class BaseServer {
|
|
|
769
456
|
urlString = decodeURIComponent(urlString)
|
|
770
457
|
}
|
|
771
458
|
let reqUrl = new URL(urlString, 'http://localhost')
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (result === true) {
|
|
779
|
-
processed = true
|
|
780
|
-
break
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
if (!processed) {
|
|
784
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
785
|
-
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
|
|
786
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)
|
|
787
475
|
} else {
|
|
788
|
-
|
|
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)
|
|
789
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
|
+
)
|
|
790
499
|
}
|
|
791
500
|
|
|
792
501
|
rememberBadAuth(ip) {
|
|
@@ -800,172 +509,8 @@ export class BaseServer {
|
|
|
800
509
|
}, 3000)
|
|
801
510
|
}
|
|
802
511
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
meta.channels = [meta.channel]
|
|
806
|
-
delete meta.channel
|
|
807
|
-
}
|
|
808
|
-
if (meta.user) {
|
|
809
|
-
meta.users = [meta.user]
|
|
810
|
-
delete meta.user
|
|
811
|
-
}
|
|
812
|
-
if (meta.client) {
|
|
813
|
-
meta.clients = [meta.client]
|
|
814
|
-
delete meta.client
|
|
815
|
-
}
|
|
816
|
-
if (meta.node) {
|
|
817
|
-
meta.nodes = [meta.node]
|
|
818
|
-
delete meta.node
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
async resendAction(action, meta, processor, start) {
|
|
823
|
-
let ctx = this.createContext(action, meta)
|
|
824
|
-
let resend
|
|
825
|
-
try {
|
|
826
|
-
resend = await processor.resend(ctx, action, meta)
|
|
827
|
-
} catch (e) {
|
|
828
|
-
this.undo(action, meta, 'error')
|
|
829
|
-
this.emitter.emit('error', e, action, meta)
|
|
830
|
-
this.finally(processor, ctx, action, meta)
|
|
831
|
-
return
|
|
832
|
-
}
|
|
833
|
-
if (resend) {
|
|
834
|
-
if (typeof resend === 'string') {
|
|
835
|
-
resend = { channels: [resend] }
|
|
836
|
-
} else if (Array.isArray(resend)) {
|
|
837
|
-
resend = { channels: resend }
|
|
838
|
-
} else {
|
|
839
|
-
this.replaceResendShortcuts(resend)
|
|
840
|
-
}
|
|
841
|
-
let diff = {}
|
|
842
|
-
for (let i of RESEND_META) {
|
|
843
|
-
if (resend[i]) diff[i] = resend[i]
|
|
844
|
-
}
|
|
845
|
-
await this.log.changeMeta(meta.id, diff)
|
|
846
|
-
meta = { ...meta, ...diff }
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
await this.sendAction(action, meta)
|
|
850
|
-
this.runProcessor(action, meta, processor, start)
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
resolveTargets(action, meta, targets) {
|
|
854
|
-
let from = parseId(meta.id).clientId
|
|
855
|
-
let ignoreClients = new Set(meta.excludeClients || [])
|
|
856
|
-
ignoreClients.add(from)
|
|
857
|
-
|
|
858
|
-
if (meta.nodes) {
|
|
859
|
-
for (let id of meta.nodes) {
|
|
860
|
-
let client = this.nodeIds.get(id)
|
|
861
|
-
if (client) {
|
|
862
|
-
ignoreClients.add(client.clientId)
|
|
863
|
-
addTarget(targets, client, action, meta)
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (meta.clients) {
|
|
869
|
-
for (let id of meta.clients) {
|
|
870
|
-
if (this.clientIds.has(id)) {
|
|
871
|
-
let client = this.clientIds.get(id)
|
|
872
|
-
ignoreClients.add(client.clientId)
|
|
873
|
-
addTarget(targets, client, action, meta)
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
if (meta.users) {
|
|
879
|
-
for (let userId of meta.users) {
|
|
880
|
-
let users = this.userIds.get(userId)
|
|
881
|
-
if (users) {
|
|
882
|
-
for (let client of users) {
|
|
883
|
-
if (!ignoreClients.has(client.clientId)) {
|
|
884
|
-
ignoreClients.add(client.clientId)
|
|
885
|
-
addTarget(targets, client, action, meta)
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
if (!meta.channels) return undefined
|
|
893
|
-
|
|
894
|
-
let waiting = []
|
|
895
|
-
for (let channel of meta.channels) {
|
|
896
|
-
if (this.subscribers[channel]) {
|
|
897
|
-
for (let nodeId in this.subscribers[channel]) {
|
|
898
|
-
let clientId = parseId(nodeId).clientId
|
|
899
|
-
if (!ignoreClients.has(clientId)) {
|
|
900
|
-
let subscriber = this.subscribers[channel][nodeId]
|
|
901
|
-
if (subscriber) {
|
|
902
|
-
let ctx = this.createContext(action, meta)
|
|
903
|
-
let client = this.clientIds.get(clientId)
|
|
904
|
-
for (let filter of Object.values(subscriber.filters)) {
|
|
905
|
-
if (typeof filter === 'function') {
|
|
906
|
-
waiting.push(
|
|
907
|
-
Promise.resolve(filter(ctx, action, meta)).then(result => {
|
|
908
|
-
if (result && client && !ignoreClients.has(clientId)) {
|
|
909
|
-
ignoreClients.add(clientId)
|
|
910
|
-
addTarget(targets, client, action, meta)
|
|
911
|
-
}
|
|
912
|
-
})
|
|
913
|
-
)
|
|
914
|
-
} else if (filter && client) {
|
|
915
|
-
ignoreClients.add(clientId)
|
|
916
|
-
addTarget(targets, client, action, meta)
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return waiting.length > 0 ? Promise.all(waiting) : undefined
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
runProcessor(action, meta, processor, start) {
|
|
928
|
-
if (meta.status === 'waiting') {
|
|
929
|
-
if (!processor) {
|
|
930
|
-
this.internalUnknownType(action, meta)
|
|
931
|
-
return
|
|
932
|
-
}
|
|
933
|
-
if (processor.process) {
|
|
934
|
-
void this.processAction(processor, action, meta, start)
|
|
935
|
-
} else {
|
|
936
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
937
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
938
|
-
this.markAsProcessed(meta)
|
|
939
|
-
}
|
|
940
|
-
} else {
|
|
941
|
-
this.emitter.emit('processed', action, meta, 0)
|
|
942
|
-
this.finally(processor, this.createContext(action, meta), action, meta)
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
async sendAction(action, meta) {
|
|
947
|
-
let targets = new Map()
|
|
948
|
-
let waiting = this.resolveTargets(action, meta, targets)
|
|
949
|
-
if (waiting) await waiting
|
|
950
|
-
sendTargets(this, targets, maxAdded(0, meta))
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
sendBatch(entries) {
|
|
954
|
-
let targets = new Map()
|
|
955
|
-
let waiting = []
|
|
956
|
-
let settled = 0
|
|
957
|
-
for (let [action, meta] of entries) {
|
|
958
|
-
settled = maxAdded(settled, meta)
|
|
959
|
-
let promise = this.resolveTargets(action, meta, targets)
|
|
960
|
-
if (promise) waiting.push(promise)
|
|
961
|
-
}
|
|
962
|
-
if (waiting.length === 0) {
|
|
963
|
-
sendTargets(this, targets, settled)
|
|
964
|
-
return undefined
|
|
965
|
-
}
|
|
966
|
-
return Promise.all(waiting).then(() => {
|
|
967
|
-
sendTargets(this, targets, settled)
|
|
968
|
-
})
|
|
512
|
+
sendWithoutProcess(action, meta) {
|
|
513
|
+
return this.publisher.send([[action, meta]])
|
|
969
514
|
}
|
|
970
515
|
|
|
971
516
|
sendOnConnect(loader) {
|
|
@@ -981,163 +526,68 @@ export class BaseServer {
|
|
|
981
526
|
}, ms)
|
|
982
527
|
}
|
|
983
528
|
|
|
984
|
-
subscribe(nodeId, channel) {
|
|
529
|
+
async subscribe(nodeId, channel) {
|
|
985
530
|
if (channel === '__proto__' || nodeId === '__proto__') return
|
|
986
531
|
if (!this.subscribers[channel] || !this.subscribers[channel][nodeId]) {
|
|
987
532
|
if (!this.subscribers[channel]) {
|
|
988
533
|
this.subscribers[channel] = {}
|
|
989
534
|
}
|
|
990
535
|
this.subscribers[channel][nodeId] = { filters: { '{}': true } }
|
|
991
|
-
this.log.add(
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
async subscribeAction(action, meta, start) {
|
|
996
|
-
if (typeof action.channel !== 'string' || action.channel === '__proto__') {
|
|
997
|
-
this.wrongChannel(action, meta)
|
|
998
|
-
return
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
let channels = this.channels
|
|
1002
|
-
if (this.otherSubscriber) {
|
|
1003
|
-
channels = this.channels.concat([this.otherSubscriber])
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
let match
|
|
1007
|
-
for (let channel of channels) {
|
|
1008
|
-
if (channel.pattern) {
|
|
1009
|
-
match = channel.pattern(action.channel)
|
|
1010
|
-
} else {
|
|
1011
|
-
match = action.channel.match(channel.regexp)
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
let subscribed = false
|
|
1015
|
-
if (match) {
|
|
1016
|
-
let ctx = this.createContext(action, meta)
|
|
1017
|
-
if (ctx.nodeId === '__proto__') return
|
|
1018
|
-
ctx.params = match
|
|
1019
|
-
try {
|
|
1020
|
-
let access = await channel.access(ctx, action, meta)
|
|
1021
|
-
if (this.wrongChannels[meta.id]) {
|
|
1022
|
-
delete this.wrongChannels[meta.id]
|
|
1023
|
-
return
|
|
1024
|
-
}
|
|
1025
|
-
if (!access) {
|
|
1026
|
-
this.denyAction(action, meta)
|
|
1027
|
-
return
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
let client = this.clientIds.get(ctx.clientId)
|
|
1031
|
-
if (!client) {
|
|
1032
|
-
this.emitter.emit('subscriptionCancelled')
|
|
1033
|
-
return
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
let filterId = subscriberFilterId(action)
|
|
1037
|
-
let filters = { [filterId]: true }
|
|
1038
|
-
|
|
1039
|
-
if (channel.filter) {
|
|
1040
|
-
let filter = await channel.filter(ctx, action, meta)
|
|
1041
|
-
filters = { [filterId]: filter }
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
this.emitter.emit('report', 'subscribed', {
|
|
1045
|
-
actionId: meta.id,
|
|
1046
|
-
channel: action.channel
|
|
1047
|
-
})
|
|
1048
|
-
|
|
1049
|
-
if (!this.subscribers[action.channel]) {
|
|
1050
|
-
this.subscribers[action.channel] = {}
|
|
1051
|
-
this.emitter.emit('subscribing', action, meta)
|
|
1052
|
-
}
|
|
1053
|
-
let subscriber = this.subscribers[action.channel][ctx.nodeId]
|
|
1054
|
-
if (subscriber) {
|
|
1055
|
-
filters = { ...subscriber.filters, ...filters }
|
|
1056
|
-
}
|
|
1057
|
-
this.subscribers[action.channel][ctx.nodeId] = {
|
|
1058
|
-
filters,
|
|
1059
|
-
unsubscribe: channel.unsubscribe
|
|
1060
|
-
? (unsubscribeAction, unsubscribeMeta) =>
|
|
1061
|
-
channel.unsubscribe(ctx, unsubscribeAction, unsubscribeMeta)
|
|
1062
|
-
: undefined
|
|
1063
|
-
}
|
|
1064
|
-
subscribed = true
|
|
1065
|
-
|
|
1066
|
-
if (channel.load) {
|
|
1067
|
-
// Actions from a single `load()` are sent in a single message
|
|
1068
|
-
let sendBack = await channel.load(ctx, action, meta)
|
|
1069
|
-
if (sendBack) await ctx.sendBack(sendBack)
|
|
1070
|
-
}
|
|
1071
|
-
this.emitter.emit('subscribed', action, meta, Date.now() - start)
|
|
1072
|
-
this.markAsProcessed(meta)
|
|
1073
|
-
} catch (e) {
|
|
1074
|
-
if (e.name === 'LoguxNotFoundError') {
|
|
1075
|
-
this.undo(action, meta, 'notFound')
|
|
1076
|
-
} else {
|
|
1077
|
-
this.emitter.emit('error', e, action, meta)
|
|
1078
|
-
this.undo(action, meta, 'error')
|
|
1079
|
-
}
|
|
1080
|
-
if (subscribed) {
|
|
1081
|
-
this.unsubscribe(action, meta)
|
|
1082
|
-
}
|
|
1083
|
-
} finally {
|
|
1084
|
-
this.finally(channel, ctx, action, meta)
|
|
536
|
+
await this.log.add(
|
|
537
|
+
{ channel, type: 'logux/subscribed' },
|
|
538
|
+
{
|
|
539
|
+
nodes: [nodeId]
|
|
1085
540
|
}
|
|
1086
|
-
|
|
1087
|
-
}
|
|
541
|
+
)
|
|
1088
542
|
}
|
|
1089
|
-
|
|
1090
|
-
if (!match) this.wrongChannel(action, meta)
|
|
1091
543
|
}
|
|
1092
544
|
|
|
1093
545
|
type(name, callbacks, options = {}) {
|
|
1094
|
-
let queue = options.queue || 'main'
|
|
1095
|
-
this.typeToQueue.set(name, queue)
|
|
1096
|
-
|
|
1097
546
|
if (typeof name === 'function') name = name.type
|
|
1098
|
-
|
|
547
|
+
let handler = normalizeType(
|
|
548
|
+
`Action type ${name}`,
|
|
549
|
+
callbacks,
|
|
550
|
+
options.queue || 'main'
|
|
551
|
+
)
|
|
1099
552
|
|
|
1100
553
|
if (name instanceof RegExp) {
|
|
1101
|
-
this.regexTypes.set(name,
|
|
554
|
+
this.regexTypes.set(name, handler)
|
|
1102
555
|
} else {
|
|
1103
556
|
if (this.types[name]) {
|
|
1104
557
|
throw new Error(`Action type ${name} was already defined`)
|
|
1105
558
|
}
|
|
1106
|
-
this.types[name] =
|
|
559
|
+
this.types[name] = handler
|
|
1107
560
|
}
|
|
1108
561
|
}
|
|
1109
562
|
|
|
1110
|
-
|
|
1111
|
-
let
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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)
|
|
1115
569
|
}
|
|
1116
570
|
|
|
1117
|
-
|
|
1118
|
-
this.
|
|
1119
|
-
this.
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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)
|
|
1125
580
|
}
|
|
1126
581
|
|
|
1127
|
-
|
|
1128
|
-
if (
|
|
1129
|
-
this.
|
|
1130
|
-
return
|
|
582
|
+
unknownType(action, meta) {
|
|
583
|
+
if (parseId(meta.id).userId === 'server') {
|
|
584
|
+
this.reportFailure('unknownType', action, meta)
|
|
585
|
+
return Promise.resolve()
|
|
1131
586
|
}
|
|
1132
|
-
|
|
1133
|
-
this.unsubscribe(action, meta)
|
|
1134
|
-
|
|
1135
|
-
this.markAsProcessed(meta)
|
|
1136
|
-
this.contexts.delete(action)
|
|
587
|
+
return this.undo(action, meta, 'unknownType')
|
|
1137
588
|
}
|
|
1138
589
|
|
|
1139
590
|
wrongChannel(action, meta) {
|
|
1140
|
-
this.
|
|
1141
|
-
this.wrongChannels[meta.id] = true
|
|
591
|
+
return this.undo(action, meta, 'wrongChannel')
|
|
1142
592
|
}
|
|
1143
593
|
}
|