@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/dist/index.js CHANGED
@@ -6,23 +6,59 @@ import { Fragment as Fragment2 } from "preact";
6
6
  import { useState as useState2, useEffect as useEffect2, useRef as useRef2, useCallback } from "preact/hooks";
7
7
 
8
8
  // src/components/styles.ts
9
+ var DEFAULT_GRADIENT = { from: "#36e3ff", to: "#7c5cff", direction: "to right" };
10
+ function isGradient(value) {
11
+ return typeof value === "object" && value !== null && "from" in value && "to" in value;
12
+ }
13
+ function toCssColor(value) {
14
+ if (isGradient(value)) {
15
+ const direction = value.direction || "to right";
16
+ return `linear-gradient(${direction}, ${value.from}, ${value.to})`;
17
+ }
18
+ return value;
19
+ }
20
+ function getBaseColor(value) {
21
+ if (isGradient(value)) {
22
+ return value.to;
23
+ }
24
+ return value;
25
+ }
9
26
  function resolveThemeColor(color, isDark, defaultLight, defaultDark) {
10
27
  if (!color) {
11
- return isDark ? defaultDark : defaultLight;
28
+ return toCssColor(isDark ? defaultDark : defaultLight);
12
29
  }
13
30
  if (typeof color === "string") {
14
31
  return color;
15
32
  }
16
- return isDark ? color.dark : color.light;
33
+ if (isGradient(color)) {
34
+ return toCssColor(color);
35
+ }
36
+ const resolved = isDark ? color.dark : color.light;
37
+ return toCssColor(resolved);
38
+ }
39
+ function resolveBaseColor(color, isDark, defaultLight, defaultDark) {
40
+ if (!color) {
41
+ return getBaseColor(isDark ? defaultDark : defaultLight);
42
+ }
43
+ if (typeof color === "string") {
44
+ return color;
45
+ }
46
+ if (isGradient(color)) {
47
+ return getBaseColor(color);
48
+ }
49
+ const resolved = isDark ? color.dark : color.light;
50
+ return getBaseColor(resolved);
17
51
  }
18
52
  function styles(options) {
19
53
  const { primaryColor, theme, headerColor, footerColor, chatBackground, toggleColor } = options;
20
54
  const isDark = theme === "dark";
21
- const resolvedHeaderColor = resolveThemeColor(headerColor, isDark, "#008069", "#202c33");
55
+ const resolvedHeaderColor = resolveThemeColor(headerColor, isDark, DEFAULT_GRADIENT, "#202c33");
22
56
  const resolvedFooterColor = resolveThemeColor(footerColor, isDark, "#f0f2f5", "#202c33");
23
- const resolvedToggleColor = resolveThemeColor(toggleColor, isDark, "#25d366", "#25d366");
24
- const resolvedToggleHoverColor = resolvedToggleColor !== "#25d366" ? adjustBrightness(resolvedToggleColor, -10) : "#22c55e";
25
- const resolvedSendButtonHoverColor = adjustBrightness(resolvedHeaderColor, -15);
57
+ const resolvedToggleColor = resolveThemeColor(toggleColor, isDark, DEFAULT_GRADIENT, DEFAULT_GRADIENT);
58
+ const headerBaseColor = resolveBaseColor(headerColor, isDark, DEFAULT_GRADIENT, "#202c33");
59
+ const toggleBaseColor = resolveBaseColor(toggleColor, isDark, DEFAULT_GRADIENT, DEFAULT_GRADIENT);
60
+ const resolvedToggleHoverColor = adjustBrightness(toggleBaseColor, -15);
61
+ const resolvedSendButtonHoverColor = adjustBrightness(headerBaseColor, -15);
26
62
  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")`;
27
63
  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")`;
28
64
  const resolvedChatBg = resolveThemeColor(chatBackground, isDark, "whatsapp", "whatsapp");
@@ -121,7 +157,7 @@ function styles(options) {
121
157
  right: 4px;
122
158
  width: 12px;
123
159
  height: 12px;
124
- background: #22c55e;
160
+ background: #ff5fd4;
125
161
  border-radius: 50%;
126
162
  border: 2px solid white;
127
163
  }
@@ -277,8 +313,8 @@ function styles(options) {
277
313
  }
278
314
 
279
315
  .pp-status-dot.pp-online {
280
- background: #25d366;
281
- box-shadow: 0 0 0 2px rgba(37, 211, 102, 0.3);
316
+ background: #ff5fd4;
317
+ box-shadow: 0 0 0 2px rgba(255, 95, 212, 0.3);
282
318
  }
283
319
 
284
320
  .pp-close-btn {
@@ -982,7 +1018,7 @@ function styles(options) {
982
1018
  justify-content: center;
983
1019
  gap: 16px;
984
1020
  z-index: 100;
985
- border: 3px dashed #00a884;
1021
+ border: 3px dashed #7c5cff;
986
1022
  border-radius: 12px;
987
1023
  margin: 4px;
988
1024
  pointer-events: none;
@@ -991,7 +1027,7 @@ function styles(options) {
991
1027
  .pp-drop-icon svg {
992
1028
  width: 56px;
993
1029
  height: 56px;
994
- color: #00a884;
1030
+ color: #7c5cff;
995
1031
  }
996
1032
 
997
1033
  .pp-drop-text {
@@ -1132,7 +1168,7 @@ function styles(options) {
1132
1168
  padding: 10px 20px;
1133
1169
  border: none;
1134
1170
  border-radius: 8px;
1135
- background: #00a884;
1171
+ background: #7c5cff;
1136
1172
  color: white;
1137
1173
  font-size: 14px;
1138
1174
  font-weight: 500;
@@ -1141,7 +1177,7 @@ function styles(options) {
1141
1177
  }
1142
1178
 
1143
1179
  .pp-edit-save:hover:not(:disabled) {
1144
- background: #008f72;
1180
+ background: #6a4ee6;
1145
1181
  }
1146
1182
 
1147
1183
  .pp-edit-save:disabled {
@@ -1156,7 +1192,7 @@ function styles(options) {
1156
1192
  gap: 10px;
1157
1193
  padding: 8px 12px;
1158
1194
  background: ${resolvedFooterColor};
1159
- border-left: 4px solid #00a884;
1195
+ border-left: 4px solid #7c5cff;
1160
1196
  }
1161
1197
 
1162
1198
  .pp-reply-preview-content {
@@ -1167,7 +1203,7 @@ function styles(options) {
1167
1203
  .pp-reply-label {
1168
1204
  display: block;
1169
1205
  font-size: 12px;
1170
- color: #00a884;
1206
+ color: #7c5cff;
1171
1207
  font-weight: 500;
1172
1208
  margin-bottom: 2px;
1173
1209
  }
@@ -1211,7 +1247,7 @@ function styles(options) {
1211
1247
  /* Reply Quote in Message */
1212
1248
  .pp-reply-quote {
1213
1249
  background: ${isDark ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.05)"};
1214
- border-left: 3px solid #00a884;
1250
+ border-left: 3px solid #7c5cff;
1215
1251
  padding: 6px 10px;
1216
1252
  margin-bottom: 4px;
1217
1253
  border-radius: 0 6px 6px 0;
@@ -1223,7 +1259,7 @@ function styles(options) {
1223
1259
  .pp-reply-sender {
1224
1260
  display: block;
1225
1261
  font-weight: 500;
1226
- color: #00a884;
1262
+ color: #7c5cff;
1227
1263
  margin-bottom: 2px;
1228
1264
  font-size: 12px;
1229
1265
  }
@@ -1253,7 +1289,7 @@ function styles(options) {
1253
1289
  /* Reply quote in visitor message bubble needs higher contrast */
1254
1290
  .pp-message-visitor .pp-reply-quote {
1255
1291
  background: ${isDark ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.08)"};
1256
- border-left-color: ${isDark ? "rgba(255,255,255,0.5)" : "#00a884"};
1292
+ border-left-color: ${isDark ? "rgba(255,255,255,0.5)" : "#7c5cff"};
1257
1293
  }
1258
1294
 
1259
1295
  .pp-message-visitor .pp-reply-quote-clickable:hover {
@@ -1261,7 +1297,7 @@ function styles(options) {
1261
1297
  }
1262
1298
 
1263
1299
  .pp-message-visitor .pp-reply-sender {
1264
- color: ${isDark ? "rgba(255,255,255,0.9)" : "#00a884"};
1300
+ color: ${isDark ? "rgba(255,255,255,0.9)" : "#7c5cff"};
1265
1301
  }
1266
1302
 
1267
1303
  .pp-message-visitor .pp-reply-content {
@@ -1275,13 +1311,13 @@ function styles(options) {
1275
1311
 
1276
1312
  @keyframes pp-highlight-pulse {
1277
1313
  0% {
1278
- box-shadow: 0 0 0 0 rgba(0, 168, 132, 0.5);
1314
+ box-shadow: 0 0 0 0 rgba(124, 92, 255, 0.5);
1279
1315
  }
1280
1316
  30% {
1281
- box-shadow: 0 0 0 6px rgba(0, 168, 132, 0.25);
1317
+ box-shadow: 0 0 0 6px rgba(124, 92, 255, 0.25);
1282
1318
  }
1283
1319
  100% {
1284
- box-shadow: 0 0 0 0 rgba(0, 168, 132, 0);
1320
+ box-shadow: 0 0 0 0 rgba(124, 92, 255, 0);
1285
1321
  }
1286
1322
  }
1287
1323
 
@@ -1542,7 +1578,7 @@ function styles(options) {
1542
1578
  margin-top: 12px;
1543
1579
  border: none;
1544
1580
  border-radius: 8px;
1545
- background: #00a884;
1581
+ background: #7c5cff;
1546
1582
  color: white;
1547
1583
  font-size: 15px;
1548
1584
  font-weight: 500;
@@ -1551,7 +1587,7 @@ function styles(options) {
1551
1587
  }
1552
1588
 
1553
1589
  .pp-prechat-submit:hover:not(:disabled) {
1554
- background: #008f72;
1590
+ background: #6a4ee6;
1555
1591
  }
1556
1592
 
1557
1593
  .pp-prechat-submit:active:not(:disabled) {
@@ -2939,6 +2975,10 @@ var PocketPingClient = class {
2939
2975
  this.currentTrackedElements = [];
2940
2976
  this.inspectorMode = false;
2941
2977
  this.inspectorCleanup = null;
2978
+ this.connectedAt = 0;
2979
+ this.disconnectNotified = false;
2980
+ this.boundHandleUnload = null;
2981
+ this.boundHandleVisibilityChange = null;
2942
2982
  this.config = config;
2943
2983
  }
2944
2984
  // ─────────────────────────────────────────────────────────────────
@@ -3012,6 +3052,9 @@ var PocketPingClient = class {
3012
3052
  welcomeMessage: this.config.welcomeMessage
3013
3053
  });
3014
3054
  this.storeSessionId(response.sessionId);
3055
+ this.connectedAt = Date.now();
3056
+ this.disconnectNotified = false;
3057
+ this.setupUnloadListeners();
3015
3058
  this.connectRealtime();
3016
3059
  if (response.inspectorMode) {
3017
3060
  this.enableInspectorMode();
@@ -3023,6 +3066,8 @@ var PocketPingClient = class {
3023
3066
  return this.session;
3024
3067
  }
3025
3068
  disconnect() {
3069
+ this.notifyDisconnect();
3070
+ this.cleanupUnloadListeners();
3026
3071
  if (this.ws) {
3027
3072
  this.ws.onclose = null;
3028
3073
  this.ws.onmessage = null;
@@ -4226,6 +4271,86 @@ var PocketPingClient = class {
4226
4271
  this.pollingFailures = 0;
4227
4272
  }
4228
4273
  // ─────────────────────────────────────────────────────────────────
4274
+ // Visitor Disconnect Notification
4275
+ // ─────────────────────────────────────────────────────────────────
4276
+ setupUnloadListeners() {
4277
+ this.boundHandleUnload = () => this.notifyDisconnect();
4278
+ window.addEventListener("beforeunload", this.boundHandleUnload);
4279
+ window.addEventListener("pagehide", this.boundHandleUnload);
4280
+ this.boundHandleVisibilityChange = () => {
4281
+ if (document.visibilityState === "hidden") {
4282
+ this.sendVisibilityPing("hidden");
4283
+ } else if (document.visibilityState === "visible") {
4284
+ this.sendVisibilityPing("visible");
4285
+ }
4286
+ };
4287
+ document.addEventListener("visibilitychange", this.boundHandleVisibilityChange);
4288
+ }
4289
+ cleanupUnloadListeners() {
4290
+ if (this.boundHandleUnload) {
4291
+ window.removeEventListener("beforeunload", this.boundHandleUnload);
4292
+ window.removeEventListener("pagehide", this.boundHandleUnload);
4293
+ this.boundHandleUnload = null;
4294
+ }
4295
+ if (this.boundHandleVisibilityChange) {
4296
+ document.removeEventListener("visibilitychange", this.boundHandleVisibilityChange);
4297
+ this.boundHandleVisibilityChange = null;
4298
+ }
4299
+ }
4300
+ /**
4301
+ * Notify backend that visitor is leaving
4302
+ * Uses sendBeacon for reliability on page unload
4303
+ */
4304
+ notifyDisconnect() {
4305
+ if (this.disconnectNotified || !this.session) {
4306
+ return;
4307
+ }
4308
+ this.disconnectNotified = true;
4309
+ const sessionDuration = this.connectedAt ? Math.round((Date.now() - this.connectedAt) / 1e3) : 0;
4310
+ const url = this.config.endpoint.replace(/\/$/, "") + "/disconnect";
4311
+ const data = JSON.stringify({
4312
+ sessionId: this.session.sessionId,
4313
+ duration: sessionDuration,
4314
+ reason: "page_unload"
4315
+ });
4316
+ if (navigator.sendBeacon) {
4317
+ const blob = new Blob([data], { type: "application/json" });
4318
+ navigator.sendBeacon(url, blob);
4319
+ } else {
4320
+ try {
4321
+ const xhr = new XMLHttpRequest();
4322
+ xhr.open("POST", url, false);
4323
+ xhr.setRequestHeader("Content-Type", "application/json");
4324
+ xhr.send(data);
4325
+ } catch {
4326
+ }
4327
+ }
4328
+ }
4329
+ /**
4330
+ * Send visibility ping to backend (for inactivity tracking)
4331
+ */
4332
+ sendVisibilityPing(state) {
4333
+ if (!this.session) return;
4334
+ const url = this.config.endpoint.replace(/\/$/, "") + "/visibility";
4335
+ const data = JSON.stringify({
4336
+ sessionId: this.session.sessionId,
4337
+ state,
4338
+ timestamp: Date.now()
4339
+ });
4340
+ if (state === "hidden" && navigator.sendBeacon) {
4341
+ const blob = new Blob([data], { type: "application/json" });
4342
+ navigator.sendBeacon(url, blob);
4343
+ } else {
4344
+ fetch(url, {
4345
+ method: "POST",
4346
+ headers: { "Content-Type": "application/json" },
4347
+ body: data,
4348
+ keepalive: true
4349
+ }).catch(() => {
4350
+ });
4351
+ }
4352
+ }
4353
+ // ─────────────────────────────────────────────────────────────────
4229
4354
  // HTTP
4230
4355
  // ─────────────────────────────────────────────────────────────────
4231
4356
  async fetch(path, options) {