@pocketping/widget 0.1.0 → 0.3.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/LICENSE +21 -0
- package/README.md +89 -2
- package/dist/index.d.mts +199 -4
- package/dist/index.d.ts +199 -4
- package/dist/index.js +292 -14
- package/dist/index.mjs +284 -13
- package/dist/pocketping.min.global.js +329 -0
- package/package.json +12 -8
- package/dist/index.global.js +0 -1475
package/dist/index.js
CHANGED
|
@@ -23,10 +23,17 @@ __export(index_exports, {
|
|
|
23
23
|
close: () => close,
|
|
24
24
|
default: () => index_default,
|
|
25
25
|
destroy: () => destroy,
|
|
26
|
+
getIdentity: () => getIdentity,
|
|
27
|
+
identify: () => identify,
|
|
26
28
|
init: () => init,
|
|
29
|
+
offEvent: () => offEvent,
|
|
30
|
+
on: () => on,
|
|
31
|
+
onEvent: () => onEvent,
|
|
27
32
|
open: () => open,
|
|
33
|
+
reset: () => reset,
|
|
28
34
|
sendMessage: () => sendMessage,
|
|
29
|
-
toggle: () => toggle
|
|
35
|
+
toggle: () => toggle,
|
|
36
|
+
trigger: () => trigger
|
|
30
37
|
});
|
|
31
38
|
module.exports = __toCommonJS(index_exports);
|
|
32
39
|
var import_preact2 = require("preact");
|
|
@@ -629,6 +636,9 @@ function StatusIcon({ status }) {
|
|
|
629
636
|
return null;
|
|
630
637
|
}
|
|
631
638
|
|
|
639
|
+
// src/version.ts
|
|
640
|
+
var VERSION = "0.3.0";
|
|
641
|
+
|
|
632
642
|
// src/client.ts
|
|
633
643
|
var PocketPingClient = class {
|
|
634
644
|
constructor(config) {
|
|
@@ -636,6 +646,7 @@ var PocketPingClient = class {
|
|
|
636
646
|
this.ws = null;
|
|
637
647
|
this.isOpen = false;
|
|
638
648
|
this.listeners = /* @__PURE__ */ new Map();
|
|
649
|
+
this.customEventHandlers = /* @__PURE__ */ new Map();
|
|
639
650
|
this.reconnectAttempts = 0;
|
|
640
651
|
this.maxReconnectAttempts = 5;
|
|
641
652
|
this.reconnectTimeout = null;
|
|
@@ -647,6 +658,7 @@ var PocketPingClient = class {
|
|
|
647
658
|
async connect() {
|
|
648
659
|
const visitorId = this.getOrCreateVisitorId();
|
|
649
660
|
const storedSessionId = this.getStoredSessionId();
|
|
661
|
+
const storedIdentity = this.getStoredIdentity();
|
|
650
662
|
const response = await this.fetch("/connect", {
|
|
651
663
|
method: "POST",
|
|
652
664
|
body: JSON.stringify({
|
|
@@ -660,14 +672,17 @@ var PocketPingClient = class {
|
|
|
660
672
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
661
673
|
language: navigator.language,
|
|
662
674
|
screenResolution: `${window.screen.width}x${window.screen.height}`
|
|
663
|
-
}
|
|
675
|
+
},
|
|
676
|
+
// Include stored identity if available
|
|
677
|
+
identity: storedIdentity || void 0
|
|
664
678
|
})
|
|
665
679
|
});
|
|
666
680
|
this.session = {
|
|
667
681
|
sessionId: response.sessionId,
|
|
668
682
|
visitorId: response.visitorId,
|
|
669
683
|
operatorOnline: response.operatorOnline ?? false,
|
|
670
|
-
messages: response.messages ?? []
|
|
684
|
+
messages: response.messages ?? [],
|
|
685
|
+
identity: response.identity || storedIdentity || void 0
|
|
671
686
|
};
|
|
672
687
|
this.storeSessionId(response.sessionId);
|
|
673
688
|
this.connectWebSocket();
|
|
@@ -790,6 +805,69 @@ var PocketPingClient = class {
|
|
|
790
805
|
return this.fetch("/presence", { method: "GET" });
|
|
791
806
|
}
|
|
792
807
|
// ─────────────────────────────────────────────────────────────────
|
|
808
|
+
// User Identity
|
|
809
|
+
// ─────────────────────────────────────────────────────────────────
|
|
810
|
+
/**
|
|
811
|
+
* Identify the current user with metadata
|
|
812
|
+
* Call after user logs in or when user data becomes available
|
|
813
|
+
* @param identity - User identity data with required id field
|
|
814
|
+
* @example
|
|
815
|
+
* PocketPing.identify({
|
|
816
|
+
* id: 'user_123',
|
|
817
|
+
* email: 'john@example.com',
|
|
818
|
+
* name: 'John Doe',
|
|
819
|
+
* plan: 'pro',
|
|
820
|
+
* company: 'Acme Inc'
|
|
821
|
+
* })
|
|
822
|
+
*/
|
|
823
|
+
async identify(identity) {
|
|
824
|
+
if (!identity?.id) {
|
|
825
|
+
throw new Error("[PocketPing] identity.id is required");
|
|
826
|
+
}
|
|
827
|
+
this.storeIdentity(identity);
|
|
828
|
+
if (this.session) {
|
|
829
|
+
try {
|
|
830
|
+
await this.fetch("/identify", {
|
|
831
|
+
method: "POST",
|
|
832
|
+
body: JSON.stringify({
|
|
833
|
+
sessionId: this.session.sessionId,
|
|
834
|
+
identity
|
|
835
|
+
})
|
|
836
|
+
});
|
|
837
|
+
this.session.identity = identity;
|
|
838
|
+
this.emit("identify", identity);
|
|
839
|
+
} catch (err) {
|
|
840
|
+
console.error("[PocketPing] Failed to identify:", err);
|
|
841
|
+
throw err;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Reset the user identity and optionally start a new session
|
|
847
|
+
* Call on user logout to clear user data
|
|
848
|
+
* @param options - Optional settings: { newSession: boolean }
|
|
849
|
+
*/
|
|
850
|
+
async reset(options) {
|
|
851
|
+
this.clearIdentity();
|
|
852
|
+
if (this.session) {
|
|
853
|
+
this.session.identity = void 0;
|
|
854
|
+
}
|
|
855
|
+
if (options?.newSession) {
|
|
856
|
+
localStorage.removeItem("pocketping_session_id");
|
|
857
|
+
localStorage.removeItem("pocketping_visitor_id");
|
|
858
|
+
this.disconnect();
|
|
859
|
+
await this.connect();
|
|
860
|
+
}
|
|
861
|
+
this.emit("reset", null);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get the current user identity
|
|
865
|
+
* @returns UserIdentity or null if not identified
|
|
866
|
+
*/
|
|
867
|
+
getIdentity() {
|
|
868
|
+
return this.session?.identity || this.getStoredIdentity();
|
|
869
|
+
}
|
|
870
|
+
// ─────────────────────────────────────────────────────────────────
|
|
793
871
|
// State
|
|
794
872
|
// ─────────────────────────────────────────────────────────────────
|
|
795
873
|
getSession() {
|
|
@@ -832,6 +910,67 @@ var PocketPingClient = class {
|
|
|
832
910
|
this.listeners.get(event)?.forEach((listener) => listener(data));
|
|
833
911
|
}
|
|
834
912
|
// ─────────────────────────────────────────────────────────────────
|
|
913
|
+
// Custom Events (bidirectional)
|
|
914
|
+
// ─────────────────────────────────────────────────────────────────
|
|
915
|
+
/**
|
|
916
|
+
* Trigger a custom event to the backend
|
|
917
|
+
* @param eventName - The name of the event (e.g., 'clicked_pricing', 'viewed_demo')
|
|
918
|
+
* @param data - Optional payload to send with the event
|
|
919
|
+
* @example
|
|
920
|
+
* PocketPing.trigger('clicked_cta', { button: 'signup', page: '/pricing' })
|
|
921
|
+
*/
|
|
922
|
+
trigger(eventName, data) {
|
|
923
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
924
|
+
console.warn("[PocketPing] Cannot trigger event: WebSocket not connected");
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const event = {
|
|
928
|
+
name: eventName,
|
|
929
|
+
data,
|
|
930
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
931
|
+
};
|
|
932
|
+
this.ws.send(JSON.stringify({
|
|
933
|
+
type: "event",
|
|
934
|
+
data: event
|
|
935
|
+
}));
|
|
936
|
+
this.emit(`event:${eventName}`, event);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Subscribe to custom events from the backend
|
|
940
|
+
* @param eventName - The name of the event to listen for (e.g., 'show_offer', 'open_chat')
|
|
941
|
+
* @param handler - Callback function when event is received
|
|
942
|
+
* @returns Unsubscribe function
|
|
943
|
+
* @example
|
|
944
|
+
* const unsubscribe = PocketPing.onEvent('show_offer', (data) => {
|
|
945
|
+
* showPopup(data.message)
|
|
946
|
+
* })
|
|
947
|
+
*/
|
|
948
|
+
onEvent(eventName, handler) {
|
|
949
|
+
if (!this.customEventHandlers.has(eventName)) {
|
|
950
|
+
this.customEventHandlers.set(eventName, /* @__PURE__ */ new Set());
|
|
951
|
+
}
|
|
952
|
+
this.customEventHandlers.get(eventName).add(handler);
|
|
953
|
+
return () => {
|
|
954
|
+
this.customEventHandlers.get(eventName)?.delete(handler);
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Unsubscribe from a custom event
|
|
959
|
+
* @param eventName - The name of the event
|
|
960
|
+
* @param handler - The handler to remove
|
|
961
|
+
*/
|
|
962
|
+
offEvent(eventName, handler) {
|
|
963
|
+
this.customEventHandlers.get(eventName)?.delete(handler);
|
|
964
|
+
}
|
|
965
|
+
emitCustomEvent(event) {
|
|
966
|
+
const handlers = this.customEventHandlers.get(event.name);
|
|
967
|
+
if (handlers) {
|
|
968
|
+
handlers.forEach((handler) => handler(event.data, event));
|
|
969
|
+
}
|
|
970
|
+
this.emit("event", event);
|
|
971
|
+
this.emit(`event:${event.name}`, event);
|
|
972
|
+
}
|
|
973
|
+
// ─────────────────────────────────────────────────────────────────
|
|
835
974
|
// WebSocket
|
|
836
975
|
// ─────────────────────────────────────────────────────────────────
|
|
837
976
|
connectWebSocket() {
|
|
@@ -929,6 +1068,40 @@ var PocketPingClient = class {
|
|
|
929
1068
|
}
|
|
930
1069
|
this.emit("read", readData);
|
|
931
1070
|
break;
|
|
1071
|
+
case "event":
|
|
1072
|
+
const customEvent = event.data;
|
|
1073
|
+
this.emitCustomEvent(customEvent);
|
|
1074
|
+
break;
|
|
1075
|
+
case "version_warning":
|
|
1076
|
+
const versionWarning = event.data;
|
|
1077
|
+
this.handleVersionWarning(versionWarning);
|
|
1078
|
+
break;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1082
|
+
// Version Management
|
|
1083
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1084
|
+
handleVersionWarning(warning) {
|
|
1085
|
+
const prefix = "[PocketPing]";
|
|
1086
|
+
const upgradeHint = warning.upgradeUrl ? ` Upgrade: ${warning.upgradeUrl}` : " Update your widget to the latest version.";
|
|
1087
|
+
switch (warning.severity) {
|
|
1088
|
+
case "error":
|
|
1089
|
+
console.error(`${prefix} \u{1F6A8} VERSION ERROR: ${warning.message}${upgradeHint}`);
|
|
1090
|
+
console.error(`${prefix} Current: ${warning.currentVersion}, Required: ${warning.minVersion || "unknown"}`);
|
|
1091
|
+
break;
|
|
1092
|
+
case "warning":
|
|
1093
|
+
console.warn(`${prefix} \u26A0\uFE0F VERSION WARNING: ${warning.message}${upgradeHint}`);
|
|
1094
|
+
console.warn(`${prefix} Current: ${warning.currentVersion}, Latest: ${warning.latestVersion || "unknown"}`);
|
|
1095
|
+
break;
|
|
1096
|
+
case "info":
|
|
1097
|
+
console.info(`${prefix} \u2139\uFE0F ${warning.message}`);
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
this.emit("versionWarning", warning);
|
|
1101
|
+
this.config.onVersionWarning?.(warning);
|
|
1102
|
+
if (!warning.canContinue) {
|
|
1103
|
+
console.error(`${prefix} Widget is incompatible with backend. Please update immediately.`);
|
|
1104
|
+
this.disconnect();
|
|
932
1105
|
}
|
|
933
1106
|
}
|
|
934
1107
|
scheduleReconnect() {
|
|
@@ -974,15 +1147,46 @@ var PocketPingClient = class {
|
|
|
974
1147
|
...options,
|
|
975
1148
|
headers: {
|
|
976
1149
|
"Content-Type": "application/json",
|
|
1150
|
+
"X-PocketPing-Version": VERSION,
|
|
977
1151
|
...options.headers
|
|
978
1152
|
}
|
|
979
1153
|
});
|
|
1154
|
+
this.checkVersionHeaders(response);
|
|
980
1155
|
if (!response.ok) {
|
|
981
1156
|
const error = await response.text();
|
|
982
1157
|
throw new Error(`PocketPing API error: ${response.status} ${error}`);
|
|
983
1158
|
}
|
|
984
1159
|
return response.json();
|
|
985
1160
|
}
|
|
1161
|
+
checkVersionHeaders(response) {
|
|
1162
|
+
const versionStatus = response.headers.get("X-PocketPing-Version-Status");
|
|
1163
|
+
const minVersion = response.headers.get("X-PocketPing-Min-Version");
|
|
1164
|
+
const latestVersion = response.headers.get("X-PocketPing-Latest-Version");
|
|
1165
|
+
const versionMessage = response.headers.get("X-PocketPing-Version-Message");
|
|
1166
|
+
if (!versionStatus || versionStatus === "ok") {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
let severity = "info";
|
|
1170
|
+
let canContinue = true;
|
|
1171
|
+
if (versionStatus === "deprecated") {
|
|
1172
|
+
severity = "warning";
|
|
1173
|
+
} else if (versionStatus === "unsupported") {
|
|
1174
|
+
severity = "error";
|
|
1175
|
+
canContinue = false;
|
|
1176
|
+
} else if (versionStatus === "outdated") {
|
|
1177
|
+
severity = "info";
|
|
1178
|
+
}
|
|
1179
|
+
const warning = {
|
|
1180
|
+
severity,
|
|
1181
|
+
message: versionMessage || `Widget version ${VERSION} is ${versionStatus}`,
|
|
1182
|
+
currentVersion: VERSION,
|
|
1183
|
+
minVersion: minVersion || void 0,
|
|
1184
|
+
latestVersion: latestVersion || void 0,
|
|
1185
|
+
canContinue,
|
|
1186
|
+
upgradeUrl: "https://docs.pocketping.io/widget/installation"
|
|
1187
|
+
};
|
|
1188
|
+
this.handleVersionWarning(warning);
|
|
1189
|
+
}
|
|
986
1190
|
// ─────────────────────────────────────────────────────────────────
|
|
987
1191
|
// Storage
|
|
988
1192
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -1001,6 +1205,20 @@ var PocketPingClient = class {
|
|
|
1001
1205
|
storeSessionId(sessionId) {
|
|
1002
1206
|
localStorage.setItem("pocketping_session_id", sessionId);
|
|
1003
1207
|
}
|
|
1208
|
+
getStoredIdentity() {
|
|
1209
|
+
try {
|
|
1210
|
+
const stored = localStorage.getItem("pocketping_user_identity");
|
|
1211
|
+
return stored ? JSON.parse(stored) : null;
|
|
1212
|
+
} catch {
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
storeIdentity(identity) {
|
|
1217
|
+
localStorage.setItem("pocketping_user_identity", JSON.stringify(identity));
|
|
1218
|
+
}
|
|
1219
|
+
clearIdentity() {
|
|
1220
|
+
localStorage.removeItem("pocketping_user_identity");
|
|
1221
|
+
}
|
|
1004
1222
|
generateId() {
|
|
1005
1223
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1006
1224
|
}
|
|
@@ -1009,15 +1227,21 @@ var PocketPingClient = class {
|
|
|
1009
1227
|
// src/index.ts
|
|
1010
1228
|
var client = null;
|
|
1011
1229
|
var container = null;
|
|
1230
|
+
var SAAS_API_BASE = "https://app.pocketping.io/api/widget";
|
|
1012
1231
|
function init(config) {
|
|
1013
1232
|
if (client) {
|
|
1014
1233
|
console.warn("[PocketPing] Already initialized");
|
|
1015
1234
|
return client;
|
|
1016
1235
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1236
|
+
let resolvedEndpoint = config.endpoint;
|
|
1237
|
+
if (!resolvedEndpoint && config.projectId) {
|
|
1238
|
+
resolvedEndpoint = `${SAAS_API_BASE}/${config.projectId}`;
|
|
1239
|
+
}
|
|
1240
|
+
if (!resolvedEndpoint) {
|
|
1241
|
+
throw new Error("[PocketPing] endpoint or projectId is required");
|
|
1019
1242
|
}
|
|
1020
|
-
|
|
1243
|
+
const resolvedConfig = { ...config, endpoint: resolvedEndpoint };
|
|
1244
|
+
client = new PocketPingClient(resolvedConfig);
|
|
1021
1245
|
container = document.createElement("div");
|
|
1022
1246
|
container.id = "pocketping-container";
|
|
1023
1247
|
document.body.appendChild(container);
|
|
@@ -1053,23 +1277,77 @@ function sendMessage(content) {
|
|
|
1053
1277
|
}
|
|
1054
1278
|
return client.sendMessage(content);
|
|
1055
1279
|
}
|
|
1280
|
+
function trigger(eventName, data) {
|
|
1281
|
+
if (!client) {
|
|
1282
|
+
console.warn("[PocketPing] Not initialized, cannot trigger event");
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
client.trigger(eventName, data);
|
|
1286
|
+
}
|
|
1287
|
+
function onEvent(eventName, handler) {
|
|
1288
|
+
if (!client) {
|
|
1289
|
+
console.warn("[PocketPing] Not initialized, cannot subscribe to event");
|
|
1290
|
+
return () => {
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
return client.onEvent(eventName, handler);
|
|
1294
|
+
}
|
|
1295
|
+
function offEvent(eventName, handler) {
|
|
1296
|
+
client?.offEvent(eventName, handler);
|
|
1297
|
+
}
|
|
1298
|
+
async function identify(identity) {
|
|
1299
|
+
if (!client) {
|
|
1300
|
+
throw new Error("[PocketPing] Not initialized");
|
|
1301
|
+
}
|
|
1302
|
+
return client.identify(identity);
|
|
1303
|
+
}
|
|
1304
|
+
async function reset(options) {
|
|
1305
|
+
if (!client) {
|
|
1306
|
+
console.warn("[PocketPing] Not initialized");
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
return client.reset(options);
|
|
1310
|
+
}
|
|
1311
|
+
function getIdentity() {
|
|
1312
|
+
return client?.getIdentity() || null;
|
|
1313
|
+
}
|
|
1314
|
+
function on(eventName, handler) {
|
|
1315
|
+
if (!client) {
|
|
1316
|
+
console.warn("[PocketPing] Not initialized, cannot subscribe to event");
|
|
1317
|
+
return () => {
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
return client.on(eventName, handler);
|
|
1321
|
+
}
|
|
1056
1322
|
if (typeof document !== "undefined") {
|
|
1057
1323
|
const script = document.currentScript;
|
|
1058
|
-
if (script
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1324
|
+
if (script) {
|
|
1325
|
+
const projectId = script.dataset.key;
|
|
1326
|
+
const endpoint = script.dataset.endpoint;
|
|
1327
|
+
if (projectId || endpoint) {
|
|
1328
|
+
init({
|
|
1329
|
+
projectId,
|
|
1330
|
+
endpoint,
|
|
1331
|
+
theme: script.dataset.theme || "auto",
|
|
1332
|
+
position: script.dataset.position || "bottom-right"
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1064
1335
|
}
|
|
1065
1336
|
}
|
|
1066
|
-
var index_default = { init, destroy, open, close, toggle, sendMessage };
|
|
1337
|
+
var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
|
|
1067
1338
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1068
1339
|
0 && (module.exports = {
|
|
1069
1340
|
close,
|
|
1070
1341
|
destroy,
|
|
1342
|
+
getIdentity,
|
|
1343
|
+
identify,
|
|
1071
1344
|
init,
|
|
1345
|
+
offEvent,
|
|
1346
|
+
on,
|
|
1347
|
+
onEvent,
|
|
1072
1348
|
open,
|
|
1349
|
+
reset,
|
|
1073
1350
|
sendMessage,
|
|
1074
|
-
toggle
|
|
1351
|
+
toggle,
|
|
1352
|
+
trigger
|
|
1075
1353
|
});
|