@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/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.3.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
  }
@@ -979,15 +1190,21 @@ var PocketPingClient = class {
979
1190
  // src/index.ts
980
1191
  var client = null;
981
1192
  var container = null;
1193
+ var SAAS_API_BASE = "https://app.pocketping.io/api/widget";
982
1194
  function init(config) {
983
1195
  if (client) {
984
1196
  console.warn("[PocketPing] Already initialized");
985
1197
  return client;
986
1198
  }
987
- if (!config.endpoint) {
988
- throw new Error("[PocketPing] endpoint is required");
1199
+ let resolvedEndpoint = config.endpoint;
1200
+ if (!resolvedEndpoint && config.projectId) {
1201
+ resolvedEndpoint = `${SAAS_API_BASE}/${config.projectId}`;
1202
+ }
1203
+ if (!resolvedEndpoint) {
1204
+ throw new Error("[PocketPing] endpoint or projectId is required");
989
1205
  }
990
- client = new PocketPingClient(config);
1206
+ const resolvedConfig = { ...config, endpoint: resolvedEndpoint };
1207
+ client = new PocketPingClient(resolvedConfig);
991
1208
  container = document.createElement("div");
992
1209
  container.id = "pocketping-container";
993
1210
  document.body.appendChild(container);
@@ -1023,23 +1240,77 @@ function sendMessage(content) {
1023
1240
  }
1024
1241
  return client.sendMessage(content);
1025
1242
  }
1243
+ function trigger(eventName, data) {
1244
+ if (!client) {
1245
+ console.warn("[PocketPing] Not initialized, cannot trigger event");
1246
+ return;
1247
+ }
1248
+ client.trigger(eventName, data);
1249
+ }
1250
+ function onEvent(eventName, handler) {
1251
+ if (!client) {
1252
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1253
+ return () => {
1254
+ };
1255
+ }
1256
+ return client.onEvent(eventName, handler);
1257
+ }
1258
+ function offEvent(eventName, handler) {
1259
+ client?.offEvent(eventName, handler);
1260
+ }
1261
+ async function identify(identity) {
1262
+ if (!client) {
1263
+ throw new Error("[PocketPing] Not initialized");
1264
+ }
1265
+ return client.identify(identity);
1266
+ }
1267
+ async function reset(options) {
1268
+ if (!client) {
1269
+ console.warn("[PocketPing] Not initialized");
1270
+ return;
1271
+ }
1272
+ return client.reset(options);
1273
+ }
1274
+ function getIdentity() {
1275
+ return client?.getIdentity() || null;
1276
+ }
1277
+ function on(eventName, handler) {
1278
+ if (!client) {
1279
+ console.warn("[PocketPing] Not initialized, cannot subscribe to event");
1280
+ return () => {
1281
+ };
1282
+ }
1283
+ return client.on(eventName, handler);
1284
+ }
1026
1285
  if (typeof document !== "undefined") {
1027
1286
  const script = document.currentScript;
1028
- if (script?.dataset.endpoint) {
1029
- init({
1030
- endpoint: script.dataset.endpoint,
1031
- theme: script.dataset.theme || "auto",
1032
- position: script.dataset.position || "bottom-right"
1033
- });
1287
+ if (script) {
1288
+ const projectId = script.dataset.key;
1289
+ const endpoint = script.dataset.endpoint;
1290
+ if (projectId || endpoint) {
1291
+ init({
1292
+ projectId,
1293
+ endpoint,
1294
+ theme: script.dataset.theme || "auto",
1295
+ position: script.dataset.position || "bottom-right"
1296
+ });
1297
+ }
1034
1298
  }
1035
1299
  }
1036
- var index_default = { init, destroy, open, close, toggle, sendMessage };
1300
+ var index_default = { init, destroy, open, close, toggle, sendMessage, trigger, onEvent, offEvent, on, identify, reset, getIdentity };
1037
1301
  export {
1038
1302
  close,
1039
1303
  index_default as default,
1040
1304
  destroy,
1305
+ getIdentity,
1306
+ identify,
1041
1307
  init,
1308
+ offEvent,
1309
+ on,
1310
+ onEvent,
1042
1311
  open,
1312
+ reset,
1043
1313
  sendMessage,
1044
- toggle
1314
+ toggle,
1315
+ trigger
1045
1316
  };