@insitue/sdk 0.1.6 → 0.1.8

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.
@@ -20,6 +20,17 @@ interface CaptureOnlyOptions {
20
20
  * `window.__insitu_capture__` (useful for prod validation).
21
21
  */
22
22
  onCapture?: (draft: IssueDraft, bundle: CaptureBundle) => void;
23
+ /**
24
+ * Force the pixel-perfect (`getDisplayMedia`) path for every
25
+ * capture from mount. Costs a one-time tab-share permission per
26
+ * session in exchange for screenshots that are guaranteed to
27
+ * match what the user actually saw — bypasses every html-to-image
28
+ * quirk (next/image srcset, video frames, canvas content,
29
+ * cross-origin assets). Use in dev/dogfood where capture quality
30
+ * matters more than permission UX; leave off for prod end-users
31
+ * who shouldn't see a permission dialog uninvited.
32
+ */
33
+ defaultPixelPerfect?: boolean;
23
34
  }
24
35
  declare function mountCaptureOnly(opts?: CaptureOnlyOptions): () => void;
25
36
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountCaptureOnly
3
- } from "./chunk-UWKF2IKW.js";
4
- import "./chunk-TQEOC7QZ.js";
3
+ } from "./chunk-KAHON3H3.js";
4
+ import "./chunk-AMC2RGMK.js";
5
5
  export {
6
6
  mountCaptureOnly
7
7
  };
@@ -1644,72 +1644,6 @@ async function toCanvas(node, options = {}) {
1644
1644
  var IMAGE_PLACEHOLDER = "data:image/svg+xml;base64," + (typeof btoa !== "undefined" ? btoa(
1645
1645
  '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect width="32" height="32" fill="#e8e8e8"/><path d="M0 0 L32 32 M32 0 L0 32" stroke="#b0b0b0" stroke-width="1.5"/></svg>'
1646
1646
  ) : "");
1647
- async function preResolveImages(cropRect) {
1648
- const restorations = [];
1649
- const failedImages = /* @__PURE__ */ new Set();
1650
- const PAD = 64;
1651
- const images = Array.from(
1652
- document.querySelectorAll("img")
1653
- ).filter((img) => {
1654
- if (img.closest?.("#insitu-root, [data-insitu-layer]")) return false;
1655
- if (!cropRect) return true;
1656
- const r3 = img.getBoundingClientRect();
1657
- if (r3.width <= 0 || r3.height <= 0) return false;
1658
- return r3.right >= cropRect.x - PAD && r3.left <= cropRect.x + cropRect.width + PAD && r3.bottom >= cropRect.y - PAD && r3.top <= cropRect.y + cropRect.height + PAD;
1659
- });
1660
- const PER_IMAGE_TIMEOUT_MS = 3e3;
1661
- await Promise.allSettled(
1662
- images.map(async (img) => {
1663
- const srcToFetch = img.currentSrc || img.src;
1664
- if (!srcToFetch || srcToFetch.startsWith("data:") || srcToFetch.startsWith("blob:")) {
1665
- return;
1666
- }
1667
- const ac = new AbortController();
1668
- const timer = setTimeout(() => ac.abort(), PER_IMAGE_TIMEOUT_MS);
1669
- try {
1670
- const res = await fetch(srcToFetch, {
1671
- cache: "force-cache",
1672
- signal: ac.signal
1673
- });
1674
- if (!res.ok) {
1675
- failedImages.add(img);
1676
- return;
1677
- }
1678
- const blob = await res.blob();
1679
- const dataUrl = await new Promise((resolve, reject) => {
1680
- const reader = new FileReader();
1681
- reader.onload = () => resolve(reader.result);
1682
- reader.onerror = () => reject(reader.error);
1683
- reader.readAsDataURL(blob);
1684
- });
1685
- const origSrc = img.getAttribute("src");
1686
- const origSrcset = img.getAttribute("srcset");
1687
- restorations.push(() => {
1688
- if (origSrc != null) img.setAttribute("src", origSrc);
1689
- else img.removeAttribute("src");
1690
- if (origSrcset != null) img.setAttribute("srcset", origSrcset);
1691
- else img.removeAttribute("srcset");
1692
- });
1693
- img.removeAttribute("srcset");
1694
- img.src = dataUrl;
1695
- try {
1696
- await img.decode();
1697
- } catch {
1698
- }
1699
- } catch {
1700
- failedImages.add(img);
1701
- } finally {
1702
- clearTimeout(timer);
1703
- }
1704
- })
1705
- );
1706
- return {
1707
- restore: () => {
1708
- for (const r3 of restorations) r3();
1709
- },
1710
- failedImages
1711
- };
1712
- }
1713
1647
  function findContextAncestor(el) {
1714
1648
  const minW = 420;
1715
1649
  const minH = 140;
@@ -1731,43 +1665,37 @@ async function renderViewportCrop(cropRect, pixelRatio) {
1731
1665
  const bodyBg = getComputedStyle(document.body).backgroundColor;
1732
1666
  const htmlBg = getComputedStyle(document.documentElement).backgroundColor;
1733
1667
  const backgroundColor = bodyBg && bodyBg !== "rgba(0, 0, 0, 0)" && bodyBg !== "transparent" ? bodyBg : htmlBg && htmlBg !== "rgba(0, 0, 0, 0)" && htmlBg !== "transparent" ? htmlBg : "#ffffff";
1734
- const { restore: restoreImages, failedImages } = await preResolveImages(cropRect);
1735
- try {
1736
- const fullCanvas = await toCanvas(document.documentElement, {
1737
- pixelRatio,
1738
- // cacheBust off — we've already swapped srcs to data URLs.
1739
- cacheBust: false,
1740
- backgroundColor,
1741
- imagePlaceholder: IMAGE_PLACEHOLDER,
1742
- // Strip only our own overlay layers.
1743
- filter: (n2) => !(n2 instanceof Element && n2.closest?.("#insitu-root, [data-insitu-layer]"))
1744
- });
1745
- const sx = window.scrollX;
1746
- const sy = window.scrollY;
1747
- const out = document.createElement("canvas");
1748
- out.width = Math.max(1, Math.round(cropRect.width * pixelRatio));
1749
- out.height = Math.max(1, Math.round(cropRect.height * pixelRatio));
1750
- const ctx = out.getContext("2d");
1751
- if (!ctx) return { dataUrl: null, failedImages };
1752
- ctx.drawImage(
1753
- fullCanvas,
1754
- Math.round((cropRect.x + sx) * pixelRatio),
1755
- Math.round((cropRect.y + sy) * pixelRatio),
1756
- Math.round(cropRect.width * pixelRatio),
1757
- Math.round(cropRect.height * pixelRatio),
1758
- 0,
1759
- 0,
1760
- out.width,
1761
- out.height
1762
- );
1763
- if (looksBlankUniform(ctx, out.width, out.height)) {
1764
- return { dataUrl: null, failedImages };
1765
- }
1766
- detectUnrenderedImages(ctx, cropRect, out, pixelRatio, failedImages);
1767
- return { dataUrl: out.toDataURL("image/png"), failedImages };
1768
- } finally {
1769
- restoreImages();
1668
+ const failedImages = /* @__PURE__ */ new Set();
1669
+ const fullCanvas = await toCanvas(document.documentElement, {
1670
+ pixelRatio,
1671
+ cacheBust: true,
1672
+ backgroundColor,
1673
+ imagePlaceholder: IMAGE_PLACEHOLDER,
1674
+ filter: (n2) => !(n2 instanceof Element && n2.closest?.("#insitu-root, [data-insitu-layer]"))
1675
+ });
1676
+ const sx = window.scrollX;
1677
+ const sy = window.scrollY;
1678
+ const out = document.createElement("canvas");
1679
+ out.width = Math.max(1, Math.round(cropRect.width * pixelRatio));
1680
+ out.height = Math.max(1, Math.round(cropRect.height * pixelRatio));
1681
+ const ctx = out.getContext("2d");
1682
+ if (!ctx) return { dataUrl: null, failedImages };
1683
+ ctx.drawImage(
1684
+ fullCanvas,
1685
+ Math.round((cropRect.x + sx) * pixelRatio),
1686
+ Math.round((cropRect.y + sy) * pixelRatio),
1687
+ Math.round(cropRect.width * pixelRatio),
1688
+ Math.round(cropRect.height * pixelRatio),
1689
+ 0,
1690
+ 0,
1691
+ out.width,
1692
+ out.height
1693
+ );
1694
+ if (looksBlankUniform(ctx, out.width, out.height)) {
1695
+ return { dataUrl: null, failedImages };
1770
1696
  }
1697
+ detectUnrenderedImages(ctx, cropRect, out, pixelRatio, failedImages);
1698
+ return { dataUrl: out.toDataURL("image/png"), failedImages };
1771
1699
  }
1772
1700
  function detectUnrenderedImages(cropCtx, cropRect, cropCanvas, pixelRatio, failedImages) {
1773
1701
  const imgs = Array.from(
@@ -15,7 +15,7 @@ import {
15
15
  setCaptureSettings,
16
16
  stopDisplayMedia,
17
17
  y
18
- } from "./chunk-TQEOC7QZ.js";
18
+ } from "./chunk-AMC2RGMK.js";
19
19
 
20
20
  // src/client.ts
21
21
  var CompanionClient = class {
@@ -8,9 +8,10 @@ import {
8
8
  k,
9
9
  onDisplayMediaChange,
10
10
  retryDisplayMedia,
11
+ setCaptureSettings,
11
12
  stopDisplayMedia,
12
13
  y
13
- } from "./chunk-TQEOC7QZ.js";
14
+ } from "./chunk-AMC2RGMK.js";
14
15
 
15
16
  // src/capture-only.ts
16
17
  var DEFAULT_INGEST = "https://www.insitue.com/api/v1/capture";
@@ -353,8 +354,8 @@ function CaptureOnlyApp(props) {
353
354
  k("span", {}, "\u{1F512} Secrets scrubbed automatically"),
354
355
  k(
355
356
  "span",
356
- { title: `@insitue/sdk@${"0.1.6"}` },
357
- `InSitue \xB7 v${"0.1.6"}`
357
+ { title: `@insitue/sdk@${"0.1.8"}` },
358
+ `InSitue \xB7 v${"0.1.8"}`
358
359
  )
359
360
  ]
360
361
  )
@@ -362,6 +363,9 @@ function CaptureOnlyApp(props) {
362
363
  }
363
364
  function mountCaptureOnly(opts = {}) {
364
365
  installRuntimeCollectors();
366
+ if (opts.defaultPixelPerfect === true) {
367
+ setCaptureSettings({ alwaysPixelPerfect: true });
368
+ }
365
369
  const host = document.createElement("div");
366
370
  host.id = "insitu-capture-root";
367
371
  host.setAttribute("data-insitu", "");
package/dist/index.d.ts CHANGED
@@ -21,6 +21,18 @@ interface InSitueCaptureProps {
21
21
  * (neither set): console + JSON download + `window.__insitu_capture__`.
22
22
  */
23
23
  onCapture?: (draft: IssueDraft, bundle: CaptureBundle) => void;
24
+ /**
25
+ * Default the user's "Always pixel-perfect screenshots" setting
26
+ * to `true` on mount — every capture uses the `getDisplayMedia`
27
+ * OS-compositor path, paying a one-time tab-share permission per
28
+ * session in exchange for screenshots that are pixel-accurate
29
+ * across any content (next/image, video, canvas, cross-origin).
30
+ *
31
+ * Recommended for dev / dogfood, where capture quality matters
32
+ * more than the permission UX. Not the default — production
33
+ * end-users shouldn't see a permission dialog they didn't ask for.
34
+ */
35
+ defaultPixelPerfect?: boolean;
24
36
  }
25
37
  /**
26
38
  * `<InSitueCapture />` — the prod capture-only path. UNLIKE
@@ -31,7 +43,7 @@ interface InSitueCaptureProps {
31
43
  * The simplest path: set `projectKey` and the SDK POSTs captures to
32
44
  * the InSitue cloud automatically.
33
45
  */
34
- declare function InSitueCapture({ projectKey, endpoint, onCapture, }: InSitueCaptureProps): null;
46
+ declare function InSitueCapture({ projectKey, endpoint, onCapture, defaultPixelPerfect, }: InSitueCaptureProps): null;
35
47
 
36
48
  /** Build-time-inlined version of `@insitue/sdk` (from package.json).
37
49
  * Exposed so the host app can self-verify which SDK build is loaded
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  mountCaptureOnly
3
- } from "./chunk-UWKF2IKW.js";
3
+ } from "./chunk-KAHON3H3.js";
4
4
  import {
5
5
  mountInSitue
6
- } from "./chunk-V2VYHBQC.js";
7
- import "./chunk-TQEOC7QZ.js";
6
+ } from "./chunk-DBHURN5L.js";
7
+ import "./chunk-AMC2RGMK.js";
8
8
 
9
9
  // src/InSitue.tsx
10
10
  import { useEffect } from "react";
@@ -27,26 +27,32 @@ function InSitue({ port }) {
27
27
  function InSitueCapture({
28
28
  projectKey,
29
29
  endpoint,
30
- onCapture
30
+ onCapture,
31
+ defaultPixelPerfect
31
32
  }) {
32
33
  useEffect(() => {
33
34
  let active = true;
34
35
  let dispose;
35
36
  void import("./capture-only.js").then((m) => {
36
37
  if (active) {
37
- dispose = m.mountCaptureOnly({ projectKey, endpoint, onCapture });
38
+ dispose = m.mountCaptureOnly({
39
+ projectKey,
40
+ endpoint,
41
+ onCapture,
42
+ defaultPixelPerfect
43
+ });
38
44
  }
39
45
  });
40
46
  return () => {
41
47
  active = false;
42
48
  dispose?.();
43
49
  };
44
- }, [projectKey, endpoint, onCapture]);
50
+ }, [projectKey, endpoint, onCapture, defaultPixelPerfect]);
45
51
  return null;
46
52
  }
47
53
 
48
54
  // src/index.ts
49
- var SDK_VERSION = "0.1.6";
55
+ var SDK_VERSION = "0.1.8";
50
56
  export {
51
57
  InSitue,
52
58
  InSitueCapture,
package/dist/overlay.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountInSitue
3
- } from "./chunk-V2VYHBQC.js";
4
- import "./chunk-TQEOC7QZ.js";
3
+ } from "./chunk-DBHURN5L.js";
4
+ import "./chunk-AMC2RGMK.js";
5
5
  export {
6
6
  mountInSitue
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insitue/sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "InSitue capture SDK — drop one snippet into your deployed app; your users point at a bug, InSitue opens a verified pull request.",
5
5
  "license": "MIT",
6
6
  "type": "module",