@pocketping/sdk-node 0.1.0 → 0.2.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 +178 -1
- package/dist/index.d.ts +178 -1
- package/dist/index.js +399 -11
- package/dist/index.mjs +399 -11
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/pocketping.ts
|
|
2
|
+
import { createHmac } from "crypto";
|
|
2
3
|
import { WebSocketServer, WebSocket } from "ws";
|
|
3
4
|
|
|
4
5
|
// src/storage/memory.ts
|
|
@@ -104,11 +105,27 @@ function parseUserAgent(userAgent) {
|
|
|
104
105
|
else if (ua.includes("iphone") || ua.includes("ipad")) os = "iOS";
|
|
105
106
|
return { deviceType, browser, os };
|
|
106
107
|
}
|
|
108
|
+
function parseVersion(version) {
|
|
109
|
+
return version.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
110
|
+
}
|
|
111
|
+
function compareVersions(a, b) {
|
|
112
|
+
const vA = parseVersion(a);
|
|
113
|
+
const vB = parseVersion(b);
|
|
114
|
+
const len = Math.max(vA.length, vB.length);
|
|
115
|
+
for (let i = 0; i < len; i++) {
|
|
116
|
+
const numA = vA[i] ?? 0;
|
|
117
|
+
const numB = vB[i] ?? 0;
|
|
118
|
+
if (numA < numB) return -1;
|
|
119
|
+
if (numA > numB) return 1;
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
107
123
|
var PocketPing = class {
|
|
108
124
|
constructor(config = {}) {
|
|
109
125
|
this.wss = null;
|
|
110
126
|
this.sessionSockets = /* @__PURE__ */ new Map();
|
|
111
127
|
this.operatorOnline = false;
|
|
128
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
112
129
|
this.config = config;
|
|
113
130
|
this.storage = this.initStorage(config.storage);
|
|
114
131
|
this.bridges = config.bridges ?? [];
|
|
@@ -128,16 +145,32 @@ var PocketPing = class {
|
|
|
128
145
|
const path = url.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
129
146
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
130
147
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
131
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
148
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-PocketPing-Version");
|
|
149
|
+
res.setHeader("Access-Control-Expose-Headers", "X-PocketPing-Version-Status, X-PocketPing-Min-Version, X-PocketPing-Latest-Version, X-PocketPing-Version-Message");
|
|
132
150
|
if (req.method === "OPTIONS") {
|
|
133
151
|
res.statusCode = 204;
|
|
134
152
|
res.end();
|
|
135
153
|
return;
|
|
136
154
|
}
|
|
155
|
+
const widgetVersion = req.headers["x-pocketping-version"];
|
|
156
|
+
const versionCheck = this.checkWidgetVersion(widgetVersion);
|
|
157
|
+
this.setVersionHeaders(res, versionCheck);
|
|
158
|
+
if (!versionCheck.canContinue) {
|
|
159
|
+
res.statusCode = 426;
|
|
160
|
+
res.setHeader("Content-Type", "application/json");
|
|
161
|
+
res.end(JSON.stringify({
|
|
162
|
+
error: "Widget version unsupported",
|
|
163
|
+
message: versionCheck.message,
|
|
164
|
+
minVersion: versionCheck.minVersion,
|
|
165
|
+
upgradeUrl: this.config.versionUpgradeUrl || "https://docs.pocketping.io/widget/installation"
|
|
166
|
+
}));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
137
169
|
try {
|
|
138
170
|
const body = await this.parseBody(req);
|
|
139
171
|
const query = Object.fromEntries(url.searchParams);
|
|
140
172
|
let result;
|
|
173
|
+
let sessionId;
|
|
141
174
|
switch (path) {
|
|
142
175
|
case "connect": {
|
|
143
176
|
const connectReq = body;
|
|
@@ -156,7 +189,9 @@ var PocketPing = class {
|
|
|
156
189
|
...uaInfo
|
|
157
190
|
};
|
|
158
191
|
}
|
|
159
|
-
|
|
192
|
+
const connectResult = await this.handleConnect(connectReq);
|
|
193
|
+
sessionId = connectResult.sessionId;
|
|
194
|
+
result = connectResult;
|
|
160
195
|
break;
|
|
161
196
|
}
|
|
162
197
|
case "message":
|
|
@@ -174,6 +209,9 @@ var PocketPing = class {
|
|
|
174
209
|
case "read":
|
|
175
210
|
result = await this.handleRead(body);
|
|
176
211
|
break;
|
|
212
|
+
case "identify":
|
|
213
|
+
result = await this.handleIdentify(body);
|
|
214
|
+
break;
|
|
177
215
|
default:
|
|
178
216
|
if (next) {
|
|
179
217
|
next();
|
|
@@ -186,6 +224,11 @@ var PocketPing = class {
|
|
|
186
224
|
res.setHeader("Content-Type", "application/json");
|
|
187
225
|
res.statusCode = 200;
|
|
188
226
|
res.end(JSON.stringify(result));
|
|
227
|
+
if (sessionId && versionCheck.status !== "ok") {
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
this.sendVersionWarning(sessionId, versionCheck);
|
|
230
|
+
}, 500);
|
|
231
|
+
}
|
|
189
232
|
} catch (error) {
|
|
190
233
|
console.error("[PocketPing] Error:", error);
|
|
191
234
|
res.statusCode = 500;
|
|
@@ -249,7 +292,42 @@ var PocketPing = class {
|
|
|
249
292
|
data: event.data
|
|
250
293
|
});
|
|
251
294
|
break;
|
|
295
|
+
case "event":
|
|
296
|
+
const customEvent = event.data;
|
|
297
|
+
customEvent.sessionId = sessionId;
|
|
298
|
+
await this.handleCustomEvent(sessionId, customEvent);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async handleCustomEvent(sessionId, event) {
|
|
303
|
+
const session = await this.storage.getSession(sessionId);
|
|
304
|
+
if (!session) {
|
|
305
|
+
console.warn(`[PocketPing] Event received for unknown session: ${sessionId}`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const handlers = this.eventHandlers.get(event.name);
|
|
309
|
+
if (handlers) {
|
|
310
|
+
for (const handler of handlers) {
|
|
311
|
+
try {
|
|
312
|
+
await handler(event, session);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.error(`[PocketPing] Event handler error for '${event.name}':`, err);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const wildcardHandlers = this.eventHandlers.get("*");
|
|
319
|
+
if (wildcardHandlers) {
|
|
320
|
+
for (const handler of wildcardHandlers) {
|
|
321
|
+
try {
|
|
322
|
+
await handler(event, session);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
console.error(`[PocketPing] Wildcard event handler error:`, err);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
252
327
|
}
|
|
328
|
+
await this.config.onEvent?.(event, session);
|
|
329
|
+
await this.notifyBridgesEvent(event, session);
|
|
330
|
+
this.forwardToWebhook(event, session);
|
|
253
331
|
}
|
|
254
332
|
broadcastToSession(sessionId, event) {
|
|
255
333
|
const sockets = this.sessionSockets.get(sessionId);
|
|
@@ -280,20 +358,31 @@ var PocketPing = class {
|
|
|
280
358
|
lastActivity: /* @__PURE__ */ new Date(),
|
|
281
359
|
operatorOnline: this.operatorOnline,
|
|
282
360
|
aiActive: false,
|
|
283
|
-
metadata: request.metadata
|
|
361
|
+
metadata: request.metadata,
|
|
362
|
+
identity: request.identity
|
|
284
363
|
};
|
|
285
364
|
await this.storage.createSession(session);
|
|
286
365
|
await this.notifyBridges("new_session", session);
|
|
287
366
|
await this.config.onNewSession?.(session);
|
|
288
|
-
} else
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
367
|
+
} else {
|
|
368
|
+
let needsUpdate = false;
|
|
369
|
+
if (request.metadata) {
|
|
370
|
+
if (session.metadata) {
|
|
371
|
+
request.metadata.ip = session.metadata.ip ?? request.metadata.ip;
|
|
372
|
+
request.metadata.country = session.metadata.country ?? request.metadata.country;
|
|
373
|
+
request.metadata.city = session.metadata.city ?? request.metadata.city;
|
|
374
|
+
}
|
|
375
|
+
session.metadata = request.metadata;
|
|
376
|
+
needsUpdate = true;
|
|
377
|
+
}
|
|
378
|
+
if (request.identity) {
|
|
379
|
+
session.identity = request.identity;
|
|
380
|
+
needsUpdate = true;
|
|
381
|
+
}
|
|
382
|
+
if (needsUpdate) {
|
|
383
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
384
|
+
await this.storage.updateSession(session);
|
|
293
385
|
}
|
|
294
|
-
session.metadata = request.metadata;
|
|
295
|
-
session.lastActivity = /* @__PURE__ */ new Date();
|
|
296
|
-
await this.storage.updateSession(session);
|
|
297
386
|
}
|
|
298
387
|
const messages = await this.storage.getMessages(session.id);
|
|
299
388
|
return {
|
|
@@ -394,6 +483,35 @@ var PocketPing = class {
|
|
|
394
483
|
return { updated };
|
|
395
484
|
}
|
|
396
485
|
// ─────────────────────────────────────────────────────────────────
|
|
486
|
+
// User Identity
|
|
487
|
+
// ─────────────────────────────────────────────────────────────────
|
|
488
|
+
/**
|
|
489
|
+
* Handle user identification from widget
|
|
490
|
+
* Called when visitor calls PocketPing.identify()
|
|
491
|
+
*/
|
|
492
|
+
async handleIdentify(request) {
|
|
493
|
+
if (!request.identity?.id) {
|
|
494
|
+
throw new Error("identity.id is required");
|
|
495
|
+
}
|
|
496
|
+
const session = await this.storage.getSession(request.sessionId);
|
|
497
|
+
if (!session) {
|
|
498
|
+
throw new Error("Session not found");
|
|
499
|
+
}
|
|
500
|
+
session.identity = request.identity;
|
|
501
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
502
|
+
await this.storage.updateSession(session);
|
|
503
|
+
await this.notifyBridgesIdentity(session);
|
|
504
|
+
await this.config.onIdentify?.(session);
|
|
505
|
+
this.forwardIdentityToWebhook(session);
|
|
506
|
+
return { ok: true };
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Get a session by ID
|
|
510
|
+
*/
|
|
511
|
+
async getSession(sessionId) {
|
|
512
|
+
return this.storage.getSession(sessionId);
|
|
513
|
+
}
|
|
514
|
+
// ─────────────────────────────────────────────────────────────────
|
|
397
515
|
// Operator Actions (for bridges)
|
|
398
516
|
// ─────────────────────────────────────────────────────────────────
|
|
399
517
|
async sendOperatorMessage(sessionId, content) {
|
|
@@ -421,6 +539,113 @@ var PocketPing = class {
|
|
|
421
539
|
}
|
|
422
540
|
}
|
|
423
541
|
// ─────────────────────────────────────────────────────────────────
|
|
542
|
+
// Custom Events (bidirectional)
|
|
543
|
+
// ─────────────────────────────────────────────────────────────────
|
|
544
|
+
/**
|
|
545
|
+
* Subscribe to custom events from widgets
|
|
546
|
+
* @param eventName - The name of the event to listen for, or '*' for all events
|
|
547
|
+
* @param handler - Callback function when event is received
|
|
548
|
+
* @returns Unsubscribe function
|
|
549
|
+
* @example
|
|
550
|
+
* // Listen for specific event
|
|
551
|
+
* pp.onEvent('clicked_pricing', async (event, session) => {
|
|
552
|
+
* console.log(`User ${session.visitorId} clicked pricing: ${event.data?.plan}`)
|
|
553
|
+
* })
|
|
554
|
+
*
|
|
555
|
+
* // Listen for all events
|
|
556
|
+
* pp.onEvent('*', async (event, session) => {
|
|
557
|
+
* console.log(`Event: ${event.name}`, event.data)
|
|
558
|
+
* })
|
|
559
|
+
*/
|
|
560
|
+
onEvent(eventName, handler) {
|
|
561
|
+
if (!this.eventHandlers.has(eventName)) {
|
|
562
|
+
this.eventHandlers.set(eventName, /* @__PURE__ */ new Set());
|
|
563
|
+
}
|
|
564
|
+
this.eventHandlers.get(eventName).add(handler);
|
|
565
|
+
return () => {
|
|
566
|
+
this.eventHandlers.get(eventName)?.delete(handler);
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Unsubscribe from a custom event
|
|
571
|
+
* @param eventName - The name of the event
|
|
572
|
+
* @param handler - The handler to remove
|
|
573
|
+
*/
|
|
574
|
+
offEvent(eventName, handler) {
|
|
575
|
+
this.eventHandlers.get(eventName)?.delete(handler);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Send a custom event to a specific widget/session
|
|
579
|
+
* @param sessionId - The session ID to send the event to
|
|
580
|
+
* @param eventName - The name of the event
|
|
581
|
+
* @param data - Optional payload to send with the event
|
|
582
|
+
* @example
|
|
583
|
+
* // Send a promotion offer to a specific user
|
|
584
|
+
* pp.emitEvent('session-123', 'show_offer', {
|
|
585
|
+
* discount: 20,
|
|
586
|
+
* code: 'SAVE20',
|
|
587
|
+
* message: 'Special offer just for you!'
|
|
588
|
+
* })
|
|
589
|
+
*/
|
|
590
|
+
emitEvent(sessionId, eventName, data) {
|
|
591
|
+
const event = {
|
|
592
|
+
name: eventName,
|
|
593
|
+
data,
|
|
594
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
595
|
+
sessionId
|
|
596
|
+
};
|
|
597
|
+
this.broadcastToSession(sessionId, {
|
|
598
|
+
type: "event",
|
|
599
|
+
data: event
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Broadcast a custom event to all connected widgets
|
|
604
|
+
* @param eventName - The name of the event
|
|
605
|
+
* @param data - Optional payload to send with the event
|
|
606
|
+
* @example
|
|
607
|
+
* // Notify all users about maintenance
|
|
608
|
+
* pp.broadcastEvent('maintenance_warning', {
|
|
609
|
+
* message: 'Site will be down for maintenance in 5 minutes'
|
|
610
|
+
* })
|
|
611
|
+
*/
|
|
612
|
+
broadcastEvent(eventName, data) {
|
|
613
|
+
const event = {
|
|
614
|
+
name: eventName,
|
|
615
|
+
data,
|
|
616
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
617
|
+
};
|
|
618
|
+
for (const sessionId of this.sessionSockets.keys()) {
|
|
619
|
+
event.sessionId = sessionId;
|
|
620
|
+
this.broadcastToSession(sessionId, {
|
|
621
|
+
type: "event",
|
|
622
|
+
data: event
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Process a custom event server-side (runs handlers, bridges, webhooks)
|
|
628
|
+
* Useful for server-side automation or triggering events programmatically
|
|
629
|
+
* @param sessionId - The session ID to associate with the event
|
|
630
|
+
* @param eventName - The name of the event
|
|
631
|
+
* @param data - Optional payload for the event
|
|
632
|
+
* @example
|
|
633
|
+
* // Trigger event from backend logic (e.g., after purchase)
|
|
634
|
+
* await pp.triggerEvent('session-123', 'purchase_completed', {
|
|
635
|
+
* orderId: 'order-456',
|
|
636
|
+
* amount: 99.99
|
|
637
|
+
* })
|
|
638
|
+
*/
|
|
639
|
+
async triggerEvent(sessionId, eventName, data) {
|
|
640
|
+
const event = {
|
|
641
|
+
name: eventName,
|
|
642
|
+
data,
|
|
643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
644
|
+
sessionId
|
|
645
|
+
};
|
|
646
|
+
await this.handleCustomEvent(sessionId, event);
|
|
647
|
+
}
|
|
648
|
+
// ─────────────────────────────────────────────────────────────────
|
|
424
649
|
// Bridges
|
|
425
650
|
// ─────────────────────────────────────────────────────────────────
|
|
426
651
|
async notifyBridges(event, ...args) {
|
|
@@ -448,6 +673,86 @@ var PocketPing = class {
|
|
|
448
673
|
}
|
|
449
674
|
}
|
|
450
675
|
}
|
|
676
|
+
async notifyBridgesEvent(event, session) {
|
|
677
|
+
for (const bridge of this.bridges) {
|
|
678
|
+
try {
|
|
679
|
+
await bridge.onEvent?.(event, session);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
console.error(`[PocketPing] Bridge ${bridge.name} event notification error:`, err);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
async notifyBridgesIdentity(session) {
|
|
686
|
+
for (const bridge of this.bridges) {
|
|
687
|
+
try {
|
|
688
|
+
await bridge.onIdentityUpdate?.(session);
|
|
689
|
+
} catch (err) {
|
|
690
|
+
console.error(`[PocketPing] Bridge ${bridge.name} identity notification error:`, err);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// ─────────────────────────────────────────────────────────────────
|
|
695
|
+
// Webhook Forwarding
|
|
696
|
+
// ─────────────────────────────────────────────────────────────────
|
|
697
|
+
/**
|
|
698
|
+
* Forward custom event to configured webhook URL (non-blocking)
|
|
699
|
+
* Used for integrations with Zapier, Make, n8n, or custom backends
|
|
700
|
+
*/
|
|
701
|
+
forwardToWebhook(event, session) {
|
|
702
|
+
if (!this.config.webhookUrl) return;
|
|
703
|
+
const payload = {
|
|
704
|
+
event,
|
|
705
|
+
session: {
|
|
706
|
+
id: session.id,
|
|
707
|
+
visitorId: session.visitorId,
|
|
708
|
+
metadata: session.metadata,
|
|
709
|
+
identity: session.identity
|
|
710
|
+
},
|
|
711
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
712
|
+
};
|
|
713
|
+
const body = JSON.stringify(payload);
|
|
714
|
+
const headers = {
|
|
715
|
+
"Content-Type": "application/json"
|
|
716
|
+
};
|
|
717
|
+
if (this.config.webhookSecret) {
|
|
718
|
+
const signature = createHmac("sha256", this.config.webhookSecret).update(body).digest("hex");
|
|
719
|
+
headers["X-PocketPing-Signature"] = `sha256=${signature}`;
|
|
720
|
+
}
|
|
721
|
+
const timeout = this.config.webhookTimeout ?? 5e3;
|
|
722
|
+
const controller = new AbortController();
|
|
723
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
724
|
+
fetch(this.config.webhookUrl, {
|
|
725
|
+
method: "POST",
|
|
726
|
+
headers,
|
|
727
|
+
body,
|
|
728
|
+
signal: controller.signal
|
|
729
|
+
}).then((response) => {
|
|
730
|
+
clearTimeout(timeoutId);
|
|
731
|
+
if (!response.ok) {
|
|
732
|
+
console.error(`[PocketPing] Webhook returned ${response.status}: ${response.statusText}`);
|
|
733
|
+
}
|
|
734
|
+
}).catch((err) => {
|
|
735
|
+
clearTimeout(timeoutId);
|
|
736
|
+
if (err.name === "AbortError") {
|
|
737
|
+
console.error(`[PocketPing] Webhook timed out after ${timeout}ms`);
|
|
738
|
+
} else {
|
|
739
|
+
console.error(`[PocketPing] Webhook error:`, err.message);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Forward identity update to webhook as a special event
|
|
745
|
+
*/
|
|
746
|
+
forwardIdentityToWebhook(session) {
|
|
747
|
+
if (!this.config.webhookUrl || !session.identity) return;
|
|
748
|
+
const event = {
|
|
749
|
+
name: "identify",
|
|
750
|
+
data: session.identity,
|
|
751
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
752
|
+
sessionId: session.id
|
|
753
|
+
};
|
|
754
|
+
this.forwardToWebhook(event, session);
|
|
755
|
+
}
|
|
451
756
|
addBridge(bridge) {
|
|
452
757
|
this.bridges.push(bridge);
|
|
453
758
|
bridge.init?.(this);
|
|
@@ -461,6 +766,89 @@ var PocketPing = class {
|
|
|
461
766
|
getStorage() {
|
|
462
767
|
return this.storage;
|
|
463
768
|
}
|
|
769
|
+
// ─────────────────────────────────────────────────────────────────
|
|
770
|
+
// Version Management
|
|
771
|
+
// ─────────────────────────────────────────────────────────────────
|
|
772
|
+
/**
|
|
773
|
+
* Check widget version against configured min/latest versions
|
|
774
|
+
* @param widgetVersion - Version from X-PocketPing-Version header
|
|
775
|
+
* @returns Version check result with status and headers to set
|
|
776
|
+
*/
|
|
777
|
+
checkWidgetVersion(widgetVersion) {
|
|
778
|
+
if (!widgetVersion) {
|
|
779
|
+
return {
|
|
780
|
+
status: "ok",
|
|
781
|
+
canContinue: true
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
const { minWidgetVersion, latestWidgetVersion } = this.config;
|
|
785
|
+
if (!minWidgetVersion && !latestWidgetVersion) {
|
|
786
|
+
return {
|
|
787
|
+
status: "ok",
|
|
788
|
+
canContinue: true
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
let status = "ok";
|
|
792
|
+
let message;
|
|
793
|
+
let canContinue = true;
|
|
794
|
+
if (minWidgetVersion && compareVersions(widgetVersion, minWidgetVersion) < 0) {
|
|
795
|
+
status = "unsupported";
|
|
796
|
+
message = this.config.versionWarningMessage || `Widget version ${widgetVersion} is no longer supported. Minimum version: ${minWidgetVersion}`;
|
|
797
|
+
canContinue = false;
|
|
798
|
+
} else if (latestWidgetVersion && compareVersions(widgetVersion, latestWidgetVersion) < 0) {
|
|
799
|
+
const majorDiff = parseVersion(latestWidgetVersion)[0] - parseVersion(widgetVersion)[0];
|
|
800
|
+
if (majorDiff >= 1) {
|
|
801
|
+
status = "deprecated";
|
|
802
|
+
message = this.config.versionWarningMessage || `Widget version ${widgetVersion} is deprecated. Please update to ${latestWidgetVersion}`;
|
|
803
|
+
} else {
|
|
804
|
+
status = "outdated";
|
|
805
|
+
message = `A newer widget version ${latestWidgetVersion} is available`;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
status,
|
|
810
|
+
message,
|
|
811
|
+
minVersion: minWidgetVersion,
|
|
812
|
+
latestVersion: latestWidgetVersion,
|
|
813
|
+
canContinue
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Set version warning headers on HTTP response
|
|
818
|
+
*/
|
|
819
|
+
setVersionHeaders(res, versionCheck) {
|
|
820
|
+
if (versionCheck.status !== "ok") {
|
|
821
|
+
res.setHeader("X-PocketPing-Version-Status", versionCheck.status);
|
|
822
|
+
if (versionCheck.minVersion) {
|
|
823
|
+
res.setHeader("X-PocketPing-Min-Version", versionCheck.minVersion);
|
|
824
|
+
}
|
|
825
|
+
if (versionCheck.latestVersion) {
|
|
826
|
+
res.setHeader("X-PocketPing-Latest-Version", versionCheck.latestVersion);
|
|
827
|
+
}
|
|
828
|
+
if (versionCheck.message) {
|
|
829
|
+
res.setHeader("X-PocketPing-Version-Message", versionCheck.message);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Send version warning via WebSocket to a session
|
|
835
|
+
*/
|
|
836
|
+
sendVersionWarning(sessionId, versionCheck) {
|
|
837
|
+
if (versionCheck.status === "ok") return;
|
|
838
|
+
this.broadcastToSession(sessionId, {
|
|
839
|
+
type: "version_warning",
|
|
840
|
+
data: {
|
|
841
|
+
severity: versionCheck.status === "unsupported" ? "error" : versionCheck.status === "deprecated" ? "warning" : "info",
|
|
842
|
+
message: versionCheck.message,
|
|
843
|
+
currentVersion: "unknown",
|
|
844
|
+
// Will be filled by widget
|
|
845
|
+
minVersion: versionCheck.minVersion,
|
|
846
|
+
latestVersion: versionCheck.latestVersion,
|
|
847
|
+
canContinue: versionCheck.canContinue,
|
|
848
|
+
upgradeUrl: this.config.versionUpgradeUrl || "https://docs.pocketping.io/widget/installation"
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
}
|
|
464
852
|
};
|
|
465
853
|
export {
|
|
466
854
|
MemoryStorage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pocketping/sdk-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Node.js SDK for implementing PocketPing protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
+
"clean": "rm -rf dist && find src -name '*.js' -o -name '*.js.map' -o -name '*.d.ts' -o -name '*.d.ts.map' | xargs rm -f 2>/dev/null || true",
|
|
13
|
+
"prebuild": "pnpm clean",
|
|
12
14
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
13
15
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
14
|
-
"test": "vitest"
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest"
|
|
15
18
|
},
|
|
16
19
|
"dependencies": {
|
|
17
20
|
"ws": "^8.16.0"
|