@pocketping/widget 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.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.2.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
  }
@@ -1053,6 +1271,48 @@ function sendMessage(content) {
1053
1271
  }
1054
1272
  return client.sendMessage(content);
1055
1273
  }
1274
+ function trigger(eventName, data) {
1275
+ if (!client) {
1276
+ console.warn("[PocketPing] Not initialized, cannot trigger event");
1277
+ return;
1278
+ }
1279
+ client.trigger(eventName, data);
1280
+ }
1281
+ function onEvent(eventName, handler) {
1282
+ if (!client) {
1283
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1284
+ return () => {
1285
+ };
1286
+ }
1287
+ return client.onEvent(eventName, handler);
1288
+ }
1289
+ function offEvent(eventName, handler) {
1290
+ client?.offEvent(eventName, handler);
1291
+ }
1292
+ async function identify(identity) {
1293
+ if (!client) {
1294
+ throw new Error("[PocketPing] Not initialized");
1295
+ }
1296
+ return client.identify(identity);
1297
+ }
1298
+ async function reset(options) {
1299
+ if (!client) {
1300
+ console.warn("[PocketPing] Not initialized");
1301
+ return;
1302
+ }
1303
+ return client.reset(options);
1304
+ }
1305
+ function getIdentity() {
1306
+ return client?.getIdentity() || null;
1307
+ }
1308
+ function on(eventName, handler) {
1309
+ if (!client) {
1310
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1311
+ return () => {
1312
+ };
1313
+ }
1314
+ return client.on(eventName, handler);
1315
+ }
1056
1316
  if (typeof document !== "undefined") {
1057
1317
  const script = document.currentScript;
1058
1318
  if (script?.dataset.endpoint) {
@@ -1063,13 +1323,20 @@ if (typeof document !== "undefined") {
1063
1323
  });
1064
1324
  }
1065
1325
  }
1066
- var index_default = { init, destroy, open, close, toggle, sendMessage };
1326
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
1067
1327
  // Annotate the CommonJS export names for ESM import in node:
1068
1328
  0 && (module.exports = {
1069
1329
  close,
1070
1330
  destroy,
1331
+ getIdentity,
1332
+ identify,
1071
1333
  init,
1334
+ offEvent,
1335
+ on,
1336
+ onEvent,
1072
1337
  open,
1338
+ reset,
1073
1339
  sendMessage,
1074
- toggle
1340
+ toggle,
1341
+ trigger
1075
1342
  });