@gigabuddy/chat-sdk 0.1.0
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 +357 -0
- package/index.js.map +7 -0
- package/package.json +17 -0
- package/src/index.d.ts +3 -0
- package/src/lib/Conversation.d.ts +42 -0
- package/src/lib/GigabuddyChat.d.ts +32 -0
- package/src/lib/SSEClient.d.ts +33 -0
- package/src/lib/types.d.ts +45 -0
package/index.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
// libs/chat-sdk/src/lib/SSEClient.ts
|
|
2
|
+
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
3
|
+
var INITIAL_BACKOFF_MS = 1e3;
|
|
4
|
+
var MAX_BACKOFF_MS = 3e4;
|
|
5
|
+
var SSEClient = class {
|
|
6
|
+
config;
|
|
7
|
+
abortController = null;
|
|
8
|
+
reconnectTimer = null;
|
|
9
|
+
reconnectAttempt = 0;
|
|
10
|
+
stopped = false;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
}
|
|
14
|
+
async connect() {
|
|
15
|
+
this.stopped = false;
|
|
16
|
+
this.reconnectAttempt = 0;
|
|
17
|
+
await this.startStream();
|
|
18
|
+
}
|
|
19
|
+
disconnect() {
|
|
20
|
+
this.stopped = true;
|
|
21
|
+
if (this.reconnectTimer) {
|
|
22
|
+
clearTimeout(this.reconnectTimer);
|
|
23
|
+
this.reconnectTimer = null;
|
|
24
|
+
}
|
|
25
|
+
if (this.abortController) {
|
|
26
|
+
this.abortController.abort();
|
|
27
|
+
this.abortController = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async updateToken(newToken) {
|
|
31
|
+
const originalGetToken = this.config.getToken;
|
|
32
|
+
this.config.getToken = () => newToken;
|
|
33
|
+
if (this.abortController) {
|
|
34
|
+
this.abortController.abort();
|
|
35
|
+
this.abortController = null;
|
|
36
|
+
this.reconnectAttempt = 0;
|
|
37
|
+
await this.startStream();
|
|
38
|
+
}
|
|
39
|
+
this.config.getToken = originalGetToken;
|
|
40
|
+
}
|
|
41
|
+
async startStream() {
|
|
42
|
+
if (this.stopped)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
const token = await this.config.getToken();
|
|
46
|
+
this.abortController = new AbortController();
|
|
47
|
+
const response = await fetch(this.config.url, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${token}`,
|
|
51
|
+
Accept: "text/event-stream"
|
|
52
|
+
},
|
|
53
|
+
signal: this.abortController.signal
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
|
|
57
|
+
}
|
|
58
|
+
if (!response.body) {
|
|
59
|
+
throw new Error("SSE response has no body");
|
|
60
|
+
}
|
|
61
|
+
this.reconnectAttempt = 0;
|
|
62
|
+
await this.readStream(response.body);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (this.stopped)
|
|
65
|
+
return;
|
|
66
|
+
if (error instanceof DOMException && error.name === "AbortError")
|
|
67
|
+
return;
|
|
68
|
+
this.config.onDisconnected();
|
|
69
|
+
this.scheduleReconnect();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async readStream(body) {
|
|
73
|
+
const reader = body.getReader();
|
|
74
|
+
const decoder = new TextDecoder();
|
|
75
|
+
let buffer = "";
|
|
76
|
+
try {
|
|
77
|
+
while (true) {
|
|
78
|
+
const { done, value } = await reader.read();
|
|
79
|
+
if (done)
|
|
80
|
+
break;
|
|
81
|
+
buffer += decoder.decode(value, { stream: true });
|
|
82
|
+
const messages = buffer.split("\n\n");
|
|
83
|
+
buffer = messages.pop() ?? "";
|
|
84
|
+
for (const message of messages) {
|
|
85
|
+
this.parseSSEMessage(message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (this.stopped)
|
|
90
|
+
return;
|
|
91
|
+
if (error instanceof DOMException && error.name === "AbortError")
|
|
92
|
+
return;
|
|
93
|
+
} finally {
|
|
94
|
+
reader.releaseLock();
|
|
95
|
+
}
|
|
96
|
+
if (!this.stopped) {
|
|
97
|
+
this.config.onDisconnected();
|
|
98
|
+
this.scheduleReconnect();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
parseSSEMessage(message) {
|
|
102
|
+
if (message.trim().startsWith(":"))
|
|
103
|
+
return;
|
|
104
|
+
let eventType = "message";
|
|
105
|
+
let data = "";
|
|
106
|
+
for (const line of message.split("\n")) {
|
|
107
|
+
if (line.startsWith("event: ")) {
|
|
108
|
+
eventType = line.slice(7).trim();
|
|
109
|
+
} else if (line.startsWith("data: ")) {
|
|
110
|
+
data = line.slice(6);
|
|
111
|
+
} else if (line.startsWith("data:")) {
|
|
112
|
+
data = line.slice(5);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (eventType === "connected") {
|
|
116
|
+
this.config.onConnected();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!data)
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(data);
|
|
123
|
+
if (!parsed.type) {
|
|
124
|
+
parsed.type = eventType;
|
|
125
|
+
}
|
|
126
|
+
this.config.onEvent(parsed);
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
scheduleReconnect() {
|
|
131
|
+
if (this.stopped)
|
|
132
|
+
return;
|
|
133
|
+
if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS)
|
|
134
|
+
return;
|
|
135
|
+
const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, this.reconnectAttempt), MAX_BACKOFF_MS);
|
|
136
|
+
this.reconnectAttempt++;
|
|
137
|
+
this.reconnectTimer = setTimeout(() => {
|
|
138
|
+
this.reconnectTimer = null;
|
|
139
|
+
this.startStream();
|
|
140
|
+
}, backoff);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// libs/chat-sdk/src/lib/Conversation.ts
|
|
145
|
+
var Conversation = class {
|
|
146
|
+
conversationId;
|
|
147
|
+
config;
|
|
148
|
+
baseUrl;
|
|
149
|
+
sseClient = null;
|
|
150
|
+
listeners = /* @__PURE__ */ new Map();
|
|
151
|
+
tokenRefreshTimer = null;
|
|
152
|
+
constructor(conversationId, config) {
|
|
153
|
+
this.conversationId = conversationId;
|
|
154
|
+
this.config = config;
|
|
155
|
+
const url = new URL(config.apiUrl);
|
|
156
|
+
this.baseUrl = url.origin;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Send a message to this conversation.
|
|
160
|
+
* Calls the send-message action via the /chat/ proxy route.
|
|
161
|
+
*/
|
|
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() {
|
|
221
|
+
if (this.sseClient)
|
|
222
|
+
return;
|
|
223
|
+
this.sseClient = new SSEClient({
|
|
224
|
+
url: `${this.baseUrl}/ext/stream/${this.conversationId}`,
|
|
225
|
+
getToken: this.config.getToken,
|
|
226
|
+
onEvent: (event) => this.handleEvent(event),
|
|
227
|
+
onConnected: () => this.emit("connected"),
|
|
228
|
+
onDisconnected: () => this.emit("disconnected")
|
|
229
|
+
});
|
|
230
|
+
await this.sseClient.connect();
|
|
231
|
+
this.tokenRefreshTimer = setInterval(
|
|
232
|
+
async () => {
|
|
233
|
+
if (this.sseClient) {
|
|
234
|
+
const newToken = await this.config.getToken();
|
|
235
|
+
await this.sseClient.updateToken(newToken);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
50 * 60 * 1e3
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Close SSE stream and clean up.
|
|
243
|
+
*/
|
|
244
|
+
disconnect() {
|
|
245
|
+
if (this.tokenRefreshTimer) {
|
|
246
|
+
clearInterval(this.tokenRefreshTimer);
|
|
247
|
+
this.tokenRefreshTimer = null;
|
|
248
|
+
}
|
|
249
|
+
if (this.sseClient) {
|
|
250
|
+
this.sseClient.disconnect();
|
|
251
|
+
this.sseClient = null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
handleEvent(event) {
|
|
255
|
+
const handlers = this.listeners.get(event.type);
|
|
256
|
+
if (handlers) {
|
|
257
|
+
for (const handler of handlers) {
|
|
258
|
+
try {
|
|
259
|
+
handler(event);
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
emit(event) {
|
|
266
|
+
const handlers = this.listeners.get(event);
|
|
267
|
+
if (handlers) {
|
|
268
|
+
for (const handler of handlers) {
|
|
269
|
+
try {
|
|
270
|
+
handler();
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
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;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
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.
|
|
290
|
+
*/
|
|
291
|
+
async openConversation(options = {}) {
|
|
292
|
+
const token = await this.config.getToken();
|
|
293
|
+
const headers = {
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
Authorization: `Bearer ${token}`
|
|
296
|
+
};
|
|
297
|
+
const createResponse = await fetch(`${this.config.apiUrl}/chat/create-conversation`, {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers,
|
|
300
|
+
body: JSON.stringify({
|
|
301
|
+
...options.topic ? { topic: options.topic } : {},
|
|
302
|
+
...options.userName ? { userName: options.userName } : {}
|
|
303
|
+
})
|
|
304
|
+
});
|
|
305
|
+
if (!createResponse.ok) {
|
|
306
|
+
const body = await createResponse.text();
|
|
307
|
+
throw new Error(`Failed to create conversation: ${createResponse.status} ${body}`);
|
|
308
|
+
}
|
|
309
|
+
const createData = await createResponse.json();
|
|
310
|
+
const conversationId = createData.conversationId;
|
|
311
|
+
if (!conversationId) {
|
|
312
|
+
throw new Error("No conversationId returned from create-conversation");
|
|
313
|
+
}
|
|
314
|
+
if (options.buddyId) {
|
|
315
|
+
const attachResponse = await fetch(`${this.config.apiUrl}/chat/attach-buddy`, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers,
|
|
318
|
+
body: JSON.stringify({
|
|
319
|
+
conversationId,
|
|
320
|
+
buddyId: options.buddyId,
|
|
321
|
+
...options.buddyProjectId ? { buddyProjectId: options.buddyProjectId } : {}
|
|
322
|
+
})
|
|
323
|
+
});
|
|
324
|
+
if (!attachResponse.ok) {
|
|
325
|
+
const body = await attachResponse.text();
|
|
326
|
+
throw new Error(`Failed to attach buddy: ${attachResponse.status} ${body}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const conversation = new Conversation(conversationId, this.config);
|
|
330
|
+
this.conversations.set(conversationId, conversation);
|
|
331
|
+
return conversation;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Close and clean up a conversation.
|
|
335
|
+
*/
|
|
336
|
+
closeConversation(conversationId) {
|
|
337
|
+
const conversation = this.conversations.get(conversationId);
|
|
338
|
+
if (conversation) {
|
|
339
|
+
conversation.disconnect();
|
|
340
|
+
this.conversations.delete(conversationId);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Close all conversations and clean up.
|
|
345
|
+
*/
|
|
346
|
+
destroy() {
|
|
347
|
+
for (const [id, conversation] of this.conversations) {
|
|
348
|
+
conversation.disconnect();
|
|
349
|
+
this.conversations.delete(id);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
export {
|
|
354
|
+
Conversation,
|
|
355
|
+
GigabuddyChat
|
|
356
|
+
};
|
|
357
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
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 { conversationId?: string };\n const conversationId = createData.conversationId;\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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gigabuddy/chat-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Gigabuddy Chat SDK — embed AI-powered conversations in your app",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation
|
|
3
|
+
*
|
|
4
|
+
* Represents an active conversation with realtime event streaming.
|
|
5
|
+
* Wraps the SSE client and provides typed event handlers.
|
|
6
|
+
*/
|
|
7
|
+
import type { ConversationEventMap, GigabuddyChatConfig, Message } from './types.js';
|
|
8
|
+
export declare class Conversation {
|
|
9
|
+
readonly conversationId: string;
|
|
10
|
+
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
|
+
*/
|
|
24
|
+
getMessages(opts?: {
|
|
25
|
+
limit?: number;
|
|
26
|
+
before?: string;
|
|
27
|
+
}): Promise<Message[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Register an event handler. Returns an unsubscribe function.
|
|
30
|
+
*/
|
|
31
|
+
on<T extends keyof ConversationEventMap>(event: T, handler: ConversationEventMap[T]): () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Start the SSE stream for realtime events.
|
|
34
|
+
*/
|
|
35
|
+
connect(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Close SSE stream and clean up.
|
|
38
|
+
*/
|
|
39
|
+
disconnect(): void;
|
|
40
|
+
private handleEvent;
|
|
41
|
+
private emit;
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GigabuddyChat
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the Chat SDK. Manages conversation lifecycle.
|
|
5
|
+
*/
|
|
6
|
+
import { Conversation } from './Conversation.js';
|
|
7
|
+
import type { GigabuddyChatConfig } from './types.js';
|
|
8
|
+
export declare class GigabuddyChat {
|
|
9
|
+
private config;
|
|
10
|
+
private conversations;
|
|
11
|
+
constructor(config: GigabuddyChatConfig);
|
|
12
|
+
/**
|
|
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.
|
|
17
|
+
*/
|
|
18
|
+
openConversation(options?: {
|
|
19
|
+
topic?: string;
|
|
20
|
+
buddyId?: string;
|
|
21
|
+
buddyProjectId?: string;
|
|
22
|
+
userName?: string;
|
|
23
|
+
}): Promise<Conversation>;
|
|
24
|
+
/**
|
|
25
|
+
* Close and clean up a conversation.
|
|
26
|
+
*/
|
|
27
|
+
closeConversation(conversationId: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Close all conversations and clean up.
|
|
30
|
+
*/
|
|
31
|
+
destroy(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE Client
|
|
3
|
+
*
|
|
4
|
+
* Uses fetch() + ReadableStream instead of native EventSource
|
|
5
|
+
* to support Authorization headers. Handles reconnection with
|
|
6
|
+
* exponential backoff.
|
|
7
|
+
*/
|
|
8
|
+
import type { ChannelEvent } from './types.js';
|
|
9
|
+
export type SSEEventHandler = (event: ChannelEvent) => void;
|
|
10
|
+
export type SSEConnectionHandler = () => void;
|
|
11
|
+
interface SSEClientConfig {
|
|
12
|
+
url: string;
|
|
13
|
+
getToken: () => Promise<string> | string;
|
|
14
|
+
onEvent: SSEEventHandler;
|
|
15
|
+
onConnected: SSEConnectionHandler;
|
|
16
|
+
onDisconnected: SSEConnectionHandler;
|
|
17
|
+
}
|
|
18
|
+
export declare class SSEClient {
|
|
19
|
+
private config;
|
|
20
|
+
private abortController;
|
|
21
|
+
private reconnectTimer;
|
|
22
|
+
private reconnectAttempt;
|
|
23
|
+
private stopped;
|
|
24
|
+
constructor(config: SSEClientConfig);
|
|
25
|
+
connect(): Promise<void>;
|
|
26
|
+
disconnect(): void;
|
|
27
|
+
updateToken(newToken: string): Promise<void>;
|
|
28
|
+
private startStream;
|
|
29
|
+
private readStream;
|
|
30
|
+
private parseSSEMessage;
|
|
31
|
+
private scheduleReconnect;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat SDK public type definitions.
|
|
3
|
+
*/
|
|
4
|
+
export interface GigabuddyChatConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Customer project API URL.
|
|
7
|
+
* e.g. 'https://api.gigabuddy.com/orgSlug/projectId'
|
|
8
|
+
*
|
|
9
|
+
* The SDK calls `/chat/<action>` under this URL. The server
|
|
10
|
+
* resolves the target Conversations project from the project's
|
|
11
|
+
* `gigabuddy-chat` connection config.
|
|
12
|
+
*/
|
|
13
|
+
apiUrl: string;
|
|
14
|
+
/** Return a valid auth token (API key or Firebase ID token). */
|
|
15
|
+
getToken: () => Promise<string> | string;
|
|
16
|
+
}
|
|
17
|
+
export type ChannelEventType = 'message' | 'widget' | 'data' | 'instruction' | 'typing' | 'status' | 'error';
|
|
18
|
+
export interface ChannelEvent {
|
|
19
|
+
type: ChannelEventType;
|
|
20
|
+
conversationId: string;
|
|
21
|
+
eventId: string;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
data: Record<string, unknown>;
|
|
24
|
+
source?: string;
|
|
25
|
+
messageId?: string;
|
|
26
|
+
senderName?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Message {
|
|
29
|
+
id: string;
|
|
30
|
+
role: string;
|
|
31
|
+
content: string;
|
|
32
|
+
senderContextInstanceId?: string;
|
|
33
|
+
senderName?: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
export interface ConversationEventMap {
|
|
38
|
+
message: (event: ChannelEvent) => void;
|
|
39
|
+
widget: (event: ChannelEvent) => void;
|
|
40
|
+
typing: (event: ChannelEvent) => void;
|
|
41
|
+
status: (event: ChannelEvent) => void;
|
|
42
|
+
error: (event: ChannelEvent) => void;
|
|
43
|
+
connected: () => void;
|
|
44
|
+
disconnected: () => void;
|
|
45
|
+
}
|