@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.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
28
|
// src/pocketping.ts
|
|
29
|
+
var import_crypto = require("crypto");
|
|
29
30
|
var import_ws = require("ws");
|
|
30
31
|
|
|
31
32
|
// src/storage/memory.ts
|
|
@@ -131,11 +132,27 @@ function parseUserAgent(userAgent) {
|
|
|
131
132
|
else if (ua.includes("iphone") || ua.includes("ipad")) os = "iOS";
|
|
132
133
|
return { deviceType, browser, os };
|
|
133
134
|
}
|
|
135
|
+
function parseVersion(version) {
|
|
136
|
+
return version.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
137
|
+
}
|
|
138
|
+
function compareVersions(a, b) {
|
|
139
|
+
const vA = parseVersion(a);
|
|
140
|
+
const vB = parseVersion(b);
|
|
141
|
+
const len = Math.max(vA.length, vB.length);
|
|
142
|
+
for (let i = 0; i < len; i++) {
|
|
143
|
+
const numA = vA[i] ?? 0;
|
|
144
|
+
const numB = vB[i] ?? 0;
|
|
145
|
+
if (numA < numB) return -1;
|
|
146
|
+
if (numA > numB) return 1;
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
134
150
|
var PocketPing = class {
|
|
135
151
|
constructor(config = {}) {
|
|
136
152
|
this.wss = null;
|
|
137
153
|
this.sessionSockets = /* @__PURE__ */ new Map();
|
|
138
154
|
this.operatorOnline = false;
|
|
155
|
+
this.eventHandlers = /* @__PURE__ */ new Map();
|
|
139
156
|
this.config = config;
|
|
140
157
|
this.storage = this.initStorage(config.storage);
|
|
141
158
|
this.bridges = config.bridges ?? [];
|
|
@@ -155,16 +172,32 @@ var PocketPing = class {
|
|
|
155
172
|
const path = url.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
156
173
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
157
174
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
158
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
175
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-PocketPing-Version");
|
|
176
|
+
res.setHeader("Access-Control-Expose-Headers", "X-PocketPing-Version-Status, X-PocketPing-Min-Version, X-PocketPing-Latest-Version, X-PocketPing-Version-Message");
|
|
159
177
|
if (req.method === "OPTIONS") {
|
|
160
178
|
res.statusCode = 204;
|
|
161
179
|
res.end();
|
|
162
180
|
return;
|
|
163
181
|
}
|
|
182
|
+
const widgetVersion = req.headers["x-pocketping-version"];
|
|
183
|
+
const versionCheck = this.checkWidgetVersion(widgetVersion);
|
|
184
|
+
this.setVersionHeaders(res, versionCheck);
|
|
185
|
+
if (!versionCheck.canContinue) {
|
|
186
|
+
res.statusCode = 426;
|
|
187
|
+
res.setHeader("Content-Type", "application/json");
|
|
188
|
+
res.end(JSON.stringify({
|
|
189
|
+
error: "Widget version unsupported",
|
|
190
|
+
message: versionCheck.message,
|
|
191
|
+
minVersion: versionCheck.minVersion,
|
|
192
|
+
upgradeUrl: this.config.versionUpgradeUrl || "https://docs.pocketping.io/widget/installation"
|
|
193
|
+
}));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
164
196
|
try {
|
|
165
197
|
const body = await this.parseBody(req);
|
|
166
198
|
const query = Object.fromEntries(url.searchParams);
|
|
167
199
|
let result;
|
|
200
|
+
let sessionId;
|
|
168
201
|
switch (path) {
|
|
169
202
|
case "connect": {
|
|
170
203
|
const connectReq = body;
|
|
@@ -183,7 +216,9 @@ var PocketPing = class {
|
|
|
183
216
|
...uaInfo
|
|
184
217
|
};
|
|
185
218
|
}
|
|
186
|
-
|
|
219
|
+
const connectResult = await this.handleConnect(connectReq);
|
|
220
|
+
sessionId = connectResult.sessionId;
|
|
221
|
+
result = connectResult;
|
|
187
222
|
break;
|
|
188
223
|
}
|
|
189
224
|
case "message":
|
|
@@ -201,6 +236,9 @@ var PocketPing = class {
|
|
|
201
236
|
case "read":
|
|
202
237
|
result = await this.handleRead(body);
|
|
203
238
|
break;
|
|
239
|
+
case "identify":
|
|
240
|
+
result = await this.handleIdentify(body);
|
|
241
|
+
break;
|
|
204
242
|
default:
|
|
205
243
|
if (next) {
|
|
206
244
|
next();
|
|
@@ -213,6 +251,11 @@ var PocketPing = class {
|
|
|
213
251
|
res.setHeader("Content-Type", "application/json");
|
|
214
252
|
res.statusCode = 200;
|
|
215
253
|
res.end(JSON.stringify(result));
|
|
254
|
+
if (sessionId && versionCheck.status !== "ok") {
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
this.sendVersionWarning(sessionId, versionCheck);
|
|
257
|
+
}, 500);
|
|
258
|
+
}
|
|
216
259
|
} catch (error) {
|
|
217
260
|
console.error("[PocketPing] Error:", error);
|
|
218
261
|
res.statusCode = 500;
|
|
@@ -276,7 +319,42 @@ var PocketPing = class {
|
|
|
276
319
|
data: event.data
|
|
277
320
|
});
|
|
278
321
|
break;
|
|
322
|
+
case "event":
|
|
323
|
+
const customEvent = event.data;
|
|
324
|
+
customEvent.sessionId = sessionId;
|
|
325
|
+
await this.handleCustomEvent(sessionId, customEvent);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async handleCustomEvent(sessionId, event) {
|
|
330
|
+
const session = await this.storage.getSession(sessionId);
|
|
331
|
+
if (!session) {
|
|
332
|
+
console.warn(`[PocketPing] Event received for unknown session: ${sessionId}`);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const handlers = this.eventHandlers.get(event.name);
|
|
336
|
+
if (handlers) {
|
|
337
|
+
for (const handler of handlers) {
|
|
338
|
+
try {
|
|
339
|
+
await handler(event, session);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.error(`[PocketPing] Event handler error for '${event.name}':`, err);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const wildcardHandlers = this.eventHandlers.get("*");
|
|
346
|
+
if (wildcardHandlers) {
|
|
347
|
+
for (const handler of wildcardHandlers) {
|
|
348
|
+
try {
|
|
349
|
+
await handler(event, session);
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.error(`[PocketPing] Wildcard event handler error:`, err);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
279
354
|
}
|
|
355
|
+
await this.config.onEvent?.(event, session);
|
|
356
|
+
await this.notifyBridgesEvent(event, session);
|
|
357
|
+
this.forwardToWebhook(event, session);
|
|
280
358
|
}
|
|
281
359
|
broadcastToSession(sessionId, event) {
|
|
282
360
|
const sockets = this.sessionSockets.get(sessionId);
|
|
@@ -307,20 +385,31 @@ var PocketPing = class {
|
|
|
307
385
|
lastActivity: /* @__PURE__ */ new Date(),
|
|
308
386
|
operatorOnline: this.operatorOnline,
|
|
309
387
|
aiActive: false,
|
|
310
|
-
metadata: request.metadata
|
|
388
|
+
metadata: request.metadata,
|
|
389
|
+
identity: request.identity
|
|
311
390
|
};
|
|
312
391
|
await this.storage.createSession(session);
|
|
313
392
|
await this.notifyBridges("new_session", session);
|
|
314
393
|
await this.config.onNewSession?.(session);
|
|
315
|
-
} else
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
394
|
+
} else {
|
|
395
|
+
let needsUpdate = false;
|
|
396
|
+
if (request.metadata) {
|
|
397
|
+
if (session.metadata) {
|
|
398
|
+
request.metadata.ip = session.metadata.ip ?? request.metadata.ip;
|
|
399
|
+
request.metadata.country = session.metadata.country ?? request.metadata.country;
|
|
400
|
+
request.metadata.city = session.metadata.city ?? request.metadata.city;
|
|
401
|
+
}
|
|
402
|
+
session.metadata = request.metadata;
|
|
403
|
+
needsUpdate = true;
|
|
404
|
+
}
|
|
405
|
+
if (request.identity) {
|
|
406
|
+
session.identity = request.identity;
|
|
407
|
+
needsUpdate = true;
|
|
408
|
+
}
|
|
409
|
+
if (needsUpdate) {
|
|
410
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
411
|
+
await this.storage.updateSession(session);
|
|
320
412
|
}
|
|
321
|
-
session.metadata = request.metadata;
|
|
322
|
-
session.lastActivity = /* @__PURE__ */ new Date();
|
|
323
|
-
await this.storage.updateSession(session);
|
|
324
413
|
}
|
|
325
414
|
const messages = await this.storage.getMessages(session.id);
|
|
326
415
|
return {
|
|
@@ -421,6 +510,35 @@ var PocketPing = class {
|
|
|
421
510
|
return { updated };
|
|
422
511
|
}
|
|
423
512
|
// ─────────────────────────────────────────────────────────────────
|
|
513
|
+
// User Identity
|
|
514
|
+
// ─────────────────────────────────────────────────────────────────
|
|
515
|
+
/**
|
|
516
|
+
* Handle user identification from widget
|
|
517
|
+
* Called when visitor calls PocketPing.identify()
|
|
518
|
+
*/
|
|
519
|
+
async handleIdentify(request) {
|
|
520
|
+
if (!request.identity?.id) {
|
|
521
|
+
throw new Error("identity.id is required");
|
|
522
|
+
}
|
|
523
|
+
const session = await this.storage.getSession(request.sessionId);
|
|
524
|
+
if (!session) {
|
|
525
|
+
throw new Error("Session not found");
|
|
526
|
+
}
|
|
527
|
+
session.identity = request.identity;
|
|
528
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
529
|
+
await this.storage.updateSession(session);
|
|
530
|
+
await this.notifyBridgesIdentity(session);
|
|
531
|
+
await this.config.onIdentify?.(session);
|
|
532
|
+
this.forwardIdentityToWebhook(session);
|
|
533
|
+
return { ok: true };
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Get a session by ID
|
|
537
|
+
*/
|
|
538
|
+
async getSession(sessionId) {
|
|
539
|
+
return this.storage.getSession(sessionId);
|
|
540
|
+
}
|
|
541
|
+
// ─────────────────────────────────────────────────────────────────
|
|
424
542
|
// Operator Actions (for bridges)
|
|
425
543
|
// ─────────────────────────────────────────────────────────────────
|
|
426
544
|
async sendOperatorMessage(sessionId, content) {
|
|
@@ -448,6 +566,113 @@ var PocketPing = class {
|
|
|
448
566
|
}
|
|
449
567
|
}
|
|
450
568
|
// ─────────────────────────────────────────────────────────────────
|
|
569
|
+
// Custom Events (bidirectional)
|
|
570
|
+
// ─────────────────────────────────────────────────────────────────
|
|
571
|
+
/**
|
|
572
|
+
* Subscribe to custom events from widgets
|
|
573
|
+
* @param eventName - The name of the event to listen for, or '*' for all events
|
|
574
|
+
* @param handler - Callback function when event is received
|
|
575
|
+
* @returns Unsubscribe function
|
|
576
|
+
* @example
|
|
577
|
+
* // Listen for specific event
|
|
578
|
+
* pp.onEvent('clicked_pricing', async (event, session) => {
|
|
579
|
+
* console.log(`User ${session.visitorId} clicked pricing: ${event.data?.plan}`)
|
|
580
|
+
* })
|
|
581
|
+
*
|
|
582
|
+
* // Listen for all events
|
|
583
|
+
* pp.onEvent('*', async (event, session) => {
|
|
584
|
+
* console.log(`Event: ${event.name}`, event.data)
|
|
585
|
+
* })
|
|
586
|
+
*/
|
|
587
|
+
onEvent(eventName, handler) {
|
|
588
|
+
if (!this.eventHandlers.has(eventName)) {
|
|
589
|
+
this.eventHandlers.set(eventName, /* @__PURE__ */ new Set());
|
|
590
|
+
}
|
|
591
|
+
this.eventHandlers.get(eventName).add(handler);
|
|
592
|
+
return () => {
|
|
593
|
+
this.eventHandlers.get(eventName)?.delete(handler);
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Unsubscribe from a custom event
|
|
598
|
+
* @param eventName - The name of the event
|
|
599
|
+
* @param handler - The handler to remove
|
|
600
|
+
*/
|
|
601
|
+
offEvent(eventName, handler) {
|
|
602
|
+
this.eventHandlers.get(eventName)?.delete(handler);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Send a custom event to a specific widget/session
|
|
606
|
+
* @param sessionId - The session ID to send the event to
|
|
607
|
+
* @param eventName - The name of the event
|
|
608
|
+
* @param data - Optional payload to send with the event
|
|
609
|
+
* @example
|
|
610
|
+
* // Send a promotion offer to a specific user
|
|
611
|
+
* pp.emitEvent('session-123', 'show_offer', {
|
|
612
|
+
* discount: 20,
|
|
613
|
+
* code: 'SAVE20',
|
|
614
|
+
* message: 'Special offer just for you!'
|
|
615
|
+
* })
|
|
616
|
+
*/
|
|
617
|
+
emitEvent(sessionId, eventName, data) {
|
|
618
|
+
const event = {
|
|
619
|
+
name: eventName,
|
|
620
|
+
data,
|
|
621
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
622
|
+
sessionId
|
|
623
|
+
};
|
|
624
|
+
this.broadcastToSession(sessionId, {
|
|
625
|
+
type: "event",
|
|
626
|
+
data: event
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Broadcast a custom event to all connected widgets
|
|
631
|
+
* @param eventName - The name of the event
|
|
632
|
+
* @param data - Optional payload to send with the event
|
|
633
|
+
* @example
|
|
634
|
+
* // Notify all users about maintenance
|
|
635
|
+
* pp.broadcastEvent('maintenance_warning', {
|
|
636
|
+
* message: 'Site will be down for maintenance in 5 minutes'
|
|
637
|
+
* })
|
|
638
|
+
*/
|
|
639
|
+
broadcastEvent(eventName, data) {
|
|
640
|
+
const event = {
|
|
641
|
+
name: eventName,
|
|
642
|
+
data,
|
|
643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
644
|
+
};
|
|
645
|
+
for (const sessionId of this.sessionSockets.keys()) {
|
|
646
|
+
event.sessionId = sessionId;
|
|
647
|
+
this.broadcastToSession(sessionId, {
|
|
648
|
+
type: "event",
|
|
649
|
+
data: event
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Process a custom event server-side (runs handlers, bridges, webhooks)
|
|
655
|
+
* Useful for server-side automation or triggering events programmatically
|
|
656
|
+
* @param sessionId - The session ID to associate with the event
|
|
657
|
+
* @param eventName - The name of the event
|
|
658
|
+
* @param data - Optional payload for the event
|
|
659
|
+
* @example
|
|
660
|
+
* // Trigger event from backend logic (e.g., after purchase)
|
|
661
|
+
* await pp.triggerEvent('session-123', 'purchase_completed', {
|
|
662
|
+
* orderId: 'order-456',
|
|
663
|
+
* amount: 99.99
|
|
664
|
+
* })
|
|
665
|
+
*/
|
|
666
|
+
async triggerEvent(sessionId, eventName, data) {
|
|
667
|
+
const event = {
|
|
668
|
+
name: eventName,
|
|
669
|
+
data,
|
|
670
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
671
|
+
sessionId
|
|
672
|
+
};
|
|
673
|
+
await this.handleCustomEvent(sessionId, event);
|
|
674
|
+
}
|
|
675
|
+
// ─────────────────────────────────────────────────────────────────
|
|
451
676
|
// Bridges
|
|
452
677
|
// ─────────────────────────────────────────────────────────────────
|
|
453
678
|
async notifyBridges(event, ...args) {
|
|
@@ -475,6 +700,86 @@ var PocketPing = class {
|
|
|
475
700
|
}
|
|
476
701
|
}
|
|
477
702
|
}
|
|
703
|
+
async notifyBridgesEvent(event, session) {
|
|
704
|
+
for (const bridge of this.bridges) {
|
|
705
|
+
try {
|
|
706
|
+
await bridge.onEvent?.(event, session);
|
|
707
|
+
} catch (err) {
|
|
708
|
+
console.error(`[PocketPing] Bridge ${bridge.name} event notification error:`, err);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
async notifyBridgesIdentity(session) {
|
|
713
|
+
for (const bridge of this.bridges) {
|
|
714
|
+
try {
|
|
715
|
+
await bridge.onIdentityUpdate?.(session);
|
|
716
|
+
} catch (err) {
|
|
717
|
+
console.error(`[PocketPing] Bridge ${bridge.name} identity notification error:`, err);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// ─────────────────────────────────────────────────────────────────
|
|
722
|
+
// Webhook Forwarding
|
|
723
|
+
// ─────────────────────────────────────────────────────────────────
|
|
724
|
+
/**
|
|
725
|
+
* Forward custom event to configured webhook URL (non-blocking)
|
|
726
|
+
* Used for integrations with Zapier, Make, n8n, or custom backends
|
|
727
|
+
*/
|
|
728
|
+
forwardToWebhook(event, session) {
|
|
729
|
+
if (!this.config.webhookUrl) return;
|
|
730
|
+
const payload = {
|
|
731
|
+
event,
|
|
732
|
+
session: {
|
|
733
|
+
id: session.id,
|
|
734
|
+
visitorId: session.visitorId,
|
|
735
|
+
metadata: session.metadata,
|
|
736
|
+
identity: session.identity
|
|
737
|
+
},
|
|
738
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
739
|
+
};
|
|
740
|
+
const body = JSON.stringify(payload);
|
|
741
|
+
const headers = {
|
|
742
|
+
"Content-Type": "application/json"
|
|
743
|
+
};
|
|
744
|
+
if (this.config.webhookSecret) {
|
|
745
|
+
const signature = (0, import_crypto.createHmac)("sha256", this.config.webhookSecret).update(body).digest("hex");
|
|
746
|
+
headers["X-PocketPing-Signature"] = `sha256=${signature}`;
|
|
747
|
+
}
|
|
748
|
+
const timeout = this.config.webhookTimeout ?? 5e3;
|
|
749
|
+
const controller = new AbortController();
|
|
750
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
751
|
+
fetch(this.config.webhookUrl, {
|
|
752
|
+
method: "POST",
|
|
753
|
+
headers,
|
|
754
|
+
body,
|
|
755
|
+
signal: controller.signal
|
|
756
|
+
}).then((response) => {
|
|
757
|
+
clearTimeout(timeoutId);
|
|
758
|
+
if (!response.ok) {
|
|
759
|
+
console.error(`[PocketPing] Webhook returned ${response.status}: ${response.statusText}`);
|
|
760
|
+
}
|
|
761
|
+
}).catch((err) => {
|
|
762
|
+
clearTimeout(timeoutId);
|
|
763
|
+
if (err.name === "AbortError") {
|
|
764
|
+
console.error(`[PocketPing] Webhook timed out after ${timeout}ms`);
|
|
765
|
+
} else {
|
|
766
|
+
console.error(`[PocketPing] Webhook error:`, err.message);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Forward identity update to webhook as a special event
|
|
772
|
+
*/
|
|
773
|
+
forwardIdentityToWebhook(session) {
|
|
774
|
+
if (!this.config.webhookUrl || !session.identity) return;
|
|
775
|
+
const event = {
|
|
776
|
+
name: "identify",
|
|
777
|
+
data: session.identity,
|
|
778
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
779
|
+
sessionId: session.id
|
|
780
|
+
};
|
|
781
|
+
this.forwardToWebhook(event, session);
|
|
782
|
+
}
|
|
478
783
|
addBridge(bridge) {
|
|
479
784
|
this.bridges.push(bridge);
|
|
480
785
|
bridge.init?.(this);
|
|
@@ -488,6 +793,89 @@ var PocketPing = class {
|
|
|
488
793
|
getStorage() {
|
|
489
794
|
return this.storage;
|
|
490
795
|
}
|
|
796
|
+
// ─────────────────────────────────────────────────────────────────
|
|
797
|
+
// Version Management
|
|
798
|
+
// ─────────────────────────────────────────────────────────────────
|
|
799
|
+
/**
|
|
800
|
+
* Check widget version against configured min/latest versions
|
|
801
|
+
* @param widgetVersion - Version from X-PocketPing-Version header
|
|
802
|
+
* @returns Version check result with status and headers to set
|
|
803
|
+
*/
|
|
804
|
+
checkWidgetVersion(widgetVersion) {
|
|
805
|
+
if (!widgetVersion) {
|
|
806
|
+
return {
|
|
807
|
+
status: "ok",
|
|
808
|
+
canContinue: true
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const { minWidgetVersion, latestWidgetVersion } = this.config;
|
|
812
|
+
if (!minWidgetVersion && !latestWidgetVersion) {
|
|
813
|
+
return {
|
|
814
|
+
status: "ok",
|
|
815
|
+
canContinue: true
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
let status = "ok";
|
|
819
|
+
let message;
|
|
820
|
+
let canContinue = true;
|
|
821
|
+
if (minWidgetVersion && compareVersions(widgetVersion, minWidgetVersion) < 0) {
|
|
822
|
+
status = "unsupported";
|
|
823
|
+
message = this.config.versionWarningMessage || `Widget version ${widgetVersion} is no longer supported. Minimum version: ${minWidgetVersion}`;
|
|
824
|
+
canContinue = false;
|
|
825
|
+
} else if (latestWidgetVersion && compareVersions(widgetVersion, latestWidgetVersion) < 0) {
|
|
826
|
+
const majorDiff = parseVersion(latestWidgetVersion)[0] - parseVersion(widgetVersion)[0];
|
|
827
|
+
if (majorDiff >= 1) {
|
|
828
|
+
status = "deprecated";
|
|
829
|
+
message = this.config.versionWarningMessage || `Widget version ${widgetVersion} is deprecated. Please update to ${latestWidgetVersion}`;
|
|
830
|
+
} else {
|
|
831
|
+
status = "outdated";
|
|
832
|
+
message = `A newer widget version ${latestWidgetVersion} is available`;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
status,
|
|
837
|
+
message,
|
|
838
|
+
minVersion: minWidgetVersion,
|
|
839
|
+
latestVersion: latestWidgetVersion,
|
|
840
|
+
canContinue
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Set version warning headers on HTTP response
|
|
845
|
+
*/
|
|
846
|
+
setVersionHeaders(res, versionCheck) {
|
|
847
|
+
if (versionCheck.status !== "ok") {
|
|
848
|
+
res.setHeader("X-PocketPing-Version-Status", versionCheck.status);
|
|
849
|
+
if (versionCheck.minVersion) {
|
|
850
|
+
res.setHeader("X-PocketPing-Min-Version", versionCheck.minVersion);
|
|
851
|
+
}
|
|
852
|
+
if (versionCheck.latestVersion) {
|
|
853
|
+
res.setHeader("X-PocketPing-Latest-Version", versionCheck.latestVersion);
|
|
854
|
+
}
|
|
855
|
+
if (versionCheck.message) {
|
|
856
|
+
res.setHeader("X-PocketPing-Version-Message", versionCheck.message);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Send version warning via WebSocket to a session
|
|
862
|
+
*/
|
|
863
|
+
sendVersionWarning(sessionId, versionCheck) {
|
|
864
|
+
if (versionCheck.status === "ok") return;
|
|
865
|
+
this.broadcastToSession(sessionId, {
|
|
866
|
+
type: "version_warning",
|
|
867
|
+
data: {
|
|
868
|
+
severity: versionCheck.status === "unsupported" ? "error" : versionCheck.status === "deprecated" ? "warning" : "info",
|
|
869
|
+
message: versionCheck.message,
|
|
870
|
+
currentVersion: "unknown",
|
|
871
|
+
// Will be filled by widget
|
|
872
|
+
minVersion: versionCheck.minVersion,
|
|
873
|
+
latestVersion: versionCheck.latestVersion,
|
|
874
|
+
canContinue: versionCheck.canContinue,
|
|
875
|
+
upgradeUrl: this.config.versionUpgradeUrl || "https://docs.pocketping.io/widget/installation"
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
}
|
|
491
879
|
};
|
|
492
880
|
// Annotate the CommonJS export names for ESM import in node:
|
|
493
881
|
0 && (module.exports = {
|