@merittdev/horus-lens 0.0.3 → 0.1.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.
- package/README.md +9 -47
- package/dist/browser.cjs +84 -85
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +2 -4
- package/dist/{chunk-CTBY5XIN.js → chunk-CRV65EEY.js} +40 -2
- package/dist/chunk-CRV65EEY.js.map +1 -0
- package/dist/{chunk-XRONR4GR.js → chunk-UB7YYHI4.js} +13672 -968
- package/dist/chunk-UB7YYHI4.js.map +1 -0
- package/dist/index.cjs +46 -111
- package/dist/index.cjs.map +1 -1
- package/dist/{index.d-BA540Fae.d.ts → index.d-DeLYS02n.d.ts} +13 -0
- package/dist/{index.d-DOCLVJGS.d.cts → index.d-sNoEEgRg.d.cts} +13 -0
- package/dist/index.d.cts +14 -165
- package/dist/index.d.ts +14 -165
- package/dist/index.js +1 -29
- package/dist/lens.min.js +31 -31
- package/dist/lens.min.js.map +1 -1
- package/dist/react.cjs +84 -85
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -4
- package/dist/react.js.map +1 -1
- package/package.json +6 -19
- package/dist/chunk-CTBY5XIN.js.map +0 -1
- package/dist/chunk-DWOH2ENF.js +0 -17613
- package/dist/chunk-DWOH2ENF.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-XRONR4GR.js.map +0 -1
- package/dist/dashboard.cjs +0 -20124
- package/dist/dashboard.cjs.map +0 -1
- package/dist/dashboard.css +0 -2456
- package/dist/dashboard.d.cts +0 -791
- package/dist/dashboard.d.ts +0 -791
- package/dist/dashboard.js +0 -2584
- package/dist/dashboard.js.map +0 -1
- package/dist/rrweb-EYUUZIYR.js +0 -30
- package/dist/rrweb-EYUUZIYR.js.map +0 -1
- package/dist/server.cjs +0 -109
- package/dist/server.cjs.map +0 -1
- package/dist/server.d.cts +0 -59
- package/dist/server.d.ts +0 -59
- package/dist/server.js +0 -82
- package/dist/server.js.map +0 -1
package/README.md
CHANGED
|
@@ -54,51 +54,14 @@ Lens('app', { release: 'v2.14.3', featureFlags: { newCheckout: true } });
|
|
|
54
54
|
Lens('open'); // open the report panel programmatically
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
###
|
|
57
|
+
### Reviewing reports
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
export default function Page() {
|
|
67
|
-
return <LensPageClient config={{ projects: JSON.parse(process.env.LENS_PROJECTS ?? '[]') }} />;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// LensPageClient.tsx — client component
|
|
71
|
-
'use client'
|
|
72
|
-
import { LensProvider, LensAdmin, type LensAdminConfig } from '@merittdev/horus-lens/dashboard';
|
|
73
|
-
import '@merittdev/horus-lens/dist/dashboard.css';
|
|
74
|
-
|
|
75
|
-
export default function LensPageClient({ config }: { config: LensAdminConfig }) {
|
|
76
|
-
return (
|
|
77
|
-
<LensProvider config={config}>
|
|
78
|
-
<LensAdmin />
|
|
79
|
-
</LensProvider>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
LENS_PROJECTS=[{"name":"front","accessId":"lai_…","secretKey":"las_…"}]
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
No projects yet? Pass `config={{ projects: [], adminToken: process.env.LENS_ADMIN_TOKEN }}`
|
|
89
|
-
and the built-in wizard mints the first project and hands you that env line.
|
|
90
|
-
|
|
91
|
-
You get the full surface: reports (search + pagination + shareable
|
|
92
|
-
`?report=` deep links), replay player, integrations (OAuth to
|
|
93
|
-
Linear/GitHub/Jira with provider-fed dropdowns), project switcher, onboarding.
|
|
94
|
-
Light-first monochrome; dark mode via `theme="dark"`, `.dark`, or
|
|
95
|
-
`[data-theme="dark"]`.
|
|
96
|
-
|
|
97
|
-
Prefer keys to never reach the browser at all (shared machines, stricter
|
|
98
|
-
compliance)? Use proxy mode instead: mount the package's server handlers and
|
|
99
|
-
drop the `config` prop — see `@merittdev/horus-lens/server`
|
|
100
|
-
(`createLensProxy` + `createLensProjectsRoute`). Piecemeal components
|
|
101
|
-
(`ReportsTable`, `ReportDetail`, …) remain exported for custom layouts.
|
|
59
|
+
This package is the **capture SDK only** — there's no dashboard to install. Once
|
|
60
|
+
a report is submitted, review it in the **horus-cloud** workspace under its
|
|
61
|
+
"Lens" section: reports (search + pagination + shareable deep links), the replay
|
|
62
|
+
player, and issue-tracker integrations (OAuth to Linear/GitHub/Jira) all live
|
|
63
|
+
there. Your `las_…` secret stays server-side in horus-cloud and never reaches the
|
|
64
|
+
browser.
|
|
102
65
|
|
|
103
66
|
## When does the overlay appear?
|
|
104
67
|
|
|
@@ -111,9 +74,8 @@ import on the same check.
|
|
|
111
74
|
|
|
112
75
|
- `lai_…` **access id** — public by design (like a GA measurement id). Enforced by a
|
|
113
76
|
per-project origin allowlist and rate limits on the API.
|
|
114
|
-
- `las_…` **secret key** — full project access.
|
|
115
|
-
|
|
116
|
-
the management API — never a public page.
|
|
77
|
+
- `las_…` **secret key** — full project access. Held server-side by horus-cloud
|
|
78
|
+
(the "Lens" workspace section), CI, or the management API — never a public page.
|
|
117
79
|
|
|
118
80
|
The SDK talks to the hosted Lens API out of the box — no endpoint configuration.
|
|
119
81
|
Self-hosting? Pass `endpoint` (React/script `init` config) to point anywhere else.
|
package/dist/browser.cjs
CHANGED
|
@@ -29000,93 +29000,11 @@ var ApiErrorSchema = external_exports.object({
|
|
|
29000
29000
|
error: external_exports.string(),
|
|
29001
29001
|
message: external_exports.string()
|
|
29002
29002
|
});
|
|
29003
|
-
var IntegrationProviderSchema = external_exports.enum(["github", "jira", "linear"]);
|
|
29004
|
-
var IntegrationStatusSchema = external_exports.enum(["disconnected", "pending", "connected"]);
|
|
29005
|
-
var IntegrationConfigSchema = external_exports.object({
|
|
29006
|
-
/** github: "owner/repo" issues target. */
|
|
29007
|
-
repo: external_exports.string().optional(),
|
|
29008
|
-
/** linear: team key (e.g. "ENG"); defaults to the first team when unset. */
|
|
29009
|
-
teamKey: external_exports.string().optional(),
|
|
29010
|
-
/** jira: project key (e.g. "BUG"). Required before issues can be created. */
|
|
29011
|
-
projectKey: external_exports.string().optional(),
|
|
29012
|
-
/** jira: issue type name. Defaults to "Bug", falling back to "Task". */
|
|
29013
|
-
issueType: external_exports.string().optional()
|
|
29014
|
-
});
|
|
29015
|
-
var IntegrationSchema = external_exports.object({
|
|
29016
|
-
provider: IntegrationProviderSchema,
|
|
29017
|
-
status: IntegrationStatusSchema,
|
|
29018
|
-
account: external_exports.string().nullable().default(null),
|
|
29019
|
-
active: external_exports.boolean().default(false),
|
|
29020
|
-
config: IntegrationConfigSchema.default({})
|
|
29021
|
-
});
|
|
29022
|
-
var S3ConfigInputSchema = external_exports.object({
|
|
29023
|
-
bucket: external_exports.string().min(1),
|
|
29024
|
-
region: external_exports.string().min(1),
|
|
29025
|
-
endpoint: external_exports.string().url().optional(),
|
|
29026
|
-
accessKeyId: external_exports.string().min(1),
|
|
29027
|
-
secretAccessKey: external_exports.string().min(1),
|
|
29028
|
-
forcePathStyle: external_exports.boolean().optional()
|
|
29029
|
-
});
|
|
29030
|
-
var S3ConfigPublicSchema = external_exports.object({
|
|
29031
|
-
bucket: external_exports.string(),
|
|
29032
|
-
region: external_exports.string(),
|
|
29033
|
-
endpoint: external_exports.string().optional(),
|
|
29034
|
-
forcePathStyle: external_exports.boolean().optional(),
|
|
29035
|
-
/** e.g. "AKIA…F3D2" */
|
|
29036
|
-
accessKeyIdMasked: external_exports.string()
|
|
29037
|
-
});
|
|
29038
|
-
var ProjectSchema = external_exports.object({
|
|
29039
|
-
id: external_exports.string(),
|
|
29040
|
-
name: external_exports.string(),
|
|
29041
|
-
accessId: external_exports.string(),
|
|
29042
|
-
allowedOrigins: external_exports.array(external_exports.string()),
|
|
29043
|
-
/** Active issue destination; null until onboarding picks one. */
|
|
29044
|
-
destination: IntegrationProviderSchema.nullable(),
|
|
29045
|
-
/**
|
|
29046
|
-
* The customer dashboard page hosting the Lens components (e.g.
|
|
29047
|
-
* "https://admin.acme.com/lens"). Auto-created issues deep-link reports as
|
|
29048
|
-
* `<dashboardUrl>?report=<id>` when set.
|
|
29049
|
-
*/
|
|
29050
|
-
dashboardUrl: external_exports.string().nullable().default(null),
|
|
29051
|
-
s3: S3ConfigPublicSchema.nullable(),
|
|
29052
|
-
onboardedAt: external_exports.string().nullable(),
|
|
29053
|
-
createdAt: external_exports.string()
|
|
29054
|
-
});
|
|
29055
|
-
var CreateProjectRequestSchema = external_exports.object({
|
|
29056
|
-
name: external_exports.string().min(1).max(120),
|
|
29057
|
-
allowedOrigins: external_exports.array(external_exports.string()).default([])
|
|
29058
|
-
});
|
|
29059
|
-
var CreateProjectResponseSchema = external_exports.object({
|
|
29060
|
-
project: ProjectSchema,
|
|
29061
|
-
accessId: external_exports.string(),
|
|
29062
|
-
accessSecret: external_exports.string()
|
|
29063
|
-
});
|
|
29064
|
-
var UpdateProjectRequestSchema = external_exports.object({
|
|
29065
|
-
name: external_exports.string().min(1).max(120).optional(),
|
|
29066
|
-
allowedOrigins: external_exports.array(external_exports.string()).optional(),
|
|
29067
|
-
onboarded: external_exports.boolean().optional(),
|
|
29068
|
-
dashboardUrl: external_exports.string().url().nullable().optional()
|
|
29069
|
-
});
|
|
29070
|
-
var ListIntegrationsResponseSchema = external_exports.object({
|
|
29071
|
-
integrations: external_exports.array(IntegrationSchema)
|
|
29072
|
-
});
|
|
29073
|
-
var ConnectIntegrationRequestSchema = external_exports.object({
|
|
29074
|
-
/**
|
|
29075
|
-
* Where the provider's OAuth dance should land the user afterwards —
|
|
29076
|
-
* the calling dashboard's own URL. Packed into the signed `state` so the
|
|
29077
|
-
* fixed API callback can redirect back to any consumer dashboard.
|
|
29078
|
-
*/
|
|
29079
|
-
returnUrl: external_exports.string().url().optional()
|
|
29080
|
-
});
|
|
29081
|
-
var ConnectIntegrationResponseSchema = external_exports.object({
|
|
29082
|
-
authorizeUrl: external_exports.string(),
|
|
29083
|
-
provider: IntegrationProviderSchema
|
|
29084
|
-
});
|
|
29085
29003
|
|
|
29086
29004
|
// ../core/dist/index.js
|
|
29087
29005
|
var SDK_NAME = "@merittdev/horus-lens";
|
|
29088
29006
|
var SDK_VERSION = "0.0.1";
|
|
29089
|
-
var DEFAULT_ENDPOINT = "https://api
|
|
29007
|
+
var DEFAULT_ENDPOINT = "https://api.horus.sh";
|
|
29090
29008
|
var DEFAULT_REPLAY_WINDOW_MS = 15e3;
|
|
29091
29009
|
var DEFAULT_MAX_CONSOLE_ENTRIES = 200;
|
|
29092
29010
|
var DEFAULT_MAX_NETWORK_ENTRIES = 100;
|
|
@@ -29435,6 +29353,11 @@ var ReplayRecorder = class {
|
|
|
29435
29353
|
emit: (event, isCheckout) => this.onEmit(event, isCheckout),
|
|
29436
29354
|
checkoutEveryNms: this.windowMs,
|
|
29437
29355
|
recordCanvas: false,
|
|
29356
|
+
// Inline same-origin + CORS images as data URIs at snapshot time so the
|
|
29357
|
+
// dashboard replay paints on a different origin (tainted cross-origin
|
|
29358
|
+
// images fall back to their URL, which is fine for public CDNs).
|
|
29359
|
+
inlineImages: true,
|
|
29360
|
+
collectFonts: true,
|
|
29438
29361
|
// Keep the Lens overlay itself out of every recording.
|
|
29439
29362
|
blockSelector: `#${LENS_OVERLAY_HOST_ID}`
|
|
29440
29363
|
});
|
|
@@ -29541,7 +29464,7 @@ var ReplayRecorder = class {
|
|
|
29541
29464
|
console.warn("[lens] rrweb recording unavailable; replay disabled", err);
|
|
29542
29465
|
}
|
|
29543
29466
|
};
|
|
29544
|
-
async function captureScreenshot(excludeHost) {
|
|
29467
|
+
async function captureScreenshot(excludeHost, crop) {
|
|
29545
29468
|
if (typeof document === "undefined" || typeof window === "undefined") return null;
|
|
29546
29469
|
try {
|
|
29547
29470
|
const viewportPixels = window.innerWidth * window.innerHeight || 1;
|
|
@@ -29575,12 +29498,50 @@ async function captureScreenshot(excludeHost) {
|
|
|
29575
29498
|
);
|
|
29576
29499
|
return null;
|
|
29577
29500
|
}
|
|
29501
|
+
if (crop) {
|
|
29502
|
+
const cropped = await cropBlob(blob2, crop, scale);
|
|
29503
|
+
return cropped ?? blob2;
|
|
29504
|
+
}
|
|
29578
29505
|
return blob2;
|
|
29579
29506
|
} catch (err) {
|
|
29580
29507
|
console.warn("[lens] screenshot capture failed:", err);
|
|
29581
29508
|
return null;
|
|
29582
29509
|
}
|
|
29583
29510
|
}
|
|
29511
|
+
async function cropBlob(blob2, crop, scale) {
|
|
29512
|
+
try {
|
|
29513
|
+
if (typeof createImageBitmap !== "function" || typeof document === "undefined") return null;
|
|
29514
|
+
const bitmap = await createImageBitmap(blob2);
|
|
29515
|
+
let sx = Math.round(crop.x * scale);
|
|
29516
|
+
let sy = Math.round(crop.y * scale);
|
|
29517
|
+
let sw = Math.round(crop.width * scale);
|
|
29518
|
+
let sh = Math.round(crop.height * scale);
|
|
29519
|
+
sx = Math.max(0, Math.min(sx, bitmap.width));
|
|
29520
|
+
sy = Math.max(0, Math.min(sy, bitmap.height));
|
|
29521
|
+
sw = Math.min(sw, bitmap.width - sx);
|
|
29522
|
+
sh = Math.min(sh, bitmap.height - sy);
|
|
29523
|
+
if (sw <= 0 || sh <= 0) {
|
|
29524
|
+
bitmap.close?.();
|
|
29525
|
+
return null;
|
|
29526
|
+
}
|
|
29527
|
+
const canvas = document.createElement("canvas");
|
|
29528
|
+
canvas.width = sw;
|
|
29529
|
+
canvas.height = sh;
|
|
29530
|
+
const ctx = canvas.getContext("2d");
|
|
29531
|
+
if (!ctx) {
|
|
29532
|
+
bitmap.close?.();
|
|
29533
|
+
return null;
|
|
29534
|
+
}
|
|
29535
|
+
ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
29536
|
+
bitmap.close?.();
|
|
29537
|
+
return await new Promise((resolve) => {
|
|
29538
|
+
canvas.toBlob((out) => resolve(out), "image/jpeg", 0.82);
|
|
29539
|
+
});
|
|
29540
|
+
} catch (err) {
|
|
29541
|
+
console.warn("[lens] screenshot crop failed:", err);
|
|
29542
|
+
return null;
|
|
29543
|
+
}
|
|
29544
|
+
}
|
|
29584
29545
|
function parseBrowser(ua) {
|
|
29585
29546
|
if (!ua) return void 0;
|
|
29586
29547
|
const checks = [
|
|
@@ -29845,7 +29806,7 @@ var LensClient = class {
|
|
|
29845
29806
|
const wantErrors = include.errors !== false;
|
|
29846
29807
|
const wantScreenshot = include.screenshot !== false;
|
|
29847
29808
|
const wantDom = include.dom !== false;
|
|
29848
|
-
const screenshotPromise = this.screenshotEnabled && wantScreenshot ? captureScreenshot(input2.excludeFromScreenshot) : Promise.resolve(null);
|
|
29809
|
+
const screenshotPromise = this.screenshotEnabled && wantScreenshot ? captureScreenshot(input2.excludeFromScreenshot, input2.screenshotCrop) : Promise.resolve(null);
|
|
29849
29810
|
const replayEvents = wantReplay ? this.replay?.snapshot() ?? [] : [];
|
|
29850
29811
|
const errorEntries = this.errorBuffer.drain();
|
|
29851
29812
|
const consoleTail = this.consoleBuffer.tail(CONSOLE_TAIL_SIZE);
|
|
@@ -30081,6 +30042,13 @@ var Annotator = class {
|
|
|
30081
30042
|
list() {
|
|
30082
30043
|
return this.annotations.map((a) => ({ ...a, viewport: { ...a.viewport } }));
|
|
30083
30044
|
}
|
|
30045
|
+
/**
|
|
30046
|
+
* Live rendered rect elements (fixed-positioned, viewport coords). Exposed so
|
|
30047
|
+
* the overlay can derive a document-space bounding box for screenshot crop.
|
|
30048
|
+
*/
|
|
30049
|
+
rects() {
|
|
30050
|
+
return [...this.renderedRects];
|
|
30051
|
+
}
|
|
30084
30052
|
clear() {
|
|
30085
30053
|
this.annotations = [];
|
|
30086
30054
|
for (const rect of this.renderedRects) rect.remove();
|
|
@@ -31358,11 +31326,13 @@ var Overlay = class {
|
|
|
31358
31326
|
}
|
|
31359
31327
|
async submit(comment2, include) {
|
|
31360
31328
|
const annotations = this.annotator.list();
|
|
31329
|
+
const screenshotCrop = this.computeAnnotationCrop();
|
|
31361
31330
|
const result2 = await this.deps.client.submitReport({
|
|
31362
31331
|
comment: comment2 || void 0,
|
|
31363
31332
|
annotations,
|
|
31364
31333
|
target: this.target ?? void 0,
|
|
31365
31334
|
excludeFromScreenshot: this.host,
|
|
31335
|
+
screenshotCrop,
|
|
31366
31336
|
include
|
|
31367
31337
|
});
|
|
31368
31338
|
this.target = null;
|
|
@@ -31371,6 +31341,35 @@ var Overlay = class {
|
|
|
31371
31341
|
this.deps.client.discardRecording();
|
|
31372
31342
|
return result2;
|
|
31373
31343
|
}
|
|
31344
|
+
/**
|
|
31345
|
+
* Union the marked rects into a single bounding box in DOCUMENT coordinates,
|
|
31346
|
+
* or `undefined` if nothing is marked (→ full-page screenshot). Each rect is
|
|
31347
|
+
* fixed-positioned, so getBoundingClientRect() gives viewport coords; add the
|
|
31348
|
+
* page scroll offset to convert to document space.
|
|
31349
|
+
*/
|
|
31350
|
+
computeAnnotationCrop() {
|
|
31351
|
+
const rects = this.annotator.rects();
|
|
31352
|
+
if (rects.length === 0) return void 0;
|
|
31353
|
+
const scrollX = window.scrollX || window.pageXOffset || 0;
|
|
31354
|
+
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
31355
|
+
let minX = Infinity;
|
|
31356
|
+
let minY = Infinity;
|
|
31357
|
+
let maxX = -Infinity;
|
|
31358
|
+
let maxY = -Infinity;
|
|
31359
|
+
for (const rect of rects) {
|
|
31360
|
+
const r = rect.getBoundingClientRect();
|
|
31361
|
+
const left = r.left + scrollX;
|
|
31362
|
+
const top = r.top + scrollY;
|
|
31363
|
+
minX = Math.min(minX, left);
|
|
31364
|
+
minY = Math.min(minY, top);
|
|
31365
|
+
maxX = Math.max(maxX, left + r.width);
|
|
31366
|
+
maxY = Math.max(maxY, top + r.height);
|
|
31367
|
+
}
|
|
31368
|
+
const width = maxX - minX;
|
|
31369
|
+
const height = maxY - minY;
|
|
31370
|
+
if (!(width > 0) || !(height > 0)) return void 0;
|
|
31371
|
+
return { x: minX, y: minY, width, height };
|
|
31372
|
+
}
|
|
31374
31373
|
dismiss() {
|
|
31375
31374
|
this.deps.onDismiss();
|
|
31376
31375
|
this.destroy();
|