@pocketping/widget 2.0.0 → 2.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/README.md CHANGED
@@ -423,3 +423,4 @@ For advanced customization, you can target these CSS classes:
423
423
 
424
424
  MIT
425
425
 
426
+
package/dist/index.cjs CHANGED
@@ -47,23 +47,59 @@ var import_preact = require("preact");
47
47
  var import_hooks2 = require("preact/hooks");
48
48
 
49
49
  // src/components/styles.ts
50
+ var DEFAULT_GRADIENT = { from: "#36e3ff", to: "#7c5cff", direction: "to right" };
51
+ function isGradient(value) {
52
+ return typeof value === "object" && value !== null && "from" in value && "to" in value;
53
+ }
54
+ function toCssColor(value) {
55
+ if (isGradient(value)) {
56
+ const direction = value.direction || "to right";
57
+ return `linear-gradient(${direction}, ${value.from}, ${value.to})`;
58
+ }
59
+ return value;
60
+ }
61
+ function getBaseColor(value) {
62
+ if (isGradient(value)) {
63
+ return value.to;
64
+ }
65
+ return value;
66
+ }
50
67
  function resolveThemeColor(color, isDark, defaultLight, defaultDark) {
51
68
  if (!color) {
52
- return isDark ? defaultDark : defaultLight;
69
+ return toCssColor(isDark ? defaultDark : defaultLight);
53
70
  }
54
71
  if (typeof color === "string") {
55
72
  return color;
56
73
  }
57
- return isDark ? color.dark : color.light;
74
+ if (isGradient(color)) {
75
+ return toCssColor(color);
76
+ }
77
+ const resolved = isDark ? color.dark : color.light;
78
+ return toCssColor(resolved);
79
+ }
80
+ function resolveBaseColor(color, isDark, defaultLight, defaultDark) {
81
+ if (!color) {
82
+ return getBaseColor(isDark ? defaultDark : defaultLight);
83
+ }
84
+ if (typeof color === "string") {
85
+ return color;
86
+ }
87
+ if (isGradient(color)) {
88
+ return getBaseColor(color);
89
+ }
90
+ const resolved = isDark ? color.dark : color.light;
91
+ return getBaseColor(resolved);
58
92
  }
59
93
  function styles(options) {
60
94
  const { primaryColor, theme, headerColor, footerColor, chatBackground, toggleColor } = options;
61
95
  const isDark = theme === "dark";
62
- const resolvedHeaderColor = resolveThemeColor(headerColor, isDark, "#008069", "#202c33");
96
+ const resolvedHeaderColor = resolveThemeColor(headerColor, isDark, DEFAULT_GRADIENT, "#202c33");
63
97
  const resolvedFooterColor = resolveThemeColor(footerColor, isDark, "#f0f2f5", "#202c33");
64
- const resolvedToggleColor = resolveThemeColor(toggleColor, isDark, "#25d366", "#25d366");
65
- const resolvedToggleHoverColor = resolvedToggleColor !== "#25d366" ? adjustBrightness(resolvedToggleColor, -10) : "#22c55e";
66
- const resolvedSendButtonHoverColor = adjustBrightness(resolvedHeaderColor, -15);
98
+ const resolvedToggleColor = resolveThemeColor(toggleColor, isDark, DEFAULT_GRADIENT, DEFAULT_GRADIENT);
99
+ const headerBaseColor = resolveBaseColor(headerColor, isDark, DEFAULT_GRADIENT, "#202c33");
100
+ const toggleBaseColor = resolveBaseColor(toggleColor, isDark, DEFAULT_GRADIENT, DEFAULT_GRADIENT);
101
+ const resolvedToggleHoverColor = adjustBrightness(toggleBaseColor, -15);
102
+ const resolvedSendButtonHoverColor = adjustBrightness(headerBaseColor, -15);
67
103
  const whatsappPattern = isDark ? `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")` : `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23000000' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`;
68
104
  const dotsPattern = isDark ? `url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='10' cy='10' r='1' fill='%23ffffff' fill-opacity='0.05'/%3E%3C/svg%3E")` : `url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='10' cy='10' r='1' fill='%23000000' fill-opacity='0.05'/%3E%3C/svg%3E")`;
69
105
  const resolvedChatBg = resolveThemeColor(chatBackground, isDark, "whatsapp", "whatsapp");
@@ -162,7 +198,7 @@ function styles(options) {
162
198
  right: 4px;
163
199
  width: 12px;
164
200
  height: 12px;
165
- background: #22c55e;
201
+ background: #ff5fd4;
166
202
  border-radius: 50%;
167
203
  border: 2px solid white;
168
204
  }
@@ -318,8 +354,8 @@ function styles(options) {
318
354
  }
319
355
 
320
356
  .pp-status-dot.pp-online {
321
- background: #25d366;
322
- box-shadow: 0 0 0 2px rgba(37, 211, 102, 0.3);
357
+ background: #ff5fd4;
358
+ box-shadow: 0 0 0 2px rgba(255, 95, 212, 0.3);
323
359
  }
324
360
 
325
361
  .pp-close-btn {
@@ -1023,7 +1059,7 @@ function styles(options) {
1023
1059
  justify-content: center;
1024
1060
  gap: 16px;
1025
1061
  z-index: 100;
1026
- border: 3px dashed #00a884;
1062
+ border: 3px dashed #7c5cff;
1027
1063
  border-radius: 12px;
1028
1064
  margin: 4px;
1029
1065
  pointer-events: none;
@@ -1032,7 +1068,7 @@ function styles(options) {
1032
1068
  .pp-drop-icon svg {
1033
1069
  width: 56px;
1034
1070
  height: 56px;
1035
- color: #00a884;
1071
+ color: #7c5cff;
1036
1072
  }
1037
1073
 
1038
1074
  .pp-drop-text {
@@ -1173,7 +1209,7 @@ function styles(options) {
1173
1209
  padding: 10px 20px;
1174
1210
  border: none;
1175
1211
  border-radius: 8px;
1176
- background: #00a884;
1212
+ background: #7c5cff;
1177
1213
  color: white;
1178
1214
  font-size: 14px;
1179
1215
  font-weight: 500;
@@ -1182,7 +1218,7 @@ function styles(options) {
1182
1218
  }
1183
1219
 
1184
1220
  .pp-edit-save:hover:not(:disabled) {
1185
- background: #008f72;
1221
+ background: #6a4ee6;
1186
1222
  }
1187
1223
 
1188
1224
  .pp-edit-save:disabled {
@@ -1197,7 +1233,7 @@ function styles(options) {
1197
1233
  gap: 10px;
1198
1234
  padding: 8px 12px;
1199
1235
  background: ${resolvedFooterColor};
1200
- border-left: 4px solid #00a884;
1236
+ border-left: 4px solid #7c5cff;
1201
1237
  }
1202
1238
 
1203
1239
  .pp-reply-preview-content {
@@ -1208,7 +1244,7 @@ function styles(options) {
1208
1244
  .pp-reply-label {
1209
1245
  display: block;
1210
1246
  font-size: 12px;
1211
- color: #00a884;
1247
+ color: #7c5cff;
1212
1248
  font-weight: 500;
1213
1249
  margin-bottom: 2px;
1214
1250
  }
@@ -1252,7 +1288,7 @@ function styles(options) {
1252
1288
  /* Reply Quote in Message */
1253
1289
  .pp-reply-quote {
1254
1290
  background: ${isDark ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.05)"};
1255
- border-left: 3px solid #00a884;
1291
+ border-left: 3px solid #7c5cff;
1256
1292
  padding: 6px 10px;
1257
1293
  margin-bottom: 4px;
1258
1294
  border-radius: 0 6px 6px 0;
@@ -1264,7 +1300,7 @@ function styles(options) {
1264
1300
  .pp-reply-sender {
1265
1301
  display: block;
1266
1302
  font-weight: 500;
1267
- color: #00a884;
1303
+ color: #7c5cff;
1268
1304
  margin-bottom: 2px;
1269
1305
  font-size: 12px;
1270
1306
  }
@@ -1294,7 +1330,7 @@ function styles(options) {
1294
1330
  /* Reply quote in visitor message bubble needs higher contrast */
1295
1331
  .pp-message-visitor .pp-reply-quote {
1296
1332
  background: ${isDark ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.08)"};
1297
- border-left-color: ${isDark ? "rgba(255,255,255,0.5)" : "#00a884"};
1333
+ border-left-color: ${isDark ? "rgba(255,255,255,0.5)" : "#7c5cff"};
1298
1334
  }
1299
1335
 
1300
1336
  .pp-message-visitor .pp-reply-quote-clickable:hover {
@@ -1302,7 +1338,7 @@ function styles(options) {
1302
1338
  }
1303
1339
 
1304
1340
  .pp-message-visitor .pp-reply-sender {
1305
- color: ${isDark ? "rgba(255,255,255,0.9)" : "#00a884"};
1341
+ color: ${isDark ? "rgba(255,255,255,0.9)" : "#7c5cff"};
1306
1342
  }
1307
1343
 
1308
1344
  .pp-message-visitor .pp-reply-content {
@@ -1316,13 +1352,13 @@ function styles(options) {
1316
1352
 
1317
1353
  @keyframes pp-highlight-pulse {
1318
1354
  0% {
1319
- box-shadow: 0 0 0 0 rgba(0, 168, 132, 0.5);
1355
+ box-shadow: 0 0 0 0 rgba(124, 92, 255, 0.5);
1320
1356
  }
1321
1357
  30% {
1322
- box-shadow: 0 0 0 6px rgba(0, 168, 132, 0.25);
1358
+ box-shadow: 0 0 0 6px rgba(124, 92, 255, 0.25);
1323
1359
  }
1324
1360
  100% {
1325
- box-shadow: 0 0 0 0 rgba(0, 168, 132, 0);
1361
+ box-shadow: 0 0 0 0 rgba(124, 92, 255, 0);
1326
1362
  }
1327
1363
  }
1328
1364
 
@@ -1583,7 +1619,7 @@ function styles(options) {
1583
1619
  margin-top: 12px;
1584
1620
  border: none;
1585
1621
  border-radius: 8px;
1586
- background: #00a884;
1622
+ background: #7c5cff;
1587
1623
  color: white;
1588
1624
  font-size: 15px;
1589
1625
  font-weight: 500;
@@ -1592,7 +1628,7 @@ function styles(options) {
1592
1628
  }
1593
1629
 
1594
1630
  .pp-prechat-submit:hover:not(:disabled) {
1595
- background: #008f72;
1631
+ background: #6a4ee6;
1596
1632
  }
1597
1633
 
1598
1634
  .pp-prechat-submit:active:not(:disabled) {
@@ -2980,6 +3016,10 @@ var PocketPingClient = class {
2980
3016
  this.currentTrackedElements = [];
2981
3017
  this.inspectorMode = false;
2982
3018
  this.inspectorCleanup = null;
3019
+ this.connectedAt = 0;
3020
+ this.disconnectNotified = false;
3021
+ this.boundHandleUnload = null;
3022
+ this.boundHandleVisibilityChange = null;
2983
3023
  this.config = config;
2984
3024
  }
2985
3025
  // ─────────────────────────────────────────────────────────────────
@@ -3053,6 +3093,9 @@ var PocketPingClient = class {
3053
3093
  welcomeMessage: this.config.welcomeMessage
3054
3094
  });
3055
3095
  this.storeSessionId(response.sessionId);
3096
+ this.connectedAt = Date.now();
3097
+ this.disconnectNotified = false;
3098
+ this.setupUnloadListeners();
3056
3099
  this.connectRealtime();
3057
3100
  if (response.inspectorMode) {
3058
3101
  this.enableInspectorMode();
@@ -3064,6 +3107,8 @@ var PocketPingClient = class {
3064
3107
  return this.session;
3065
3108
  }
3066
3109
  disconnect() {
3110
+ this.notifyDisconnect();
3111
+ this.cleanupUnloadListeners();
3067
3112
  if (this.ws) {
3068
3113
  this.ws.onclose = null;
3069
3114
  this.ws.onmessage = null;
@@ -4267,6 +4312,86 @@ var PocketPingClient = class {
4267
4312
  this.pollingFailures = 0;
4268
4313
  }
4269
4314
  // ─────────────────────────────────────────────────────────────────
4315
+ // Visitor Disconnect Notification
4316
+ // ─────────────────────────────────────────────────────────────────
4317
+ setupUnloadListeners() {
4318
+ this.boundHandleUnload = () => this.notifyDisconnect();
4319
+ window.addEventListener("beforeunload", this.boundHandleUnload);
4320
+ window.addEventListener("pagehide", this.boundHandleUnload);
4321
+ this.boundHandleVisibilityChange = () => {
4322
+ if (document.visibilityState === "hidden") {
4323
+ this.sendVisibilityPing("hidden");
4324
+ } else if (document.visibilityState === "visible") {
4325
+ this.sendVisibilityPing("visible");
4326
+ }
4327
+ };
4328
+ document.addEventListener("visibilitychange", this.boundHandleVisibilityChange);
4329
+ }
4330
+ cleanupUnloadListeners() {
4331
+ if (this.boundHandleUnload) {
4332
+ window.removeEventListener("beforeunload", this.boundHandleUnload);
4333
+ window.removeEventListener("pagehide", this.boundHandleUnload);
4334
+ this.boundHandleUnload = null;
4335
+ }
4336
+ if (this.boundHandleVisibilityChange) {
4337
+ document.removeEventListener("visibilitychange", this.boundHandleVisibilityChange);
4338
+ this.boundHandleVisibilityChange = null;
4339
+ }
4340
+ }
4341
+ /**
4342
+ * Notify backend that visitor is leaving
4343
+ * Uses sendBeacon for reliability on page unload
4344
+ */
4345
+ notifyDisconnect() {
4346
+ if (this.disconnectNotified || !this.session) {
4347
+ return;
4348
+ }
4349
+ this.disconnectNotified = true;
4350
+ const sessionDuration = this.connectedAt ? Math.round((Date.now() - this.connectedAt) / 1e3) : 0;
4351
+ const url = this.config.endpoint.replace(/\/$/, "") + "/disconnect";
4352
+ const data = JSON.stringify({
4353
+ sessionId: this.session.sessionId,
4354
+ duration: sessionDuration,
4355
+ reason: "page_unload"
4356
+ });
4357
+ if (navigator.sendBeacon) {
4358
+ const blob = new Blob([data], { type: "application/json" });
4359
+ navigator.sendBeacon(url, blob);
4360
+ } else {
4361
+ try {
4362
+ const xhr = new XMLHttpRequest();
4363
+ xhr.open("POST", url, false);
4364
+ xhr.setRequestHeader("Content-Type", "application/json");
4365
+ xhr.send(data);
4366
+ } catch {
4367
+ }
4368
+ }
4369
+ }
4370
+ /**
4371
+ * Send visibility ping to backend (for inactivity tracking)
4372
+ */
4373
+ sendVisibilityPing(state) {
4374
+ if (!this.session) return;
4375
+ const url = this.config.endpoint.replace(/\/$/, "") + "/visibility";
4376
+ const data = JSON.stringify({
4377
+ sessionId: this.session.sessionId,
4378
+ state,
4379
+ timestamp: Date.now()
4380
+ });
4381
+ if (state === "hidden" && navigator.sendBeacon) {
4382
+ const blob = new Blob([data], { type: "application/json" });
4383
+ navigator.sendBeacon(url, blob);
4384
+ } else {
4385
+ fetch(url, {
4386
+ method: "POST",
4387
+ headers: { "Content-Type": "application/json" },
4388
+ body: data,
4389
+ keepalive: true
4390
+ }).catch(() => {
4391
+ });
4392
+ }
4393
+ }
4394
+ // ─────────────────────────────────────────────────────────────────
4270
4395
  // HTTP
4271
4396
  // ─────────────────────────────────────────────────────────────────
4272
4397
  async fetch(path, options) {
package/dist/index.d.cts CHANGED
@@ -6,6 +6,28 @@ interface ThemeColor {
6
6
  light: string;
7
7
  dark: string;
8
8
  }
9
+ /**
10
+ * Gradient color value
11
+ * Creates a linear gradient between two colors
12
+ * @example { from: '#36e3ff', to: '#7c5cff', direction: 'to right' }
13
+ */
14
+ interface GradientColor {
15
+ from: string;
16
+ to: string;
17
+ /** CSS gradient direction (default: 'to right') */
18
+ direction?: 'to right' | 'to left' | 'to bottom' | 'to top' | string;
19
+ }
20
+ /**
21
+ * Color value that can be a solid color, gradient, or theme-aware
22
+ */
23
+ type ColorValue = string | GradientColor | ThemeColor | ThemeGradientColor;
24
+ /**
25
+ * Theme-aware gradient - different gradients for light/dark themes
26
+ */
27
+ interface ThemeGradientColor {
28
+ light: string | GradientColor;
29
+ dark: string | GradientColor;
30
+ }
9
31
  interface PocketPingConfig {
10
32
  /** Your backend endpoint for self-hosted (e.g., "https://yoursite.com/pocketping") */
11
33
  endpoint?: string;
@@ -32,11 +54,14 @@ interface PocketPingConfig {
32
54
  /** Text color on primary background (defaults to white) */
33
55
  primaryTextColor?: string;
34
56
  /**
35
- * Header background color
36
- * Can be a string (same for both themes) or object with light/dark values
37
- * @example "#008069" or { light: "#008069", dark: "#202c33" }
57
+ * Header background color or gradient
58
+ * Can be a solid color, gradient, or theme-aware
59
+ * @example "#7c5cff"
60
+ * @example { from: '#36e3ff', to: '#7c5cff' }
61
+ * @example { light: "#7c5cff", dark: "#202c33" }
62
+ * @example { light: { from: '#36e3ff', to: '#7c5cff' }, dark: "#202c33" }
38
63
  */
39
- headerColor?: string | ThemeColor;
64
+ headerColor?: ColorValue;
40
65
  /**
41
66
  * Footer/input area background color
42
67
  * Can be a string (same for both themes) or object with light/dark values
@@ -53,11 +78,13 @@ interface PocketPingConfig {
53
78
  */
54
79
  chatBackground?: 'whatsapp' | 'dots' | 'plain' | string | ThemeColor;
55
80
  /**
56
- * Toggle button background color
57
- * Can be a string (same for both themes) or object with light/dark values
58
- * @example "#25d366" or { light: "#25d366", dark: "#00a884" }
81
+ * Toggle button background color or gradient
82
+ * Can be a solid color, gradient, or theme-aware
83
+ * @example "#7c5cff"
84
+ * @example { from: '#36e3ff', to: '#7c5cff' }
85
+ * @example { light: "#7c5cff", dark: "#7c5cff" }
59
86
  */
60
- toggleColor?: string | ThemeColor;
87
+ toggleColor?: ColorValue;
61
88
  /** Widget position */
62
89
  position?: 'bottom-right' | 'bottom-left';
63
90
  /** Distance from edge in pixels (default: 20) */
@@ -273,6 +300,10 @@ declare class PocketPingClient {
273
300
  private currentTrackedElements;
274
301
  private inspectorMode;
275
302
  private inspectorCleanup;
303
+ private connectedAt;
304
+ private disconnectNotified;
305
+ private boundHandleUnload;
306
+ private boundHandleVisibilityChange;
276
307
  constructor(config: ResolvedPocketPingConfig);
277
308
  connect(): Promise<Session>;
278
309
  disconnect(): void;
@@ -424,6 +455,17 @@ declare class PocketPingClient {
424
455
  private scheduleReconnect;
425
456
  private startPolling;
426
457
  private stopPolling;
458
+ private setupUnloadListeners;
459
+ private cleanupUnloadListeners;
460
+ /**
461
+ * Notify backend that visitor is leaving
462
+ * Uses sendBeacon for reliability on page unload
463
+ */
464
+ private notifyDisconnect;
465
+ /**
466
+ * Send visibility ping to backend (for inactivity tracking)
467
+ */
468
+ private sendVisibilityPing;
427
469
  private fetch;
428
470
  private checkVersionHeaders;
429
471
  private getOrCreateVisitorId;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,28 @@ interface ThemeColor {
6
6
  light: string;
7
7
  dark: string;
8
8
  }
9
+ /**
10
+ * Gradient color value
11
+ * Creates a linear gradient between two colors
12
+ * @example { from: '#36e3ff', to: '#7c5cff', direction: 'to right' }
13
+ */
14
+ interface GradientColor {
15
+ from: string;
16
+ to: string;
17
+ /** CSS gradient direction (default: 'to right') */
18
+ direction?: 'to right' | 'to left' | 'to bottom' | 'to top' | string;
19
+ }
20
+ /**
21
+ * Color value that can be a solid color, gradient, or theme-aware
22
+ */
23
+ type ColorValue = string | GradientColor | ThemeColor | ThemeGradientColor;
24
+ /**
25
+ * Theme-aware gradient - different gradients for light/dark themes
26
+ */
27
+ interface ThemeGradientColor {
28
+ light: string | GradientColor;
29
+ dark: string | GradientColor;
30
+ }
9
31
  interface PocketPingConfig {
10
32
  /** Your backend endpoint for self-hosted (e.g., "https://yoursite.com/pocketping") */
11
33
  endpoint?: string;
@@ -32,11 +54,14 @@ interface PocketPingConfig {
32
54
  /** Text color on primary background (defaults to white) */
33
55
  primaryTextColor?: string;
34
56
  /**
35
- * Header background color
36
- * Can be a string (same for both themes) or object with light/dark values
37
- * @example "#008069" or { light: "#008069", dark: "#202c33" }
57
+ * Header background color or gradient
58
+ * Can be a solid color, gradient, or theme-aware
59
+ * @example "#7c5cff"
60
+ * @example { from: '#36e3ff', to: '#7c5cff' }
61
+ * @example { light: "#7c5cff", dark: "#202c33" }
62
+ * @example { light: { from: '#36e3ff', to: '#7c5cff' }, dark: "#202c33" }
38
63
  */
39
- headerColor?: string | ThemeColor;
64
+ headerColor?: ColorValue;
40
65
  /**
41
66
  * Footer/input area background color
42
67
  * Can be a string (same for both themes) or object with light/dark values
@@ -53,11 +78,13 @@ interface PocketPingConfig {
53
78
  */
54
79
  chatBackground?: 'whatsapp' | 'dots' | 'plain' | string | ThemeColor;
55
80
  /**
56
- * Toggle button background color
57
- * Can be a string (same for both themes) or object with light/dark values
58
- * @example "#25d366" or { light: "#25d366", dark: "#00a884" }
81
+ * Toggle button background color or gradient
82
+ * Can be a solid color, gradient, or theme-aware
83
+ * @example "#7c5cff"
84
+ * @example { from: '#36e3ff', to: '#7c5cff' }
85
+ * @example { light: "#7c5cff", dark: "#7c5cff" }
59
86
  */
60
- toggleColor?: string | ThemeColor;
87
+ toggleColor?: ColorValue;
61
88
  /** Widget position */
62
89
  position?: 'bottom-right' | 'bottom-left';
63
90
  /** Distance from edge in pixels (default: 20) */
@@ -273,6 +300,10 @@ declare class PocketPingClient {
273
300
  private currentTrackedElements;
274
301
  private inspectorMode;
275
302
  private inspectorCleanup;
303
+ private connectedAt;
304
+ private disconnectNotified;
305
+ private boundHandleUnload;
306
+ private boundHandleVisibilityChange;
276
307
  constructor(config: ResolvedPocketPingConfig);
277
308
  connect(): Promise<Session>;
278
309
  disconnect(): void;
@@ -424,6 +455,17 @@ declare class PocketPingClient {
424
455
  private scheduleReconnect;
425
456
  private startPolling;
426
457
  private stopPolling;
458
+ private setupUnloadListeners;
459
+ private cleanupUnloadListeners;
460
+ /**
461
+ * Notify backend that visitor is leaving
462
+ * Uses sendBeacon for reliability on page unload
463
+ */
464
+ private notifyDisconnect;
465
+ /**
466
+ * Send visibility ping to backend (for inactivity tracking)
467
+ */
468
+ private sendVisibilityPing;
427
469
  private fetch;
428
470
  private checkVersionHeaders;
429
471
  private getOrCreateVisitorId;