@merittdev/horus-lens 0.5.1 → 0.6.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.
package/dist/react.cjs CHANGED
@@ -32,6 +32,260 @@ module.exports = __toCommonJS(react_exports);
32
32
  // ../react/dist/index.js
33
33
  var import_react = require("react");
34
34
 
35
+ // ../core/dist/chunk-YBVE6CBL.js
36
+ var SDK_NAME = "@merittdev/horus-lens";
37
+ var SDK_VERSION = true ? "0.6.0" : "0.0.0-dev";
38
+ var DEFAULT_ENDPOINT = "https://api.horus.sh";
39
+ var DEFAULT_REPLAY_WINDOW_MS = 15e3;
40
+ var DEFAULT_MAX_CONSOLE_ENTRIES = 200;
41
+ var DEFAULT_MAX_NETWORK_ENTRIES = 100;
42
+ var MAX_ERROR_ENTRIES = 100;
43
+ var CONSOLE_TAIL_SIZE = 50;
44
+ var NETWORK_TAIL_SIZE = 30;
45
+ var MAX_SERIALIZED_ARG_CHARS = 2e3;
46
+ var MAX_DOM_BYTES = 2 * 1024 * 1024;
47
+ var MAX_TARGET_TEXT_CHARS = 200;
48
+ var SCREENSHOT_TIMEOUT_MS = 2e4;
49
+ var SCREENSHOT_RESOURCE_TIMEOUT_MS = 3500;
50
+ var SCREENSHOT_MAX_SCALE = 2;
51
+ var SCREENSHOT_MAX_PIXELS = 25e5;
52
+ var AUTO_CAPTURE_COOLDOWN_MS = 10 * 60 * 1e3;
53
+ var AUTO_CAPTURE_SESSION_CAP = 5;
54
+ var MAX_MANUAL_RECORDING_MS = 12e4;
55
+ var MAX_MANUAL_RECORDING_EVENTS = 1e4;
56
+ var LENS_OVERLAY_HOST_ID = "__lens_root__";
57
+ var RingBuffer = class {
58
+ constructor(capacity) {
59
+ this.capacity = capacity;
60
+ if (capacity <= 0) throw new Error("RingBuffer capacity must be > 0");
61
+ }
62
+ capacity;
63
+ items = [];
64
+ push(item) {
65
+ this.items.push(item);
66
+ if (this.items.length > this.capacity) {
67
+ this.items.splice(0, this.items.length - this.capacity);
68
+ }
69
+ }
70
+ /** Snapshot of current contents, oldest first. */
71
+ drain() {
72
+ return [...this.items];
73
+ }
74
+ tail(count) {
75
+ return this.items.slice(-count);
76
+ }
77
+ clear() {
78
+ this.items = [];
79
+ }
80
+ get size() {
81
+ return this.items.length;
82
+ }
83
+ };
84
+ var HttpError = class extends Error {
85
+ constructor(status, message) {
86
+ super(message);
87
+ this.status = status;
88
+ this.name = "HttpError";
89
+ }
90
+ status;
91
+ };
92
+ var Transport = class {
93
+ constructor(config2) {
94
+ this.config = config2;
95
+ }
96
+ config;
97
+ async initiate(assets) {
98
+ const body = {
99
+ accessId: this.config.accessId,
100
+ assets
101
+ };
102
+ return this.postJson(
103
+ `${this.config.endpoint}/v1/reports/initiate`,
104
+ body,
105
+ { retries: 0 }
106
+ );
107
+ }
108
+ /** Record a late-uploaded asset (background screenshot) on a finalized report. */
109
+ async attachAsset(reportId, asset) {
110
+ const body = {
111
+ accessId: this.config.accessId,
112
+ ...asset
113
+ };
114
+ await this.postJson(
115
+ `${this.config.endpoint}/v1/reports/${encodeURIComponent(reportId)}/assets`,
116
+ body
117
+ );
118
+ }
119
+ async uploadAsset(upload, blob2, signal) {
120
+ const session = this.config.signal;
121
+ const controller = session ? new AbortController() : void 0;
122
+ const abort = () => controller?.abort();
123
+ if (session?.aborted || signal?.aborted) abort();
124
+ session?.addEventListener("abort", abort);
125
+ if (controller) signal?.addEventListener("abort", abort);
126
+ try {
127
+ const response = await (this.config.fetch ?? fetch)(upload.url, {
128
+ method: upload.method,
129
+ headers: upload.headers,
130
+ body: blob2,
131
+ signal: controller?.signal ?? signal
132
+ });
133
+ if (!response.ok) {
134
+ throw new Error(
135
+ `Asset upload failed (${upload.kind}): ${response.status}`
136
+ );
137
+ }
138
+ } finally {
139
+ session?.removeEventListener("abort", abort);
140
+ if (controller) signal?.removeEventListener("abort", abort);
141
+ }
142
+ }
143
+ async finalize(reportId, metadata, uploadedAssets) {
144
+ const body = {
145
+ accessId: this.config.accessId,
146
+ metadata,
147
+ uploadedAssets
148
+ };
149
+ try {
150
+ return await this.postJson(
151
+ `${this.config.endpoint}/v1/reports/${reportId}/finalize`,
152
+ body
153
+ );
154
+ } catch (err) {
155
+ if (err instanceof HttpError && err.status === 409) {
156
+ return { reportId, status: "submitted" };
157
+ }
158
+ throw err;
159
+ }
160
+ }
161
+ async postJson(url2, body, opts = {}) {
162
+ const retries = opts.retries ?? 1;
163
+ let lastError;
164
+ for (let attempt = 0; attempt <= retries; attempt++) {
165
+ const controller = this.config.requestTimeoutMs || this.config.signal ? new AbortController() : void 0;
166
+ const abort = () => controller?.abort();
167
+ const session = this.config.signal;
168
+ if (session?.aborted) throw new Error("Capture session ended");
169
+ session?.addEventListener("abort", abort);
170
+ const timeout = controller && this.config.requestTimeoutMs ? setTimeout(() => controller.abort(), this.config.requestTimeoutMs) : void 0;
171
+ try {
172
+ const response = await (this.config.fetch ?? fetch)(url2, {
173
+ method: "POST",
174
+ headers: {
175
+ "content-type": "application/json",
176
+ ...this.config.origin ? { Origin: this.config.origin } : {}
177
+ },
178
+ body: JSON.stringify(body),
179
+ ...controller ? { signal: controller.signal } : {}
180
+ });
181
+ if (!response.ok) {
182
+ const text = await response.text().catch(() => "");
183
+ throw new HttpError(
184
+ response.status,
185
+ `Request to ${url2} failed: ${response.status} ${text}`.trim()
186
+ );
187
+ }
188
+ return await response.json();
189
+ } catch (err) {
190
+ lastError = err;
191
+ if (this.config.debug) {
192
+ console.warn(
193
+ `[lens] request failed (attempt ${attempt + 1}/${retries + 1})`,
194
+ err
195
+ );
196
+ }
197
+ } finally {
198
+ if (timeout !== void 0) clearTimeout(timeout);
199
+ session?.removeEventListener("abort", abort);
200
+ }
201
+ }
202
+ throw lastError;
203
+ }
204
+ };
205
+ var LEVELS = ["log", "info", "warn", "error", "debug"];
206
+ function cap(value) {
207
+ return value.length > MAX_SERIALIZED_ARG_CHARS ? `${value.slice(0, MAX_SERIALIZED_ARG_CHARS)}\u2026[truncated]` : value;
208
+ }
209
+ function serializeArg(value) {
210
+ if (value === null) return "null";
211
+ if (value === void 0) return "undefined";
212
+ const type = typeof value;
213
+ if (type === "string") return cap(value);
214
+ if (type === "number" || type === "boolean" || type === "bigint" || type === "symbol") {
215
+ return String(value);
216
+ }
217
+ if (type === "function") {
218
+ return `[Function: ${value.name || "anonymous"}]`;
219
+ }
220
+ if (value instanceof Error) {
221
+ return cap(value.stack ? `${value.name}: ${value.message}
222
+ ${value.stack}` : `${value.name}: ${value.message}`);
223
+ }
224
+ try {
225
+ const seen = /* @__PURE__ */ new WeakSet();
226
+ const json2 = JSON.stringify(value, (_key, val) => {
227
+ if (typeof val === "bigint") return String(val);
228
+ if (typeof val === "function") return "[Function]";
229
+ if (typeof val === "object" && val !== null) {
230
+ if (seen.has(val)) return "[Circular]";
231
+ seen.add(val);
232
+ }
233
+ return val;
234
+ });
235
+ return cap(json2 ?? String(value));
236
+ } catch {
237
+ return cap(String(value));
238
+ }
239
+ }
240
+ function firstErrorStack(args) {
241
+ for (const arg of args) {
242
+ if (arg instanceof Error && arg.stack) return arg.stack;
243
+ }
244
+ return void 0;
245
+ }
246
+ function installConsole(consoleBuffer, errorBuffer) {
247
+ if (typeof console === "undefined") return () => {
248
+ };
249
+ const target = console;
250
+ const restores = [];
251
+ let active = true;
252
+ for (const level of LEVELS) {
253
+ const original = target[level];
254
+ if (typeof original !== "function") continue;
255
+ const wrapped = (...args) => {
256
+ if (!active) {
257
+ original.apply(console, args);
258
+ return;
259
+ }
260
+ try {
261
+ const serialized = args.map(serializeArg);
262
+ const timestamp = Date.now();
263
+ consoleBuffer.push({ level, args: serialized, timestamp });
264
+ if (level === "error") {
265
+ const stack = firstErrorStack(args);
266
+ const entry = {
267
+ message: serialized.join(" ") || "console.error",
268
+ source: "console.error",
269
+ timestamp
270
+ };
271
+ if (stack) entry.stack = stack;
272
+ errorBuffer.push(entry);
273
+ }
274
+ } catch {
275
+ }
276
+ original.apply(console, args);
277
+ };
278
+ target[level] = wrapped;
279
+ restores.push(() => {
280
+ if (target[level] === wrapped) target[level] = original;
281
+ });
282
+ }
283
+ return () => {
284
+ active = false;
285
+ for (const restore of restores) restore();
286
+ };
287
+ }
288
+
35
289
  // ../../node_modules/.pnpm/rrweb@2.1.0/node_modules/rrweb/dist/rrweb.js
36
290
  var __defProp2 = Object.defineProperty;
37
291
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -11681,9 +11935,9 @@ var saveWebGLVar = (value, win, ctx) => {
11681
11935
  }
11682
11936
  return index2;
11683
11937
  };
11684
- function serializeArg(value, win, ctx) {
11938
+ function serializeArg2(value, win, ctx) {
11685
11939
  if (value instanceof Array) {
11686
- return value.map((arg) => serializeArg(arg, win, ctx));
11940
+ return value.map((arg) => serializeArg2(arg, win, ctx));
11687
11941
  } else if (value === null) {
11688
11942
  return value;
11689
11943
  } else if (value instanceof Float32Array || value instanceof Float64Array || value instanceof Int32Array || value instanceof Uint32Array || value instanceof Uint8Array || value instanceof Uint16Array || value instanceof Int16Array || value instanceof Int8Array || value instanceof Uint8ClampedArray) {
@@ -11709,7 +11963,7 @@ function serializeArg(value, win, ctx) {
11709
11963
  return {
11710
11964
  rr_type: name,
11711
11965
  args: [
11712
- serializeArg(value.buffer, win, ctx),
11966
+ serializeArg2(value.buffer, win, ctx),
11713
11967
  value.byteOffset,
11714
11968
  value.byteLength
11715
11969
  ]
@@ -11732,7 +11986,7 @@ function serializeArg(value, win, ctx) {
11732
11986
  const name = value.constructor.name;
11733
11987
  return {
11734
11988
  rr_type: name,
11735
- args: [serializeArg(value.data, win, ctx), value.width, value.height]
11989
+ args: [serializeArg2(value.data, win, ctx), value.width, value.height]
11736
11990
  };
11737
11991
  } else if (isInstanceOfWebGLObject(value, win) || typeof value === "object") {
11738
11992
  const name = value.constructor.name;
@@ -11745,7 +11999,7 @@ function serializeArg(value, win, ctx) {
11745
11999
  return value;
11746
12000
  }
11747
12001
  var serializeArgs = (args, win, ctx) => {
11748
- return args.map((arg) => serializeArg(arg, win, ctx));
12002
+ return args.map((arg) => serializeArg2(arg, win, ctx));
11749
12003
  };
11750
12004
  var isInstanceOfWebGLObject = (value, win) => {
11751
12005
  const webGLConstructorNames = [
@@ -29082,26 +29336,6 @@ var ApiErrorSchema = external_exports.object({
29082
29336
  });
29083
29337
 
29084
29338
  // ../core/dist/index.js
29085
- var SDK_NAME = "@merittdev/horus-lens";
29086
- var SDK_VERSION = true ? "0.5.1" : "0.0.0-dev";
29087
- var DEFAULT_ENDPOINT = "https://api.horus.sh";
29088
- var DEFAULT_REPLAY_WINDOW_MS = 15e3;
29089
- var DEFAULT_MAX_CONSOLE_ENTRIES = 200;
29090
- var DEFAULT_MAX_NETWORK_ENTRIES = 100;
29091
- var MAX_ERROR_ENTRIES = 100;
29092
- var CONSOLE_TAIL_SIZE = 50;
29093
- var NETWORK_TAIL_SIZE = 30;
29094
- var MAX_SERIALIZED_ARG_CHARS = 2e3;
29095
- var MAX_DOM_BYTES = 2 * 1024 * 1024;
29096
- var MAX_TARGET_TEXT_CHARS = 200;
29097
- var SCREENSHOT_TIMEOUT_MS = 2e4;
29098
- var SCREENSHOT_MAX_SCALE = 2;
29099
- var SCREENSHOT_MAX_PIXELS = 25e5;
29100
- var AUTO_CAPTURE_COOLDOWN_MS = 10 * 60 * 1e3;
29101
- var AUTO_CAPTURE_SESSION_CAP = 5;
29102
- var MAX_MANUAL_RECORDING_MS = 12e4;
29103
- var MAX_MANUAL_RECORDING_EVENTS = 1e4;
29104
- var LENS_OVERLAY_HOST_ID = "__lens_root__";
29105
29339
  function autoCaptureSignature(message) {
29106
29340
  return message.toLowerCase().replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g, "<uuid>").replace(/\d+/g, "<n>").replace(/\s+/g, " ").trim();
29107
29341
  }
@@ -29180,189 +29414,6 @@ function startAutoCapture(client, options = {}) {
29180
29414
  window.removeEventListener("unhandledrejection", onRejection);
29181
29415
  };
29182
29416
  }
29183
- var RingBuffer = class {
29184
- constructor(capacity) {
29185
- this.capacity = capacity;
29186
- if (capacity <= 0) throw new Error("RingBuffer capacity must be > 0");
29187
- }
29188
- capacity;
29189
- items = [];
29190
- push(item) {
29191
- this.items.push(item);
29192
- if (this.items.length > this.capacity) {
29193
- this.items.splice(0, this.items.length - this.capacity);
29194
- }
29195
- }
29196
- /** Snapshot of current contents, oldest first. */
29197
- drain() {
29198
- return [...this.items];
29199
- }
29200
- tail(count) {
29201
- return this.items.slice(-count);
29202
- }
29203
- clear() {
29204
- this.items = [];
29205
- }
29206
- get size() {
29207
- return this.items.length;
29208
- }
29209
- };
29210
- var HttpError = class extends Error {
29211
- constructor(status, message) {
29212
- super(message);
29213
- this.status = status;
29214
- this.name = "HttpError";
29215
- }
29216
- status;
29217
- };
29218
- var Transport = class {
29219
- constructor(config2) {
29220
- this.config = config2;
29221
- }
29222
- config;
29223
- async initiate(assets) {
29224
- const body = { accessId: this.config.accessId, assets };
29225
- return this.postJson(
29226
- `${this.config.endpoint}/v1/reports/initiate`,
29227
- body,
29228
- { retries: 0 }
29229
- );
29230
- }
29231
- /** Record a late-uploaded asset (background screenshot) on a finalized report. */
29232
- async attachAsset(reportId, asset) {
29233
- const body = { accessId: this.config.accessId, ...asset };
29234
- await this.postJson(`${this.config.endpoint}/v1/reports/${encodeURIComponent(reportId)}/assets`, body);
29235
- }
29236
- async uploadAsset(upload, blob2, signal) {
29237
- const response = await fetch(upload.url, {
29238
- method: upload.method,
29239
- headers: upload.headers,
29240
- body: blob2,
29241
- signal
29242
- });
29243
- if (!response.ok) {
29244
- throw new Error(`Asset upload failed (${upload.kind}): ${response.status}`);
29245
- }
29246
- }
29247
- async finalize(reportId, metadata, uploadedAssets) {
29248
- const body = { accessId: this.config.accessId, metadata, uploadedAssets };
29249
- try {
29250
- return await this.postJson(
29251
- `${this.config.endpoint}/v1/reports/${reportId}/finalize`,
29252
- body
29253
- );
29254
- } catch (err) {
29255
- if (err instanceof HttpError && err.status === 409) {
29256
- return { reportId, status: "submitted" };
29257
- }
29258
- throw err;
29259
- }
29260
- }
29261
- async postJson(url2, body, opts = {}) {
29262
- const retries = opts.retries ?? 1;
29263
- let lastError;
29264
- for (let attempt = 0; attempt <= retries; attempt++) {
29265
- try {
29266
- const response = await fetch(url2, {
29267
- method: "POST",
29268
- headers: { "content-type": "application/json" },
29269
- body: JSON.stringify(body)
29270
- });
29271
- if (!response.ok) {
29272
- const text = await response.text().catch(() => "");
29273
- throw new HttpError(
29274
- response.status,
29275
- `Request to ${url2} failed: ${response.status} ${text}`.trim()
29276
- );
29277
- }
29278
- return await response.json();
29279
- } catch (err) {
29280
- lastError = err;
29281
- if (this.config.debug) {
29282
- console.warn(`[lens] request failed (attempt ${attempt + 1}/${retries + 1})`, err);
29283
- }
29284
- }
29285
- }
29286
- throw lastError;
29287
- }
29288
- };
29289
- var LEVELS = ["log", "info", "warn", "error", "debug"];
29290
- function cap(value) {
29291
- return value.length > MAX_SERIALIZED_ARG_CHARS ? `${value.slice(0, MAX_SERIALIZED_ARG_CHARS)}\u2026[truncated]` : value;
29292
- }
29293
- function serializeArg2(value) {
29294
- if (value === null) return "null";
29295
- if (value === void 0) return "undefined";
29296
- const type = typeof value;
29297
- if (type === "string") return cap(value);
29298
- if (type === "number" || type === "boolean" || type === "bigint" || type === "symbol") {
29299
- return String(value);
29300
- }
29301
- if (type === "function") {
29302
- return `[Function: ${value.name || "anonymous"}]`;
29303
- }
29304
- if (value instanceof Error) {
29305
- return cap(value.stack ? `${value.name}: ${value.message}
29306
- ${value.stack}` : `${value.name}: ${value.message}`);
29307
- }
29308
- try {
29309
- const seen = /* @__PURE__ */ new WeakSet();
29310
- const json2 = JSON.stringify(value, (_key, val) => {
29311
- if (typeof val === "bigint") return String(val);
29312
- if (typeof val === "function") return "[Function]";
29313
- if (typeof val === "object" && val !== null) {
29314
- if (seen.has(val)) return "[Circular]";
29315
- seen.add(val);
29316
- }
29317
- return val;
29318
- });
29319
- return cap(json2 ?? String(value));
29320
- } catch {
29321
- return cap(String(value));
29322
- }
29323
- }
29324
- function firstErrorStack(args) {
29325
- for (const arg of args) {
29326
- if (arg instanceof Error && arg.stack) return arg.stack;
29327
- }
29328
- return void 0;
29329
- }
29330
- function installConsole(consoleBuffer, errorBuffer) {
29331
- if (typeof console === "undefined") return () => {
29332
- };
29333
- const target = console;
29334
- const restores = [];
29335
- for (const level of LEVELS) {
29336
- const original = target[level];
29337
- if (typeof original !== "function") continue;
29338
- const wrapped = (...args) => {
29339
- try {
29340
- const serialized = args.map(serializeArg2);
29341
- const timestamp = Date.now();
29342
- consoleBuffer.push({ level, args: serialized, timestamp });
29343
- if (level === "error") {
29344
- const stack = firstErrorStack(args);
29345
- const entry = {
29346
- message: serialized.join(" ") || "console.error",
29347
- source: "console.error",
29348
- timestamp
29349
- };
29350
- if (stack) entry.stack = stack;
29351
- errorBuffer.push(entry);
29352
- }
29353
- } catch {
29354
- }
29355
- original.apply(console, args);
29356
- };
29357
- target[level] = wrapped;
29358
- restores.push(() => {
29359
- target[level] = original;
29360
- });
29361
- }
29362
- return () => {
29363
- for (const restore of restores) restore();
29364
- };
29365
- }
29366
29417
  function nowMs() {
29367
29418
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
29368
29419
  }
@@ -29649,6 +29700,14 @@ var ReplayRecorder = class {
29649
29700
  }
29650
29701
  };
29651
29702
  var SVG_RASTERIZE_TIMEOUT_MS = 3e3;
29703
+ function isOffscreenMedia(node2) {
29704
+ if (!(node2 instanceof HTMLImageElement) && !(node2 instanceof HTMLIFrameElement) && !(typeof HTMLVideoElement !== "undefined" && node2 instanceof HTMLVideoElement)) {
29705
+ return false;
29706
+ }
29707
+ const r = node2.getBoundingClientRect();
29708
+ if (r.width === 0 && r.height === 0) return false;
29709
+ return r.bottom < 0 || r.top > window.innerHeight || r.right < 0 || r.left > window.innerWidth;
29710
+ }
29652
29711
  function computeCropRect(crop, scale, rasterWidth, rasterHeight) {
29653
29712
  let sx = Math.round(crop.x * scale);
29654
29713
  let sy = Math.round(crop.y * scale);
@@ -29690,12 +29749,22 @@ async function captureScreenshot(excludeHost, crop, composite) {
29690
29749
  transformOrigin: "top left"
29691
29750
  }
29692
29751
  } : {},
29693
- // Exclude the overlay from the capture CLONE — never hide the live node.
29694
- filter: (node2) => node2 !== excludeHost,
29695
- // Per-resource fetch budget: a single slow cross-origin image must not
29696
- // consume the whole capture window.
29752
+ // Exclude the overlay from the capture CLONE (never hide the live node),
29753
+ // and — for a plain viewport report — drop images/iframes entirely outside
29754
+ // the viewport so we don't fetch dozens of off-screen cross-origin assets.
29755
+ filter: (node2) => {
29756
+ if (node2 === excludeHost) return false;
29757
+ if (viewportOnly && isOffscreenMedia(node2)) return false;
29758
+ return true;
29759
+ },
29760
+ // Skip web-font download/embed. On font-heavy pages (multiple custom
29761
+ // families) this is a dominant, flaky cost; fallback fonts are fine for a
29762
+ // bug-report screenshot and capture becomes far more reliable.
29763
+ font: false,
29697
29764
  fetch: { requestInit: { cache: "force-cache" }, bypassingCache: false },
29698
- timeout: SCREENSHOT_TIMEOUT_MS - 1e3
29765
+ // PER-RESOURCE cap (NOT the overall budget): a hanging asset fails fast to
29766
+ // a placeholder instead of stalling the whole capture.
29767
+ timeout: SCREENSHOT_RESOURCE_TIMEOUT_MS
29699
29768
  });
29700
29769
  const started = Date.now();
29701
29770
  const timeout = new Promise((resolve) => {