@gigabuddy/chat-sdk 0.1.1 → 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 +539 -115
- package/index.js.map +3 -3
- package/package.json +1 -1
- package/src/index.d.ts +1 -1
- package/src/lib/Conversation.d.ts +28 -25
- package/src/lib/GigabuddyChat.d.ts +61 -13
- package/src/lib/types.d.ts +105 -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,93 +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.
|
|
458
|
+
* Connect the user stream SSE. Call once after auth.
|
|
161
459
|
*/
|
|
162
|
-
|
|
163
|
-
const token = await this.config.getToken();
|
|
164
|
-
const response = await fetch(`${this.config.apiUrl}/chat/send-message`, {
|
|
165
|
-
method: "POST",
|
|
166
|
-
headers: {
|
|
167
|
-
"Content-Type": "application/json",
|
|
168
|
-
Authorization: `Bearer ${token}`
|
|
169
|
-
},
|
|
170
|
-
body: JSON.stringify({
|
|
171
|
-
conversationId: this.conversationId,
|
|
172
|
-
text
|
|
173
|
-
})
|
|
174
|
-
});
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
const body = await response.text();
|
|
177
|
-
throw new Error(`Failed to send message: ${response.status} ${body}`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Get messages for this conversation.
|
|
182
|
-
*/
|
|
183
|
-
async getMessages(opts) {
|
|
184
|
-
const token = await this.config.getToken();
|
|
185
|
-
const response = await fetch(`${this.config.apiUrl}/chat/get-messages`, {
|
|
186
|
-
method: "POST",
|
|
187
|
-
headers: {
|
|
188
|
-
"Content-Type": "application/json",
|
|
189
|
-
Authorization: `Bearer ${token}`
|
|
190
|
-
},
|
|
191
|
-
body: JSON.stringify({
|
|
192
|
-
conversationId: this.conversationId,
|
|
193
|
-
...opts?.limit ? { limit: opts.limit } : {},
|
|
194
|
-
...opts?.before ? { before: opts.before } : {}
|
|
195
|
-
})
|
|
196
|
-
});
|
|
197
|
-
if (!response.ok) {
|
|
198
|
-
const body = await response.text();
|
|
199
|
-
throw new Error(`Failed to get messages: ${response.status} ${body}`);
|
|
200
|
-
}
|
|
201
|
-
const data = await response.json();
|
|
202
|
-
return data.messages ?? [];
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Register an event handler. Returns an unsubscribe function.
|
|
206
|
-
*/
|
|
207
|
-
on(event, handler) {
|
|
208
|
-
if (!this.listeners.has(event)) {
|
|
209
|
-
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
210
|
-
}
|
|
211
|
-
const handlers = this.listeners.get(event);
|
|
212
|
-
handlers.add(handler);
|
|
213
|
-
return () => {
|
|
214
|
-
handlers.delete(handler);
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Start the SSE stream for realtime events.
|
|
219
|
-
*/
|
|
220
|
-
async connect() {
|
|
460
|
+
connect() {
|
|
221
461
|
if (this.sseClient)
|
|
222
462
|
return;
|
|
223
463
|
this.sseClient = new SSEClient({
|
|
224
|
-
url: `${this.baseUrl}/ext/stream
|
|
464
|
+
url: `${this.baseUrl}/ext/stream/user?projectId=${encodeURIComponent(this.projectId)}`,
|
|
225
465
|
getToken: this.config.getToken,
|
|
226
|
-
onEvent: (event) => this.
|
|
227
|
-
onConnected: () =>
|
|
228
|
-
|
|
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
|
+
}
|
|
229
475
|
});
|
|
230
|
-
|
|
476
|
+
void this.sseClient.connect();
|
|
231
477
|
this.tokenRefreshTimer = setInterval(
|
|
232
478
|
async () => {
|
|
233
479
|
if (this.sseClient) {
|
|
@@ -239,7 +485,13 @@ var Conversation = class {
|
|
|
239
485
|
);
|
|
240
486
|
}
|
|
241
487
|
/**
|
|
242
|
-
*
|
|
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.
|
|
243
495
|
*/
|
|
244
496
|
disconnect() {
|
|
245
497
|
if (this.tokenRefreshTimer) {
|
|
@@ -250,50 +502,132 @@ var Conversation = class {
|
|
|
250
502
|
this.sseClient.disconnect();
|
|
251
503
|
this.sseClient = null;
|
|
252
504
|
}
|
|
505
|
+
this._connected = false;
|
|
506
|
+
}
|
|
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
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
253
566
|
}
|
|
254
|
-
|
|
255
|
-
const handlers = this.
|
|
567
|
+
emitGlobal(event, ...args) {
|
|
568
|
+
const handlers = this.globalListeners.get(event);
|
|
256
569
|
if (handlers) {
|
|
257
570
|
for (const handler of handlers) {
|
|
258
571
|
try {
|
|
259
|
-
handler(
|
|
572
|
+
handler(...args);
|
|
260
573
|
} catch {
|
|
261
574
|
}
|
|
262
575
|
}
|
|
263
576
|
}
|
|
264
577
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
578
|
+
// ===========================================================================
|
|
579
|
+
// API helpers
|
|
580
|
+
// ===========================================================================
|
|
581
|
+
async buildHeaders() {
|
|
582
|
+
const token = await this.config.getToken();
|
|
583
|
+
const headers = {
|
|
584
|
+
"Content-Type": "application/json",
|
|
585
|
+
Authorization: `Bearer ${token}`
|
|
586
|
+
};
|
|
587
|
+
if (this.config.userId) {
|
|
588
|
+
headers["X-Chat-User-Id"] = this.config.userId;
|
|
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") {
|
|
269
597
|
try {
|
|
270
|
-
|
|
598
|
+
conv = JSON.parse(conv);
|
|
271
599
|
} catch {
|
|
272
600
|
}
|
|
273
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");
|
|
274
608
|
}
|
|
609
|
+
return conversationId;
|
|
275
610
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.config = config;
|
|
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;
|
|
284
618
|
}
|
|
285
619
|
/**
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* Creates a new conversation via the API and optionally attaches a buddy.
|
|
289
|
-
* Returns a Conversation instance for sending messages and receiving events.
|
|
620
|
+
* Get or create a Conversation handle for a known conversationId.
|
|
621
|
+
* Does not make any API calls — just returns a local wrapper.
|
|
290
622
|
*/
|
|
623
|
+
getConversation(conversationId) {
|
|
624
|
+
return this.trackConversation(conversationId);
|
|
625
|
+
}
|
|
626
|
+
// ===========================================================================
|
|
627
|
+
// Conversation creation (v1 compat)
|
|
628
|
+
// ===========================================================================
|
|
291
629
|
async openConversation(options = {}) {
|
|
292
|
-
const
|
|
293
|
-
const headers = {
|
|
294
|
-
"Content-Type": "application/json",
|
|
295
|
-
Authorization: `Bearer ${token}`
|
|
296
|
-
};
|
|
630
|
+
const headers = await this.buildHeaders();
|
|
297
631
|
const createResponse = await fetch(`${this.config.apiUrl}/chat/create-conversation`, {
|
|
298
632
|
method: "POST",
|
|
299
633
|
headers,
|
|
@@ -307,10 +641,7 @@ var GigabuddyChat = class {
|
|
|
307
641
|
throw new Error(`Failed to create conversation: ${createResponse.status} ${body}`);
|
|
308
642
|
}
|
|
309
643
|
const createData = await createResponse.json();
|
|
310
|
-
const conversationId =
|
|
311
|
-
if (!conversationId) {
|
|
312
|
-
throw new Error("No conversationId returned from create-conversation");
|
|
313
|
-
}
|
|
644
|
+
const conversationId = this.extractConversationId(createData);
|
|
314
645
|
if (options.buddyId) {
|
|
315
646
|
const attachResponse = await fetch(`${this.config.apiUrl}/chat/attach-buddy`, {
|
|
316
647
|
method: "POST",
|
|
@@ -326,28 +657,121 @@ var GigabuddyChat = class {
|
|
|
326
657
|
throw new Error(`Failed to attach buddy: ${attachResponse.status} ${body}`);
|
|
327
658
|
}
|
|
328
659
|
}
|
|
329
|
-
|
|
330
|
-
this.conversations.set(conversationId, conversation);
|
|
331
|
-
return conversation;
|
|
660
|
+
return this.trackConversation(conversationId);
|
|
332
661
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
// ===========================================================================
|
|
336
760
|
closeConversation(conversationId) {
|
|
337
761
|
const conversation = this.conversations.get(conversationId);
|
|
338
762
|
if (conversation) {
|
|
339
763
|
conversation.disconnect();
|
|
340
764
|
this.conversations.delete(conversationId);
|
|
341
765
|
}
|
|
766
|
+
this.conversationListeners.delete(conversationId);
|
|
342
767
|
}
|
|
343
|
-
/**
|
|
344
|
-
* Close all conversations and clean up.
|
|
345
|
-
*/
|
|
346
768
|
destroy() {
|
|
347
|
-
|
|
348
|
-
|
|
769
|
+
this.disconnect();
|
|
770
|
+
for (const [id] of this.conversations) {
|
|
349
771
|
this.conversations.delete(id);
|
|
350
772
|
}
|
|
773
|
+
this.conversationListeners.clear();
|
|
774
|
+
this.globalListeners.clear();
|
|
351
775
|
}
|
|
352
776
|
};
|
|
353
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 async sendMessage(text: string): Promise<void> {\n const token = await this.config.getToken();\n const response = await fetch(`${this.config.apiUrl}/chat/send-message`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\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 token = await this.config.getToken();\n const response = await fetch(`${this.config.apiUrl}/chat/get-messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\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 = {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\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 {\n conversationId?: string;\n conversation?: { id?: string };\n };\n const conversationId = createData.conversationId ?? createData.conversation?.id;\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,42 +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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
private eventBus;
|
|
13
|
+
private unsubscribes;
|
|
14
|
+
constructor(conversationId: string, config: GigabuddyChatConfig, eventBus?: ChatEventBus);
|
|
15
|
+
private buildHeaders;
|
|
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>;
|
|
24
27
|
getMessages(opts?: {
|
|
25
28
|
limit?: number;
|
|
26
29
|
before?: string;
|
|
27
30
|
}): Promise<Message[]>;
|
|
31
|
+
getThreadReplies(parentMessageId: string, opts?: {
|
|
32
|
+
limit?: number;
|
|
33
|
+
before?: string;
|
|
34
|
+
}): Promise<Message[]>;
|
|
28
35
|
/**
|
|
29
|
-
* 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.
|
|
30
39
|
*/
|
|
31
40
|
on<T extends keyof ConversationEventMap>(event: T, handler: ConversationEventMap[T]): () => void;
|
|
32
|
-
/**
|
|
33
|
-
* Start the SSE stream for realtime events.
|
|
34
|
-
*/
|
|
41
|
+
/** @deprecated No-op — SSE is managed by GigabuddyChat.connect() */
|
|
35
42
|
connect(): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* Close SSE stream and clean up.
|
|
38
|
-
*/
|
|
43
|
+
/** @deprecated No-op — SSE is managed by GigabuddyChat.disconnect() */
|
|
39
44
|
disconnect(): void;
|
|
40
|
-
private handleEvent;
|
|
41
|
-
private emit;
|
|
42
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
|
@@ -13,8 +13,103 @@ export interface GigabuddyChatConfig {
|
|
|
13
13
|
apiUrl: string;
|
|
14
14
|
/** Return a valid auth token (API key or Firebase ID token). */
|
|
15
15
|
getToken: () => Promise<string> | string;
|
|
16
|
+
/**
|
|
17
|
+
* Optional user identifier sent as `X-Chat-User-Id` header.
|
|
18
|
+
* Used by the backend to scope conversations to a specific user
|
|
19
|
+
* when authenticating with an API key.
|
|
20
|
+
*/
|
|
21
|
+
userId?: string;
|
|
22
|
+
}
|
|
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;
|
|
16
112
|
}
|
|
17
|
-
export type ChannelEventType = 'message' | 'widget' | 'data' | 'instruction' | 'typing' | 'status' | 'error';
|
|
18
113
|
export interface ChannelEvent {
|
|
19
114
|
type: ChannelEventType;
|
|
20
115
|
conversationId: string;
|
|
@@ -28,11 +123,15 @@ export interface ChannelEvent {
|
|
|
28
123
|
export interface Message {
|
|
29
124
|
id: string;
|
|
30
125
|
role: string;
|
|
31
|
-
content: string;
|
|
126
|
+
content: string | MessageContent;
|
|
32
127
|
senderContextInstanceId?: string;
|
|
33
128
|
senderName?: string;
|
|
34
129
|
createdAt: string;
|
|
35
130
|
metadata?: Record<string, unknown>;
|
|
131
|
+
parentMessageId?: string;
|
|
132
|
+
threadCount?: number;
|
|
133
|
+
editedAt?: string;
|
|
134
|
+
reactions?: MessageReaction[];
|
|
36
135
|
}
|
|
37
136
|
export interface ConversationEventMap {
|
|
38
137
|
message: (event: ChannelEvent) => void;
|
|
@@ -42,4 +141,8 @@ export interface ConversationEventMap {
|
|
|
42
141
|
error: (event: ChannelEvent) => void;
|
|
43
142
|
connected: () => void;
|
|
44
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;
|
|
45
148
|
}
|