@pocketping/widget 2.3.0 → 2.5.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.cjs CHANGED
@@ -705,7 +705,7 @@ function styles(options) {
705
705
 
706
706
  .pp-input-form {
707
707
  display: flex;
708
- padding: 8px 10px;
708
+ padding: 12px 10px 6px;
709
709
  gap: 8px;
710
710
  background: ${resolvedFooterColor};
711
711
  align-items: flex-end;
@@ -2991,9 +2991,23 @@ function AttachmentDisplay({ attachment }) {
2991
2991
  }
2992
2992
 
2993
2993
  // src/version.ts
2994
- var VERSION = "0.3.7";
2994
+ var VERSION = "0.3.8";
2995
2995
 
2996
2996
  // src/client.ts
2997
+ function detectBotSignals() {
2998
+ return {
2999
+ // navigator.webdriver is true in Puppeteer/Selenium/Playwright
3000
+ webdriver: !!navigator.webdriver,
3001
+ // Headless browsers typically have no plugins
3002
+ plugins: navigator.plugins.length === 0,
3003
+ // Headless browsers may have empty languages
3004
+ languages: navigator.languages.length === 0,
3005
+ // HeadlessChrome doesn't have window.chrome
3006
+ chrome: /Chrome/.test(navigator.userAgent) && typeof window.chrome === "undefined",
3007
+ // Notification permission in headless is often 'denied' by default
3008
+ permissions: typeof Notification !== "undefined" && Notification.permission === "denied"
3009
+ };
3010
+ }
2997
3011
  var PocketPingClient = class {
2998
3012
  constructor(config) {
2999
3013
  this.session = null;
@@ -3044,7 +3058,8 @@ var PocketPingClient = class {
3044
3058
  userAgent: navigator.userAgent,
3045
3059
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
3046
3060
  language: navigator.language,
3047
- screenResolution: `${window.screen.width}x${window.screen.height}`
3061
+ screenResolution: `${window.screen.width}x${window.screen.height}`,
3062
+ botSignals: detectBotSignals()
3048
3063
  },
3049
3064
  // Include stored identity if available
3050
3065
  identity: storedIdentity || void 0
@@ -4009,6 +4024,14 @@ var PocketPingClient = class {
4009
4024
  console.error("[PocketPing] Failed to parse SSE message:", err);
4010
4025
  }
4011
4026
  });
4027
+ this.sse.addEventListener("screenshot_request", (event) => {
4028
+ try {
4029
+ const data = JSON.parse(event.data);
4030
+ this.handleRealtimeEvent(data);
4031
+ } catch (err) {
4032
+ console.error("[PocketPing] Failed to parse SSE screenshot_request:", err);
4033
+ }
4034
+ });
4012
4035
  this.sse.addEventListener("connected", () => {
4013
4036
  });
4014
4037
  this.sse.onerror = () => {
@@ -4191,6 +4214,10 @@ var PocketPingClient = class {
4191
4214
  this.emit("configUpdate", configData);
4192
4215
  }
4193
4216
  break;
4217
+ case "screenshot_request":
4218
+ const screenshotData = event.data;
4219
+ this.handleScreenshotRequest(screenshotData);
4220
+ break;
4194
4221
  }
4195
4222
  }
4196
4223
  // ─────────────────────────────────────────────────────────────────
@@ -4237,6 +4264,7 @@ var PocketPingClient = class {
4237
4264
  try {
4238
4265
  const lastEventTimestamp = this.getLastEventTimestamp();
4239
4266
  const newMessages = await this.fetchMessages(lastEventTimestamp ?? void 0);
4267
+ await this.pollScreenshotRequests();
4240
4268
  this.pollingFailures = 0;
4241
4269
  for (const message of newMessages) {
4242
4270
  const existingIndex = this.session.messages.findIndex((m) => m.id === message.id);
@@ -4487,6 +4515,135 @@ var PocketPingClient = class {
4487
4515
  generateId() {
4488
4516
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
4489
4517
  }
4518
+ // ─────────────────────────────────────────────────────────────────
4519
+ // Screenshot Request Polling (for polling fallback mode)
4520
+ // ─────────────────────────────────────────────────────────────────
4521
+ async pollScreenshotRequests() {
4522
+ if (!this.session) return;
4523
+ try {
4524
+ const response = await this.fetch(`/screenshot/requests?sessionId=${this.session.sessionId}`, { method: "GET" });
4525
+ for (const request of response.requests || []) {
4526
+ this.handleScreenshotRequest(request);
4527
+ }
4528
+ } catch (err) {
4529
+ }
4530
+ }
4531
+ // ─────────────────────────────────────────────────────────────────
4532
+ // Screenshot Capture
4533
+ // ─────────────────────────────────────────────────────────────────
4534
+ /**
4535
+ * Handle screenshot request from operator
4536
+ * @param data.silent - If true, screenshot won't appear in widget chat (only sent to bridges)
4537
+ */
4538
+ async handleScreenshotRequest(data) {
4539
+ console.log("[PocketPing] Screenshot requested by", data.requestedBy, "via", data.requestedFrom, data.silent ? "(silent)" : "");
4540
+ try {
4541
+ const blob = await this.captureScreenshot();
4542
+ const initResponse = await this.fetch("/screenshot/upload", {
4543
+ method: "POST",
4544
+ body: JSON.stringify({
4545
+ sessionId: this.session?.sessionId,
4546
+ requestId: data.requestId
4547
+ })
4548
+ });
4549
+ const uploadResponse = await fetch(initResponse.uploadUrl, {
4550
+ method: "PUT",
4551
+ body: blob,
4552
+ headers: {
4553
+ "Content-Type": "image/png"
4554
+ }
4555
+ });
4556
+ if (!uploadResponse.ok) {
4557
+ throw new Error(`Upload failed: ${uploadResponse.status}`);
4558
+ }
4559
+ await this.fetch("/screenshot/upload/complete", {
4560
+ method: "POST",
4561
+ body: JSON.stringify({
4562
+ sessionId: this.session?.sessionId,
4563
+ requestId: data.requestId,
4564
+ attachmentId: initResponse.attachmentId,
4565
+ size: blob.size
4566
+ })
4567
+ });
4568
+ console.log("[PocketPing] Screenshot captured and uploaded successfully");
4569
+ } catch (error) {
4570
+ console.error("[PocketPing] Screenshot capture failed:", error);
4571
+ }
4572
+ }
4573
+ /**
4574
+ * Capture screenshot of the viewport using html2canvas
4575
+ */
4576
+ async captureScreenshot() {
4577
+ const html2canvas = await this.loadHtml2Canvas();
4578
+ const widgetContainer = document.getElementById("pocketping-widget");
4579
+ const widgetToggle = document.getElementById("pocketping-toggle");
4580
+ let widgetWasVisible = false;
4581
+ let toggleWasVisible = false;
4582
+ if (widgetContainer) {
4583
+ widgetWasVisible = widgetContainer.style.display !== "none";
4584
+ widgetContainer.style.display = "none";
4585
+ }
4586
+ if (widgetToggle) {
4587
+ toggleWasVisible = widgetToggle.style.display !== "none";
4588
+ widgetToggle.style.display = "none";
4589
+ }
4590
+ try {
4591
+ const canvas = await html2canvas(document.documentElement, {
4592
+ width: window.innerWidth,
4593
+ height: window.innerHeight,
4594
+ x: window.scrollX,
4595
+ y: window.scrollY,
4596
+ windowWidth: window.innerWidth,
4597
+ windowHeight: window.innerHeight,
4598
+ logging: false,
4599
+ useCORS: true,
4600
+ allowTaint: true
4601
+ });
4602
+ return new Promise((resolve, reject) => {
4603
+ canvas.toBlob(
4604
+ (blob) => {
4605
+ if (blob) {
4606
+ resolve(blob);
4607
+ } else {
4608
+ reject(new Error("Failed to create blob from canvas"));
4609
+ }
4610
+ },
4611
+ "image/png",
4612
+ 0.9
4613
+ );
4614
+ });
4615
+ } finally {
4616
+ if (widgetContainer && widgetWasVisible) {
4617
+ widgetContainer.style.display = "";
4618
+ }
4619
+ if (widgetToggle && toggleWasVisible) {
4620
+ widgetToggle.style.display = "";
4621
+ }
4622
+ }
4623
+ }
4624
+ /**
4625
+ * Dynamically load html2canvas from CDN
4626
+ */
4627
+ async loadHtml2Canvas() {
4628
+ const win = window;
4629
+ if (typeof win.html2canvas !== "undefined") {
4630
+ return win.html2canvas;
4631
+ }
4632
+ return new Promise((resolve, reject) => {
4633
+ const script = document.createElement("script");
4634
+ script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
4635
+ script.onload = () => {
4636
+ const w = window;
4637
+ if (typeof w.html2canvas !== "undefined") {
4638
+ resolve(w.html2canvas);
4639
+ } else {
4640
+ reject(new Error("html2canvas failed to load"));
4641
+ }
4642
+ };
4643
+ script.onerror = () => reject(new Error("Failed to load html2canvas from CDN"));
4644
+ document.head.appendChild(script);
4645
+ });
4646
+ }
4490
4647
  };
4491
4648
 
4492
4649
  // src/index.ts
package/dist/index.d.cts CHANGED
@@ -475,6 +475,20 @@ declare class PocketPingClient {
475
475
  private storeIdentity;
476
476
  private clearIdentity;
477
477
  private generateId;
478
+ private pollScreenshotRequests;
479
+ /**
480
+ * Handle screenshot request from operator
481
+ * @param data.silent - If true, screenshot won't appear in widget chat (only sent to bridges)
482
+ */
483
+ private handleScreenshotRequest;
484
+ /**
485
+ * Capture screenshot of the viewport using html2canvas
486
+ */
487
+ private captureScreenshot;
488
+ /**
489
+ * Dynamically load html2canvas from CDN
490
+ */
491
+ private loadHtml2Canvas;
478
492
  }
479
493
 
480
494
  declare function init(config: PocketPingConfig): PocketPingClient;
package/dist/index.d.ts CHANGED
@@ -475,6 +475,20 @@ declare class PocketPingClient {
475
475
  private storeIdentity;
476
476
  private clearIdentity;
477
477
  private generateId;
478
+ private pollScreenshotRequests;
479
+ /**
480
+ * Handle screenshot request from operator
481
+ * @param data.silent - If true, screenshot won't appear in widget chat (only sent to bridges)
482
+ */
483
+ private handleScreenshotRequest;
484
+ /**
485
+ * Capture screenshot of the viewport using html2canvas
486
+ */
487
+ private captureScreenshot;
488
+ /**
489
+ * Dynamically load html2canvas from CDN
490
+ */
491
+ private loadHtml2Canvas;
478
492
  }
479
493
 
480
494
  declare function init(config: PocketPingConfig): PocketPingClient;
package/dist/index.js CHANGED
@@ -664,7 +664,7 @@ function styles(options) {
664
664
 
665
665
  .pp-input-form {
666
666
  display: flex;
667
- padding: 8px 10px;
667
+ padding: 12px 10px 6px;
668
668
  gap: 8px;
669
669
  background: ${resolvedFooterColor};
670
670
  align-items: flex-end;
@@ -2950,9 +2950,23 @@ function AttachmentDisplay({ attachment }) {
2950
2950
  }
2951
2951
 
2952
2952
  // src/version.ts
2953
- var VERSION = "0.3.7";
2953
+ var VERSION = "0.3.8";
2954
2954
 
2955
2955
  // src/client.ts
2956
+ function detectBotSignals() {
2957
+ return {
2958
+ // navigator.webdriver is true in Puppeteer/Selenium/Playwright
2959
+ webdriver: !!navigator.webdriver,
2960
+ // Headless browsers typically have no plugins
2961
+ plugins: navigator.plugins.length === 0,
2962
+ // Headless browsers may have empty languages
2963
+ languages: navigator.languages.length === 0,
2964
+ // HeadlessChrome doesn't have window.chrome
2965
+ chrome: /Chrome/.test(navigator.userAgent) && typeof window.chrome === "undefined",
2966
+ // Notification permission in headless is often 'denied' by default
2967
+ permissions: typeof Notification !== "undefined" && Notification.permission === "denied"
2968
+ };
2969
+ }
2956
2970
  var PocketPingClient = class {
2957
2971
  constructor(config) {
2958
2972
  this.session = null;
@@ -3003,7 +3017,8 @@ var PocketPingClient = class {
3003
3017
  userAgent: navigator.userAgent,
3004
3018
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
3005
3019
  language: navigator.language,
3006
- screenResolution: `${window.screen.width}x${window.screen.height}`
3020
+ screenResolution: `${window.screen.width}x${window.screen.height}`,
3021
+ botSignals: detectBotSignals()
3007
3022
  },
3008
3023
  // Include stored identity if available
3009
3024
  identity: storedIdentity || void 0
@@ -3968,6 +3983,14 @@ var PocketPingClient = class {
3968
3983
  console.error("[PocketPing] Failed to parse SSE message:", err);
3969
3984
  }
3970
3985
  });
3986
+ this.sse.addEventListener("screenshot_request", (event) => {
3987
+ try {
3988
+ const data = JSON.parse(event.data);
3989
+ this.handleRealtimeEvent(data);
3990
+ } catch (err) {
3991
+ console.error("[PocketPing] Failed to parse SSE screenshot_request:", err);
3992
+ }
3993
+ });
3971
3994
  this.sse.addEventListener("connected", () => {
3972
3995
  });
3973
3996
  this.sse.onerror = () => {
@@ -4150,6 +4173,10 @@ var PocketPingClient = class {
4150
4173
  this.emit("configUpdate", configData);
4151
4174
  }
4152
4175
  break;
4176
+ case "screenshot_request":
4177
+ const screenshotData = event.data;
4178
+ this.handleScreenshotRequest(screenshotData);
4179
+ break;
4153
4180
  }
4154
4181
  }
4155
4182
  // ─────────────────────────────────────────────────────────────────
@@ -4196,6 +4223,7 @@ var PocketPingClient = class {
4196
4223
  try {
4197
4224
  const lastEventTimestamp = this.getLastEventTimestamp();
4198
4225
  const newMessages = await this.fetchMessages(lastEventTimestamp ?? void 0);
4226
+ await this.pollScreenshotRequests();
4199
4227
  this.pollingFailures = 0;
4200
4228
  for (const message of newMessages) {
4201
4229
  const existingIndex = this.session.messages.findIndex((m) => m.id === message.id);
@@ -4446,6 +4474,135 @@ var PocketPingClient = class {
4446
4474
  generateId() {
4447
4475
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
4448
4476
  }
4477
+ // ─────────────────────────────────────────────────────────────────
4478
+ // Screenshot Request Polling (for polling fallback mode)
4479
+ // ─────────────────────────────────────────────────────────────────
4480
+ async pollScreenshotRequests() {
4481
+ if (!this.session) return;
4482
+ try {
4483
+ const response = await this.fetch(`/screenshot/requests?sessionId=${this.session.sessionId}`, { method: "GET" });
4484
+ for (const request of response.requests || []) {
4485
+ this.handleScreenshotRequest(request);
4486
+ }
4487
+ } catch (err) {
4488
+ }
4489
+ }
4490
+ // ─────────────────────────────────────────────────────────────────
4491
+ // Screenshot Capture
4492
+ // ─────────────────────────────────────────────────────────────────
4493
+ /**
4494
+ * Handle screenshot request from operator
4495
+ * @param data.silent - If true, screenshot won't appear in widget chat (only sent to bridges)
4496
+ */
4497
+ async handleScreenshotRequest(data) {
4498
+ console.log("[PocketPing] Screenshot requested by", data.requestedBy, "via", data.requestedFrom, data.silent ? "(silent)" : "");
4499
+ try {
4500
+ const blob = await this.captureScreenshot();
4501
+ const initResponse = await this.fetch("/screenshot/upload", {
4502
+ method: "POST",
4503
+ body: JSON.stringify({
4504
+ sessionId: this.session?.sessionId,
4505
+ requestId: data.requestId
4506
+ })
4507
+ });
4508
+ const uploadResponse = await fetch(initResponse.uploadUrl, {
4509
+ method: "PUT",
4510
+ body: blob,
4511
+ headers: {
4512
+ "Content-Type": "image/png"
4513
+ }
4514
+ });
4515
+ if (!uploadResponse.ok) {
4516
+ throw new Error(`Upload failed: ${uploadResponse.status}`);
4517
+ }
4518
+ await this.fetch("/screenshot/upload/complete", {
4519
+ method: "POST",
4520
+ body: JSON.stringify({
4521
+ sessionId: this.session?.sessionId,
4522
+ requestId: data.requestId,
4523
+ attachmentId: initResponse.attachmentId,
4524
+ size: blob.size
4525
+ })
4526
+ });
4527
+ console.log("[PocketPing] Screenshot captured and uploaded successfully");
4528
+ } catch (error) {
4529
+ console.error("[PocketPing] Screenshot capture failed:", error);
4530
+ }
4531
+ }
4532
+ /**
4533
+ * Capture screenshot of the viewport using html2canvas
4534
+ */
4535
+ async captureScreenshot() {
4536
+ const html2canvas = await this.loadHtml2Canvas();
4537
+ const widgetContainer = document.getElementById("pocketping-widget");
4538
+ const widgetToggle = document.getElementById("pocketping-toggle");
4539
+ let widgetWasVisible = false;
4540
+ let toggleWasVisible = false;
4541
+ if (widgetContainer) {
4542
+ widgetWasVisible = widgetContainer.style.display !== "none";
4543
+ widgetContainer.style.display = "none";
4544
+ }
4545
+ if (widgetToggle) {
4546
+ toggleWasVisible = widgetToggle.style.display !== "none";
4547
+ widgetToggle.style.display = "none";
4548
+ }
4549
+ try {
4550
+ const canvas = await html2canvas(document.documentElement, {
4551
+ width: window.innerWidth,
4552
+ height: window.innerHeight,
4553
+ x: window.scrollX,
4554
+ y: window.scrollY,
4555
+ windowWidth: window.innerWidth,
4556
+ windowHeight: window.innerHeight,
4557
+ logging: false,
4558
+ useCORS: true,
4559
+ allowTaint: true
4560
+ });
4561
+ return new Promise((resolve, reject) => {
4562
+ canvas.toBlob(
4563
+ (blob) => {
4564
+ if (blob) {
4565
+ resolve(blob);
4566
+ } else {
4567
+ reject(new Error("Failed to create blob from canvas"));
4568
+ }
4569
+ },
4570
+ "image/png",
4571
+ 0.9
4572
+ );
4573
+ });
4574
+ } finally {
4575
+ if (widgetContainer && widgetWasVisible) {
4576
+ widgetContainer.style.display = "";
4577
+ }
4578
+ if (widgetToggle && toggleWasVisible) {
4579
+ widgetToggle.style.display = "";
4580
+ }
4581
+ }
4582
+ }
4583
+ /**
4584
+ * Dynamically load html2canvas from CDN
4585
+ */
4586
+ async loadHtml2Canvas() {
4587
+ const win = window;
4588
+ if (typeof win.html2canvas !== "undefined") {
4589
+ return win.html2canvas;
4590
+ }
4591
+ return new Promise((resolve, reject) => {
4592
+ const script = document.createElement("script");
4593
+ script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
4594
+ script.onload = () => {
4595
+ const w = window;
4596
+ if (typeof w.html2canvas !== "undefined") {
4597
+ resolve(w.html2canvas);
4598
+ } else {
4599
+ reject(new Error("html2canvas failed to load"));
4600
+ }
4601
+ };
4602
+ script.onerror = () => reject(new Error("Failed to load html2canvas from CDN"));
4603
+ document.head.appendChild(script);
4604
+ });
4605
+ }
4449
4606
  };
4450
4607
 
4451
4608
  // src/index.ts