@corbado/observe 1.8.0 → 1.9.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/analysis/client.mjs +82 -56
- package/dist/analysis/config-manager.mjs +30 -5
- package/dist/analysis/device-info-manager.mjs +16 -1
- package/dist/analysis/device.mjs +88 -10
- package/dist/analysis/handoff.mjs +7 -0
- package/dist/analysis/low-events/grouped-input-tracker.mjs +148 -33
- package/dist/analysis/low-events/input-tracker.mjs +138 -59
- package/dist/analysis/low-events/shared-window-observer.mjs +148 -77
- package/dist/analysis/low-events/viewport-batcher.mjs +3 -3
- package/dist/analysis/queue.mjs +75 -8
- package/dist/analysis/{low-events/input-paste-observer.mjs → readers/input-paste.mjs} +2 -2
- package/dist/analysis/readers/input.mjs +61 -0
- package/dist/analysis/readers/loader-global.mjs +15 -0
- package/dist/analysis/readers/network.mjs +14 -0
- package/dist/analysis/readers/page.mjs +30 -0
- package/dist/analysis/readers/storage.mjs +28 -0
- package/dist/analysis/{utils → readers}/web-lock.mjs +6 -2
- package/dist/analysis/readers/window-events.mjs +48 -0
- package/dist/analysis/storage/index.mjs +61 -9
- package/dist/analysis/store/event-store.mjs +34 -16
- package/dist/analysis/transports/fetch.mjs +45 -3
- package/dist/analysis/utils/email-link-operation-full.mjs +22 -2
- package/dist/analysis/utils/email-otp-operation-full.mjs +0 -1
- package/dist/analysis/utils/form-operation-full.mjs +0 -1
- package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +6 -6
- package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +9 -6
- package/dist/analysis/utils/passkey-login-operation-full.mjs +10 -5
- package/dist/analysis/utils/password-login-operation-full.mjs +8 -4
- package/dist/analysis/utils/password-manager-sampler.mjs +44 -4
- package/dist/analysis/utils/sms-otp-operation-full.mjs +0 -1
- package/dist/analysis/utils/totp-operation-full.mjs +0 -1
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +4 -4
- package/dist/index.d.mts +131 -19
- package/dist/index.d.ts +131 -19
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +5 -3
- package/tools/reader-meta-text.d.mts +2 -0
- package/tools/reader-meta-text.mjs +26 -0
- package/tools/strip-reader-meta.d.mts +33 -0
- package/tools/strip-reader-meta.mjs +131 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corbado/observe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"./dist/cdn/*"
|
|
9
9
|
],
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"tools"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"build": "tsup src/index.ts --format cjs,esm --dts --minify --define.__CORBADO_OBSERVE_SDK_VERSION__='\"'${CORBADO_OBSERVE_SDK_VERSION:-0.0.0-dev}'\"' && pnpm build:analysis",
|
|
@@ -19,12 +20,13 @@
|
|
|
19
20
|
"example:password-managers": "pnpm build:example:password-managers && pnpm dlx serve example/password-managers -p 4173",
|
|
20
21
|
"dev": "tsup src/index.ts --format cjs,esm --watch --dts",
|
|
21
22
|
"lint": "tsc --noEmit",
|
|
23
|
+
"check:browser-globals": "node scripts/check-browser-globals.mjs",
|
|
22
24
|
"docs": "tsx scripts/inject-docs.ts",
|
|
23
25
|
"test": "vitest run",
|
|
24
26
|
"test:migration": "pnpm build:cdn && playwright test --config e2e/playwright.config.ts",
|
|
25
27
|
"test:password-managers": "tsx --test e2e/password-managers/password-managers.test.ts",
|
|
26
28
|
"test:watch": "vitest",
|
|
27
|
-
"test:data-guards": "pnpm build && node scripts/check-data-guards.mjs",
|
|
29
|
+
"test:data-guards": "pnpm build && pnpm build:cdn && node scripts/check-data-guards.mjs",
|
|
28
30
|
"format": "prettier --write 'src/**/*.ts'",
|
|
29
31
|
"format:check": "prettier --check 'src/**/*.ts'"
|
|
30
32
|
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Rule for customer-facing reader metadata text (`title`, `group`): plain language a customer's PM or
|
|
2
|
+
// security owner can read. API names, globals, config keys and code belong in `accessNote`.
|
|
3
|
+
// Shared by the SDK's scanner (scripts/check-browser-globals.mjs) and @corbado/autocapture's manifest checks.
|
|
4
|
+
|
|
5
|
+
// DOM event names that read as code in a title, e.g. "Page unload (pagehide)".
|
|
6
|
+
const EVENT_NAMES =
|
|
7
|
+
/^(pagehide|pageshow|visibilitychange|popstate|hashchange|readystatechange|beforeunload|unload|focusin|focusout|pointerdown|pointerup|keydown|keyup|domcontentloaded|load|paste|submit|input|change|blur|focus)$/i;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Why `text` reads as code, or `undefined` when it is plain language. Rejects backticks, `__`, `=`,
|
|
11
|
+
* `window.`, dotted identifiers (`navigator.webdriver`) and parentheses with code-ish content
|
|
12
|
+
* (camelCase, `.`/`_`/`/`/`:` and similar, calls, DOM event names).
|
|
13
|
+
*/
|
|
14
|
+
export function customerTextProblem(text) {
|
|
15
|
+
if (typeof text !== "string" || text.trim() === "") return "empty";
|
|
16
|
+
if (text.includes("`")) return "backtick";
|
|
17
|
+
if (text.includes("__")) return "double underscore";
|
|
18
|
+
if (text.includes("=")) return "'='";
|
|
19
|
+
if (/\bwindow\./.test(text)) return "'window.'";
|
|
20
|
+
if (/[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*/.test(text)) return "dotted identifier";
|
|
21
|
+
for (const [, inner] of text.matchAll(/\(([^)]*)\)/g)) {
|
|
22
|
+
if (/[a-z][A-Z]|[._/$<>{}[\]:;]|\w\(/.test(inner) || EVENT_NAMES.test(inner.trim()))
|
|
23
|
+
return `code in parentheses "(${inner})"`;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type * as TS from "typescript";
|
|
2
|
+
|
|
3
|
+
/** Manifest key shape `<kind>:<namespace>.<name>` of a reader metadata literal. */
|
|
4
|
+
export declare const READER_META_KEY: RegExp;
|
|
5
|
+
|
|
6
|
+
/** Whether `node` is a reader metadata object literal (object literal with a manifest-shaped `key` and `fields`). */
|
|
7
|
+
export declare function isReaderMetaLiteral(ts: typeof TS, node: TS.Node): boolean;
|
|
8
|
+
|
|
9
|
+
/** Replaces every reader metadata call argument in `code` with `0`. */
|
|
10
|
+
export declare function stripReaderMeta(
|
|
11
|
+
ts: typeof TS,
|
|
12
|
+
code: string,
|
|
13
|
+
fileName?: string,
|
|
14
|
+
): { code: string; count: number };
|
|
15
|
+
|
|
16
|
+
export type StripReaderMetaOptions = {
|
|
17
|
+
/** Paths the plugin loads (default: TS/JS sources). */
|
|
18
|
+
filter?: RegExp;
|
|
19
|
+
/** A path must match this to be stripped. */
|
|
20
|
+
include?: RegExp;
|
|
21
|
+
/** Paths left alone (default `node_modules`); `null` disables. */
|
|
22
|
+
exclude?: RegExp | null;
|
|
23
|
+
/** The TypeScript compiler module (default: `import("typescript")`). */
|
|
24
|
+
typescript?: typeof TS;
|
|
25
|
+
/** Called for every changed module. */
|
|
26
|
+
onStrip?: (path: string, count: number) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** esbuild plugin (tsup: `esbuildPlugins`) replacing reader metadata call arguments with `0`. */
|
|
30
|
+
export declare function stripReaderMetaPlugin(options?: StripReaderMetaOptions): {
|
|
31
|
+
name: string;
|
|
32
|
+
setup(build: unknown): void;
|
|
33
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// esbuild plugin (also usable from tsup via `esbuildPlugins`) that removes reader metadata from shipped builds.
|
|
2
|
+
//
|
|
3
|
+
// Every reader call takes a literal `ReaderMeta` (`{ key: "<kind>:<namespace>.<name>", fields: [...], ... }`) with
|
|
4
|
+
// no runtime effect: readers ignore it, and the collection manifest is extracted from the unminified analysis
|
|
5
|
+
// build (`dist/analysis`), not from the shipped bundles. This plugin parses each loaded module with the
|
|
6
|
+
// TypeScript compiler and replaces every such call argument with `0`, so titles, notes and field lists do not
|
|
7
|
+
// ship to browsers. Everything else in the module is left byte-for-byte unchanged.
|
|
8
|
+
//
|
|
9
|
+
// A call argument is treated as reader metadata when it is an object literal with a `key` property whose value
|
|
10
|
+
// is a string literal of the manifest key shape and a `fields` property. Object literals that are not call
|
|
11
|
+
// arguments (e.g. exported constants that code reads at runtime) are never touched.
|
|
12
|
+
//
|
|
13
|
+
// Usage (esbuild / tsup):
|
|
14
|
+
// import { stripReaderMetaPlugin } from "@corbado/observe/tools/strip-reader-meta.mjs";
|
|
15
|
+
// esbuildPlugins: [stripReaderMetaPlugin()] // tsup
|
|
16
|
+
// plugins: [stripReaderMetaPlugin({ include: /\/src\// })] // esbuild
|
|
17
|
+
// Do NOT use it for analysis builds: the manifest extractor reads the literals there.
|
|
18
|
+
//
|
|
19
|
+
// Requires the `typescript` package (resolved from the consumer, or passed as `options.typescript`).
|
|
20
|
+
import { readFile } from "node:fs/promises";
|
|
21
|
+
|
|
22
|
+
/** Manifest key shape `<kind>:<namespace>.<name>` of a reader metadata literal. */
|
|
23
|
+
export const READER_META_KEY = /^(dom|network|page|browser-api|storage|host-config):[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+$/;
|
|
24
|
+
|
|
25
|
+
function propertyName(ts, prop) {
|
|
26
|
+
if (!ts.isPropertyAssignment(prop)) return undefined;
|
|
27
|
+
const name = prop.name;
|
|
28
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whether `node` is a reader metadata object literal (see the header). */
|
|
33
|
+
export function isReaderMetaLiteral(ts, node) {
|
|
34
|
+
if (!ts.isObjectLiteralExpression(node)) return false;
|
|
35
|
+
let key = false;
|
|
36
|
+
let fields = false;
|
|
37
|
+
for (const prop of node.properties) {
|
|
38
|
+
const name = propertyName(ts, prop);
|
|
39
|
+
if (name === "key") {
|
|
40
|
+
const value = prop.initializer;
|
|
41
|
+
key =
|
|
42
|
+
(ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value)) && READER_META_KEY.test(value.text);
|
|
43
|
+
} else if (name === "fields") fields = true;
|
|
44
|
+
}
|
|
45
|
+
return key && fields;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function scriptKind(ts, fileName) {
|
|
49
|
+
if (/\.tsx$/.test(fileName)) return ts.ScriptKind.TSX;
|
|
50
|
+
if (/\.[cm]?ts$/.test(fileName)) return ts.ScriptKind.TS;
|
|
51
|
+
if (/\.jsx$/.test(fileName)) return ts.ScriptKind.JSX;
|
|
52
|
+
return ts.ScriptKind.JS;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Replaces every reader metadata call argument in `code` with `0`.
|
|
57
|
+
* Returns `{ code, count }` (`code` is the input unchanged when `count` is 0).
|
|
58
|
+
*/
|
|
59
|
+
export function stripReaderMeta(ts, code, fileName = "module.ts") {
|
|
60
|
+
if (!code.includes("fields")) return { code, count: 0 };
|
|
61
|
+
const sf = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true, scriptKind(ts, fileName));
|
|
62
|
+
const ranges = [];
|
|
63
|
+
const visit = (node) => {
|
|
64
|
+
if ((ts.isCallExpression(node) || ts.isNewExpression(node)) && node.arguments) {
|
|
65
|
+
for (const arg of node.arguments) {
|
|
66
|
+
let inner = arg;
|
|
67
|
+
while (ts.isParenthesizedExpression(inner) || ts.isAsExpression(inner) || ts.isSatisfiesExpression?.(inner))
|
|
68
|
+
inner = inner.expression;
|
|
69
|
+
if (isReaderMetaLiteral(ts, inner)) {
|
|
70
|
+
ranges.push([arg.getStart(sf), arg.getEnd()]);
|
|
71
|
+
return; // the literal is replaced as a whole; nothing inside it needs a visit
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
ts.forEachChild(node, visit);
|
|
76
|
+
};
|
|
77
|
+
visit(sf);
|
|
78
|
+
if (ranges.length === 0) return { code, count: 0 };
|
|
79
|
+
ranges.sort((a, b) => a[0] - b[0]);
|
|
80
|
+
let out = "";
|
|
81
|
+
let at = 0;
|
|
82
|
+
for (const [start, end] of ranges) {
|
|
83
|
+
out += code.slice(at, start) + "0";
|
|
84
|
+
at = end;
|
|
85
|
+
}
|
|
86
|
+
out += code.slice(at);
|
|
87
|
+
return { code: out, count: ranges.length };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function loaderOf(fileName) {
|
|
91
|
+
if (/\.tsx$/.test(fileName)) return "tsx";
|
|
92
|
+
if (/\.[cm]?ts$/.test(fileName)) return "ts";
|
|
93
|
+
if (/\.jsx$/.test(fileName)) return "jsx";
|
|
94
|
+
return "js";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* esbuild plugin replacing reader metadata call arguments with `0`.
|
|
99
|
+
* Options: `filter` (RegExp of paths to load, default TS/JS sources), `include` (RegExp a path must match),
|
|
100
|
+
* `exclude` (RegExp, default `node_modules`), `typescript` (the compiler module; default `import("typescript")`),
|
|
101
|
+
* `onStrip(path, count)` (called per changed module, e.g. for build stats).
|
|
102
|
+
*/
|
|
103
|
+
export function stripReaderMetaPlugin(options = {}) {
|
|
104
|
+
const filter = options.filter ?? /\.[cm]?[jt]sx?$/;
|
|
105
|
+
const exclude = options.exclude === undefined ? /[\\/]node_modules[\\/]/ : options.exclude;
|
|
106
|
+
const include = options.include;
|
|
107
|
+
return {
|
|
108
|
+
name: "corbado-strip-reader-meta",
|
|
109
|
+
setup(build) {
|
|
110
|
+
let tsPromise;
|
|
111
|
+
const compiler = () => {
|
|
112
|
+
tsPromise ??= options.typescript
|
|
113
|
+
? Promise.resolve(options.typescript)
|
|
114
|
+
: import("typescript").then((m) => m.default ?? m);
|
|
115
|
+
return tsPromise;
|
|
116
|
+
};
|
|
117
|
+
build.onLoad({ filter }, async (args) => {
|
|
118
|
+
if (args.namespace !== "file") return undefined;
|
|
119
|
+
if (exclude && exclude.test(args.path)) return undefined;
|
|
120
|
+
if (include && !include.test(args.path)) return undefined;
|
|
121
|
+
const source = await readFile(args.path, "utf8");
|
|
122
|
+
if (!source.includes("fields")) return undefined;
|
|
123
|
+
const ts = await compiler();
|
|
124
|
+
const { code, count } = stripReaderMeta(ts, source, args.path);
|
|
125
|
+
if (count === 0) return undefined;
|
|
126
|
+
options.onStrip?.(args.path, count);
|
|
127
|
+
return { contents: code, loader: loaderOf(args.path) };
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|