@merittdev/horus-lens 0.1.0 → 0.2.0

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,6 +1,6 @@
1
- import { L as LensConfig, a as LensClient } from './index.d-DOCLVJGS.cjs';
1
+ import { L as LensConfig, a as LensClient } from './index.d-QzZn4EMM.cjs';
2
2
  import { z } from 'zod';
3
- import './index.d-Bzgtl7-B.cjs';
3
+ import './index.d-Cyvb07sp.cjs';
4
4
 
5
5
  declare const AppContextSchema: z.ZodObject<{
6
6
  release: z.ZodOptional<z.ZodString>;
package/dist/browser.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { L as LensConfig, a as LensClient } from './index.d-BA540Fae.js';
1
+ import { L as LensConfig, a as LensClient } from './index.d-BtnbCqUx.js';
2
2
  import { z } from 'zod';
3
- import './index.d-Bzgtl7-B.js';
3
+ import './index.d-Cyvb07sp.js';
4
4
 
5
5
  declare const AppContextSchema: z.ZodObject<{
6
6
  release: z.ZodOptional<z.ZodString>;
package/dist/browser.js CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  isGateActive,
8
8
  loaderSnippet,
9
9
  processQueue
10
- } from "./chunk-A6SNBIRS.js";
11
- import "./chunk-MTQ3IO4U.js";
10
+ } from "./chunk-UT7E5LCG.js";
11
+ import "./chunk-JXSK4JG4.js";
12
12
  export {
13
13
  DEFAULT_CDN_URL,
14
14
  LOADER_SNIPPET,
@@ -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,
@@ -29334,6 +29405,11 @@ var ReplayRecorder = class {
29334
29405
  emit: (event, isCheckout) => this.onEmit(event, isCheckout),
29335
29406
  checkoutEveryNms: this.windowMs,
29336
29407
  recordCanvas: false,
29408
+ // Inline same-origin + CORS images as data URIs at snapshot time so the
29409
+ // dashboard replay paints on a different origin (tainted cross-origin
29410
+ // images fall back to their URL, which is fine for public CDNs).
29411
+ inlineImages: true,
29412
+ collectFonts: true,
29337
29413
  // Keep the Lens overlay itself out of every recording.
29338
29414
  blockSelector: `#${LENS_OVERLAY_HOST_ID}`
29339
29415
  });
@@ -29440,7 +29516,20 @@ var ReplayRecorder = class {
29440
29516
  console.warn("[lens] rrweb recording unavailable; replay disabled", err);
29441
29517
  }
29442
29518
  };
29443
- async function captureScreenshot(excludeHost) {
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) {
29444
29533
  if (typeof document === "undefined" || typeof window === "undefined") return null;
29445
29534
  try {
29446
29535
  const viewportPixels = window.innerWidth * window.innerHeight || 1;
@@ -29450,6 +29539,15 @@ async function captureScreenshot(excludeHost) {
29450
29539
  scale,
29451
29540
  type: "image/jpeg",
29452
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 } : {},
29453
29551
  // Exclude the overlay from the capture CLONE — never hide the live node.
29454
29552
  filter: (node2) => node2 !== excludeHost,
29455
29553
  // Per-resource fetch budget: a single slow cross-origin image must not
@@ -29474,12 +29572,171 @@ async function captureScreenshot(excludeHost) {
29474
29572
  );
29475
29573
  return null;
29476
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
+ }
29585
+ if (crop) {
29586
+ const cropped = await cropBlob(blob2, crop, scale);
29587
+ return cropped ?? blob2;
29588
+ }
29477
29589
  return blob2;
29478
29590
  } catch (err) {
29479
29591
  console.warn("[lens] screenshot capture failed:", err);
29480
29592
  return null;
29481
29593
  }
29482
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
+ }
29715
+ async function cropBlob(blob2, crop, scale) {
29716
+ try {
29717
+ if (typeof createImageBitmap !== "function" || typeof document === "undefined") return null;
29718
+ const bitmap = await createImageBitmap(blob2);
29719
+ const rect = computeCropRect(crop, scale, bitmap.width, bitmap.height);
29720
+ if (!rect) {
29721
+ bitmap.close?.();
29722
+ return null;
29723
+ }
29724
+ const canvas = document.createElement("canvas");
29725
+ canvas.width = rect.sw;
29726
+ canvas.height = rect.sh;
29727
+ const ctx = canvas.getContext("2d");
29728
+ if (!ctx) {
29729
+ bitmap.close?.();
29730
+ return null;
29731
+ }
29732
+ ctx.drawImage(bitmap, rect.sx, rect.sy, rect.sw, rect.sh, 0, 0, rect.sw, rect.sh);
29733
+ bitmap.close?.();
29734
+ return await canvasToJpegBlob(canvas);
29735
+ } catch (err) {
29736
+ console.warn("[lens] screenshot crop failed:", err);
29737
+ return null;
29738
+ }
29739
+ }
29483
29740
  function parseBrowser(ua) {
29484
29741
  if (!ua) return void 0;
29485
29742
  const checks = [
@@ -29744,7 +30001,11 @@ var LensClient = class {
29744
30001
  const wantErrors = include.errors !== false;
29745
30002
  const wantScreenshot = include.screenshot !== false;
29746
30003
  const wantDom = include.dom !== false;
29747
- const screenshotPromise = this.screenshotEnabled && wantScreenshot ? captureScreenshot(input2.excludeFromScreenshot) : Promise.resolve(null);
30004
+ const screenshotPromise = this.screenshotEnabled && wantScreenshot ? captureScreenshot(
30005
+ input2.excludeFromScreenshot,
30006
+ input2.screenshotCrop,
30007
+ input2.annotationComposite
30008
+ ) : Promise.resolve(null);
29748
30009
  const replayEvents = wantReplay ? this.replay?.snapshot() ?? [] : [];
29749
30010
  const errorEntries = this.errorBuffer.drain();
29750
30011
  const consoleTail = this.consoleBuffer.tail(CONSOLE_TAIL_SIZE);
@@ -29883,4 +30144,4 @@ rrweb/dist/rrweb.js:
29883
30144
  PERFORMANCE OF THIS SOFTWARE.
29884
30145
  ***************************************************************************** *)
29885
30146
  */
29886
- //# sourceMappingURL=chunk-MTQ3IO4U.js.map
30147
+ //# sourceMappingURL=chunk-JXSK4JG4.js.map