@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 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
- await this.readStream(response.body);
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/Conversation.ts
145
- var Conversation = class {
146
- conversationId;
432
+ // libs/chat-sdk/src/lib/GigabuddyChat.ts
433
+ var GigabuddyChat = class {
147
434
  config;
148
- baseUrl;
435
+ conversations = /* @__PURE__ */ new Map();
436
+ // User stream SSE
149
437
  sseClient = null;
150
- listeners = /* @__PURE__ */ new Map();
151
438
  tokenRefreshTimer = null;
152
- constructor(conversationId, config) {
153
- this.conversationId = conversationId;
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
- * Send a message to this conversation.
160
- * Calls the send-message action via the /chat/ proxy route.
458
+ * Connect the user stream SSE. Call once after auth.
161
459
  */
162
- async sendMessage(text) {
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/${this.conversationId}`,
464
+ url: `${this.baseUrl}/ext/stream/user?projectId=${encodeURIComponent(this.projectId)}`,
225
465
  getToken: this.config.getToken,
226
- onEvent: (event) => this.handleEvent(event),
227
- onConnected: () => this.emit("connected"),
228
- onDisconnected: () => this.emit("disconnected")
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
- await this.sseClient.connect();
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
- * Close SSE stream and clean up.
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
- handleEvent(event) {
255
- const handlers = this.listeners.get(event.type);
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(event);
572
+ handler(...args);
260
573
  } catch {
261
574
  }
262
575
  }
263
576
  }
264
577
  }
265
- emit(event) {
266
- const handlers = this.listeners.get(event);
267
- if (handlers) {
268
- for (const handler of handlers) {
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
- handler();
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
- // libs/chat-sdk/src/lib/GigabuddyChat.ts
279
- var GigabuddyChat = class {
280
- config;
281
- conversations = /* @__PURE__ */ new Map();
282
- constructor(config) {
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
- * Open a conversation.
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 token = await this.config.getToken();
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 = createData.conversationId ?? createData.conversation?.id;
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
- const conversation = new Conversation(conversationId, this.config);
330
- this.conversations.set(conversationId, conversation);
331
- return conversation;
660
+ return this.trackConversation(conversationId);
332
661
  }
333
- /**
334
- * Close and clean up a conversation.
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
- for (const [id, conversation] of this.conversations) {
348
- conversation.disconnect();
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/SSEClient.ts", "../../../libs/chat-sdk/src/lib/Conversation.ts", "../../../libs/chat-sdk/src/lib/GigabuddyChat.ts"],
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gigabuddy/chat-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Gigabuddy Chat SDK — embed AI-powered conversations in your app",
5
5
  "type": "module",
6
6
  "main": "./index.js",
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 with realtime event streaming.
5
- * Wraps the SSE client and provides typed event handlers.
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 baseUrl;
12
- private sseClient;
13
- private listeners;
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
- */
20
- sendMessage(text: string): Promise<void>;
21
- /**
22
- * Get messages for this conversation.
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. Returns an unsubscribe function.
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 conversation lifecycle.
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
- * Open a conversation.
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
- * Close and clean up a conversation.
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
  }
@@ -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
  }