@gigabuddy/chat-sdk 0.1.3 → 0.1.4
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/index.js +537 -133
- package/index.js.map +3 -3
- package/package.json +1 -1
- package/src/index.d.ts +1 -1
- package/src/lib/Conversation.d.ts +27 -25
- package/src/lib/GigabuddyChat.d.ts +61 -13
- package/src/lib/types.d.ts +99 -2
package/index.js
CHANGED
|
@@ -1,3 +1,291 @@
|
|
|
1
|
+
// libs/chat-sdk/src/lib/Conversation.ts
|
|
2
|
+
var Conversation = class {
|
|
3
|
+
conversationId;
|
|
4
|
+
config;
|
|
5
|
+
eventBus;
|
|
6
|
+
unsubscribes = [];
|
|
7
|
+
constructor(conversationId, config, eventBus) {
|
|
8
|
+
this.conversationId = conversationId;
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.eventBus = eventBus ?? null;
|
|
11
|
+
}
|
|
12
|
+
async buildHeaders() {
|
|
13
|
+
const token = await this.config.getToken();
|
|
14
|
+
const headers = {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
Authorization: `Bearer ${token}`
|
|
17
|
+
};
|
|
18
|
+
if (this.config.userId) {
|
|
19
|
+
headers["X-Chat-User-Id"] = this.config.userId;
|
|
20
|
+
}
|
|
21
|
+
return headers;
|
|
22
|
+
}
|
|
23
|
+
// ===========================================================================
|
|
24
|
+
// Messages
|
|
25
|
+
// ===========================================================================
|
|
26
|
+
async sendMessage(content) {
|
|
27
|
+
const headers = await this.buildHeaders();
|
|
28
|
+
const body = {
|
|
29
|
+
conversationId: this.conversationId
|
|
30
|
+
};
|
|
31
|
+
if (typeof content === "string") {
|
|
32
|
+
body["text"] = content;
|
|
33
|
+
} else {
|
|
34
|
+
body["content"] = content;
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(`${this.config.apiUrl}/chat/send-message`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers,
|
|
39
|
+
body: JSON.stringify(body)
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const responseBody = await response.text();
|
|
43
|
+
throw new Error(`Failed to send message: ${response.status} ${responseBody}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async replyTo(messageId, content) {
|
|
47
|
+
const headers = await this.buildHeaders();
|
|
48
|
+
const body = {
|
|
49
|
+
conversationId: this.conversationId,
|
|
50
|
+
parentMessageId: messageId
|
|
51
|
+
};
|
|
52
|
+
if (typeof content === "string") {
|
|
53
|
+
body["text"] = content;
|
|
54
|
+
} else {
|
|
55
|
+
body["content"] = content;
|
|
56
|
+
}
|
|
57
|
+
const response = await fetch(`${this.config.apiUrl}/chat/reply-to-message`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers,
|
|
60
|
+
body: JSON.stringify(body)
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const responseBody = await response.text();
|
|
64
|
+
throw new Error(`Failed to reply to message: ${response.status} ${responseBody}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async editMessage(messageId, content) {
|
|
68
|
+
const headers = await this.buildHeaders();
|
|
69
|
+
const response = await fetch(`${this.config.apiUrl}/chat/edit-message`, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers,
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
conversationId: this.conversationId,
|
|
74
|
+
messageId,
|
|
75
|
+
content
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const body = await response.text();
|
|
80
|
+
throw new Error(`Failed to edit message: ${response.status} ${body}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async deleteMessage(messageId) {
|
|
84
|
+
const headers = await this.buildHeaders();
|
|
85
|
+
const response = await fetch(`${this.config.apiUrl}/chat/delete-message`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers,
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
conversationId: this.conversationId,
|
|
90
|
+
messageId
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const body = await response.text();
|
|
95
|
+
throw new Error(`Failed to delete message: ${response.status} ${body}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ===========================================================================
|
|
99
|
+
// Reactions
|
|
100
|
+
// ===========================================================================
|
|
101
|
+
async addReaction(messageId, emoji) {
|
|
102
|
+
const headers = await this.buildHeaders();
|
|
103
|
+
const response = await fetch(`${this.config.apiUrl}/chat/add-reaction`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers,
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
conversationId: this.conversationId,
|
|
108
|
+
messageId,
|
|
109
|
+
emoji
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
const body = await response.text();
|
|
114
|
+
throw new Error(`Failed to add reaction: ${response.status} ${body}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async removeReaction(messageId, emoji) {
|
|
118
|
+
const headers = await this.buildHeaders();
|
|
119
|
+
const response = await fetch(`${this.config.apiUrl}/chat/remove-reaction`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers,
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
conversationId: this.conversationId,
|
|
124
|
+
messageId,
|
|
125
|
+
emoji
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const body = await response.text();
|
|
130
|
+
throw new Error(`Failed to remove reaction: ${response.status} ${body}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ===========================================================================
|
|
134
|
+
// Read state & metadata
|
|
135
|
+
// ===========================================================================
|
|
136
|
+
async markRead(messageId) {
|
|
137
|
+
const headers = await this.buildHeaders();
|
|
138
|
+
const response = await fetch(`${this.config.apiUrl}/chat/mark-read`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers,
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
conversationId: this.conversationId,
|
|
143
|
+
...messageId ? { messageId } : {}
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
const body = await response.text();
|
|
148
|
+
throw new Error(`Failed to mark read: ${response.status} ${body}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async updateTopic(topic) {
|
|
152
|
+
const headers = await this.buildHeaders();
|
|
153
|
+
const response = await fetch(`${this.config.apiUrl}/chat/update-channel`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers,
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
conversationId: this.conversationId,
|
|
158
|
+
topic
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
const body = await response.text();
|
|
163
|
+
throw new Error(`Failed to update topic: ${response.status} ${body}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ===========================================================================
|
|
167
|
+
// Participants
|
|
168
|
+
// ===========================================================================
|
|
169
|
+
async addParticipant(userId) {
|
|
170
|
+
const headers = await this.buildHeaders();
|
|
171
|
+
const response = await fetch(`${this.config.apiUrl}/chat/invite-to-channel`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers,
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
conversationId: this.conversationId,
|
|
176
|
+
userId
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
const body = await response.text();
|
|
181
|
+
throw new Error(`Failed to add participant: ${response.status} ${body}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async removeParticipant(userId) {
|
|
185
|
+
const headers = await this.buildHeaders();
|
|
186
|
+
const response = await fetch(`${this.config.apiUrl}/chat/remove-participant`, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers,
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
conversationId: this.conversationId,
|
|
191
|
+
userId
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
const body = await response.text();
|
|
196
|
+
throw new Error(`Failed to remove participant: ${response.status} ${body}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async leave() {
|
|
200
|
+
const headers = await this.buildHeaders();
|
|
201
|
+
const response = await fetch(`${this.config.apiUrl}/chat/leave-conversation`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers,
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
conversationId: this.conversationId
|
|
206
|
+
})
|
|
207
|
+
});
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
const body = await response.text();
|
|
210
|
+
throw new Error(`Failed to leave conversation: ${response.status} ${body}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ===========================================================================
|
|
214
|
+
// Message history
|
|
215
|
+
// ===========================================================================
|
|
216
|
+
async getMessages(opts) {
|
|
217
|
+
const headers = await this.buildHeaders();
|
|
218
|
+
const response = await fetch(`${this.config.apiUrl}/chat/get-messages`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers,
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
conversationId: this.conversationId,
|
|
223
|
+
...opts?.limit ? { limit: opts.limit } : {},
|
|
224
|
+
...opts?.before ? { before: opts.before } : {}
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
const body = await response.text();
|
|
229
|
+
throw new Error(`Failed to get messages: ${response.status} ${body}`);
|
|
230
|
+
}
|
|
231
|
+
const data = await response.json();
|
|
232
|
+
return data.messages ?? [];
|
|
233
|
+
}
|
|
234
|
+
async getThreadReplies(parentMessageId, opts) {
|
|
235
|
+
const headers = await this.buildHeaders();
|
|
236
|
+
const response = await fetch(`${this.config.apiUrl}/chat/get-thread-replies`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers,
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
conversationId: this.conversationId,
|
|
241
|
+
parentMessageId,
|
|
242
|
+
...opts?.limit ? { limit: opts.limit } : {},
|
|
243
|
+
...opts?.before ? { before: opts.before } : {}
|
|
244
|
+
})
|
|
245
|
+
});
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
const body = await response.text();
|
|
248
|
+
throw new Error(`Failed to get thread replies: ${response.status} ${body}`);
|
|
249
|
+
}
|
|
250
|
+
const data = await response.json();
|
|
251
|
+
return data.messages ?? [];
|
|
252
|
+
}
|
|
253
|
+
// ===========================================================================
|
|
254
|
+
// Event listeners — route through parent user stream
|
|
255
|
+
// ===========================================================================
|
|
256
|
+
/**
|
|
257
|
+
* Register an event handler filtered to this conversation.
|
|
258
|
+
* Events are received from the parent GigabuddyChat's user stream.
|
|
259
|
+
* Returns an unsubscribe function.
|
|
260
|
+
*/
|
|
261
|
+
on(event, handler) {
|
|
262
|
+
if (this.eventBus) {
|
|
263
|
+
const unsub = this.eventBus.addConversationListener(
|
|
264
|
+
this.conversationId,
|
|
265
|
+
event,
|
|
266
|
+
handler
|
|
267
|
+
);
|
|
268
|
+
this.unsubscribes.push(unsub);
|
|
269
|
+
return unsub;
|
|
270
|
+
}
|
|
271
|
+
return () => {
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// ===========================================================================
|
|
275
|
+
// SSE lifecycle — no-ops (stream managed by GigabuddyChat)
|
|
276
|
+
// ===========================================================================
|
|
277
|
+
/** @deprecated No-op — SSE is managed by GigabuddyChat.connect() */
|
|
278
|
+
async connect() {
|
|
279
|
+
}
|
|
280
|
+
/** @deprecated No-op — SSE is managed by GigabuddyChat.disconnect() */
|
|
281
|
+
disconnect() {
|
|
282
|
+
for (const unsub of this.unsubscribes) {
|
|
283
|
+
unsub();
|
|
284
|
+
}
|
|
285
|
+
this.unsubscribes = [];
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
1
289
|
// libs/chat-sdk/src/lib/SSEClient.ts
|
|
2
290
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
3
291
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
@@ -59,7 +347,7 @@ var SSEClient = class {
|
|
|
59
347
|
throw new Error("SSE response has no body");
|
|
60
348
|
}
|
|
61
349
|
this.reconnectAttempt = 0;
|
|
62
|
-
|
|
350
|
+
this.readStream(response.body);
|
|
63
351
|
} catch (error) {
|
|
64
352
|
if (this.stopped)
|
|
65
353
|
return;
|
|
@@ -141,98 +429,51 @@ var SSEClient = class {
|
|
|
141
429
|
}
|
|
142
430
|
};
|
|
143
431
|
|
|
144
|
-
// libs/chat-sdk/src/lib/
|
|
145
|
-
var
|
|
146
|
-
conversationId;
|
|
432
|
+
// libs/chat-sdk/src/lib/GigabuddyChat.ts
|
|
433
|
+
var GigabuddyChat = class {
|
|
147
434
|
config;
|
|
148
|
-
|
|
435
|
+
conversations = /* @__PURE__ */ new Map();
|
|
436
|
+
// User stream SSE
|
|
149
437
|
sseClient = null;
|
|
150
|
-
listeners = /* @__PURE__ */ new Map();
|
|
151
438
|
tokenRefreshTimer = null;
|
|
152
|
-
|
|
153
|
-
|
|
439
|
+
_connected = false;
|
|
440
|
+
// Global event listeners (across all conversations)
|
|
441
|
+
globalListeners = /* @__PURE__ */ new Map();
|
|
442
|
+
// Per-conversation event listeners
|
|
443
|
+
conversationListeners = /* @__PURE__ */ new Map();
|
|
444
|
+
// Derived URL parts
|
|
445
|
+
baseUrl;
|
|
446
|
+
projectId;
|
|
447
|
+
constructor(config) {
|
|
154
448
|
this.config = config;
|
|
155
449
|
const url = new URL(config.apiUrl);
|
|
156
450
|
this.baseUrl = url.origin;
|
|
451
|
+
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
452
|
+
this.projectId = pathParts[pathParts.length - 1];
|
|
157
453
|
}
|
|
454
|
+
// ===========================================================================
|
|
455
|
+
// User stream connection
|
|
456
|
+
// ===========================================================================
|
|
158
457
|
/**
|
|
159
|
-
*
|
|
160
|
-
* Calls the send-message action via the /chat/ proxy route.
|
|
161
|
-
*/
|
|
162
|
-
async buildHeaders() {
|
|
163
|
-
const token = await this.config.getToken();
|
|
164
|
-
const headers = {
|
|
165
|
-
"Content-Type": "application/json",
|
|
166
|
-
Authorization: `Bearer ${token}`
|
|
167
|
-
};
|
|
168
|
-
if (this.config.userId) {
|
|
169
|
-
headers["X-Chat-User-Id"] = this.config.userId;
|
|
170
|
-
}
|
|
171
|
-
return headers;
|
|
172
|
-
}
|
|
173
|
-
async sendMessage(text) {
|
|
174
|
-
const headers = await this.buildHeaders();
|
|
175
|
-
const response = await fetch(`${this.config.apiUrl}/chat/send-message`, {
|
|
176
|
-
method: "POST",
|
|
177
|
-
headers,
|
|
178
|
-
body: JSON.stringify({
|
|
179
|
-
conversationId: this.conversationId,
|
|
180
|
-
text
|
|
181
|
-
})
|
|
182
|
-
});
|
|
183
|
-
if (!response.ok) {
|
|
184
|
-
const body = await response.text();
|
|
185
|
-
throw new Error(`Failed to send message: ${response.status} ${body}`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Get messages for this conversation.
|
|
190
|
-
*/
|
|
191
|
-
async getMessages(opts) {
|
|
192
|
-
const headers = await this.buildHeaders();
|
|
193
|
-
const response = await fetch(`${this.config.apiUrl}/chat/get-messages`, {
|
|
194
|
-
method: "POST",
|
|
195
|
-
headers,
|
|
196
|
-
body: JSON.stringify({
|
|
197
|
-
conversationId: this.conversationId,
|
|
198
|
-
...opts?.limit ? { limit: opts.limit } : {},
|
|
199
|
-
...opts?.before ? { before: opts.before } : {}
|
|
200
|
-
})
|
|
201
|
-
});
|
|
202
|
-
if (!response.ok) {
|
|
203
|
-
const body = await response.text();
|
|
204
|
-
throw new Error(`Failed to get messages: ${response.status} ${body}`);
|
|
205
|
-
}
|
|
206
|
-
const data = await response.json();
|
|
207
|
-
return data.messages ?? [];
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Register an event handler. Returns an unsubscribe function.
|
|
211
|
-
*/
|
|
212
|
-
on(event, handler) {
|
|
213
|
-
if (!this.listeners.has(event)) {
|
|
214
|
-
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
215
|
-
}
|
|
216
|
-
const handlers = this.listeners.get(event);
|
|
217
|
-
handlers.add(handler);
|
|
218
|
-
return () => {
|
|
219
|
-
handlers.delete(handler);
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Start the SSE stream for realtime events.
|
|
458
|
+
* Connect the user stream SSE. Call once after auth.
|
|
224
459
|
*/
|
|
225
|
-
|
|
460
|
+
connect() {
|
|
226
461
|
if (this.sseClient)
|
|
227
462
|
return;
|
|
228
463
|
this.sseClient = new SSEClient({
|
|
229
|
-
url: `${this.baseUrl}/ext/stream
|
|
464
|
+
url: `${this.baseUrl}/ext/stream/user?projectId=${encodeURIComponent(this.projectId)}`,
|
|
230
465
|
getToken: this.config.getToken,
|
|
231
|
-
onEvent: (event) => this.
|
|
232
|
-
onConnected: () =>
|
|
233
|
-
|
|
466
|
+
onEvent: (event) => this.handleStreamEvent(event),
|
|
467
|
+
onConnected: () => {
|
|
468
|
+
this._connected = true;
|
|
469
|
+
this.emitGlobal("connected");
|
|
470
|
+
},
|
|
471
|
+
onDisconnected: () => {
|
|
472
|
+
this._connected = false;
|
|
473
|
+
this.emitGlobal("disconnected");
|
|
474
|
+
}
|
|
234
475
|
});
|
|
235
|
-
|
|
476
|
+
void this.sseClient.connect();
|
|
236
477
|
this.tokenRefreshTimer = setInterval(
|
|
237
478
|
async () => {
|
|
238
479
|
if (this.sseClient) {
|
|
@@ -244,7 +485,13 @@ var Conversation = class {
|
|
|
244
485
|
);
|
|
245
486
|
}
|
|
246
487
|
/**
|
|
247
|
-
*
|
|
488
|
+
* Returns true if the user stream is connected.
|
|
489
|
+
*/
|
|
490
|
+
isConnected() {
|
|
491
|
+
return this._connected;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Disconnect the user stream and clean up all listeners.
|
|
248
495
|
*/
|
|
249
496
|
disconnect() {
|
|
250
497
|
if (this.tokenRefreshTimer) {
|
|
@@ -255,45 +502,83 @@ var Conversation = class {
|
|
|
255
502
|
this.sseClient.disconnect();
|
|
256
503
|
this.sseClient = null;
|
|
257
504
|
}
|
|
505
|
+
this._connected = false;
|
|
258
506
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
507
|
+
// ===========================================================================
|
|
508
|
+
// Global event listeners
|
|
509
|
+
// ===========================================================================
|
|
510
|
+
/**
|
|
511
|
+
* Listen for events across all conversations.
|
|
512
|
+
* Returns an unsubscribe function.
|
|
513
|
+
*/
|
|
514
|
+
on(event, handler) {
|
|
515
|
+
if (!this.globalListeners.has(event)) {
|
|
516
|
+
this.globalListeners.set(event, /* @__PURE__ */ new Set());
|
|
517
|
+
}
|
|
518
|
+
this.globalListeners.get(event).add(handler);
|
|
519
|
+
return () => {
|
|
520
|
+
this.globalListeners.get(event)?.delete(handler);
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Remove a global event listener.
|
|
525
|
+
*/
|
|
526
|
+
off(event, handler) {
|
|
527
|
+
this.globalListeners.get(event)?.delete(handler);
|
|
528
|
+
}
|
|
529
|
+
// ===========================================================================
|
|
530
|
+
// ChatEventBus: per-conversation listeners (used by Conversation)
|
|
531
|
+
// ===========================================================================
|
|
532
|
+
addConversationListener(conversationId, event, handler) {
|
|
533
|
+
if (!this.conversationListeners.has(conversationId)) {
|
|
534
|
+
this.conversationListeners.set(conversationId, /* @__PURE__ */ new Map());
|
|
535
|
+
}
|
|
536
|
+
const convMap = this.conversationListeners.get(conversationId);
|
|
537
|
+
if (!convMap.has(event)) {
|
|
538
|
+
convMap.set(event, /* @__PURE__ */ new Set());
|
|
539
|
+
}
|
|
540
|
+
convMap.get(event).add(handler);
|
|
541
|
+
return () => {
|
|
542
|
+
convMap.get(event)?.delete(handler);
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
// ===========================================================================
|
|
546
|
+
// Internal event routing
|
|
547
|
+
// ===========================================================================
|
|
548
|
+
handleStreamEvent(event) {
|
|
549
|
+
const eventType = event.type;
|
|
550
|
+
const conversationId = event.conversationId;
|
|
551
|
+
this.emitGlobal(eventType, event);
|
|
552
|
+
if (conversationId) {
|
|
553
|
+
const convMap = this.conversationListeners.get(conversationId);
|
|
554
|
+
if (convMap) {
|
|
555
|
+
const handlers = convMap.get(eventType);
|
|
556
|
+
if (handlers) {
|
|
557
|
+
for (const handler of handlers) {
|
|
558
|
+
try {
|
|
559
|
+
handler(event);
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
}
|
|
266
563
|
}
|
|
267
564
|
}
|
|
268
565
|
}
|
|
269
566
|
}
|
|
270
|
-
|
|
271
|
-
const handlers = this.
|
|
567
|
+
emitGlobal(event, ...args) {
|
|
568
|
+
const handlers = this.globalListeners.get(event);
|
|
272
569
|
if (handlers) {
|
|
273
570
|
for (const handler of handlers) {
|
|
274
571
|
try {
|
|
275
|
-
handler();
|
|
572
|
+
handler(...args);
|
|
276
573
|
} catch {
|
|
277
574
|
}
|
|
278
575
|
}
|
|
279
576
|
}
|
|
280
577
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
config;
|
|
286
|
-
conversations = /* @__PURE__ */ new Map();
|
|
287
|
-
constructor(config) {
|
|
288
|
-
this.config = config;
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Open a conversation.
|
|
292
|
-
*
|
|
293
|
-
* Creates a new conversation via the API and optionally attaches a buddy.
|
|
294
|
-
* Returns a Conversation instance for sending messages and receiving events.
|
|
295
|
-
*/
|
|
296
|
-
async openConversation(options = {}) {
|
|
578
|
+
// ===========================================================================
|
|
579
|
+
// API helpers
|
|
580
|
+
// ===========================================================================
|
|
581
|
+
async buildHeaders() {
|
|
297
582
|
const token = await this.config.getToken();
|
|
298
583
|
const headers = {
|
|
299
584
|
"Content-Type": "application/json",
|
|
@@ -302,6 +587,47 @@ var GigabuddyChat = class {
|
|
|
302
587
|
if (this.config.userId) {
|
|
303
588
|
headers["X-Chat-User-Id"] = this.config.userId;
|
|
304
589
|
}
|
|
590
|
+
return headers;
|
|
591
|
+
}
|
|
592
|
+
extractConversationId(data) {
|
|
593
|
+
let conversationId = data["conversationId"];
|
|
594
|
+
if (!conversationId && data["conversation"]) {
|
|
595
|
+
let conv = data["conversation"];
|
|
596
|
+
if (typeof conv === "string") {
|
|
597
|
+
try {
|
|
598
|
+
conv = JSON.parse(conv);
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (conv && typeof conv === "object" && "id" in conv) {
|
|
603
|
+
conversationId = conv.id;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (!conversationId) {
|
|
607
|
+
throw new Error("No conversationId returned from API");
|
|
608
|
+
}
|
|
609
|
+
return conversationId;
|
|
610
|
+
}
|
|
611
|
+
trackConversation(conversationId) {
|
|
612
|
+
const existing = this.conversations.get(conversationId);
|
|
613
|
+
if (existing)
|
|
614
|
+
return existing;
|
|
615
|
+
const conversation = new Conversation(conversationId, this.config, this);
|
|
616
|
+
this.conversations.set(conversationId, conversation);
|
|
617
|
+
return conversation;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Get or create a Conversation handle for a known conversationId.
|
|
621
|
+
* Does not make any API calls — just returns a local wrapper.
|
|
622
|
+
*/
|
|
623
|
+
getConversation(conversationId) {
|
|
624
|
+
return this.trackConversation(conversationId);
|
|
625
|
+
}
|
|
626
|
+
// ===========================================================================
|
|
627
|
+
// Conversation creation (v1 compat)
|
|
628
|
+
// ===========================================================================
|
|
629
|
+
async openConversation(options = {}) {
|
|
630
|
+
const headers = await this.buildHeaders();
|
|
305
631
|
const createResponse = await fetch(`${this.config.apiUrl}/chat/create-conversation`, {
|
|
306
632
|
method: "POST",
|
|
307
633
|
headers,
|
|
@@ -315,22 +641,7 @@ var GigabuddyChat = class {
|
|
|
315
641
|
throw new Error(`Failed to create conversation: ${createResponse.status} ${body}`);
|
|
316
642
|
}
|
|
317
643
|
const createData = await createResponse.json();
|
|
318
|
-
|
|
319
|
-
if (!conversationId && createData["conversation"]) {
|
|
320
|
-
let conv = createData["conversation"];
|
|
321
|
-
if (typeof conv === "string") {
|
|
322
|
-
try {
|
|
323
|
-
conv = JSON.parse(conv);
|
|
324
|
-
} catch {
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (conv && typeof conv === "object" && "id" in conv) {
|
|
328
|
-
conversationId = conv.id;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (!conversationId) {
|
|
332
|
-
throw new Error("No conversationId returned from create-conversation");
|
|
333
|
-
}
|
|
644
|
+
const conversationId = this.extractConversationId(createData);
|
|
334
645
|
if (options.buddyId) {
|
|
335
646
|
const attachResponse = await fetch(`${this.config.apiUrl}/chat/attach-buddy`, {
|
|
336
647
|
method: "POST",
|
|
@@ -346,28 +657,121 @@ var GigabuddyChat = class {
|
|
|
346
657
|
throw new Error(`Failed to attach buddy: ${attachResponse.status} ${body}`);
|
|
347
658
|
}
|
|
348
659
|
}
|
|
349
|
-
|
|
350
|
-
this.conversations.set(conversationId, conversation);
|
|
351
|
-
return conversation;
|
|
660
|
+
return this.trackConversation(conversationId);
|
|
352
661
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
662
|
+
// ===========================================================================
|
|
663
|
+
// v2: Channel management
|
|
664
|
+
// ===========================================================================
|
|
665
|
+
async createChannel(opts) {
|
|
666
|
+
const headers = await this.buildHeaders();
|
|
667
|
+
const response = await fetch(`${this.config.apiUrl}/chat/create-channel`, {
|
|
668
|
+
method: "POST",
|
|
669
|
+
headers,
|
|
670
|
+
body: JSON.stringify(opts)
|
|
671
|
+
});
|
|
672
|
+
if (!response.ok) {
|
|
673
|
+
const body = await response.text();
|
|
674
|
+
throw new Error(`Failed to create channel: ${response.status} ${body}`);
|
|
675
|
+
}
|
|
676
|
+
const data = await response.json();
|
|
677
|
+
const conversationId = this.extractConversationId(data);
|
|
678
|
+
return this.trackConversation(conversationId);
|
|
679
|
+
}
|
|
680
|
+
async joinChannel(conversationId) {
|
|
681
|
+
const headers = await this.buildHeaders();
|
|
682
|
+
const response = await fetch(`${this.config.apiUrl}/chat/join-channel`, {
|
|
683
|
+
method: "POST",
|
|
684
|
+
headers,
|
|
685
|
+
body: JSON.stringify({ conversationId })
|
|
686
|
+
});
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
const body = await response.text();
|
|
689
|
+
throw new Error(`Failed to join channel: ${response.status} ${body}`);
|
|
690
|
+
}
|
|
691
|
+
return this.trackConversation(conversationId);
|
|
692
|
+
}
|
|
693
|
+
async listChannels(opts) {
|
|
694
|
+
const headers = await this.buildHeaders();
|
|
695
|
+
const response = await fetch(`${this.config.apiUrl}/chat/list-channels`, {
|
|
696
|
+
method: "POST",
|
|
697
|
+
headers,
|
|
698
|
+
body: JSON.stringify(opts ?? {})
|
|
699
|
+
});
|
|
700
|
+
if (!response.ok) {
|
|
701
|
+
const body = await response.text();
|
|
702
|
+
throw new Error(`Failed to list channels: ${response.status} ${body}`);
|
|
703
|
+
}
|
|
704
|
+
const data = await response.json();
|
|
705
|
+
return data.conversations ?? [];
|
|
706
|
+
}
|
|
707
|
+
// ===========================================================================
|
|
708
|
+
// v2: DMs
|
|
709
|
+
// ===========================================================================
|
|
710
|
+
async openDM(userId) {
|
|
711
|
+
const headers = await this.buildHeaders();
|
|
712
|
+
const response = await fetch(`${this.config.apiUrl}/chat/create-dm`, {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers,
|
|
715
|
+
body: JSON.stringify({ participantId: userId })
|
|
716
|
+
});
|
|
717
|
+
if (!response.ok) {
|
|
718
|
+
const body = await response.text();
|
|
719
|
+
throw new Error(`Failed to open DM: ${response.status} ${body}`);
|
|
720
|
+
}
|
|
721
|
+
const data = await response.json();
|
|
722
|
+
const conversationId = this.extractConversationId(data);
|
|
723
|
+
return this.trackConversation(conversationId);
|
|
724
|
+
}
|
|
725
|
+
async openGroupDM(userIds) {
|
|
726
|
+
const headers = await this.buildHeaders();
|
|
727
|
+
const response = await fetch(`${this.config.apiUrl}/chat/create-group-dm`, {
|
|
728
|
+
method: "POST",
|
|
729
|
+
headers,
|
|
730
|
+
body: JSON.stringify({ participantIds: userIds })
|
|
731
|
+
});
|
|
732
|
+
if (!response.ok) {
|
|
733
|
+
const body = await response.text();
|
|
734
|
+
throw new Error(`Failed to open group DM: ${response.status} ${body}`);
|
|
735
|
+
}
|
|
736
|
+
const data = await response.json();
|
|
737
|
+
const conversationId = this.extractConversationId(data);
|
|
738
|
+
return this.trackConversation(conversationId);
|
|
739
|
+
}
|
|
740
|
+
// ===========================================================================
|
|
741
|
+
// v2: List conversations (all types)
|
|
742
|
+
// ===========================================================================
|
|
743
|
+
async listConversations(opts) {
|
|
744
|
+
const headers = await this.buildHeaders();
|
|
745
|
+
const response = await fetch(`${this.config.apiUrl}/chat/list-conversations`, {
|
|
746
|
+
method: "POST",
|
|
747
|
+
headers,
|
|
748
|
+
body: JSON.stringify(opts ?? {})
|
|
749
|
+
});
|
|
750
|
+
if (!response.ok) {
|
|
751
|
+
const body = await response.text();
|
|
752
|
+
throw new Error(`Failed to list conversations: ${response.status} ${body}`);
|
|
753
|
+
}
|
|
754
|
+
const data = await response.json();
|
|
755
|
+
return data.conversations ?? [];
|
|
756
|
+
}
|
|
757
|
+
// ===========================================================================
|
|
758
|
+
// Lifecycle
|
|
759
|
+
// ===========================================================================
|
|
356
760
|
closeConversation(conversationId) {
|
|
357
761
|
const conversation = this.conversations.get(conversationId);
|
|
358
762
|
if (conversation) {
|
|
359
763
|
conversation.disconnect();
|
|
360
764
|
this.conversations.delete(conversationId);
|
|
361
765
|
}
|
|
766
|
+
this.conversationListeners.delete(conversationId);
|
|
362
767
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Close all conversations and clean up.
|
|
365
|
-
*/
|
|
366
768
|
destroy() {
|
|
367
|
-
|
|
368
|
-
|
|
769
|
+
this.disconnect();
|
|
770
|
+
for (const [id] of this.conversations) {
|
|
369
771
|
this.conversations.delete(id);
|
|
370
772
|
}
|
|
773
|
+
this.conversationListeners.clear();
|
|
774
|
+
this.globalListeners.clear();
|
|
371
775
|
}
|
|
372
776
|
};
|
|
373
777
|
export {
|
package/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../libs/chat-sdk/src/lib/
|
|
4
|
-
"sourcesContent": ["/**\n * SSE Client\n *\n * Uses fetch() + ReadableStream instead of native EventSource\n * to support Authorization headers. Handles reconnection with\n * exponential backoff.\n */\n\nimport type { ChannelEvent, ChannelEventType } from './types.js';\n\nexport type SSEEventHandler = (event: ChannelEvent) => void;\nexport type SSEConnectionHandler = () => void;\n\ninterface SSEClientConfig {\n url: string;\n getToken: () => Promise<string> | string;\n onEvent: SSEEventHandler;\n onConnected: SSEConnectionHandler;\n onDisconnected: SSEConnectionHandler;\n}\n\nconst MAX_RECONNECT_ATTEMPTS = 10;\nconst INITIAL_BACKOFF_MS = 1_000;\nconst MAX_BACKOFF_MS = 30_000;\n\nexport class SSEClient {\n private config: SSEClientConfig;\n private abortController: AbortController | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private reconnectAttempt = 0;\n private stopped = false;\n\n constructor(config: SSEClientConfig) {\n this.config = config;\n }\n\n async connect(): Promise<void> {\n this.stopped = false;\n this.reconnectAttempt = 0;\n await this.startStream();\n }\n\n disconnect(): void {\n this.stopped = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n }\n\n async updateToken(newToken: string): Promise<void> {\n // Store the new token getter for reconnections\n const originalGetToken = this.config.getToken;\n this.config.getToken = () => newToken;\n\n // Reconnect with new token\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n this.reconnectAttempt = 0;\n await this.startStream();\n }\n\n // Restore the dynamic getter for future reconnections\n this.config.getToken = originalGetToken;\n }\n\n private async startStream(): Promise<void> {\n if (this.stopped) return;\n\n try {\n const token = await this.config.getToken();\n this.abortController = new AbortController();\n\n const response = await fetch(this.config.url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'text/event-stream',\n },\n signal: this.abortController.signal,\n });\n\n if (!response.ok) {\n throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);\n }\n\n if (!response.body) {\n throw new Error('SSE response has no body');\n }\n\n this.reconnectAttempt = 0;\n await this.readStream(response.body);\n } catch (error) {\n if (this.stopped) return;\n if (error instanceof DOMException && error.name === 'AbortError') return;\n\n this.config.onDisconnected();\n this.scheduleReconnect();\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete SSE messages (terminated by \\n\\n)\n const messages = buffer.split('\\n\\n');\n buffer = messages.pop() ?? '';\n\n for (const message of messages) {\n this.parseSSEMessage(message);\n }\n }\n } catch (error) {\n if (this.stopped) return;\n if (error instanceof DOMException && error.name === 'AbortError') return;\n } finally {\n reader.releaseLock();\n }\n\n // Stream ended \u2014 reconnect if not explicitly stopped\n if (!this.stopped) {\n this.config.onDisconnected();\n this.scheduleReconnect();\n }\n }\n\n private parseSSEMessage(message: string): void {\n // Skip heartbeat comments\n if (message.trim().startsWith(':')) return;\n\n let eventType = 'message';\n let data = '';\n\n for (const line of message.split('\\n')) {\n if (line.startsWith('event: ')) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith('data: ')) {\n data = line.slice(6);\n } else if (line.startsWith('data:')) {\n data = line.slice(5);\n }\n }\n\n if (eventType === 'connected') {\n this.config.onConnected();\n return;\n }\n\n if (!data) return;\n\n try {\n const parsed = JSON.parse(data) as ChannelEvent;\n // Ensure type field matches event type\n if (!parsed.type) {\n parsed.type = eventType as ChannelEventType;\n }\n this.config.onEvent(parsed);\n } catch {\n // Ignore malformed events\n }\n }\n\n private scheduleReconnect(): void {\n if (this.stopped) return;\n if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) return;\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, this.reconnectAttempt), MAX_BACKOFF_MS);\n this.reconnectAttempt++;\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.startStream();\n }, backoff);\n }\n}\n", "/**\n * Conversation\n *\n * Represents an active conversation with realtime event streaming.\n * Wraps the SSE client and provides typed event handlers.\n */\n\nimport { SSEClient } from './SSEClient.js';\nimport type { ChannelEvent, ConversationEventMap, GigabuddyChatConfig, Message } from './types.js';\n\nexport class Conversation {\n readonly conversationId: string;\n private config: GigabuddyChatConfig;\n private baseUrl: string;\n private sseClient: SSEClient | null = null;\n private listeners = new Map<string, Set<(...args: unknown[]) => void>>();\n private tokenRefreshTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(conversationId: string, config: GigabuddyChatConfig) {\n this.conversationId = conversationId;\n this.config = config;\n // Derive base API URL by stripping the /:orgSlug/:projectId suffix\n // apiUrl = https://api.gigabuddy.com/orgSlug/projectId\n // baseUrl = https://api.gigabuddy.com\n const url = new URL(config.apiUrl);\n this.baseUrl = url.origin;\n }\n\n /**\n * Send a message to this conversation.\n * Calls the send-message action via the /chat/ proxy route.\n */\n private async buildHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getToken();\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n };\n if (this.config.userId) {\n headers['X-Chat-User-Id'] = this.config.userId;\n }\n return headers;\n }\n\n async sendMessage(text: string): Promise<void> {\n const headers = await this.buildHeaders();\n const response = await fetch(`${this.config.apiUrl}/chat/send-message`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n text,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to send message: ${response.status} ${body}`);\n }\n }\n\n /**\n * Get messages for this conversation.\n */\n async getMessages(opts?: { limit?: number; before?: string }): Promise<Message[]> {\n const headers = await this.buildHeaders();\n const response = await fetch(`${this.config.apiUrl}/chat/get-messages`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n ...(opts?.limit ? { limit: opts.limit } : {}),\n ...(opts?.before ? { before: opts.before } : {}),\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to get messages: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as { messages?: Message[] };\n return data.messages ?? [];\n }\n\n /**\n * Register an event handler. Returns an unsubscribe function.\n */\n on<T extends keyof ConversationEventMap>(event: T, handler: ConversationEventMap[T]): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n const handlers = this.listeners.get(event)!;\n handlers.add(handler as (...args: unknown[]) => void);\n\n return () => {\n handlers.delete(handler as (...args: unknown[]) => void);\n };\n }\n\n /**\n * Start the SSE stream for realtime events.\n */\n async connect(): Promise<void> {\n if (this.sseClient) return;\n\n this.sseClient = new SSEClient({\n url: `${this.baseUrl}/ext/stream/${this.conversationId}`,\n getToken: this.config.getToken,\n onEvent: (event: ChannelEvent) => this.handleEvent(event),\n onConnected: () => this.emit('connected'),\n onDisconnected: () => this.emit('disconnected'),\n });\n\n await this.sseClient.connect();\n\n // Refresh token every 50 minutes to keep the stream alive\n this.tokenRefreshTimer = setInterval(\n async () => {\n if (this.sseClient) {\n const newToken = await this.config.getToken();\n await this.sseClient.updateToken(newToken);\n }\n },\n 50 * 60 * 1_000,\n );\n }\n\n /**\n * Close SSE stream and clean up.\n */\n disconnect(): void {\n if (this.tokenRefreshTimer) {\n clearInterval(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n if (this.sseClient) {\n this.sseClient.disconnect();\n this.sseClient = null;\n }\n }\n\n private handleEvent(event: ChannelEvent): void {\n // Dispatch to typed listeners\n const handlers = this.listeners.get(event.type);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(event);\n } catch {\n // Don't let listener errors break the stream\n }\n }\n }\n }\n\n private emit(event: 'connected' | 'disconnected'): void {\n const handlers = this.listeners.get(event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler();\n } catch {\n // Don't let listener errors break the stream\n }\n }\n }\n }\n}\n", "/**\n * GigabuddyChat\n *\n * Main entry point for the Chat SDK. Manages conversation lifecycle.\n */\n\nimport { Conversation } from './Conversation.js';\nimport type { GigabuddyChatConfig } from './types.js';\n\nexport class GigabuddyChat {\n private config: GigabuddyChatConfig;\n private conversations = new Map<string, Conversation>();\n\n constructor(config: GigabuddyChatConfig) {\n this.config = config;\n }\n\n /**\n * Open a conversation.\n *\n * Creates a new conversation via the API and optionally attaches a buddy.\n * Returns a Conversation instance for sending messages and receiving events.\n */\n async openConversation(\n options: {\n topic?: string;\n buddyId?: string;\n buddyProjectId?: string;\n userName?: string;\n } = {},\n ): Promise<Conversation> {\n const token = await this.config.getToken();\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n };\n if (this.config.userId) {\n headers['X-Chat-User-Id'] = this.config.userId;\n }\n\n // Create conversation\n const createResponse = await fetch(`${this.config.apiUrl}/chat/create-conversation`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n ...(options.topic ? { topic: options.topic } : {}),\n ...(options.userName ? { userName: options.userName } : {}),\n }),\n });\n\n if (!createResponse.ok) {\n const body = await createResponse.text();\n throw new Error(`Failed to create conversation: ${createResponse.status} ${body}`);\n }\n\n const createData = (await createResponse.json()) as Record<string, unknown>;\n let conversationId = createData['conversationId'] as string | undefined;\n if (!conversationId && createData['conversation']) {\n // The conversation field may be a parsed object or a stringified JSON string\n let conv = createData['conversation'];\n if (typeof conv === 'string') {\n try {\n conv = JSON.parse(conv);\n } catch {\n // not JSON \u2014 ignore\n }\n }\n if (conv && typeof conv === 'object' && 'id' in conv) {\n conversationId = (conv as { id: string }).id;\n }\n }\n if (!conversationId) {\n throw new Error('No conversationId returned from create-conversation');\n }\n\n // Attach buddy if requested\n if (options.buddyId) {\n const attachResponse = await fetch(`${this.config.apiUrl}/chat/attach-buddy`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId,\n buddyId: options.buddyId,\n ...(options.buddyProjectId ? { buddyProjectId: options.buddyProjectId } : {}),\n }),\n });\n\n if (!attachResponse.ok) {\n const body = await attachResponse.text();\n throw new Error(`Failed to attach buddy: ${attachResponse.status} ${body}`);\n }\n }\n\n const conversation = new Conversation(conversationId, this.config);\n this.conversations.set(conversationId, conversation);\n return conversation;\n }\n\n /**\n * Close and clean up a conversation.\n */\n closeConversation(conversationId: string): void {\n const conversation = this.conversations.get(conversationId);\n if (conversation) {\n conversation.disconnect();\n this.conversations.delete(conversationId);\n }\n }\n\n /**\n * Close all conversations and clean up.\n */\n destroy(): void {\n for (const [id, conversation] of this.conversations) {\n conversation.disconnect();\n this.conversations.delete(id);\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
3
|
+
"sources": ["../../../libs/chat-sdk/src/lib/Conversation.ts", "../../../libs/chat-sdk/src/lib/SSEClient.ts", "../../../libs/chat-sdk/src/lib/GigabuddyChat.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Conversation\n *\n * Represents an active conversation. Events are received via the\n * parent GigabuddyChat's user stream \u2014 filtered by conversationId.\n * connect()/disconnect() are no-ops (kept for backward compat).\n */\n\nimport type {\n ChatEventBus,\n ChannelEvent,\n ConversationEventMap,\n GigabuddyChatConfig,\n Message,\n MessageContent,\n} from './types.js';\n\nexport class Conversation {\n readonly conversationId: string;\n private config: GigabuddyChatConfig;\n private eventBus: ChatEventBus | null;\n private unsubscribes: (() => void)[] = [];\n\n constructor(conversationId: string, config: GigabuddyChatConfig, eventBus?: ChatEventBus) {\n this.conversationId = conversationId;\n this.config = config;\n this.eventBus = eventBus ?? null;\n }\n\n private async buildHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getToken();\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n };\n if (this.config.userId) {\n headers['X-Chat-User-Id'] = this.config.userId;\n }\n return headers;\n }\n\n // ===========================================================================\n // Messages\n // ===========================================================================\n\n async sendMessage(content: string | MessageContent): Promise<void> {\n const headers = await this.buildHeaders();\n\n const body: Record<string, unknown> = {\n conversationId: this.conversationId,\n };\n\n if (typeof content === 'string') {\n body['text'] = content;\n } else {\n body['content'] = content;\n }\n\n const response = await fetch(`${this.config.apiUrl}/chat/send-message`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const responseBody = await response.text();\n throw new Error(`Failed to send message: ${response.status} ${responseBody}`);\n }\n }\n\n async replyTo(messageId: string, content: string | MessageContent): Promise<void> {\n const headers = await this.buildHeaders();\n\n const body: Record<string, unknown> = {\n conversationId: this.conversationId,\n parentMessageId: messageId,\n };\n\n if (typeof content === 'string') {\n body['text'] = content;\n } else {\n body['content'] = content;\n }\n\n const response = await fetch(`${this.config.apiUrl}/chat/reply-to-message`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const responseBody = await response.text();\n throw new Error(`Failed to reply to message: ${response.status} ${responseBody}`);\n }\n }\n\n async editMessage(messageId: string, content: string | MessageContent): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/edit-message`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n messageId,\n content,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to edit message: ${response.status} ${body}`);\n }\n }\n\n async deleteMessage(messageId: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/delete-message`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n messageId,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to delete message: ${response.status} ${body}`);\n }\n }\n\n // ===========================================================================\n // Reactions\n // ===========================================================================\n\n async addReaction(messageId: string, emoji: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/add-reaction`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n messageId,\n emoji,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to add reaction: ${response.status} ${body}`);\n }\n }\n\n async removeReaction(messageId: string, emoji: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/remove-reaction`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n messageId,\n emoji,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to remove reaction: ${response.status} ${body}`);\n }\n }\n\n // ===========================================================================\n // Read state & metadata\n // ===========================================================================\n\n async markRead(messageId?: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/mark-read`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n ...(messageId ? { messageId } : {}),\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to mark read: ${response.status} ${body}`);\n }\n }\n\n async updateTopic(topic: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/update-channel`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n topic,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to update topic: ${response.status} ${body}`);\n }\n }\n\n // ===========================================================================\n // Participants\n // ===========================================================================\n\n async addParticipant(userId: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/invite-to-channel`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n userId,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to add participant: ${response.status} ${body}`);\n }\n }\n\n async removeParticipant(userId: string): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/remove-participant`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n userId,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to remove participant: ${response.status} ${body}`);\n }\n }\n\n async leave(): Promise<void> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/leave-conversation`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to leave conversation: ${response.status} ${body}`);\n }\n }\n\n // ===========================================================================\n // Message history\n // ===========================================================================\n\n async getMessages(opts?: { limit?: number; before?: string }): Promise<Message[]> {\n const headers = await this.buildHeaders();\n const response = await fetch(`${this.config.apiUrl}/chat/get-messages`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n ...(opts?.limit ? { limit: opts.limit } : {}),\n ...(opts?.before ? { before: opts.before } : {}),\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to get messages: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as { messages?: Message[] };\n return data.messages ?? [];\n }\n\n async getThreadReplies(parentMessageId: string, opts?: { limit?: number; before?: string }): Promise<Message[]> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/get-thread-replies`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId: this.conversationId,\n parentMessageId,\n ...(opts?.limit ? { limit: opts.limit } : {}),\n ...(opts?.before ? { before: opts.before } : {}),\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to get thread replies: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as { messages?: Message[] };\n return data.messages ?? [];\n }\n\n // ===========================================================================\n // Event listeners \u2014 route through parent user stream\n // ===========================================================================\n\n /**\n * Register an event handler filtered to this conversation.\n * Events are received from the parent GigabuddyChat's user stream.\n * Returns an unsubscribe function.\n */\n on<T extends keyof ConversationEventMap>(event: T, handler: ConversationEventMap[T]): () => void {\n if (this.eventBus) {\n const unsub = this.eventBus.addConversationListener(\n this.conversationId,\n event,\n handler as (...args: unknown[]) => void,\n );\n this.unsubscribes.push(unsub);\n return unsub;\n }\n // No event bus \u2014 listener is a no-op\n return () => {};\n }\n\n // ===========================================================================\n // SSE lifecycle \u2014 no-ops (stream managed by GigabuddyChat)\n // ===========================================================================\n\n /** @deprecated No-op \u2014 SSE is managed by GigabuddyChat.connect() */\n async connect(): Promise<void> {\n // No-op: user stream is managed at the GigabuddyChat level\n }\n\n /** @deprecated No-op \u2014 SSE is managed by GigabuddyChat.disconnect() */\n disconnect(): void {\n // Clean up any registered listeners\n for (const unsub of this.unsubscribes) {\n unsub();\n }\n this.unsubscribes = [];\n }\n}\n", "/**\n * SSE Client\n *\n * Uses fetch() + ReadableStream instead of native EventSource\n * to support Authorization headers. Handles reconnection with\n * exponential backoff.\n */\n\nimport type { ChannelEvent, ChannelEventType } from './types.js';\n\nexport type SSEEventHandler = (event: ChannelEvent) => void;\nexport type SSEConnectionHandler = () => void;\n\ninterface SSEClientConfig {\n url: string;\n getToken: () => Promise<string> | string;\n onEvent: SSEEventHandler;\n onConnected: SSEConnectionHandler;\n onDisconnected: SSEConnectionHandler;\n}\n\nconst MAX_RECONNECT_ATTEMPTS = 10;\nconst INITIAL_BACKOFF_MS = 1_000;\nconst MAX_BACKOFF_MS = 30_000;\n\nexport class SSEClient {\n private config: SSEClientConfig;\n private abortController: AbortController | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private reconnectAttempt = 0;\n private stopped = false;\n\n constructor(config: SSEClientConfig) {\n this.config = config;\n }\n\n async connect(): Promise<void> {\n this.stopped = false;\n this.reconnectAttempt = 0;\n await this.startStream();\n }\n\n disconnect(): void {\n this.stopped = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n }\n\n async updateToken(newToken: string): Promise<void> {\n // Store the new token getter for reconnections\n const originalGetToken = this.config.getToken;\n this.config.getToken = () => newToken;\n\n // Reconnect with new token\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n this.reconnectAttempt = 0;\n await this.startStream();\n }\n\n // Restore the dynamic getter for future reconnections\n this.config.getToken = originalGetToken;\n }\n\n private async startStream(): Promise<void> {\n if (this.stopped) return;\n\n try {\n const token = await this.config.getToken();\n this.abortController = new AbortController();\n\n const response = await fetch(this.config.url, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: 'text/event-stream',\n },\n signal: this.abortController.signal,\n });\n\n if (!response.ok) {\n throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);\n }\n\n if (!response.body) {\n throw new Error('SSE response has no body');\n }\n\n this.reconnectAttempt = 0;\n // Read stream in background \u2014 don't await (readStream blocks until stream ends)\n this.readStream(response.body);\n } catch (error) {\n if (this.stopped) return;\n if (error instanceof DOMException && error.name === 'AbortError') return;\n\n this.config.onDisconnected();\n this.scheduleReconnect();\n }\n }\n\n private async readStream(body: ReadableStream<Uint8Array>): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // Process complete SSE messages (terminated by \\n\\n)\n const messages = buffer.split('\\n\\n');\n buffer = messages.pop() ?? '';\n\n for (const message of messages) {\n this.parseSSEMessage(message);\n }\n }\n } catch (error) {\n if (this.stopped) return;\n if (error instanceof DOMException && error.name === 'AbortError') return;\n } finally {\n reader.releaseLock();\n }\n\n // Stream ended \u2014 reconnect if not explicitly stopped\n if (!this.stopped) {\n this.config.onDisconnected();\n this.scheduleReconnect();\n }\n }\n\n private parseSSEMessage(message: string): void {\n // Skip heartbeat comments\n if (message.trim().startsWith(':')) return;\n\n let eventType = 'message';\n let data = '';\n\n for (const line of message.split('\\n')) {\n if (line.startsWith('event: ')) {\n eventType = line.slice(7).trim();\n } else if (line.startsWith('data: ')) {\n data = line.slice(6);\n } else if (line.startsWith('data:')) {\n data = line.slice(5);\n }\n }\n\n if (eventType === 'connected') {\n this.config.onConnected();\n return;\n }\n\n if (!data) return;\n\n try {\n const parsed = JSON.parse(data) as ChannelEvent;\n // Ensure type field matches event type\n if (!parsed.type) {\n parsed.type = eventType as ChannelEventType;\n }\n this.config.onEvent(parsed);\n } catch {\n // Ignore malformed events\n }\n }\n\n private scheduleReconnect(): void {\n if (this.stopped) return;\n if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) return;\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, this.reconnectAttempt), MAX_BACKOFF_MS);\n this.reconnectAttempt++;\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.startStream();\n }, backoff);\n }\n}\n", "/**\n * GigabuddyChat\n *\n * Main entry point for the Chat SDK. Manages a single user-level SSE\n * connection and routes events to conversation-level listeners.\n */\n\nimport { Conversation } from './Conversation.js';\nimport { SSEClient } from './SSEClient.js';\nimport type {\n ChatEventBus,\n ChannelEvent,\n ConversationInfo,\n ConversationType,\n ConversationVisibility,\n GigabuddyChatConfig,\n} from './types.js';\n\nexport class GigabuddyChat implements ChatEventBus {\n private config: GigabuddyChatConfig;\n private conversations = new Map<string, Conversation>();\n\n // User stream SSE\n private sseClient: SSEClient | null = null;\n private tokenRefreshTimer: ReturnType<typeof setInterval> | null = null;\n private _connected = false;\n\n // Global event listeners (across all conversations)\n private globalListeners = new Map<string, Set<(...args: unknown[]) => void>>();\n\n // Per-conversation event listeners\n private conversationListeners = new Map<string, Map<string, Set<(...args: unknown[]) => void>>>();\n\n // Derived URL parts\n private baseUrl: string;\n private projectId: string;\n\n constructor(config: GigabuddyChatConfig) {\n this.config = config;\n const url = new URL(config.apiUrl);\n this.baseUrl = url.origin;\n const pathParts = url.pathname.split('/').filter(Boolean);\n this.projectId = pathParts[pathParts.length - 1];\n }\n\n // ===========================================================================\n // User stream connection\n // ===========================================================================\n\n /**\n * Connect the user stream SSE. Call once after auth.\n */\n connect(): void {\n if (this.sseClient) return;\n\n this.sseClient = new SSEClient({\n url: `${this.baseUrl}/ext/stream/user?projectId=${encodeURIComponent(this.projectId)}`,\n getToken: this.config.getToken,\n onEvent: (event: ChannelEvent) => this.handleStreamEvent(event),\n onConnected: () => {\n this._connected = true;\n this.emitGlobal('connected');\n },\n onDisconnected: () => {\n this._connected = false;\n this.emitGlobal('disconnected');\n },\n });\n\n void this.sseClient.connect();\n\n // Refresh token every 50 minutes\n this.tokenRefreshTimer = setInterval(\n async () => {\n if (this.sseClient) {\n const newToken = await this.config.getToken();\n await this.sseClient.updateToken(newToken);\n }\n },\n 50 * 60 * 1_000,\n );\n }\n\n /**\n * Returns true if the user stream is connected.\n */\n isConnected(): boolean {\n return this._connected;\n }\n\n /**\n * Disconnect the user stream and clean up all listeners.\n */\n disconnect(): void {\n if (this.tokenRefreshTimer) {\n clearInterval(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n if (this.sseClient) {\n this.sseClient.disconnect();\n this.sseClient = null;\n }\n this._connected = false;\n }\n\n // ===========================================================================\n // Global event listeners\n // ===========================================================================\n\n /**\n * Listen for events across all conversations.\n * Returns an unsubscribe function.\n */\n on(event: string, handler: (...args: unknown[]) => void): () => void {\n if (!this.globalListeners.has(event)) {\n this.globalListeners.set(event, new Set());\n }\n this.globalListeners.get(event)!.add(handler);\n return () => {\n this.globalListeners.get(event)?.delete(handler);\n };\n }\n\n /**\n * Remove a global event listener.\n */\n off(event: string, handler: (...args: unknown[]) => void): void {\n this.globalListeners.get(event)?.delete(handler);\n }\n\n // ===========================================================================\n // ChatEventBus: per-conversation listeners (used by Conversation)\n // ===========================================================================\n\n addConversationListener(conversationId: string, event: string, handler: (...args: unknown[]) => void): () => void {\n if (!this.conversationListeners.has(conversationId)) {\n this.conversationListeners.set(conversationId, new Map());\n }\n const convMap = this.conversationListeners.get(conversationId)!;\n if (!convMap.has(event)) {\n convMap.set(event, new Set());\n }\n convMap.get(event)!.add(handler);\n\n return () => {\n convMap.get(event)?.delete(handler);\n };\n }\n\n // ===========================================================================\n // Internal event routing\n // ===========================================================================\n\n private handleStreamEvent(event: ChannelEvent): void {\n const eventType = event.type;\n const conversationId = event.conversationId;\n\n // Dispatch to global listeners\n this.emitGlobal(eventType, event);\n\n // Dispatch to conversation-specific listeners\n if (conversationId) {\n const convMap = this.conversationListeners.get(conversationId);\n if (convMap) {\n const handlers = convMap.get(eventType);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(event);\n } catch {\n // Don't let listener errors break the stream\n }\n }\n }\n }\n }\n }\n\n private emitGlobal(event: string, ...args: unknown[]): void {\n const handlers = this.globalListeners.get(event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(...args);\n } catch {\n // Don't let listener errors break the stream\n }\n }\n }\n }\n\n // ===========================================================================\n // API helpers\n // ===========================================================================\n\n private async buildHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getToken();\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n };\n if (this.config.userId) {\n headers['X-Chat-User-Id'] = this.config.userId;\n }\n return headers;\n }\n\n private extractConversationId(data: Record<string, unknown>): string {\n let conversationId = data['conversationId'] as string | undefined;\n if (!conversationId && data['conversation']) {\n let conv = data['conversation'];\n if (typeof conv === 'string') {\n try {\n conv = JSON.parse(conv);\n } catch {\n // not JSON\n }\n }\n if (conv && typeof conv === 'object' && 'id' in conv) {\n conversationId = (conv as { id: string }).id;\n }\n }\n if (!conversationId) {\n throw new Error('No conversationId returned from API');\n }\n return conversationId;\n }\n\n private trackConversation(conversationId: string): Conversation {\n const existing = this.conversations.get(conversationId);\n if (existing) return existing;\n const conversation = new Conversation(conversationId, this.config, this);\n this.conversations.set(conversationId, conversation);\n return conversation;\n }\n\n /**\n * Get or create a Conversation handle for a known conversationId.\n * Does not make any API calls \u2014 just returns a local wrapper.\n */\n getConversation(conversationId: string): Conversation {\n return this.trackConversation(conversationId);\n }\n\n // ===========================================================================\n // Conversation creation (v1 compat)\n // ===========================================================================\n\n async openConversation(\n options: {\n topic?: string;\n buddyId?: string;\n buddyProjectId?: string;\n userName?: string;\n } = {},\n ): Promise<Conversation> {\n const headers = await this.buildHeaders();\n\n const createResponse = await fetch(`${this.config.apiUrl}/chat/create-conversation`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n ...(options.topic ? { topic: options.topic } : {}),\n ...(options.userName ? { userName: options.userName } : {}),\n }),\n });\n\n if (!createResponse.ok) {\n const body = await createResponse.text();\n throw new Error(`Failed to create conversation: ${createResponse.status} ${body}`);\n }\n\n const createData = (await createResponse.json()) as Record<string, unknown>;\n const conversationId = this.extractConversationId(createData);\n\n if (options.buddyId) {\n const attachResponse = await fetch(`${this.config.apiUrl}/chat/attach-buddy`, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n conversationId,\n buddyId: options.buddyId,\n ...(options.buddyProjectId ? { buddyProjectId: options.buddyProjectId } : {}),\n }),\n });\n\n if (!attachResponse.ok) {\n const body = await attachResponse.text();\n throw new Error(`Failed to attach buddy: ${attachResponse.status} ${body}`);\n }\n }\n\n return this.trackConversation(conversationId);\n }\n\n // ===========================================================================\n // v2: Channel management\n // ===========================================================================\n\n async createChannel(opts: {\n name: string;\n visibility?: ConversationVisibility;\n description?: string;\n topic?: string;\n }): Promise<Conversation> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/create-channel`, {\n method: 'POST',\n headers,\n body: JSON.stringify(opts),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to create channel: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const conversationId = this.extractConversationId(data);\n return this.trackConversation(conversationId);\n }\n\n async joinChannel(conversationId: string): Promise<Conversation> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/join-channel`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ conversationId }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to join channel: ${response.status} ${body}`);\n }\n\n return this.trackConversation(conversationId);\n }\n\n async listChannels(opts?: { visibility?: ConversationVisibility; search?: string }): Promise<ConversationInfo[]> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/list-channels`, {\n method: 'POST',\n headers,\n body: JSON.stringify(opts ?? {}),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to list channels: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as { conversations?: ConversationInfo[] };\n return data.conversations ?? [];\n }\n\n // ===========================================================================\n // v2: DMs\n // ===========================================================================\n\n async openDM(userId: string): Promise<Conversation> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/create-dm`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ participantId: userId }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to open DM: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const conversationId = this.extractConversationId(data);\n return this.trackConversation(conversationId);\n }\n\n async openGroupDM(userIds: string[]): Promise<Conversation> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/create-group-dm`, {\n method: 'POST',\n headers,\n body: JSON.stringify({ participantIds: userIds }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to open group DM: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const conversationId = this.extractConversationId(data);\n return this.trackConversation(conversationId);\n }\n\n // ===========================================================================\n // v2: List conversations (all types)\n // ===========================================================================\n\n async listConversations(opts?: {\n type?: ConversationType;\n status?: string;\n visibility?: ConversationVisibility;\n limit?: number;\n offset?: number;\n }): Promise<ConversationInfo[]> {\n const headers = await this.buildHeaders();\n\n const response = await fetch(`${this.config.apiUrl}/chat/list-conversations`, {\n method: 'POST',\n headers,\n body: JSON.stringify(opts ?? {}),\n });\n\n if (!response.ok) {\n const body = await response.text();\n throw new Error(`Failed to list conversations: ${response.status} ${body}`);\n }\n\n const data = (await response.json()) as { conversations?: ConversationInfo[] };\n return data.conversations ?? [];\n }\n\n // ===========================================================================\n // Lifecycle\n // ===========================================================================\n\n closeConversation(conversationId: string): void {\n const conversation = this.conversations.get(conversationId);\n if (conversation) {\n conversation.disconnect();\n this.conversations.delete(conversationId);\n }\n // Clean up conversation-specific listeners\n this.conversationListeners.delete(conversationId);\n }\n\n destroy(): void {\n this.disconnect();\n for (const [id] of this.conversations) {\n this.conversations.delete(id);\n }\n this.conversationListeners.clear();\n this.globalListeners.clear();\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { GigabuddyChat } from './lib/GigabuddyChat.js';
|
|
2
2
|
export { Conversation } from './lib/Conversation.js';
|
|
3
|
-
export type { GigabuddyChatConfig, ChannelEvent, ChannelEventType, ConversationEventMap, Message, } from './lib/types.js';
|
|
3
|
+
export type { GigabuddyChatConfig, ChannelEvent, ChannelEventType, ConversationEventMap, Message, ConversationType, ConversationVisibility, MessageRole, MessageContent, TextContent, WidgetContent, DataContent, SystemContent, CompositeContent, MessageReaction, ConversationReadState, ConversationInfo, UserStreamEventType, ConversationAddedEvent, ConversationRemovedEvent, ChatEventBus, } from './lib/types.js';
|
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Conversation
|
|
3
3
|
*
|
|
4
|
-
* Represents an active conversation
|
|
5
|
-
*
|
|
4
|
+
* Represents an active conversation. Events are received via the
|
|
5
|
+
* parent GigabuddyChat's user stream — filtered by conversationId.
|
|
6
|
+
* connect()/disconnect() are no-ops (kept for backward compat).
|
|
6
7
|
*/
|
|
7
|
-
import type { ConversationEventMap, GigabuddyChatConfig, Message } from './types.js';
|
|
8
|
+
import type { ChatEventBus, ConversationEventMap, GigabuddyChatConfig, Message, MessageContent } from './types.js';
|
|
8
9
|
export declare class Conversation {
|
|
9
10
|
readonly conversationId: string;
|
|
10
11
|
private config;
|
|
11
|
-
private
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
private tokenRefreshTimer;
|
|
15
|
-
constructor(conversationId: string, config: GigabuddyChatConfig);
|
|
16
|
-
/**
|
|
17
|
-
* Send a message to this conversation.
|
|
18
|
-
* Calls the send-message action via the /chat/ proxy route.
|
|
19
|
-
*/
|
|
12
|
+
private eventBus;
|
|
13
|
+
private unsubscribes;
|
|
14
|
+
constructor(conversationId: string, config: GigabuddyChatConfig, eventBus?: ChatEventBus);
|
|
20
15
|
private buildHeaders;
|
|
21
|
-
sendMessage(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
sendMessage(content: string | MessageContent): Promise<void>;
|
|
17
|
+
replyTo(messageId: string, content: string | MessageContent): Promise<void>;
|
|
18
|
+
editMessage(messageId: string, content: string | MessageContent): Promise<void>;
|
|
19
|
+
deleteMessage(messageId: string): Promise<void>;
|
|
20
|
+
addReaction(messageId: string, emoji: string): Promise<void>;
|
|
21
|
+
removeReaction(messageId: string, emoji: string): Promise<void>;
|
|
22
|
+
markRead(messageId?: string): Promise<void>;
|
|
23
|
+
updateTopic(topic: string): Promise<void>;
|
|
24
|
+
addParticipant(userId: string): Promise<void>;
|
|
25
|
+
removeParticipant(userId: string): Promise<void>;
|
|
26
|
+
leave(): Promise<void>;
|
|
25
27
|
getMessages(opts?: {
|
|
26
28
|
limit?: number;
|
|
27
29
|
before?: string;
|
|
28
30
|
}): Promise<Message[]>;
|
|
31
|
+
getThreadReplies(parentMessageId: string, opts?: {
|
|
32
|
+
limit?: number;
|
|
33
|
+
before?: string;
|
|
34
|
+
}): Promise<Message[]>;
|
|
29
35
|
/**
|
|
30
|
-
* Register an event handler
|
|
36
|
+
* Register an event handler filtered to this conversation.
|
|
37
|
+
* Events are received from the parent GigabuddyChat's user stream.
|
|
38
|
+
* Returns an unsubscribe function.
|
|
31
39
|
*/
|
|
32
40
|
on<T extends keyof ConversationEventMap>(event: T, handler: ConversationEventMap[T]): () => void;
|
|
33
|
-
/**
|
|
34
|
-
* Start the SSE stream for realtime events.
|
|
35
|
-
*/
|
|
41
|
+
/** @deprecated No-op — SSE is managed by GigabuddyChat.connect() */
|
|
36
42
|
connect(): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* Close SSE stream and clean up.
|
|
39
|
-
*/
|
|
43
|
+
/** @deprecated No-op — SSE is managed by GigabuddyChat.disconnect() */
|
|
40
44
|
disconnect(): void;
|
|
41
|
-
private handleEvent;
|
|
42
|
-
private emit;
|
|
43
45
|
}
|
|
@@ -1,32 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GigabuddyChat
|
|
3
3
|
*
|
|
4
|
-
* Main entry point for the Chat SDK. Manages
|
|
4
|
+
* Main entry point for the Chat SDK. Manages a single user-level SSE
|
|
5
|
+
* connection and routes events to conversation-level listeners.
|
|
5
6
|
*/
|
|
6
7
|
import { Conversation } from './Conversation.js';
|
|
7
|
-
import type { GigabuddyChatConfig } from './types.js';
|
|
8
|
-
export declare class GigabuddyChat {
|
|
8
|
+
import type { ChatEventBus, ConversationInfo, ConversationType, ConversationVisibility, GigabuddyChatConfig } from './types.js';
|
|
9
|
+
export declare class GigabuddyChat implements ChatEventBus {
|
|
9
10
|
private config;
|
|
10
11
|
private conversations;
|
|
12
|
+
private sseClient;
|
|
13
|
+
private tokenRefreshTimer;
|
|
14
|
+
private _connected;
|
|
15
|
+
private globalListeners;
|
|
16
|
+
private conversationListeners;
|
|
17
|
+
private baseUrl;
|
|
18
|
+
private projectId;
|
|
11
19
|
constructor(config: GigabuddyChatConfig);
|
|
12
20
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Creates a new conversation via the API and optionally attaches a buddy.
|
|
16
|
-
* Returns a Conversation instance for sending messages and receiving events.
|
|
21
|
+
* Connect the user stream SSE. Call once after auth.
|
|
17
22
|
*/
|
|
23
|
+
connect(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if the user stream is connected.
|
|
26
|
+
*/
|
|
27
|
+
isConnected(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Disconnect the user stream and clean up all listeners.
|
|
30
|
+
*/
|
|
31
|
+
disconnect(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Listen for events across all conversations.
|
|
34
|
+
* Returns an unsubscribe function.
|
|
35
|
+
*/
|
|
36
|
+
on(event: string, handler: (...args: unknown[]) => void): () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Remove a global event listener.
|
|
39
|
+
*/
|
|
40
|
+
off(event: string, handler: (...args: unknown[]) => void): void;
|
|
41
|
+
addConversationListener(conversationId: string, event: string, handler: (...args: unknown[]) => void): () => void;
|
|
42
|
+
private handleStreamEvent;
|
|
43
|
+
private emitGlobal;
|
|
44
|
+
private buildHeaders;
|
|
45
|
+
private extractConversationId;
|
|
46
|
+
private trackConversation;
|
|
47
|
+
/**
|
|
48
|
+
* Get or create a Conversation handle for a known conversationId.
|
|
49
|
+
* Does not make any API calls — just returns a local wrapper.
|
|
50
|
+
*/
|
|
51
|
+
getConversation(conversationId: string): Conversation;
|
|
18
52
|
openConversation(options?: {
|
|
19
53
|
topic?: string;
|
|
20
54
|
buddyId?: string;
|
|
21
55
|
buddyProjectId?: string;
|
|
22
56
|
userName?: string;
|
|
23
57
|
}): Promise<Conversation>;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
58
|
+
createChannel(opts: {
|
|
59
|
+
name: string;
|
|
60
|
+
visibility?: ConversationVisibility;
|
|
61
|
+
description?: string;
|
|
62
|
+
topic?: string;
|
|
63
|
+
}): Promise<Conversation>;
|
|
64
|
+
joinChannel(conversationId: string): Promise<Conversation>;
|
|
65
|
+
listChannels(opts?: {
|
|
66
|
+
visibility?: ConversationVisibility;
|
|
67
|
+
search?: string;
|
|
68
|
+
}): Promise<ConversationInfo[]>;
|
|
69
|
+
openDM(userId: string): Promise<Conversation>;
|
|
70
|
+
openGroupDM(userIds: string[]): Promise<Conversation>;
|
|
71
|
+
listConversations(opts?: {
|
|
72
|
+
type?: ConversationType;
|
|
73
|
+
status?: string;
|
|
74
|
+
visibility?: ConversationVisibility;
|
|
75
|
+
limit?: number;
|
|
76
|
+
offset?: number;
|
|
77
|
+
}): Promise<ConversationInfo[]>;
|
|
27
78
|
closeConversation(conversationId: string): void;
|
|
28
|
-
/**
|
|
29
|
-
* Close all conversations and clean up.
|
|
30
|
-
*/
|
|
31
79
|
destroy(): void;
|
|
32
80
|
}
|
package/src/lib/types.d.ts
CHANGED
|
@@ -20,7 +20,96 @@ export interface GigabuddyChatConfig {
|
|
|
20
20
|
*/
|
|
21
21
|
userId?: string;
|
|
22
22
|
}
|
|
23
|
-
export type
|
|
23
|
+
export type ConversationType = 'channel' | 'dm' | 'group_dm';
|
|
24
|
+
export type ConversationVisibility = 'public' | 'private';
|
|
25
|
+
export type MessageRole = 'member' | 'buddy' | 'operator' | 'system';
|
|
26
|
+
/** Rich message content — discriminated union */
|
|
27
|
+
export type MessageContent = TextContent | WidgetContent | DataContent | SystemContent | CompositeContent;
|
|
28
|
+
export interface TextContent {
|
|
29
|
+
type: 'text';
|
|
30
|
+
text: string;
|
|
31
|
+
format?: 'plain' | 'markdown';
|
|
32
|
+
}
|
|
33
|
+
export interface WidgetContent {
|
|
34
|
+
type: 'widget';
|
|
35
|
+
widgetRef: {
|
|
36
|
+
type: 'widget';
|
|
37
|
+
id: string;
|
|
38
|
+
};
|
|
39
|
+
props?: Record<string, unknown>;
|
|
40
|
+
renderHint?: 'inline' | 'modal' | 'replace';
|
|
41
|
+
fallbackText?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface DataContent {
|
|
44
|
+
type: 'data';
|
|
45
|
+
key: string;
|
|
46
|
+
value: unknown;
|
|
47
|
+
displayAs?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface SystemContent {
|
|
50
|
+
type: 'system';
|
|
51
|
+
event: string;
|
|
52
|
+
text: string;
|
|
53
|
+
metadata?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
export interface CompositeContent {
|
|
56
|
+
type: 'composite';
|
|
57
|
+
blocks: (TextContent | WidgetContent | DataContent)[];
|
|
58
|
+
}
|
|
59
|
+
export interface MessageReaction {
|
|
60
|
+
emoji: string;
|
|
61
|
+
count: number;
|
|
62
|
+
userIds: string[];
|
|
63
|
+
}
|
|
64
|
+
export interface ConversationReadState {
|
|
65
|
+
lastReadMessageId: string;
|
|
66
|
+
lastReadAt: string;
|
|
67
|
+
unreadCount: number;
|
|
68
|
+
mentionCount: number;
|
|
69
|
+
threadUnreadCount: number;
|
|
70
|
+
}
|
|
71
|
+
export interface ConversationInfo {
|
|
72
|
+
id: string;
|
|
73
|
+
type?: ConversationType;
|
|
74
|
+
visibility?: ConversationVisibility;
|
|
75
|
+
name?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
topic?: string;
|
|
78
|
+
status: string;
|
|
79
|
+
participants: Array<{
|
|
80
|
+
contextInstanceId: string;
|
|
81
|
+
role: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
}>;
|
|
84
|
+
buddyRef?: {
|
|
85
|
+
type: 'buddy';
|
|
86
|
+
id: string;
|
|
87
|
+
};
|
|
88
|
+
messageCount: number;
|
|
89
|
+
lastMessageAt?: string;
|
|
90
|
+
lastMessagePreview?: string;
|
|
91
|
+
}
|
|
92
|
+
export type ChannelEventType = 'message' | 'widget' | 'data' | 'instruction' | 'typing' | 'status' | 'error' | 'thread_reply' | 'reaction_added' | 'participant_joined' | 'participant_left';
|
|
93
|
+
/** User-stream-level event types (superset of channel events) */
|
|
94
|
+
export type UserStreamEventType = ChannelEventType | 'conversation_added' | 'conversation_removed';
|
|
95
|
+
/** Data for conversation_added events */
|
|
96
|
+
export interface ConversationAddedEvent {
|
|
97
|
+
type: 'conversation_added';
|
|
98
|
+
conversationId: string;
|
|
99
|
+
conversation: ConversationInfo;
|
|
100
|
+
}
|
|
101
|
+
/** Data for conversation_removed events */
|
|
102
|
+
export interface ConversationRemovedEvent {
|
|
103
|
+
type: 'conversation_removed';
|
|
104
|
+
conversationId: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Event bus interface for routing user-stream events to conversations.
|
|
108
|
+
* GigabuddyChat implements this; Conversation uses it to filter events.
|
|
109
|
+
*/
|
|
110
|
+
export interface ChatEventBus {
|
|
111
|
+
addConversationListener(conversationId: string, event: string, handler: (...args: unknown[]) => void): () => void;
|
|
112
|
+
}
|
|
24
113
|
export interface ChannelEvent {
|
|
25
114
|
type: ChannelEventType;
|
|
26
115
|
conversationId: string;
|
|
@@ -34,11 +123,15 @@ export interface ChannelEvent {
|
|
|
34
123
|
export interface Message {
|
|
35
124
|
id: string;
|
|
36
125
|
role: string;
|
|
37
|
-
content: string;
|
|
126
|
+
content: string | MessageContent;
|
|
38
127
|
senderContextInstanceId?: string;
|
|
39
128
|
senderName?: string;
|
|
40
129
|
createdAt: string;
|
|
41
130
|
metadata?: Record<string, unknown>;
|
|
131
|
+
parentMessageId?: string;
|
|
132
|
+
threadCount?: number;
|
|
133
|
+
editedAt?: string;
|
|
134
|
+
reactions?: MessageReaction[];
|
|
42
135
|
}
|
|
43
136
|
export interface ConversationEventMap {
|
|
44
137
|
message: (event: ChannelEvent) => void;
|
|
@@ -48,4 +141,8 @@ export interface ConversationEventMap {
|
|
|
48
141
|
error: (event: ChannelEvent) => void;
|
|
49
142
|
connected: () => void;
|
|
50
143
|
disconnected: () => void;
|
|
144
|
+
thread_reply: (event: ChannelEvent) => void;
|
|
145
|
+
reaction_added: (event: ChannelEvent) => void;
|
|
146
|
+
participant_joined: (event: ChannelEvent) => void;
|
|
147
|
+
participant_left: (event: ChannelEvent) => void;
|
|
51
148
|
}
|