@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.
- package/dist/capture-only.d.ts +11 -0
- package/dist/capture-only.js +2 -2
- package/dist/{chunk-TQEOC7QZ.js → chunk-AMC2RGMK.js} +30 -102
- package/dist/{chunk-V2VYHBQC.js → chunk-DBHURN5L.js} +1 -1
- package/dist/{chunk-UWKF2IKW.js → chunk-KAHON3H3.js} +7 -3
- package/dist/index.d.ts +13 -1
- package/dist/index.js +13 -7
- package/dist/overlay.js +2 -2
- package/package.json +1 -1
package/dist/capture-only.d.ts
CHANGED
|
@@ -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
|
|
package/dist/capture-only.js
CHANGED
|
@@ -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
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
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(
|
|
@@ -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-
|
|
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.
|
|
357
|
-
`InSitue \xB7 v${"0.1.
|
|
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-
|
|
3
|
+
} from "./chunk-KAHON3H3.js";
|
|
4
4
|
import {
|
|
5
5
|
mountInSitue
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
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({
|
|
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.
|
|
55
|
+
var SDK_VERSION = "0.1.8";
|
|
50
56
|
export {
|
|
51
57
|
InSitue,
|
|
52
58
|
InSitueCapture,
|
package/dist/overlay.js
CHANGED
package/package.json
CHANGED