@logux/server 0.12.9 → 0.13.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/add-sync-map/index.d.ts +2 -2
- package/allowed-meta/index.d.ts +1 -1
- package/base-server/index.d.ts +48 -18
- package/base-server/index.js +95 -24
- package/bind-backend-proxy/index.js +4 -4
- package/create-http-server/index.js +4 -4
- package/create-reporter/index.js +3 -3
- package/filtered-node/index.js +7 -19
- package/human-formatter/index.js +15 -5
- package/options-loader/index.test.js +1 -1
- package/package.json +12 -12
- package/server/index.js +1 -1
- package/server-client/index.js +107 -58
- package/test-client/index.js +1 -1
- package/vite.config.ts +15 -3
package/add-sync-map/index.d.ts
CHANGED
|
@@ -27,9 +27,9 @@ export type WithoutTime<Value extends SyncMapTypes | SyncMapTypes[]> = {
|
|
|
27
27
|
[WITH_TIME]: false
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export type SyncMapData<Value extends SyncMapValues> = {
|
|
30
|
+
export type SyncMapData<Value extends SyncMapValues> = {
|
|
31
31
|
[Key in keyof Value]: WithoutTime<Value[Key]> | WithTime<Value[Key]>
|
|
32
|
-
}
|
|
32
|
+
} & { id: string }
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Add last changed time to value to use in conflict resolution.
|
package/allowed-meta/index.d.ts
CHANGED
package/base-server/index.d.ts
CHANGED
|
@@ -13,13 +13,33 @@ import type {
|
|
|
13
13
|
ServerConnection,
|
|
14
14
|
TestTime
|
|
15
15
|
} from '@logux/core'
|
|
16
|
-
import type { Server as HTTPServer, IncomingMessage, ServerResponse } from 'http'
|
|
17
16
|
import type { Unsubscribe } from 'nanoevents'
|
|
17
|
+
import type {
|
|
18
|
+
Server as HTTPServer,
|
|
19
|
+
IncomingMessage,
|
|
20
|
+
ServerResponse
|
|
21
|
+
} from 'node:http'
|
|
18
22
|
import type { LogFn } from 'pino'
|
|
19
23
|
|
|
20
24
|
import type { ChannelContext, Context } from '../context/index.js'
|
|
21
25
|
import type { ServerClient } from '../server-client/index.js'
|
|
22
26
|
|
|
27
|
+
interface TypeOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Name of the queue that will be used to process actions
|
|
30
|
+
* of the specified type. Default is 'main'
|
|
31
|
+
*/
|
|
32
|
+
queue?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ChannelOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Name of the queue that will be used to process channels
|
|
38
|
+
* with the specified name pattern. Default is 'main'
|
|
39
|
+
*/
|
|
40
|
+
queue?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
export interface ServerMeta extends Meta {
|
|
24
44
|
/**
|
|
25
45
|
* All nodes subscribed to channel will receive the action.
|
|
@@ -446,7 +466,10 @@ type ActionCallbacks<
|
|
|
446
466
|
TypeAction extends Action,
|
|
447
467
|
Data extends object,
|
|
448
468
|
Headers extends object
|
|
449
|
-
> =
|
|
469
|
+
> = {
|
|
470
|
+
finally?: ActionFinally<TypeAction, Data, Headers>
|
|
471
|
+
resend?: Resender<TypeAction, Data, Headers>
|
|
472
|
+
} & (
|
|
450
473
|
| {
|
|
451
474
|
access: Authorizer<TypeAction, Data, Headers>
|
|
452
475
|
process?: Processor<TypeAction, Data, Headers>
|
|
@@ -454,17 +477,18 @@ type ActionCallbacks<
|
|
|
454
477
|
| {
|
|
455
478
|
accessAndProcess: Processor<TypeAction, Data, Headers>
|
|
456
479
|
}
|
|
457
|
-
)
|
|
458
|
-
finally?: ActionFinally<TypeAction, Data, Headers>
|
|
459
|
-
resend?: Resender<TypeAction, Data, Headers>
|
|
460
|
-
}
|
|
480
|
+
)
|
|
461
481
|
|
|
462
482
|
type ChannelCallbacks<
|
|
463
483
|
SubscribeAction extends Action,
|
|
464
484
|
Data extends object,
|
|
465
485
|
ChannelParams extends object | string[],
|
|
466
486
|
Headers extends object
|
|
467
|
-
> =
|
|
487
|
+
> = {
|
|
488
|
+
filter?: FilterCreator<SubscribeAction, Data, ChannelParams, Headers>
|
|
489
|
+
finally?: ChannelFinally<SubscribeAction, Data, ChannelParams, Headers>
|
|
490
|
+
unsubscribe?: ChannelUnsubscribe<Data, ChannelParams, Headers>
|
|
491
|
+
} & (
|
|
468
492
|
| {
|
|
469
493
|
access: ChannelAuthorizer<SubscribeAction, Data, ChannelParams, Headers>
|
|
470
494
|
load?: ChannelLoader<SubscribeAction, Data, ChannelParams, Headers>
|
|
@@ -477,11 +501,7 @@ type ChannelCallbacks<
|
|
|
477
501
|
Headers
|
|
478
502
|
>
|
|
479
503
|
}
|
|
480
|
-
)
|
|
481
|
-
filter?: FilterCreator<SubscribeAction, Data, ChannelParams, Headers>
|
|
482
|
-
finally?: ChannelFinally<SubscribeAction, Data, ChannelParams, Headers>
|
|
483
|
-
unsubscribe?: ChannelUnsubscribe<Data, ChannelParams, Headers>
|
|
484
|
-
}
|
|
504
|
+
)
|
|
485
505
|
|
|
486
506
|
interface ActionReporter {
|
|
487
507
|
action: Readonly<Action>
|
|
@@ -505,6 +525,7 @@ interface AuthenticationReporter {
|
|
|
505
525
|
|
|
506
526
|
interface ReportersArguments {
|
|
507
527
|
add: ActionReporter
|
|
528
|
+
addClean: ActionReporter
|
|
508
529
|
authenticated: AuthenticationReporter
|
|
509
530
|
clean: CleanReporter
|
|
510
531
|
clientError: {
|
|
@@ -803,6 +824,7 @@ export class BaseServer<
|
|
|
803
824
|
*
|
|
804
825
|
* @param pattern Pattern for channel name.
|
|
805
826
|
* @param callbacks Callback during subscription process.
|
|
827
|
+
* @param options Additional options
|
|
806
828
|
*/
|
|
807
829
|
channel<
|
|
808
830
|
ChannelParams extends object = {},
|
|
@@ -810,12 +832,14 @@ export class BaseServer<
|
|
|
810
832
|
SubscribeAction extends LoguxSubscribeAction = LoguxSubscribeAction
|
|
811
833
|
>(
|
|
812
834
|
pattern: string,
|
|
813
|
-
callbacks: ChannelCallbacks<SubscribeAction, Data, ChannelParams, Headers
|
|
835
|
+
callbacks: ChannelCallbacks<SubscribeAction, Data, ChannelParams, Headers>,
|
|
836
|
+
options?: ChannelOptions
|
|
814
837
|
): void
|
|
815
838
|
|
|
816
839
|
/**
|
|
817
840
|
* @param pattern Regular expression for channel name.
|
|
818
841
|
* @param callbacks Callback during subscription process.
|
|
842
|
+
* @param options Additional options
|
|
819
843
|
*/
|
|
820
844
|
channel<
|
|
821
845
|
ChannelParams extends string[] = string[],
|
|
@@ -823,7 +847,8 @@ export class BaseServer<
|
|
|
823
847
|
SubscribeAction extends LoguxSubscribeAction = LoguxSubscribeAction
|
|
824
848
|
>(
|
|
825
849
|
pattern: RegExp,
|
|
826
|
-
callbacks: ChannelCallbacks<SubscribeAction, Data, ChannelParams, Headers
|
|
850
|
+
callbacks: ChannelCallbacks<SubscribeAction, Data, ChannelParams, Headers>,
|
|
851
|
+
options?: ChannelOptions
|
|
827
852
|
): void
|
|
828
853
|
|
|
829
854
|
/**
|
|
@@ -975,7 +1000,7 @@ export class BaseServer<
|
|
|
975
1000
|
* @param listener Client listener.
|
|
976
1001
|
*/
|
|
977
1002
|
on(
|
|
978
|
-
event: 'authenticated',
|
|
1003
|
+
event: 'authenticated' | 'unauthenticated',
|
|
979
1004
|
listener: (client: ServerClient, latencyMilliseconds: number) => void
|
|
980
1005
|
): Unsubscribe
|
|
981
1006
|
|
|
@@ -1009,7 +1034,8 @@ export class BaseServer<
|
|
|
1009
1034
|
event: 'unsubscribed',
|
|
1010
1035
|
listener: (
|
|
1011
1036
|
action: LoguxUnsubscribeAction,
|
|
1012
|
-
meta: Readonly<ServerMeta
|
|
1037
|
+
meta: Readonly<ServerMeta>,
|
|
1038
|
+
clientNodeId: string
|
|
1013
1039
|
) => void
|
|
1014
1040
|
): Unsubscribe
|
|
1015
1041
|
|
|
@@ -1120,10 +1146,12 @@ export class BaseServer<
|
|
|
1120
1146
|
/**
|
|
1121
1147
|
* @param actionCreator Action creator function.
|
|
1122
1148
|
* @param callbacks Callbacks for action created by creator.
|
|
1149
|
+
* @param options Additional options
|
|
1123
1150
|
*/
|
|
1124
1151
|
type<Creator extends AbstractActionCreator, Data extends object = {}>(
|
|
1125
1152
|
actionCreator: Creator,
|
|
1126
|
-
callbacks: ActionCallbacks<ReturnType<Creator>, Data, Headers
|
|
1153
|
+
callbacks: ActionCallbacks<ReturnType<Creator>, Data, Headers>,
|
|
1154
|
+
options?: TypeOptions
|
|
1127
1155
|
): void
|
|
1128
1156
|
|
|
1129
1157
|
/**
|
|
@@ -1147,10 +1175,12 @@ export class BaseServer<
|
|
|
1147
1175
|
*
|
|
1148
1176
|
* @param name The action’s type or action’s type matching rule as RegExp..
|
|
1149
1177
|
* @param callbacks Callbacks for actions with this type.
|
|
1178
|
+
* @param options Additional options
|
|
1150
1179
|
*/
|
|
1151
1180
|
type<TypeAction extends Action = AnyAction, Data extends object = {}>(
|
|
1152
1181
|
name: RegExp | TypeAction['type'],
|
|
1153
|
-
callbacks: ActionCallbacks<TypeAction, Data, Headers
|
|
1182
|
+
callbacks: ActionCallbacks<TypeAction, Data, Headers>,
|
|
1183
|
+
options?: TypeOptions
|
|
1154
1184
|
): void
|
|
1155
1185
|
|
|
1156
1186
|
/**
|
package/base-server/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { LoguxNotFoundError } from '@logux/actions'
|
|
2
2
|
import { Log, MemoryStore, parseId, ServerConnection } from '@logux/core'
|
|
3
|
-
import { promises as fs } from 'fs'
|
|
4
3
|
import { createNanoEvents } from 'nanoevents'
|
|
5
4
|
import { nanoid } from 'nanoid'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { promises as fs } from 'node:fs'
|
|
6
|
+
import { dirname, join } from 'node:path'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
8
|
import UrlPattern from 'url-pattern'
|
|
9
9
|
import { WebSocketServer } from 'ws'
|
|
10
10
|
|
|
@@ -185,7 +185,9 @@ export class BaseServer {
|
|
|
185
185
|
this.emitter.emit('report', 'add', { action, meta })
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
if (this.destroying)
|
|
188
|
+
if (this.destroying && !this.actionToQueue.has(meta.id)) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
189
191
|
|
|
190
192
|
if (action.type === 'logux/subscribe') {
|
|
191
193
|
if (meta.server === this.nodeId) {
|
|
@@ -238,7 +240,7 @@ export class BaseServer {
|
|
|
238
240
|
|
|
239
241
|
if (meta.status === 'waiting') {
|
|
240
242
|
if (!processor) {
|
|
241
|
-
this.
|
|
243
|
+
this.internalUnknownType(action, meta)
|
|
242
244
|
return
|
|
243
245
|
}
|
|
244
246
|
if (processor.process) {
|
|
@@ -333,6 +335,10 @@ export class BaseServer {
|
|
|
333
335
|
this.timeouts = {}
|
|
334
336
|
this.lastTimeout = 0
|
|
335
337
|
|
|
338
|
+
this.typeToQueue = new Map()
|
|
339
|
+
this.queues = new Map()
|
|
340
|
+
this.actionToQueue = new Map()
|
|
341
|
+
|
|
336
342
|
this.controls = {
|
|
337
343
|
'GET /': {
|
|
338
344
|
async request() {
|
|
@@ -355,24 +361,83 @@ export class BaseServer {
|
|
|
355
361
|
this.listenNotes = {}
|
|
356
362
|
bindBackendProxy(this)
|
|
357
363
|
|
|
364
|
+
let end = (actionId, queue, queueKey, ...args) => {
|
|
365
|
+
this.actionToQueue.delete(actionId)
|
|
366
|
+
if (queue.length() === 0) {
|
|
367
|
+
this.queues.delete(queueKey)
|
|
368
|
+
}
|
|
369
|
+
queue.next(...args)
|
|
370
|
+
}
|
|
371
|
+
let undoRemainingTasks = queue => {
|
|
372
|
+
let remainingTasks = queue.getQueue()
|
|
373
|
+
if (remainingTasks) {
|
|
374
|
+
for (let task of remainingTasks) {
|
|
375
|
+
this.undo(task.action, task.meta, 'error')
|
|
376
|
+
this.actionToQueue.delete(task.meta.id)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
queue.killAndDrain()
|
|
380
|
+
}
|
|
381
|
+
this.on('error', (e, action, meta) => {
|
|
382
|
+
let queueKey = this.actionToQueue.get(meta?.id)
|
|
383
|
+
if (queueKey) {
|
|
384
|
+
let queue = this.queues.get(queueKey)
|
|
385
|
+
undoRemainingTasks(queue)
|
|
386
|
+
end(meta.id, queue, queueKey, e)
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
this.on('processed', (action, meta) => {
|
|
390
|
+
if (action.type === 'logux/undo') {
|
|
391
|
+
let queueKey = this.actionToQueue.get(action.id)
|
|
392
|
+
if (queueKey) {
|
|
393
|
+
let queue = this.queues.get(queueKey)
|
|
394
|
+
undoRemainingTasks(queue)
|
|
395
|
+
end(action.id, queue, queueKey, null, meta)
|
|
396
|
+
}
|
|
397
|
+
} else if (action.type === 'logux/processed') {
|
|
398
|
+
let queueKey = this.actionToQueue.get(action.id)
|
|
399
|
+
if (queueKey) {
|
|
400
|
+
let queue = this.queues.get(queueKey)
|
|
401
|
+
end(action.id, queue, queueKey, null, meta)
|
|
402
|
+
}
|
|
403
|
+
} else if (
|
|
404
|
+
action.type !== 'logux/subscribed' &&
|
|
405
|
+
action.type !== 'logux/unsubscribed'
|
|
406
|
+
) {
|
|
407
|
+
let queueKey = this.actionToQueue.get(meta.id)
|
|
408
|
+
if (queueKey) {
|
|
409
|
+
let queue = this.queues.get(queueKey)
|
|
410
|
+
end(meta.id, queue, queueKey, null, meta)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
|
|
358
415
|
this.unbind.push(() => {
|
|
359
416
|
for (let i of this.connected.values()) i.destroy()
|
|
360
417
|
for (let i in this.timeouts) {
|
|
361
418
|
clearTimeout(this.timeouts[i])
|
|
362
419
|
}
|
|
363
420
|
})
|
|
364
|
-
this.unbind.push(
|
|
365
|
-
(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
421
|
+
this.unbind.push(() => {
|
|
422
|
+
return new Promise(resolve => {
|
|
423
|
+
if (this.processing === 0) {
|
|
424
|
+
resolve()
|
|
425
|
+
} else {
|
|
426
|
+
this.on('processed', () => {
|
|
427
|
+
if (this.processing === 0) resolve()
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
})
|
|
432
|
+
this.unbind.push(() => {
|
|
433
|
+
return Promise.allSettled(
|
|
434
|
+
[...this.queues.values()].map(queue => {
|
|
435
|
+
return new Promise(resolve => {
|
|
436
|
+
queue.drain = resolve
|
|
437
|
+
})
|
|
374
438
|
})
|
|
375
|
-
|
|
439
|
+
)
|
|
440
|
+
})
|
|
376
441
|
}
|
|
377
442
|
|
|
378
443
|
addClient(connection) {
|
|
@@ -409,7 +474,7 @@ export class BaseServer {
|
|
|
409
474
|
return [undoAction, undoMeta]
|
|
410
475
|
}
|
|
411
476
|
|
|
412
|
-
channel(pattern, callbacks) {
|
|
477
|
+
channel(pattern, callbacks, options = {}) {
|
|
413
478
|
normalizeChannelCallbacks(`Channel ${pattern}`, callbacks)
|
|
414
479
|
let channel = Object.assign({}, callbacks)
|
|
415
480
|
if (typeof pattern === 'string') {
|
|
@@ -419,6 +484,8 @@ export class BaseServer {
|
|
|
419
484
|
} else {
|
|
420
485
|
channel.regexp = pattern
|
|
421
486
|
}
|
|
487
|
+
|
|
488
|
+
channel.queueName = options.queue || 'main'
|
|
422
489
|
this.channels.push(channel)
|
|
423
490
|
}
|
|
424
491
|
|
|
@@ -497,7 +564,7 @@ export class BaseServer {
|
|
|
497
564
|
this.httpListener = listener
|
|
498
565
|
}
|
|
499
566
|
|
|
500
|
-
|
|
567
|
+
internalUnknownType(action, meta) {
|
|
501
568
|
this.contexts.delete(action)
|
|
502
569
|
this.log.changeMeta(meta.id, { status: 'error' })
|
|
503
570
|
this.emitter.emit('report', 'unknownType', {
|
|
@@ -657,7 +724,7 @@ export class BaseServer {
|
|
|
657
724
|
}
|
|
658
725
|
}
|
|
659
726
|
}
|
|
660
|
-
this.emitter.emit('unsubscribed', action, meta)
|
|
727
|
+
this.emitter.emit('unsubscribed', action, meta, clientNodeId)
|
|
661
728
|
this.emitter.emit('report', 'unsubscribed', {
|
|
662
729
|
actionId: meta.id,
|
|
663
730
|
channel: action.channel
|
|
@@ -785,9 +852,10 @@ export class BaseServer {
|
|
|
785
852
|
let ctx = this.createContext(action, meta)
|
|
786
853
|
let client = this.clientIds.get(clientId)
|
|
787
854
|
for (let filter of Object.values(subscriber.filters)) {
|
|
788
|
-
filter =
|
|
789
|
-
|
|
790
|
-
|
|
855
|
+
filter =
|
|
856
|
+
typeof filter === 'function'
|
|
857
|
+
? await filter(ctx, action, meta)
|
|
858
|
+
: filter
|
|
791
859
|
if (filter && client) {
|
|
792
860
|
ignoreClients.add(clientId)
|
|
793
861
|
client.node.onAdd(action, meta)
|
|
@@ -924,7 +992,10 @@ export class BaseServer {
|
|
|
924
992
|
if (!match) this.wrongChannel(action, meta)
|
|
925
993
|
}
|
|
926
994
|
|
|
927
|
-
type(name, callbacks) {
|
|
995
|
+
type(name, callbacks, options = {}) {
|
|
996
|
+
let queue = options.queue || 'main'
|
|
997
|
+
this.typeToQueue.set(name, queue)
|
|
998
|
+
|
|
928
999
|
if (typeof name === 'function') name = name.type
|
|
929
1000
|
normalizeTypeCallbacks(`Action type ${name}`, callbacks)
|
|
930
1001
|
|
|
@@ -946,7 +1017,7 @@ export class BaseServer {
|
|
|
946
1017
|
}
|
|
947
1018
|
|
|
948
1019
|
unknownType(action, meta) {
|
|
949
|
-
this.
|
|
1020
|
+
this.internalUnknownType(action, meta)
|
|
950
1021
|
this.unknownTypes[meta.id] = true
|
|
951
1022
|
}
|
|
952
1023
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LoguxError } from '@logux/core'
|
|
2
|
-
import http from 'http'
|
|
3
|
-
import https from 'https'
|
|
4
2
|
import JSONStream from 'JSONStream'
|
|
5
3
|
import { nanoid } from 'nanoid'
|
|
4
|
+
import http from 'node:http'
|
|
5
|
+
import https from 'node:https'
|
|
6
6
|
|
|
7
7
|
const VERSION = 4
|
|
8
8
|
|
|
@@ -126,7 +126,7 @@ export function bindBackendProxy(app) {
|
|
|
126
126
|
})
|
|
127
127
|
)
|
|
128
128
|
}
|
|
129
|
-
let
|
|
129
|
+
let accessReject, accessResolve
|
|
130
130
|
accessing.set(
|
|
131
131
|
meta.id,
|
|
132
132
|
new Promise((resolve, reject) => {
|
|
@@ -134,7 +134,7 @@ export function bindBackendProxy(app) {
|
|
|
134
134
|
accessReject = reject
|
|
135
135
|
})
|
|
136
136
|
)
|
|
137
|
-
let
|
|
137
|
+
let processReject, processResolve
|
|
138
138
|
processing.set(
|
|
139
139
|
meta.id,
|
|
140
140
|
new Promise((resolve, reject) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { promises as fs } from 'fs'
|
|
2
|
-
import http from 'http'
|
|
3
|
-
import https from 'https'
|
|
4
|
-
import { isAbsolute, join } from 'path'
|
|
1
|
+
import { promises as fs } from 'node:fs'
|
|
2
|
+
import http from 'node:http'
|
|
3
|
+
import https from 'node:https'
|
|
4
|
+
import { isAbsolute, join } from 'node:path'
|
|
5
5
|
|
|
6
6
|
const PEM_PREAMBLE = '-----BEGIN'
|
|
7
7
|
|
package/create-reporter/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import os from 'os'
|
|
2
|
-
import { dirname, join, sep } from 'path'
|
|
1
|
+
import os from 'node:os'
|
|
2
|
+
import { dirname, join, sep } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import pico from 'picocolors'
|
|
4
5
|
import pino from 'pino'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
8
|
export const PATH_TO_PRETTIFYING_PINO_TRANSPORT = join(
|
package/filtered-node/index.js
CHANGED
|
@@ -16,24 +16,12 @@ export class FilteredNode extends ServerNode {
|
|
|
16
16
|
delete this.received
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(has(meta.clients, this.client.clientId) ||
|
|
27
|
-
has(meta.nodes, this.client.nodeId) ||
|
|
28
|
-
has(meta.users, this.client.userId)) &&
|
|
29
|
-
!has(meta.excludeClients, this.client.clientId)
|
|
30
|
-
) {
|
|
31
|
-
if (meta.added > data.added) data.added = meta.added
|
|
32
|
-
data.entries.push([action, meta])
|
|
33
|
-
}
|
|
34
|
-
return true
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
return data
|
|
19
|
+
syncFilter(action, meta) {
|
|
20
|
+
return (
|
|
21
|
+
(has(meta.clients, this.client.clientId) ||
|
|
22
|
+
has(meta.nodes, this.client.nodeId) ||
|
|
23
|
+
has(meta.users, this.client.userId)) &&
|
|
24
|
+
!has(meta.excludeClients, this.client.clientId)
|
|
25
|
+
)
|
|
38
26
|
}
|
|
39
27
|
}
|
package/human-formatter/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { once } from 'events'
|
|
2
|
-
import os from 'os'
|
|
1
|
+
import { once } from 'node:events'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import { Transform } from 'node:stream'
|
|
3
4
|
import pico from 'picocolors'
|
|
4
5
|
import pino from 'pino'
|
|
5
6
|
import abstractTransport from 'pino-abstract-transport'
|
|
6
|
-
import { Transform } from 'stream'
|
|
7
7
|
import stripAnsi from 'strip-ansi'
|
|
8
|
-
import yyyymmdd from 'yyyy-mm-dd'
|
|
9
8
|
|
|
10
9
|
import { mulberry32, onceXmur3 } from './utils.js'
|
|
11
10
|
|
|
@@ -39,6 +38,17 @@ const LABELS = {
|
|
|
39
38
|
|
|
40
39
|
const COLORS = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan']
|
|
41
40
|
|
|
41
|
+
function formatNow() {
|
|
42
|
+
let date = new Date()
|
|
43
|
+
let year = date.getFullYear()
|
|
44
|
+
let month = String(date.getMonth() + 1).padStart(2, '0')
|
|
45
|
+
let day = String(date.getDate()).padStart(2, '0')
|
|
46
|
+
let hour = String(date.getHours()).padStart(2, '0')
|
|
47
|
+
let minutes = String(date.getMinutes()).padStart(2, '0')
|
|
48
|
+
let seconds = String(date.getSeconds()).padStart(2, '0')
|
|
49
|
+
return `${year}-${month}-${day} ${hour}:${minutes}:${seconds}`
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
function rightPag(str, length) {
|
|
43
53
|
let add = length - stripAnsi(str).length
|
|
44
54
|
for (let i = 0; i < add; i++) str += ' '
|
|
@@ -47,7 +57,7 @@ function rightPag(str, length) {
|
|
|
47
57
|
|
|
48
58
|
function label(c, type, color, labelBg, labelText, message) {
|
|
49
59
|
let pagged = rightPag(c[labelBg](c[labelText](type)), 8)
|
|
50
|
-
let time = c.dim(`at ${
|
|
60
|
+
let time = c.dim(`at ${formatNow()}`)
|
|
51
61
|
let highlighted = message.replace(/`([^`]+)`/g, c.yellow('$1'))
|
|
52
62
|
return `${pagged}${c.bold(c[color](highlighted))} ${time}`
|
|
53
63
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logux/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Build own Logux server or make proxy between WebSocket and HTTP backend on any language",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"logux",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"homepage": "https://logux.org/",
|
|
19
19
|
"repository": "logux/server",
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": "^
|
|
21
|
+
"node": "^18.0.0 || ^18.0.0 || >=22.0.0"
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"types": "./index.d.ts",
|
|
@@ -27,24 +27,24 @@
|
|
|
27
27
|
"./package.json": "./package.json"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@logux/actions": "^0.
|
|
31
|
-
"@logux/core": "^0.
|
|
30
|
+
"@logux/actions": "^0.4.0",
|
|
31
|
+
"@logux/core": "^0.9.0",
|
|
32
32
|
"JSONStream": "^1.3.5",
|
|
33
33
|
"cookie": "^0.6.0",
|
|
34
|
-
"dotenv": "^16.4.
|
|
34
|
+
"dotenv": "^16.4.5",
|
|
35
35
|
"fast-glob": "^3.3.2",
|
|
36
|
+
"fastq": "^1.17.1",
|
|
36
37
|
"nanodelay": "^2.0.2",
|
|
37
38
|
"nanoevents": "^9.0.0",
|
|
38
|
-
"nanoid": "^
|
|
39
|
+
"nanoid": "^5.0.7",
|
|
39
40
|
"netmask": "^2.0.2",
|
|
40
41
|
"node-fetch": "^3.3.2",
|
|
41
|
-
"picocolors": "^1.0.
|
|
42
|
-
"pino": "
|
|
43
|
-
"pino-abstract-transport": "^1.
|
|
44
|
-
"semver": "^7.6.
|
|
42
|
+
"picocolors": "^1.0.1",
|
|
43
|
+
"pino": "9.3.2",
|
|
44
|
+
"pino-abstract-transport": "^1.2.0",
|
|
45
|
+
"semver": "^7.6.3",
|
|
45
46
|
"strip-ansi": "^7.1.0",
|
|
46
47
|
"url-pattern": "^1.0.3",
|
|
47
|
-
"ws": "^8.
|
|
48
|
-
"yyyy-mm-dd": "^1.0.2"
|
|
48
|
+
"ws": "^8.18.0"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/server/index.js
CHANGED
package/server-client/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { LoguxError, parseId } from '@logux/core'
|
|
2
2
|
import cookie from 'cookie'
|
|
3
|
+
import fastq from 'fastq'
|
|
3
4
|
import semver from 'semver'
|
|
4
5
|
|
|
5
6
|
import { ALLOWED_META } from '../allowed-meta/index.js'
|
|
6
7
|
import { filterMeta } from '../filter-meta/index.js'
|
|
7
8
|
import { FilteredNode } from '../filtered-node/index.js'
|
|
8
9
|
|
|
10
|
+
async function onSend(action, meta) {
|
|
11
|
+
return [action, filterMeta(meta)]
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
function reportDetails(client) {
|
|
10
15
|
return {
|
|
11
16
|
connectionId: client.key,
|
|
@@ -14,6 +19,51 @@ function reportDetails(client) {
|
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
function denyBack(app, clientId, action, meta) {
|
|
23
|
+
app.emitter.emit('report', 'denied', { actionId: meta.id })
|
|
24
|
+
let [undoAction, undoMeta] = app.buildUndo(action, meta, 'denied')
|
|
25
|
+
undoMeta.clients = (undoMeta.clients || []).concat([clientId])
|
|
26
|
+
app.log.add(undoAction, undoMeta)
|
|
27
|
+
app.debugActionError(meta, `Action "${meta.id}" was denied`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function queueWorker(task, next) {
|
|
31
|
+
let { action, app, clientId, meta, onReceiveResolve, queue } = task
|
|
32
|
+
queue.next = next
|
|
33
|
+
|
|
34
|
+
let type = action.type
|
|
35
|
+
if (type === 'logux/subscribe' || type === 'logux/unsubscribe') {
|
|
36
|
+
return onReceiveResolve([action, meta])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let processor = app.getProcessor(type)
|
|
40
|
+
if (!processor) {
|
|
41
|
+
app.internalUnknownType(action, meta)
|
|
42
|
+
return onReceiveResolve(false)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let ctx = app.createContext(action, meta)
|
|
46
|
+
try {
|
|
47
|
+
let result = await processor.access(ctx, action, meta)
|
|
48
|
+
if (app.unknownTypes[meta.id]) {
|
|
49
|
+
delete app.unknownTypes[meta.id]
|
|
50
|
+
app.finally(processor, ctx, action, meta)
|
|
51
|
+
return false
|
|
52
|
+
} else if (!result) {
|
|
53
|
+
app.finally(processor, ctx, action, meta)
|
|
54
|
+
denyBack(app, clientId, action, meta)
|
|
55
|
+
return onReceiveResolve(false)
|
|
56
|
+
} else {
|
|
57
|
+
return onReceiveResolve([action, meta])
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
app.undo(action, meta, 'error')
|
|
61
|
+
app.emitter.emit('error', e, action, meta)
|
|
62
|
+
app.finally(processor, ctx, action, meta)
|
|
63
|
+
return onReceiveResolve(false)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
17
67
|
export class ServerClient {
|
|
18
68
|
constructor(app, connection, key) {
|
|
19
69
|
this.app = app
|
|
@@ -33,9 +83,8 @@ export class ServerClient {
|
|
|
33
83
|
|
|
34
84
|
this.node = new FilteredNode(this, app.nodeId, app.log, connection, {
|
|
35
85
|
auth: this.auth.bind(this),
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
outMap: this.outMap.bind(this),
|
|
86
|
+
onReceive: this.onReceive.bind(this),
|
|
87
|
+
onSend,
|
|
39
88
|
ping: app.options.ping,
|
|
40
89
|
subprotocol: app.options.subprotocol,
|
|
41
90
|
timeout: app.options.timeout
|
|
@@ -77,6 +126,7 @@ export class ServerClient {
|
|
|
77
126
|
}
|
|
78
127
|
|
|
79
128
|
if (nodeId === 'server' || userId === 'server') {
|
|
129
|
+
this.app.emitter.emit('unauthenticated', this, 0)
|
|
80
130
|
this.app.emitter.emit('report', 'unauthenticated', reportDetails(this))
|
|
81
131
|
return false
|
|
82
132
|
}
|
|
@@ -133,20 +183,13 @@ export class ServerClient {
|
|
|
133
183
|
this.app.emitter.emit('authenticated', this, Date.now() - start)
|
|
134
184
|
this.app.emitter.emit('report', 'authenticated', reportDetails(this))
|
|
135
185
|
} else {
|
|
186
|
+
this.app.emitter.emit('unauthenticated', this, Date.now() - start)
|
|
136
187
|
this.app.emitter.emit('report', 'unauthenticated', reportDetails(this))
|
|
137
188
|
this.app.rememberBadAuth(this.remoteAddress)
|
|
138
189
|
}
|
|
139
190
|
return result
|
|
140
191
|
}
|
|
141
192
|
|
|
142
|
-
denyBack(action, meta) {
|
|
143
|
-
this.app.emitter.emit('report', 'denied', { actionId: meta.id })
|
|
144
|
-
let [undoAction, undoMeta] = this.app.buildUndo(action, meta, 'denied')
|
|
145
|
-
undoMeta.clients = (undoMeta.clients || []).concat([this.clientId])
|
|
146
|
-
this.app.log.add(undoAction, undoMeta)
|
|
147
|
-
this.app.debugActionError(meta, `Action "${meta.id}" was denied`)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
193
|
destroy() {
|
|
151
194
|
this.destroyed = true
|
|
152
195
|
this.node.destroy()
|
|
@@ -162,9 +205,6 @@ export class ServerClient {
|
|
|
162
205
|
}
|
|
163
206
|
}
|
|
164
207
|
if (this.clientId) {
|
|
165
|
-
this.app.clientIds.delete(this.clientId)
|
|
166
|
-
this.app.nodeIds.delete(this.nodeId)
|
|
167
|
-
|
|
168
208
|
for (let channel in this.app.subscribers) {
|
|
169
209
|
let subscriber = this.app.subscribers[channel][this.nodeId]
|
|
170
210
|
if (subscriber) {
|
|
@@ -174,6 +214,8 @@ export class ServerClient {
|
|
|
174
214
|
this.app.performUnsubscribe(this.nodeId, action, meta)
|
|
175
215
|
}
|
|
176
216
|
}
|
|
217
|
+
this.app.clientIds.delete(this.clientId)
|
|
218
|
+
this.app.nodeIds.delete(this.nodeId)
|
|
177
219
|
}
|
|
178
220
|
if (!this.app.destroying) {
|
|
179
221
|
this.app.emitter.emit('disconnected', this)
|
|
@@ -181,61 +223,68 @@ export class ServerClient {
|
|
|
181
223
|
this.app.connected.delete(this.key)
|
|
182
224
|
}
|
|
183
225
|
|
|
184
|
-
|
|
185
|
-
|
|
226
|
+
isSubprotocol(range) {
|
|
227
|
+
return semver.satisfies(this.node.remoteSubprotocol, range)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
onReceive(action, meta) {
|
|
231
|
+
if (this.app.actionToQueue.has(meta.id)) {
|
|
232
|
+
return Promise.resolve(false)
|
|
233
|
+
}
|
|
186
234
|
|
|
187
|
-
let
|
|
235
|
+
let actionClientId = parseId(meta.id).clientId
|
|
236
|
+
let wrongUser = !this.clientId || this.clientId !== actionClientId
|
|
188
237
|
let wrongMeta = Object.keys(meta).some(i => !ALLOWED_META.includes(i))
|
|
189
238
|
if (wrongUser || wrongMeta) {
|
|
190
|
-
this.app.
|
|
191
|
-
|
|
192
|
-
return false
|
|
239
|
+
denyBack(this.app, this.clientId, action, meta)
|
|
240
|
+
return Promise.resolve(false)
|
|
193
241
|
}
|
|
194
242
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
243
|
+
return new Promise(resolve => {
|
|
244
|
+
let clientId = parseId(meta.id).clientId
|
|
245
|
+
let queueName = ''
|
|
199
246
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
247
|
+
let isChannel =
|
|
248
|
+
(action.type === 'logux/subscribe' ||
|
|
249
|
+
action.type === 'logux/unsubscribe') &&
|
|
250
|
+
action.channel
|
|
205
251
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
this.denyBack(action, meta)
|
|
215
|
-
return false
|
|
252
|
+
if (isChannel) {
|
|
253
|
+
for (let channel of this.app.channels) {
|
|
254
|
+
let pattern = channel.regexp || channel.pattern.regex
|
|
255
|
+
if (action.channel.match(pattern)) {
|
|
256
|
+
queueName = channel.queue
|
|
257
|
+
break
|
|
258
|
+
}
|
|
259
|
+
}
|
|
216
260
|
} else {
|
|
217
|
-
|
|
261
|
+
queueName = this.app.typeToQueue.get(action.type)
|
|
218
262
|
}
|
|
219
|
-
} catch (e) {
|
|
220
|
-
this.app.undo(action, meta, 'error')
|
|
221
|
-
this.app.emitter.emit('error', e, action, meta)
|
|
222
|
-
this.app.finally(processor, ctx, action, meta)
|
|
223
|
-
return false
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
263
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
return [action, meta]
|
|
232
|
-
}
|
|
264
|
+
queueName = queueName || 'main'
|
|
265
|
+
let queueKey = `${clientId}/${queueName}`
|
|
266
|
+
let queue = this.app.queues.get(queueKey)
|
|
233
267
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
268
|
+
if (!queue) {
|
|
269
|
+
queue = fastq(queueWorker, 1)
|
|
270
|
+
this.app.queues.set(queueKey, queue)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!meta.subprotocol) {
|
|
274
|
+
meta.subprotocol = this.node.remoteSubprotocol
|
|
275
|
+
}
|
|
237
276
|
|
|
238
|
-
|
|
239
|
-
|
|
277
|
+
this.app.actionToQueue.set(meta.id, queueKey)
|
|
278
|
+
queue.push({
|
|
279
|
+
action,
|
|
280
|
+
app: this.app,
|
|
281
|
+
clientId,
|
|
282
|
+
meta,
|
|
283
|
+
onReceiveResolve: result => {
|
|
284
|
+
resolve(result)
|
|
285
|
+
},
|
|
286
|
+
queue
|
|
287
|
+
})
|
|
288
|
+
})
|
|
240
289
|
}
|
|
241
290
|
}
|
package/test-client/index.js
CHANGED
package/vite.config.ts
CHANGED
|
@@ -3,9 +3,21 @@ import { defineConfig } from 'vitest/config'
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
5
|
coverage: {
|
|
6
|
-
exclude: [
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
exclude: [
|
|
7
|
+
'node_modules',
|
|
8
|
+
'server/index.js',
|
|
9
|
+
'test/*',
|
|
10
|
+
'**/*.d.ts',
|
|
11
|
+
'**/*.test.ts',
|
|
12
|
+
'*/errors.ts',
|
|
13
|
+
'*/types.ts',
|
|
14
|
+
'*.config.*',
|
|
15
|
+
'human-formatter'
|
|
16
|
+
],
|
|
17
|
+
provider: 'v8',
|
|
18
|
+
thresholds: {
|
|
19
|
+
lines: 100
|
|
20
|
+
}
|
|
9
21
|
},
|
|
10
22
|
environment: 'node',
|
|
11
23
|
exclude: ['node_modules', 'test/servers']
|