@insitue/sdk 0.1.7 → 0.1.9
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-3ZUAZAJB.js → chunk-BNREKX5C.js} +7 -3
- package/dist/{chunk-AMC2RGMK.js → chunk-CPPXBTE5.js} +119 -6
- package/dist/{chunk-DBHURN5L.js → chunk-DSHJX2LF.js} +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +13 -7
- package/dist/overlay.js +2 -2
- package/package.json +7 -2
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
|
@@ -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-CPPXBTE5.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.9"}` },
|
|
358
|
+
`InSitue \xB7 v${"0.1.9"}`
|
|
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", "");
|
|
@@ -1641,6 +1641,16 @@ 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
|
+
}
|
|
1653
|
+
}
|
|
1644
1654
|
var IMAGE_PLACEHOLDER = "data:image/svg+xml;base64," + (typeof btoa !== "undefined" ? btoa(
|
|
1645
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>'
|
|
1646
1656
|
) : "");
|
|
@@ -1666,20 +1676,34 @@ async function renderViewportCrop(cropRect, pixelRatio) {
|
|
|
1666
1676
|
const htmlBg = getComputedStyle(document.documentElement).backgroundColor;
|
|
1667
1677
|
const backgroundColor = bodyBg && bodyBg !== "rgba(0, 0, 0, 0)" && bodyBg !== "transparent" ? bodyBg : htmlBg && htmlBg !== "rgba(0, 0, 0, 0)" && htmlBg !== "transparent" ? htmlBg : "#ffffff";
|
|
1668
1678
|
const failedImages = /* @__PURE__ */ new Set();
|
|
1679
|
+
const out = document.createElement("canvas");
|
|
1680
|
+
out.width = Math.max(1, Math.round(cropRect.width * pixelRatio));
|
|
1681
|
+
out.height = Math.max(1, Math.round(cropRect.height * pixelRatio));
|
|
1682
|
+
const ctx = out.getContext("2d");
|
|
1683
|
+
if (!ctx) return { dataUrl: null, failedImages };
|
|
1684
|
+
const drawnImgs = drawAbsoluteImagesOnto(
|
|
1685
|
+
ctx,
|
|
1686
|
+
cropRect,
|
|
1687
|
+
pixelRatio,
|
|
1688
|
+
failedImages
|
|
1689
|
+
);
|
|
1669
1690
|
const fullCanvas = await toCanvas(document.documentElement, {
|
|
1670
1691
|
pixelRatio,
|
|
1671
1692
|
cacheBust: true,
|
|
1672
1693
|
backgroundColor,
|
|
1673
1694
|
imagePlaceholder: IMAGE_PLACEHOLDER,
|
|
1674
|
-
filter: (n2) =>
|
|
1695
|
+
filter: (n2) => {
|
|
1696
|
+
if (n2 instanceof Element && n2.closest?.("#insitu-root, [data-insitu-layer]")) {
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
if (n2 instanceof HTMLImageElement && drawnImgs.has(n2)) {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1675
1704
|
});
|
|
1676
1705
|
const sx = window.scrollX;
|
|
1677
1706
|
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
1707
|
ctx.drawImage(
|
|
1684
1708
|
fullCanvas,
|
|
1685
1709
|
Math.round((cropRect.x + sx) * pixelRatio),
|
|
@@ -1697,6 +1721,95 @@ async function renderViewportCrop(cropRect, pixelRatio) {
|
|
|
1697
1721
|
detectUnrenderedImages(ctx, cropRect, out, pixelRatio, failedImages);
|
|
1698
1722
|
return { dataUrl: out.toDataURL("image/png"), failedImages };
|
|
1699
1723
|
}
|
|
1724
|
+
function drawAbsoluteImagesOnto(ctx, cropRect, pixelRatio, failedImages) {
|
|
1725
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
1726
|
+
const imgs = Array.from(
|
|
1727
|
+
document.querySelectorAll("img")
|
|
1728
|
+
).filter(
|
|
1729
|
+
(img) => !img.closest?.("#insitu-root, [data-insitu-layer]")
|
|
1730
|
+
);
|
|
1731
|
+
for (const img of imgs) {
|
|
1732
|
+
const r3 = img.getBoundingClientRect();
|
|
1733
|
+
if (r3.width <= 0 || r3.height <= 0) continue;
|
|
1734
|
+
const cs = getComputedStyle(img);
|
|
1735
|
+
if (cs.position !== "absolute" && cs.position !== "fixed") continue;
|
|
1736
|
+
if (r3.right < cropRect.x || r3.left > cropRect.x + cropRect.width || r3.bottom < cropRect.y || r3.top > cropRect.y + cropRect.height) {
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
const src = img.currentSrc || img.src;
|
|
1740
|
+
if (!src) continue;
|
|
1741
|
+
if (crossOrigin(src) && img.crossOrigin !== "anonymous" && img.crossOrigin !== "use-credentials") {
|
|
1742
|
+
failedImages.add(img);
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
if (!img.complete || img.naturalWidth === 0 || img.naturalHeight === 0) {
|
|
1746
|
+
failedImages.add(img);
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const dest = {
|
|
1750
|
+
x: (r3.left - cropRect.x) * pixelRatio,
|
|
1751
|
+
y: (r3.top - cropRect.y) * pixelRatio,
|
|
1752
|
+
w: r3.width * pixelRatio,
|
|
1753
|
+
h: r3.height * pixelRatio
|
|
1754
|
+
};
|
|
1755
|
+
const source = computeObjectFitSource(img, cs);
|
|
1756
|
+
try {
|
|
1757
|
+
ctx.drawImage(
|
|
1758
|
+
img,
|
|
1759
|
+
source.sx,
|
|
1760
|
+
source.sy,
|
|
1761
|
+
source.sw,
|
|
1762
|
+
source.sh,
|
|
1763
|
+
dest.x,
|
|
1764
|
+
dest.y,
|
|
1765
|
+
dest.w,
|
|
1766
|
+
dest.h
|
|
1767
|
+
);
|
|
1768
|
+
drawn.add(img);
|
|
1769
|
+
} catch {
|
|
1770
|
+
failedImages.add(img);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return drawn;
|
|
1774
|
+
}
|
|
1775
|
+
function computeObjectFitSource(img, cs) {
|
|
1776
|
+
const nw = img.naturalWidth;
|
|
1777
|
+
const nh = img.naturalHeight;
|
|
1778
|
+
const r3 = img.getBoundingClientRect();
|
|
1779
|
+
const dw = r3.width;
|
|
1780
|
+
const dh = r3.height;
|
|
1781
|
+
if (!nw || !nh || !dw || !dh) {
|
|
1782
|
+
return { sx: 0, sy: 0, sw: nw || 1, sh: nh || 1 };
|
|
1783
|
+
}
|
|
1784
|
+
const fit = cs.objectFit || "fill";
|
|
1785
|
+
if (fit === "fill") {
|
|
1786
|
+
return { sx: 0, sy: 0, sw: nw, sh: nh };
|
|
1787
|
+
}
|
|
1788
|
+
const destAR = dw / dh;
|
|
1789
|
+
const srcAR = nw / nh;
|
|
1790
|
+
if (fit === "cover") {
|
|
1791
|
+
if (srcAR > destAR) {
|
|
1792
|
+
const sw = nh * destAR;
|
|
1793
|
+
return { sx: (nw - sw) / 2, sy: 0, sw, sh: nh };
|
|
1794
|
+
}
|
|
1795
|
+
const sh = nw / destAR;
|
|
1796
|
+
return { sx: 0, sy: (nh - sh) / 2, sw: nw, sh };
|
|
1797
|
+
}
|
|
1798
|
+
if (fit === "contain" || fit === "scale-down") {
|
|
1799
|
+
return { sx: 0, sy: 0, sw: nw, sh: nh };
|
|
1800
|
+
}
|
|
1801
|
+
if (fit === "none") {
|
|
1802
|
+
const sw = Math.min(nw, dw);
|
|
1803
|
+
const sh = Math.min(nh, dh);
|
|
1804
|
+
return {
|
|
1805
|
+
sx: (nw - sw) / 2,
|
|
1806
|
+
sy: (nh - sh) / 2,
|
|
1807
|
+
sw,
|
|
1808
|
+
sh
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
return { sx: 0, sy: 0, sw: nw, sh: nh };
|
|
1812
|
+
}
|
|
1700
1813
|
function detectUnrenderedImages(cropCtx, cropRect, cropCanvas, pixelRatio, failedImages) {
|
|
1701
1814
|
const imgs = Array.from(
|
|
1702
1815
|
document.querySelectorAll("img")
|
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-BNREKX5C.js";
|
|
4
4
|
import {
|
|
5
5
|
mountInSitue
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-DSHJX2LF.js";
|
|
7
|
+
import "./chunk-CPPXBTE5.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.9";
|
|
50
56
|
export {
|
|
51
57
|
InSitue,
|
|
52
58
|
InSitueCapture,
|
package/dist/overlay.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insitue/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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",
|
|
@@ -50,9 +50,12 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^22.9.0",
|
|
52
52
|
"@types/react": "^19.0.0",
|
|
53
|
+
"@vitest/browser": "^3.2.4",
|
|
54
|
+
"playwright": "^1.60.0",
|
|
53
55
|
"react": "^19.0.0",
|
|
54
56
|
"tsup": "^8.3.5",
|
|
55
57
|
"typescript": "^5.6.3",
|
|
58
|
+
"vitest": "^3.2.4",
|
|
56
59
|
"@insitue/capture-core": "0.0.0"
|
|
57
60
|
},
|
|
58
61
|
"repository": {
|
|
@@ -84,6 +87,8 @@
|
|
|
84
87
|
"build": "tsup",
|
|
85
88
|
"dev": "tsup --watch",
|
|
86
89
|
"typecheck": "tsc --noEmit",
|
|
87
|
-
"lint": "tsc --noEmit"
|
|
90
|
+
"lint": "tsc --noEmit",
|
|
91
|
+
"test": "vitest run",
|
|
92
|
+
"test:watch": "vitest"
|
|
88
93
|
}
|
|
89
94
|
}
|