@animalabs/portal-client 0.1.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/dist/src/cache.d.ts +22 -0
- package/dist/src/cache.d.ts.map +1 -0
- package/dist/src/cache.js +69 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/client.d.ts +93 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +230 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/emitter.d.ts +8 -0
- package/dist/src/emitter.d.ts.map +1 -0
- package/dist/src/emitter.js +20 -0
- package/dist/src/emitter.js.map +1 -0
- package/dist/src/enroll.d.ts +47 -0
- package/dist/src/enroll.d.ts.map +1 -0
- package/dist/src/enroll.js +96 -0
- package/dist/src/enroll.js.map +1 -0
- package/dist/src/files.d.ts +10 -0
- package/dist/src/files.d.ts.map +1 -0
- package/dist/src/files.js +13 -0
- package/dist/src/files.js.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +13 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test/enroll.test.d.ts +2 -0
- package/dist/test/enroll.test.d.ts.map +1 -0
- package/dist/test/enroll.test.js +75 -0
- package/dist/test/enroll.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +27 -0
- package/src/cache.ts +83 -0
- package/src/client.ts +281 -0
- package/src/emitter.ts +21 -0
- package/src/enroll.ts +126 -0
- package/src/files.ts +18 -0
- package/src/index.ts +14 -0
- package/test/enroll.test.ts +87 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side cache, hydrated from `ready` and kept fresh by dispatch events.
|
|
3
|
+
* Provides synchronous getters so callers (and the MCPL layer) can resolve
|
|
4
|
+
* names/ids and capabilities without a round-trip.
|
|
5
|
+
*/
|
|
6
|
+
import type { PortalChannel, PortalEvent, PortalGuild, Persona, ReadyData } from '@animalabs/portal-protocol';
|
|
7
|
+
export declare class ClientCache {
|
|
8
|
+
persona?: Persona;
|
|
9
|
+
private guilds;
|
|
10
|
+
private channels;
|
|
11
|
+
hydrate(ready: ReadyData): void;
|
|
12
|
+
/** Apply structural events; returns true if the cache changed. */
|
|
13
|
+
apply(event: PortalEvent): boolean;
|
|
14
|
+
getGuild(id: string): PortalGuild | undefined;
|
|
15
|
+
getChannel(id: string): PortalChannel | undefined;
|
|
16
|
+
allGuilds(): PortalGuild[];
|
|
17
|
+
allChannels(): PortalChannel[];
|
|
18
|
+
/** Threads + channels under a parent. */
|
|
19
|
+
childrenOf(parentId: string): PortalChannel[];
|
|
20
|
+
findChannelByName(name: string, guildId?: string): PortalChannel | undefined;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,OAAO,EACP,SAAS,EACV,MAAM,4BAA4B,CAAC;AAEpC,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,QAAQ,CAAoC;IAEpD,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAQ/B,kEAAkE;IAClE,KAAK,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAkClC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAG7C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAGjD,SAAS,IAAI,WAAW,EAAE;IAG1B,WAAW,IAAI,aAAa,EAAE;IAG9B,yCAAyC;IACzC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE;IAG7C,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;CAK7E"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export class ClientCache {
|
|
2
|
+
persona;
|
|
3
|
+
guilds = new Map();
|
|
4
|
+
channels = new Map();
|
|
5
|
+
hydrate(ready) {
|
|
6
|
+
this.persona = ready.persona;
|
|
7
|
+
this.guilds.clear();
|
|
8
|
+
this.channels.clear();
|
|
9
|
+
for (const g of ready.guilds)
|
|
10
|
+
this.guilds.set(g.id, g);
|
|
11
|
+
for (const c of ready.channels)
|
|
12
|
+
this.channels.set(c.id, c);
|
|
13
|
+
}
|
|
14
|
+
/** Apply structural events; returns true if the cache changed. */
|
|
15
|
+
apply(event) {
|
|
16
|
+
switch (event.type) {
|
|
17
|
+
case 'channel_create':
|
|
18
|
+
case 'channel_update':
|
|
19
|
+
case 'thread_create':
|
|
20
|
+
case 'thread_update':
|
|
21
|
+
this.channels.set(event.channel.id, event.channel);
|
|
22
|
+
return true;
|
|
23
|
+
case 'channel_delete':
|
|
24
|
+
return this.channels.delete(event.channelId);
|
|
25
|
+
case 'thread_delete':
|
|
26
|
+
return this.channels.delete(event.channelId);
|
|
27
|
+
case 'guild_create':
|
|
28
|
+
this.guilds.set(event.guild.id, event.guild);
|
|
29
|
+
for (const c of event.channels)
|
|
30
|
+
this.channels.set(c.id, c);
|
|
31
|
+
return true;
|
|
32
|
+
case 'guild_delete':
|
|
33
|
+
return this.guilds.delete(event.guildId);
|
|
34
|
+
case 'persona_update':
|
|
35
|
+
this.persona = event.persona;
|
|
36
|
+
return true;
|
|
37
|
+
case 'capabilities_update': {
|
|
38
|
+
const ch = this.channels.get(event.channelId);
|
|
39
|
+
if (ch) {
|
|
40
|
+
this.channels.set(event.channelId, { ...ch, capabilities: event.capabilities });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
getGuild(id) {
|
|
50
|
+
return this.guilds.get(id);
|
|
51
|
+
}
|
|
52
|
+
getChannel(id) {
|
|
53
|
+
return this.channels.get(id);
|
|
54
|
+
}
|
|
55
|
+
allGuilds() {
|
|
56
|
+
return [...this.guilds.values()];
|
|
57
|
+
}
|
|
58
|
+
allChannels() {
|
|
59
|
+
return [...this.channels.values()];
|
|
60
|
+
}
|
|
61
|
+
/** Threads + channels under a parent. */
|
|
62
|
+
childrenOf(parentId) {
|
|
63
|
+
return [...this.channels.values()].filter((c) => c.parentId === parentId);
|
|
64
|
+
}
|
|
65
|
+
findChannelByName(name, guildId) {
|
|
66
|
+
return [...this.channels.values()].find((c) => c.name === name && (!guildId || c.guildId === guildId));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PortalClient — WS transport + cache + typed RPC + reconnect/resume.
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle: connect → hello → identify → ready. On an unexpected close it
|
|
5
|
+
* reconnects and `resume`s from the last seq; a non-resumable rejection falls
|
|
6
|
+
* back to a fresh identify. The agent-facing read watermark is NOT here — that
|
|
7
|
+
* lives in portal-mcpl; this layer only does transport-level resume.
|
|
8
|
+
*/
|
|
9
|
+
import { WebSocket } from 'ws';
|
|
10
|
+
import { type AddressReason, type PortalChannel, type PortalEvent, type PortalMessage, type ReadyData, type RpcMethod, type RpcParams, type RpcResult } from '@animalabs/portal-protocol';
|
|
11
|
+
import { ClientCache } from './cache.js';
|
|
12
|
+
import { TypedEmitter } from './emitter.js';
|
|
13
|
+
export interface PortalClientOptions {
|
|
14
|
+
url: string;
|
|
15
|
+
token: string;
|
|
16
|
+
personaId: string;
|
|
17
|
+
/** Ambient channel subscriptions to request on connect. */
|
|
18
|
+
subscriptions?: string[];
|
|
19
|
+
/** RPC timeout in ms (default 15000). */
|
|
20
|
+
rpcTimeoutMs?: number;
|
|
21
|
+
/** Max reconnect backoff in ms (default 30000). */
|
|
22
|
+
maxBackoffMs?: number;
|
|
23
|
+
/** Provide a WebSocket impl (tests). Defaults to ws. */
|
|
24
|
+
wsFactory?: (url: string) => WebSocket;
|
|
25
|
+
}
|
|
26
|
+
type MessageEvent = {
|
|
27
|
+
message: PortalMessage;
|
|
28
|
+
addressedToMe: boolean;
|
|
29
|
+
reasons: AddressReason[];
|
|
30
|
+
};
|
|
31
|
+
export interface PortalClientEvents extends Record<string, (...args: never[]) => void> {
|
|
32
|
+
ready: (data: ReadyData) => void;
|
|
33
|
+
resumed: (replayed: number) => void;
|
|
34
|
+
message: (e: MessageEvent) => void;
|
|
35
|
+
messageUpdate: (e: MessageEvent) => void;
|
|
36
|
+
messageDelete: (e: {
|
|
37
|
+
channelId: string;
|
|
38
|
+
threadId?: string;
|
|
39
|
+
messageId: string;
|
|
40
|
+
}) => void;
|
|
41
|
+
/** Any dispatch event, after the cache has been updated. */
|
|
42
|
+
event: (e: PortalEvent) => void;
|
|
43
|
+
channelChange: (channel: PortalChannel) => void;
|
|
44
|
+
close: (info: {
|
|
45
|
+
code: number;
|
|
46
|
+
willReconnect: boolean;
|
|
47
|
+
}) => void;
|
|
48
|
+
error: (err: Error) => void;
|
|
49
|
+
}
|
|
50
|
+
export declare class PortalClient extends TypedEmitter<PortalClientEvents> {
|
|
51
|
+
readonly cache: ClientCache;
|
|
52
|
+
private ws?;
|
|
53
|
+
private opts;
|
|
54
|
+
private pending;
|
|
55
|
+
private sessionId?;
|
|
56
|
+
private lastSeq;
|
|
57
|
+
private heartbeat?;
|
|
58
|
+
private backoff;
|
|
59
|
+
private closedByUser;
|
|
60
|
+
private ready;
|
|
61
|
+
constructor(options: PortalClientOptions);
|
|
62
|
+
/** Connect and resolve once `ready` (or `resumed`) is received. */
|
|
63
|
+
connect(): Promise<ReadyData>;
|
|
64
|
+
close(): void;
|
|
65
|
+
/** Typed RPC call. */
|
|
66
|
+
call<M extends RpcMethod>(method: M, params: RpcParams<M>): Promise<RpcResult<M>>;
|
|
67
|
+
sendMessage(params: RpcParams<'send_message'>): Promise<import("@animalabs/portal-protocol").SendMessageResult>;
|
|
68
|
+
editMessage(messageId: string, content: string): Promise<{
|
|
69
|
+
[x: string]: never;
|
|
70
|
+
}>;
|
|
71
|
+
deleteMessage(messageId: string): Promise<{
|
|
72
|
+
[x: string]: never;
|
|
73
|
+
}>;
|
|
74
|
+
react(messageId: string, emoji: string, visible?: boolean): Promise<{
|
|
75
|
+
[x: string]: never;
|
|
76
|
+
}>;
|
|
77
|
+
fetchHistory(params: RpcParams<'fetch_history'>): Promise<import("@animalabs/portal-protocol").FetchHistoryResult>;
|
|
78
|
+
subscribe(channelId: string): Promise<{
|
|
79
|
+
[x: string]: never;
|
|
80
|
+
}>;
|
|
81
|
+
unsubscribe(channelId: string): Promise<{
|
|
82
|
+
[x: string]: never;
|
|
83
|
+
}>;
|
|
84
|
+
private open;
|
|
85
|
+
private onMessage;
|
|
86
|
+
private onEvent;
|
|
87
|
+
private identify;
|
|
88
|
+
private startHeartbeat;
|
|
89
|
+
private onClose;
|
|
90
|
+
private sendFrame;
|
|
91
|
+
}
|
|
92
|
+
export {};
|
|
93
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EAEf,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC;CACxC;AAED,KAAK,YAAY,GAAG;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC;AAEjG,MAAM,WAAW,kBAAmB,SAAQ,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACpF,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IACnC,aAAa,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,aAAa,EAAE,CAAC,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxF,4DAA4D;IAC5D,KAAK,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,IAAI,CAAC;IAChC,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,KAAK,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC7B;AAQD,qBAAa,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IAChE,QAAQ,CAAC,KAAK,cAAqB;IACnC,OAAO,CAAC,EAAE,CAAC,CAAY;IACvB,OAAO,CAAC,IAAI,CAC+C;IAC3D,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,SAAS,CAAC,CAAiC;IACnD,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,EAAE,mBAAmB;IAexC,mEAAmE;IACnE,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC;IAmB7B,KAAK,IAAI,IAAI;IAMb,sBAAsB;IACtB,IAAI,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAajF,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,cAAc,CAAC;IAG7C,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;IAG9C,aAAa,CAAC,SAAS,EAAE,MAAM;;;IAG/B,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,UAAQ;;;IAGvD,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,eAAe,CAAC;IAG/C,SAAS,CAAC,SAAS,EAAE,MAAM;;;IAK3B,WAAW,CAAC,SAAS,EAAE,MAAM;;;IAO7B,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,SAAS;IAqDjB,OAAO,CAAC,OAAO;IAsBf,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,SAAS;CAKlB"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PortalClient — WS transport + cache + typed RPC + reconnect/resume.
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle: connect → hello → identify → ready. On an unexpected close it
|
|
5
|
+
* reconnects and `resume`s from the last seq; a non-resumable rejection falls
|
|
6
|
+
* back to a fresh identify. The agent-facing read watermark is NOT here — that
|
|
7
|
+
* lives in portal-mcpl; this layer only does transport-level resume.
|
|
8
|
+
*/
|
|
9
|
+
import { WebSocket } from 'ws';
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { PORTAL_PROTOCOL_VERSION, isServerFrame, } from '@animalabs/portal-protocol';
|
|
12
|
+
import { ClientCache } from './cache.js';
|
|
13
|
+
import { TypedEmitter } from './emitter.js';
|
|
14
|
+
export class PortalClient extends TypedEmitter {
|
|
15
|
+
cache = new ClientCache();
|
|
16
|
+
ws;
|
|
17
|
+
opts;
|
|
18
|
+
pending = new Map();
|
|
19
|
+
sessionId;
|
|
20
|
+
lastSeq = 0;
|
|
21
|
+
heartbeat;
|
|
22
|
+
backoff = 1000;
|
|
23
|
+
closedByUser = false;
|
|
24
|
+
ready = false;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
super();
|
|
27
|
+
this.opts = {
|
|
28
|
+
url: options.url,
|
|
29
|
+
token: options.token,
|
|
30
|
+
personaId: options.personaId,
|
|
31
|
+
rpcTimeoutMs: options.rpcTimeoutMs ?? 15_000,
|
|
32
|
+
maxBackoffMs: options.maxBackoffMs ?? 30_000,
|
|
33
|
+
// Kept as a mutable set so subscribe/unsubscribe stay durable across
|
|
34
|
+
// reconnects (identify replays this list).
|
|
35
|
+
subscriptions: options.subscriptions ? [...options.subscriptions] : [],
|
|
36
|
+
wsFactory: options.wsFactory,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Connect and resolve once `ready` (or `resumed`) is received. */
|
|
40
|
+
connect() {
|
|
41
|
+
this.closedByUser = false;
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const onReady = this.on('ready', (d) => {
|
|
44
|
+
onReady();
|
|
45
|
+
onErr();
|
|
46
|
+
resolve(d);
|
|
47
|
+
});
|
|
48
|
+
const onErr = this.on('error', (e) => {
|
|
49
|
+
if (!this.ready) {
|
|
50
|
+
onReady();
|
|
51
|
+
onErr();
|
|
52
|
+
reject(e);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this.open();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
close() {
|
|
59
|
+
this.closedByUser = true;
|
|
60
|
+
if (this.heartbeat)
|
|
61
|
+
clearInterval(this.heartbeat);
|
|
62
|
+
this.ws?.close(1000, 'client closing');
|
|
63
|
+
}
|
|
64
|
+
/** Typed RPC call. */
|
|
65
|
+
call(method, params) {
|
|
66
|
+
const id = `rpc_${randomUUID()}`;
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
this.pending.delete(id);
|
|
70
|
+
reject(new Error(`RPC ${method} timed out`));
|
|
71
|
+
}, this.opts.rpcTimeoutMs);
|
|
72
|
+
this.pending.set(id, { resolve: resolve, reject, timer });
|
|
73
|
+
this.sendFrame({ op: 'rpc', d: { id, method, params } });
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Convenience wrappers
|
|
77
|
+
sendMessage(params) {
|
|
78
|
+
return this.call('send_message', params);
|
|
79
|
+
}
|
|
80
|
+
editMessage(messageId, content) {
|
|
81
|
+
return this.call('edit_message', { messageId, content });
|
|
82
|
+
}
|
|
83
|
+
deleteMessage(messageId) {
|
|
84
|
+
return this.call('delete_message', { messageId });
|
|
85
|
+
}
|
|
86
|
+
react(messageId, emoji, visible = false) {
|
|
87
|
+
return this.call('react', { messageId, emoji, visible });
|
|
88
|
+
}
|
|
89
|
+
fetchHistory(params) {
|
|
90
|
+
return this.call('fetch_history', params);
|
|
91
|
+
}
|
|
92
|
+
subscribe(channelId) {
|
|
93
|
+
const subs = (this.opts.subscriptions ??= []);
|
|
94
|
+
if (!subs.includes(channelId))
|
|
95
|
+
subs.push(channelId);
|
|
96
|
+
return this.call('subscribe_channel', { channelId });
|
|
97
|
+
}
|
|
98
|
+
unsubscribe(channelId) {
|
|
99
|
+
this.opts.subscriptions = (this.opts.subscriptions ?? []).filter((c) => c !== channelId);
|
|
100
|
+
return this.call('unsubscribe_channel', { channelId });
|
|
101
|
+
}
|
|
102
|
+
// ── Internals ──
|
|
103
|
+
open() {
|
|
104
|
+
const ws = this.opts.wsFactory ? this.opts.wsFactory(this.opts.url) : new WebSocket(this.opts.url);
|
|
105
|
+
this.ws = ws;
|
|
106
|
+
ws.on('message', (data) => this.onMessage(data.toString()));
|
|
107
|
+
ws.on('close', (code) => this.onClose(code));
|
|
108
|
+
ws.on('error', (err) => this.emit('error', err));
|
|
109
|
+
}
|
|
110
|
+
onMessage(raw) {
|
|
111
|
+
const parsed = safeParse(raw);
|
|
112
|
+
if (!isServerFrame(parsed))
|
|
113
|
+
return;
|
|
114
|
+
const frame = parsed;
|
|
115
|
+
switch (frame.op) {
|
|
116
|
+
case 'hello':
|
|
117
|
+
this.startHeartbeat(frame.d.heartbeatIntervalMs);
|
|
118
|
+
if (this.sessionId && this.lastSeq >= 0) {
|
|
119
|
+
this.sendFrame({ op: 'resume', d: { sessionId: this.sessionId, seq: this.lastSeq } });
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.identify();
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
case 'ready':
|
|
126
|
+
this.ready = true;
|
|
127
|
+
this.backoff = 1000;
|
|
128
|
+
this.sessionId = frame.d.sessionId;
|
|
129
|
+
this.lastSeq = frame.d.seq;
|
|
130
|
+
this.cache.hydrate(frame.d);
|
|
131
|
+
this.emit('ready', frame.d);
|
|
132
|
+
return;
|
|
133
|
+
case 'resumed':
|
|
134
|
+
this.ready = true;
|
|
135
|
+
this.backoff = 1000;
|
|
136
|
+
this.emit('resumed', frame.d.replayedEvents);
|
|
137
|
+
return;
|
|
138
|
+
case 'invalid_session':
|
|
139
|
+
// Can't resume — start fresh.
|
|
140
|
+
this.sessionId = undefined;
|
|
141
|
+
this.lastSeq = 0;
|
|
142
|
+
if (frame.d.resumable) {
|
|
143
|
+
/* relay said retry resume, but we lack state → identify */
|
|
144
|
+
}
|
|
145
|
+
this.identify();
|
|
146
|
+
return;
|
|
147
|
+
case 'heartbeat_ack':
|
|
148
|
+
return;
|
|
149
|
+
case 'dispatch':
|
|
150
|
+
this.lastSeq = frame.seq;
|
|
151
|
+
this.onEvent(frame.d);
|
|
152
|
+
return;
|
|
153
|
+
case 'rpc_result': {
|
|
154
|
+
const p = this.pending.get(frame.d.id);
|
|
155
|
+
if (!p)
|
|
156
|
+
return;
|
|
157
|
+
this.pending.delete(frame.d.id);
|
|
158
|
+
clearTimeout(p.timer);
|
|
159
|
+
if (frame.d.ok)
|
|
160
|
+
p.resolve(frame.d.result);
|
|
161
|
+
else
|
|
162
|
+
p.reject(Object.assign(new Error(frame.d.error.message), { code: frame.d.error.code }));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
onEvent(event) {
|
|
168
|
+
this.cache.apply(event);
|
|
169
|
+
this.emit('event', event);
|
|
170
|
+
switch (event.type) {
|
|
171
|
+
case 'message_create':
|
|
172
|
+
this.emit('message', { message: event.message, addressedToMe: event.addressedToMe, reasons: event.reasons });
|
|
173
|
+
break;
|
|
174
|
+
case 'message_update':
|
|
175
|
+
this.emit('messageUpdate', { message: event.message, addressedToMe: event.addressedToMe, reasons: event.reasons });
|
|
176
|
+
break;
|
|
177
|
+
case 'message_delete':
|
|
178
|
+
this.emit('messageDelete', { channelId: event.channelId, threadId: event.threadId, messageId: event.messageId });
|
|
179
|
+
break;
|
|
180
|
+
case 'channel_create':
|
|
181
|
+
case 'channel_update':
|
|
182
|
+
case 'thread_create':
|
|
183
|
+
case 'thread_update':
|
|
184
|
+
this.emit('channelChange', event.channel);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
identify() {
|
|
189
|
+
this.sendFrame({
|
|
190
|
+
op: 'identify',
|
|
191
|
+
d: {
|
|
192
|
+
protocolVersion: PORTAL_PROTOCOL_VERSION,
|
|
193
|
+
token: this.opts.token,
|
|
194
|
+
personaId: this.opts.personaId,
|
|
195
|
+
subscriptions: this.opts.subscriptions,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
startHeartbeat(intervalMs) {
|
|
200
|
+
if (this.heartbeat)
|
|
201
|
+
clearInterval(this.heartbeat);
|
|
202
|
+
this.heartbeat = setInterval(() => this.sendFrame({ op: 'heartbeat', d: { seq: this.lastSeq } }), intervalMs);
|
|
203
|
+
}
|
|
204
|
+
onClose(code) {
|
|
205
|
+
if (this.heartbeat)
|
|
206
|
+
clearInterval(this.heartbeat);
|
|
207
|
+
this.ready = false;
|
|
208
|
+
const willReconnect = !this.closedByUser;
|
|
209
|
+
this.emit('close', { code, willReconnect });
|
|
210
|
+
if (!willReconnect)
|
|
211
|
+
return;
|
|
212
|
+
const delay = Math.min(this.backoff, this.opts.maxBackoffMs);
|
|
213
|
+
this.backoff = Math.min(this.backoff * 2, this.opts.maxBackoffMs);
|
|
214
|
+
setTimeout(() => this.open(), delay);
|
|
215
|
+
}
|
|
216
|
+
sendFrame(frame) {
|
|
217
|
+
if (this.ws && this.ws.readyState === this.ws.OPEN) {
|
|
218
|
+
this.ws.send(JSON.stringify(frame));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function safeParse(raw) {
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(raw);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Minimal typed event emitter (no `any`, no Node EventEmitter dependency). */
|
|
2
|
+
export declare class TypedEmitter<Events extends Record<string, (...args: never[]) => void>> {
|
|
3
|
+
private listeners;
|
|
4
|
+
on<K extends keyof Events>(event: K, fn: Events[K]): () => void;
|
|
5
|
+
off<K extends keyof Events>(event: K, fn: Events[K]): void;
|
|
6
|
+
emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/emitter.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,qBAAa,YAAY,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACjF,OAAO,CAAC,SAAS,CAA4D;IAE7E,EAAE,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAO/D,GAAG,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI;IAI1D,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;CAK7E"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Minimal typed event emitter (no `any`, no Node EventEmitter dependency). */
|
|
2
|
+
export class TypedEmitter {
|
|
3
|
+
listeners = new Map();
|
|
4
|
+
on(event, fn) {
|
|
5
|
+
let set = this.listeners.get(event);
|
|
6
|
+
if (!set)
|
|
7
|
+
this.listeners.set(event, (set = new Set()));
|
|
8
|
+
set.add(fn);
|
|
9
|
+
return () => this.off(event, fn);
|
|
10
|
+
}
|
|
11
|
+
off(event, fn) {
|
|
12
|
+
this.listeners.get(event)?.delete(fn);
|
|
13
|
+
}
|
|
14
|
+
emit(event, ...args) {
|
|
15
|
+
for (const fn of this.listeners.get(event) ?? []) {
|
|
16
|
+
fn(...args);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/emitter.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,OAAO,YAAY;IACf,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;IAE7E,EAAE,CAAyB,KAAQ,EAAE,EAAa;QAChD,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QACvD,GAAG,CAAC,GAAG,CAAC,EAAgC,CAAC,CAAC;QAC1C,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,GAAG,CAAyB,KAAQ,EAAE,EAAa;QACjD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,EAAgC,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,CAAyB,KAAQ,EAAE,GAAG,IAA2B;QACnE,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAChD,EAA4C,CAAC,GAAG,IAAI,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-enrollment helpers.
|
|
3
|
+
*
|
|
4
|
+
* A brand-new agent with no persona/token opens a short-lived WS connection,
|
|
5
|
+
* presents an invite (an admin-minted access-rights template) plus a desired
|
|
6
|
+
* name, and receives minted credentials. The credentials are then persisted and
|
|
7
|
+
* used by a normal PortalClient `identify` (which also gives transport resume).
|
|
8
|
+
*
|
|
9
|
+
* This is deliberately separate from PortalClient: the client stays a pure
|
|
10
|
+
* identify/resume transport, and enrollment is a one-shot bootstrap.
|
|
11
|
+
*/
|
|
12
|
+
import { WebSocket } from 'ws';
|
|
13
|
+
export interface EnrollOptions {
|
|
14
|
+
url: string;
|
|
15
|
+
/** Invite code (access-rights template). */
|
|
16
|
+
invite: string;
|
|
17
|
+
/** Desired display name for the new persona. */
|
|
18
|
+
desiredName: string;
|
|
19
|
+
/** Optional avatar filename/URL. */
|
|
20
|
+
avatar?: string;
|
|
21
|
+
/** Timeout for the enroll handshake (default 15000ms). */
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
/** Provide a WebSocket impl (tests). Defaults to ws. */
|
|
24
|
+
wsFactory?: (url: string) => WebSocket;
|
|
25
|
+
}
|
|
26
|
+
export interface PortalCredentials {
|
|
27
|
+
personaId: string;
|
|
28
|
+
token: string;
|
|
29
|
+
}
|
|
30
|
+
/** One-shot: open WS, register, return minted credentials, close. */
|
|
31
|
+
export declare function enroll(opts: EnrollOptions): Promise<PortalCredentials>;
|
|
32
|
+
/**
|
|
33
|
+
* Load persisted credentials, or enroll once and persist them. Idempotent: the
|
|
34
|
+
* first run of a new agent enrolls and writes `credsPath`; every run after just
|
|
35
|
+
* reads it. Returns the credentials to hand to a PortalClient.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadOrEnrollCreds(opts: {
|
|
38
|
+
url: string;
|
|
39
|
+
/** Where minted creds are cached (JSON: { personaId, token }). */
|
|
40
|
+
credsPath: string;
|
|
41
|
+
/** Required only when no creds exist yet. */
|
|
42
|
+
invite?: string;
|
|
43
|
+
desiredName?: string;
|
|
44
|
+
avatar?: string;
|
|
45
|
+
wsFactory?: (url: string) => WebSocket;
|
|
46
|
+
}): Promise<PortalCredentials>;
|
|
47
|
+
//# sourceMappingURL=enroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enroll.d.ts","sourceRoot":"","sources":["../../src/enroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAK/B,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qEAAqE;AACrE,wBAAgB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqDtE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC;CACxC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoB7B"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-enrollment helpers.
|
|
3
|
+
*
|
|
4
|
+
* A brand-new agent with no persona/token opens a short-lived WS connection,
|
|
5
|
+
* presents an invite (an admin-minted access-rights template) plus a desired
|
|
6
|
+
* name, and receives minted credentials. The credentials are then persisted and
|
|
7
|
+
* used by a normal PortalClient `identify` (which also gives transport resume).
|
|
8
|
+
*
|
|
9
|
+
* This is deliberately separate from PortalClient: the client stays a pure
|
|
10
|
+
* identify/resume transport, and enrollment is a one-shot bootstrap.
|
|
11
|
+
*/
|
|
12
|
+
import { WebSocket } from 'ws';
|
|
13
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
14
|
+
import { dirname } from 'node:path';
|
|
15
|
+
import { PORTAL_PROTOCOL_VERSION, isServerFrame } from '@animalabs/portal-protocol';
|
|
16
|
+
/** One-shot: open WS, register, return minted credentials, close. */
|
|
17
|
+
export function enroll(opts) {
|
|
18
|
+
const ws = opts.wsFactory ? opts.wsFactory(opts.url) : new WebSocket(opts.url);
|
|
19
|
+
const timeoutMs = opts.timeoutMs ?? 15_000;
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
let settled = false;
|
|
22
|
+
const done = (fn) => {
|
|
23
|
+
if (settled)
|
|
24
|
+
return;
|
|
25
|
+
settled = true;
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
try {
|
|
28
|
+
ws.close();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* ignore */
|
|
32
|
+
}
|
|
33
|
+
fn();
|
|
34
|
+
};
|
|
35
|
+
const timer = setTimeout(() => done(() => reject(new Error('enroll timed out'))), timeoutMs);
|
|
36
|
+
ws.on('message', (data) => {
|
|
37
|
+
let frame;
|
|
38
|
+
try {
|
|
39
|
+
frame = JSON.parse(data.toString());
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (!isServerFrame(frame))
|
|
45
|
+
return;
|
|
46
|
+
const f = frame;
|
|
47
|
+
if (f.op === 'hello') {
|
|
48
|
+
ws.send(JSON.stringify({
|
|
49
|
+
op: 'register',
|
|
50
|
+
d: {
|
|
51
|
+
protocolVersion: PORTAL_PROTOCOL_VERSION,
|
|
52
|
+
invite: opts.invite,
|
|
53
|
+
desiredName: opts.desiredName,
|
|
54
|
+
...(opts.avatar ? { avatar: opts.avatar } : {}),
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
else if (f.op === 'registered') {
|
|
59
|
+
const d = f.d;
|
|
60
|
+
done(() => resolve({ personaId: d.personaId, token: d.token }));
|
|
61
|
+
}
|
|
62
|
+
else if (f.op === 'invalid_session') {
|
|
63
|
+
const reason = f.d?.reason ?? 'rejected';
|
|
64
|
+
done(() => reject(new Error(`enroll rejected: ${reason}`)));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
ws.on('error', (err) => done(() => reject(err)));
|
|
68
|
+
ws.on('close', () => done(() => reject(new Error('connection closed before register'))));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Load persisted credentials, or enroll once and persist them. Idempotent: the
|
|
73
|
+
* first run of a new agent enrolls and writes `credsPath`; every run after just
|
|
74
|
+
* reads it. Returns the credentials to hand to a PortalClient.
|
|
75
|
+
*/
|
|
76
|
+
export async function loadOrEnrollCreds(opts) {
|
|
77
|
+
if (existsSync(opts.credsPath)) {
|
|
78
|
+
const raw = JSON.parse(readFileSync(opts.credsPath, 'utf8'));
|
|
79
|
+
if (raw.personaId && raw.token)
|
|
80
|
+
return { personaId: raw.personaId, token: raw.token };
|
|
81
|
+
}
|
|
82
|
+
if (!opts.invite || !opts.desiredName) {
|
|
83
|
+
throw new Error(`no saved credentials at ${opts.credsPath} and no invite/desiredName to enroll with`);
|
|
84
|
+
}
|
|
85
|
+
const creds = await enroll({
|
|
86
|
+
url: opts.url,
|
|
87
|
+
invite: opts.invite,
|
|
88
|
+
desiredName: opts.desiredName,
|
|
89
|
+
avatar: opts.avatar,
|
|
90
|
+
wsFactory: opts.wsFactory,
|
|
91
|
+
});
|
|
92
|
+
mkdirSync(dirname(opts.credsPath), { recursive: true });
|
|
93
|
+
writeFileSync(opts.credsPath, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
|
|
94
|
+
return creds;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=enroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../src/enroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OutgoingFile } from '@animalabs/portal-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Build an `OutgoingFile` from in-memory bytes (the portable way to attach a
|
|
4
|
+
* file — works from any client/host, no relay-side filesystem access).
|
|
5
|
+
*/
|
|
6
|
+
export declare function fileFromBytes(name: string, data: Buffer | Uint8Array, opts?: {
|
|
7
|
+
contentType?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}): OutgoingFile;
|
|
10
|
+
//# sourceMappingURL=files.d.ts.map
|