@bootdesk/js-web-adapter-core 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/README.md +78 -0
- package/dist/index.cjs +1042 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +433 -0
- package/dist/index.d.ts +433 -0
- package/dist/index.js +997 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ChatEvent: () => ChatEvent,
|
|
24
|
+
DMRequestedEvent: () => DMRequestedEvent,
|
|
25
|
+
HttpClient: () => HttpClient,
|
|
26
|
+
LaravelEchoBroadcastClient: () => LaravelEchoBroadcastClient,
|
|
27
|
+
MessageDeletedEvent: () => MessageDeletedEvent,
|
|
28
|
+
MessageEditedEvent: () => MessageEditedEvent,
|
|
29
|
+
MessagePostedEvent: () => MessagePostedEvent,
|
|
30
|
+
PushManager: () => PushManager,
|
|
31
|
+
PusherBroadcastClient: () => PusherBroadcastClient,
|
|
32
|
+
ReactionAddedEvent: () => ReactionAddedEvent,
|
|
33
|
+
ReactionRemovedEvent: () => ReactionRemovedEvent,
|
|
34
|
+
StreamingChunkEvent: () => StreamingChunkEvent,
|
|
35
|
+
TypingStartedEvent: () => TypingStartedEvent,
|
|
36
|
+
UnknownEvent: () => UnknownEvent,
|
|
37
|
+
WebChatClient: () => WebChatClient,
|
|
38
|
+
createPushSubscriptionHandlers: () => createPushSubscriptionHandlers,
|
|
39
|
+
generateConversationId: () => generateConversationId,
|
|
40
|
+
generateId: () => generateId,
|
|
41
|
+
parseChatEvent: () => parseChatEvent
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(index_exports);
|
|
44
|
+
|
|
45
|
+
// src/client/HttpClient.ts
|
|
46
|
+
var HttpClient = class {
|
|
47
|
+
constructor(config) {
|
|
48
|
+
const headers = { ...config.headers };
|
|
49
|
+
if (config.verifyToken) {
|
|
50
|
+
headers["X-Verify-Token"] = config.verifyToken;
|
|
51
|
+
}
|
|
52
|
+
this.config = { apiUrl: config.apiUrl, timeout: config.timeout ?? 3e4, headers };
|
|
53
|
+
}
|
|
54
|
+
async get(url, signal) {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
57
|
+
try {
|
|
58
|
+
const fullUrl = this.resolve(url);
|
|
59
|
+
const combined = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
60
|
+
const response = await fetch(fullUrl, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: this.config.headers,
|
|
63
|
+
signal: combined
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
66
|
+
return response.json();
|
|
67
|
+
} finally {
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async post(url, body, signal) {
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
74
|
+
try {
|
|
75
|
+
const fullUrl = this.resolve(url);
|
|
76
|
+
const combined = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
77
|
+
const response = await fetch(fullUrl, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "Content-Type": "application/json", ...this.config.headers },
|
|
80
|
+
signal: combined,
|
|
81
|
+
body: JSON.stringify(body)
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
84
|
+
return response.json();
|
|
85
|
+
} finally {
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async delete(url, signal) {
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
92
|
+
try {
|
|
93
|
+
const fullUrl = this.resolve(url);
|
|
94
|
+
const combined = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
|
|
95
|
+
const response = await fetch(fullUrl, {
|
|
96
|
+
method: "DELETE",
|
|
97
|
+
headers: this.config.headers,
|
|
98
|
+
signal: combined
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
return text ? JSON.parse(text) : void 0;
|
|
103
|
+
} finally {
|
|
104
|
+
clearTimeout(timeoutId);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async sendMessage(messages, endpoint = "/api/webhooks/web", conversationId) {
|
|
108
|
+
return this.post(endpoint, { id: conversationId, messages });
|
|
109
|
+
}
|
|
110
|
+
async sendAction(actionId, value, messageId, conversationId, endpoint = "/api/webhooks/web") {
|
|
111
|
+
return this.post(endpoint, {
|
|
112
|
+
id: conversationId,
|
|
113
|
+
action: { actionId, value, messageId }
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async editMessage(messageId, newText, endpointTemplate = "/api/chat/messages/{id}/edit") {
|
|
117
|
+
const url = this.expandTemplate(endpointTemplate, { id: messageId });
|
|
118
|
+
await this.post(url, { text: newText });
|
|
119
|
+
}
|
|
120
|
+
async deleteMessage(messageId, endpointTemplate = "/api/chat/messages/{id}") {
|
|
121
|
+
const url = this.expandTemplate(endpointTemplate, { id: messageId });
|
|
122
|
+
await this.delete(url);
|
|
123
|
+
}
|
|
124
|
+
async addReaction(messageId, emoji, endpointTemplate = "/api/chat/messages/{id}/reactions") {
|
|
125
|
+
const url = this.expandTemplate(endpointTemplate, { id: messageId });
|
|
126
|
+
await this.post(url, { emoji });
|
|
127
|
+
}
|
|
128
|
+
async removeReaction(messageId, emoji, endpointTemplate = "/api/chat/messages/{id}/reactions/{emoji}") {
|
|
129
|
+
const url = this.expandTemplate(endpointTemplate, { id: messageId, emoji });
|
|
130
|
+
await this.delete(url);
|
|
131
|
+
}
|
|
132
|
+
resolve(url) {
|
|
133
|
+
return /^https?:\/\//.test(url) ? url : `${this.config.apiUrl}${url}`;
|
|
134
|
+
}
|
|
135
|
+
expandTemplate(template, params) {
|
|
136
|
+
let url = template;
|
|
137
|
+
for (const [key, value] of Object.entries(params)) {
|
|
138
|
+
url = url.replace(`{${key}}`, encodeURIComponent(value));
|
|
139
|
+
}
|
|
140
|
+
return this.resolve(url);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/events/base/ChatEvent.ts
|
|
145
|
+
var ChatEvent = class {
|
|
146
|
+
constructor(type, threadId, timestamp) {
|
|
147
|
+
this.type = type;
|
|
148
|
+
this.threadId = threadId;
|
|
149
|
+
this.timestamp = timestamp;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var UnknownEvent = class extends ChatEvent {
|
|
153
|
+
constructor(type, threadId, data, timestamp) {
|
|
154
|
+
super(type, threadId, timestamp);
|
|
155
|
+
this.data = data;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/events/MessagePostedEvent.ts
|
|
160
|
+
var MessagePostedEvent = class extends ChatEvent {
|
|
161
|
+
constructor(threadId, messageId, text, author, card, attachments, timestamp) {
|
|
162
|
+
super("message.posted", threadId, timestamp ?? Date.now());
|
|
163
|
+
this.messageId = messageId;
|
|
164
|
+
this.text = text;
|
|
165
|
+
this.author = author;
|
|
166
|
+
this.card = card;
|
|
167
|
+
this.attachments = attachments;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// src/events/MessageEditedEvent.ts
|
|
172
|
+
var MessageEditedEvent = class extends ChatEvent {
|
|
173
|
+
constructor(threadId, messageId, newText, card, timestamp) {
|
|
174
|
+
super("message.edited", threadId, timestamp ?? Date.now());
|
|
175
|
+
this.messageId = messageId;
|
|
176
|
+
this.newText = newText;
|
|
177
|
+
this.card = card;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// src/events/MessageDeletedEvent.ts
|
|
182
|
+
var MessageDeletedEvent = class extends ChatEvent {
|
|
183
|
+
constructor(threadId, messageId, timestamp) {
|
|
184
|
+
super("message.deleted", threadId, timestamp ?? Date.now());
|
|
185
|
+
this.messageId = messageId;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/events/ReactionAddedEvent.ts
|
|
190
|
+
var ReactionAddedEvent = class extends ChatEvent {
|
|
191
|
+
constructor(threadId, messageId, emoji, user, timestamp) {
|
|
192
|
+
super("reaction.added", threadId, timestamp ?? Date.now());
|
|
193
|
+
this.messageId = messageId;
|
|
194
|
+
this.emoji = emoji;
|
|
195
|
+
this.user = user;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/events/ReactionRemovedEvent.ts
|
|
200
|
+
var ReactionRemovedEvent = class extends ChatEvent {
|
|
201
|
+
constructor(threadId, messageId, emoji, user, timestamp) {
|
|
202
|
+
super("reaction.removed", threadId, timestamp ?? Date.now());
|
|
203
|
+
this.messageId = messageId;
|
|
204
|
+
this.emoji = emoji;
|
|
205
|
+
this.user = user;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/events/TypingStartedEvent.ts
|
|
210
|
+
var TypingStartedEvent = class extends ChatEvent {
|
|
211
|
+
constructor(threadId, userId, timestamp) {
|
|
212
|
+
super("typing.started", threadId, timestamp ?? Date.now());
|
|
213
|
+
this.userId = userId;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/events/StreamingChunkEvent.ts
|
|
218
|
+
var StreamingChunkEvent = class extends ChatEvent {
|
|
219
|
+
constructor(threadId, messageId, chunk, isFinal, timestamp) {
|
|
220
|
+
super("streaming.chunk", threadId, timestamp ?? Date.now());
|
|
221
|
+
this.messageId = messageId;
|
|
222
|
+
this.chunk = chunk;
|
|
223
|
+
this.isFinal = isFinal;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/events/DMRequestedEvent.ts
|
|
228
|
+
var DMRequestedEvent = class extends ChatEvent {
|
|
229
|
+
constructor(threadId, userId, timestamp) {
|
|
230
|
+
super("dm.requested", threadId, timestamp ?? Date.now());
|
|
231
|
+
this.userId = userId;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/events/ChatEventFactory.ts
|
|
236
|
+
function parseCard(value) {
|
|
237
|
+
if (!value || typeof value !== "object") return void 0;
|
|
238
|
+
const obj = value;
|
|
239
|
+
if (typeof obj.type !== "string") return void 0;
|
|
240
|
+
return obj;
|
|
241
|
+
}
|
|
242
|
+
function parseChatEvent(json) {
|
|
243
|
+
const type = json.type;
|
|
244
|
+
const threadId = json.threadId;
|
|
245
|
+
const timestamp = json.timestamp;
|
|
246
|
+
const data = json.data ?? {};
|
|
247
|
+
switch (type) {
|
|
248
|
+
case "message.posted":
|
|
249
|
+
return new MessagePostedEvent(
|
|
250
|
+
threadId,
|
|
251
|
+
data.messageId,
|
|
252
|
+
data.text,
|
|
253
|
+
data.author,
|
|
254
|
+
parseCard(data.card),
|
|
255
|
+
data.attachments,
|
|
256
|
+
timestamp
|
|
257
|
+
);
|
|
258
|
+
case "message.edited":
|
|
259
|
+
return new MessageEditedEvent(
|
|
260
|
+
threadId,
|
|
261
|
+
data.messageId,
|
|
262
|
+
data.newText,
|
|
263
|
+
parseCard(data.card),
|
|
264
|
+
timestamp
|
|
265
|
+
);
|
|
266
|
+
case "message.deleted":
|
|
267
|
+
return new MessageDeletedEvent(threadId, data.messageId, timestamp);
|
|
268
|
+
case "reaction.added":
|
|
269
|
+
return new ReactionAddedEvent(
|
|
270
|
+
threadId,
|
|
271
|
+
data.messageId,
|
|
272
|
+
data.emoji,
|
|
273
|
+
data.user,
|
|
274
|
+
timestamp
|
|
275
|
+
);
|
|
276
|
+
case "reaction.removed":
|
|
277
|
+
return new ReactionRemovedEvent(
|
|
278
|
+
threadId,
|
|
279
|
+
data.messageId,
|
|
280
|
+
data.emoji,
|
|
281
|
+
data.user,
|
|
282
|
+
timestamp
|
|
283
|
+
);
|
|
284
|
+
case "typing.started":
|
|
285
|
+
return new TypingStartedEvent(threadId, data.userId, timestamp);
|
|
286
|
+
case "streaming.chunk":
|
|
287
|
+
return new StreamingChunkEvent(
|
|
288
|
+
threadId,
|
|
289
|
+
data.messageId,
|
|
290
|
+
data.chunk,
|
|
291
|
+
data.isFinal,
|
|
292
|
+
timestamp
|
|
293
|
+
);
|
|
294
|
+
case "dm.requested":
|
|
295
|
+
return new DMRequestedEvent(threadId, data.userId, timestamp);
|
|
296
|
+
default:
|
|
297
|
+
return new UnknownEvent(type, threadId, data, timestamp);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
ChatEvent.fromJSON = parseChatEvent;
|
|
301
|
+
|
|
302
|
+
// src/utils/eventIdGenerator.ts
|
|
303
|
+
function generateId() {
|
|
304
|
+
return `msg-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
305
|
+
}
|
|
306
|
+
function generateConversationId() {
|
|
307
|
+
return `conv-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/client/WebChatClient.ts
|
|
311
|
+
var WebChatClient = class {
|
|
312
|
+
constructor(config) {
|
|
313
|
+
this.messages = [];
|
|
314
|
+
this.eventHandlers = {};
|
|
315
|
+
this.streamingMessages = /* @__PURE__ */ new Map();
|
|
316
|
+
this.pendingTyping = null;
|
|
317
|
+
this.subscribers = /* @__PURE__ */ new Map();
|
|
318
|
+
this.config = config;
|
|
319
|
+
this.httpClient = new HttpClient({
|
|
320
|
+
apiUrl: config.apiUrl,
|
|
321
|
+
headers: {
|
|
322
|
+
"X-User-Id": config.userId,
|
|
323
|
+
"X-User-Name": config.userName,
|
|
324
|
+
...config.headers ?? {}
|
|
325
|
+
},
|
|
326
|
+
verifyToken: config.verifyToken
|
|
327
|
+
});
|
|
328
|
+
this.broadcastClient = config.broadcastClient;
|
|
329
|
+
this.conversationId = config.conversationId ?? generateConversationId();
|
|
330
|
+
this.currentUserId = config.userId;
|
|
331
|
+
}
|
|
332
|
+
async connect() {
|
|
333
|
+
if (this.broadcastClient) {
|
|
334
|
+
this.broadcastClient.connect();
|
|
335
|
+
const threadId = this.getThreadId();
|
|
336
|
+
const threadEvents = {
|
|
337
|
+
onMessagePosted: (event) => this.handleMessagePosted(event)
|
|
338
|
+
};
|
|
339
|
+
if (this.config.features?.editMessages) {
|
|
340
|
+
threadEvents.onMessageEdited = (event) => this.handleMessageEdited(event);
|
|
341
|
+
}
|
|
342
|
+
if (this.config.features?.deleteMessages) {
|
|
343
|
+
threadEvents.onMessageDeleted = (event) => this.handleMessageDeleted(event);
|
|
344
|
+
}
|
|
345
|
+
if (this.config.features?.reactions) {
|
|
346
|
+
threadEvents.onReactionAdded = (event) => this.handleReactionAdded(event);
|
|
347
|
+
threadEvents.onReactionRemoved = (event) => this.handleReactionRemoved(event);
|
|
348
|
+
}
|
|
349
|
+
this.broadcastClient.subscribe(threadId, threadEvents);
|
|
350
|
+
this.unsubscribeUserChannel = this.broadcastClient.subscribeToUser(
|
|
351
|
+
threadId,
|
|
352
|
+
this.currentUserId,
|
|
353
|
+
{
|
|
354
|
+
onTypingStarted: (event) => this.handleTypingStarted(event),
|
|
355
|
+
onStreamingChunk: (event) => this.handleStreamingChunk(event),
|
|
356
|
+
onDMRequested: (event) => this.handleDMRequested(event)
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
disconnect() {
|
|
362
|
+
this.unsubscribeUserChannel?.();
|
|
363
|
+
this.unsubscribeUserChannel = void 0;
|
|
364
|
+
this.broadcastClient?.disconnect();
|
|
365
|
+
this.streamingMessages.clear();
|
|
366
|
+
}
|
|
367
|
+
async loadMessages(options, signal) {
|
|
368
|
+
const endpoint = this.config.endpoints?.loadMessages ?? "/api/chat/messages";
|
|
369
|
+
const threadId = this.getThreadId();
|
|
370
|
+
const params = new URLSearchParams({
|
|
371
|
+
threadId,
|
|
372
|
+
limit: String(options?.limit ?? 50)
|
|
373
|
+
});
|
|
374
|
+
if (options?.before) params.set("before", String(options.before));
|
|
375
|
+
if (options?.after) params.set("after", String(options.after));
|
|
376
|
+
const response = await this.httpClient.get(
|
|
377
|
+
`${endpoint}?${params.toString()}`,
|
|
378
|
+
signal
|
|
379
|
+
);
|
|
380
|
+
const messages = (response.messages || []).map((msg) => ({
|
|
381
|
+
id: msg.id,
|
|
382
|
+
threadId,
|
|
383
|
+
content: { text: msg.text, cards: msg.card ? [msg.card] : void 0 },
|
|
384
|
+
author: {
|
|
385
|
+
id: msg.author.id,
|
|
386
|
+
name: msg.author.name,
|
|
387
|
+
isBot: msg.author.isBot ?? false,
|
|
388
|
+
isMe: msg.author.id === this.currentUserId
|
|
389
|
+
},
|
|
390
|
+
timestamp: msg.timestamp,
|
|
391
|
+
attachments: msg.attachments?.map((a) => ({
|
|
392
|
+
id: `att-${msg.id}-${a.url}`,
|
|
393
|
+
url: a.url,
|
|
394
|
+
name: a.name,
|
|
395
|
+
type: a.type,
|
|
396
|
+
mimeType: a.mime_type,
|
|
397
|
+
size: a.size
|
|
398
|
+
})),
|
|
399
|
+
reactions: msg.reactions ?? []
|
|
400
|
+
}));
|
|
401
|
+
if (!options?.before && !options?.after && !options?.skipStateSeed) {
|
|
402
|
+
this.messages = messages;
|
|
403
|
+
this.notifySubscribers("messages:loaded", messages);
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
messages,
|
|
407
|
+
hasMore: response.hasMore ?? false,
|
|
408
|
+
nextCursor: response.nextCursor,
|
|
409
|
+
prevCursor: response.prevCursor
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
async sendMessage(text, attachments = []) {
|
|
413
|
+
const messageId = generateId();
|
|
414
|
+
const userMessage = {
|
|
415
|
+
id: messageId,
|
|
416
|
+
threadId: this.getThreadId(),
|
|
417
|
+
content: { text },
|
|
418
|
+
author: { id: this.currentUserId, name: this.config.userName, isMe: true },
|
|
419
|
+
timestamp: Date.now(),
|
|
420
|
+
attachments: attachments.length > 0 ? attachments.map((a, i) => ({
|
|
421
|
+
id: `att-${messageId}-${i}`,
|
|
422
|
+
name: a.name || "",
|
|
423
|
+
url: a.url,
|
|
424
|
+
size: a.size,
|
|
425
|
+
mimeType: a.mimeType
|
|
426
|
+
})) : void 0
|
|
427
|
+
};
|
|
428
|
+
this.messages.push(userMessage);
|
|
429
|
+
this.notifySubscribers("message:added", userMessage);
|
|
430
|
+
const endpoint = this.config.endpoints?.sendMessage ?? "/api/webhooks/web";
|
|
431
|
+
const response = await this.httpClient.sendMessage(
|
|
432
|
+
[
|
|
433
|
+
{
|
|
434
|
+
id: messageId,
|
|
435
|
+
role: "user",
|
|
436
|
+
text,
|
|
437
|
+
attachments: attachments?.map((a) => ({
|
|
438
|
+
url: a.url,
|
|
439
|
+
name: a.name,
|
|
440
|
+
mime_type: a.mimeType,
|
|
441
|
+
size: a.size
|
|
442
|
+
}))
|
|
443
|
+
}
|
|
444
|
+
],
|
|
445
|
+
endpoint,
|
|
446
|
+
this.conversationId
|
|
447
|
+
);
|
|
448
|
+
if (response.events) {
|
|
449
|
+
response.events.forEach((eventData) => {
|
|
450
|
+
const event = parseChatEvent(eventData);
|
|
451
|
+
this.dispatchEvent(event);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (response.text && !response.events?.some((e) => e.type === "message.posted")) {
|
|
455
|
+
const assistantMessage = {
|
|
456
|
+
id: response.id || generateId(),
|
|
457
|
+
threadId: this.getThreadId(),
|
|
458
|
+
content: { text: response.text },
|
|
459
|
+
author: { id: "assistant", name: "Assistant", isBot: true },
|
|
460
|
+
timestamp: Date.now(),
|
|
461
|
+
attachments: response.attachments?.map((a, i) => ({
|
|
462
|
+
id: `att-${response.id || "msg"}-${i}`,
|
|
463
|
+
name: a.name || "",
|
|
464
|
+
url: a.url || "",
|
|
465
|
+
type: a.type,
|
|
466
|
+
mimeType: a.mime_type,
|
|
467
|
+
size: a.size
|
|
468
|
+
}))
|
|
469
|
+
};
|
|
470
|
+
this.messages.push(assistantMessage);
|
|
471
|
+
this.notifySubscribers("message:added", assistantMessage);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async sendAction(messageId, actionId, value) {
|
|
475
|
+
const endpoint = this.config.endpoints?.sendMessage ?? "/api/webhooks/web";
|
|
476
|
+
const response = await this.httpClient.sendAction(
|
|
477
|
+
actionId,
|
|
478
|
+
value,
|
|
479
|
+
messageId,
|
|
480
|
+
this.conversationId,
|
|
481
|
+
endpoint
|
|
482
|
+
);
|
|
483
|
+
if (response.events) {
|
|
484
|
+
response.events.forEach((eventData) => {
|
|
485
|
+
const event = parseChatEvent(eventData);
|
|
486
|
+
this.dispatchEvent(event);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async editMessage(messageId, newText) {
|
|
491
|
+
if (!this.config.features?.editMessages) {
|
|
492
|
+
throw new Error("Edit messages not enabled. Set features.editMessages = true in config.");
|
|
493
|
+
}
|
|
494
|
+
const endpoint = this.config.endpoints?.editMessage ?? "/api/chat/messages/{id}/edit";
|
|
495
|
+
await this.httpClient.editMessage(messageId, newText, endpoint);
|
|
496
|
+
}
|
|
497
|
+
async deleteMessage(messageId) {
|
|
498
|
+
if (!this.config.features?.deleteMessages) {
|
|
499
|
+
throw new Error("Delete messages not enabled. Set features.deleteMessages = true in config.");
|
|
500
|
+
}
|
|
501
|
+
const endpoint = this.config.endpoints?.deleteMessage ?? "/api/chat/messages/{id}";
|
|
502
|
+
await this.httpClient.deleteMessage(messageId, endpoint);
|
|
503
|
+
}
|
|
504
|
+
async addReaction(messageId, emoji) {
|
|
505
|
+
if (!this.config.features?.reactions) {
|
|
506
|
+
throw new Error("Reactions not enabled. Set features.reactions = true in config.");
|
|
507
|
+
}
|
|
508
|
+
const endpoint = this.config.endpoints?.addReaction ?? "/api/chat/messages/{id}/reactions";
|
|
509
|
+
await this.httpClient.addReaction(messageId, emoji, endpoint);
|
|
510
|
+
}
|
|
511
|
+
async removeReaction(messageId, emoji) {
|
|
512
|
+
if (!this.config.features?.reactions) {
|
|
513
|
+
throw new Error("Reactions not enabled. Set features.reactions = true in config.");
|
|
514
|
+
}
|
|
515
|
+
const endpoint = this.config.endpoints?.removeReaction ?? "/api/chat/messages/{id}/reactions/{emoji}";
|
|
516
|
+
await this.httpClient.removeReaction(messageId, emoji, endpoint);
|
|
517
|
+
}
|
|
518
|
+
onMessagePosted(handler) {
|
|
519
|
+
return this.addEventListener("message.posted", handler);
|
|
520
|
+
}
|
|
521
|
+
onStreamingChunk(handler) {
|
|
522
|
+
return this.addEventListener("streaming.chunk", handler);
|
|
523
|
+
}
|
|
524
|
+
onTypingStarted(handler) {
|
|
525
|
+
return this.addEventListener("typing.started", handler);
|
|
526
|
+
}
|
|
527
|
+
getConversationId() {
|
|
528
|
+
return this.conversationId;
|
|
529
|
+
}
|
|
530
|
+
getMessages() {
|
|
531
|
+
return [...this.messages];
|
|
532
|
+
}
|
|
533
|
+
getThreadId() {
|
|
534
|
+
return `web:${this.currentUserId}:${this.conversationId}`;
|
|
535
|
+
}
|
|
536
|
+
getCurrentUserId() {
|
|
537
|
+
return this.currentUserId;
|
|
538
|
+
}
|
|
539
|
+
getFeatures() {
|
|
540
|
+
return this.config.features ?? {};
|
|
541
|
+
}
|
|
542
|
+
getEndpoints() {
|
|
543
|
+
return this.config.endpoints ?? {};
|
|
544
|
+
}
|
|
545
|
+
getHttpClient() {
|
|
546
|
+
return this.httpClient;
|
|
547
|
+
}
|
|
548
|
+
addEventListener(eventType, handler) {
|
|
549
|
+
if (!this.subscribers.has(eventType)) this.subscribers.set(eventType, []);
|
|
550
|
+
this.subscribers.get(eventType).push(handler);
|
|
551
|
+
return () => {
|
|
552
|
+
const handlers = this.subscribers.get(eventType);
|
|
553
|
+
if (handlers) {
|
|
554
|
+
const index = handlers.indexOf(handler);
|
|
555
|
+
if (index !== -1) handlers.splice(index, 1);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
handleMessagePosted(event) {
|
|
560
|
+
if (this.messages.some((m) => m.id === event.messageId)) return;
|
|
561
|
+
if (this.streamingMessages.has(event.messageId)) return;
|
|
562
|
+
const message = {
|
|
563
|
+
id: event.messageId,
|
|
564
|
+
threadId: event.threadId,
|
|
565
|
+
content: { text: event.text, cards: event.card ? [event.card] : void 0 },
|
|
566
|
+
author: event.author,
|
|
567
|
+
timestamp: event.timestamp,
|
|
568
|
+
attachments: event.attachments?.map((a) => ({
|
|
569
|
+
id: `att-${event.messageId}-${Math.random().toString(36).slice(2, 8)}`,
|
|
570
|
+
name: a.name || "",
|
|
571
|
+
url: a.url || "",
|
|
572
|
+
type: a.type,
|
|
573
|
+
mimeType: a.mimeType,
|
|
574
|
+
size: a.size
|
|
575
|
+
}))
|
|
576
|
+
};
|
|
577
|
+
this.messages.push(message);
|
|
578
|
+
this.notifySubscribers("message:added", message);
|
|
579
|
+
this.notifySubscribers("message.posted", event);
|
|
580
|
+
}
|
|
581
|
+
handleMessageEdited(event) {
|
|
582
|
+
const message = this.messages.find((m) => m.id === event.messageId);
|
|
583
|
+
if (message?.content) {
|
|
584
|
+
message.content.text = event.newText;
|
|
585
|
+
this.notifySubscribers("message:edited", {
|
|
586
|
+
messageId: event.messageId,
|
|
587
|
+
newText: event.newText
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
handleMessageDeleted(event) {
|
|
592
|
+
const index = this.messages.findIndex((m) => m.id === event.messageId);
|
|
593
|
+
if (index !== -1) {
|
|
594
|
+
this.messages.splice(index, 1);
|
|
595
|
+
this.notifySubscribers("message:deleted", { messageId: event.messageId });
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
handleReactionAdded(event) {
|
|
599
|
+
const message = this.messages.find((m) => m.id === event.messageId);
|
|
600
|
+
if (!message) return;
|
|
601
|
+
if (!message.reactions) message.reactions = [];
|
|
602
|
+
const existing = message.reactions.find((r) => r.emoji === event.emoji);
|
|
603
|
+
if (existing) {
|
|
604
|
+
existing.count++;
|
|
605
|
+
existing.users.push(event.user.id);
|
|
606
|
+
} else {
|
|
607
|
+
message.reactions.push({ emoji: event.emoji, count: 1, users: [event.user.id] });
|
|
608
|
+
}
|
|
609
|
+
this.notifySubscribers("reaction:added", { messageId: event.messageId, emoji: event.emoji });
|
|
610
|
+
}
|
|
611
|
+
handleReactionRemoved(event) {
|
|
612
|
+
const message = this.messages.find((m) => m.id === event.messageId);
|
|
613
|
+
if (!message?.reactions) return;
|
|
614
|
+
const index = message.reactions.findIndex((r) => r.emoji === event.emoji);
|
|
615
|
+
if (index !== -1) {
|
|
616
|
+
const reaction = message.reactions[index];
|
|
617
|
+
reaction.count--;
|
|
618
|
+
reaction.users = reaction.users.filter((id) => id !== event.user.id);
|
|
619
|
+
if (reaction.count === 0) message.reactions.splice(index, 1);
|
|
620
|
+
}
|
|
621
|
+
this.notifySubscribers("reaction:removed", { messageId: event.messageId, emoji: event.emoji });
|
|
622
|
+
}
|
|
623
|
+
handleStreamingChunk(event) {
|
|
624
|
+
const { messageId, chunk, isFinal } = event;
|
|
625
|
+
if (!this.streamingMessages.has(messageId)) {
|
|
626
|
+
this.streamingMessages.set(messageId, { messageId, accumulatedText: "", isComplete: false });
|
|
627
|
+
this.notifySubscribers("streaming:started", { messageId });
|
|
628
|
+
}
|
|
629
|
+
const state = this.streamingMessages.get(messageId);
|
|
630
|
+
state.accumulatedText += chunk;
|
|
631
|
+
if (isFinal) {
|
|
632
|
+
state.isComplete = true;
|
|
633
|
+
if (!this.messages.some((m) => m.id === messageId)) {
|
|
634
|
+
const message = {
|
|
635
|
+
id: messageId,
|
|
636
|
+
threadId: event.threadId,
|
|
637
|
+
content: { text: state.accumulatedText },
|
|
638
|
+
author: { id: "assistant", name: "Assistant", isBot: true },
|
|
639
|
+
timestamp: event.timestamp
|
|
640
|
+
};
|
|
641
|
+
this.messages.push(message);
|
|
642
|
+
this.notifySubscribers("message:added", message);
|
|
643
|
+
}
|
|
644
|
+
this.streamingMessages.delete(messageId);
|
|
645
|
+
this.notifySubscribers("streaming:complete", { messageId, text: state.accumulatedText });
|
|
646
|
+
} else {
|
|
647
|
+
this.notifySubscribers("streaming:chunk", {
|
|
648
|
+
messageId,
|
|
649
|
+
chunk,
|
|
650
|
+
fullText: state.accumulatedText
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
this.notifySubscribers("streaming.chunk", event);
|
|
654
|
+
}
|
|
655
|
+
handleTypingStarted(event) {
|
|
656
|
+
if (this.pendingTyping) clearTimeout(this.pendingTyping);
|
|
657
|
+
this.notifySubscribers("typing:started", { userId: event.userId });
|
|
658
|
+
this.pendingTyping = setTimeout(() => {
|
|
659
|
+
this.notifySubscribers("typing:stopped", { userId: event.userId });
|
|
660
|
+
}, 3e3);
|
|
661
|
+
this.notifySubscribers("typing.started", event);
|
|
662
|
+
}
|
|
663
|
+
handleDMRequested(event) {
|
|
664
|
+
this.notifySubscribers("dm.requested", { userId: event.userId, threadId: event.threadId });
|
|
665
|
+
}
|
|
666
|
+
notifySubscribers(eventType, data) {
|
|
667
|
+
this.subscribers.get(eventType)?.forEach((handler) => handler(data));
|
|
668
|
+
}
|
|
669
|
+
dispatchEvent(event) {
|
|
670
|
+
switch (event.type) {
|
|
671
|
+
case "message.posted":
|
|
672
|
+
this.handleMessagePosted(event);
|
|
673
|
+
break;
|
|
674
|
+
case "message.edited":
|
|
675
|
+
this.handleMessageEdited(event);
|
|
676
|
+
break;
|
|
677
|
+
case "message.deleted":
|
|
678
|
+
this.handleMessageDeleted(event);
|
|
679
|
+
break;
|
|
680
|
+
case "reaction.added":
|
|
681
|
+
this.handleReactionAdded(event);
|
|
682
|
+
break;
|
|
683
|
+
case "reaction.removed":
|
|
684
|
+
this.handleReactionRemoved(event);
|
|
685
|
+
break;
|
|
686
|
+
case "typing.started":
|
|
687
|
+
this.handleTypingStarted(event);
|
|
688
|
+
break;
|
|
689
|
+
case "streaming.chunk":
|
|
690
|
+
this.handleStreamingChunk(event);
|
|
691
|
+
break;
|
|
692
|
+
case "dm.requested":
|
|
693
|
+
this.handleDMRequested(event);
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// src/client/PusherBroadcastClient.ts
|
|
700
|
+
var PusherBroadcastClient = class {
|
|
701
|
+
constructor(pusherOrConfig, channelPrefix = "chat", channelTypes) {
|
|
702
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
703
|
+
this.channelPrefix = channelPrefix;
|
|
704
|
+
this.threadChannelType = channelTypes?.threadChannel ?? "public";
|
|
705
|
+
this.userChannelType = channelTypes?.userChannel ?? "private";
|
|
706
|
+
if ("key" in pusherOrConfig) {
|
|
707
|
+
const PusherCtor = globalThis.Pusher ?? globalThis.pusherJs;
|
|
708
|
+
if (!PusherCtor) {
|
|
709
|
+
throw new Error("pusher-js not found. Install it or pass a Pusher instance.");
|
|
710
|
+
}
|
|
711
|
+
this.pusher = new PusherCtor(pusherOrConfig.key, pusherOrConfig);
|
|
712
|
+
} else {
|
|
713
|
+
this.pusher = pusherOrConfig;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
buildChannelName(base, type) {
|
|
717
|
+
switch (type) {
|
|
718
|
+
case "private":
|
|
719
|
+
return `private-${base}`;
|
|
720
|
+
case "presence":
|
|
721
|
+
return `presence-${base}`;
|
|
722
|
+
default:
|
|
723
|
+
return base;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
connect() {
|
|
727
|
+
if (this.pusher.connection?.state !== "connected") {
|
|
728
|
+
this.pusher.connect();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
disconnect() {
|
|
732
|
+
this.subscriptions.forEach((channel) => channel.unbind_all?.() ?? channel.unsubscribe?.());
|
|
733
|
+
this.subscriptions.clear();
|
|
734
|
+
this.pusher.disconnect?.();
|
|
735
|
+
}
|
|
736
|
+
subscribe(threadId, handlers) {
|
|
737
|
+
const baseName = `${this.channelPrefix}.${threadId}`;
|
|
738
|
+
const channelName = this.buildChannelName(baseName, this.threadChannelType);
|
|
739
|
+
const channel = this.pusher.subscribe(channelName);
|
|
740
|
+
const key = `thread:${threadId}`;
|
|
741
|
+
this.subscriptions.set(key, channel);
|
|
742
|
+
const threadEvents = [
|
|
743
|
+
"message.posted",
|
|
744
|
+
"message.edited",
|
|
745
|
+
"message.deleted",
|
|
746
|
+
"reaction.added",
|
|
747
|
+
"reaction.removed"
|
|
748
|
+
];
|
|
749
|
+
threadEvents.forEach((eventType) => {
|
|
750
|
+
const eventName = `${this.channelPrefix}.${eventType}`;
|
|
751
|
+
channel.bind(eventName, (data) => {
|
|
752
|
+
const event = parseChatEvent(data);
|
|
753
|
+
this.dispatchToHandler(event, handlers);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
return () => {
|
|
757
|
+
channel.unbind_all?.();
|
|
758
|
+
this.pusher.unsubscribe(channelName);
|
|
759
|
+
this.subscriptions.delete(key);
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
subscribeToUser(threadId, userId, handlers) {
|
|
763
|
+
const baseName = `${this.channelPrefix}.${threadId}.${userId}`;
|
|
764
|
+
const channelName = this.buildChannelName(baseName, this.userChannelType);
|
|
765
|
+
const channel = this.pusher.subscribe(channelName);
|
|
766
|
+
const key = `user:${threadId}:${userId}`;
|
|
767
|
+
this.subscriptions.set(key, channel);
|
|
768
|
+
const userEvents = ["typing.started", "streaming.chunk", "dm.requested"];
|
|
769
|
+
userEvents.forEach((eventType) => {
|
|
770
|
+
const eventName = `${this.channelPrefix}.${eventType}`;
|
|
771
|
+
channel.bind(eventName, (data) => {
|
|
772
|
+
const event = parseChatEvent(data);
|
|
773
|
+
this.dispatchToHandler(event, handlers);
|
|
774
|
+
});
|
|
775
|
+
});
|
|
776
|
+
return () => {
|
|
777
|
+
channel.unbind_all?.();
|
|
778
|
+
this.pusher.unsubscribe(channelName);
|
|
779
|
+
this.subscriptions.delete(key);
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
isConnected() {
|
|
783
|
+
return this.pusher.connection?.state === "connected";
|
|
784
|
+
}
|
|
785
|
+
dispatchToHandler(event, handlers) {
|
|
786
|
+
switch (event.type) {
|
|
787
|
+
case "message.posted":
|
|
788
|
+
handlers.onMessagePosted?.(event);
|
|
789
|
+
break;
|
|
790
|
+
case "message.edited":
|
|
791
|
+
handlers.onMessageEdited?.(event);
|
|
792
|
+
break;
|
|
793
|
+
case "message.deleted":
|
|
794
|
+
handlers.onMessageDeleted?.(event);
|
|
795
|
+
break;
|
|
796
|
+
case "reaction.added":
|
|
797
|
+
handlers.onReactionAdded?.(event);
|
|
798
|
+
break;
|
|
799
|
+
case "reaction.removed":
|
|
800
|
+
handlers.onReactionRemoved?.(event);
|
|
801
|
+
break;
|
|
802
|
+
case "typing.started":
|
|
803
|
+
handlers.onTypingStarted?.(event);
|
|
804
|
+
break;
|
|
805
|
+
case "streaming.chunk":
|
|
806
|
+
handlers.onStreamingChunk?.(event);
|
|
807
|
+
break;
|
|
808
|
+
case "dm.requested":
|
|
809
|
+
handlers.onDMRequested?.(event);
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// src/client/LaravelEchoBroadcastClient.ts
|
|
816
|
+
var LaravelEchoBroadcastClient = class {
|
|
817
|
+
constructor(echo, channelPrefix = "chat", channelTypes) {
|
|
818
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
819
|
+
this.echo = echo;
|
|
820
|
+
this.channelPrefix = channelPrefix;
|
|
821
|
+
this.threadChannelType = channelTypes?.threadChannel ?? "public";
|
|
822
|
+
this.userChannelType = channelTypes?.userChannel ?? "private";
|
|
823
|
+
}
|
|
824
|
+
subscribeToEcho(name, type) {
|
|
825
|
+
switch (type) {
|
|
826
|
+
case "private":
|
|
827
|
+
return this.echo.private(name);
|
|
828
|
+
case "presence":
|
|
829
|
+
return this.echo.join(name);
|
|
830
|
+
default:
|
|
831
|
+
return this.echo.channel(name);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
connect() {
|
|
835
|
+
return Promise.resolve();
|
|
836
|
+
}
|
|
837
|
+
disconnect() {
|
|
838
|
+
this.subscriptions.forEach((channel) => {
|
|
839
|
+
channel.unsubscribe?.();
|
|
840
|
+
channel.stopListening?.();
|
|
841
|
+
});
|
|
842
|
+
this.subscriptions.clear();
|
|
843
|
+
}
|
|
844
|
+
subscribe(threadId, handlers) {
|
|
845
|
+
const channelName = `${this.channelPrefix}.${threadId}`;
|
|
846
|
+
const channel = this.subscribeToEcho(channelName, this.threadChannelType);
|
|
847
|
+
const key = `thread:${threadId}`;
|
|
848
|
+
this.subscriptions.set(key, channel);
|
|
849
|
+
const threadEvents = [
|
|
850
|
+
{ type: "message.posted", handler: "onMessagePosted" },
|
|
851
|
+
{ type: "message.edited", handler: "onMessageEdited" },
|
|
852
|
+
{ type: "message.deleted", handler: "onMessageDeleted" },
|
|
853
|
+
{ type: "reaction.added", handler: "onReactionAdded" },
|
|
854
|
+
{ type: "reaction.removed", handler: "onReactionRemoved" }
|
|
855
|
+
];
|
|
856
|
+
threadEvents.forEach(({ type, handler }) => {
|
|
857
|
+
const eventName = `.${this.channelPrefix}.${type}`;
|
|
858
|
+
channel.listen(eventName, (data) => {
|
|
859
|
+
const event = parseChatEvent(data);
|
|
860
|
+
handlers[handler]?.(event);
|
|
861
|
+
});
|
|
862
|
+
});
|
|
863
|
+
return () => {
|
|
864
|
+
channel.unsubscribe?.();
|
|
865
|
+
this.subscriptions.delete(key);
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
subscribeToUser(threadId, userId, handlers) {
|
|
869
|
+
const channelName = `${this.channelPrefix}.${threadId}.${userId}`;
|
|
870
|
+
const channel = this.subscribeToEcho(channelName, this.userChannelType);
|
|
871
|
+
const key = `user:${threadId}:${userId}`;
|
|
872
|
+
this.subscriptions.set(key, channel);
|
|
873
|
+
const userEvents = [
|
|
874
|
+
{ type: "typing.started", handler: "onTypingStarted" },
|
|
875
|
+
{ type: "streaming.chunk", handler: "onStreamingChunk" },
|
|
876
|
+
{ type: "dm.requested", handler: "onDMRequested" }
|
|
877
|
+
];
|
|
878
|
+
userEvents.forEach(({ type, handler }) => {
|
|
879
|
+
const eventName = `.${this.channelPrefix}.${type}`;
|
|
880
|
+
channel.listen(eventName, (data) => {
|
|
881
|
+
const event = parseChatEvent(data);
|
|
882
|
+
handlers[handler]?.(event);
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
return () => {
|
|
886
|
+
channel.unsubscribe?.();
|
|
887
|
+
this.subscriptions.delete(key);
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
isConnected() {
|
|
891
|
+
try {
|
|
892
|
+
const connector = this.echo.connector;
|
|
893
|
+
if (!connector) return false;
|
|
894
|
+
if (connector.pusher?.connection?.state === "connected") return true;
|
|
895
|
+
if (connector.socket?.connected) return true;
|
|
896
|
+
return false;
|
|
897
|
+
} catch {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/push/PushManager.ts
|
|
904
|
+
var PushManager = class _PushManager {
|
|
905
|
+
constructor(config) {
|
|
906
|
+
this.registration = null;
|
|
907
|
+
this.status = "unsupported";
|
|
908
|
+
this.statusListeners = /* @__PURE__ */ new Set();
|
|
909
|
+
this.messageListeners = /* @__PURE__ */ new Set();
|
|
910
|
+
this.config = config;
|
|
911
|
+
}
|
|
912
|
+
static isSupported() {
|
|
913
|
+
return typeof navigator !== "undefined" && "serviceWorker" in navigator && "PushManager" in window && "Notification" in window;
|
|
914
|
+
}
|
|
915
|
+
getStatus() {
|
|
916
|
+
return this.status;
|
|
917
|
+
}
|
|
918
|
+
onStatusChange(listener) {
|
|
919
|
+
this.statusListeners.add(listener);
|
|
920
|
+
return () => {
|
|
921
|
+
this.statusListeners.delete(listener);
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
onMessage(listener) {
|
|
925
|
+
this.messageListeners.add(listener);
|
|
926
|
+
return () => {
|
|
927
|
+
this.messageListeners.delete(listener);
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
async initialize() {
|
|
931
|
+
if (!_PushManager.isSupported()) {
|
|
932
|
+
this.setStatus("unsupported");
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (Notification.permission === "denied") {
|
|
936
|
+
this.setStatus("denied");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
try {
|
|
940
|
+
this.registration = await navigator.serviceWorker.register(
|
|
941
|
+
this.config.serviceWorkerUrl || "/chat-service-worker.js",
|
|
942
|
+
{ scope: this.config.serviceWorkerScope || "/" }
|
|
943
|
+
);
|
|
944
|
+
await navigator.serviceWorker.ready;
|
|
945
|
+
const subscription = await this.registration.pushManager.getSubscription();
|
|
946
|
+
this.setStatus(subscription ? "subscribed" : "default");
|
|
947
|
+
} catch {
|
|
948
|
+
this.setStatus("error");
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async subscribe() {
|
|
952
|
+
if (!this.registration)
|
|
953
|
+
throw new Error("PushManager not initialized. Call initialize() first.");
|
|
954
|
+
this.setStatus("subscribing");
|
|
955
|
+
try {
|
|
956
|
+
let subscription = await this.registration.pushManager.getSubscription();
|
|
957
|
+
if (!subscription) {
|
|
958
|
+
const permission = await Notification.requestPermission();
|
|
959
|
+
if (permission !== "granted") {
|
|
960
|
+
this.setStatus(permission === "denied" ? "denied" : "default");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const vapidPublicKey = await this.config.getVapidPublicKey();
|
|
964
|
+
const convertedKey = this.urlBase64ToUint8Array(vapidPublicKey);
|
|
965
|
+
subscription = await this.registration.pushManager.subscribe({
|
|
966
|
+
userVisibleOnly: true,
|
|
967
|
+
applicationServerKey: convertedKey.buffer
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
await this.config.onSubscribe(subscription.toJSON());
|
|
971
|
+
this.setStatus("subscribed");
|
|
972
|
+
} catch {
|
|
973
|
+
this.setStatus("error");
|
|
974
|
+
throw new Error("Push subscription failed");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
async unsubscribe() {
|
|
978
|
+
if (!this.registration) return;
|
|
979
|
+
try {
|
|
980
|
+
const subscription = await this.registration.pushManager.getSubscription();
|
|
981
|
+
if (subscription) {
|
|
982
|
+
await this.config.onUnsubscribe(subscription.toJSON());
|
|
983
|
+
await subscription.unsubscribe();
|
|
984
|
+
}
|
|
985
|
+
this.setStatus("default");
|
|
986
|
+
} catch {
|
|
987
|
+
this.setStatus("error");
|
|
988
|
+
throw new Error("Push unsubscription failed");
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
urlBase64ToUint8Array(base64String) {
|
|
992
|
+
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
993
|
+
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
994
|
+
const rawData = atob(base64);
|
|
995
|
+
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
|
|
996
|
+
}
|
|
997
|
+
setStatus(status) {
|
|
998
|
+
this.status = status;
|
|
999
|
+
this.statusListeners.forEach((listener) => listener(status));
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
// src/push/PushSubscriptionManager.ts
|
|
1004
|
+
function createPushSubscriptionHandlers(httpClient, userId) {
|
|
1005
|
+
return {
|
|
1006
|
+
onSubscribe: async (subscription) => {
|
|
1007
|
+
await httpClient.post("/api/push/subscriptions", {
|
|
1008
|
+
userId,
|
|
1009
|
+
subscription,
|
|
1010
|
+
userAgent: navigator.userAgent
|
|
1011
|
+
});
|
|
1012
|
+
},
|
|
1013
|
+
onUnsubscribe: async (subscription) => {
|
|
1014
|
+
await httpClient.delete(
|
|
1015
|
+
`/api/push/subscriptions?userId=${encodeURIComponent(userId)}&endpoint=${encodeURIComponent(subscription.endpoint || "")}`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1021
|
+
0 && (module.exports = {
|
|
1022
|
+
ChatEvent,
|
|
1023
|
+
DMRequestedEvent,
|
|
1024
|
+
HttpClient,
|
|
1025
|
+
LaravelEchoBroadcastClient,
|
|
1026
|
+
MessageDeletedEvent,
|
|
1027
|
+
MessageEditedEvent,
|
|
1028
|
+
MessagePostedEvent,
|
|
1029
|
+
PushManager,
|
|
1030
|
+
PusherBroadcastClient,
|
|
1031
|
+
ReactionAddedEvent,
|
|
1032
|
+
ReactionRemovedEvent,
|
|
1033
|
+
StreamingChunkEvent,
|
|
1034
|
+
TypingStartedEvent,
|
|
1035
|
+
UnknownEvent,
|
|
1036
|
+
WebChatClient,
|
|
1037
|
+
createPushSubscriptionHandlers,
|
|
1038
|
+
generateConversationId,
|
|
1039
|
+
generateId,
|
|
1040
|
+
parseChatEvent
|
|
1041
|
+
});
|
|
1042
|
+
//# sourceMappingURL=index.cjs.map
|