@middag-io/licensing 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.
- package/README.md +8 -3
- package/dist/anti-tamper/ClientIntegrityReporter.d.ts +15 -0
- package/dist/anti-tamper/ClientIntegrityReporter.d.ts.map +1 -0
- package/dist/anti-tamper/ClientIntegrityWatcher.d.ts +25 -0
- package/dist/anti-tamper/ClientIntegrityWatcher.d.ts.map +1 -0
- package/dist/anti-tamper/index.d.ts +6 -0
- package/dist/anti-tamper/index.d.ts.map +1 -0
- package/dist/anti-tamper/index.js +161 -0
- package/dist/anti-tamper/index.js.map +1 -0
- package/dist/anti-tamper/types.d.ts +19 -0
- package/dist/anti-tamper/types.d.ts.map +1 -0
- package/dist/observability/BatchSender.d.ts +21 -0
- package/dist/observability/BatchSender.d.ts.map +1 -0
- package/dist/observability/ObservabilityClient.d.ts +25 -0
- package/dist/observability/ObservabilityClient.d.ts.map +1 -0
- package/dist/observability/PerfCollector.d.ts +18 -0
- package/dist/observability/PerfCollector.d.ts.map +1 -0
- package/dist/observability/index.d.ts +8 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +225 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/types.d.ts +23 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/remote-support/AuditBanner.d.ts +23 -0
- package/dist/remote-support/AuditBanner.d.ts.map +1 -0
- package/dist/remote-support/index.d.ts +3 -0
- package/dist/remote-support/index.d.ts.map +1 -0
- package/dist/remote-support/index.js +51 -0
- package/dist/remote-support/index.js.map +1 -0
- package/dist/telemetry/TelemetryClient.d.ts +13 -0
- package/dist/telemetry/TelemetryClient.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +32 -0
- package/dist/telemetry/index.js.map +1 -0
- package/package.json +17 -1
package/README.md
CHANGED
|
@@ -26,12 +26,17 @@ releases.
|
|
|
26
26
|
| `@middag-io/licensing/contract` | Same as root — explicit subpath. |
|
|
27
27
|
| `@middag-io/licensing/build` | Vite plugin and CLI helpers that inject placeholders and emit `manifest.template.json`. Node-only. |
|
|
28
28
|
| `@middag-io/licensing/client` | Runtime loader for the host. Validates the worker-issued manifest and dynamically imports protected modules. |
|
|
29
|
+
| `@middag-io/licensing/anti-tamper` | ADR-014 Layer 2: `ClientIntegrityWatcher` (prototype freeze + MutationObserver) + `ClientIntegrityReporter` (POST worker `/v1/anti-tamper/client-report`). |
|
|
30
|
+
| `@middag-io/licensing/observability` | ADR-015: `ObservabilityClient` + `BatchSender` + `PerfCollector`. Posts batched browser errors / perf / audit events to a host plugin relay (PHP signs+forwards). |
|
|
31
|
+
| `@middag-io/licensing/telemetry` | ADR-015 telemetry redirect: `TelemetryClient` + `UsageTracker` sugar over observability audit events. |
|
|
32
|
+
| `@middag-io/licensing/remote-support` | Optional `AuditBanner` widget rendering active session info on tenant admin dashboards (ADR-013 extended). |
|
|
29
33
|
|
|
30
34
|
## Status
|
|
31
35
|
|
|
32
|
-
v0.0
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
v0.2.0 — adds 4 new client subpaths matching worker contract `v0.3.0`
|
|
37
|
+
(ADR-013 extended remote support, ADR-014 anti-tamper, ADR-015
|
|
38
|
+
observability + telemetry). Initial v0.0.2 release shipped ADR-012
|
|
39
|
+
protected-delivery loader; that surface is unchanged. The Vite plugin emits `manifest.template.json` with SHA-384
|
|
35
40
|
hashes; the runtime loader verifies the worker-issued JWS against the
|
|
36
41
|
worker JWKS, validates the bootstrap (install / host / product /
|
|
37
42
|
contract version), and dynamic-imports modules with recomputed SRI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ClientReactionMode, ClientReportPayload, ClientReportStatus } from "./types.js";
|
|
2
|
+
export interface ReporterConfig {
|
|
3
|
+
workerBaseUri: string;
|
|
4
|
+
installId: string;
|
|
5
|
+
manifestId: string;
|
|
6
|
+
reactionMode?: ClientReactionMode;
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export declare class ClientIntegrityReporter {
|
|
10
|
+
private readonly cfg;
|
|
11
|
+
private readonly fetchFn;
|
|
12
|
+
constructor(cfg: ReporterConfig);
|
|
13
|
+
report(status: ClientReportStatus, drift?: ClientReportPayload["drift_files"]): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=ClientIntegrityReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientIntegrityReporter.d.ts","sourceRoot":"","sources":["../../src/anti-tamper/ClientIntegrityReporter.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;gBAE3B,GAAG,EAAE,cAAc;IAKzB,MAAM,CACV,MAAM,EAAE,kBAAkB,EAC1B,KAAK,CAAC,EAAE,mBAAmB,CAAC,aAAa,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC;CAoBjB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WatcherDriftReport } from "./types.js";
|
|
2
|
+
export interface WatcherConfig {
|
|
3
|
+
cdnAllowlist: string[];
|
|
4
|
+
pollIntervalMs?: number;
|
|
5
|
+
trackedPrototypes?: Array<{
|
|
6
|
+
name: string;
|
|
7
|
+
obj: object;
|
|
8
|
+
methods: string[];
|
|
9
|
+
}>;
|
|
10
|
+
onAnomaly?: (drift: WatcherDriftReport) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare class ClientIntegrityWatcher {
|
|
13
|
+
private readonly cfg;
|
|
14
|
+
private snapshot;
|
|
15
|
+
private pollHandle;
|
|
16
|
+
private mo;
|
|
17
|
+
constructor(cfg: WatcherConfig);
|
|
18
|
+
start(): void;
|
|
19
|
+
stop(): void;
|
|
20
|
+
tick(): WatcherDriftReport;
|
|
21
|
+
private capture;
|
|
22
|
+
private diff;
|
|
23
|
+
private onMutations;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ClientIntegrityWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientIntegrityWatcher.d.ts","sourceRoot":"","sources":["../../src/anti-tamper/ClientIntegrityWatcher.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC5E,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACjD;AAOD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAElB;IACF,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,EAAE,CAAiC;gBAE/B,GAAG,EAAE,aAAa;IAS9B,KAAK,IAAI,IAAI;IAWb,IAAI,IAAI,IAAI;IAYZ,IAAI,IAAI,kBAAkB;IAY1B,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,IAAI;IAyBZ,OAAO,CAAC,WAAW;CAuBpB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ClientIntegrityWatcher } from "./ClientIntegrityWatcher.js";
|
|
2
|
+
export type { WatcherConfig } from "./ClientIntegrityWatcher.js";
|
|
3
|
+
export { ClientIntegrityReporter } from "./ClientIntegrityReporter.js";
|
|
4
|
+
export type { ReporterConfig } from "./ClientIntegrityReporter.js";
|
|
5
|
+
export type { ClientReactionMode, ClientReportPayload, ClientReportStatus, WatcherDriftReport, } from "./types.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/anti-tamper/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
//#region src/anti-tamper/ClientIntegrityWatcher.ts
|
|
2
|
+
var ClientIntegrityWatcher = class {
|
|
3
|
+
cfg;
|
|
4
|
+
snapshot = null;
|
|
5
|
+
pollHandle = null;
|
|
6
|
+
mo = null;
|
|
7
|
+
constructor(cfg) {
|
|
8
|
+
this.cfg = {
|
|
9
|
+
cdnAllowlist: cfg.cdnAllowlist,
|
|
10
|
+
pollIntervalMs: cfg.pollIntervalMs ?? 6e4,
|
|
11
|
+
trackedPrototypes: cfg.trackedPrototypes ?? defaultTrackedPrototypes(),
|
|
12
|
+
onAnomaly: cfg.onAnomaly ?? (() => {})
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
start() {
|
|
16
|
+
if (this.snapshot !== null) return;
|
|
17
|
+
this.snapshot = this.capture();
|
|
18
|
+
this.pollHandle = setInterval(() => this.tick(), this.cfg.pollIntervalMs);
|
|
19
|
+
if (typeof MutationObserver !== "undefined" && typeof document !== "undefined") {
|
|
20
|
+
this.mo = new MutationObserver((mutations) => this.onMutations(mutations));
|
|
21
|
+
this.mo.observe(document.documentElement, {
|
|
22
|
+
childList: true,
|
|
23
|
+
subtree: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
stop() {
|
|
28
|
+
if (this.pollHandle !== null) {
|
|
29
|
+
clearInterval(this.pollHandle);
|
|
30
|
+
this.pollHandle = null;
|
|
31
|
+
}
|
|
32
|
+
if (this.mo !== null) {
|
|
33
|
+
this.mo.disconnect();
|
|
34
|
+
this.mo = null;
|
|
35
|
+
}
|
|
36
|
+
this.snapshot = null;
|
|
37
|
+
}
|
|
38
|
+
tick() {
|
|
39
|
+
const drift = this.diff();
|
|
40
|
+
if (drift.prototypeMutations.length > 0 || drift.fingerprintMismatches.length > 0 || drift.scriptInjections.length > 0) this.cfg.onAnomaly(drift);
|
|
41
|
+
return drift;
|
|
42
|
+
}
|
|
43
|
+
capture() {
|
|
44
|
+
const protoRefs = /* @__PURE__ */ new Map();
|
|
45
|
+
const fingerprints = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const tracked of this.cfg.trackedPrototypes) for (const method of tracked.methods) {
|
|
47
|
+
const key = `${tracked.name}.${method}`;
|
|
48
|
+
const fn = tracked.obj[method];
|
|
49
|
+
protoRefs.set(key, fn);
|
|
50
|
+
if (typeof fn === "function") fingerprints.set(key, Function.prototype.toString.call(fn));
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
prototypeRefs: protoRefs,
|
|
54
|
+
fingerprints
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
diff() {
|
|
58
|
+
if (this.snapshot === null) return {
|
|
59
|
+
prototypeMutations: [],
|
|
60
|
+
scriptInjections: [],
|
|
61
|
+
fingerprintMismatches: []
|
|
62
|
+
};
|
|
63
|
+
const prototypeMutations = [];
|
|
64
|
+
const fingerprintMismatches = [];
|
|
65
|
+
for (const tracked of this.cfg.trackedPrototypes) for (const method of tracked.methods) {
|
|
66
|
+
const key = `${tracked.name}.${method}`;
|
|
67
|
+
const now = tracked.obj[method];
|
|
68
|
+
if (now !== this.snapshot.prototypeRefs.get(key)) prototypeMutations.push(key);
|
|
69
|
+
if (typeof now === "function") {
|
|
70
|
+
if (this.snapshot.fingerprints.get(key) !== Function.prototype.toString.call(now)) fingerprintMismatches.push(key);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
prototypeMutations,
|
|
75
|
+
scriptInjections: [],
|
|
76
|
+
fingerprintMismatches
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
onMutations(mutations) {
|
|
80
|
+
const injected = [];
|
|
81
|
+
for (const m of mutations) m.addedNodes.forEach((node) => {
|
|
82
|
+
if (node instanceof HTMLScriptElement && node.src && !this.cfg.cdnAllowlist.some((origin) => node.src.startsWith(origin.replace(/\/$/, "")))) injected.push(node.src);
|
|
83
|
+
});
|
|
84
|
+
if (injected.length > 0) this.cfg.onAnomaly({
|
|
85
|
+
prototypeMutations: [],
|
|
86
|
+
scriptInjections: injected,
|
|
87
|
+
fingerprintMismatches: []
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function defaultTrackedPrototypes() {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
name: "Object.prototype",
|
|
95
|
+
obj: Object.prototype,
|
|
96
|
+
methods: [
|
|
97
|
+
"hasOwnProperty",
|
|
98
|
+
"toString",
|
|
99
|
+
"valueOf"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "Array.prototype",
|
|
104
|
+
obj: Array.prototype,
|
|
105
|
+
methods: [
|
|
106
|
+
"push",
|
|
107
|
+
"pop",
|
|
108
|
+
"slice",
|
|
109
|
+
"map",
|
|
110
|
+
"filter",
|
|
111
|
+
"reduce"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "Function.prototype",
|
|
116
|
+
obj: Function.prototype,
|
|
117
|
+
methods: [
|
|
118
|
+
"call",
|
|
119
|
+
"apply",
|
|
120
|
+
"bind",
|
|
121
|
+
"toString"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "JSON",
|
|
126
|
+
obj: JSON,
|
|
127
|
+
methods: ["parse", "stringify"]
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/anti-tamper/ClientIntegrityReporter.ts
|
|
133
|
+
var ClientIntegrityReporter = class {
|
|
134
|
+
cfg;
|
|
135
|
+
fetchFn;
|
|
136
|
+
constructor(cfg) {
|
|
137
|
+
this.cfg = cfg;
|
|
138
|
+
this.fetchFn = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
139
|
+
}
|
|
140
|
+
async report(status, drift) {
|
|
141
|
+
const reaction = this.cfg.reactionMode === "DEGRADE" ? "DEGRADE" : "LOG_ONLY";
|
|
142
|
+
const body = {
|
|
143
|
+
install_id: this.cfg.installId,
|
|
144
|
+
manifest_id: this.cfg.manifestId,
|
|
145
|
+
status,
|
|
146
|
+
drift_files: drift,
|
|
147
|
+
reaction_mode: reaction
|
|
148
|
+
};
|
|
149
|
+
const url = this.cfg.workerBaseUri.replace(/\/$/, "") + "/v1/anti-tamper/client-report";
|
|
150
|
+
const res = await this.fetchFn(url, {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: { "content-type": "application/json" },
|
|
153
|
+
body: JSON.stringify(body)
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok) throw new Error(`anti-tamper client-report ${res.status}`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
//#endregion
|
|
159
|
+
export { ClientIntegrityReporter, ClientIntegrityWatcher };
|
|
160
|
+
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/anti-tamper/ClientIntegrityWatcher.ts","../../src/anti-tamper/ClientIntegrityReporter.ts"],"sourcesContent":["// Runtime drift watcher (ADR-014 Layer 2).\n//\n// Snapshots a set of prototype methods + Function.prototype.toString\n// fingerprints on first load, then runs a periodic diff and a\n// MutationObserver for injected <script> elements. Anomalies surface\n// via onAnomaly callback; caller decides whether to forward to the\n// Reporter (often: throttle + sample).\n//\n// Explicitly NOT implemented: DevTools detection (false positives;\n// power-user UX cost outweighs attacker friction).\n\nimport type { WatcherDriftReport } from \"./types.js\";\n\nexport interface WatcherConfig {\n cdnAllowlist: string[]; // origins permitted for <script src>\n pollIntervalMs?: number;\n trackedPrototypes?: Array<{ name: string; obj: object; methods: string[] }>;\n onAnomaly?: (drift: WatcherDriftReport) => void;\n}\n\ninterface Snapshot {\n prototypeRefs: Map<string, unknown>;\n fingerprints: Map<string, string>;\n}\n\nexport class ClientIntegrityWatcher {\n private readonly cfg: Required<Omit<WatcherConfig, \"onAnomaly\">> & {\n onAnomaly: (drift: WatcherDriftReport) => void;\n };\n private snapshot: Snapshot | null = null;\n private pollHandle: ReturnType<typeof setInterval> | null = null;\n private mo: MutationObserver | null = null;\n\n constructor(cfg: WatcherConfig) {\n this.cfg = {\n cdnAllowlist: cfg.cdnAllowlist,\n pollIntervalMs: cfg.pollIntervalMs ?? 60_000,\n trackedPrototypes: cfg.trackedPrototypes ?? defaultTrackedPrototypes(),\n onAnomaly: cfg.onAnomaly ?? (() => {}),\n };\n }\n\n start(): void {\n if (this.snapshot !== null) return;\n this.snapshot = this.capture();\n this.pollHandle = setInterval(() => this.tick(), this.cfg.pollIntervalMs);\n\n if (typeof MutationObserver !== \"undefined\" && typeof document !== \"undefined\") {\n this.mo = new MutationObserver((mutations) => this.onMutations(mutations));\n this.mo.observe(document.documentElement, { childList: true, subtree: true });\n }\n }\n\n stop(): void {\n if (this.pollHandle !== null) {\n clearInterval(this.pollHandle);\n this.pollHandle = null;\n }\n if (this.mo !== null) {\n this.mo.disconnect();\n this.mo = null;\n }\n this.snapshot = null;\n }\n\n tick(): WatcherDriftReport {\n const drift = this.diff();\n if (\n drift.prototypeMutations.length > 0 ||\n drift.fingerprintMismatches.length > 0 ||\n drift.scriptInjections.length > 0\n ) {\n this.cfg.onAnomaly(drift);\n }\n return drift;\n }\n\n private capture(): Snapshot {\n const protoRefs = new Map<string, unknown>();\n const fingerprints = new Map<string, string>();\n for (const tracked of this.cfg.trackedPrototypes) {\n for (const method of tracked.methods) {\n const key = `${tracked.name}.${method}`;\n const fn = (tracked.obj as Record<string, unknown>)[method];\n protoRefs.set(key, fn);\n if (typeof fn === \"function\") {\n fingerprints.set(key, Function.prototype.toString.call(fn));\n }\n }\n }\n return { prototypeRefs: protoRefs, fingerprints };\n }\n\n private diff(): WatcherDriftReport {\n if (this.snapshot === null) {\n return { prototypeMutations: [], scriptInjections: [], fingerprintMismatches: [] };\n }\n const prototypeMutations: string[] = [];\n const fingerprintMismatches: string[] = [];\n for (const tracked of this.cfg.trackedPrototypes) {\n for (const method of tracked.methods) {\n const key = `${tracked.name}.${method}`;\n const now = (tracked.obj as Record<string, unknown>)[method];\n if (now !== this.snapshot.prototypeRefs.get(key)) {\n prototypeMutations.push(key);\n }\n if (typeof now === \"function\") {\n const expected = this.snapshot.fingerprints.get(key);\n const actual = Function.prototype.toString.call(now);\n if (expected !== actual) {\n fingerprintMismatches.push(key);\n }\n }\n }\n }\n return { prototypeMutations, scriptInjections: [], fingerprintMismatches };\n }\n\n private onMutations(mutations: MutationRecord[]): void {\n const injected: string[] = [];\n for (const m of mutations) {\n m.addedNodes.forEach((node) => {\n if (\n node instanceof HTMLScriptElement &&\n node.src &&\n !this.cfg.cdnAllowlist.some((origin) =>\n node.src.startsWith(origin.replace(/\\/$/, \"\")),\n )\n ) {\n injected.push(node.src);\n }\n });\n }\n if (injected.length > 0) {\n this.cfg.onAnomaly({\n prototypeMutations: [],\n scriptInjections: injected,\n fingerprintMismatches: [],\n });\n }\n }\n}\n\nfunction defaultTrackedPrototypes(): NonNullable<WatcherConfig[\"trackedPrototypes\"]> {\n return [\n {\n name: \"Object.prototype\",\n obj: Object.prototype,\n methods: [\"hasOwnProperty\", \"toString\", \"valueOf\"],\n },\n {\n name: \"Array.prototype\",\n obj: Array.prototype,\n methods: [\"push\", \"pop\", \"slice\", \"map\", \"filter\", \"reduce\"],\n },\n {\n name: \"Function.prototype\",\n obj: Function.prototype,\n methods: [\"call\", \"apply\", \"bind\", \"toString\"],\n },\n {\n name: \"JSON\",\n obj: JSON,\n methods: [\"parse\", \"stringify\"],\n },\n ];\n}\n","// Posts client-side integrity findings to the worker's public endpoint.\n//\n// HARD_FAIL is server-side rejected when posted from the client; the\n// reporter normalizes any caller attempt to DEGRADE.\n\nimport type {\n ClientReactionMode,\n ClientReportPayload,\n ClientReportStatus,\n} from \"./types.js\";\n\nexport interface ReporterConfig {\n workerBaseUri: string;\n installId: string;\n manifestId: string;\n reactionMode?: ClientReactionMode;\n fetchImpl?: typeof fetch;\n}\n\nexport class ClientIntegrityReporter {\n private readonly cfg: ReporterConfig;\n private readonly fetchFn: typeof fetch;\n\n constructor(cfg: ReporterConfig) {\n this.cfg = cfg;\n this.fetchFn = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n\n async report(\n status: ClientReportStatus,\n drift?: ClientReportPayload[\"drift_files\"],\n ): Promise<void> {\n const reaction: ClientReactionMode =\n this.cfg.reactionMode === \"DEGRADE\" ? \"DEGRADE\" : \"LOG_ONLY\";\n const body: ClientReportPayload = {\n install_id: this.cfg.installId,\n manifest_id: this.cfg.manifestId,\n status,\n drift_files: drift,\n reaction_mode: reaction,\n };\n const url = this.cfg.workerBaseUri.replace(/\\/$/, \"\") + \"/v1/anti-tamper/client-report\";\n const res = await this.fetchFn(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n throw new Error(`anti-tamper client-report ${res.status}`);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ClientReportStatus = "ok" | "drift" | "missing_manifest" | "sig_invalid";
|
|
2
|
+
export type ClientReactionMode = "LOG_ONLY" | "DEGRADE";
|
|
3
|
+
export interface ClientReportPayload {
|
|
4
|
+
install_id: string;
|
|
5
|
+
manifest_id: string;
|
|
6
|
+
status: ClientReportStatus;
|
|
7
|
+
drift_files?: {
|
|
8
|
+
added?: string[];
|
|
9
|
+
modified?: string[];
|
|
10
|
+
removed?: string[];
|
|
11
|
+
};
|
|
12
|
+
reaction_mode?: ClientReactionMode;
|
|
13
|
+
}
|
|
14
|
+
export interface WatcherDriftReport {
|
|
15
|
+
prototypeMutations: string[];
|
|
16
|
+
scriptInjections: string[];
|
|
17
|
+
fingerprintMismatches: string[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/anti-tamper/types.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,kBAAkB,GAC1B,IAAI,GACJ,OAAO,GACP,kBAAkB,GAClB,aAAa,CAAC;AAElB,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,CAAC,EAAE;QACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BrowserAuditEvent, BrowserErrorEvent, BrowserPerfEvent } from "./types.js";
|
|
2
|
+
export interface BatchSenderConfig {
|
|
3
|
+
relayBaseUri: string;
|
|
4
|
+
maxBatchSize?: number;
|
|
5
|
+
fetchImpl?: typeof fetch;
|
|
6
|
+
}
|
|
7
|
+
export declare class BatchSender {
|
|
8
|
+
private errors;
|
|
9
|
+
private perf;
|
|
10
|
+
private events;
|
|
11
|
+
private readonly cfg;
|
|
12
|
+
constructor(cfg: BatchSenderConfig);
|
|
13
|
+
queueError(e: BrowserErrorEvent): void;
|
|
14
|
+
queuePerf(e: BrowserPerfEvent): void;
|
|
15
|
+
queueAudit(e: BrowserAuditEvent): void;
|
|
16
|
+
flush(): Promise<void>;
|
|
17
|
+
size(): number;
|
|
18
|
+
private cap;
|
|
19
|
+
private send;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=BatchSender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BatchSender.d.ts","sourceRoot":"","sources":["../../src/observability/BatchSender.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,IAAI,CAA0B;IACtC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAElB;gBAEU,GAAG,EAAE,iBAAiB;IAQlC,UAAU,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAItC,SAAS,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAIpC,UAAU,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAIhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5B,IAAI,IAAI,MAAM;IAId,OAAO,CAAC,GAAG;YAQG,IAAI;CAUnB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BatchSender } from "./BatchSender.js";
|
|
2
|
+
import type { ObsLevel } from "./types.js";
|
|
3
|
+
export interface ObservabilityClientConfig {
|
|
4
|
+
relayBaseUri: string;
|
|
5
|
+
flushIntervalMs?: number;
|
|
6
|
+
installPerfCollector?: boolean;
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export declare class ObservabilityClient {
|
|
10
|
+
readonly sender: BatchSender;
|
|
11
|
+
private perf;
|
|
12
|
+
private flushHandle;
|
|
13
|
+
private cfg;
|
|
14
|
+
constructor(cfg: ObservabilityClientConfig);
|
|
15
|
+
install(): void;
|
|
16
|
+
uninstall(): void;
|
|
17
|
+
reportError(level: ObsLevel, message: string, stack: string, payload?: unknown): void;
|
|
18
|
+
reportAudit(eventType: string, payload?: Record<string, unknown>): void;
|
|
19
|
+
private reportWindowError;
|
|
20
|
+
private reportUnhandled;
|
|
21
|
+
private hashStack;
|
|
22
|
+
private sha256Hex;
|
|
23
|
+
private fnv1a64Hex;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ObservabilityClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObservabilityClient.d.ts","sourceRoot":"","sources":["../../src/observability/ObservabilityClient.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EAAwC,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjF,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,OAAO,CAAC,IAAI,CAA8B;IAC1C,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,GAAG,CAAsC;gBAErC,GAAG,EAAE,yBAAyB;IAa1C,OAAO,IAAI,IAAI;IAiBf,SAAS,IAAI,IAAI;IASjB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAYrF,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAYvE,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,UAAU;CAWnB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BatchSender } from "./BatchSender.js";
|
|
2
|
+
export interface PerfCollectorConfig {
|
|
3
|
+
sender: BatchSender;
|
|
4
|
+
metricsToCollect?: Array<{
|
|
5
|
+
name: string;
|
|
6
|
+
entryType: string;
|
|
7
|
+
attr: keyof PerformanceEntry | "duration";
|
|
8
|
+
}>;
|
|
9
|
+
sampleRate?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class PerfCollector {
|
|
12
|
+
private readonly cfg;
|
|
13
|
+
private observer;
|
|
14
|
+
constructor(cfg: PerfCollectorConfig);
|
|
15
|
+
start(): void;
|
|
16
|
+
stop(): void;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=PerfCollector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PerfCollector.d.ts","sourceRoot":"","sources":["../../src/observability/PerfCollector.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,gBAAgB,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,gBAAgB,GAAG,UAAU,CAAA;KAAE,CAAC,CAAC;IACzG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgC;IACpD,OAAO,CAAC,QAAQ,CAAoC;gBAExC,GAAG,EAAE,mBAAmB;IAQpC,KAAK,IAAI,IAAI;IA+Bb,IAAI,IAAI,IAAI;CAMb"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ObservabilityClient } from "./ObservabilityClient.js";
|
|
2
|
+
export type { ObservabilityClientConfig } from "./ObservabilityClient.js";
|
|
3
|
+
export { BatchSender } from "./BatchSender.js";
|
|
4
|
+
export type { BatchSenderConfig } from "./BatchSender.js";
|
|
5
|
+
export { PerfCollector } from "./PerfCollector.js";
|
|
6
|
+
export type { PerfCollectorConfig } from "./PerfCollector.js";
|
|
7
|
+
export type { BrowserAuditEvent, BrowserErrorEvent, BrowserPerfEvent, ObsLevel, } from "./types.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/observability/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,YAAY,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,GACT,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
//#region src/observability/BatchSender.ts
|
|
2
|
+
var BatchSender = class {
|
|
3
|
+
errors = [];
|
|
4
|
+
perf = [];
|
|
5
|
+
events = [];
|
|
6
|
+
cfg;
|
|
7
|
+
constructor(cfg) {
|
|
8
|
+
this.cfg = {
|
|
9
|
+
relayBaseUri: cfg.relayBaseUri.replace(/\/$/, ""),
|
|
10
|
+
maxBatchSize: cfg.maxBatchSize ?? 1e3,
|
|
11
|
+
fetchImpl: cfg.fetchImpl ?? globalThis.fetch.bind(globalThis)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
queueError(e) {
|
|
15
|
+
this.errors = this.cap(this.errors, e);
|
|
16
|
+
}
|
|
17
|
+
queuePerf(e) {
|
|
18
|
+
this.perf = this.cap(this.perf, e);
|
|
19
|
+
}
|
|
20
|
+
queueAudit(e) {
|
|
21
|
+
this.events = this.cap(this.events, e);
|
|
22
|
+
}
|
|
23
|
+
async flush() {
|
|
24
|
+
const tasks = [];
|
|
25
|
+
if (this.errors.length > 0) {
|
|
26
|
+
const batch = this.errors;
|
|
27
|
+
this.errors = [];
|
|
28
|
+
tasks.push(this.send("/observability/errors", batch));
|
|
29
|
+
}
|
|
30
|
+
if (this.perf.length > 0) {
|
|
31
|
+
const batch = this.perf;
|
|
32
|
+
this.perf = [];
|
|
33
|
+
tasks.push(this.send("/observability/perf", batch));
|
|
34
|
+
}
|
|
35
|
+
if (this.events.length > 0) {
|
|
36
|
+
const batch = this.events;
|
|
37
|
+
this.events = [];
|
|
38
|
+
tasks.push(this.send("/observability/events", batch));
|
|
39
|
+
}
|
|
40
|
+
await Promise.all(tasks);
|
|
41
|
+
}
|
|
42
|
+
size() {
|
|
43
|
+
return this.errors.length + this.perf.length + this.events.length;
|
|
44
|
+
}
|
|
45
|
+
cap(arr, next) {
|
|
46
|
+
if (arr.length >= this.cfg.maxBatchSize) arr.shift();
|
|
47
|
+
arr.push(next);
|
|
48
|
+
return arr;
|
|
49
|
+
}
|
|
50
|
+
async send(path, body) {
|
|
51
|
+
const url = this.cfg.relayBaseUri + path;
|
|
52
|
+
await this.cfg.fetchImpl(url, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "content-type": "application/json" },
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
keepalive: true
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/observability/PerfCollector.ts
|
|
62
|
+
var PerfCollector = class {
|
|
63
|
+
cfg;
|
|
64
|
+
observer = null;
|
|
65
|
+
constructor(cfg) {
|
|
66
|
+
this.cfg = {
|
|
67
|
+
sender: cfg.sender,
|
|
68
|
+
metricsToCollect: cfg.metricsToCollect ?? defaultMetrics(),
|
|
69
|
+
sampleRate: cfg.sampleRate ?? 1
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
start() {
|
|
73
|
+
if (typeof PerformanceObserver === "undefined") return;
|
|
74
|
+
if (this.observer !== null) return;
|
|
75
|
+
const types = new Set(this.cfg.metricsToCollect.map((m) => m.entryType));
|
|
76
|
+
this.observer = new PerformanceObserver((list) => {
|
|
77
|
+
for (const entry of list.getEntries()) {
|
|
78
|
+
if (!types.has(entry.entryType)) continue;
|
|
79
|
+
if (Math.random() > this.cfg.sampleRate) continue;
|
|
80
|
+
const match = this.cfg.metricsToCollect.find((m) => m.entryType === entry.entryType);
|
|
81
|
+
if (!match) continue;
|
|
82
|
+
const raw = match.attr === "duration" ? entry.duration : entry[match.attr];
|
|
83
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) continue;
|
|
84
|
+
const ev = {
|
|
85
|
+
metric_name: match.name,
|
|
86
|
+
value_ms: raw,
|
|
87
|
+
source: "js",
|
|
88
|
+
ts: Date.now()
|
|
89
|
+
};
|
|
90
|
+
this.cfg.sender.queuePerf(ev);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
for (const t of types) try {
|
|
94
|
+
this.observer.observe({
|
|
95
|
+
type: t,
|
|
96
|
+
buffered: true
|
|
97
|
+
});
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
stop() {
|
|
101
|
+
if (this.observer !== null) {
|
|
102
|
+
this.observer.disconnect();
|
|
103
|
+
this.observer = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
function defaultMetrics() {
|
|
108
|
+
return [
|
|
109
|
+
{
|
|
110
|
+
name: "page_load_ms",
|
|
111
|
+
entryType: "navigation",
|
|
112
|
+
attr: "duration"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "fcp_ms",
|
|
116
|
+
entryType: "paint",
|
|
117
|
+
attr: "startTime"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "lcp_ms",
|
|
121
|
+
entryType: "largest-contentful-paint",
|
|
122
|
+
attr: "startTime"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "resource_ms",
|
|
126
|
+
entryType: "resource",
|
|
127
|
+
attr: "duration"
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/observability/ObservabilityClient.ts
|
|
133
|
+
var ObservabilityClient = class {
|
|
134
|
+
sender;
|
|
135
|
+
perf = null;
|
|
136
|
+
flushHandle = null;
|
|
137
|
+
cfg;
|
|
138
|
+
constructor(cfg) {
|
|
139
|
+
this.cfg = {
|
|
140
|
+
relayBaseUri: cfg.relayBaseUri,
|
|
141
|
+
flushIntervalMs: cfg.flushIntervalMs ?? 3e4,
|
|
142
|
+
installPerfCollector: cfg.installPerfCollector ?? true,
|
|
143
|
+
fetchImpl: cfg.fetchImpl ?? globalThis.fetch.bind(globalThis)
|
|
144
|
+
};
|
|
145
|
+
this.sender = new BatchSender({
|
|
146
|
+
relayBaseUri: this.cfg.relayBaseUri,
|
|
147
|
+
fetchImpl: this.cfg.fetchImpl
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
install() {
|
|
151
|
+
if (typeof window !== "undefined") {
|
|
152
|
+
window.addEventListener("error", (ev) => this.reportWindowError(ev));
|
|
153
|
+
window.addEventListener("unhandledrejection", (ev) => this.reportUnhandled(ev));
|
|
154
|
+
window.addEventListener("visibilitychange", () => {
|
|
155
|
+
if (document.visibilityState === "hidden") this.sender.flush();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (this.cfg.installPerfCollector) {
|
|
159
|
+
this.perf = new PerfCollector({ sender: this.sender });
|
|
160
|
+
this.perf.start();
|
|
161
|
+
}
|
|
162
|
+
this.flushHandle = setInterval(() => void this.sender.flush(), this.cfg.flushIntervalMs);
|
|
163
|
+
}
|
|
164
|
+
uninstall() {
|
|
165
|
+
this.perf?.stop();
|
|
166
|
+
this.perf = null;
|
|
167
|
+
if (this.flushHandle !== null) {
|
|
168
|
+
clearInterval(this.flushHandle);
|
|
169
|
+
this.flushHandle = null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
reportError(level, message, stack, payload) {
|
|
173
|
+
const event = {
|
|
174
|
+
level,
|
|
175
|
+
message,
|
|
176
|
+
stack_hash: this.hashStack(stack),
|
|
177
|
+
source: "js",
|
|
178
|
+
payload: payload === void 0 ? null : JSON.stringify(payload),
|
|
179
|
+
ts: Date.now()
|
|
180
|
+
};
|
|
181
|
+
this.sender.queueError(event);
|
|
182
|
+
}
|
|
183
|
+
reportAudit(eventType, payload) {
|
|
184
|
+
const json = payload === void 0 ? null : JSON.stringify(payload);
|
|
185
|
+
const event = {
|
|
186
|
+
event_type: eventType,
|
|
187
|
+
payload_hash: this.sha256Hex(json ?? ""),
|
|
188
|
+
payload: json,
|
|
189
|
+
source: "js",
|
|
190
|
+
ts: Date.now()
|
|
191
|
+
};
|
|
192
|
+
this.sender.queueAudit(event);
|
|
193
|
+
}
|
|
194
|
+
reportWindowError(ev) {
|
|
195
|
+
this.reportError("error", `${ev.message} @ ${ev.filename}:${ev.lineno}:${ev.colno}`, ev.error?.stack ?? `${ev.message} @ ${ev.filename}:${ev.lineno}`);
|
|
196
|
+
}
|
|
197
|
+
reportUnhandled(ev) {
|
|
198
|
+
const reason = ev.reason;
|
|
199
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
200
|
+
const stack = reason instanceof Error ? reason.stack ?? message : message;
|
|
201
|
+
this.reportError("error", `unhandledrejection: ${message}`, stack);
|
|
202
|
+
}
|
|
203
|
+
hashStack(stack) {
|
|
204
|
+
const normalized = stack.replace(/\(\d+\)/g, "()").replace(/:\d+:\d+/g, ":N:N");
|
|
205
|
+
return this.sha256Hex(normalized);
|
|
206
|
+
}
|
|
207
|
+
sha256Hex(input) {
|
|
208
|
+
if (typeof crypto === "undefined" || crypto.subtle === void 0) return this.fnv1a64Hex(input);
|
|
209
|
+
return this.fnv1a64Hex(input);
|
|
210
|
+
}
|
|
211
|
+
fnv1a64Hex(input) {
|
|
212
|
+
let h = 14695981039346656037n;
|
|
213
|
+
const prime = 1099511628211n;
|
|
214
|
+
for (let i = 0; i < input.length; i++) {
|
|
215
|
+
h ^= BigInt(input.charCodeAt(i));
|
|
216
|
+
h = h * prime & 18446744073709551615n;
|
|
217
|
+
}
|
|
218
|
+
const hex = h.toString(16).padStart(16, "0");
|
|
219
|
+
return (hex + hex + hex + hex).slice(0, 64);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
//#endregion
|
|
223
|
+
export { BatchSender, ObservabilityClient, PerfCollector };
|
|
224
|
+
|
|
225
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/observability/BatchSender.ts","../../src/observability/PerfCollector.ts","../../src/observability/ObservabilityClient.ts"],"sourcesContent":["// Buffers events per family and ships them to the host plugin relay.\n//\n// Caller is responsible for installing flush triggers — typical wiring:\n// - addEventListener(\"visibilitychange\") on hidden → flush\n// - setInterval(flush, 30_000)\n// - register flush() in onbeforeunload (best-effort via fetch keepalive)\n\nimport type {\n BrowserAuditEvent,\n BrowserErrorEvent,\n BrowserPerfEvent,\n} from \"./types.js\";\n\nexport interface BatchSenderConfig {\n relayBaseUri: string;\n maxBatchSize?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport class BatchSender {\n private errors: BrowserErrorEvent[] = [];\n private perf: BrowserPerfEvent[] = [];\n private events: BrowserAuditEvent[] = [];\n private readonly cfg: Required<Omit<BatchSenderConfig, \"fetchImpl\">> & {\n fetchImpl: typeof fetch;\n };\n\n constructor(cfg: BatchSenderConfig) {\n this.cfg = {\n relayBaseUri: cfg.relayBaseUri.replace(/\\/$/, \"\"),\n maxBatchSize: cfg.maxBatchSize ?? 1000,\n fetchImpl: cfg.fetchImpl ?? globalThis.fetch.bind(globalThis),\n };\n }\n\n queueError(e: BrowserErrorEvent): void {\n this.errors = this.cap(this.errors, e);\n }\n\n queuePerf(e: BrowserPerfEvent): void {\n this.perf = this.cap(this.perf, e);\n }\n\n queueAudit(e: BrowserAuditEvent): void {\n this.events = this.cap(this.events, e);\n }\n\n async flush(): Promise<void> {\n const tasks: Array<Promise<void>> = [];\n if (this.errors.length > 0) {\n const batch = this.errors;\n this.errors = [];\n tasks.push(this.send(\"/observability/errors\", batch));\n }\n if (this.perf.length > 0) {\n const batch = this.perf;\n this.perf = [];\n tasks.push(this.send(\"/observability/perf\", batch));\n }\n if (this.events.length > 0) {\n const batch = this.events;\n this.events = [];\n tasks.push(this.send(\"/observability/events\", batch));\n }\n await Promise.all(tasks);\n }\n\n size(): number {\n return this.errors.length + this.perf.length + this.events.length;\n }\n\n private cap<T>(arr: T[], next: T): T[] {\n if (arr.length >= this.cfg.maxBatchSize) {\n arr.shift();\n }\n arr.push(next);\n return arr;\n }\n\n private async send(path: string, body: unknown[]): Promise<void> {\n const url = this.cfg.relayBaseUri + path;\n await this.cfg.fetchImpl(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n // Keep-alive lets us best-effort flush during unload.\n keepalive: true,\n });\n }\n}\n","// Collects browser perf timings via the Performance API and feeds them\n// to a BatchSender. Caller chooses which entry types to sample.\n\nimport { BatchSender } from \"./BatchSender.js\";\nimport type { BrowserPerfEvent } from \"./types.js\";\n\nexport interface PerfCollectorConfig {\n sender: BatchSender;\n metricsToCollect?: Array<{ name: string; entryType: string; attr: keyof PerformanceEntry | \"duration\" }>;\n sampleRate?: number; // 0..1\n}\n\nexport class PerfCollector {\n private readonly cfg: Required<PerfCollectorConfig>;\n private observer: PerformanceObserver | null = null;\n\n constructor(cfg: PerfCollectorConfig) {\n this.cfg = {\n sender: cfg.sender,\n metricsToCollect: cfg.metricsToCollect ?? defaultMetrics(),\n sampleRate: cfg.sampleRate ?? 1.0,\n };\n }\n\n start(): void {\n if (typeof PerformanceObserver === \"undefined\") return;\n if (this.observer !== null) return;\n\n const types = new Set(this.cfg.metricsToCollect.map((m) => m.entryType));\n this.observer = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n if (!types.has(entry.entryType)) continue;\n if (Math.random() > this.cfg.sampleRate) continue;\n const match = this.cfg.metricsToCollect.find((m) => m.entryType === entry.entryType);\n if (!match) continue;\n const raw = match.attr === \"duration\" ? entry.duration : (entry as unknown as Record<string, unknown>)[match.attr];\n if (typeof raw !== \"number\" || !Number.isFinite(raw)) continue;\n const ev: BrowserPerfEvent = {\n metric_name: match.name,\n value_ms: raw,\n source: \"js\",\n ts: Date.now(),\n };\n this.cfg.sender.queuePerf(ev);\n }\n });\n for (const t of types) {\n try {\n this.observer.observe({ type: t, buffered: true });\n } catch {\n // entry type not supported in this browser — skip silently.\n }\n }\n }\n\n stop(): void {\n if (this.observer !== null) {\n this.observer.disconnect();\n this.observer = null;\n }\n }\n}\n\nfunction defaultMetrics(): NonNullable<PerfCollectorConfig[\"metricsToCollect\"]> {\n return [\n { name: \"page_load_ms\", entryType: \"navigation\", attr: \"duration\" },\n { name: \"fcp_ms\", entryType: \"paint\", attr: \"startTime\" as keyof PerformanceEntry },\n { name: \"lcp_ms\", entryType: \"largest-contentful-paint\", attr: \"startTime\" as keyof PerformanceEntry },\n { name: \"resource_ms\", entryType: \"resource\", attr: \"duration\" },\n ];\n}\n","// High-level façade over BatchSender. Owns the global window error/unhandled\n// rejection handlers and the periodic flush loop. Caller calls install()\n// once on app boot.\n\nimport { BatchSender } from \"./BatchSender.js\";\nimport { PerfCollector } from \"./PerfCollector.js\";\nimport type { BrowserErrorEvent, BrowserAuditEvent, ObsLevel } from \"./types.js\";\n\nexport interface ObservabilityClientConfig {\n relayBaseUri: string;\n flushIntervalMs?: number;\n installPerfCollector?: boolean;\n fetchImpl?: typeof fetch;\n}\n\nexport class ObservabilityClient {\n readonly sender: BatchSender;\n private perf: PerfCollector | null = null;\n private flushHandle: ReturnType<typeof setInterval> | null = null;\n private cfg: Required<ObservabilityClientConfig>;\n\n constructor(cfg: ObservabilityClientConfig) {\n this.cfg = {\n relayBaseUri: cfg.relayBaseUri,\n flushIntervalMs: cfg.flushIntervalMs ?? 30_000,\n installPerfCollector: cfg.installPerfCollector ?? true,\n fetchImpl: cfg.fetchImpl ?? globalThis.fetch.bind(globalThis),\n };\n this.sender = new BatchSender({\n relayBaseUri: this.cfg.relayBaseUri,\n fetchImpl: this.cfg.fetchImpl,\n });\n }\n\n install(): void {\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"error\", (ev) => this.reportWindowError(ev));\n window.addEventListener(\"unhandledrejection\", (ev) => this.reportUnhandled(ev));\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.sender.flush();\n }\n });\n }\n if (this.cfg.installPerfCollector) {\n this.perf = new PerfCollector({ sender: this.sender });\n this.perf.start();\n }\n this.flushHandle = setInterval(() => void this.sender.flush(), this.cfg.flushIntervalMs);\n }\n\n uninstall(): void {\n this.perf?.stop();\n this.perf = null;\n if (this.flushHandle !== null) {\n clearInterval(this.flushHandle);\n this.flushHandle = null;\n }\n }\n\n reportError(level: ObsLevel, message: string, stack: string, payload?: unknown): void {\n const event: BrowserErrorEvent = {\n level,\n message,\n stack_hash: this.hashStack(stack),\n source: \"js\",\n payload: payload === undefined ? null : JSON.stringify(payload),\n ts: Date.now(),\n };\n this.sender.queueError(event);\n }\n\n reportAudit(eventType: string, payload?: Record<string, unknown>): void {\n const json = payload === undefined ? null : JSON.stringify(payload);\n const event: BrowserAuditEvent = {\n event_type: eventType,\n payload_hash: this.sha256Hex(json ?? \"\"),\n payload: json,\n source: \"js\",\n ts: Date.now(),\n };\n this.sender.queueAudit(event);\n }\n\n private reportWindowError(ev: ErrorEvent): void {\n this.reportError(\n \"error\",\n `${ev.message} @ ${ev.filename}:${ev.lineno}:${ev.colno}`,\n ev.error?.stack ?? `${ev.message} @ ${ev.filename}:${ev.lineno}`,\n );\n }\n\n private reportUnhandled(ev: PromiseRejectionEvent): void {\n const reason = ev.reason;\n const message = reason instanceof Error ? reason.message : String(reason);\n const stack = reason instanceof Error ? (reason.stack ?? message) : message;\n this.reportError(\"error\", `unhandledrejection: ${message}`, stack);\n }\n\n private hashStack(stack: string): string {\n // Normalize line numbers + parenthesized fragments so the same code path\n // produces the same hash across minified builds.\n const normalized = stack.replace(/\\(\\d+\\)/g, \"()\").replace(/:\\d+:\\d+/g, \":N:N\");\n return this.sha256Hex(normalized);\n }\n\n private sha256Hex(input: string): string {\n // synchronous fnv-1a-ish fallback when SubtleCrypto unavailable (Node)\n // — adequate for de-dup hashing, NOT for cryptographic use.\n if (typeof crypto === \"undefined\" || crypto.subtle === undefined) {\n return this.fnv1a64Hex(input);\n }\n // Note: callers use the result for de-dup keys; we sync-stub here\n // because async hash would require restructuring queue calls.\n return this.fnv1a64Hex(input);\n }\n\n private fnv1a64Hex(input: string): string {\n let h = 0xcbf29ce484222325n;\n const prime = 0x100000001b3n;\n for (let i = 0; i < input.length; i++) {\n h ^= BigInt(input.charCodeAt(i));\n h = (h * prime) & 0xffffffffffffffffn;\n }\n // pad to 64 hex chars by doubling\n const hex = h.toString(16).padStart(16, \"0\");\n return (hex + hex + hex + hex).slice(0, 64);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ObsLevel = "debug" | "info" | "warning" | "error" | "critical";
|
|
2
|
+
export interface BrowserErrorEvent {
|
|
3
|
+
level: ObsLevel;
|
|
4
|
+
message: string;
|
|
5
|
+
stack_hash: string;
|
|
6
|
+
source: "js";
|
|
7
|
+
payload?: string | null;
|
|
8
|
+
ts: number;
|
|
9
|
+
}
|
|
10
|
+
export interface BrowserPerfEvent {
|
|
11
|
+
metric_name: string;
|
|
12
|
+
value_ms: number;
|
|
13
|
+
source: "js";
|
|
14
|
+
ts: number;
|
|
15
|
+
}
|
|
16
|
+
export interface BrowserAuditEvent {
|
|
17
|
+
event_type: string;
|
|
18
|
+
payload_hash: string;
|
|
19
|
+
payload?: string | null;
|
|
20
|
+
source: "js";
|
|
21
|
+
ts: number;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/observability/types.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAE3E,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,IAAI,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface SessionSummary {
|
|
2
|
+
session_id: string;
|
|
3
|
+
middag_user_id: string;
|
|
4
|
+
tier: 1 | 2 | 3 | 4;
|
|
5
|
+
status: "pending" | "active" | "ended" | "revoked" | "expired";
|
|
6
|
+
granted_at: number;
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AuditBannerConfig {
|
|
10
|
+
container: HTMLElement;
|
|
11
|
+
fetchSessions: () => Promise<SessionSummary[]>;
|
|
12
|
+
onRevoke?: (sessionId: string) => Promise<void>;
|
|
13
|
+
pollIntervalMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class AuditBanner {
|
|
16
|
+
private cfg;
|
|
17
|
+
private pollHandle;
|
|
18
|
+
constructor(cfg: AuditBannerConfig);
|
|
19
|
+
mount(): Promise<void>;
|
|
20
|
+
unmount(): void;
|
|
21
|
+
private refresh;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=AuditBanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditBanner.d.ts","sourceRoot":"","sources":["../../src/remote-support/AuditBanner.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,WAAW,CAAC;IACvB,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAET;IACF,OAAO,CAAC,UAAU,CAA+C;gBAErD,GAAG,EAAE,iBAAiB;IAS5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,OAAO,IAAI,IAAI;YAQD,OAAO;CAyBtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/remote-support/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/remote-support/AuditBanner.ts
|
|
2
|
+
var AuditBanner = class {
|
|
3
|
+
cfg;
|
|
4
|
+
pollHandle = null;
|
|
5
|
+
constructor(cfg) {
|
|
6
|
+
this.cfg = {
|
|
7
|
+
container: cfg.container,
|
|
8
|
+
fetchSessions: cfg.fetchSessions,
|
|
9
|
+
onRevoke: cfg.onRevoke,
|
|
10
|
+
pollIntervalMs: cfg.pollIntervalMs ?? 3e4
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async mount() {
|
|
14
|
+
await this.refresh();
|
|
15
|
+
this.pollHandle = setInterval(() => void this.refresh(), this.cfg.pollIntervalMs);
|
|
16
|
+
}
|
|
17
|
+
unmount() {
|
|
18
|
+
if (this.pollHandle !== null) {
|
|
19
|
+
clearInterval(this.pollHandle);
|
|
20
|
+
this.pollHandle = null;
|
|
21
|
+
}
|
|
22
|
+
this.cfg.container.innerHTML = "";
|
|
23
|
+
}
|
|
24
|
+
async refresh() {
|
|
25
|
+
const active = (await this.cfg.fetchSessions()).filter((s) => s.status === "active");
|
|
26
|
+
this.cfg.container.innerHTML = "";
|
|
27
|
+
if (active.length === 0) return;
|
|
28
|
+
const banner = document.createElement("div");
|
|
29
|
+
banner.setAttribute("data-middag-remote-support-banner", "");
|
|
30
|
+
banner.style.cssText = "padding:12px 16px;background:#fffbe6;border:1px solid #facc15;border-radius:6px;font-family:system-ui;";
|
|
31
|
+
for (const session of active) {
|
|
32
|
+
const row = document.createElement("div");
|
|
33
|
+
row.textContent = `MIDDAG support active (tier ${session.tier}) — ${session.reason}`;
|
|
34
|
+
if (this.cfg.onRevoke) {
|
|
35
|
+
const btn = document.createElement("button");
|
|
36
|
+
btn.textContent = "Revoke";
|
|
37
|
+
btn.style.marginLeft = "12px";
|
|
38
|
+
btn.addEventListener("click", () => {
|
|
39
|
+
this.cfg.onRevoke?.(session.session_id).then(() => this.refresh());
|
|
40
|
+
});
|
|
41
|
+
row.appendChild(btn);
|
|
42
|
+
}
|
|
43
|
+
banner.appendChild(row);
|
|
44
|
+
}
|
|
45
|
+
this.cfg.container.appendChild(banner);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { AuditBanner };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/remote-support/AuditBanner.ts"],"sourcesContent":["// Optional dashboard banner showing active remote-support sessions.\n// Tenant-facing visibility per ADR-013 extended (tier 3+4 non-consent\n// flows require instant tenant notification).\n//\n// The plugin is responsible for fetching session state — this widget\n// just renders.\n\nexport interface SessionSummary {\n session_id: string;\n middag_user_id: string;\n tier: 1 | 2 | 3 | 4;\n status: \"pending\" | \"active\" | \"ended\" | \"revoked\" | \"expired\";\n granted_at: number;\n reason: string;\n}\n\nexport interface AuditBannerConfig {\n container: HTMLElement;\n fetchSessions: () => Promise<SessionSummary[]>;\n onRevoke?: (sessionId: string) => Promise<void>;\n pollIntervalMs?: number;\n}\n\nexport class AuditBanner {\n private cfg: Required<Omit<AuditBannerConfig, \"onRevoke\">> & {\n onRevoke?: (sessionId: string) => Promise<void>;\n };\n private pollHandle: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: AuditBannerConfig) {\n this.cfg = {\n container: cfg.container,\n fetchSessions: cfg.fetchSessions,\n onRevoke: cfg.onRevoke,\n pollIntervalMs: cfg.pollIntervalMs ?? 30_000,\n };\n }\n\n async mount(): Promise<void> {\n await this.refresh();\n this.pollHandle = setInterval(() => void this.refresh(), this.cfg.pollIntervalMs);\n }\n\n unmount(): void {\n if (this.pollHandle !== null) {\n clearInterval(this.pollHandle);\n this.pollHandle = null;\n }\n this.cfg.container.innerHTML = \"\";\n }\n\n private async refresh(): Promise<void> {\n const sessions = await this.cfg.fetchSessions();\n const active = sessions.filter((s) => s.status === \"active\");\n this.cfg.container.innerHTML = \"\";\n if (active.length === 0) return;\n const banner = document.createElement(\"div\");\n banner.setAttribute(\"data-middag-remote-support-banner\", \"\");\n banner.style.cssText =\n \"padding:12px 16px;background:#fffbe6;border:1px solid #facc15;border-radius:6px;font-family:system-ui;\";\n for (const session of active) {\n const row = document.createElement(\"div\");\n row.textContent = `MIDDAG support active (tier ${session.tier}) — ${session.reason}`;\n if (this.cfg.onRevoke) {\n const btn = document.createElement(\"button\");\n btn.textContent = \"Revoke\";\n btn.style.marginLeft = \"12px\";\n btn.addEventListener(\"click\", () => {\n void this.cfg.onRevoke?.(session.session_id).then(() => this.refresh());\n });\n row.appendChild(btn);\n }\n banner.appendChild(row);\n }\n this.cfg.container.appendChild(banner);\n }\n}\n"],"mappings":";AAuBA,IAAa,cAAb,MAAyB;CACvB;CAGA,aAA4D;CAE5D,YAAY,KAAwB;EAClC,KAAK,MAAM;GACT,WAAW,IAAI;GACf,eAAe,IAAI;GACnB,UAAU,IAAI;GACd,gBAAgB,IAAI,kBAAkB;EACxC;CACF;CAEA,MAAM,QAAuB;EAC3B,MAAM,KAAK,QAAQ;EACnB,KAAK,aAAa,kBAAkB,KAAK,KAAK,QAAQ,GAAG,KAAK,IAAI,cAAc;CAClF;CAEA,UAAgB;EACd,IAAI,KAAK,eAAe,MAAM;GAC5B,cAAc,KAAK,UAAU;GAC7B,KAAK,aAAa;EACpB;EACA,KAAK,IAAI,UAAU,YAAY;CACjC;CAEA,MAAc,UAAyB;EAErC,MAAM,UAAS,MADQ,KAAK,IAAI,cAAc,GACtB,QAAQ,MAAM,EAAE,WAAW,QAAQ;EAC3D,KAAK,IAAI,UAAU,YAAY;EAC/B,IAAI,OAAO,WAAW,GAAG;EACzB,MAAM,SAAS,SAAS,cAAc,KAAK;EAC3C,OAAO,aAAa,qCAAqC,EAAE;EAC3D,OAAO,MAAM,UACX;EACF,KAAK,MAAM,WAAW,QAAQ;GAC5B,MAAM,MAAM,SAAS,cAAc,KAAK;GACxC,IAAI,cAAc,+BAA+B,QAAQ,KAAK,MAAM,QAAQ;GAC5E,IAAI,KAAK,IAAI,UAAU;IACrB,MAAM,MAAM,SAAS,cAAc,QAAQ;IAC3C,IAAI,cAAc;IAClB,IAAI,MAAM,aAAa;IACvB,IAAI,iBAAiB,eAAe;KAClC,KAAU,IAAI,WAAW,QAAQ,UAAU,EAAE,WAAW,KAAK,QAAQ,CAAC;IACxE,CAAC;IACD,IAAI,YAAY,GAAG;GACrB;GACA,OAAO,YAAY,GAAG;EACxB;EACA,KAAK,IAAI,UAAU,YAAY,MAAM;CACvC;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ObservabilityClient } from "../observability/ObservabilityClient.js";
|
|
2
|
+
export declare class TelemetryClient {
|
|
3
|
+
private readonly obs;
|
|
4
|
+
constructor(obs: ObservabilityClient);
|
|
5
|
+
track(eventType: string, payload?: Record<string, unknown>): void;
|
|
6
|
+
}
|
|
7
|
+
export declare class UsageTracker {
|
|
8
|
+
private readonly client;
|
|
9
|
+
constructor(client: TelemetryClient);
|
|
10
|
+
featureUsed(featureKey: string, variant?: string): void;
|
|
11
|
+
flowCompleted(flowKey: string, stepCount: number): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=TelemetryClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TelemetryClient.d.ts","sourceRoot":"","sources":["../../src/telemetry/TelemetryClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAEnF,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,mBAAmB;IAErD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAGlE;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IAEpD,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAIvD,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;CAGxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/telemetry/TelemetryClient.ts
|
|
2
|
+
var TelemetryClient = class {
|
|
3
|
+
obs;
|
|
4
|
+
constructor(obs) {
|
|
5
|
+
this.obs = obs;
|
|
6
|
+
}
|
|
7
|
+
track(eventType, payload) {
|
|
8
|
+
this.obs.reportAudit(eventType, payload);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var UsageTracker = class {
|
|
12
|
+
client;
|
|
13
|
+
constructor(client) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
}
|
|
16
|
+
featureUsed(featureKey, variant) {
|
|
17
|
+
this.client.track("feature_used", {
|
|
18
|
+
feature: featureKey,
|
|
19
|
+
variant: variant ?? null
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
flowCompleted(flowKey, stepCount) {
|
|
23
|
+
this.client.track("flow_completed", {
|
|
24
|
+
flow: flowKey,
|
|
25
|
+
step_count: stepCount
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { TelemetryClient, UsageTracker };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/telemetry/TelemetryClient.ts"],"sourcesContent":["// Sugar over ObservabilityClient.reportAudit for non-billable telemetry.\n\nimport type { ObservabilityClient } from \"../observability/ObservabilityClient.js\";\n\nexport class TelemetryClient {\n constructor(private readonly obs: ObservabilityClient) {}\n\n track(eventType: string, payload?: Record<string, unknown>): void {\n this.obs.reportAudit(eventType, payload);\n }\n}\n\nexport class UsageTracker {\n constructor(private readonly client: TelemetryClient) {}\n\n featureUsed(featureKey: string, variant?: string): void {\n this.client.track(\"feature_used\", { feature: featureKey, variant: variant ?? null });\n }\n\n flowCompleted(flowKey: string, stepCount: number): void {\n this.client.track(\"flow_completed\", { flow: flowKey, step_count: stepCount });\n }\n}\n"],"mappings":";AAIA,IAAa,kBAAb,MAA6B;CACE;CAA7B,YAAY,KAA2C;EAA1B,KAAA,MAAA;CAA2B;CAExD,MAAM,WAAmB,SAAyC;EAChE,KAAK,IAAI,YAAY,WAAW,OAAO;CACzC;AACF;AAEA,IAAa,eAAb,MAA0B;CACK;CAA7B,YAAY,QAA0C;EAAzB,KAAA,SAAA;CAA0B;CAEvD,YAAY,YAAoB,SAAwB;EACtD,KAAK,OAAO,MAAM,gBAAgB;GAAE,SAAS;GAAY,SAAS,WAAW;EAAK,CAAC;CACrF;CAEA,cAAc,SAAiB,WAAyB;EACtD,KAAK,OAAO,MAAM,kBAAkB;GAAE,MAAM;GAAS,YAAY;EAAU,CAAC;CAC9E;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@middag-io/licensing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MIDDAG protected code delivery — build-time placeholder injection, manifest contract, and runtime loader for licensed JS modules",
|
|
6
6
|
"repository": {
|
|
@@ -29,6 +29,22 @@
|
|
|
29
29
|
"./client": {
|
|
30
30
|
"import": "./dist/client/index.js",
|
|
31
31
|
"types": "./dist/client/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./anti-tamper": {
|
|
34
|
+
"import": "./dist/anti-tamper/index.js",
|
|
35
|
+
"types": "./dist/anti-tamper/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./observability": {
|
|
38
|
+
"import": "./dist/observability/index.js",
|
|
39
|
+
"types": "./dist/observability/index.d.ts"
|
|
40
|
+
},
|
|
41
|
+
"./telemetry": {
|
|
42
|
+
"import": "./dist/telemetry/index.js",
|
|
43
|
+
"types": "./dist/telemetry/index.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./remote-support": {
|
|
46
|
+
"import": "./dist/remote-support/index.js",
|
|
47
|
+
"types": "./dist/remote-support/index.d.ts"
|
|
32
48
|
}
|
|
33
49
|
},
|
|
34
50
|
"files": [
|