@pocketping/sdk-node 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/dist/index.d.mts +222 -0
- package/dist/index.d.ts +222 -0
- package/dist/index.js +496 -0
- package/dist/index.mjs +468 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
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
|
+
MemoryStorage: () => MemoryStorage,
|
|
24
|
+
PocketPing: () => PocketPing
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/pocketping.ts
|
|
29
|
+
var import_ws = require("ws");
|
|
30
|
+
|
|
31
|
+
// src/storage/memory.ts
|
|
32
|
+
var MemoryStorage = class {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
35
|
+
this.messages = /* @__PURE__ */ new Map();
|
|
36
|
+
this.messageById = /* @__PURE__ */ new Map();
|
|
37
|
+
}
|
|
38
|
+
async createSession(session) {
|
|
39
|
+
this.sessions.set(session.id, session);
|
|
40
|
+
this.messages.set(session.id, []);
|
|
41
|
+
}
|
|
42
|
+
async getSession(sessionId) {
|
|
43
|
+
return this.sessions.get(sessionId) ?? null;
|
|
44
|
+
}
|
|
45
|
+
async getSessionByVisitorId(visitorId) {
|
|
46
|
+
const visitorSessions = Array.from(this.sessions.values()).filter(
|
|
47
|
+
(s) => s.visitorId === visitorId
|
|
48
|
+
);
|
|
49
|
+
if (visitorSessions.length === 0) return null;
|
|
50
|
+
return visitorSessions.reduce(
|
|
51
|
+
(latest, s) => s.lastActivity > latest.lastActivity ? s : latest
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async updateSession(session) {
|
|
55
|
+
this.sessions.set(session.id, session);
|
|
56
|
+
}
|
|
57
|
+
async deleteSession(sessionId) {
|
|
58
|
+
this.sessions.delete(sessionId);
|
|
59
|
+
this.messages.delete(sessionId);
|
|
60
|
+
}
|
|
61
|
+
async saveMessage(message) {
|
|
62
|
+
const sessionMessages = this.messages.get(message.sessionId) ?? [];
|
|
63
|
+
sessionMessages.push(message);
|
|
64
|
+
this.messages.set(message.sessionId, sessionMessages);
|
|
65
|
+
this.messageById.set(message.id, message);
|
|
66
|
+
}
|
|
67
|
+
async getMessages(sessionId, after, limit = 50) {
|
|
68
|
+
const sessionMessages = this.messages.get(sessionId) ?? [];
|
|
69
|
+
let startIndex = 0;
|
|
70
|
+
if (after) {
|
|
71
|
+
const afterIndex = sessionMessages.findIndex((m) => m.id === after);
|
|
72
|
+
if (afterIndex !== -1) {
|
|
73
|
+
startIndex = afterIndex + 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return sessionMessages.slice(startIndex, startIndex + limit);
|
|
77
|
+
}
|
|
78
|
+
async getMessage(messageId) {
|
|
79
|
+
return this.messageById.get(messageId) ?? null;
|
|
80
|
+
}
|
|
81
|
+
async cleanupOldSessions(olderThan) {
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const [id, session] of this.sessions) {
|
|
84
|
+
if (session.lastActivity < olderThan) {
|
|
85
|
+
this.sessions.delete(id);
|
|
86
|
+
this.messages.delete(id);
|
|
87
|
+
count++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return count;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/pocketping.ts
|
|
95
|
+
function getClientIp(req) {
|
|
96
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
97
|
+
if (forwarded) {
|
|
98
|
+
const ip = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(",")[0];
|
|
99
|
+
return ip?.trim() ?? "unknown";
|
|
100
|
+
}
|
|
101
|
+
const realIp = req.headers["x-real-ip"];
|
|
102
|
+
if (realIp) {
|
|
103
|
+
return Array.isArray(realIp) ? realIp[0] ?? "unknown" : realIp;
|
|
104
|
+
}
|
|
105
|
+
return req.socket?.remoteAddress ?? "unknown";
|
|
106
|
+
}
|
|
107
|
+
function parseUserAgent(userAgent) {
|
|
108
|
+
if (!userAgent) {
|
|
109
|
+
return { deviceType: void 0, browser: void 0, os: void 0 };
|
|
110
|
+
}
|
|
111
|
+
const ua = userAgent.toLowerCase();
|
|
112
|
+
let deviceType;
|
|
113
|
+
if (["mobile", "android", "iphone", "ipod"].some((x) => ua.includes(x))) {
|
|
114
|
+
deviceType = "mobile";
|
|
115
|
+
} else if (["ipad", "tablet"].some((x) => ua.includes(x))) {
|
|
116
|
+
deviceType = "tablet";
|
|
117
|
+
} else {
|
|
118
|
+
deviceType = "desktop";
|
|
119
|
+
}
|
|
120
|
+
let browser;
|
|
121
|
+
if (ua.includes("firefox")) browser = "Firefox";
|
|
122
|
+
else if (ua.includes("edg")) browser = "Edge";
|
|
123
|
+
else if (ua.includes("chrome")) browser = "Chrome";
|
|
124
|
+
else if (ua.includes("safari")) browser = "Safari";
|
|
125
|
+
else if (ua.includes("opera") || ua.includes("opr")) browser = "Opera";
|
|
126
|
+
let os;
|
|
127
|
+
if (ua.includes("windows")) os = "Windows";
|
|
128
|
+
else if (ua.includes("mac os") || ua.includes("macos")) os = "macOS";
|
|
129
|
+
else if (ua.includes("linux")) os = "Linux";
|
|
130
|
+
else if (ua.includes("android")) os = "Android";
|
|
131
|
+
else if (ua.includes("iphone") || ua.includes("ipad")) os = "iOS";
|
|
132
|
+
return { deviceType, browser, os };
|
|
133
|
+
}
|
|
134
|
+
var PocketPing = class {
|
|
135
|
+
constructor(config = {}) {
|
|
136
|
+
this.wss = null;
|
|
137
|
+
this.sessionSockets = /* @__PURE__ */ new Map();
|
|
138
|
+
this.operatorOnline = false;
|
|
139
|
+
this.config = config;
|
|
140
|
+
this.storage = this.initStorage(config.storage);
|
|
141
|
+
this.bridges = config.bridges ?? [];
|
|
142
|
+
}
|
|
143
|
+
initStorage(storage) {
|
|
144
|
+
if (!storage || storage === "memory") {
|
|
145
|
+
return new MemoryStorage();
|
|
146
|
+
}
|
|
147
|
+
return storage;
|
|
148
|
+
}
|
|
149
|
+
// ─────────────────────────────────────────────────────────────────
|
|
150
|
+
// Express/Connect Middleware
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────
|
|
152
|
+
middleware() {
|
|
153
|
+
return async (req, res, next) => {
|
|
154
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
155
|
+
const path = url.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
156
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
157
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
158
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
159
|
+
if (req.method === "OPTIONS") {
|
|
160
|
+
res.statusCode = 204;
|
|
161
|
+
res.end();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const body = await this.parseBody(req);
|
|
166
|
+
const query = Object.fromEntries(url.searchParams);
|
|
167
|
+
let result;
|
|
168
|
+
switch (path) {
|
|
169
|
+
case "connect": {
|
|
170
|
+
const connectReq = body;
|
|
171
|
+
const clientIp = getClientIp(req);
|
|
172
|
+
const userAgent = req.headers["user-agent"];
|
|
173
|
+
const uaInfo = parseUserAgent(connectReq.metadata?.userAgent ?? userAgent);
|
|
174
|
+
if (connectReq.metadata) {
|
|
175
|
+
connectReq.metadata.ip = clientIp;
|
|
176
|
+
connectReq.metadata.deviceType = connectReq.metadata.deviceType ?? uaInfo.deviceType;
|
|
177
|
+
connectReq.metadata.browser = connectReq.metadata.browser ?? uaInfo.browser;
|
|
178
|
+
connectReq.metadata.os = connectReq.metadata.os ?? uaInfo.os;
|
|
179
|
+
} else {
|
|
180
|
+
connectReq.metadata = {
|
|
181
|
+
ip: clientIp,
|
|
182
|
+
userAgent,
|
|
183
|
+
...uaInfo
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
result = await this.handleConnect(connectReq);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "message":
|
|
190
|
+
result = await this.handleMessage(body);
|
|
191
|
+
break;
|
|
192
|
+
case "messages":
|
|
193
|
+
result = await this.handleGetMessages(query);
|
|
194
|
+
break;
|
|
195
|
+
case "typing":
|
|
196
|
+
result = await this.handleTyping(body);
|
|
197
|
+
break;
|
|
198
|
+
case "presence":
|
|
199
|
+
result = await this.handlePresence();
|
|
200
|
+
break;
|
|
201
|
+
case "read":
|
|
202
|
+
result = await this.handleRead(body);
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
if (next) {
|
|
206
|
+
next();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
res.statusCode = 404;
|
|
210
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
res.setHeader("Content-Type", "application/json");
|
|
214
|
+
res.statusCode = 200;
|
|
215
|
+
res.end(JSON.stringify(result));
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error("[PocketPing] Error:", error);
|
|
218
|
+
res.statusCode = 500;
|
|
219
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async parseBody(req) {
|
|
224
|
+
if (req.body) return req.body;
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
let data = "";
|
|
227
|
+
req.on("data", (chunk) => data += chunk);
|
|
228
|
+
req.on("end", () => {
|
|
229
|
+
try {
|
|
230
|
+
resolve(data ? JSON.parse(data) : {});
|
|
231
|
+
} catch {
|
|
232
|
+
reject(new Error("Invalid JSON"));
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
req.on("error", reject);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// ─────────────────────────────────────────────────────────────────
|
|
239
|
+
// WebSocket
|
|
240
|
+
// ─────────────────────────────────────────────────────────────────
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
242
|
+
attachWebSocket(server) {
|
|
243
|
+
this.wss = new import_ws.WebSocketServer({
|
|
244
|
+
server,
|
|
245
|
+
path: "/pocketping/stream"
|
|
246
|
+
});
|
|
247
|
+
this.wss.on("connection", (ws, req) => {
|
|
248
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
249
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
250
|
+
if (!sessionId) {
|
|
251
|
+
ws.close(4e3, "sessionId required");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!this.sessionSockets.has(sessionId)) {
|
|
255
|
+
this.sessionSockets.set(sessionId, /* @__PURE__ */ new Set());
|
|
256
|
+
}
|
|
257
|
+
this.sessionSockets.get(sessionId).add(ws);
|
|
258
|
+
ws.on("close", () => {
|
|
259
|
+
this.sessionSockets.get(sessionId)?.delete(ws);
|
|
260
|
+
});
|
|
261
|
+
ws.on("message", async (data) => {
|
|
262
|
+
try {
|
|
263
|
+
const event = JSON.parse(data.toString());
|
|
264
|
+
await this.handleWebSocketMessage(sessionId, event);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.error("[PocketPing] WS message error:", err);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
async handleWebSocketMessage(sessionId, event) {
|
|
272
|
+
switch (event.type) {
|
|
273
|
+
case "typing":
|
|
274
|
+
this.broadcastToSession(sessionId, {
|
|
275
|
+
type: "typing",
|
|
276
|
+
data: event.data
|
|
277
|
+
});
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
broadcastToSession(sessionId, event) {
|
|
282
|
+
const sockets = this.sessionSockets.get(sessionId);
|
|
283
|
+
if (!sockets) return;
|
|
284
|
+
const message = JSON.stringify(event);
|
|
285
|
+
for (const ws of sockets) {
|
|
286
|
+
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
287
|
+
ws.send(message);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// ─────────────────────────────────────────────────────────────────
|
|
292
|
+
// Protocol Handlers
|
|
293
|
+
// ─────────────────────────────────────────────────────────────────
|
|
294
|
+
async handleConnect(request) {
|
|
295
|
+
let session = null;
|
|
296
|
+
if (request.sessionId) {
|
|
297
|
+
session = await this.storage.getSession(request.sessionId);
|
|
298
|
+
}
|
|
299
|
+
if (!session && this.storage.getSessionByVisitorId) {
|
|
300
|
+
session = await this.storage.getSessionByVisitorId(request.visitorId);
|
|
301
|
+
}
|
|
302
|
+
if (!session) {
|
|
303
|
+
session = {
|
|
304
|
+
id: this.generateId(),
|
|
305
|
+
visitorId: request.visitorId,
|
|
306
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
307
|
+
lastActivity: /* @__PURE__ */ new Date(),
|
|
308
|
+
operatorOnline: this.operatorOnline,
|
|
309
|
+
aiActive: false,
|
|
310
|
+
metadata: request.metadata
|
|
311
|
+
};
|
|
312
|
+
await this.storage.createSession(session);
|
|
313
|
+
await this.notifyBridges("new_session", session);
|
|
314
|
+
await this.config.onNewSession?.(session);
|
|
315
|
+
} else if (request.metadata) {
|
|
316
|
+
if (session.metadata) {
|
|
317
|
+
request.metadata.ip = session.metadata.ip ?? request.metadata.ip;
|
|
318
|
+
request.metadata.country = session.metadata.country ?? request.metadata.country;
|
|
319
|
+
request.metadata.city = session.metadata.city ?? request.metadata.city;
|
|
320
|
+
}
|
|
321
|
+
session.metadata = request.metadata;
|
|
322
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
323
|
+
await this.storage.updateSession(session);
|
|
324
|
+
}
|
|
325
|
+
const messages = await this.storage.getMessages(session.id);
|
|
326
|
+
return {
|
|
327
|
+
sessionId: session.id,
|
|
328
|
+
visitorId: session.visitorId,
|
|
329
|
+
operatorOnline: this.operatorOnline,
|
|
330
|
+
welcomeMessage: this.config.welcomeMessage,
|
|
331
|
+
messages
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async handleMessage(request) {
|
|
335
|
+
const session = await this.storage.getSession(request.sessionId);
|
|
336
|
+
if (!session) {
|
|
337
|
+
throw new Error("Session not found");
|
|
338
|
+
}
|
|
339
|
+
const message = {
|
|
340
|
+
id: this.generateId(),
|
|
341
|
+
sessionId: request.sessionId,
|
|
342
|
+
content: request.content,
|
|
343
|
+
sender: request.sender,
|
|
344
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
345
|
+
replyTo: request.replyTo
|
|
346
|
+
};
|
|
347
|
+
await this.storage.saveMessage(message);
|
|
348
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
349
|
+
await this.storage.updateSession(session);
|
|
350
|
+
if (request.sender === "visitor") {
|
|
351
|
+
await this.notifyBridges("message", message, session);
|
|
352
|
+
}
|
|
353
|
+
this.broadcastToSession(request.sessionId, {
|
|
354
|
+
type: "message",
|
|
355
|
+
data: message
|
|
356
|
+
});
|
|
357
|
+
await this.config.onMessage?.(message, session);
|
|
358
|
+
return {
|
|
359
|
+
messageId: message.id,
|
|
360
|
+
timestamp: message.timestamp.toISOString()
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
async handleGetMessages(request) {
|
|
364
|
+
const limit = Math.min(request.limit ?? 50, 100);
|
|
365
|
+
const messages = await this.storage.getMessages(request.sessionId, request.after, limit + 1);
|
|
366
|
+
return {
|
|
367
|
+
messages: messages.slice(0, limit),
|
|
368
|
+
hasMore: messages.length > limit
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async handleTyping(request) {
|
|
372
|
+
this.broadcastToSession(request.sessionId, {
|
|
373
|
+
type: "typing",
|
|
374
|
+
data: {
|
|
375
|
+
sessionId: request.sessionId,
|
|
376
|
+
sender: request.sender,
|
|
377
|
+
isTyping: request.isTyping ?? true
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return { ok: true };
|
|
381
|
+
}
|
|
382
|
+
async handlePresence() {
|
|
383
|
+
return {
|
|
384
|
+
online: this.operatorOnline,
|
|
385
|
+
aiEnabled: !!this.config.ai,
|
|
386
|
+
aiActiveAfter: this.config.aiTakeoverDelay ?? 300
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async handleRead(request) {
|
|
390
|
+
const session = await this.storage.getSession(request.sessionId);
|
|
391
|
+
if (!session) {
|
|
392
|
+
throw new Error("Session not found");
|
|
393
|
+
}
|
|
394
|
+
const status = request.status ?? "read";
|
|
395
|
+
const now = /* @__PURE__ */ new Date();
|
|
396
|
+
let updated = 0;
|
|
397
|
+
const messages = await this.storage.getMessages(request.sessionId);
|
|
398
|
+
for (const msg of messages) {
|
|
399
|
+
if (request.messageIds.includes(msg.id)) {
|
|
400
|
+
msg.status = status;
|
|
401
|
+
if (status === "delivered") {
|
|
402
|
+
msg.deliveredAt = now;
|
|
403
|
+
} else if (status === "read") {
|
|
404
|
+
msg.deliveredAt = msg.deliveredAt ?? now;
|
|
405
|
+
msg.readAt = now;
|
|
406
|
+
}
|
|
407
|
+
await this.storage.saveMessage(msg);
|
|
408
|
+
updated++;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
this.broadcastToSession(request.sessionId, {
|
|
412
|
+
type: "read",
|
|
413
|
+
data: {
|
|
414
|
+
messageIds: request.messageIds,
|
|
415
|
+
status,
|
|
416
|
+
deliveredAt: status === "delivered" ? now.toISOString() : void 0,
|
|
417
|
+
readAt: status === "read" ? now.toISOString() : void 0
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
await this.notifyBridgesRead(request.sessionId, request.messageIds, status);
|
|
421
|
+
return { updated };
|
|
422
|
+
}
|
|
423
|
+
// ─────────────────────────────────────────────────────────────────
|
|
424
|
+
// Operator Actions (for bridges)
|
|
425
|
+
// ─────────────────────────────────────────────────────────────────
|
|
426
|
+
async sendOperatorMessage(sessionId, content) {
|
|
427
|
+
const response = await this.handleMessage({
|
|
428
|
+
sessionId,
|
|
429
|
+
content,
|
|
430
|
+
sender: "operator"
|
|
431
|
+
});
|
|
432
|
+
const message = {
|
|
433
|
+
id: response.messageId,
|
|
434
|
+
sessionId,
|
|
435
|
+
content,
|
|
436
|
+
sender: "operator",
|
|
437
|
+
timestamp: new Date(response.timestamp)
|
|
438
|
+
};
|
|
439
|
+
return message;
|
|
440
|
+
}
|
|
441
|
+
setOperatorOnline(online) {
|
|
442
|
+
this.operatorOnline = online;
|
|
443
|
+
for (const sessionId of this.sessionSockets.keys()) {
|
|
444
|
+
this.broadcastToSession(sessionId, {
|
|
445
|
+
type: "presence",
|
|
446
|
+
data: { online }
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// ─────────────────────────────────────────────────────────────────
|
|
451
|
+
// Bridges
|
|
452
|
+
// ─────────────────────────────────────────────────────────────────
|
|
453
|
+
async notifyBridges(event, ...args) {
|
|
454
|
+
for (const bridge of this.bridges) {
|
|
455
|
+
try {
|
|
456
|
+
switch (event) {
|
|
457
|
+
case "new_session":
|
|
458
|
+
await bridge.onNewSession?.(args[0]);
|
|
459
|
+
break;
|
|
460
|
+
case "message":
|
|
461
|
+
await bridge.onMessage?.(args[0], args[1]);
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
} catch (err) {
|
|
465
|
+
console.error(`[PocketPing] Bridge ${bridge.name} error:`, err);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
async notifyBridgesRead(sessionId, messageIds, status) {
|
|
470
|
+
for (const bridge of this.bridges) {
|
|
471
|
+
try {
|
|
472
|
+
await bridge.onMessageRead?.(sessionId, messageIds, status);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
console.error(`[PocketPing] Bridge ${bridge.name} read notification error:`, err);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
addBridge(bridge) {
|
|
479
|
+
this.bridges.push(bridge);
|
|
480
|
+
bridge.init?.(this);
|
|
481
|
+
}
|
|
482
|
+
// ─────────────────────────────────────────────────────────────────
|
|
483
|
+
// Utilities
|
|
484
|
+
// ─────────────────────────────────────────────────────────────────
|
|
485
|
+
generateId() {
|
|
486
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
487
|
+
}
|
|
488
|
+
getStorage() {
|
|
489
|
+
return this.storage;
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
493
|
+
0 && (module.exports = {
|
|
494
|
+
MemoryStorage,
|
|
495
|
+
PocketPing
|
|
496
|
+
});
|