raffel 1.0.9 → 1.0.10-next.22f4d4e
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/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/websocket.d.ts +100 -1
- package/dist/adapters/websocket.d.ts.map +1 -1
- package/dist/adapters/websocket.js +428 -14
- package/dist/adapters/websocket.js.map +1 -1
- package/dist/channels/channel-manager.d.ts.map +1 -1
- package/dist/channels/channel-manager.js +265 -4
- package/dist/channels/channel-manager.js.map +1 -1
- package/dist/channels/history.d.ts +54 -0
- package/dist/channels/history.d.ts.map +1 -0
- package/dist/channels/history.js +78 -0
- package/dist/channels/history.js.map +1 -0
- package/dist/channels/index.d.ts +9 -1
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +8 -1
- package/dist/channels/index.js.map +1 -1
- package/dist/channels/recovery.d.ts +57 -0
- package/dist/channels/recovery.d.ts.map +1 -0
- package/dist/channels/recovery.js +58 -0
- package/dist/channels/recovery.js.map +1 -0
- package/dist/channels/rest-api.d.ts +32 -0
- package/dist/channels/rest-api.d.ts.map +1 -0
- package/dist/channels/rest-api.js +197 -0
- package/dist/channels/rest-api.js.map +1 -0
- package/dist/channels/ticket-store.d.ts +25 -0
- package/dist/channels/ticket-store.d.ts.map +1 -0
- package/dist/channels/ticket-store.js +70 -0
- package/dist/channels/ticket-store.js.map +1 -0
- package/dist/channels/types.d.ts +267 -5
- package/dist/channels/types.d.ts.map +1 -1
- package/dist/channels/types.js +15 -1
- package/dist/channels/types.js.map +1 -1
- package/dist/client/index.d.ts +39 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +531 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/reconnect.d.ts +33 -0
- package/dist/client/reconnect.d.ts.map +1 -0
- package/dist/client/reconnect.js +60 -0
- package/dist/client/reconnect.js.map +1 -0
- package/dist/client/types.d.ts +107 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +5 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/interceptors/field-filter.d.ts +51 -0
- package/dist/middleware/interceptors/field-filter.d.ts.map +1 -0
- package/dist/middleware/interceptors/field-filter.js +81 -0
- package/dist/middleware/interceptors/field-filter.js.map +1 -0
- package/dist/middleware/interceptors/guard.d.ts +51 -0
- package/dist/middleware/interceptors/guard.d.ts.map +1 -0
- package/dist/middleware/interceptors/guard.js +45 -0
- package/dist/middleware/interceptors/guard.js.map +1 -0
- package/dist/middleware/interceptors/index.d.ts +4 -0
- package/dist/middleware/interceptors/index.d.ts.map +1 -1
- package/dist/middleware/interceptors/index.js +4 -0
- package/dist/middleware/interceptors/index.js.map +1 -1
- package/dist/resource-module/index.d.ts +44 -0
- package/dist/resource-module/index.d.ts.map +1 -0
- package/dist/resource-module/index.js +146 -0
- package/dist/resource-module/index.js.map +1 -0
- package/dist/resource-module/types.d.ts +109 -0
- package/dist/resource-module/types.d.ts.map +1 -0
- package/dist/resource-module/types.js +7 -0
- package/dist/resource-module/types.js.map +1 -0
- package/dist/resource-module/watch-helpers.d.ts +16 -0
- package/dist/resource-module/watch-helpers.d.ts.map +1 -0
- package/dist/resource-module/watch-helpers.js +65 -0
- package/dist/resource-module/watch-helpers.js.map +1 -0
- package/dist/server/types.d.ts +27 -0
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +9 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel History
|
|
3
|
+
*
|
|
4
|
+
* Pluggable message history for channel catchup on reconnect.
|
|
5
|
+
* Stores recent messages per channel in a circular buffer.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create an in-memory history store with circular buffer per channel
|
|
9
|
+
*/
|
|
10
|
+
export function createMemoryHistoryStore(options = {}) {
|
|
11
|
+
const { maxSize = 100, ttl = 300_000, gcInterval = 30_000, } = options;
|
|
12
|
+
/** Per-channel circular buffer */
|
|
13
|
+
const buffers = new Map();
|
|
14
|
+
// Periodic GC for expired entries
|
|
15
|
+
const timer = setInterval(() => {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
for (const [channel, entries] of buffers) {
|
|
18
|
+
const filtered = entries.filter((e) => now - e.timestamp < ttl);
|
|
19
|
+
if (filtered.length === 0) {
|
|
20
|
+
buffers.delete(channel);
|
|
21
|
+
}
|
|
22
|
+
else if (filtered.length < entries.length) {
|
|
23
|
+
buffers.set(channel, filtered);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, gcInterval);
|
|
27
|
+
if (timer.unref)
|
|
28
|
+
timer.unref();
|
|
29
|
+
return {
|
|
30
|
+
append(entry) {
|
|
31
|
+
let buffer = buffers.get(entry.channel);
|
|
32
|
+
if (!buffer) {
|
|
33
|
+
buffer = [];
|
|
34
|
+
buffers.set(entry.channel, buffer);
|
|
35
|
+
}
|
|
36
|
+
buffer.push(entry);
|
|
37
|
+
// Circular: trim to maxSize
|
|
38
|
+
if (buffer.length > maxSize) {
|
|
39
|
+
buffers.set(entry.channel, buffer.slice(buffer.length - maxSize));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
getAfterSeq(channel, sinceSeq, epoch, limit) {
|
|
43
|
+
const buffer = buffers.get(channel);
|
|
44
|
+
if (!buffer || buffer.length === 0)
|
|
45
|
+
return [];
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const results = [];
|
|
48
|
+
for (const entry of buffer) {
|
|
49
|
+
// Must match epoch
|
|
50
|
+
if (entry.epoch !== epoch)
|
|
51
|
+
continue;
|
|
52
|
+
// Must be after sinceSeq
|
|
53
|
+
if (entry.seq <= sinceSeq)
|
|
54
|
+
continue;
|
|
55
|
+
// Must not be expired
|
|
56
|
+
if (now - entry.timestamp >= ttl)
|
|
57
|
+
continue;
|
|
58
|
+
results.push(entry);
|
|
59
|
+
if (limit && results.length >= limit)
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
},
|
|
64
|
+
trim(channel, trimSize) {
|
|
65
|
+
const buffer = buffers.get(channel);
|
|
66
|
+
if (!buffer)
|
|
67
|
+
return;
|
|
68
|
+
if (buffer.length > trimSize) {
|
|
69
|
+
buffers.set(channel, buffer.slice(buffer.length - trimSize));
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
dispose() {
|
|
73
|
+
clearInterval(timer);
|
|
74
|
+
buffers.clear();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/channels/history.ts"],"names":[],"mappings":"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"}
|
package/dist/channels/index.d.ts
CHANGED
|
@@ -43,5 +43,13 @@
|
|
|
43
43
|
*/
|
|
44
44
|
export { createChannelManager } from './channel-manager.js';
|
|
45
45
|
export type { SendToSocketFn } from './channel-manager.js';
|
|
46
|
-
export {
|
|
46
|
+
export { createMemoryTicketStore, generateTicket } from './ticket-store.js';
|
|
47
|
+
export type { MemoryTicketStoreOptions } from './ticket-store.js';
|
|
48
|
+
export { createMemoryHistoryStore } from './history.js';
|
|
49
|
+
export type { ChannelHistoryEntry, ChannelHistoryPort, MemoryHistoryStoreOptions, } from './history.js';
|
|
50
|
+
export { createMemoryRecoveryStore, generateRecoveryToken } from './recovery.js';
|
|
51
|
+
export type { RecoverableSession, ConnectionRecoveryPort, MemoryRecoveryStoreOptions, } from './recovery.js';
|
|
52
|
+
export { createChannelRestApi } from './rest-api.js';
|
|
53
|
+
export type { ChannelRestApiOptions } from './rest-api.js';
|
|
54
|
+
export { type ChannelType, type ChannelOptions, type ChannelMember, type ChannelState, type ChannelManager, type SubscribeResult, type ClientInfo, type RoomInfo, type GroupInfo, type ChannelLifecycleHooks, type ClientConnectEvent, type ClientDisconnectEvent, type ConnectionTicket, type TicketStore, type WebSocketAuthConfig, type ChannelRateLimits, type BackpressureConfig, type AuthRefreshMessage, type AuthRefreshedMessage, type RecoverMessage, type SubscribeMessage, type SubscribedMessage, type UnsubscribeMessage, type UnsubscribedMessage, type PublishMessage, type ChannelEventMessage, type ChannelErrorMessage, type ChannelMessage, type BatchSubscribeMessage, type BatchSubscribedMessage, type BatchPublishMessage, type TypingMessage, isChannelMessage, isRecoverMessage, getChannelType, requiresAuth, } from './types.js';
|
|
47
55
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAC3E,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAGjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AACvD,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAChF,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EAGnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAGnB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EAGxB,KAAK,aAAa,EAGlB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,YAAY,GACb,MAAM,YAAY,CAAA"}
|
package/dist/channels/index.js
CHANGED
|
@@ -42,7 +42,14 @@
|
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
export { createChannelManager } from './channel-manager.js';
|
|
45
|
+
export { createMemoryTicketStore, generateTicket } from './ticket-store.js';
|
|
46
|
+
// History
|
|
47
|
+
export { createMemoryHistoryStore } from './history.js';
|
|
48
|
+
// Recovery
|
|
49
|
+
export { createMemoryRecoveryStore, generateRecoveryToken } from './recovery.js';
|
|
50
|
+
// REST API
|
|
51
|
+
export { createChannelRestApi } from './rest-api.js';
|
|
45
52
|
export {
|
|
46
53
|
// Helpers
|
|
47
|
-
isChannelMessage, getChannelType, requiresAuth, } from './types.js';
|
|
54
|
+
isChannelMessage, isRecoverMessage, getChannelType, requiresAuth, } from './types.js';
|
|
48
55
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAE3D,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAG3E,UAAU;AACV,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAOvD,WAAW;AACX,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAOhF,WAAW;AACX,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAGpD,OAAO;AAyCL,UAAU;AACV,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,YAAY,GACb,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection State Recovery
|
|
3
|
+
*
|
|
4
|
+
* Saves session state on disconnect and restores on reconnect,
|
|
5
|
+
* allowing clients to recover subscriptions and replay missed messages.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Saved session state for recovery
|
|
9
|
+
*/
|
|
10
|
+
export interface RecoverableSession {
|
|
11
|
+
/** Opaque recovery token given to the client */
|
|
12
|
+
recoveryToken: string;
|
|
13
|
+
/** Original socket ID */
|
|
14
|
+
socketId: string;
|
|
15
|
+
/** User ID (if authenticated) */
|
|
16
|
+
userId?: string;
|
|
17
|
+
/** Channels the client was subscribed to, with last seen seq */
|
|
18
|
+
channels: {
|
|
19
|
+
name: string;
|
|
20
|
+
lastSeq: number;
|
|
21
|
+
}[];
|
|
22
|
+
/** Groups the client belonged to */
|
|
23
|
+
groups: string[];
|
|
24
|
+
/** Custom metadata */
|
|
25
|
+
metadata: Record<string, unknown>;
|
|
26
|
+
/** When this session expires */
|
|
27
|
+
expiresAt: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Port interface for pluggable recovery storage
|
|
31
|
+
*/
|
|
32
|
+
export interface ConnectionRecoveryPort {
|
|
33
|
+
/** Save a recoverable session */
|
|
34
|
+
save(session: RecoverableSession): void;
|
|
35
|
+
/** Get a session by recovery token */
|
|
36
|
+
get(recoveryToken: string): RecoverableSession | null;
|
|
37
|
+
/** Delete a session (consumed or expired) */
|
|
38
|
+
delete(recoveryToken: string): void;
|
|
39
|
+
}
|
|
40
|
+
export interface MemoryRecoveryStoreOptions {
|
|
41
|
+
/** TTL in ms for recovery sessions (default: 120000 = 2 minutes) */
|
|
42
|
+
ttl?: number;
|
|
43
|
+
/** GC interval in ms (default: 30000) */
|
|
44
|
+
gcInterval?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create an in-memory recovery store with TTL and periodic GC
|
|
48
|
+
*/
|
|
49
|
+
export declare function createMemoryRecoveryStore(options?: MemoryRecoveryStoreOptions): ConnectionRecoveryPort & {
|
|
50
|
+
dispose(): void;
|
|
51
|
+
size: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Generate a recovery token
|
|
55
|
+
*/
|
|
56
|
+
export declare function generateRecoveryToken(): string;
|
|
57
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/channels/recovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAA;IACrB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC7C,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,iCAAiC;IACjC,IAAI,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACvC,sCAAsC;IACtC,GAAG,CAAC,aAAa,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAA;IACrD,6CAA6C;IAC7C,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;AAID,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,0BAA+B,GACvC,sBAAsB,GAAG;IAAE,OAAO,IAAI,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAmD5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection State Recovery
|
|
3
|
+
*
|
|
4
|
+
* Saves session state on disconnect and restores on reconnect,
|
|
5
|
+
* allowing clients to recover subscriptions and replay missed messages.
|
|
6
|
+
*/
|
|
7
|
+
import { sid } from '../utils/id/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create an in-memory recovery store with TTL and periodic GC
|
|
10
|
+
*/
|
|
11
|
+
export function createMemoryRecoveryStore(options = {}) {
|
|
12
|
+
const { ttl = 120_000, gcInterval = 30_000, } = options;
|
|
13
|
+
const sessions = new Map();
|
|
14
|
+
// Periodic GC for expired sessions
|
|
15
|
+
const timer = setInterval(() => {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
for (const [token, session] of sessions) {
|
|
18
|
+
if (session.expiresAt < now) {
|
|
19
|
+
sessions.delete(token);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}, gcInterval);
|
|
23
|
+
if (timer.unref)
|
|
24
|
+
timer.unref();
|
|
25
|
+
return {
|
|
26
|
+
save(session) {
|
|
27
|
+
sessions.set(session.recoveryToken, session);
|
|
28
|
+
},
|
|
29
|
+
get(recoveryToken) {
|
|
30
|
+
const session = sessions.get(recoveryToken);
|
|
31
|
+
if (!session)
|
|
32
|
+
return null;
|
|
33
|
+
// Check expiration
|
|
34
|
+
if (session.expiresAt < Date.now()) {
|
|
35
|
+
sessions.delete(recoveryToken);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return session;
|
|
39
|
+
},
|
|
40
|
+
delete(recoveryToken) {
|
|
41
|
+
sessions.delete(recoveryToken);
|
|
42
|
+
},
|
|
43
|
+
dispose() {
|
|
44
|
+
clearInterval(timer);
|
|
45
|
+
sessions.clear();
|
|
46
|
+
},
|
|
47
|
+
get size() {
|
|
48
|
+
return sessions.size;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate a recovery token
|
|
54
|
+
*/
|
|
55
|
+
export function generateRecoveryToken() {
|
|
56
|
+
return `rec_${sid()}`;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/channels/recovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AA6C1C;;GAEG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAAsC,EAAE;IAExC,MAAM,EACJ,GAAG,GAAG,OAAO,EACb,UAAU,GAAG,MAAM,GACpB,GAAG,OAAO,CAAA;IAEX,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAA;IAEtD,mCAAmC;IACnC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,QAAQ,EAAE,CAAC;YACxC,IAAI,OAAO,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;gBAC5B,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACxB,CAAC;QACH,CAAC;IACH,CAAC,EAAE,UAAU,CAAC,CAAA;IAEd,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,KAAK,EAAE,CAAA;IAE9B,OAAO;QACL,IAAI,CAAC,OAA2B;YAC9B,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;QAC9C,CAAC;QAED,GAAG,CAAC,aAAqB;YACvB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;YAC3C,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAA;YAEzB,mBAAmB;YACnB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACnC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;gBAC9B,OAAO,IAAI,CAAA;YACb,CAAC;YAED,OAAO,OAAO,CAAA;QAChB,CAAC;QAED,MAAM,CAAC,aAAqB;YAC1B,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;QAChC,CAAC;QAED,OAAO;YACL,aAAa,CAAC,KAAK,CAAC,CAAA;YACpB,QAAQ,CAAC,KAAK,EAAE,CAAA;QAClB,CAAC;QAED,IAAI,IAAI;YACN,OAAO,QAAQ,CAAC,IAAI,CAAA;QACtB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,OAAO,GAAG,EAAE,EAAE,CAAA;AACvB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel REST API
|
|
3
|
+
*
|
|
4
|
+
* HTTP middleware for server-side channel management.
|
|
5
|
+
* Allows publishing events, listing channels, and sending
|
|
6
|
+
* messages to specific clients via HTTP endpoints.
|
|
7
|
+
*/
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
9
|
+
import type { ChannelManager } from './types.js';
|
|
10
|
+
export interface ChannelRestApiOptions {
|
|
11
|
+
/** Base path for REST endpoints (default: '/channels') */
|
|
12
|
+
path?: string;
|
|
13
|
+
/** API key for authentication */
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/** Custom auth function (overrides apiKey) */
|
|
16
|
+
auth?: (req: IncomingMessage) => boolean | Promise<boolean>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create HTTP middleware for channel REST API.
|
|
20
|
+
*
|
|
21
|
+
* Returns an `HttpMiddleware` compatible with the Raffel adapter layer:
|
|
22
|
+
* `(req, res) => boolean | Promise<boolean>` — returns true if handled.
|
|
23
|
+
*
|
|
24
|
+
* Endpoints:
|
|
25
|
+
* - POST /<path>/:channel/events — broadcast to channel
|
|
26
|
+
* - GET /<path> — list active channels
|
|
27
|
+
* - GET /<path>/:channel — channel info
|
|
28
|
+
* - POST /<path>/clients/:socketId/events — send to specific client
|
|
29
|
+
* - POST /<path>/broadcast — broadcast to all
|
|
30
|
+
*/
|
|
31
|
+
export declare function createChannelRestApi(manager: ChannelManager, options?: ChannelRestApiOptions): (req: IncomingMessage, res: ServerResponse) => boolean | Promise<boolean>;
|
|
32
|
+
//# sourceMappingURL=rest-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../../src/channels/rest-api.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAGhD,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8CAA8C;IAC9C,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,qBAA0B,GAClC,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA4M3E"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel REST API
|
|
3
|
+
*
|
|
4
|
+
* HTTP middleware for server-side channel management.
|
|
5
|
+
* Allows publishing events, listing channels, and sending
|
|
6
|
+
* messages to specific clients via HTTP endpoints.
|
|
7
|
+
*/
|
|
8
|
+
import { getChannelType } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create HTTP middleware for channel REST API.
|
|
11
|
+
*
|
|
12
|
+
* Returns an `HttpMiddleware` compatible with the Raffel adapter layer:
|
|
13
|
+
* `(req, res) => boolean | Promise<boolean>` — returns true if handled.
|
|
14
|
+
*
|
|
15
|
+
* Endpoints:
|
|
16
|
+
* - POST /<path>/:channel/events — broadcast to channel
|
|
17
|
+
* - GET /<path> — list active channels
|
|
18
|
+
* - GET /<path>/:channel — channel info
|
|
19
|
+
* - POST /<path>/clients/:socketId/events — send to specific client
|
|
20
|
+
* - POST /<path>/broadcast — broadcast to all
|
|
21
|
+
*/
|
|
22
|
+
export function createChannelRestApi(manager, options = {}) {
|
|
23
|
+
const basePath = (options.path ?? '/channels').replace(/\/+$/, '');
|
|
24
|
+
/**
|
|
25
|
+
* Check authorization
|
|
26
|
+
*/
|
|
27
|
+
async function checkAuth(req, res) {
|
|
28
|
+
if (options.auth) {
|
|
29
|
+
const allowed = await options.auth(req);
|
|
30
|
+
if (!allowed) {
|
|
31
|
+
sendJson(res, 401, { error: 'Unauthorized' });
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (options.apiKey) {
|
|
37
|
+
const auth = req.headers['authorization'];
|
|
38
|
+
if (auth !== `Bearer ${options.apiKey}`) {
|
|
39
|
+
sendJson(res, 401, { error: 'Invalid API key' });
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
// No auth configured — allow all
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function sendJson(res, status, body) {
|
|
48
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
49
|
+
res.end(JSON.stringify(body));
|
|
50
|
+
}
|
|
51
|
+
function readBody(req) {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
55
|
+
req.on('end', () => {
|
|
56
|
+
try {
|
|
57
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
58
|
+
resolve(raw ? JSON.parse(raw) : {});
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
reject(err);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
req.on('error', reject);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse URL pathname and extract route match
|
|
69
|
+
*/
|
|
70
|
+
function matchRoute(pathname, method) {
|
|
71
|
+
// Normalize
|
|
72
|
+
const path = pathname.replace(/\/+$/, '') || '/';
|
|
73
|
+
// POST /channels/broadcast
|
|
74
|
+
if (method === 'POST' && path === `${basePath}/broadcast`) {
|
|
75
|
+
return { route: 'broadcast' };
|
|
76
|
+
}
|
|
77
|
+
// POST /channels/clients/:socketId/events
|
|
78
|
+
const clientEventsMatch = path.match(new RegExp(`^${escapeRegExp(basePath)}/clients/([^/]+)/events$`));
|
|
79
|
+
if (method === 'POST' && clientEventsMatch) {
|
|
80
|
+
return { route: 'client-events', socketId: clientEventsMatch[1] };
|
|
81
|
+
}
|
|
82
|
+
// POST /channels/:channel/events
|
|
83
|
+
const channelEventsMatch = path.match(new RegExp(`^${escapeRegExp(basePath)}/([^/]+)/events$`));
|
|
84
|
+
if (method === 'POST' && channelEventsMatch) {
|
|
85
|
+
return { route: 'channel-events', channel: decodeURIComponent(channelEventsMatch[1]) };
|
|
86
|
+
}
|
|
87
|
+
// GET /channels/:channel
|
|
88
|
+
const channelInfoMatch = path.match(new RegExp(`^${escapeRegExp(basePath)}/([^/]+)$`));
|
|
89
|
+
if (method === 'GET' && channelInfoMatch) {
|
|
90
|
+
// Exclude reserved sub-paths
|
|
91
|
+
const segment = channelInfoMatch[1];
|
|
92
|
+
if (segment === 'broadcast' || segment === 'clients') {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return { route: 'channel-info', channel: decodeURIComponent(segment) };
|
|
96
|
+
}
|
|
97
|
+
// GET /channels
|
|
98
|
+
if (method === 'GET' && path === basePath) {
|
|
99
|
+
return { route: 'list-channels' };
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return async function channelRestApiMiddleware(req, res) {
|
|
104
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
105
|
+
const method = req.method?.toUpperCase() ?? 'GET';
|
|
106
|
+
const match = matchRoute(url.pathname, method);
|
|
107
|
+
if (!match)
|
|
108
|
+
return false;
|
|
109
|
+
// Auth check
|
|
110
|
+
const authed = await checkAuth(req, res);
|
|
111
|
+
if (!authed)
|
|
112
|
+
return true; // Already sent 401
|
|
113
|
+
try {
|
|
114
|
+
if (match.route === 'list-channels') {
|
|
115
|
+
const channelNames = manager.getChannels();
|
|
116
|
+
const result = channelNames.map((name) => ({
|
|
117
|
+
name,
|
|
118
|
+
type: getChannelType(name),
|
|
119
|
+
subscriberCount: manager.getSubscriberCount(name),
|
|
120
|
+
}));
|
|
121
|
+
sendJson(res, 200, { channels: result });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (match.route === 'channel-info') {
|
|
125
|
+
const name = match.channel;
|
|
126
|
+
if (!manager.hasChannel(name)) {
|
|
127
|
+
sendJson(res, 404, { error: 'Channel not found' });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
const type = getChannelType(name);
|
|
131
|
+
const info = {
|
|
132
|
+
name,
|
|
133
|
+
type,
|
|
134
|
+
subscriberCount: manager.getSubscriberCount(name),
|
|
135
|
+
subscribers: manager.getSubscribers(name),
|
|
136
|
+
};
|
|
137
|
+
if (type === 'presence') {
|
|
138
|
+
info.members = manager.getMembers(name);
|
|
139
|
+
}
|
|
140
|
+
sendJson(res, 200, info);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (match.route === 'channel-events') {
|
|
144
|
+
const body = (await readBody(req));
|
|
145
|
+
const event = body.event;
|
|
146
|
+
const data = body.data;
|
|
147
|
+
const except = body.except;
|
|
148
|
+
if (!event) {
|
|
149
|
+
sendJson(res, 400, { error: 'Missing event field' });
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
manager.broadcast(match.channel, event, data, except);
|
|
153
|
+
sendJson(res, 200, { ok: true });
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (match.route === 'client-events') {
|
|
157
|
+
const body = (await readBody(req));
|
|
158
|
+
const event = body.event;
|
|
159
|
+
const data = body.data;
|
|
160
|
+
if (!event) {
|
|
161
|
+
sendJson(res, 400, { error: 'Missing event field' });
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
const client = manager.getClient(match.socketId);
|
|
165
|
+
if (!client) {
|
|
166
|
+
sendJson(res, 404, { error: 'Client not found' });
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
manager.sendToClient(match.socketId, event, data);
|
|
170
|
+
sendJson(res, 200, { ok: true });
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (match.route === 'broadcast') {
|
|
174
|
+
const body = (await readBody(req));
|
|
175
|
+
const event = body.event;
|
|
176
|
+
const data = body.data;
|
|
177
|
+
const except = body.except;
|
|
178
|
+
if (!event) {
|
|
179
|
+
sendJson(res, 400, { error: 'Missing event field' });
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
manager.broadcastAll(event, data, except);
|
|
183
|
+
sendJson(res, 200, { ok: true });
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
sendJson(res, 500, { error: 'Internal server error' });
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function escapeRegExp(s) {
|
|
195
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=rest-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-api.js","sourceRoot":"","sources":["../../src/channels/rest-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Ticket Store
|
|
3
|
+
*
|
|
4
|
+
* Default implementation of TicketStore for connection ticket auth.
|
|
5
|
+
* Tickets are single-use, short-lived tokens generated via HTTP
|
|
6
|
+
* and consumed on WebSocket connect.
|
|
7
|
+
*/
|
|
8
|
+
import type { ConnectionTicket, TicketStore } from './types.js';
|
|
9
|
+
export interface MemoryTicketStoreOptions {
|
|
10
|
+
/** GC interval in ms (default: 10000) */
|
|
11
|
+
gcInterval?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function createMemoryTicketStore(options?: MemoryTicketStoreOptions): TicketStore & {
|
|
14
|
+
dispose(): void;
|
|
15
|
+
size: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Helper: generate a ticket for a user
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateTicket(userId: string, options?: {
|
|
21
|
+
ttl?: number;
|
|
22
|
+
permissions?: string[];
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
}): ConnectionTicket;
|
|
25
|
+
//# sourceMappingURL=ticket-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket-store.d.ts","sourceRoot":"","sources":["../../src/channels/ticket-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE/D,MAAM,WAAW,wBAAwB;IACvC,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,WAAW,GAAG;IAAE,OAAO,IAAI,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAuDjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,GACA,gBAAgB,CASlB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Ticket Store
|
|
3
|
+
*
|
|
4
|
+
* Default implementation of TicketStore for connection ticket auth.
|
|
5
|
+
* Tickets are single-use, short-lived tokens generated via HTTP
|
|
6
|
+
* and consumed on WebSocket connect.
|
|
7
|
+
*/
|
|
8
|
+
import { sid } from '../utils/id/index.js';
|
|
9
|
+
export function createMemoryTicketStore(options = {}) {
|
|
10
|
+
const { gcInterval = 10_000 } = options;
|
|
11
|
+
const tickets = new Map();
|
|
12
|
+
// Periodic GC for expired tickets
|
|
13
|
+
const timer = setInterval(() => {
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
for (const [id, ticket] of tickets) {
|
|
16
|
+
if (ticket.expiresAt < now || ticket.usedAt) {
|
|
17
|
+
tickets.delete(id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}, gcInterval);
|
|
21
|
+
if (timer.unref)
|
|
22
|
+
timer.unref();
|
|
23
|
+
return {
|
|
24
|
+
async create(ticket) {
|
|
25
|
+
tickets.set(ticket.id, { ...ticket });
|
|
26
|
+
},
|
|
27
|
+
async consume(ticketId) {
|
|
28
|
+
const ticket = tickets.get(ticketId);
|
|
29
|
+
if (!ticket)
|
|
30
|
+
return null;
|
|
31
|
+
// Check expiration
|
|
32
|
+
if (ticket.expiresAt < Date.now()) {
|
|
33
|
+
tickets.delete(ticketId);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
// Check single-use
|
|
37
|
+
if (ticket.usedAt) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Mark as used and return
|
|
41
|
+
ticket.usedAt = Date.now();
|
|
42
|
+
tickets.delete(ticketId); // Remove after consumption
|
|
43
|
+
return ticket;
|
|
44
|
+
},
|
|
45
|
+
async revoke(ticketId) {
|
|
46
|
+
tickets.delete(ticketId);
|
|
47
|
+
},
|
|
48
|
+
dispose() {
|
|
49
|
+
clearInterval(timer);
|
|
50
|
+
tickets.clear();
|
|
51
|
+
},
|
|
52
|
+
get size() {
|
|
53
|
+
return tickets.size;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Helper: generate a ticket for a user
|
|
59
|
+
*/
|
|
60
|
+
export function generateTicket(userId, options) {
|
|
61
|
+
const ttl = options?.ttl ?? 30_000;
|
|
62
|
+
return {
|
|
63
|
+
id: sid(),
|
|
64
|
+
userId,
|
|
65
|
+
permissions: options?.permissions,
|
|
66
|
+
expiresAt: Date.now() + ttl,
|
|
67
|
+
metadata: options?.metadata,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ticket-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticket-store.js","sourceRoot":"","sources":["../../src/channels/ticket-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAQ1C,MAAM,UAAU,uBAAuB,CACrC,UAAoC,EAAE;IAEtC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,OAAO,CAAA;IACvC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAA;IAEnD,kCAAkC;IAClC,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACnC,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC5C,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACpB,CAAC;QACH,CAAC;IACH,CAAC,EAAE,UAAU,CAAC,CAAA;IAEd,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,KAAK,EAAE,CAAA;IAE9B,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,MAAwB;YACnC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;QACvC,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,QAAgB;YAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACpC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAA;YAExB,mBAAmB;YACnB,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBAClC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;gBACxB,OAAO,IAAI,CAAA;YACb,CAAC;YAED,mBAAmB;YACnB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,IAAI,CAAA;YACb,CAAC;YAED,0BAA0B;YAC1B,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAC1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA,CAAC,2BAA2B;YACpD,OAAO,MAAM,CAAA;QACf,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,QAAgB;YAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;QAED,OAAO;YACL,aAAa,CAAC,KAAK,CAAC,CAAA;YACpB,OAAO,CAAC,KAAK,EAAE,CAAA;QACjB,CAAC;QAED,IAAI,IAAI;YACN,OAAO,OAAO,CAAC,IAAI,CAAA;QACrB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,OAIC;IAED,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,MAAM,CAAA;IAClC,OAAO;QACL,EAAE,EAAE,GAAG,EAAE;QACT,MAAM;QACN,WAAW,EAAE,OAAO,EAAE,WAAW;QACjC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG;QAC3B,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAA;AACH,CAAC"}
|