browsertrack 0.0.1 → 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/.github/workflows/docs.yml +54 -0
- package/AGENTS.md +133 -0
- package/README.md +143 -0
- package/dist/chunk-6VA7GBAO.js +246 -0
- package/dist/chunk-6VA7GBAO.js.map +1 -0
- package/dist/chunk-G2Y3CXCY.js +659 -0
- package/dist/chunk-G2Y3CXCY.js.map +1 -0
- package/dist/chunk-ILRYKMME.js +1228 -0
- package/dist/chunk-ILRYKMME.js.map +1 -0
- package/dist/chunk-SKCMT2DE.js +2641 -0
- package/dist/chunk-SKCMT2DE.js.map +1 -0
- package/dist/chunk-SPCIROIU.js +616 -0
- package/dist/chunk-SPCIROIU.js.map +1 -0
- package/dist/cli/index.js +2942 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.cjs +2780 -0
- package/dist/client/index.d.ts +211 -0
- package/dist/client/index.js +20 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client.iife.js +630 -0
- package/dist/core/index.d.ts +86 -0
- package/dist/core/index.js +37 -0
- package/dist/core/index.js.map +1 -0
- package/dist/daemon/index.d.ts +58 -0
- package/dist/daemon/index.js +32 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/engine-CeT9URuN.d.ts +161 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +12 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/notes-CBvN91Wf.d.ts +260 -0
- package/dist/projects-CY8ungMt.d.ts +99 -0
- package/dist/server-Dd8NX2Mk.d.ts +55 -0
- package/docboot.config.js +16 -0
- package/docs/cli.md +51 -0
- package/docs/closed-loop-verification.md +57 -0
- package/docs/getting-started.md +122 -0
- package/docs/incidents-diagnostics.md +61 -0
- package/docs/index.md +46 -0
- package/docs/mcp-reference.md +128 -0
- package/docs/security-privacy.md +32 -0
- package/docs/visual-notes.md +67 -0
- package/examples/test-app/index.html +299 -0
- package/package.json +56 -7
- package/packages/cli/src/index.ts +413 -0
- package/packages/client/package.json +28 -0
- package/packages/client/src/breadcrumbs.ts +30 -0
- package/packages/client/src/client.ts +278 -0
- package/packages/client/src/commands/handler.ts +301 -0
- package/packages/client/src/config.ts +37 -0
- package/packages/client/src/index.ts +50 -0
- package/packages/client/src/interceptors/console.ts +68 -0
- package/packages/client/src/interceptors/interaction.ts +119 -0
- package/packages/client/src/interceptors/navigation.ts +93 -0
- package/packages/client/src/interceptors/network.ts +165 -0
- package/packages/client/src/interceptors/runtime.ts +65 -0
- package/packages/client/src/notes/inspector.ts +1553 -0
- package/packages/client/src/screenshot/browser-script-driver.ts +192 -0
- package/packages/client/src/screenshot/driver.ts +14 -0
- package/packages/client/src/transport/websocket.ts +194 -0
- package/packages/client/tsconfig.json +8 -0
- package/packages/core/package.json +26 -0
- package/packages/core/src/fingerprint.ts +120 -0
- package/packages/core/src/index.ts +9 -0
- package/packages/core/src/redaction.ts +174 -0
- package/packages/core/src/selector.ts +77 -0
- package/packages/core/src/types/commands.ts +101 -0
- package/packages/core/src/types/events.ts +108 -0
- package/packages/core/src/types/incidents.ts +54 -0
- package/packages/core/src/types/notes.ts +106 -0
- package/packages/core/src/types/probes.ts +44 -0
- package/packages/core/src/types/projects.ts +20 -0
- package/packages/core/tsconfig.json +8 -0
- package/packages/daemon/src/config.ts +29 -0
- package/packages/daemon/src/incidents/engine.ts +211 -0
- package/packages/daemon/src/index.ts +18 -0
- package/packages/daemon/src/notes/engine.ts +92 -0
- package/packages/daemon/src/notes/verification.ts +191 -0
- package/packages/daemon/src/server/daemon.ts +112 -0
- package/packages/daemon/src/server/http.ts +165 -0
- package/packages/daemon/src/server/ws.ts +246 -0
- package/packages/daemon/src/session/manager.ts +147 -0
- package/packages/daemon/src/storage/db.ts +742 -0
- package/packages/daemon/src/storage/screenshot-store.ts +49 -0
- package/packages/daemon/src/verification/engine.ts +264 -0
- package/packages/mcp/src/handlers.ts +398 -0
- package/packages/mcp/src/index.ts +3 -0
- package/packages/mcp/src/server.ts +86 -0
- package/packages/mcp/src/tools.ts +236 -0
- package/src/index.ts +4 -0
- package/test/client/interceptors.test.ts +130 -0
- package/test/core/fingerprint.test.ts +53 -0
- package/test/core/notes.test.ts +66 -0
- package/test/core/redaction.test.ts +60 -0
- package/test/daemon/incident-engine.test.ts +98 -0
- package/test/daemon/notes-storage.test.ts +130 -0
- package/test/daemon/notes-verification.test.ts +135 -0
- package/test/daemon/storage.test.ts +123 -0
- package/test/daemon/verification-engine.test.ts +88 -0
- package/test/e2e/daemon-mcp-e2e.test.ts +153 -0
- package/test/e2e/visual-notes-e2e.test.ts +205 -0
- package/test/mcp/handlers.test.ts +116 -0
- package/test/mcp/notes.test.ts +107 -0
- package/tsconfig.base.json +17 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +54 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { redact as visulimaRedact, standardRules, type Rules } from '@visulima/redact';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Security and Redaction Utilities powered by @visulima/redact
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const REDACTED_PLACEHOLDER = '[REDACTED]';
|
|
8
|
+
|
|
9
|
+
export const SENSITIVE_KEY_PATTERNS = [
|
|
10
|
+
/^authorization$/i,
|
|
11
|
+
/^cookie$/i,
|
|
12
|
+
/^set-cookie$/i,
|
|
13
|
+
/password/i,
|
|
14
|
+
/token/i,
|
|
15
|
+
/secret/i,
|
|
16
|
+
/api[-_]?key/i,
|
|
17
|
+
/access[-_]?token/i,
|
|
18
|
+
/refresh[-_]?token/i,
|
|
19
|
+
/credentials/i,
|
|
20
|
+
/private[-_]?key/i,
|
|
21
|
+
/ssn/i,
|
|
22
|
+
/credit[-_]?card/i,
|
|
23
|
+
/cvv/i,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const SENSITIVE_QUERY_PARAMS = [
|
|
27
|
+
'token',
|
|
28
|
+
'auth',
|
|
29
|
+
'key',
|
|
30
|
+
'apikey',
|
|
31
|
+
'api_key',
|
|
32
|
+
'secret',
|
|
33
|
+
'password',
|
|
34
|
+
'access_token',
|
|
35
|
+
'refresh_token',
|
|
36
|
+
'code',
|
|
37
|
+
'signature',
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Developer security rules for @visulima/redact
|
|
42
|
+
*/
|
|
43
|
+
export const BROWSER_SECURITY_RULES: Rules = [
|
|
44
|
+
{ deep: true, key: 'password', replacement: REDACTED_PLACEHOLDER },
|
|
45
|
+
{ deep: true, key: 'secret', replacement: REDACTED_PLACEHOLDER },
|
|
46
|
+
{ deep: true, key: 'token', replacement: REDACTED_PLACEHOLDER },
|
|
47
|
+
{ deep: true, key: 'authorization', replacement: REDACTED_PLACEHOLDER },
|
|
48
|
+
{ deep: true, key: 'cookie', replacement: REDACTED_PLACEHOLDER },
|
|
49
|
+
{ deep: true, key: 'set-cookie', replacement: REDACTED_PLACEHOLDER },
|
|
50
|
+
{ deep: true, key: 'apikey', replacement: REDACTED_PLACEHOLDER },
|
|
51
|
+
{ deep: true, key: 'api_key', replacement: REDACTED_PLACEHOLDER },
|
|
52
|
+
{ deep: true, key: 'creditcard', pattern: /(?:\d[ -]*?){13,16}/, replacement: REDACTED_PLACEHOLDER },
|
|
53
|
+
{ deep: true, key: 'cvv', replacement: REDACTED_PLACEHOLDER },
|
|
54
|
+
{ deep: true, key: 'ssn', pattern: /\b\d{3}-\d{2}-\d{4}\b/, replacement: REDACTED_PLACEHOLDER },
|
|
55
|
+
{ deep: true, key: 'awsid', pattern: /\bAKIA[0-9A-Z]{16}\b/, replacement: REDACTED_PLACEHOLDER },
|
|
56
|
+
{ deep: true, key: 'awskey', pattern: /\b[0-9a-zA-Z/+]{40}\b/, replacement: REDACTED_PLACEHOLDER },
|
|
57
|
+
{ deep: true, key: 'jwt', pattern: /\beyJ[0-9a-zA-Z_\-]*\.[0-9a-zA-Z_\-]*\.[0-9a-zA-Z_\-]*\b/, replacement: REDACTED_PLACEHOLDER },
|
|
58
|
+
{ deep: true, key: 'slack_token', pattern: /\bxox[baprs]-[0-9a-zA-Z]{10,48}\b/, replacement: REDACTED_PLACEHOLDER },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export { visulimaRedact, standardRules };
|
|
62
|
+
export type { Rules };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Checks if a property name matches any sensitive pattern
|
|
66
|
+
*/
|
|
67
|
+
export function isSensitiveKey(key: string): boolean {
|
|
68
|
+
if (!key) return false;
|
|
69
|
+
const cleaned = key.replace(/[-_]/g, '');
|
|
70
|
+
return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key) || pattern.test(cleaned));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Redacts sensitive query parameters from a URL string
|
|
75
|
+
*/
|
|
76
|
+
export function redactUrl(rawUrl: string): string {
|
|
77
|
+
if (!rawUrl) return rawUrl;
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const isRelative =
|
|
81
|
+
!rawUrl.startsWith('http://') &&
|
|
82
|
+
!rawUrl.startsWith('https://') &&
|
|
83
|
+
!rawUrl.startsWith('ws://') &&
|
|
84
|
+
!rawUrl.startsWith('wss://');
|
|
85
|
+
const base = 'http://localhost';
|
|
86
|
+
const parsed = new URL(rawUrl, base);
|
|
87
|
+
|
|
88
|
+
let changed = false;
|
|
89
|
+
for (const param of SENSITIVE_QUERY_PARAMS) {
|
|
90
|
+
if (parsed.searchParams.has(param)) {
|
|
91
|
+
parsed.searchParams.set(param, REDACTED_PLACEHOLDER);
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Also check all query params against isSensitiveKey
|
|
97
|
+
for (const key of Array.from(parsed.searchParams.keys())) {
|
|
98
|
+
if (isSensitiveKey(key)) {
|
|
99
|
+
parsed.searchParams.set(key, REDACTED_PLACEHOLDER);
|
|
100
|
+
changed = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!changed) return rawUrl;
|
|
105
|
+
|
|
106
|
+
let result = isRelative ? parsed.pathname + parsed.search + parsed.hash : parsed.toString();
|
|
107
|
+
// Normalize URL-encoded brackets %5BREDACTED%5D back to [REDACTED]
|
|
108
|
+
result = result.replace(/%5BREDACTED%5D/g, REDACTED_PLACEHOLDER);
|
|
109
|
+
return result;
|
|
110
|
+
} catch {
|
|
111
|
+
// If URL parsing fails, perform regex replace
|
|
112
|
+
let safe = rawUrl;
|
|
113
|
+
for (const param of SENSITIVE_QUERY_PARAMS) {
|
|
114
|
+
const reg = new RegExp(`([?&]${param}=)[^&#]+`, 'gi');
|
|
115
|
+
safe = safe.replace(reg, `$1${REDACTED_PLACEHOLDER}`);
|
|
116
|
+
}
|
|
117
|
+
return safe;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Redacts sensitive HTTP headers
|
|
123
|
+
*/
|
|
124
|
+
export function redactHeaders(headers: Record<string, string>): Record<string, string> {
|
|
125
|
+
if (!headers || typeof headers !== 'object') return {};
|
|
126
|
+
|
|
127
|
+
const safe: Record<string, string> = {};
|
|
128
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
129
|
+
if (isSensitiveKey(key)) {
|
|
130
|
+
safe[key] = REDACTED_PLACEHOLDER;
|
|
131
|
+
} else {
|
|
132
|
+
safe[key] = value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return safe;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Deeply redacts sensitive keys and payload data from an object or array,
|
|
140
|
+
* leveraging @visulima/redact rules combined with BrowserTrack URL/header sanitizers.
|
|
141
|
+
*/
|
|
142
|
+
export function redactSensitiveData<T = any>(data: T, maxDepth = 6, currentDepth = 0): T {
|
|
143
|
+
if (data === null || data === undefined) return data;
|
|
144
|
+
if (typeof data !== 'object') return data;
|
|
145
|
+
if (currentDepth > maxDepth) return '[DEPTH_EXCEEDED]' as any;
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(data)) {
|
|
148
|
+
return data.map((item) => redactSensitiveData(item, maxDepth, currentDepth + 1)) as any;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const result: Record<string, any> = {};
|
|
152
|
+
for (const [key, value] of Object.entries(data as Record<string, any>)) {
|
|
153
|
+
if (typeof value === 'object' && value !== null) {
|
|
154
|
+
result[key] = redactSensitiveData(value, maxDepth, currentDepth + 1);
|
|
155
|
+
} else if (isSensitiveKey(key)) {
|
|
156
|
+
result[key] = REDACTED_PLACEHOLDER;
|
|
157
|
+
} else if (typeof value === 'string') {
|
|
158
|
+
// Check if value is a URL containing secrets
|
|
159
|
+
if (value.startsWith('http://') || value.startsWith('https://') || value.includes('?')) {
|
|
160
|
+
result[key] = redactUrl(value);
|
|
161
|
+
} else {
|
|
162
|
+
try {
|
|
163
|
+
result[key] = visulimaRedact(value, BROWSER_SECURITY_RULES);
|
|
164
|
+
} catch {
|
|
165
|
+
result[key] = value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
result[key] = value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return result as T;
|
|
174
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic element selector generation and inspection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface SelectorOptions {
|
|
6
|
+
maxClasses?: number;
|
|
7
|
+
maxOuterHTMLLength?: number;
|
|
8
|
+
maxInnerTextLength?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds a clean, human-readable semantic selector for a DOM element.
|
|
13
|
+
* Prioritizes data-testid/data-test, id, semantic tag + specific class, or concise parent path.
|
|
14
|
+
*/
|
|
15
|
+
export function getSemanticSelector(element: HTMLElement | Element, options: SelectorOptions = {}): string {
|
|
16
|
+
if (!element || !element.tagName) return 'unknown';
|
|
17
|
+
|
|
18
|
+
const maxClasses = options.maxClasses ?? 2;
|
|
19
|
+
|
|
20
|
+
// 1. Check for standard test attributes
|
|
21
|
+
const testId =
|
|
22
|
+
element.getAttribute('data-testid') ||
|
|
23
|
+
element.getAttribute('data-test') ||
|
|
24
|
+
element.getAttribute('data-cy') ||
|
|
25
|
+
element.getAttribute('data-qa');
|
|
26
|
+
|
|
27
|
+
if (testId) {
|
|
28
|
+
return `[data-testid="${testId}"]`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Check for meaningful ID (skip dynamic UUID/numeric IDs)
|
|
32
|
+
const id = element.id;
|
|
33
|
+
if (id && !/^[0-9]+$/.test(id) && !/[0-9a-f]{8}-[0-9a-f]{4}/i.test(id)) {
|
|
34
|
+
return `#${id}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 3. Check for aria-label or name on buttons/inputs
|
|
38
|
+
const ariaLabel = element.getAttribute('aria-label');
|
|
39
|
+
if (ariaLabel && ariaLabel.length < 30) {
|
|
40
|
+
return `${element.tagName.toLowerCase()}[aria-label="${ariaLabel}"]`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const nameAttr = element.getAttribute('name');
|
|
44
|
+
if (nameAttr) {
|
|
45
|
+
return `${element.tagName.toLowerCase()}[name="${nameAttr}"]`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 4. Tag + curated classes
|
|
49
|
+
const tag = element.tagName.toLowerCase();
|
|
50
|
+
const classList = Array.from(element.classList || [])
|
|
51
|
+
.filter((c) => !c.startsWith('css-') && !c.startsWith('_') && !/^[a-z0-9]{5,}$/i.test(c)) // filter hashed utility classes
|
|
52
|
+
.slice(0, maxClasses);
|
|
53
|
+
|
|
54
|
+
if (classList.length > 0) {
|
|
55
|
+
const classStr = classList.map((c) => `.${c}`).join('');
|
|
56
|
+
return `${tag}${classStr}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 5. If generic element (div, span, p) without attributes, try parent context
|
|
60
|
+
if (element.parentElement && element.parentElement !== document.body && element.parentElement !== document.documentElement) {
|
|
61
|
+
const parentSelector = getSemanticSelector(element.parentElement, { maxClasses: 1 });
|
|
62
|
+
if (parentSelector && parentSelector !== 'unknown' && !parentSelector.includes('>')) {
|
|
63
|
+
return `${parentSelector} > ${tag}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return tag;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Truncates string to specified max length with ellipsis
|
|
72
|
+
*/
|
|
73
|
+
export function truncate(str: string | undefined | null, maxLength = 200): string {
|
|
74
|
+
if (!str) return '';
|
|
75
|
+
if (str.length <= maxLength) return str;
|
|
76
|
+
return str.slice(0, maxLength) + '...';
|
|
77
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ElementSummary } from './events.js';
|
|
2
|
+
|
|
3
|
+
export type CommandType =
|
|
4
|
+
| 'reload'
|
|
5
|
+
| 'navigate'
|
|
6
|
+
| 'get_page_state'
|
|
7
|
+
| 'query_element'
|
|
8
|
+
| 'capture_element'
|
|
9
|
+
| 'check_overflow'
|
|
10
|
+
| 'get_element_rect'
|
|
11
|
+
| 'get_element_style';
|
|
12
|
+
|
|
13
|
+
export interface ClientCommand<T = any> {
|
|
14
|
+
id: string;
|
|
15
|
+
type: CommandType;
|
|
16
|
+
params?: T;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ReloadParams {
|
|
20
|
+
force?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NavigateParams {
|
|
24
|
+
url: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface QueryElementParams {
|
|
28
|
+
selector: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CaptureElementParams {
|
|
32
|
+
selector?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface QueryElementResult {
|
|
36
|
+
exists: boolean;
|
|
37
|
+
visible?: boolean;
|
|
38
|
+
tag?: string;
|
|
39
|
+
id?: string;
|
|
40
|
+
classes?: string[];
|
|
41
|
+
boundingRect?: {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
top: number;
|
|
47
|
+
left: number;
|
|
48
|
+
bottom: number;
|
|
49
|
+
right: number;
|
|
50
|
+
};
|
|
51
|
+
innerText?: string;
|
|
52
|
+
outerHTML?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PageStateResult {
|
|
56
|
+
url: string;
|
|
57
|
+
route: string;
|
|
58
|
+
title: string;
|
|
59
|
+
readyState: string;
|
|
60
|
+
activeElement?: ElementSummary;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface CaptureElementResult {
|
|
64
|
+
dataUrl?: string;
|
|
65
|
+
format?: string;
|
|
66
|
+
width?: number;
|
|
67
|
+
height?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface OverflowCheckResult {
|
|
71
|
+
selector: string;
|
|
72
|
+
overflow: boolean;
|
|
73
|
+
viewportWidth: number;
|
|
74
|
+
viewportHeight: number;
|
|
75
|
+
rect?: {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
width: number;
|
|
79
|
+
height: number;
|
|
80
|
+
top: number;
|
|
81
|
+
left: number;
|
|
82
|
+
bottom: number;
|
|
83
|
+
right: number;
|
|
84
|
+
};
|
|
85
|
+
overflowRightPx: number;
|
|
86
|
+
overflowBottomPx: number;
|
|
87
|
+
parentOverflow?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ElementStyleResult {
|
|
91
|
+
selector: string;
|
|
92
|
+
styles: Record<string, string>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface CommandResponse<T = any> {
|
|
96
|
+
id: string;
|
|
97
|
+
ok: boolean;
|
|
98
|
+
result?: T;
|
|
99
|
+
error?: string;
|
|
100
|
+
reason?: string;
|
|
101
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type EventType =
|
|
2
|
+
| 'runtime_error'
|
|
3
|
+
| 'unhandled_rejection'
|
|
4
|
+
| 'console'
|
|
5
|
+
| 'fetch'
|
|
6
|
+
| 'xhr'
|
|
7
|
+
| 'navigation'
|
|
8
|
+
| 'interaction';
|
|
9
|
+
|
|
10
|
+
export type ConsoleLevel = 'error' | 'warn' | 'info' | 'log' | 'debug';
|
|
11
|
+
|
|
12
|
+
export interface ElementSummary {
|
|
13
|
+
selector: string;
|
|
14
|
+
tag: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
classes?: string[];
|
|
17
|
+
attributes?: Record<string, string>;
|
|
18
|
+
outerHTML?: string;
|
|
19
|
+
innerText?: string;
|
|
20
|
+
boundingRect?: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
top: number;
|
|
26
|
+
left: number;
|
|
27
|
+
bottom: number;
|
|
28
|
+
right: number;
|
|
29
|
+
};
|
|
30
|
+
visible: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Breadcrumb {
|
|
34
|
+
id?: string;
|
|
35
|
+
type: 'navigation' | 'click' | 'submit' | 'fetch' | 'xhr' | 'console' | 'error';
|
|
36
|
+
category?: string;
|
|
37
|
+
message: string;
|
|
38
|
+
timestamp: number;
|
|
39
|
+
level?: 'info' | 'warn' | 'error';
|
|
40
|
+
data?: Record<string, any>;
|
|
41
|
+
element?: ElementSummary;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface NetworkEvent {
|
|
45
|
+
id?: string;
|
|
46
|
+
url: string;
|
|
47
|
+
method: string;
|
|
48
|
+
status?: number;
|
|
49
|
+
statusText?: string;
|
|
50
|
+
durationMs: number;
|
|
51
|
+
error?: string;
|
|
52
|
+
aborted?: boolean;
|
|
53
|
+
timestamp: number;
|
|
54
|
+
requestHeaders?: Record<string, string>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface RuntimeErrorEvent {
|
|
58
|
+
message: string;
|
|
59
|
+
stack?: string;
|
|
60
|
+
filename?: string;
|
|
61
|
+
lineno?: number;
|
|
62
|
+
colno?: number;
|
|
63
|
+
errorType: string;
|
|
64
|
+
timestamp: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ConsoleEvent {
|
|
68
|
+
level: ConsoleLevel;
|
|
69
|
+
message: string;
|
|
70
|
+
args?: any[];
|
|
71
|
+
stack?: string;
|
|
72
|
+
timestamp: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface NavigationEvent {
|
|
76
|
+
from?: string;
|
|
77
|
+
to: string;
|
|
78
|
+
type: 'pushState' | 'replaceState' | 'popstate' | 'hashchange' | 'initial';
|
|
79
|
+
timestamp: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HelloMessage {
|
|
83
|
+
type: 'hello';
|
|
84
|
+
origin: string;
|
|
85
|
+
url: string;
|
|
86
|
+
title: string;
|
|
87
|
+
userAgent: string;
|
|
88
|
+
timestamp: number;
|
|
89
|
+
projectId?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ClientEventMessage {
|
|
93
|
+
type: 'event';
|
|
94
|
+
sessionId: string;
|
|
95
|
+
eventType: EventType;
|
|
96
|
+
payload:
|
|
97
|
+
| RuntimeErrorEvent
|
|
98
|
+
| ConsoleEvent
|
|
99
|
+
| NetworkEvent
|
|
100
|
+
| NavigationEvent
|
|
101
|
+
| Breadcrumb;
|
|
102
|
+
breadcrumbs?: Breadcrumb[];
|
|
103
|
+
lastElement?: ElementSummary;
|
|
104
|
+
route?: string;
|
|
105
|
+
url: string;
|
|
106
|
+
title?: string;
|
|
107
|
+
timestamp: number;
|
|
108
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Breadcrumb, ElementSummary, NetworkEvent } from './events.js';
|
|
2
|
+
|
|
3
|
+
export type IncidentStatus =
|
|
4
|
+
| 'OPEN'
|
|
5
|
+
| 'FIX_ATTEMPTED'
|
|
6
|
+
| 'VERIFYING'
|
|
7
|
+
| 'VERIFIED'
|
|
8
|
+
| 'FAILED'
|
|
9
|
+
| 'INCONCLUSIVE';
|
|
10
|
+
|
|
11
|
+
export type IncidentSeverity = 'error' | 'warn' | 'fatal';
|
|
12
|
+
|
|
13
|
+
export interface IncidentSource {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
column?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Incident {
|
|
20
|
+
id: string;
|
|
21
|
+
projectId: string;
|
|
22
|
+
sessionId: string;
|
|
23
|
+
type: string;
|
|
24
|
+
severity: IncidentSeverity;
|
|
25
|
+
message: string;
|
|
26
|
+
source: IncidentSource;
|
|
27
|
+
fingerprint: string;
|
|
28
|
+
route: string;
|
|
29
|
+
firstSeen: string;
|
|
30
|
+
lastSeen: string;
|
|
31
|
+
occurrences: number;
|
|
32
|
+
status: IncidentStatus;
|
|
33
|
+
stack?: string;
|
|
34
|
+
breadcrumbs: Breadcrumb[];
|
|
35
|
+
networkFailures: NetworkEvent[];
|
|
36
|
+
lastElement?: ElementSummary;
|
|
37
|
+
screenshots?: {
|
|
38
|
+
error?: string;
|
|
39
|
+
before?: string;
|
|
40
|
+
after?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface IncidentOccurrence {
|
|
45
|
+
id: string;
|
|
46
|
+
incidentId: string;
|
|
47
|
+
sessionId: string;
|
|
48
|
+
timestamp: string;
|
|
49
|
+
route: string;
|
|
50
|
+
url: string;
|
|
51
|
+
stack?: string;
|
|
52
|
+
breadcrumbs: Breadcrumb[];
|
|
53
|
+
lastElement?: ElementSummary;
|
|
54
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export type NoteType = 'element' | 'region' | 'page';
|
|
2
|
+
|
|
3
|
+
export type NoteStatus =
|
|
4
|
+
| 'OPEN'
|
|
5
|
+
| 'IN_PROGRESS'
|
|
6
|
+
| 'VERIFYING'
|
|
7
|
+
| 'READY_FOR_REVIEW'
|
|
8
|
+
| 'VERIFIED'
|
|
9
|
+
| 'RESOLVED'
|
|
10
|
+
| 'FAILED'
|
|
11
|
+
| 'INCONCLUSIVE';
|
|
12
|
+
|
|
13
|
+
export interface ViewportContext {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
devicePixelRatio: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ScrollContext {
|
|
20
|
+
scrollX: number;
|
|
21
|
+
scrollY: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DOMRectJson {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
top: number;
|
|
30
|
+
left: number;
|
|
31
|
+
bottom: number;
|
|
32
|
+
right: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface NoteTarget {
|
|
36
|
+
selector: string;
|
|
37
|
+
boundingRect: DOMRectJson;
|
|
38
|
+
visible: boolean;
|
|
39
|
+
confidence?: 'high' | 'medium' | 'low';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ElementContext {
|
|
43
|
+
selector: string;
|
|
44
|
+
tag: string;
|
|
45
|
+
attributes?: Record<string, string>;
|
|
46
|
+
outerHTML?: string;
|
|
47
|
+
innerText?: string;
|
|
48
|
+
parent?: {
|
|
49
|
+
selector: string;
|
|
50
|
+
tag: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RegionContext {
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface VisualNote {
|
|
62
|
+
id: string;
|
|
63
|
+
projectId: string;
|
|
64
|
+
sessionId: string;
|
|
65
|
+
type: NoteType;
|
|
66
|
+
message: string;
|
|
67
|
+
route: string;
|
|
68
|
+
url: string;
|
|
69
|
+
viewport: ViewportContext;
|
|
70
|
+
scroll: ScrollContext;
|
|
71
|
+
target?: NoteTarget;
|
|
72
|
+
elementContext?: ElementContext;
|
|
73
|
+
region?: RegionContext;
|
|
74
|
+
status: NoteStatus;
|
|
75
|
+
incidentId?: string;
|
|
76
|
+
screenshots?: {
|
|
77
|
+
original?: string;
|
|
78
|
+
after?: string;
|
|
79
|
+
};
|
|
80
|
+
createdAt: string;
|
|
81
|
+
updatedAt: string;
|
|
82
|
+
resolvedAt?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface NoteVerificationResult {
|
|
86
|
+
noteId: string;
|
|
87
|
+
status: NoteStatus;
|
|
88
|
+
checks: Array<{
|
|
89
|
+
type: string;
|
|
90
|
+
passed: boolean;
|
|
91
|
+
details?: string;
|
|
92
|
+
}>;
|
|
93
|
+
geometryDiff?: {
|
|
94
|
+
before?: DOMRectJson;
|
|
95
|
+
current?: DOMRectJson;
|
|
96
|
+
viewportWidth?: number;
|
|
97
|
+
overflowFixed?: boolean;
|
|
98
|
+
overflowPx?: number;
|
|
99
|
+
};
|
|
100
|
+
screenshots?: {
|
|
101
|
+
before?: string;
|
|
102
|
+
after?: string;
|
|
103
|
+
};
|
|
104
|
+
timestamp: string;
|
|
105
|
+
message?: string;
|
|
106
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { IncidentStatus } from './incidents.js';
|
|
2
|
+
|
|
3
|
+
export type ProbeType =
|
|
4
|
+
| 'element_exists'
|
|
5
|
+
| 'element_visible'
|
|
6
|
+
| 'text_contains'
|
|
7
|
+
| 'route_is'
|
|
8
|
+
| 'network_request_succeeded'
|
|
9
|
+
| 'no_incident';
|
|
10
|
+
|
|
11
|
+
export interface VerificationProbe {
|
|
12
|
+
type: ProbeType;
|
|
13
|
+
selector?: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
route?: string;
|
|
16
|
+
fingerprint?: string;
|
|
17
|
+
urlPattern?: string;
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface VerificationRecipe {
|
|
22
|
+
route?: string;
|
|
23
|
+
targetSelector?: string;
|
|
24
|
+
expect?: VerificationProbe[];
|
|
25
|
+
observationWindowMs?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ProbeResult {
|
|
29
|
+
type: ProbeType;
|
|
30
|
+
passed: boolean;
|
|
31
|
+
details?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface VerificationResult {
|
|
35
|
+
incidentId: string;
|
|
36
|
+
status: IncidentStatus;
|
|
37
|
+
checks: ProbeResult[];
|
|
38
|
+
screenshots?: {
|
|
39
|
+
before?: string;
|
|
40
|
+
after?: string;
|
|
41
|
+
};
|
|
42
|
+
timestamp: string;
|
|
43
|
+
message?: string;
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface Project {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
origin: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Session {
|
|
11
|
+
id: string;
|
|
12
|
+
projectId: string;
|
|
13
|
+
origin: string;
|
|
14
|
+
url: string;
|
|
15
|
+
title: string;
|
|
16
|
+
userAgent: string;
|
|
17
|
+
connectedAt: string;
|
|
18
|
+
lastSeenAt: string;
|
|
19
|
+
active: boolean;
|
|
20
|
+
}
|