@trigger.dev/sdk 0.0.0-chat-prerelease-20260415164455 → 0.0.0-chat-prerelease-20260417152143
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/commonjs/v3/chat-react.d.ts +20 -0
- package/dist/commonjs/v3/chat-react.js +54 -0
- package/dist/commonjs/v3/chat-react.js.map +1 -1
- package/dist/commonjs/v3/chat-tab-coordinator.d.ts +65 -0
- package/dist/commonjs/v3/chat-tab-coordinator.js +235 -0
- package/dist/commonjs/v3/chat-tab-coordinator.js.map +1 -0
- package/dist/commonjs/v3/chat-tab-coordinator.test.d.ts +1 -0
- package/dist/commonjs/v3/chat-tab-coordinator.test.js +140 -0
- package/dist/commonjs/v3/chat-tab-coordinator.test.js.map +1 -0
- package/dist/commonjs/v3/chat.d.ts +42 -0
- package/dist/commonjs/v3/chat.js +90 -0
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/chat-react.d.ts +20 -0
- package/dist/esm/v3/chat-react.js +53 -0
- package/dist/esm/v3/chat-react.js.map +1 -1
- package/dist/esm/v3/chat-tab-coordinator.d.ts +65 -0
- package/dist/esm/v3/chat-tab-coordinator.js +231 -0
- package/dist/esm/v3/chat-tab-coordinator.js.map +1 -0
- package/dist/esm/v3/chat-tab-coordinator.test.d.ts +1 -0
- package/dist/esm/v3/chat-tab-coordinator.test.js +138 -0
- package/dist/esm/v3/chat-tab-coordinator.test.js.map +1 -0
- package/dist/esm/v3/chat.d.ts +42 -0
- package/dist/esm/v3/chat.js +90 -0
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -2
|
@@ -46,6 +46,26 @@ export type { InferChatUIMessage };
|
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
48
|
export declare function useTriggerChatTransport<TTask extends AnyTask = AnyTask>(options: UseTriggerChatTransportOptions<TTask>): TriggerChatTransport;
|
|
49
|
+
/**
|
|
50
|
+
* Sync chat messages across browser tabs.
|
|
51
|
+
*
|
|
52
|
+
* Requires `multiTab: true` on the transport. Handles:
|
|
53
|
+
* - Tracking read-only state (`isReadOnly`) when another tab is active
|
|
54
|
+
* - Broadcasting messages from the active tab to other tabs
|
|
55
|
+
* - Receiving messages from other tabs and updating local state via `setMessages`
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* const transport = useTriggerChatTransport({ task: "my-chat", multiTab: true, accessToken });
|
|
60
|
+
* const { messages, setMessages } = useChat({ id: chatId, transport });
|
|
61
|
+
* const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
|
|
62
|
+
*
|
|
63
|
+
* <input disabled={isReadOnly} placeholder={isReadOnly ? "Active in another tab" : "Type a message..."} />
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function useMultiTabChat<T = unknown>(transport: TriggerChatTransport, chatId: string, messages: T[], setMessages: (messages: T[]) => void): {
|
|
67
|
+
isReadOnly: boolean;
|
|
68
|
+
};
|
|
49
69
|
/** A pending message tracked by `usePendingMessages`. */
|
|
50
70
|
export type PendingMessage = {
|
|
51
71
|
id: string;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.useTriggerChatTransport = useTriggerChatTransport;
|
|
5
|
+
exports.useMultiTabChat = useMultiTabChat;
|
|
5
6
|
exports.usePendingMessages = usePendingMessages;
|
|
6
7
|
/**
|
|
7
8
|
* @module @trigger.dev/sdk/chat/react
|
|
@@ -74,8 +75,61 @@ function useTriggerChatTransport(options) {
|
|
|
74
75
|
(0, react_1.useEffect)(() => {
|
|
75
76
|
ref.current?.setTriggerTask(triggerTask);
|
|
76
77
|
}, [triggerTask]);
|
|
78
|
+
// Note: dispose() is NOT called in effect cleanup because React strict mode
|
|
79
|
+
// runs cleanup+re-setup, but the transport lives in a ref and isn't recreated.
|
|
80
|
+
// Calling dispose() would permanently close the BroadcastChannel.
|
|
81
|
+
// The coordinator's beforeunload handler handles tab close cleanup instead.
|
|
77
82
|
return ref.current;
|
|
78
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Sync chat messages across browser tabs.
|
|
86
|
+
*
|
|
87
|
+
* Requires `multiTab: true` on the transport. Handles:
|
|
88
|
+
* - Tracking read-only state (`isReadOnly`) when another tab is active
|
|
89
|
+
* - Broadcasting messages from the active tab to other tabs
|
|
90
|
+
* - Receiving messages from other tabs and updating local state via `setMessages`
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* const transport = useTriggerChatTransport({ task: "my-chat", multiTab: true, accessToken });
|
|
95
|
+
* const { messages, setMessages } = useChat({ id: chatId, transport });
|
|
96
|
+
* const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
|
|
97
|
+
*
|
|
98
|
+
* <input disabled={isReadOnly} placeholder={isReadOnly ? "Active in another tab" : "Type a message..."} />
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
function useMultiTabChat(transport, chatId, messages, setMessages) {
|
|
102
|
+
const [isReadOnly, setIsReadOnly] = (0, react_1.useState)(() => transport.isReadOnly(chatId));
|
|
103
|
+
// Track read-only state
|
|
104
|
+
(0, react_1.useEffect)(() => {
|
|
105
|
+
const listener = (id, readOnly) => {
|
|
106
|
+
if (id === chatId)
|
|
107
|
+
setIsReadOnly(readOnly);
|
|
108
|
+
};
|
|
109
|
+
transport.addReadOnlyListener(listener);
|
|
110
|
+
setIsReadOnly(transport.isReadOnly(chatId));
|
|
111
|
+
return () => transport.removeReadOnlyListener(listener);
|
|
112
|
+
}, [transport, chatId]);
|
|
113
|
+
// Active tab: broadcast messages to other tabs on change.
|
|
114
|
+
// Only broadcast when THIS tab holds the claim (is the current sender).
|
|
115
|
+
// Using !isReadOnly alone causes a feedback loop when both tabs are idle.
|
|
116
|
+
(0, react_1.useEffect)(() => {
|
|
117
|
+
if (transport.hasClaim(chatId) && messages.length > 0) {
|
|
118
|
+
transport.broadcastMessages(chatId, messages);
|
|
119
|
+
}
|
|
120
|
+
}, [transport, chatId, messages]);
|
|
121
|
+
// Read-only tab: receive messages from the active tab
|
|
122
|
+
(0, react_1.useEffect)(() => {
|
|
123
|
+
const listener = (id, msgs) => {
|
|
124
|
+
if (id === chatId) {
|
|
125
|
+
setMessages(msgs);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
transport.addMessagesListener(listener);
|
|
129
|
+
return () => transport.removeMessagesListener(listener);
|
|
130
|
+
}, [transport, chatId, setMessages]);
|
|
131
|
+
return { isReadOnly };
|
|
132
|
+
}
|
|
79
133
|
/**
|
|
80
134
|
* React hook for managing pending messages (steering) during streaming.
|
|
81
135
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-react.js","sourceRoot":"","sources":["../../../src/v3/chat-react.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;AAmFb,
|
|
1
|
+
{"version":3,"file":"chat-react.js","sourceRoot":"","sources":["../../../src/v3/chat-react.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordinates multi-tab access to chat sessions via BroadcastChannel.
|
|
3
|
+
*
|
|
4
|
+
* When multiple browser tabs open the same chat, only one can be the active
|
|
5
|
+
* sender. Others enter read-only mode. The coordinator uses a simple
|
|
6
|
+
* claim/release/heartbeat protocol to track ownership per chatId.
|
|
7
|
+
*
|
|
8
|
+
* Gracefully degrades to a no-op when BroadcastChannel is unavailable
|
|
9
|
+
* (SSR, Node.js, old browsers).
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
type ReadOnlyListener = (chatId: string, isReadOnly: boolean) => void;
|
|
14
|
+
type MessagesListener = (chatId: string, messages: unknown[]) => void;
|
|
15
|
+
type SessionListener = (chatId: string, session: {
|
|
16
|
+
lastEventId?: string;
|
|
17
|
+
}) => void;
|
|
18
|
+
export declare class ChatTabCoordinator {
|
|
19
|
+
private tabId;
|
|
20
|
+
private channel;
|
|
21
|
+
/** Claims held by OTHER tabs: chatId -> { tabId, lastSeen } */
|
|
22
|
+
private claims;
|
|
23
|
+
/** chatIds that THIS tab has claimed */
|
|
24
|
+
private myClaims;
|
|
25
|
+
private listeners;
|
|
26
|
+
private messagesListeners;
|
|
27
|
+
private sessionListeners;
|
|
28
|
+
private heartbeatTimer;
|
|
29
|
+
private beforeUnloadHandler;
|
|
30
|
+
constructor();
|
|
31
|
+
/**
|
|
32
|
+
* Attempt to claim a chatId for sending.
|
|
33
|
+
* Returns false if another tab already holds it.
|
|
34
|
+
*/
|
|
35
|
+
claim(chatId: string): boolean;
|
|
36
|
+
/** Release a chatId so other tabs can claim it. */
|
|
37
|
+
release(chatId: string): void;
|
|
38
|
+
/** Check if THIS tab currently holds a claim for the chatId. */
|
|
39
|
+
hasClaim(chatId: string): boolean;
|
|
40
|
+
/** Check if another tab holds this chatId. */
|
|
41
|
+
isReadOnly(chatId: string): boolean;
|
|
42
|
+
addListener(fn: ReadOnlyListener): void;
|
|
43
|
+
removeListener(fn: ReadOnlyListener): void;
|
|
44
|
+
/** Broadcast the current messages to other tabs (for real-time sync). */
|
|
45
|
+
broadcastMessages(chatId: string, messages: unknown[]): void;
|
|
46
|
+
addMessagesListener(fn: MessagesListener): void;
|
|
47
|
+
removeMessagesListener(fn: MessagesListener): void;
|
|
48
|
+
/** Broadcast session state (lastEventId) to other tabs. */
|
|
49
|
+
broadcastSession(chatId: string, session: {
|
|
50
|
+
lastEventId?: string;
|
|
51
|
+
}): void;
|
|
52
|
+
addSessionListener(fn: SessionListener): void;
|
|
53
|
+
removeSessionListener(fn: SessionListener): void;
|
|
54
|
+
/** Clean up channel, timers, and event listeners. */
|
|
55
|
+
dispose(): void;
|
|
56
|
+
private handleMessage;
|
|
57
|
+
private sendHeartbeats;
|
|
58
|
+
private expireStaleClaimsFromOtherTabs;
|
|
59
|
+
private releaseAll;
|
|
60
|
+
private broadcast;
|
|
61
|
+
private notify;
|
|
62
|
+
private notifyMessages;
|
|
63
|
+
private notifySession;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Coordinates multi-tab access to chat sessions via BroadcastChannel.
|
|
4
|
+
*
|
|
5
|
+
* When multiple browser tabs open the same chat, only one can be the active
|
|
6
|
+
* sender. Others enter read-only mode. The coordinator uses a simple
|
|
7
|
+
* claim/release/heartbeat protocol to track ownership per chatId.
|
|
8
|
+
*
|
|
9
|
+
* Gracefully degrades to a no-op when BroadcastChannel is unavailable
|
|
10
|
+
* (SSR, Node.js, old browsers).
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ChatTabCoordinator = void 0;
|
|
16
|
+
const CHANNEL_NAME = "trigger-chat-tab-coord";
|
|
17
|
+
const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
18
|
+
const HEARTBEAT_TIMEOUT_MS = 10_000;
|
|
19
|
+
class ChatTabCoordinator {
|
|
20
|
+
tabId;
|
|
21
|
+
channel = null;
|
|
22
|
+
/** Claims held by OTHER tabs: chatId -> { tabId, lastSeen } */
|
|
23
|
+
claims = new Map();
|
|
24
|
+
/** chatIds that THIS tab has claimed */
|
|
25
|
+
myClaims = new Set();
|
|
26
|
+
listeners = new Set();
|
|
27
|
+
messagesListeners = new Set();
|
|
28
|
+
sessionListeners = new Set();
|
|
29
|
+
heartbeatTimer = null;
|
|
30
|
+
beforeUnloadHandler = null;
|
|
31
|
+
constructor() {
|
|
32
|
+
this.tabId =
|
|
33
|
+
typeof crypto !== "undefined" && crypto.randomUUID
|
|
34
|
+
? crypto.randomUUID()
|
|
35
|
+
: `tab-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
36
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
37
|
+
return; // No-op mode
|
|
38
|
+
}
|
|
39
|
+
this.channel = new BroadcastChannel(CHANNEL_NAME);
|
|
40
|
+
this.channel.onmessage = (event) => {
|
|
41
|
+
this.handleMessage(event.data);
|
|
42
|
+
};
|
|
43
|
+
// Heartbeat: send for our claims + check for stale claims from other tabs
|
|
44
|
+
this.heartbeatTimer = setInterval(() => {
|
|
45
|
+
this.sendHeartbeats();
|
|
46
|
+
this.expireStaleClaimsFromOtherTabs();
|
|
47
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
48
|
+
// Best-effort release on tab close
|
|
49
|
+
this.beforeUnloadHandler = () => this.releaseAll();
|
|
50
|
+
if (typeof window !== "undefined") {
|
|
51
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Attempt to claim a chatId for sending.
|
|
56
|
+
* Returns false if another tab already holds it.
|
|
57
|
+
*/
|
|
58
|
+
claim(chatId) {
|
|
59
|
+
if (!this.channel)
|
|
60
|
+
return true; // No-op mode
|
|
61
|
+
const existing = this.claims.get(chatId);
|
|
62
|
+
if (existing && existing.tabId !== this.tabId) {
|
|
63
|
+
return false; // Another tab holds this chat
|
|
64
|
+
}
|
|
65
|
+
this.myClaims.add(chatId);
|
|
66
|
+
this.broadcast({ type: "claim", chatId, tabId: this.tabId });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/** Release a chatId so other tabs can claim it. */
|
|
70
|
+
release(chatId) {
|
|
71
|
+
if (!this.channel)
|
|
72
|
+
return;
|
|
73
|
+
if (!this.myClaims.has(chatId))
|
|
74
|
+
return;
|
|
75
|
+
this.myClaims.delete(chatId);
|
|
76
|
+
this.broadcast({ type: "release", chatId, tabId: this.tabId });
|
|
77
|
+
}
|
|
78
|
+
/** Check if THIS tab currently holds a claim for the chatId. */
|
|
79
|
+
hasClaim(chatId) {
|
|
80
|
+
return this.myClaims.has(chatId);
|
|
81
|
+
}
|
|
82
|
+
/** Check if another tab holds this chatId. */
|
|
83
|
+
isReadOnly(chatId) {
|
|
84
|
+
if (!this.channel)
|
|
85
|
+
return false;
|
|
86
|
+
const claim = this.claims.get(chatId);
|
|
87
|
+
return claim != null && claim.tabId !== this.tabId;
|
|
88
|
+
}
|
|
89
|
+
addListener(fn) {
|
|
90
|
+
this.listeners.add(fn);
|
|
91
|
+
}
|
|
92
|
+
removeListener(fn) {
|
|
93
|
+
this.listeners.delete(fn);
|
|
94
|
+
}
|
|
95
|
+
/** Broadcast the current messages to other tabs (for real-time sync). */
|
|
96
|
+
broadcastMessages(chatId, messages) {
|
|
97
|
+
if (!this.channel)
|
|
98
|
+
return;
|
|
99
|
+
this.broadcast({ type: "messages", chatId, tabId: this.tabId, messages });
|
|
100
|
+
}
|
|
101
|
+
addMessagesListener(fn) {
|
|
102
|
+
this.messagesListeners.add(fn);
|
|
103
|
+
}
|
|
104
|
+
removeMessagesListener(fn) {
|
|
105
|
+
this.messagesListeners.delete(fn);
|
|
106
|
+
}
|
|
107
|
+
/** Broadcast session state (lastEventId) to other tabs. */
|
|
108
|
+
broadcastSession(chatId, session) {
|
|
109
|
+
if (!this.channel)
|
|
110
|
+
return;
|
|
111
|
+
this.broadcast({ type: "session", chatId, tabId: this.tabId, session });
|
|
112
|
+
}
|
|
113
|
+
addSessionListener(fn) {
|
|
114
|
+
this.sessionListeners.add(fn);
|
|
115
|
+
}
|
|
116
|
+
removeSessionListener(fn) {
|
|
117
|
+
this.sessionListeners.delete(fn);
|
|
118
|
+
}
|
|
119
|
+
/** Clean up channel, timers, and event listeners. */
|
|
120
|
+
dispose() {
|
|
121
|
+
this.releaseAll();
|
|
122
|
+
if (this.heartbeatTimer) {
|
|
123
|
+
clearInterval(this.heartbeatTimer);
|
|
124
|
+
this.heartbeatTimer = null;
|
|
125
|
+
}
|
|
126
|
+
if (this.beforeUnloadHandler && typeof window !== "undefined") {
|
|
127
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
128
|
+
this.beforeUnloadHandler = null;
|
|
129
|
+
}
|
|
130
|
+
if (this.channel) {
|
|
131
|
+
this.channel.close();
|
|
132
|
+
this.channel = null;
|
|
133
|
+
}
|
|
134
|
+
this.listeners.clear();
|
|
135
|
+
this.messagesListeners.clear();
|
|
136
|
+
this.sessionListeners.clear();
|
|
137
|
+
}
|
|
138
|
+
// --- Private ---
|
|
139
|
+
handleMessage(msg) {
|
|
140
|
+
if (msg.tabId === this.tabId)
|
|
141
|
+
return; // Ignore own messages
|
|
142
|
+
switch (msg.type) {
|
|
143
|
+
case "claim": {
|
|
144
|
+
const wasReadOnly = this.isReadOnly(msg.chatId);
|
|
145
|
+
this.claims.set(msg.chatId, { tabId: msg.tabId, lastSeen: Date.now() });
|
|
146
|
+
if (!wasReadOnly) {
|
|
147
|
+
this.notify(msg.chatId, true);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "release": {
|
|
152
|
+
const claim = this.claims.get(msg.chatId);
|
|
153
|
+
if (claim && claim.tabId === msg.tabId) {
|
|
154
|
+
this.claims.delete(msg.chatId);
|
|
155
|
+
this.notify(msg.chatId, false);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "heartbeat": {
|
|
160
|
+
const claim = this.claims.get(msg.chatId);
|
|
161
|
+
if (claim && claim.tabId === msg.tabId) {
|
|
162
|
+
claim.lastSeen = Date.now();
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "messages": {
|
|
167
|
+
this.notifyMessages(msg.chatId, msg.messages);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "session": {
|
|
171
|
+
this.notifySession(msg.chatId, msg.session);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
sendHeartbeats() {
|
|
177
|
+
for (const chatId of this.myClaims) {
|
|
178
|
+
this.broadcast({ type: "heartbeat", chatId, tabId: this.tabId });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
expireStaleClaimsFromOtherTabs() {
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
for (const [chatId, claim] of this.claims) {
|
|
184
|
+
if (claim.tabId !== this.tabId && now - claim.lastSeen > HEARTBEAT_TIMEOUT_MS) {
|
|
185
|
+
this.claims.delete(chatId);
|
|
186
|
+
this.notify(chatId, false);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
releaseAll() {
|
|
191
|
+
for (const chatId of [...this.myClaims]) {
|
|
192
|
+
this.release(chatId);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
broadcast(msg) {
|
|
196
|
+
try {
|
|
197
|
+
this.channel?.postMessage(msg);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Channel may be closed
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
notify(chatId, isReadOnly) {
|
|
204
|
+
for (const fn of this.listeners) {
|
|
205
|
+
try {
|
|
206
|
+
fn(chatId, isReadOnly);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Non-fatal
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
notifyMessages(chatId, messages) {
|
|
214
|
+
for (const fn of this.messagesListeners) {
|
|
215
|
+
try {
|
|
216
|
+
fn(chatId, messages);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Non-fatal
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
notifySession(chatId, session) {
|
|
224
|
+
for (const fn of this.sessionListeners) {
|
|
225
|
+
try {
|
|
226
|
+
fn(chatId, session);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Non-fatal
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
exports.ChatTabCoordinator = ChatTabCoordinator;
|
|
235
|
+
//# sourceMappingURL=chat-tab-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-tab-coordinator.js","sourceRoot":"","sources":["../../../src/v3/chat-tab-coordinator.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAEH,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAC9C,MAAM,qBAAqB,GAAG,KAAK,CAAC;AACpC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAapC,MAAa,kBAAkB;IACrB,KAAK,CAAS;IACd,OAAO,GAA4B,IAAI,CAAC;IAChD,+DAA+D;IACvD,MAAM,GAAG,IAAI,GAAG,EAA+C,CAAC;IACxE,wCAAwC;IAChC,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAC7B,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IACxC,iBAAiB,GAAG,IAAI,GAAG,EAAoB,CAAC;IAChD,gBAAgB,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC9C,cAAc,GAA0C,IAAI,CAAC;IAC7D,mBAAmB,GAAwB,IAAI,CAAC;IAExD;QACE,IAAI,CAAC,KAAK;YACR,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU;gBAChD,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE;gBACrB,CAAC,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAEpE,IAAI,OAAO,gBAAgB,KAAK,WAAW,EAAE,CAAC;YAC5C,OAAO,CAAC,aAAa;QACvB,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,KAA+B,EAAE,EAAE;YAC3D,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC,CAAC;QAEF,0EAA0E;QAC1E,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,IAAI,CAAC,8BAA8B,EAAE,CAAC;QACxC,CAAC,EAAE,qBAAqB,CAAC,CAAC;QAE1B,mCAAmC;QACnC,IAAI,CAAC,mBAAmB,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACnD,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,gBAAgB,CAAC,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAc;QAClB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,CAAC,aAAa;QAE7C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9C,OAAO,KAAK,CAAC,CAAC,8BAA8B;QAC9C,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mDAAmD;IACnD,OAAO,CAAC,MAAc;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO;QAEvC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,gEAAgE;IAChE,QAAQ,CAAC,MAAc;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,8CAA8C;IAC9C,UAAU,CAAC,MAAc;QACvB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAEhC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC;IACrD,CAAC;IAED,WAAW,CAAC,EAAoB;QAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IAED,cAAc,CAAC,EAAoB;QACjC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,yEAAyE;IACzE,iBAAiB,CAAC,MAAc,EAAE,QAAmB;QACnD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,mBAAmB,CAAC,EAAoB;QACtC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,sBAAsB,CAAC,EAAoB;QACzC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,2DAA2D;IAC3D,gBAAgB,CAAC,MAAc,EAAE,OAAiC;QAChE,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,kBAAkB,CAAC,EAAmB;QACpC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;IAED,qBAAqB,CAAC,EAAmB;QACvC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,qDAAqD;IACrD,OAAO;QACL,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QAED,IAAI,IAAI,CAAC,mBAAmB,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAC9D,MAAM,CAAC,mBAAmB,CAAC,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACrE,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAClC,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED,kBAAkB;IAEV,aAAa,CAAC,GAAe;QACnC,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;YAAE,OAAO,CAAC,sBAAsB;QAE5D,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAChD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACxE,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAChC,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC1C,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC;oBACvC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBAC/B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBACjC,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC1C,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC;oBACvC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC9B,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,UAAU,CAAC,CAAC,CAAC;gBAChB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAC9C,MAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC5C,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAEO,cAAc;QACpB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAEO,8BAA8B;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,QAAQ,GAAG,oBAAoB,EAAE,CAAC;gBAC9E,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC3B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAEO,UAAU;QAChB,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAEO,SAAS,CAAC,GAAe;QAC/B,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,MAAc,EAAE,UAAmB;QAChD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YACzB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,MAAc,EAAE,QAAmB;QACxD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACxC,IAAI,CAAC;gBACH,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,MAAc,EAAE,OAAiC;QACrE,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACvC,IAAI,CAAC;gBACH,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC;CACF;AA/OD,gDA+OC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const chat_tab_coordinator_js_1 = require("./chat-tab-coordinator.js");
|
|
5
|
+
// Mock BroadcastChannel for testing
|
|
6
|
+
class MockBroadcastChannel {
|
|
7
|
+
name;
|
|
8
|
+
static instances = [];
|
|
9
|
+
onmessage = null;
|
|
10
|
+
closed = false;
|
|
11
|
+
constructor(name) {
|
|
12
|
+
this.name = name;
|
|
13
|
+
MockBroadcastChannel.instances.push(this);
|
|
14
|
+
}
|
|
15
|
+
postMessage(data) {
|
|
16
|
+
if (this.closed)
|
|
17
|
+
return;
|
|
18
|
+
// Deliver to all OTHER instances on the same channel
|
|
19
|
+
for (const instance of MockBroadcastChannel.instances) {
|
|
20
|
+
if (instance !== this && instance.name === this.name && !instance.closed) {
|
|
21
|
+
instance.onmessage?.({ data });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
close() {
|
|
26
|
+
this.closed = true;
|
|
27
|
+
MockBroadcastChannel.instances = MockBroadcastChannel.instances.filter((i) => i !== this);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
(0, vitest_1.describe)("ChatTabCoordinator", () => {
|
|
31
|
+
(0, vitest_1.beforeEach)(() => {
|
|
32
|
+
MockBroadcastChannel.instances = [];
|
|
33
|
+
vitest_1.vi.stubGlobal("BroadcastChannel", MockBroadcastChannel);
|
|
34
|
+
vitest_1.vi.useFakeTimers();
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.afterEach)(() => {
|
|
37
|
+
vitest_1.vi.restoreAllMocks();
|
|
38
|
+
vitest_1.vi.useRealTimers();
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)("tab A claims, tab B sees isReadOnly", () => {
|
|
41
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
42
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
43
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
|
|
44
|
+
a.claim("chat-1");
|
|
45
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
|
|
46
|
+
(0, vitest_1.expect)(a.isReadOnly("chat-1")).toBe(false); // Owner is not read-only
|
|
47
|
+
a.dispose();
|
|
48
|
+
b.dispose();
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)("tab A releases, tab B sees isReadOnly = false", () => {
|
|
51
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
52
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
53
|
+
a.claim("chat-1");
|
|
54
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
|
|
55
|
+
a.release("chat-1");
|
|
56
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
|
|
57
|
+
a.dispose();
|
|
58
|
+
b.dispose();
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)("fires listener on claim and release", () => {
|
|
61
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
62
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
63
|
+
const listener = vitest_1.vi.fn();
|
|
64
|
+
b.addListener(listener);
|
|
65
|
+
a.claim("chat-1");
|
|
66
|
+
(0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", true);
|
|
67
|
+
a.release("chat-1");
|
|
68
|
+
(0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", false);
|
|
69
|
+
a.dispose();
|
|
70
|
+
b.dispose();
|
|
71
|
+
});
|
|
72
|
+
(0, vitest_1.it)("removeListener stops notifications", () => {
|
|
73
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
74
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
75
|
+
const listener = vitest_1.vi.fn();
|
|
76
|
+
b.addListener(listener);
|
|
77
|
+
b.removeListener(listener);
|
|
78
|
+
a.claim("chat-1");
|
|
79
|
+
(0, vitest_1.expect)(listener).not.toHaveBeenCalled();
|
|
80
|
+
a.dispose();
|
|
81
|
+
b.dispose();
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)("claim returns false when another tab holds the chatId", () => {
|
|
84
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
85
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
86
|
+
(0, vitest_1.expect)(a.claim("chat-1")).toBe(true);
|
|
87
|
+
(0, vitest_1.expect)(b.claim("chat-1")).toBe(false);
|
|
88
|
+
a.dispose();
|
|
89
|
+
b.dispose();
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.it)("supports multiple independent chatIds", () => {
|
|
92
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
93
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
94
|
+
a.claim("chat-1");
|
|
95
|
+
b.claim("chat-2");
|
|
96
|
+
(0, vitest_1.expect)(a.isReadOnly("chat-1")).toBe(false);
|
|
97
|
+
(0, vitest_1.expect)(a.isReadOnly("chat-2")).toBe(true);
|
|
98
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
|
|
99
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(false);
|
|
100
|
+
a.dispose();
|
|
101
|
+
b.dispose();
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.it)("heartbeat timeout clears stale claim from crashed tab", () => {
|
|
104
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
105
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
106
|
+
const listener = vitest_1.vi.fn();
|
|
107
|
+
b.addListener(listener);
|
|
108
|
+
a.claim("chat-1");
|
|
109
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
|
|
110
|
+
// Simulate tab A crashing (close its channel, stop heartbeats)
|
|
111
|
+
a.dispose();
|
|
112
|
+
// Advance past heartbeat timeout (10s)
|
|
113
|
+
vitest_1.vi.advanceTimersByTime(11_000);
|
|
114
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
|
|
115
|
+
(0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", false);
|
|
116
|
+
b.dispose();
|
|
117
|
+
});
|
|
118
|
+
(0, vitest_1.it)("dispose releases all claims", () => {
|
|
119
|
+
const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
120
|
+
const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
121
|
+
a.claim("chat-1");
|
|
122
|
+
a.claim("chat-2");
|
|
123
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
|
|
124
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(true);
|
|
125
|
+
a.dispose();
|
|
126
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
|
|
127
|
+
(0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(false);
|
|
128
|
+
b.dispose();
|
|
129
|
+
});
|
|
130
|
+
(0, vitest_1.it)("gracefully degrades when BroadcastChannel is unavailable", () => {
|
|
131
|
+
vitest_1.vi.stubGlobal("BroadcastChannel", undefined);
|
|
132
|
+
const coord = new chat_tab_coordinator_js_1.ChatTabCoordinator();
|
|
133
|
+
// All operations are no-ops
|
|
134
|
+
(0, vitest_1.expect)(coord.claim("chat-1")).toBe(true);
|
|
135
|
+
(0, vitest_1.expect)(coord.isReadOnly("chat-1")).toBe(false);
|
|
136
|
+
coord.release("chat-1"); // No error
|
|
137
|
+
coord.dispose(); // No error
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=chat-tab-coordinator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-tab-coordinator.test.js","sourceRoot":"","sources":["../../../src/v3/chat-tab-coordinator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -222,6 +222,19 @@ type TriggerChatTransportOptionsBase<TClientData = unknown> = {
|
|
|
222
222
|
* Receives `chatId` and `runId` so your server action can persist the new PAT keyed by conversation.
|
|
223
223
|
*/
|
|
224
224
|
renewRunAccessToken?: (params: RenewRunAccessTokenParams) => string | undefined | null | Promise<string | undefined | null>;
|
|
225
|
+
/**
|
|
226
|
+
* Enable multi-tab coordination. When `true`, only one browser tab
|
|
227
|
+
* can send messages to a given chatId at a time. Other tabs enter
|
|
228
|
+
* read-only mode. Uses `BroadcastChannel` for cross-tab communication.
|
|
229
|
+
*
|
|
230
|
+
* Use `transport.isReadOnly(chatId)` or the `useChatTabCoordination` hook
|
|
231
|
+
* to check read-only state and disable the input UI.
|
|
232
|
+
*
|
|
233
|
+
* No-op when `BroadcastChannel` is unavailable (SSR, Node.js).
|
|
234
|
+
*
|
|
235
|
+
* @default false
|
|
236
|
+
*/
|
|
237
|
+
multiTab?: boolean;
|
|
225
238
|
/**
|
|
226
239
|
* Read-only "watch" mode for observing an existing chat run from the
|
|
227
240
|
* outside (e.g. a dashboard viewer that wants to show an agent run's
|
|
@@ -322,6 +335,7 @@ export declare class TriggerChatTransport implements ChatTransport<UIMessage> {
|
|
|
322
335
|
private readonly defaultMetadata;
|
|
323
336
|
private readonly triggerOptions;
|
|
324
337
|
private readonly watchMode;
|
|
338
|
+
private coordinator;
|
|
325
339
|
private _onSessionChange;
|
|
326
340
|
private renewRunAccessToken;
|
|
327
341
|
private sessions;
|
|
@@ -434,6 +448,34 @@ export declare class TriggerChatTransport implements ChatTransport<UIMessage> {
|
|
|
434
448
|
* Update the server-side trigger callback without recreating the transport.
|
|
435
449
|
*/
|
|
436
450
|
setTriggerTask(fn: ((params: TriggerChatTaskParams) => Promise<TriggerChatTaskResult>) | undefined): void;
|
|
451
|
+
/**
|
|
452
|
+
* Check if a chatId is read-only (another tab is actively sending).
|
|
453
|
+
* Always returns `false` when `multiTab` is not enabled.
|
|
454
|
+
*/
|
|
455
|
+
isReadOnly(chatId: string): boolean;
|
|
456
|
+
/**
|
|
457
|
+
* Check if THIS tab currently holds a claim for the chatId
|
|
458
|
+
* (i.e. this tab called sendMessages and the turn is in-flight).
|
|
459
|
+
*/
|
|
460
|
+
hasClaim(chatId: string): boolean;
|
|
461
|
+
/**
|
|
462
|
+
* Listen for read-only state changes across tabs.
|
|
463
|
+
* The listener receives `(chatId, isReadOnly)`.
|
|
464
|
+
*/
|
|
465
|
+
addReadOnlyListener(fn: (chatId: string, isReadOnly: boolean) => void): void;
|
|
466
|
+
/** Remove a previously added read-only listener. */
|
|
467
|
+
removeReadOnlyListener(fn: (chatId: string, isReadOnly: boolean) => void): void;
|
|
468
|
+
/** Broadcast messages to other tabs for real-time sync. */
|
|
469
|
+
broadcastMessages(chatId: string, messages: unknown[]): void;
|
|
470
|
+
/** Listen for message updates from other tabs. */
|
|
471
|
+
addMessagesListener(fn: (chatId: string, messages: unknown[]) => void): void;
|
|
472
|
+
/** Remove a previously added messages listener. */
|
|
473
|
+
removeMessagesListener(fn: (chatId: string, messages: unknown[]) => void): void;
|
|
474
|
+
/**
|
|
475
|
+
* Clean up resources (BroadcastChannel, heartbeat timers).
|
|
476
|
+
* Call on component unmount when using `multiTab`.
|
|
477
|
+
*/
|
|
478
|
+
dispose(): void;
|
|
437
479
|
/**
|
|
438
480
|
* Inject or update a session for a chat. Useful for resuming conversations
|
|
439
481
|
* from persisted state without recreating the transport.
|