simple-support-chat 0.3.3 → 0.4.1
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/README.md +197 -3
- package/dist/client/index.cjs +205 -103
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +86 -22
- package/dist/client/index.d.ts +86 -22
- package/dist/client/index.js +205 -104
- package/dist/client/index.js.map +1 -1
- package/dist/server/index.cjs +203 -7
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +120 -1
- package/dist/server/index.d.ts +120 -1
- package/dist/server/index.js +201 -8
- package/dist/server/index.js.map +1 -1
- package/package.json +87 -87
package/dist/server/index.cjs
CHANGED
|
@@ -45,7 +45,7 @@ var InMemoryStore = class {
|
|
|
45
45
|
|
|
46
46
|
// src/server/handler.ts
|
|
47
47
|
async function handleMessage(body, options) {
|
|
48
|
-
const { slack, slackChannel, botName, botIcon, onMessage, store } = options;
|
|
48
|
+
const { slack, slackChannel, botName, botIcon, onMessage, store, emitter } = options;
|
|
49
49
|
if (!body.message || typeof body.message !== "string") {
|
|
50
50
|
return {
|
|
51
51
|
data: { success: false, error: "Missing required field: message" },
|
|
@@ -108,6 +108,9 @@ ${body.message}`,
|
|
|
108
108
|
user: body.user,
|
|
109
109
|
context: body.context
|
|
110
110
|
});
|
|
111
|
+
if (emitter) {
|
|
112
|
+
emitter.emitThreadCreated(body.sessionId, threadTs);
|
|
113
|
+
}
|
|
111
114
|
return {
|
|
112
115
|
data: { success: true, threadId: threadTs },
|
|
113
116
|
status: 200
|
|
@@ -122,7 +125,7 @@ ${body.message}`,
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
function createSupportHandler(options) {
|
|
125
|
-
const { slackBotToken, slackChannel, botName, botIcon, onMessage, store } = options;
|
|
128
|
+
const { slackBotToken, slackChannel, botName, botIcon, onMessage, store, emitter } = options;
|
|
126
129
|
const slack = new webApi.WebClient(slackBotToken);
|
|
127
130
|
const resolvedStore = store ?? new InMemoryStore();
|
|
128
131
|
return async (request) => {
|
|
@@ -147,13 +150,14 @@ function createSupportHandler(options) {
|
|
|
147
150
|
botName,
|
|
148
151
|
botIcon,
|
|
149
152
|
onMessage,
|
|
150
|
-
store: resolvedStore
|
|
153
|
+
store: resolvedStore,
|
|
154
|
+
emitter
|
|
151
155
|
});
|
|
152
156
|
return jsonResponse(result.data, result.status);
|
|
153
157
|
};
|
|
154
158
|
}
|
|
155
159
|
function createExpressHandler(options) {
|
|
156
|
-
const { slackBotToken, slackChannel, botName, botIcon, onMessage, store } = options;
|
|
160
|
+
const { slackBotToken, slackChannel, botName, botIcon, onMessage, store, emitter } = options;
|
|
157
161
|
const slack = new webApi.WebClient(slackBotToken);
|
|
158
162
|
const resolvedStore = store ?? new InMemoryStore();
|
|
159
163
|
return async (req, res) => {
|
|
@@ -164,7 +168,8 @@ function createExpressHandler(options) {
|
|
|
164
168
|
botName,
|
|
165
169
|
botIcon,
|
|
166
170
|
onMessage,
|
|
167
|
-
store: resolvedStore
|
|
171
|
+
store: resolvedStore,
|
|
172
|
+
emitter
|
|
168
173
|
});
|
|
169
174
|
res.status(result.status).json(result.data);
|
|
170
175
|
};
|
|
@@ -2136,7 +2141,7 @@ function verifySlackSignature(signingSecret, signature, timestamp, rawBody) {
|
|
|
2136
2141
|
}
|
|
2137
2142
|
}
|
|
2138
2143
|
function createWebhookHandler(options) {
|
|
2139
|
-
const { store, signingSecret } = options;
|
|
2144
|
+
const { store, signingSecret, emitter } = options;
|
|
2140
2145
|
return async (request) => {
|
|
2141
2146
|
let rawBody;
|
|
2142
2147
|
try {
|
|
@@ -2187,13 +2192,16 @@ function createWebhookHandler(options) {
|
|
|
2187
2192
|
threadTs: event.thread_ts
|
|
2188
2193
|
};
|
|
2189
2194
|
await store.saveReply(threadRecord.sessionId, reply);
|
|
2195
|
+
if (emitter) {
|
|
2196
|
+
emitter.emit(threadRecord.sessionId, reply);
|
|
2197
|
+
}
|
|
2190
2198
|
return jsonResponse2({ ok: true }, 200);
|
|
2191
2199
|
}
|
|
2192
2200
|
return jsonResponse2({ ok: true }, 200);
|
|
2193
2201
|
};
|
|
2194
2202
|
}
|
|
2195
2203
|
function createExpressWebhookHandler(options) {
|
|
2196
|
-
const { store, signingSecret } = options;
|
|
2204
|
+
const { store, signingSecret, emitter } = options;
|
|
2197
2205
|
return async (req, res) => {
|
|
2198
2206
|
const rawBody = typeof req.body === "string" ? req.body : Buffer.isBuffer(req.body) ? req.body.toString("utf-8") : JSON.stringify(req.body);
|
|
2199
2207
|
const signature = req.headers?.["x-slack-signature"] ?? "";
|
|
@@ -2244,6 +2252,9 @@ function createExpressWebhookHandler(options) {
|
|
|
2244
2252
|
threadTs: event.thread_ts
|
|
2245
2253
|
};
|
|
2246
2254
|
await store.saveReply(threadRecord.sessionId, reply);
|
|
2255
|
+
if (emitter) {
|
|
2256
|
+
emitter.emit(threadRecord.sessionId, reply);
|
|
2257
|
+
}
|
|
2247
2258
|
res.status(200).json({ ok: true });
|
|
2248
2259
|
return;
|
|
2249
2260
|
}
|
|
@@ -2306,11 +2317,196 @@ function jsonResponse3(data, status = 200) {
|
|
|
2306
2317
|
});
|
|
2307
2318
|
}
|
|
2308
2319
|
|
|
2320
|
+
// src/server/emitter.ts
|
|
2321
|
+
function createReplyEmitter() {
|
|
2322
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
2323
|
+
return {
|
|
2324
|
+
emit(sessionId, reply) {
|
|
2325
|
+
const callbacks = subscribers.get(sessionId);
|
|
2326
|
+
if (!callbacks) return;
|
|
2327
|
+
const event = { type: "reply", sessionId, reply };
|
|
2328
|
+
for (const cb of callbacks) {
|
|
2329
|
+
try {
|
|
2330
|
+
cb(event);
|
|
2331
|
+
} catch {
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2335
|
+
emitThreadCreated(sessionId, threadTs) {
|
|
2336
|
+
const callbacks = subscribers.get(sessionId);
|
|
2337
|
+
if (!callbacks) return;
|
|
2338
|
+
const event = {
|
|
2339
|
+
type: "thread_created",
|
|
2340
|
+
sessionId,
|
|
2341
|
+
threadTs
|
|
2342
|
+
};
|
|
2343
|
+
for (const cb of callbacks) {
|
|
2344
|
+
try {
|
|
2345
|
+
cb(event);
|
|
2346
|
+
} catch {
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
},
|
|
2350
|
+
subscribe(sessionId, callback) {
|
|
2351
|
+
let callbacks = subscribers.get(sessionId);
|
|
2352
|
+
if (!callbacks) {
|
|
2353
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
2354
|
+
subscribers.set(sessionId, callbacks);
|
|
2355
|
+
}
|
|
2356
|
+
callbacks.add(callback);
|
|
2357
|
+
},
|
|
2358
|
+
unsubscribe(sessionId, callback) {
|
|
2359
|
+
const callbacks = subscribers.get(sessionId);
|
|
2360
|
+
if (!callbacks) return;
|
|
2361
|
+
callbacks.delete(callback);
|
|
2362
|
+
if (callbacks.size === 0) {
|
|
2363
|
+
subscribers.delete(sessionId);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// src/server/sse.ts
|
|
2370
|
+
function createSSEHandler(options) {
|
|
2371
|
+
const { emitter, heartbeatInterval = 3e4 } = options;
|
|
2372
|
+
return (request) => {
|
|
2373
|
+
const url = new URL(request.url);
|
|
2374
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
2375
|
+
if (!sessionId) {
|
|
2376
|
+
return new Response(
|
|
2377
|
+
JSON.stringify({ error: "Missing required query parameter: sessionId" }),
|
|
2378
|
+
{
|
|
2379
|
+
status: 400,
|
|
2380
|
+
headers: { "Content-Type": "application/json" }
|
|
2381
|
+
}
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
const sid = sessionId;
|
|
2385
|
+
let heartbeatTimer = null;
|
|
2386
|
+
let unsubscribed = false;
|
|
2387
|
+
const stream = new ReadableStream({
|
|
2388
|
+
start(controller) {
|
|
2389
|
+
const callback = (event) => {
|
|
2390
|
+
if (unsubscribed) return;
|
|
2391
|
+
try {
|
|
2392
|
+
if (event.type === "reply") {
|
|
2393
|
+
const data = JSON.stringify({ reply: event.reply });
|
|
2394
|
+
controller.enqueue(formatSSE("reply", data));
|
|
2395
|
+
} else if (event.type === "thread_created") {
|
|
2396
|
+
const data = JSON.stringify({ threadTs: event.threadTs });
|
|
2397
|
+
controller.enqueue(formatSSE("thread_created", data));
|
|
2398
|
+
}
|
|
2399
|
+
} catch {
|
|
2400
|
+
cleanup();
|
|
2401
|
+
}
|
|
2402
|
+
};
|
|
2403
|
+
emitter.subscribe(sid, callback);
|
|
2404
|
+
heartbeatTimer = setInterval(() => {
|
|
2405
|
+
try {
|
|
2406
|
+
controller.enqueue(formatSSE("heartbeat", "{}"));
|
|
2407
|
+
} catch {
|
|
2408
|
+
cleanup();
|
|
2409
|
+
}
|
|
2410
|
+
}, heartbeatInterval);
|
|
2411
|
+
function cleanup() {
|
|
2412
|
+
if (unsubscribed) return;
|
|
2413
|
+
unsubscribed = true;
|
|
2414
|
+
emitter.unsubscribe(sid, callback);
|
|
2415
|
+
if (heartbeatTimer !== null) {
|
|
2416
|
+
clearInterval(heartbeatTimer);
|
|
2417
|
+
heartbeatTimer = null;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (request.signal) {
|
|
2421
|
+
request.signal.addEventListener("abort", () => {
|
|
2422
|
+
cleanup();
|
|
2423
|
+
try {
|
|
2424
|
+
controller.close();
|
|
2425
|
+
} catch {
|
|
2426
|
+
}
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
},
|
|
2430
|
+
cancel() {
|
|
2431
|
+
unsubscribed = true;
|
|
2432
|
+
if (heartbeatTimer !== null) {
|
|
2433
|
+
clearInterval(heartbeatTimer);
|
|
2434
|
+
heartbeatTimer = null;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
return new Response(stream, {
|
|
2439
|
+
status: 200,
|
|
2440
|
+
headers: {
|
|
2441
|
+
"Content-Type": "text/event-stream",
|
|
2442
|
+
"Cache-Control": "no-cache",
|
|
2443
|
+
Connection: "keep-alive"
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
function createExpressSSEHandler(options) {
|
|
2449
|
+
const { emitter, heartbeatInterval = 3e4 } = options;
|
|
2450
|
+
return (req, res) => {
|
|
2451
|
+
const sessionId = req.query?.sessionId;
|
|
2452
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
2453
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2454
|
+
res.write(JSON.stringify({ error: "Missing required query parameter: sessionId" }));
|
|
2455
|
+
res.end();
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
res.writeHead(200, {
|
|
2459
|
+
"Content-Type": "text/event-stream",
|
|
2460
|
+
"Cache-Control": "no-cache",
|
|
2461
|
+
Connection: "keep-alive"
|
|
2462
|
+
});
|
|
2463
|
+
let cleaned = false;
|
|
2464
|
+
const callback = (event) => {
|
|
2465
|
+
if (cleaned) return;
|
|
2466
|
+
if (event.type === "reply") {
|
|
2467
|
+
const data = JSON.stringify({ reply: event.reply });
|
|
2468
|
+
res.write(formatSSEString("reply", data));
|
|
2469
|
+
} else if (event.type === "thread_created") {
|
|
2470
|
+
const data = JSON.stringify({ threadTs: event.threadTs });
|
|
2471
|
+
res.write(formatSSEString("thread_created", data));
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
emitter.subscribe(sessionId, callback);
|
|
2475
|
+
const heartbeatTimer = setInterval(() => {
|
|
2476
|
+
if (cleaned) return;
|
|
2477
|
+
res.write(formatSSEString("heartbeat", "{}"));
|
|
2478
|
+
}, heartbeatInterval);
|
|
2479
|
+
function cleanup() {
|
|
2480
|
+
if (cleaned) return;
|
|
2481
|
+
cleaned = true;
|
|
2482
|
+
emitter.unsubscribe(sessionId, callback);
|
|
2483
|
+
clearInterval(heartbeatTimer);
|
|
2484
|
+
}
|
|
2485
|
+
res.on("close", cleanup);
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
function formatSSE(event, data) {
|
|
2489
|
+
const text = `event: ${event}
|
|
2490
|
+
data: ${data}
|
|
2491
|
+
|
|
2492
|
+
`;
|
|
2493
|
+
return new TextEncoder().encode(text);
|
|
2494
|
+
}
|
|
2495
|
+
function formatSSEString(event, data) {
|
|
2496
|
+
return `event: ${event}
|
|
2497
|
+
data: ${data}
|
|
2498
|
+
|
|
2499
|
+
`;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2309
2502
|
exports.InMemoryStore = InMemoryStore;
|
|
2310
2503
|
exports.createExpressHandler = createExpressHandler;
|
|
2311
2504
|
exports.createExpressRepliesHandler = createExpressRepliesHandler;
|
|
2505
|
+
exports.createExpressSSEHandler = createExpressSSEHandler;
|
|
2312
2506
|
exports.createExpressWebhookHandler = createExpressWebhookHandler;
|
|
2313
2507
|
exports.createRepliesHandler = createRepliesHandler;
|
|
2508
|
+
exports.createReplyEmitter = createReplyEmitter;
|
|
2509
|
+
exports.createSSEHandler = createSSEHandler;
|
|
2314
2510
|
exports.createSupportHandler = createSupportHandler;
|
|
2315
2511
|
exports.createWebhookHandler = createWebhookHandler;
|
|
2316
2512
|
exports.emojify = emojify;
|