@insitue/sdk 0.1.3 → 0.1.5

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountCaptureOnly
3
- } from "./chunk-PRCHVT5A.js";
4
- import "./chunk-RYS5Z2BU.js";
3
+ } from "./chunk-Z7GTGO4C.js";
4
+ import "./chunk-2QO63Y4R.js";
5
5
  export {
6
6
  mountCaptureOnly
7
7
  };
@@ -1641,19 +1641,75 @@ async function toCanvas(node, options = {}) {
1641
1641
  }
1642
1642
 
1643
1643
  // src/capture.ts
1644
- function crossOrigin(url) {
1645
- if (!url || url.startsWith("data:") || url.startsWith("blob:")) {
1646
- return false;
1647
- }
1648
- try {
1649
- return new URL(url, location.href).origin !== location.origin;
1650
- } catch {
1651
- return false;
1652
- }
1644
+ var IMAGE_PLACEHOLDER = "data:image/svg+xml;base64," + (typeof btoa !== "undefined" ? btoa(
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
+ ) : "");
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
+ };
1653
1712
  }
1654
- var IMAGE_PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
1655
- `<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>`
1656
- );
1657
1713
  function findContextAncestor(el) {
1658
1714
  const minW = 420;
1659
1715
  const minH = 140;
@@ -1675,37 +1731,42 @@ async function renderViewportCrop(cropRect, pixelRatio) {
1675
1731
  const bodyBg = getComputedStyle(document.body).backgroundColor;
1676
1732
  const htmlBg = getComputedStyle(document.documentElement).backgroundColor;
1677
1733
  const backgroundColor = bodyBg && bodyBg !== "rgba(0, 0, 0, 0)" && bodyBg !== "transparent" ? bodyBg : htmlBg && htmlBg !== "rgba(0, 0, 0, 0)" && htmlBg !== "transparent" ? htmlBg : "#ffffff";
1678
- const fullCanvas = await toCanvas(document.documentElement, {
1679
- pixelRatio,
1680
- cacheBust: true,
1681
- backgroundColor,
1682
- imagePlaceholder: IMAGE_PLACEHOLDER,
1683
- // Only filter out our own overlay layers — leave cross-origin
1684
- // <img>s in place so embedImages can fetch+inline them.
1685
- filter: (n2) => !(n2 instanceof Element && n2.closest?.("#insitu-root, [data-insitu-layer]"))
1686
- });
1687
- const sx = window.scrollX;
1688
- const sy = window.scrollY;
1689
- const out = document.createElement("canvas");
1690
- out.width = Math.max(1, Math.round(cropRect.width * pixelRatio));
1691
- out.height = Math.max(1, Math.round(cropRect.height * pixelRatio));
1692
- const ctx = out.getContext("2d");
1693
- if (!ctx) return null;
1694
- ctx.drawImage(
1695
- fullCanvas,
1696
- Math.round((cropRect.x + sx) * pixelRatio),
1697
- Math.round((cropRect.y + sy) * pixelRatio),
1698
- Math.round(cropRect.width * pixelRatio),
1699
- Math.round(cropRect.height * pixelRatio),
1700
- 0,
1701
- 0,
1702
- out.width,
1703
- out.height
1704
- );
1705
- if (looksBlankUniform(ctx, out.width, out.height)) {
1706
- return null;
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
+ return { dataUrl: out.toDataURL("image/png"), failedImages };
1767
+ } finally {
1768
+ restoreImages();
1707
1769
  }
1708
- return out.toDataURL("image/png");
1709
1770
  }
1710
1771
  function looksBlankUniform(ctx, w3, h3) {
1711
1772
  if (w3 < 4 || h3 < 4) return false;
@@ -1724,7 +1785,7 @@ function looksBlankUniform(ctx, w3, h3) {
1724
1785
  }
1725
1786
  return new Set(samples).size === 1;
1726
1787
  }
1727
- function assessCaptureQuality(cropRect) {
1788
+ function assessCaptureQuality(cropRect, failedImages) {
1728
1789
  const out = {
1729
1790
  unembeddableImages: 0,
1730
1791
  hasVideo: false,
@@ -1739,13 +1800,7 @@ function assessCaptureQuality(cropRect) {
1739
1800
  const overlaps = r3.right >= cropRect.x && r3.left <= cropRect.x + cropRect.width && r3.bottom >= cropRect.y && r3.top <= cropRect.y + cropRect.height;
1740
1801
  if (!overlaps) continue;
1741
1802
  if (el instanceof HTMLImageElement) {
1742
- const src = el.currentSrc || el.src;
1743
- if (!src) continue;
1744
- const browserLoaded = el.complete && el.naturalWidth > 0;
1745
- const corsSafe = !crossOrigin(src) || el.crossOrigin != null;
1746
- if (!browserLoaded || !corsSafe) {
1747
- out.unembeddableImages++;
1748
- }
1803
+ if (failedImages.has(el)) out.unembeddableImages++;
1749
1804
  } else if (el instanceof HTMLVideoElement) {
1750
1805
  if (r3.width > 0 && r3.height > 0) out.hasVideo = true;
1751
1806
  } else if (el instanceof HTMLCanvasElement) {
@@ -1952,7 +2007,7 @@ async function buildBundle(sel) {
1952
2007
  const settings = getCaptureSettings();
1953
2008
  let screenshot;
1954
2009
  let screenshotUnavailable;
1955
- if (el instanceof HTMLElement) {
2010
+ if (el instanceof HTMLElement || el instanceof SVGElement) {
1956
2011
  const context = findContextAncestor(el);
1957
2012
  const cr = context.getBoundingClientRect();
1958
2013
  const cropRect = new DOMRect(
@@ -1972,11 +2027,9 @@ async function buildBundle(sel) {
1972
2027
  let layer1Result = null;
1973
2028
  let quality = null;
1974
2029
  if (!skipLayer1) {
1975
- layer1Result = await renderViewportCrop(
1976
- cropRect,
1977
- Math.min(dpr, 1.5)
1978
- );
1979
- quality = assessCaptureQuality(cropRect);
2030
+ const r3 = await renderViewportCrop(cropRect, Math.min(dpr, 1.5));
2031
+ layer1Result = r3.dataUrl;
2032
+ quality = assessCaptureQuality(cropRect, r3.failedImages);
1980
2033
  }
1981
2034
  const imperfect = !layer1Result || quality != null && (quality.unembeddableImages > 0 || quality.hasVideo || quality.hasCanvas);
1982
2035
  if (imperfect || skipLayer1) {
@@ -2044,7 +2097,14 @@ async function buildBundle(sel) {
2044
2097
  computedStyles: el ? curateComputedStyles(el) : {},
2045
2098
  tailwindClasses: el ? extractTailwindClasses(el) : [],
2046
2099
  ...screenshot ? { screenshot } : {},
2047
- ...screenshotUnavailable ? { screenshotUnavailable } : {},
2100
+ // "Never silent" if neither screenshot nor screenshotUnavailable
2101
+ // got set above (an unexpected fallthrough), surface that fact
2102
+ // so the widget never renders a blank where a result should be.
2103
+ // The structured diagnostic below tells future-me exactly which
2104
+ // branch ran, surfaced as `__insitu_capture__.bundle` in dev.
2105
+ ...screenshotUnavailable ? { screenshotUnavailable } : !screenshot ? {
2106
+ screenshotUnavailable: el ? `screenshot path didn't set a result (el=${el.tagName.toLowerCase()}; rasterisable=${el instanceof HTMLElement || el instanceof SVGElement})` : "no element selected"
2107
+ } : {},
2048
2108
  viewport: {
2049
2109
  w: window.innerWidth,
2050
2110
  h: window.innerHeight,
@@ -15,7 +15,7 @@ import {
15
15
  setCaptureSettings,
16
16
  stopDisplayMedia,
17
17
  y
18
- } from "./chunk-RYS5Z2BU.js";
18
+ } from "./chunk-2QO63Y4R.js";
19
19
 
20
20
  // src/client.ts
21
21
  var CompanionClient = class {
@@ -10,7 +10,7 @@ import {
10
10
  retryDisplayMedia,
11
11
  stopDisplayMedia,
12
12
  y
13
- } from "./chunk-RYS5Z2BU.js";
13
+ } from "./chunk-2QO63Y4R.js";
14
14
 
15
15
  // src/capture-only.ts
16
16
  var DEFAULT_INGEST = "https://www.insitue.com/api/v1/capture";
@@ -260,9 +260,11 @@ function CaptureOnlyApp(props) {
260
260
  }) : bundle?.screenshotUnavailable ? k(
261
261
  "div",
262
262
  {
263
- style: `font-size:12px;color:${C.faint};background:${C.surface2};border:1px solid ${C.line};border-radius:10px;padding:10px;margin-bottom:12px`
263
+ style: `font-size:12px;color:${C.faint};background:${C.surface2};border:1px solid ${C.line};border-radius:10px;padding:10px;margin-bottom:12px;word-break:break-word`
264
264
  },
265
- "Screenshot unavailable \u2014 sending the rest."
265
+ // Surface the actual reason inline — helps diagnose
266
+ // when a capture lands empty without expecting it.
267
+ `Screenshot unavailable \u2014 ${bundle.screenshotUnavailable}`
266
268
  ) : null,
267
269
  // Nudge: the screenshot is structurally OK but some content
268
270
  // couldn't be embedded (non-CORS img / video / canvas). Offer
@@ -347,7 +349,14 @@ function CaptureOnlyApp(props) {
347
349
  {
348
350
  style: `display:flex;justify-content:space-between;padding:9px 16px;border-top:1px solid ${C.line};color:${C.faint};font-size:11px`
349
351
  },
350
- [k("span", {}, "\u{1F512} Secrets scrubbed automatically"), k("span", {}, "InSitue")]
352
+ [
353
+ k("span", {}, "\u{1F512} Secrets scrubbed automatically"),
354
+ k(
355
+ "span",
356
+ { title: `@insitue/sdk@${"0.1.5"}` },
357
+ `InSitue \xB7 v${"0.1.5"}`
358
+ )
359
+ ]
351
360
  )
352
361
  ]);
353
362
  }
package/dist/index.d.ts CHANGED
@@ -33,4 +33,10 @@ interface InSitueCaptureProps {
33
33
  */
34
34
  declare function InSitueCapture({ projectKey, endpoint, onCapture, }: InSitueCaptureProps): null;
35
35
 
36
- export { InSitue, InSitueCapture, type InSitueCaptureProps, type InSitueProps };
36
+ /** Build-time-inlined version of `@insitue/sdk` (from package.json).
37
+ * Exposed so the host app can self-verify which SDK build is loaded
38
+ * — useful in dev when iterating across publishes. Also surfaced in
39
+ * the capture widget footer so a screenshot proves the build. */
40
+ declare const SDK_VERSION: string;
41
+
42
+ export { InSitue, InSitueCapture, type InSitueCaptureProps, type InSitueProps, SDK_VERSION };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  mountCaptureOnly
3
- } from "./chunk-PRCHVT5A.js";
3
+ } from "./chunk-Z7GTGO4C.js";
4
4
  import {
5
5
  mountInSitue
6
- } from "./chunk-VWPAKOUW.js";
7
- import "./chunk-RYS5Z2BU.js";
6
+ } from "./chunk-QL6QPM2A.js";
7
+ import "./chunk-2QO63Y4R.js";
8
8
 
9
9
  // src/InSitue.tsx
10
10
  import { useEffect } from "react";
@@ -44,9 +44,13 @@ function InSitueCapture({
44
44
  }, [projectKey, endpoint, onCapture]);
45
45
  return null;
46
46
  }
47
+
48
+ // src/index.ts
49
+ var SDK_VERSION = "0.1.5";
47
50
  export {
48
51
  InSitue,
49
52
  InSitueCapture,
53
+ SDK_VERSION,
50
54
  mountCaptureOnly,
51
55
  mountInSitue
52
56
  };
package/dist/overlay.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountInSitue
3
- } from "./chunk-VWPAKOUW.js";
4
- import "./chunk-RYS5Z2BU.js";
3
+ } from "./chunk-QL6QPM2A.js";
4
+ import "./chunk-2QO63Y4R.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.3",
3
+ "version": "0.1.5",
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",