@merittdev/horus-lens 0.1.1 → 0.2.1

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.
@@ -14586,15 +14586,86 @@ var TargetElementSchema = external_exports.object({
14586
14586
  boundingRect: external_exports.object({ x: external_exports.number(), y: external_exports.number(), width: external_exports.number(), height: external_exports.number() }).optional(),
14587
14587
  reactComponents: external_exports.array(external_exports.string()).optional()
14588
14588
  });
14589
- var AnnotationSchema = external_exports.object({
14590
- kind: external_exports.enum(["rect", "ellipse", "arrow"]),
14591
- x: external_exports.number(),
14592
- y: external_exports.number(),
14593
- width: external_exports.number(),
14594
- height: external_exports.number(),
14595
- /** Page coordinates relative to the viewport at capture time. */
14596
- viewport: external_exports.object({ width: external_exports.number(), height: external_exports.number() })
14597
- });
14589
+ var annotationCommon = {
14590
+ id: external_exports.string().optional(),
14591
+ /** Any CSS color string. */
14592
+ color: external_exports.string().optional(),
14593
+ strokeWidth: external_exports.number().optional()
14594
+ };
14595
+ var AnnotationPointSchema = external_exports.object({ x: external_exports.number(), y: external_exports.number() });
14596
+ var AnnotationSchema = external_exports.discriminatedUnion("kind", [
14597
+ external_exports.object({
14598
+ ...annotationCommon,
14599
+ kind: external_exports.literal("rect"),
14600
+ x: external_exports.number(),
14601
+ y: external_exports.number(),
14602
+ width: external_exports.number(),
14603
+ height: external_exports.number(),
14604
+ /**
14605
+ * OPTIONAL, kept for back-compat with 0.1.x clients that emit
14606
+ * viewport-anchored rects. New clients emit document-pixel rects with no
14607
+ * viewport.
14608
+ */
14609
+ viewport: external_exports.object({ width: external_exports.number(), height: external_exports.number() }).optional()
14610
+ }),
14611
+ external_exports.object({
14612
+ ...annotationCommon,
14613
+ kind: external_exports.literal("ellipse"),
14614
+ x: external_exports.number(),
14615
+ y: external_exports.number(),
14616
+ width: external_exports.number(),
14617
+ height: external_exports.number()
14618
+ }),
14619
+ external_exports.object({
14620
+ ...annotationCommon,
14621
+ kind: external_exports.literal("blur"),
14622
+ x: external_exports.number(),
14623
+ y: external_exports.number(),
14624
+ width: external_exports.number(),
14625
+ height: external_exports.number()
14626
+ }),
14627
+ external_exports.object({
14628
+ ...annotationCommon,
14629
+ kind: external_exports.literal("arrow"),
14630
+ x1: external_exports.number(),
14631
+ y1: external_exports.number(),
14632
+ x2: external_exports.number(),
14633
+ y2: external_exports.number()
14634
+ }),
14635
+ external_exports.object({
14636
+ ...annotationCommon,
14637
+ kind: external_exports.literal("line"),
14638
+ x1: external_exports.number(),
14639
+ y1: external_exports.number(),
14640
+ x2: external_exports.number(),
14641
+ y2: external_exports.number()
14642
+ }),
14643
+ external_exports.object({
14644
+ ...annotationCommon,
14645
+ kind: external_exports.literal("pencil"),
14646
+ points: external_exports.array(AnnotationPointSchema).min(2)
14647
+ }),
14648
+ external_exports.object({
14649
+ ...annotationCommon,
14650
+ kind: external_exports.literal("highlighter"),
14651
+ points: external_exports.array(AnnotationPointSchema).min(2)
14652
+ }),
14653
+ external_exports.object({
14654
+ ...annotationCommon,
14655
+ kind: external_exports.literal("text"),
14656
+ x: external_exports.number(),
14657
+ y: external_exports.number(),
14658
+ text: external_exports.string(),
14659
+ fontSize: external_exports.number().optional()
14660
+ }),
14661
+ external_exports.object({
14662
+ ...annotationCommon,
14663
+ kind: external_exports.literal("step"),
14664
+ x: external_exports.number(),
14665
+ y: external_exports.number(),
14666
+ n: external_exports.number()
14667
+ })
14668
+ ]);
14598
14669
  var AssetKindSchema = external_exports.enum(["replay", "console", "network", "dom", "screenshot", "attachment"]);
14599
14670
  var AssetRefSchema = external_exports.object({
14600
14671
  kind: AssetKindSchema,
@@ -28984,7 +29055,7 @@ async function domToBlob(node2, options) {
28984
29055
 
28985
29056
  // ../core/dist/index.js
28986
29057
  var SDK_NAME = "@merittdev/horus-lens";
28987
- var SDK_VERSION = "0.0.1";
29058
+ var SDK_VERSION = true ? "0.2.1" : "0.0.0-dev";
28988
29059
  var DEFAULT_ENDPOINT = "https://api.horus.sh";
28989
29060
  var DEFAULT_REPLAY_WINDOW_MS = 15e3;
28990
29061
  var DEFAULT_MAX_CONSOLE_ENTRIES = 200;
@@ -29445,7 +29516,20 @@ var ReplayRecorder = class {
29445
29516
  console.warn("[lens] rrweb recording unavailable; replay disabled", err);
29446
29517
  }
29447
29518
  };
29448
- async function captureScreenshot(excludeHost, crop) {
29519
+ var SVG_RASTERIZE_TIMEOUT_MS = 3e3;
29520
+ function computeCropRect(crop, scale, rasterWidth, rasterHeight) {
29521
+ let sx = Math.round(crop.x * scale);
29522
+ let sy = Math.round(crop.y * scale);
29523
+ let sw = Math.round(crop.width * scale);
29524
+ let sh = Math.round(crop.height * scale);
29525
+ sx = Math.max(0, Math.min(sx, rasterWidth));
29526
+ sy = Math.max(0, Math.min(sy, rasterHeight));
29527
+ sw = Math.min(sw, rasterWidth - sx);
29528
+ sh = Math.min(sh, rasterHeight - sy);
29529
+ if (sw <= 0 || sh <= 0) return null;
29530
+ return { sx, sy, sw, sh };
29531
+ }
29532
+ async function captureScreenshot(excludeHost, crop, composite) {
29449
29533
  if (typeof document === "undefined" || typeof window === "undefined") return null;
29450
29534
  try {
29451
29535
  const viewportPixels = window.innerWidth * window.innerHeight || 1;
@@ -29455,6 +29539,15 @@ async function captureScreenshot(excludeHost, crop) {
29455
29539
  scale,
29456
29540
  type: "image/jpeg",
29457
29541
  quality: 0.82,
29542
+ // JPEG has no alpha — transparent page regions (and any capture box
29543
+ // extending past short content) would otherwise encode as BLACK.
29544
+ backgroundColor: "#ffffff",
29545
+ // When compositing annotations, force the capture box to the editor's
29546
+ // document measurement (scrollHeight-based). modern-screenshot otherwise
29547
+ // sizes from documentElement's bounding box, which on pages SHORTER than
29548
+ // the viewport is the content height only — marks drawn in the lower
29549
+ // viewport band would fall off the raster and the crop would clamp short.
29550
+ ...composite ? { width: composite.docWidth, height: composite.docHeight } : {},
29458
29551
  // Exclude the overlay from the capture CLONE — never hide the live node.
29459
29552
  filter: (node2) => node2 !== excludeHost,
29460
29553
  // Per-resource fetch budget: a single slow cross-origin image must not
@@ -29479,6 +29572,16 @@ async function captureScreenshot(excludeHost, crop) {
29479
29572
  );
29480
29573
  return null;
29481
29574
  }
29575
+ if (composite) {
29576
+ const baked = await compositeScreenshot(blob2, composite, scale, crop);
29577
+ if (baked) return baked;
29578
+ if (composite.blurRegions.length > 0) {
29579
+ console.warn(
29580
+ "[lens] screenshot compositing failed with active redactions; dropping screenshot to avoid leaking un-blurred content"
29581
+ );
29582
+ return null;
29583
+ }
29584
+ }
29482
29585
  if (crop) {
29483
29586
  const cropped = await cropBlob(blob2, crop, scale);
29484
29587
  return cropped ?? blob2;
@@ -29489,35 +29592,146 @@ async function captureScreenshot(excludeHost, crop) {
29489
29592
  return null;
29490
29593
  }
29491
29594
  }
29595
+ async function compositeScreenshot(blob2, composite, scale, crop) {
29596
+ try {
29597
+ if (typeof createImageBitmap !== "function" || typeof document === "undefined") return null;
29598
+ const bitmap = await createImageBitmap(blob2);
29599
+ const canvas = document.createElement("canvas");
29600
+ canvas.width = bitmap.width;
29601
+ canvas.height = bitmap.height;
29602
+ const ctx = canvas.getContext("2d");
29603
+ if (!ctx) {
29604
+ bitmap.close?.();
29605
+ return null;
29606
+ }
29607
+ ctx.fillStyle = "#ffffff";
29608
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
29609
+ ctx.drawImage(bitmap, 0, 0);
29610
+ bitmap.close?.();
29611
+ applyBlurRegions(ctx, canvas, composite.blurRegions, scale);
29612
+ await drawSvgOverlay(ctx, composite, scale);
29613
+ if (crop) {
29614
+ const rect = computeCropRect(crop, scale, canvas.width, canvas.height);
29615
+ if (rect) {
29616
+ const out = document.createElement("canvas");
29617
+ out.width = rect.sw;
29618
+ out.height = rect.sh;
29619
+ const octx = out.getContext("2d");
29620
+ if (octx) {
29621
+ octx.drawImage(canvas, rect.sx, rect.sy, rect.sw, rect.sh, 0, 0, rect.sw, rect.sh);
29622
+ return await canvasToJpegBlob(out);
29623
+ }
29624
+ }
29625
+ }
29626
+ return await canvasToJpegBlob(canvas);
29627
+ } catch (err) {
29628
+ console.warn("[lens] screenshot compositing failed:", err);
29629
+ return null;
29630
+ }
29631
+ }
29632
+ function applyBlurRegions(ctx, canvas, regions, scale) {
29633
+ if (regions.length === 0) return;
29634
+ const filterSupported = typeof ctx.filter === "string";
29635
+ const blurPx = Math.max(8, 12 * scale);
29636
+ const pad = Math.ceil(blurPx);
29637
+ for (const region of regions) {
29638
+ const x = Math.max(0, Math.min(Math.round(region.x * scale), canvas.width));
29639
+ const y = Math.max(0, Math.min(Math.round(region.y * scale), canvas.height));
29640
+ const w = Math.min(Math.round(region.width * scale), canvas.width - x);
29641
+ const h = Math.min(Math.round(region.height * scale), canvas.height - y);
29642
+ if (w <= 0 || h <= 0) continue;
29643
+ if (!filterSupported) {
29644
+ ctx.fillStyle = "rgba(127,127,127,1)";
29645
+ ctx.fillRect(x, y, w, h);
29646
+ continue;
29647
+ }
29648
+ const px = Math.max(0, x - pad);
29649
+ const py = Math.max(0, y - pad);
29650
+ const pw = Math.min(canvas.width, x + w + pad) - px;
29651
+ const ph = Math.min(canvas.height, y + h + pad) - py;
29652
+ if (pw <= 0 || ph <= 0) continue;
29653
+ const temp = document.createElement("canvas");
29654
+ temp.width = pw;
29655
+ temp.height = ph;
29656
+ const tctx = temp.getContext("2d");
29657
+ if (!tctx) {
29658
+ ctx.fillStyle = "rgba(127,127,127,1)";
29659
+ ctx.fillRect(x, y, w, h);
29660
+ continue;
29661
+ }
29662
+ tctx.filter = `blur(${blurPx}px)`;
29663
+ tctx.drawImage(canvas, px, py, pw, ph, 0, 0, pw, ph);
29664
+ tctx.filter = "none";
29665
+ ctx.save();
29666
+ ctx.beginPath();
29667
+ ctx.rect(x, y, w, h);
29668
+ ctx.clip();
29669
+ ctx.drawImage(temp, px, py);
29670
+ ctx.restore();
29671
+ }
29672
+ }
29673
+ async function drawSvgOverlay(ctx, composite, scale) {
29674
+ const { svgMarkup, docWidth, docHeight } = composite;
29675
+ if (!svgMarkup) return;
29676
+ const img = await loadSvgImage(svgMarkup);
29677
+ if (!img) return;
29678
+ try {
29679
+ ctx.drawImage(img, 0, 0, docWidth * scale, docHeight * scale);
29680
+ } catch (err) {
29681
+ console.warn("[lens] annotation overlay draw failed:", err);
29682
+ }
29683
+ }
29684
+ function loadSvgImage(svg) {
29685
+ return new Promise((resolve) => {
29686
+ if (typeof Image !== "function") {
29687
+ resolve(null);
29688
+ return;
29689
+ }
29690
+ let settled = false;
29691
+ const finish = (result2) => {
29692
+ if (settled) return;
29693
+ settled = true;
29694
+ clearTimeout(timer);
29695
+ resolve(result2);
29696
+ };
29697
+ const timer = setTimeout(() => {
29698
+ console.warn("[lens] annotation overlay rasterization timed out");
29699
+ finish(null);
29700
+ }, SVG_RASTERIZE_TIMEOUT_MS);
29701
+ const img = new Image();
29702
+ img.onload = () => finish(img);
29703
+ img.onerror = () => {
29704
+ console.warn("[lens] annotation overlay failed to rasterize");
29705
+ finish(null);
29706
+ };
29707
+ img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
29708
+ });
29709
+ }
29710
+ function canvasToJpegBlob(canvas) {
29711
+ return new Promise((resolve) => {
29712
+ canvas.toBlob((out) => resolve(out), "image/jpeg", 0.82);
29713
+ });
29714
+ }
29492
29715
  async function cropBlob(blob2, crop, scale) {
29493
29716
  try {
29494
29717
  if (typeof createImageBitmap !== "function" || typeof document === "undefined") return null;
29495
29718
  const bitmap = await createImageBitmap(blob2);
29496
- let sx = Math.round(crop.x * scale);
29497
- let sy = Math.round(crop.y * scale);
29498
- let sw = Math.round(crop.width * scale);
29499
- let sh = Math.round(crop.height * scale);
29500
- sx = Math.max(0, Math.min(sx, bitmap.width));
29501
- sy = Math.max(0, Math.min(sy, bitmap.height));
29502
- sw = Math.min(sw, bitmap.width - sx);
29503
- sh = Math.min(sh, bitmap.height - sy);
29504
- if (sw <= 0 || sh <= 0) {
29719
+ const rect = computeCropRect(crop, scale, bitmap.width, bitmap.height);
29720
+ if (!rect) {
29505
29721
  bitmap.close?.();
29506
29722
  return null;
29507
29723
  }
29508
29724
  const canvas = document.createElement("canvas");
29509
- canvas.width = sw;
29510
- canvas.height = sh;
29725
+ canvas.width = rect.sw;
29726
+ canvas.height = rect.sh;
29511
29727
  const ctx = canvas.getContext("2d");
29512
29728
  if (!ctx) {
29513
29729
  bitmap.close?.();
29514
29730
  return null;
29515
29731
  }
29516
- ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, sw, sh);
29732
+ ctx.drawImage(bitmap, rect.sx, rect.sy, rect.sw, rect.sh, 0, 0, rect.sw, rect.sh);
29517
29733
  bitmap.close?.();
29518
- return await new Promise((resolve) => {
29519
- canvas.toBlob((out) => resolve(out), "image/jpeg", 0.82);
29520
- });
29734
+ return await canvasToJpegBlob(canvas);
29521
29735
  } catch (err) {
29522
29736
  console.warn("[lens] screenshot crop failed:", err);
29523
29737
  return null;
@@ -29787,7 +30001,11 @@ var LensClient = class {
29787
30001
  const wantErrors = include.errors !== false;
29788
30002
  const wantScreenshot = include.screenshot !== false;
29789
30003
  const wantDom = include.dom !== false;
29790
- const screenshotPromise = this.screenshotEnabled && wantScreenshot ? captureScreenshot(input2.excludeFromScreenshot, input2.screenshotCrop) : Promise.resolve(null);
30004
+ const screenshotPromise = this.screenshotEnabled && wantScreenshot ? captureScreenshot(
30005
+ input2.excludeFromScreenshot,
30006
+ input2.screenshotCrop,
30007
+ input2.annotationComposite
30008
+ ) : Promise.resolve(null);
29791
30009
  const replayEvents = wantReplay ? this.replay?.snapshot() ?? [] : [];
29792
30010
  const errorEntries = this.errorBuffer.drain();
29793
30011
  const consoleTail = this.consoleBuffer.tail(CONSOLE_TAIL_SIZE);
@@ -29926,4 +30144,4 @@ rrweb/dist/rrweb.js:
29926
30144
  PERFORMANCE OF THIS SOFTWARE.
29927
30145
  ***************************************************************************** *)
29928
30146
  */
29929
- //# sourceMappingURL=chunk-UB7YYHI4.js.map
30147
+ //# sourceMappingURL=chunk-U6GBOUCE.js.map