@zhin.js/client 1.1.5 → 1.1.7
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/README.md +25 -19
- package/client/app.test.ts +37 -0
- package/client/app.ts +130 -138
- package/client/bootstrap/loadConsoleEntries.test.ts +0 -15
- package/client/bootstrap/loadConsoleEntries.ts +1 -20
- package/client/console-client-context.ts +24 -0
- package/client/console-client.test.ts +46 -0
- package/client/console-client.ts +70 -0
- package/client/console-events.ts +4 -3
- package/client/console-utils/remoteApi.ts +0 -7
- package/client/index.ts +21 -13
- package/client/persistence/idb-store.test.ts +0 -18
- package/client/persistence/idb-store.ts +3 -6
- package/client/segments.test.ts +2 -0
- package/client/segments.ts +1 -1
- package/client/{websocket/manager.test.ts → transport/console-transport.test.ts} +79 -34
- package/client/{websocket/manager.ts → transport/console-transport.ts} +237 -178
- package/client/{websocket → transport}/hooks.ts +240 -23
- package/client/transport/index.ts +23 -0
- package/client/{websocket → transport}/types.ts +11 -17
- package/client/types.ts +0 -3
- package/client/workroom/registerWorkroomConsole.test.ts +0 -1
- package/dist/app.d.ts +17 -52
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +110 -95
- package/dist/app.js.map +1 -1
- package/dist/bootstrap/loadConsoleEntries.d.ts +0 -6
- package/dist/bootstrap/loadConsoleEntries.d.ts.map +1 -1
- package/dist/bootstrap/loadConsoleEntries.js +0 -9
- package/dist/bootstrap/loadConsoleEntries.js.map +1 -1
- package/dist/console-client-context.d.ts +9 -0
- package/dist/console-client-context.d.ts.map +1 -0
- package/dist/console-client-context.js +13 -0
- package/dist/console-client-context.js.map +1 -0
- package/dist/console-client.d.ts +25 -0
- package/dist/console-client.d.ts.map +1 -0
- package/dist/console-client.js +45 -0
- package/dist/console-client.js.map +1 -0
- package/dist/console-events.d.ts +1 -0
- package/dist/console-events.d.ts.map +1 -1
- package/dist/console-events.js +3 -3
- package/dist/console-events.js.map +1 -1
- package/dist/console-utils/remoteApi.d.ts +0 -1
- package/dist/console-utils/remoteApi.d.ts.map +1 -1
- package/dist/console-utils/remoteApi.js +0 -6
- package/dist/console-utils/remoteApi.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/persistence/idb-store.d.ts.map +1 -1
- package/dist/persistence/idb-store.js +3 -5
- package/dist/persistence/idb-store.js.map +1 -1
- package/dist/segments.js +1 -1
- package/dist/segments.js.map +1 -1
- package/dist/transport/console-transport.d.ts +217 -0
- package/dist/transport/console-transport.d.ts.map +1 -0
- package/dist/{websocket/manager.js → transport/console-transport.js} +147 -165
- package/dist/transport/console-transport.js.map +1 -0
- package/dist/transport/hooks.d.ts +253 -0
- package/dist/transport/hooks.d.ts.map +1 -0
- package/dist/{websocket → transport}/hooks.js +234 -19
- package/dist/transport/hooks.js.map +1 -0
- package/dist/transport/index.d.ts +4 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +4 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/{websocket → transport}/types.d.ts +10 -13
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/{websocket → transport}/types.js +4 -9
- package/dist/transport/types.js.map +1 -0
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/client/runtime/index.ts +0 -12
- package/client/websocket/index.ts +0 -11
- package/client/websocket/instance.ts +0 -24
- package/client/websocket/remote-settings.ts +0 -47
- package/dist/runtime/index.d.ts +0 -6
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -10
- package/dist/runtime/index.js.map +0 -1
- package/dist/websocket/hooks.d.ts +0 -114
- package/dist/websocket/hooks.d.ts.map +0 -1
- package/dist/websocket/hooks.js.map +0 -1
- package/dist/websocket/index.d.ts +0 -7
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/index.js +0 -7
- package/dist/websocket/index.js.map +0 -1
- package/dist/websocket/instance.d.ts +0 -5
- package/dist/websocket/instance.d.ts.map +0 -1
- package/dist/websocket/instance.js +0 -21
- package/dist/websocket/instance.js.map +0 -1
- package/dist/websocket/manager.d.ts +0 -131
- package/dist/websocket/manager.d.ts.map +0 -1
- package/dist/websocket/manager.js.map +0 -1
- package/dist/websocket/remote-settings.d.ts +0 -5
- package/dist/websocket/remote-settings.d.ts.map +0 -1
- package/dist/websocket/remote-settings.js +0 -47
- package/dist/websocket/remote-settings.js.map +0 -1
- package/dist/websocket/types.d.ts.map +0 -1
- package/dist/websocket/types.js.map +0 -1
- /package/client/{websocket → transport}/hooks.test.ts +0 -0
package/client/index.ts
CHANGED
|
@@ -7,18 +7,19 @@ export {
|
|
|
7
7
|
INBOX_RPC,
|
|
8
8
|
SIDE_EVENT_PUSH,
|
|
9
9
|
SIDE_EVENT_RPC,
|
|
10
|
-
normalizeConsolePushMessage,
|
|
11
|
-
normalizeConsolePushType,
|
|
12
10
|
parseConsoleInboxEvent,
|
|
13
11
|
type ConsoleInboxEvent,
|
|
14
12
|
type ConsoleInboxEventKind,
|
|
13
|
+
type ConsoleInboxMessageRow,
|
|
15
14
|
type ConsoleInboxNoticeRow,
|
|
16
15
|
type ConsoleInboxNoticesQuery,
|
|
17
16
|
type ConsoleInboxNoticesResult,
|
|
17
|
+
type ConsoleInboxRequestRow,
|
|
18
18
|
type ConsoleEndpointPhase,
|
|
19
19
|
type ConsoleEndpointSummary,
|
|
20
20
|
type ConsoleEventActor,
|
|
21
21
|
type ConsoleEventChannel,
|
|
22
|
+
type ConsoleEventParent,
|
|
22
23
|
type EndpointManagementCapability,
|
|
23
24
|
parseConsoleSseFrame,
|
|
24
25
|
type ConsoleEventDelivery,
|
|
@@ -64,22 +65,32 @@ export {
|
|
|
64
65
|
// Segment IM visibility (inbox vs agent panel)
|
|
65
66
|
export { segmentsForImDelivery, segmentsForAgentPanel } from "./segments.js";
|
|
66
67
|
|
|
67
|
-
// Console
|
|
68
|
+
// Owner-scoped Console client and application registry
|
|
68
69
|
export {
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
ConsoleApp,
|
|
71
|
+
createConsoleApp,
|
|
71
72
|
type AddRouteInput,
|
|
72
73
|
type AddToolInput,
|
|
73
74
|
type ConsoleRouteRecord,
|
|
74
75
|
type RouteTreeNode,
|
|
75
76
|
type ToolTreeNode,
|
|
77
|
+
type SidebarRenderer,
|
|
78
|
+
type ToolbarRenderer,
|
|
76
79
|
type ConsoleRouteRenderer,
|
|
77
|
-
} from "./app";
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
} from "./app.js";
|
|
81
|
+
export {
|
|
82
|
+
ConsoleClient,
|
|
83
|
+
createConsoleClient,
|
|
84
|
+
type ConsoleClientOptions,
|
|
85
|
+
} from './console-client.js';
|
|
86
|
+
export {
|
|
87
|
+
ConsoleClientProvider,
|
|
88
|
+
useConsoleClient,
|
|
89
|
+
type ConsoleClientProviderProps,
|
|
90
|
+
} from './console-client-context.js';
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
// Owner-scoped REST/SSE transport
|
|
93
|
+
export * from "./transport/index.js";
|
|
83
94
|
|
|
84
95
|
export {
|
|
85
96
|
createRegistryStore,
|
|
@@ -94,17 +105,14 @@ export {
|
|
|
94
105
|
getApiBase,
|
|
95
106
|
getToken,
|
|
96
107
|
resolveApiUrl,
|
|
97
|
-
resolveWebSocketUrl,
|
|
98
108
|
} from "./console-utils/remoteApi.js";
|
|
99
109
|
|
|
100
110
|
export {
|
|
101
111
|
fetchConsoleEntries,
|
|
102
|
-
createPluginRegisterHostApi,
|
|
103
112
|
getRegisterFn,
|
|
104
113
|
loadConsoleEntries,
|
|
105
114
|
registerConsolePluginsFromEntries,
|
|
106
115
|
resolveEntryRegister,
|
|
107
|
-
type CreatePluginRegisterHostApiOptions,
|
|
108
116
|
type FetchConsoleEntriesOptions,
|
|
109
117
|
type LoadConsoleEntriesOptions,
|
|
110
118
|
} from "./bootstrap/loadConsoleEntries.js";
|
|
@@ -118,22 +118,4 @@ describe("applyConsoleEvent inbox record ids", () => {
|
|
|
118
118
|
expect(rows[0]?.id).toBe('runtime-a:7');
|
|
119
119
|
expect(rows[0]?.updatedAt).toBe(1700000000000);
|
|
120
120
|
});
|
|
121
|
-
|
|
122
|
-
it("reads legacy records that only carry endpointKey (camelCase)", async () => {
|
|
123
|
-
// 旧版(DB v1 时代)记录只有 endpointKey 字段,升级后仍需能被列出
|
|
124
|
-
const { idbPutInbox } = await import("./idb-store.js");
|
|
125
|
-
const legacy = {
|
|
126
|
-
id: "icqq:bot-1:message:1700000000000:legacy",
|
|
127
|
-
adapter: "icqq",
|
|
128
|
-
endpointKey: "bot-1",
|
|
129
|
-
kind: "message",
|
|
130
|
-
payload: {},
|
|
131
|
-
updatedAt: 1700000000000,
|
|
132
|
-
};
|
|
133
|
-
await idbPutInbox(legacy as unknown as Parameters<typeof idbPutInbox>[0]);
|
|
134
|
-
|
|
135
|
-
const rows = await idbListInbox("icqq", "bot-1", "message");
|
|
136
|
-
expect(rows).toHaveLength(1);
|
|
137
|
-
expect(rows[0].id).toBe(legacy.id);
|
|
138
|
-
});
|
|
139
121
|
});
|
|
@@ -5,8 +5,6 @@ const DB_VERSION = 2;
|
|
|
5
5
|
const STORE_INBOX = "inbox";
|
|
6
6
|
const STORE_PENDING = "pending";
|
|
7
7
|
|
|
8
|
-
let inboxEventSeq = 0;
|
|
9
|
-
|
|
10
8
|
export type InboxRecord = {
|
|
11
9
|
id: string;
|
|
12
10
|
adapter: string;
|
|
@@ -60,8 +58,7 @@ export async function idbListInbox(
|
|
|
60
58
|
return all.filter(
|
|
61
59
|
(r) =>
|
|
62
60
|
r.adapter === adapter &&
|
|
63
|
-
|
|
64
|
-
(r as { endpointKey?: string }).endpointKey === endpoint_id) &&
|
|
61
|
+
r.endpoint_id === endpoint_id &&
|
|
65
62
|
r.kind === kind,
|
|
66
63
|
);
|
|
67
64
|
}
|
|
@@ -86,9 +83,9 @@ export async function applyConsoleEvent(event: {
|
|
|
86
83
|
: null;
|
|
87
84
|
await idbPutInbox({
|
|
88
85
|
// Resumable Host events are idempotent across history/live redelivery.
|
|
89
|
-
//
|
|
86
|
+
// Unsequenced sources use a collision-safe append id without shared state.
|
|
90
87
|
id: resumableId
|
|
91
|
-
?? `${parsed.adapter}:${parsed.endpointKey}:${parsed.type}:${updatedAt}:${
|
|
88
|
+
?? `${parsed.adapter}:${parsed.endpointKey}:${parsed.type}:${updatedAt}:${crypto.randomUUID()}`,
|
|
92
89
|
adapter: parsed.adapter,
|
|
93
90
|
endpoint_id: parsed.endpointKey,
|
|
94
91
|
kind: parsed.kind,
|
package/client/segments.test.ts
CHANGED
|
@@ -6,10 +6,12 @@ describe('console segmentsForImDelivery', () => {
|
|
|
6
6
|
const full = [
|
|
7
7
|
{ type: 'thinking', data: { text: 'plan' } },
|
|
8
8
|
{ type: 'text', data: { text: 'hi' } },
|
|
9
|
+
{ type: 'share', data: { title: 'Example', url: 'https://example.com' } },
|
|
9
10
|
{ type: 'tool_call', data: { name: 'bash' } },
|
|
10
11
|
];
|
|
11
12
|
expect(segmentsForImDelivery(full)).toEqual([
|
|
12
13
|
{ type: 'text', data: { text: 'hi' } },
|
|
14
|
+
{ type: 'share', data: { title: 'Example', url: 'https://example.com' } },
|
|
13
15
|
]);
|
|
14
16
|
expect(segmentsForAgentPanel(full)).toEqual(full);
|
|
15
17
|
});
|
package/client/segments.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { MessageSegment } from './types.js';
|
|
|
4
4
|
const IM_VISIBLE_TYPES = new Set([
|
|
5
5
|
'text', 'mention', 'image', 'video', 'audio', 'voice', 'record', 'file',
|
|
6
6
|
'face', 'reply', 'forward', 'link', 'dice', 'rps', 'markdown', 'html',
|
|
7
|
-
'qrcode', 'tts', 'keyboard', 'action',
|
|
7
|
+
'qrcode', 'tts', 'keyboard', 'action', 'share',
|
|
8
8
|
]);
|
|
9
9
|
|
|
10
10
|
const AI_ONLY_TYPES = new Set(['thinking', 'tool_call']);
|
|
@@ -6,8 +6,8 @@ vi.mock("../persistence/idb-store.js", () => ({
|
|
|
6
6
|
applyConsoleEvent: (...args: unknown[]) => mockApplyConsoleEvent(...args),
|
|
7
7
|
}));
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { ConnectionState
|
|
9
|
+
import { ConsoleTransport } from "./console-transport.js";
|
|
10
|
+
import { ConnectionState } from "./types.js";
|
|
11
11
|
|
|
12
12
|
function mockStorage(map: Record<string, string> = {}): Storage {
|
|
13
13
|
return {
|
|
@@ -80,7 +80,7 @@ function mockConsoleTransport(sse: () => Response | Promise<Response>) {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
describe("
|
|
83
|
+
describe("ConsoleTransport REST/SSE transport", () => {
|
|
84
84
|
beforeEach(() => {
|
|
85
85
|
vi.useFakeTimers();
|
|
86
86
|
mockApplyConsoleEvent.mockClear();
|
|
@@ -97,7 +97,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
97
97
|
const fetchMock = mockConsoleTransport(() => sseResponse(200));
|
|
98
98
|
vi.stubGlobal("fetch", fetchMock);
|
|
99
99
|
|
|
100
|
-
const manager = new
|
|
100
|
+
const manager = new ConsoleTransport({ reconnectInterval: 100, maxReconnectAttempts: 3 });
|
|
101
101
|
const states: boolean[] = [];
|
|
102
102
|
manager.onConnectionChange((c) => states.push(c));
|
|
103
103
|
manager.connect();
|
|
@@ -122,7 +122,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
122
122
|
const fetchMock = mockConsoleTransport(() => sseResponse(200));
|
|
123
123
|
vi.stubGlobal("fetch", fetchMock);
|
|
124
124
|
|
|
125
|
-
const manager = new
|
|
125
|
+
const manager = new ConsoleTransport();
|
|
126
126
|
manager.connect();
|
|
127
127
|
await vi.advanceTimersByTimeAsync(0);
|
|
128
128
|
await Promise.resolve();
|
|
@@ -144,7 +144,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
144
144
|
});
|
|
145
145
|
vi.stubGlobal("fetch", fetchMock);
|
|
146
146
|
|
|
147
|
-
const manager = new
|
|
147
|
+
const manager = new ConsoleTransport({
|
|
148
148
|
reconnectInterval: 100,
|
|
149
149
|
maxReconnectAttempts: 5,
|
|
150
150
|
});
|
|
@@ -172,13 +172,21 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
172
172
|
const fetchMock = vi.fn(async () => ({
|
|
173
173
|
ok: true,
|
|
174
174
|
status: 200,
|
|
175
|
-
json: async () => ({
|
|
175
|
+
json: async () => ({
|
|
176
|
+
success: true,
|
|
177
|
+
data: {
|
|
178
|
+
source: "http:\n port: 1\n",
|
|
179
|
+
format: 'yaml',
|
|
180
|
+
revision: 'a'.repeat(64),
|
|
181
|
+
configKeys: [],
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
176
184
|
} as Response));
|
|
177
185
|
vi.stubGlobal("fetch", fetchMock);
|
|
178
186
|
|
|
179
|
-
const manager = new
|
|
180
|
-
const data = await manager.
|
|
181
|
-
expect(data.
|
|
187
|
+
const manager = new ConsoleTransport();
|
|
188
|
+
const data = await manager.getConfigSource();
|
|
189
|
+
expect(data.source).toContain("port");
|
|
182
190
|
const init = fetchMock.mock.calls[0]![1] as RequestInit;
|
|
183
191
|
expect((init.headers as Record<string, string>).Authorization).toBeUndefined();
|
|
184
192
|
expect(init.method).toBe("POST");
|
|
@@ -193,31 +201,39 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
193
201
|
} as Response));
|
|
194
202
|
vi.stubGlobal("fetch", fetchMock);
|
|
195
203
|
|
|
196
|
-
const manager = new
|
|
204
|
+
const manager = new ConsoleTransport();
|
|
197
205
|
await manager.setConfig("sandbox", { endpoints: [] });
|
|
198
206
|
const init = fetchMock.mock.calls[0]![1] as RequestInit;
|
|
199
207
|
expect((init.headers as Record<string, string>).Authorization).toBe("Bearer t");
|
|
200
208
|
});
|
|
201
209
|
|
|
202
|
-
it(
|
|
203
|
-
installBrowserGlobals();
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
it('exposes marketplace discovery and lifecycle methods for Console pages', async () => {
|
|
211
|
+
installBrowserGlobals(mockStorage({ zhin_api_token: 'token' }));
|
|
212
|
+
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => ({
|
|
213
|
+
ok: true,
|
|
214
|
+
status: 200,
|
|
215
|
+
json: async () => ({
|
|
216
|
+
success: true,
|
|
217
|
+
data: String(input).includes('/marketplace/search') ? { items: [] } : { ok: true },
|
|
218
|
+
}),
|
|
219
|
+
headers: init?.headers,
|
|
220
|
+
} as Response));
|
|
221
|
+
const manager = new ConsoleTransport({ fetch: fetchMock });
|
|
222
|
+
|
|
223
|
+
await manager.searchMarketplace({ keyword: 'telegram', official: true });
|
|
224
|
+
await manager.getMarketplacePlugin('@zhin.js/adapter-telegram');
|
|
225
|
+
await manager.getPluginUpdates();
|
|
226
|
+
await manager.setPluginEnabled('telegram', false);
|
|
227
|
+
|
|
228
|
+
expect(String(fetchMock.mock.calls[0]![0])).toContain('/pub/marketplace/search?q=telegram&official=true');
|
|
229
|
+
expect(String(fetchMock.mock.calls[1]![0])).toContain('%40zhin.js%2Fadapter-telegram');
|
|
230
|
+
expect(String(fetchMock.mock.calls[2]![0])).toContain('/api/marketplace/updates');
|
|
231
|
+
const lifecycleBody = JSON.parse(String((fetchMock.mock.calls[3]![1] as RequestInit).body));
|
|
232
|
+
expect(lifecycleBody).toMatchObject({
|
|
233
|
+
type: 'plugin:set-enabled',
|
|
234
|
+
instanceKey: 'telegram',
|
|
235
|
+
enabled: false,
|
|
207
236
|
});
|
|
208
|
-
|
|
209
|
-
(manager as unknown as { handleMessage(event: MessageEvent): void }).handleMessage({
|
|
210
|
-
data: '{"type":"endpoint:message","data":{"$adapter":"sandbox","endpoint":"bot"}}',
|
|
211
|
-
} as MessageEvent);
|
|
212
|
-
await Promise.resolve();
|
|
213
|
-
|
|
214
|
-
expect(received).toEqual([expect.objectContaining({
|
|
215
|
-
type: "message.receive",
|
|
216
|
-
data: expect.objectContaining({ adapter: "sandbox", endpointKey: "bot" }),
|
|
217
|
-
})]);
|
|
218
|
-
expect(mockApplyConsoleEvent).toHaveBeenCalledWith(expect.objectContaining({
|
|
219
|
-
type: "message.receive",
|
|
220
|
-
}));
|
|
221
237
|
});
|
|
222
238
|
|
|
223
239
|
it("stops reconnecting after maxReconnectAttempts", async () => {
|
|
@@ -225,7 +241,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
225
241
|
const fetchMock = mockConsoleTransport(() => sseResponse(500, null));
|
|
226
242
|
vi.stubGlobal("fetch", fetchMock);
|
|
227
243
|
|
|
228
|
-
const manager = new
|
|
244
|
+
const manager = new ConsoleTransport({
|
|
229
245
|
reconnectInterval: 10,
|
|
230
246
|
maxReconnectAttempts: 2,
|
|
231
247
|
});
|
|
@@ -270,7 +286,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
270
286
|
));
|
|
271
287
|
vi.stubGlobal('fetch', fetchMock);
|
|
272
288
|
const received: Array<{ eventId: number; delivery?: string }> = [];
|
|
273
|
-
const manager = new
|
|
289
|
+
const manager = new ConsoleTransport();
|
|
274
290
|
manager.onConsoleEvent('message.receive', (event) => received.push({ eventId: event.eventId, delivery: event.delivery }));
|
|
275
291
|
manager.connect();
|
|
276
292
|
await vi.advanceTimersByTimeAsync(0);
|
|
@@ -301,7 +317,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
301
317
|
});
|
|
302
318
|
vi.stubGlobal('fetch', fetchMock);
|
|
303
319
|
const received: number[] = [];
|
|
304
|
-
const manager = new
|
|
320
|
+
const manager = new ConsoleTransport();
|
|
305
321
|
manager.onConsoleEvent('message.receive', (event) => received.push(event.eventId));
|
|
306
322
|
const recover = manager as unknown as { recoverEventHistory(signal: AbortSignal): Promise<void> };
|
|
307
323
|
await recover.recoverEventHistory(new AbortController().signal);
|
|
@@ -320,7 +336,7 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
320
336
|
return { ...body, data: { ...body.data, gap: true, oldestAvailableEventId: 8 } };
|
|
321
337
|
};
|
|
322
338
|
vi.stubGlobal('fetch', vi.fn(async () => response));
|
|
323
|
-
const manager = new
|
|
339
|
+
const manager = new ConsoleTransport();
|
|
324
340
|
const gaps: unknown[] = [];
|
|
325
341
|
manager.onConsoleEventRecoveryGap((gap) => gaps.push(gap));
|
|
326
342
|
const recover = manager as unknown as { recoverEventHistory(signal: AbortSignal): Promise<void> };
|
|
@@ -340,10 +356,39 @@ describe("WebSocketManager REST/SSE transport", () => {
|
|
|
340
356
|
data: { adapter: 'sandbox', endpointKey: 'bot', content: 'history' },
|
|
341
357
|
};
|
|
342
358
|
vi.stubGlobal('fetch', vi.fn(async () => eventHistoryResponse([historyEvent], 'runtime-a', 1)));
|
|
343
|
-
const manager = new
|
|
359
|
+
const manager = new ConsoleTransport();
|
|
344
360
|
const recover = manager as unknown as { recoverEventHistory(signal: AbortSignal): Promise<void> };
|
|
345
361
|
await recover.recoverEventHistory(new AbortController().signal);
|
|
346
362
|
|
|
347
363
|
expect(Object.keys(stored).filter((key) => key.includes('event-cursor'))).toEqual([]);
|
|
348
364
|
});
|
|
365
|
+
|
|
366
|
+
it('makes disposal terminal and idempotent', () => {
|
|
367
|
+
const transport = new ConsoleTransport();
|
|
368
|
+
transport.dispose();
|
|
369
|
+
transport.dispose();
|
|
370
|
+
|
|
371
|
+
expect(transport.getState()).toBe(ConnectionState.DISCONNECTED);
|
|
372
|
+
expect(() => transport.connect()).toThrow('ConsoleTransport has been disposed');
|
|
373
|
+
expect(() => transport.onConnectionChange(() => {}))
|
|
374
|
+
.toThrow('ConsoleTransport has been disposed');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('rejects a stale connection result after disposal', async () => {
|
|
378
|
+
installBrowserGlobals();
|
|
379
|
+
let resolveHistory!: (response: Response) => void;
|
|
380
|
+
const fetchMock = vi.fn(() => new Promise<Response>((resolve) => {
|
|
381
|
+
resolveHistory = resolve;
|
|
382
|
+
}));
|
|
383
|
+
const transport = new ConsoleTransport({ fetch: fetchMock });
|
|
384
|
+
|
|
385
|
+
transport.connect();
|
|
386
|
+
transport.dispose();
|
|
387
|
+
resolveHistory(eventHistoryResponse());
|
|
388
|
+
await Promise.resolve();
|
|
389
|
+
await Promise.resolve();
|
|
390
|
+
|
|
391
|
+
expect(transport.getState()).toBe(ConnectionState.DISCONNECTED);
|
|
392
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
393
|
+
});
|
|
349
394
|
});
|