@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.mjs CHANGED
@@ -599,6 +599,9 @@ function StatusIcon({ status }) {
599
599
  return null;
600
600
  }
601
601
 
602
+ // src/version.ts
603
+ var VERSION = "0.2.0";
604
+
602
605
  // src/client.ts
603
606
  var PocketPingClient = class {
604
607
  constructor(config) {
@@ -606,6 +609,7 @@ var PocketPingClient = class {
606
609
  this.ws = null;
607
610
  this.isOpen = false;
608
611
  this.listeners = /* @__PURE__ */ new Map();
612
+ this.customEventHandlers = /* @__PURE__ */ new Map();
609
613
  this.reconnectAttempts = 0;
610
614
  this.maxReconnectAttempts = 5;
611
615
  this.reconnectTimeout = null;
@@ -617,6 +621,7 @@ var PocketPingClient = class {
617
621
  async connect() {
618
622
  const visitorId = this.getOrCreateVisitorId();
619
623
  const storedSessionId = this.getStoredSessionId();
624
+ const storedIdentity = this.getStoredIdentity();
620
625
  const response = await this.fetch("/connect", {
621
626
  method: "POST",
622
627
  body: JSON.stringify({
@@ -630,14 +635,17 @@ var PocketPingClient = class {
630
635
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
631
636
  language: navigator.language,
632
637
  screenResolution: `${window.screen.width}x${window.screen.height}`
633
- }
638
+ },
639
+ // Include stored identity if available
640
+ identity: storedIdentity || void 0
634
641
  })
635
642
  });
636
643
  this.session = {
637
644
  sessionId: response.sessionId,
638
645
  visitorId: response.visitorId,
639
646
  operatorOnline: response.operatorOnline ?? false,
640
- messages: response.messages ?? []
647
+ messages: response.messages ?? [],
648
+ identity: response.identity || storedIdentity || void 0
641
649
  };
642
650
  this.storeSessionId(response.sessionId);
643
651
  this.connectWebSocket();
@@ -760,6 +768,69 @@ var PocketPingClient = class {
760
768
  return this.fetch("/presence", { method: "GET" });
761
769
  }
762
770
  // ─────────────────────────────────────────────────────────────────
771
+ // User Identity
772
+ // ─────────────────────────────────────────────────────────────────
773
+ /**
774
+ * Identify the current user with metadata
775
+ * Call after user logs in or when user data becomes available
776
+ * @param identity - User identity data with required id field
777
+ * @example
778
+ * PocketPing.identify({
779
+ * id: 'user_123',
780
+ * email: 'john@example.com',
781
+ * name: 'John Doe',
782
+ * plan: 'pro',
783
+ * company: 'Acme Inc'
784
+ * })
785
+ */
786
+ async identify(identity) {
787
+ if (!identity?.id) {
788
+ throw new Error("[PocketPing] identity.id is required");
789
+ }
790
+ this.storeIdentity(identity);
791
+ if (this.session) {
792
+ try {
793
+ await this.fetch("/identify", {
794
+ method: "POST",
795
+ body: JSON.stringify({
796
+ sessionId: this.session.sessionId,
797
+ identity
798
+ })
799
+ });
800
+ this.session.identity = identity;
801
+ this.emit("identify", identity);
802
+ } catch (err) {
803
+ console.error("[PocketPing] Failed to identify:", err);
804
+ throw err;
805
+ }
806
+ }
807
+ }
808
+ /**
809
+ * Reset the user identity and optionally start a new session
810
+ * Call on user logout to clear user data
811
+ * @param options - Optional settings: { newSession: boolean }
812
+ */
813
+ async reset(options) {
814
+ this.clearIdentity();
815
+ if (this.session) {
816
+ this.session.identity = void 0;
817
+ }
818
+ if (options?.newSession) {
819
+ localStorage.removeItem("pocketping_session_id");
820
+ localStorage.removeItem("pocketping_visitor_id");
821
+ this.disconnect();
822
+ await this.connect();
823
+ }
824
+ this.emit("reset", null);
825
+ }
826
+ /**
827
+ * Get the current user identity
828
+ * @returns UserIdentity or null if not identified
829
+ */
830
+ getIdentity() {
831
+ return this.session?.identity || this.getStoredIdentity();
832
+ }
833
+ // ─────────────────────────────────────────────────────────────────
763
834
  // State
764
835
  // ─────────────────────────────────────────────────────────────────
765
836
  getSession() {
@@ -802,6 +873,67 @@ var PocketPingClient = class {
802
873
  this.listeners.get(event)?.forEach((listener) => listener(data));
803
874
  }
804
875
  // ─────────────────────────────────────────────────────────────────
876
+ // Custom Events (bidirectional)
877
+ // ─────────────────────────────────────────────────────────────────
878
+ /**
879
+ * Trigger a custom event to the backend
880
+ * @param eventName - The name of the event (e.g., 'clicked_pricing', 'viewed_demo')
881
+ * @param data - Optional payload to send with the event
882
+ * @example
883
+ * PocketPing.trigger('clicked_cta', { button: 'signup', page: '/pricing' })
884
+ */
885
+ trigger(eventName, data) {
886
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
887
+ console.warn("[PocketPing] Cannot trigger event: WebSocket not connected");
888
+ return;
889
+ }
890
+ const event = {
891
+ name: eventName,
892
+ data,
893
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
894
+ };
895
+ this.ws.send(JSON.stringify({
896
+ type: "event",
897
+ data: event
898
+ }));
899
+ this.emit(`event:${eventName}`, event);
900
+ }
901
+ /**
902
+ * Subscribe to custom events from the backend
903
+ * @param eventName - The name of the event to listen for (e.g., 'show_offer', 'open_chat')
904
+ * @param handler - Callback function when event is received
905
+ * @returns Unsubscribe function
906
+ * @example
907
+ * const unsubscribe = PocketPing.onEvent('show_offer', (data) => {
908
+ * showPopup(data.message)
909
+ * })
910
+ */
911
+ onEvent(eventName, handler) {
912
+ if (!this.customEventHandlers.has(eventName)) {
913
+ this.customEventHandlers.set(eventName, /* @__PURE__ */ new Set());
914
+ }
915
+ this.customEventHandlers.get(eventName).add(handler);
916
+ return () => {
917
+ this.customEventHandlers.get(eventName)?.delete(handler);
918
+ };
919
+ }
920
+ /**
921
+ * Unsubscribe from a custom event
922
+ * @param eventName - The name of the event
923
+ * @param handler - The handler to remove
924
+ */
925
+ offEvent(eventName, handler) {
926
+ this.customEventHandlers.get(eventName)?.delete(handler);
927
+ }
928
+ emitCustomEvent(event) {
929
+ const handlers = this.customEventHandlers.get(event.name);
930
+ if (handlers) {
931
+ handlers.forEach((handler) => handler(event.data, event));
932
+ }
933
+ this.emit("event", event);
934
+ this.emit(`event:${event.name}`, event);
935
+ }
936
+ // ─────────────────────────────────────────────────────────────────
805
937
  // WebSocket
806
938
  // ─────────────────────────────────────────────────────────────────
807
939
  connectWebSocket() {
@@ -899,6 +1031,40 @@ var PocketPingClient = class {
899
1031
  }
900
1032
  this.emit("read", readData);
901
1033
  break;
1034
+ case "event":
1035
+ const customEvent = event.data;
1036
+ this.emitCustomEvent(customEvent);
1037
+ break;
1038
+ case "version_warning":
1039
+ const versionWarning = event.data;
1040
+ this.handleVersionWarning(versionWarning);
1041
+ break;
1042
+ }
1043
+ }
1044
+ // ─────────────────────────────────────────────────────────────────
1045
+ // Version Management
1046
+ // ─────────────────────────────────────────────────────────────────
1047
+ handleVersionWarning(warning) {
1048
+ const prefix = "[PocketPing]";
1049
+ const upgradeHint = warning.upgradeUrl ? ` Upgrade: ${warning.upgradeUrl}` : " Update your widget to the latest version.";
1050
+ switch (warning.severity) {
1051
+ case "error":
1052
+ console.error(`${prefix} \u{1F6A8} VERSION ERROR: ${warning.message}${upgradeHint}`);
1053
+ console.error(`${prefix} Current: ${warning.currentVersion}, Required: ${warning.minVersion || "unknown"}`);
1054
+ break;
1055
+ case "warning":
1056
+ console.warn(`${prefix} \u26A0\uFE0F VERSION WARNING: ${warning.message}${upgradeHint}`);
1057
+ console.warn(`${prefix} Current: ${warning.currentVersion}, Latest: ${warning.latestVersion || "unknown"}`);
1058
+ break;
1059
+ case "info":
1060
+ console.info(`${prefix} \u2139\uFE0F ${warning.message}`);
1061
+ break;
1062
+ }
1063
+ this.emit("versionWarning", warning);
1064
+ this.config.onVersionWarning?.(warning);
1065
+ if (!warning.canContinue) {
1066
+ console.error(`${prefix} Widget is incompatible with backend. Please update immediately.`);
1067
+ this.disconnect();
902
1068
  }
903
1069
  }
904
1070
  scheduleReconnect() {
@@ -944,15 +1110,46 @@ var PocketPingClient = class {
944
1110
  ...options,
945
1111
  headers: {
946
1112
  "Content-Type": "application/json",
1113
+ "X-PocketPing-Version": VERSION,
947
1114
  ...options.headers
948
1115
  }
949
1116
  });
1117
+ this.checkVersionHeaders(response);
950
1118
  if (!response.ok) {
951
1119
  const error = await response.text();
952
1120
  throw new Error(`PocketPing API error: ${response.status} ${error}`);
953
1121
  }
954
1122
  return response.json();
955
1123
  }
1124
+ checkVersionHeaders(response) {
1125
+ const versionStatus = response.headers.get("X-PocketPing-Version-Status");
1126
+ const minVersion = response.headers.get("X-PocketPing-Min-Version");
1127
+ const latestVersion = response.headers.get("X-PocketPing-Latest-Version");
1128
+ const versionMessage = response.headers.get("X-PocketPing-Version-Message");
1129
+ if (!versionStatus || versionStatus === "ok") {
1130
+ return;
1131
+ }
1132
+ let severity = "info";
1133
+ let canContinue = true;
1134
+ if (versionStatus === "deprecated") {
1135
+ severity = "warning";
1136
+ } else if (versionStatus === "unsupported") {
1137
+ severity = "error";
1138
+ canContinue = false;
1139
+ } else if (versionStatus === "outdated") {
1140
+ severity = "info";
1141
+ }
1142
+ const warning = {
1143
+ severity,
1144
+ message: versionMessage || `Widget version ${VERSION} is ${versionStatus}`,
1145
+ currentVersion: VERSION,
1146
+ minVersion: minVersion || void 0,
1147
+ latestVersion: latestVersion || void 0,
1148
+ canContinue,
1149
+ upgradeUrl: "https://docs.pocketping.io/widget/installation"
1150
+ };
1151
+ this.handleVersionWarning(warning);
1152
+ }
956
1153
  // ─────────────────────────────────────────────────────────────────
957
1154
  // Storage
958
1155
  // ─────────────────────────────────────────────────────────────────
@@ -971,6 +1168,20 @@ var PocketPingClient = class {
971
1168
  storeSessionId(sessionId) {
972
1169
  localStorage.setItem("pocketping_session_id", sessionId);
973
1170
  }
1171
+ getStoredIdentity() {
1172
+ try {
1173
+ const stored = localStorage.getItem("pocketping_user_identity");
1174
+ return stored ? JSON.parse(stored) : null;
1175
+ } catch {
1176
+ return null;
1177
+ }
1178
+ }
1179
+ storeIdentity(identity) {
1180
+ localStorage.setItem("pocketping_user_identity", JSON.stringify(identity));
1181
+ }
1182
+ clearIdentity() {
1183
+ localStorage.removeItem("pocketping_user_identity");
1184
+ }
974
1185
  generateId() {
975
1186
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
976
1187
  }
@@ -1023,6 +1234,48 @@ function sendMessage(content) {
1023
1234
  }
1024
1235
  return client.sendMessage(content);
1025
1236
  }
1237
+ function trigger(eventName, data) {
1238
+ if (!client) {
1239
+ console.warn("[PocketPing] Not initialized, cannot trigger event");
1240
+ return;
1241
+ }
1242
+ client.trigger(eventName, data);
1243
+ }
1244
+ function onEvent(eventName, handler) {
1245
+ if (!client) {
1246
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1247
+ return () => {
1248
+ };
1249
+ }
1250
+ return client.onEvent(eventName, handler);
1251
+ }
1252
+ function offEvent(eventName, handler) {
1253
+ client?.offEvent(eventName, handler);
1254
+ }
1255
+ async function identify(identity) {
1256
+ if (!client) {
1257
+ throw new Error("[PocketPing] Not initialized");
1258
+ }
1259
+ return client.identify(identity);
1260
+ }
1261
+ async function reset(options) {
1262
+ if (!client) {
1263
+ console.warn("[PocketPing] Not initialized");
1264
+ return;
1265
+ }
1266
+ return client.reset(options);
1267
+ }
1268
+ function getIdentity() {
1269
+ return client?.getIdentity() || null;
1270
+ }
1271
+ function on(eventName, handler) {
1272
+ if (!client) {
1273
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1274
+ return () => {
1275
+ };
1276
+ }
1277
+ return client.on(eventName, handler);
1278
+ }
1026
1279
  if (typeof document !== "undefined") {
1027
1280
  const script = document.currentScript;
1028
1281
  if (script?.dataset.endpoint) {
@@ -1033,13 +1286,20 @@ if (typeof document !== "undefined") {
1033
1286
  });
1034
1287
  }
1035
1288
  }
1036
- var index_default = { init, destroy, open, close, toggle, sendMessage };
1289
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
1037
1290
  export {
1038
1291
  close,
1039
1292
  index_default as default,
1040
1293
  destroy,
1294
+ getIdentity,
1295
+ identify,
1041
1296
  init,
1297
+ offEvent,
1298
+ on,
1299
+ onEvent,
1042
1300
  open,
1301
+ reset,
1043
1302
  sendMessage,
1044
- toggle
1303
+ toggle,
1304
+ trigger
1045
1305
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pocketping/widget",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Embeddable chat widget for PocketPing",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,9 +10,13 @@
10
10
  "dist"
11
11
  ],
12
12
  "scripts": {
13
- "build": "tsup src/index.ts --format cjs,esm,iife --global-name PocketPing --dts",
14
- "dev": "tsup src/index.ts --format iife --global-name PocketPing --watch",
15
- "test": "vitest"
13
+ "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",
14
+ "prebuild": "pnpm clean",
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "dev:test": "pnpm build && npx serve . -p 3333 --cors",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
16
20
  },
17
21
  "dependencies": {
18
22
  "preact": "^10.19.0"