@arirocha/openrisk-client 0.1.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.
Files changed (46) hide show
  1. package/README.md +21 -0
  2. package/dist/client.d.ts +11 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/fingerprint/index.d.ts +8 -0
  5. package/dist/fingerprint/index.d.ts.map +1 -0
  6. package/dist/fingerprint/raw.d.ts +4 -0
  7. package/dist/fingerprint/raw.d.ts.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +407 -0
  11. package/dist/lifecycle/interaction.d.ts +4 -0
  12. package/dist/lifecycle/interaction.d.ts.map +1 -0
  13. package/dist/lifecycle/page.d.ts +3 -0
  14. package/dist/lifecycle/page.d.ts.map +1 -0
  15. package/dist/lifecycle/scroll.d.ts +4 -0
  16. package/dist/lifecycle/scroll.d.ts.map +1 -0
  17. package/dist/lifecycle/unload.d.ts +3 -0
  18. package/dist/lifecycle/unload.d.ts.map +1 -0
  19. package/dist/lifecycle/visibility.d.ts +3 -0
  20. package/dist/lifecycle/visibility.d.ts.map +1 -0
  21. package/dist/openrisk.js +375 -0
  22. package/dist/payload.d.ts +10 -0
  23. package/dist/payload.d.ts.map +1 -0
  24. package/dist/signals/behavior.d.ts +10 -0
  25. package/dist/signals/behavior.d.ts.map +1 -0
  26. package/dist/signals/cadence.d.ts +12 -0
  27. package/dist/signals/cadence.d.ts.map +1 -0
  28. package/dist/signals/device.d.ts +20 -0
  29. package/dist/signals/device.d.ts.map +1 -0
  30. package/dist/signals/environment.d.ts +6 -0
  31. package/dist/signals/environment.d.ts.map +1 -0
  32. package/dist/signals/features.d.ts +8 -0
  33. package/dist/signals/features.d.ts.map +1 -0
  34. package/dist/signals/integrity.d.ts +7 -0
  35. package/dist/signals/integrity.d.ts.map +1 -0
  36. package/dist/signals/multitabs.d.ts +5 -0
  37. package/dist/signals/multitabs.d.ts.map +1 -0
  38. package/dist/signals/navigation.d.ts +6 -0
  39. package/dist/signals/navigation.d.ts.map +1 -0
  40. package/dist/signals/session.d.ts +7 -0
  41. package/dist/signals/session.d.ts.map +1 -0
  42. package/dist/signals/timing.d.ts +6 -0
  43. package/dist/signals/timing.d.ts.map +1 -0
  44. package/dist/types.d.ts +24 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/package.json +49 -0
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # openrisk-client
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run tests:
10
+
11
+ ```bash
12
+ bun test
13
+ ```
14
+
15
+ To build:
16
+
17
+ ```bash
18
+ bun run build
19
+ ```
20
+
21
+ This project was created using `bun init` in bun v1.3.1. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
@@ -0,0 +1,11 @@
1
+ import type { RiskClientOptions } from "./types";
2
+ export declare class OpenRiskClient {
3
+ private readonly options;
4
+ constructor(options: RiskClientOptions);
5
+ emit(eventName: string, extra?: Record<string, unknown>): void;
6
+ track(eventName: string, extra?: Record<string, unknown>): void;
7
+ private send;
8
+ }
9
+ export declare class RiskClient extends OpenRiskClient {
10
+ }
11
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAiBjD,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAmBvD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIvD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAmBxD,OAAO,CAAC,IAAI;CAmBb;AAGD,qBAAa,UAAW,SAAQ,cAAc;CAAG"}
@@ -0,0 +1,8 @@
1
+ export declare function collectFingerprint(): {
2
+ mode: string;
3
+ version: string;
4
+ data: {
5
+ canvas_fp: string | null;
6
+ };
7
+ };
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fingerprint/index.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB;;;;;;EAMjC"}
@@ -0,0 +1,4 @@
1
+ export declare function collectRawFingerprint(): {
2
+ canvas_fp: string | null;
3
+ };
4
+ //# sourceMappingURL=raw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raw.d.ts","sourceRoot":"","sources":["../../src/fingerprint/raw.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB;;EAepC"}
@@ -0,0 +1,3 @@
1
+ export { OpenRiskClient, RiskClient } from "./client";
2
+ export * from "./types";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtD,cAAc,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,407 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/index.ts
30
+ var exports_src = {};
31
+ __export(exports_src, {
32
+ RiskClient: () => RiskClient,
33
+ OpenRiskClient: () => OpenRiskClient
34
+ });
35
+ module.exports = __toCommonJS(exports_src);
36
+
37
+ // src/signals/behavior.ts
38
+ var mouseMoves = 0;
39
+ var lastMove = 0;
40
+ var deltas = [];
41
+ window.addEventListener("mousemove", () => {
42
+ const now = performance.now();
43
+ if (lastMove)
44
+ deltas.push(now - lastMove);
45
+ lastMove = now;
46
+ mouseMoves++;
47
+ });
48
+ function collectBehavior() {
49
+ if (!deltas.length) {
50
+ return {
51
+ mouse_entropy: null
52
+ };
53
+ }
54
+ const avg = deltas.reduce((a, b) => a + b, 0) / deltas.length;
55
+ return {
56
+ mouse_entropy: Math.min(1, mouseMoves / 50),
57
+ avg_mouse_interval_ms: Math.round(avg)
58
+ };
59
+ }
60
+
61
+ // src/signals/cadence.ts
62
+ var timestamps = [];
63
+ var WINDOW_MS = 60000;
64
+ var STRUCTURAL_EVENTS = new Set([
65
+ "session_start",
66
+ "page_view",
67
+ "page_loaded"
68
+ ]);
69
+ function recordEventTimestamp(eventName) {
70
+ if (STRUCTURAL_EVENTS.has(eventName))
71
+ return;
72
+ const now = Date.now();
73
+ timestamps.push(now);
74
+ while (timestamps.length > 0) {
75
+ const oldest = timestamps[0];
76
+ if (oldest !== undefined && now - oldest > WINDOW_MS) {
77
+ timestamps.shift();
78
+ } else {
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ function collectCadence() {
84
+ if (timestamps.length < 2) {
85
+ return {
86
+ events_per_minute: timestamps.length,
87
+ avg_event_interval_ms: null,
88
+ burst_detected: false
89
+ };
90
+ }
91
+ const intervals = [];
92
+ for (let i = 1;i < timestamps.length; i++) {
93
+ intervals.push(timestamps[i] - timestamps[i - 1]);
94
+ }
95
+ const avg = intervals.reduce((a, b) => a + b, 0) / intervals.length;
96
+ const burst = intervals.some((i) => i < 50);
97
+ return {
98
+ events_per_minute: timestamps.length,
99
+ avg_event_interval_ms: Math.round(avg),
100
+ burst_detected: burst
101
+ };
102
+ }
103
+
104
+ // src/fingerprint/raw.ts
105
+ function collectRawFingerprint() {
106
+ const canvas = document.createElement("canvas");
107
+ const ctx = canvas.getContext("2d");
108
+ let canvas_fp = null;
109
+ if (ctx) {
110
+ ctx.textBaseline = "top";
111
+ ctx.font = "14px Arial";
112
+ ctx.fillText("openrisk-fp", 2, 2);
113
+ canvas_fp = canvas.toDataURL();
114
+ }
115
+ return {
116
+ canvas_fp
117
+ };
118
+ }
119
+
120
+ // src/fingerprint/index.ts
121
+ function collectFingerprint() {
122
+ return {
123
+ mode: "raw",
124
+ version: "fp_raw_v1",
125
+ data: collectRawFingerprint()
126
+ };
127
+ }
128
+
129
+ // src/signals/device.ts
130
+ function collectDevice() {
131
+ const screen = window.screen;
132
+ return {
133
+ platform: navigator.platform || "unknown",
134
+ user_agent: navigator.userAgent || "unknown",
135
+ cpu_cores: navigator.hardwareConcurrency ?? null,
136
+ memory_gb: navigator.deviceMemory ?? null,
137
+ touch_support: "ontouchstart" in window,
138
+ screen: {
139
+ width: screen?.width ?? null,
140
+ height: screen?.height ?? null,
141
+ pixel_ratio: window.devicePixelRatio ?? 1
142
+ },
143
+ fingerprint: collectFingerprint()
144
+ };
145
+ }
146
+
147
+ // src/signals/environment.ts
148
+ function collectEnvironment() {
149
+ return {
150
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
151
+ locale: navigator.language,
152
+ languages: navigator.languages
153
+ };
154
+ }
155
+
156
+ // src/signals/features.ts
157
+ function collectFeatures() {
158
+ return {
159
+ webdriver: navigator.webdriver === true,
160
+ plugins_count: navigator.plugins?.length ?? null,
161
+ languages_count: navigator.languages?.length ?? null,
162
+ permissions_api: "permissions" in navigator,
163
+ hardware_concurrency_present: typeof navigator.hardwareConcurrency === "number"
164
+ };
165
+ }
166
+
167
+ // src/signals/integrity.ts
168
+ function collectIntegrity() {
169
+ return {
170
+ sdk_version: "__SDK_VERSION__",
171
+ schema_version: "1.0",
172
+ timestamp_skew_ms: Math.abs(Date.now() - performance.timeOrigin),
173
+ telemetry_blocked: false
174
+ };
175
+ }
176
+
177
+ // src/signals/multitabs.ts
178
+ var TAB_ID = crypto.randomUUID();
179
+ var channel = new BroadcastChannel("openrisk_tabs");
180
+ var peers = new Set;
181
+ peers.add(TAB_ID);
182
+ channel.onmessage = (event) => {
183
+ const msg = event.data;
184
+ if (!msg || msg.tabId === TAB_ID)
185
+ return;
186
+ if (msg.type === "hello") {
187
+ peers.add(msg.tabId);
188
+ channel.postMessage({
189
+ type: "hello_ack",
190
+ tabId: TAB_ID
191
+ });
192
+ }
193
+ if (msg.type === "hello_ack") {
194
+ peers.add(msg.tabId);
195
+ }
196
+ };
197
+ setTimeout(() => {
198
+ channel.postMessage({
199
+ type: "hello",
200
+ tabId: TAB_ID
201
+ });
202
+ }, 50);
203
+ function collectMultiTabs() {
204
+ return {
205
+ concurrent_tabs_estimate: peers.size,
206
+ multiple_tabs_detected: peers.size > 1
207
+ };
208
+ }
209
+
210
+ // src/signals/navigation.ts
211
+ function collectNavigation() {
212
+ const nav = performance.getEntriesByType("navigation")[0];
213
+ return {
214
+ entry_type: nav?.type ?? "unknown",
215
+ referrer_type: document.referrer ? "external" : "direct",
216
+ path_depth: location.pathname.split("/").filter(Boolean).length
217
+ };
218
+ }
219
+
220
+ // src/signals/session.ts
221
+ var sessionStart = Date.now();
222
+ var eventCount = 0;
223
+ var pageCount = 1;
224
+ var visibilityChanges = 0;
225
+ document.addEventListener("visibilitychange", () => {
226
+ visibilityChanges++;
227
+ });
228
+ function collectSession() {
229
+ eventCount++;
230
+ return {
231
+ session_age_ms: Date.now() - sessionStart,
232
+ event_count: eventCount,
233
+ page_count: pageCount,
234
+ tab_visibility_changes: visibilityChanges
235
+ };
236
+ }
237
+
238
+ // src/signals/timing.ts
239
+ function collectTiming() {
240
+ const nav = performance.getEntriesByType("navigation")[0];
241
+ const pageLoad = nav && nav.domComplete > 0 ? Math.round(nav.domComplete - nav.startTime) : null;
242
+ const domInteractive = nav && nav.domInteractive > 0 ? Math.round(nav.domInteractive - nav.startTime) : null;
243
+ return {
244
+ page_load_ms: pageLoad,
245
+ dom_interactive_ms: domInteractive,
246
+ network_rtt_estimate_ms: navigator.connection?.rtt ?? null
247
+ };
248
+ }
249
+
250
+ // src/payload.ts
251
+ function buildPayload(eventName, extra, signalsOverride) {
252
+ const signals = signalsOverride || {
253
+ environment: collectEnvironment(),
254
+ device: collectDevice(),
255
+ session: collectSession(),
256
+ navigation: collectNavigation(),
257
+ timing: collectTiming(),
258
+ behavior: collectBehavior(),
259
+ cadence: collectCadence(),
260
+ features: collectFeatures(),
261
+ multitabs: collectMultiTabs(),
262
+ integrity: collectIntegrity()
263
+ };
264
+ return {
265
+ event: {
266
+ name: eventName,
267
+ timestamp: new Date().toISOString(),
268
+ event_id: crypto.randomUUID()
269
+ },
270
+ signals,
271
+ extra
272
+ };
273
+ }
274
+
275
+ // src/lifecycle/interaction.ts
276
+ var interacted = false;
277
+ function setupInteraction(client) {
278
+ const markInteraction = (type) => {
279
+ if (interacted)
280
+ return;
281
+ interacted = true;
282
+ client.emit("first_interaction", {
283
+ interaction_type: type
284
+ });
285
+ cleanup();
286
+ };
287
+ const cleanup = () => {
288
+ window.removeEventListener("mousemove", onMouse);
289
+ window.removeEventListener("keydown", onKey);
290
+ window.removeEventListener("touchstart", onTouch);
291
+ };
292
+ const onMouse = () => markInteraction("mouse");
293
+ const onKey = () => markInteraction("keyboard");
294
+ const onTouch = () => markInteraction("touch");
295
+ window.addEventListener("mousemove", onMouse, { passive: true });
296
+ window.addEventListener("keydown", onKey);
297
+ window.addEventListener("touchstart", onTouch, { passive: true });
298
+ }
299
+
300
+ // src/lifecycle/page.ts
301
+ function setupPageLifecycle(client) {
302
+ client.emit("page_view");
303
+ window.addEventListener("load", () => {
304
+ client.emit("page_loaded");
305
+ });
306
+ }
307
+
308
+ // src/lifecycle/scroll.ts
309
+ var maxScroll = 0;
310
+ var thresholds = [25, 50, 75, 100];
311
+ var sent = new Set;
312
+ function setupScroll(client) {
313
+ window.addEventListener("scroll", () => {
314
+ const scrollTop = window.scrollY;
315
+ const height = document.documentElement.scrollHeight - window.innerHeight;
316
+ if (height <= 0)
317
+ return;
318
+ const percent = Math.round(scrollTop / height * 100);
319
+ maxScroll = Math.max(maxScroll, percent);
320
+ for (const t of thresholds) {
321
+ if (percent >= t && !sent.has(t)) {
322
+ sent.add(t);
323
+ client.emit("scroll_depth", { percent: t });
324
+ }
325
+ }
326
+ }, { passive: true });
327
+ }
328
+
329
+ // src/lifecycle/unload.ts
330
+ function setupUnload(client) {
331
+ window.addEventListener("beforeunload", () => {
332
+ client.emit("session_end");
333
+ });
334
+ window.addEventListener("pagehide", () => {
335
+ client.emit("session_end");
336
+ });
337
+ }
338
+
339
+ // src/lifecycle/visibility.ts
340
+ function setupVisibility(client) {
341
+ document.addEventListener("visibilitychange", () => {
342
+ if (document.visibilityState === "hidden") {
343
+ client.emit("page_hidden");
344
+ } else if (document.visibilityState === "visible") {
345
+ client.emit("page_visible");
346
+ }
347
+ });
348
+ }
349
+
350
+ // src/client.ts
351
+ function emitDebug(payload) {
352
+ window.dispatchEvent(new CustomEvent("openrisk:debug", {
353
+ detail: payload
354
+ }));
355
+ }
356
+
357
+ class OpenRiskClient {
358
+ options;
359
+ constructor(options) {
360
+ this.options = options;
361
+ this.emit("session_start");
362
+ const setupPageLifecycleFn = this.options.setupPageLifecycle || setupPageLifecycle;
363
+ const setupVisibilityFn = this.options.setupVisibility || setupVisibility;
364
+ const setupInteractionFn = this.options.setupInteraction || setupInteraction;
365
+ const setupScrollFn = this.options.setupScroll || setupScroll;
366
+ const setupUnloadFn = this.options.setupUnload || setupUnload;
367
+ setupPageLifecycleFn(this);
368
+ setupVisibilityFn(this);
369
+ setupInteractionFn(this);
370
+ setupScrollFn(this);
371
+ setupUnloadFn(this);
372
+ }
373
+ emit(eventName, extra) {
374
+ this.track(eventName, extra);
375
+ }
376
+ track(eventName, extra) {
377
+ recordEventTimestamp(eventName);
378
+ const payload = buildPayload(eventName, extra, this.options.signalsOverride);
379
+ if (this.options.debug) {
380
+ emitDebug({
381
+ event: eventName,
382
+ payload
383
+ });
384
+ }
385
+ this.send(payload);
386
+ }
387
+ send(payload) {
388
+ try {
389
+ const body = JSON.stringify(payload);
390
+ if (navigator.sendBeacon) {
391
+ navigator.sendBeacon(this.options.endpoint, body);
392
+ return;
393
+ }
394
+ fetch(this.options.endpoint, {
395
+ method: "POST",
396
+ headers: {
397
+ "content-type": "application/json"
398
+ },
399
+ body,
400
+ keepalive: true
401
+ }).catch(() => {});
402
+ } catch {}
403
+ }
404
+ }
405
+
406
+ class RiskClient extends OpenRiskClient {
407
+ }
@@ -0,0 +1,4 @@
1
+ import type { RiskClient } from "../client";
2
+ export declare function setupInteraction(client: RiskClient): void;
3
+ export declare function resetInteractionState(): void;
4
+ //# sourceMappingURL=interaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../../src/lifecycle/interaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAI5C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,QAyBlD;AAGD,wBAAgB,qBAAqB,SAEpC"}
@@ -0,0 +1,3 @@
1
+ import type { RiskClient } from "../client";
2
+ export declare function setupPageLifecycle(client: RiskClient): void;
3
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/lifecycle/page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,QAMpD"}
@@ -0,0 +1,4 @@
1
+ import type { RiskClient } from "../client";
2
+ export declare function setupScroll(client: RiskClient): void;
3
+ export declare function resetScrollState(): void;
4
+ //# sourceMappingURL=scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../src/lifecycle/scroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAM5C,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,QAqB7C;AAGD,wBAAgB,gBAAgB,SAG/B"}
@@ -0,0 +1,3 @@
1
+ import type { RiskClient } from "../client";
2
+ export declare function setupUnload(client: RiskClient): void;
3
+ //# sourceMappingURL=unload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unload.d.ts","sourceRoot":"","sources":["../../src/lifecycle/unload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,QAQ7C"}
@@ -0,0 +1,3 @@
1
+ import type { RiskClient } from "../client";
2
+ export declare function setupVisibility(client: RiskClient): void;
3
+ //# sourceMappingURL=visibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visibility.d.ts","sourceRoot":"","sources":["../../src/lifecycle/visibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,QAQjD"}
@@ -0,0 +1,375 @@
1
+ // src/signals/behavior.ts
2
+ var mouseMoves = 0;
3
+ var lastMove = 0;
4
+ var deltas = [];
5
+ window.addEventListener("mousemove", () => {
6
+ const now = performance.now();
7
+ if (lastMove)
8
+ deltas.push(now - lastMove);
9
+ lastMove = now;
10
+ mouseMoves++;
11
+ });
12
+ function collectBehavior() {
13
+ if (!deltas.length) {
14
+ return {
15
+ mouse_entropy: null
16
+ };
17
+ }
18
+ const avg = deltas.reduce((a, b) => a + b, 0) / deltas.length;
19
+ return {
20
+ mouse_entropy: Math.min(1, mouseMoves / 50),
21
+ avg_mouse_interval_ms: Math.round(avg)
22
+ };
23
+ }
24
+
25
+ // src/signals/cadence.ts
26
+ var timestamps = [];
27
+ var WINDOW_MS = 60000;
28
+ var STRUCTURAL_EVENTS = new Set([
29
+ "session_start",
30
+ "page_view",
31
+ "page_loaded"
32
+ ]);
33
+ function recordEventTimestamp(eventName) {
34
+ if (STRUCTURAL_EVENTS.has(eventName))
35
+ return;
36
+ const now = Date.now();
37
+ timestamps.push(now);
38
+ while (timestamps.length > 0) {
39
+ const oldest = timestamps[0];
40
+ if (oldest !== undefined && now - oldest > WINDOW_MS) {
41
+ timestamps.shift();
42
+ } else {
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ function collectCadence() {
48
+ if (timestamps.length < 2) {
49
+ return {
50
+ events_per_minute: timestamps.length,
51
+ avg_event_interval_ms: null,
52
+ burst_detected: false
53
+ };
54
+ }
55
+ const intervals = [];
56
+ for (let i = 1;i < timestamps.length; i++) {
57
+ intervals.push(timestamps[i] - timestamps[i - 1]);
58
+ }
59
+ const avg = intervals.reduce((a, b) => a + b, 0) / intervals.length;
60
+ const burst = intervals.some((i) => i < 50);
61
+ return {
62
+ events_per_minute: timestamps.length,
63
+ avg_event_interval_ms: Math.round(avg),
64
+ burst_detected: burst
65
+ };
66
+ }
67
+
68
+ // src/fingerprint/raw.ts
69
+ function collectRawFingerprint() {
70
+ const canvas = document.createElement("canvas");
71
+ const ctx = canvas.getContext("2d");
72
+ let canvas_fp = null;
73
+ if (ctx) {
74
+ ctx.textBaseline = "top";
75
+ ctx.font = "14px Arial";
76
+ ctx.fillText("openrisk-fp", 2, 2);
77
+ canvas_fp = canvas.toDataURL();
78
+ }
79
+ return {
80
+ canvas_fp
81
+ };
82
+ }
83
+
84
+ // src/fingerprint/index.ts
85
+ function collectFingerprint() {
86
+ return {
87
+ mode: "raw",
88
+ version: "fp_raw_v1",
89
+ data: collectRawFingerprint()
90
+ };
91
+ }
92
+
93
+ // src/signals/device.ts
94
+ function collectDevice() {
95
+ const screen = window.screen;
96
+ return {
97
+ platform: navigator.platform || "unknown",
98
+ user_agent: navigator.userAgent || "unknown",
99
+ cpu_cores: navigator.hardwareConcurrency ?? null,
100
+ memory_gb: navigator.deviceMemory ?? null,
101
+ touch_support: "ontouchstart" in window,
102
+ screen: {
103
+ width: screen?.width ?? null,
104
+ height: screen?.height ?? null,
105
+ pixel_ratio: window.devicePixelRatio ?? 1
106
+ },
107
+ fingerprint: collectFingerprint()
108
+ };
109
+ }
110
+
111
+ // src/signals/environment.ts
112
+ function collectEnvironment() {
113
+ return {
114
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
115
+ locale: navigator.language,
116
+ languages: navigator.languages
117
+ };
118
+ }
119
+
120
+ // src/signals/features.ts
121
+ function collectFeatures() {
122
+ return {
123
+ webdriver: navigator.webdriver === true,
124
+ plugins_count: navigator.plugins?.length ?? null,
125
+ languages_count: navigator.languages?.length ?? null,
126
+ permissions_api: "permissions" in navigator,
127
+ hardware_concurrency_present: typeof navigator.hardwareConcurrency === "number"
128
+ };
129
+ }
130
+
131
+ // src/signals/integrity.ts
132
+ function collectIntegrity() {
133
+ return {
134
+ sdk_version: "__SDK_VERSION__",
135
+ schema_version: "1.0",
136
+ timestamp_skew_ms: Math.abs(Date.now() - performance.timeOrigin),
137
+ telemetry_blocked: false
138
+ };
139
+ }
140
+
141
+ // src/signals/multitabs.ts
142
+ var TAB_ID = crypto.randomUUID();
143
+ var channel = new BroadcastChannel("openrisk_tabs");
144
+ var peers = new Set;
145
+ peers.add(TAB_ID);
146
+ channel.onmessage = (event) => {
147
+ const msg = event.data;
148
+ if (!msg || msg.tabId === TAB_ID)
149
+ return;
150
+ if (msg.type === "hello") {
151
+ peers.add(msg.tabId);
152
+ channel.postMessage({
153
+ type: "hello_ack",
154
+ tabId: TAB_ID
155
+ });
156
+ }
157
+ if (msg.type === "hello_ack") {
158
+ peers.add(msg.tabId);
159
+ }
160
+ };
161
+ setTimeout(() => {
162
+ channel.postMessage({
163
+ type: "hello",
164
+ tabId: TAB_ID
165
+ });
166
+ }, 50);
167
+ function collectMultiTabs() {
168
+ return {
169
+ concurrent_tabs_estimate: peers.size,
170
+ multiple_tabs_detected: peers.size > 1
171
+ };
172
+ }
173
+
174
+ // src/signals/navigation.ts
175
+ function collectNavigation() {
176
+ const nav = performance.getEntriesByType("navigation")[0];
177
+ return {
178
+ entry_type: nav?.type ?? "unknown",
179
+ referrer_type: document.referrer ? "external" : "direct",
180
+ path_depth: location.pathname.split("/").filter(Boolean).length
181
+ };
182
+ }
183
+
184
+ // src/signals/session.ts
185
+ var sessionStart = Date.now();
186
+ var eventCount = 0;
187
+ var pageCount = 1;
188
+ var visibilityChanges = 0;
189
+ document.addEventListener("visibilitychange", () => {
190
+ visibilityChanges++;
191
+ });
192
+ function collectSession() {
193
+ eventCount++;
194
+ return {
195
+ session_age_ms: Date.now() - sessionStart,
196
+ event_count: eventCount,
197
+ page_count: pageCount,
198
+ tab_visibility_changes: visibilityChanges
199
+ };
200
+ }
201
+
202
+ // src/signals/timing.ts
203
+ function collectTiming() {
204
+ const nav = performance.getEntriesByType("navigation")[0];
205
+ const pageLoad = nav && nav.domComplete > 0 ? Math.round(nav.domComplete - nav.startTime) : null;
206
+ const domInteractive = nav && nav.domInteractive > 0 ? Math.round(nav.domInteractive - nav.startTime) : null;
207
+ return {
208
+ page_load_ms: pageLoad,
209
+ dom_interactive_ms: domInteractive,
210
+ network_rtt_estimate_ms: navigator.connection?.rtt ?? null
211
+ };
212
+ }
213
+
214
+ // src/payload.ts
215
+ function buildPayload(eventName, extra, signalsOverride) {
216
+ const signals = signalsOverride || {
217
+ environment: collectEnvironment(),
218
+ device: collectDevice(),
219
+ session: collectSession(),
220
+ navigation: collectNavigation(),
221
+ timing: collectTiming(),
222
+ behavior: collectBehavior(),
223
+ cadence: collectCadence(),
224
+ features: collectFeatures(),
225
+ multitabs: collectMultiTabs(),
226
+ integrity: collectIntegrity()
227
+ };
228
+ return {
229
+ event: {
230
+ name: eventName,
231
+ timestamp: new Date().toISOString(),
232
+ event_id: crypto.randomUUID()
233
+ },
234
+ signals,
235
+ extra
236
+ };
237
+ }
238
+
239
+ // src/lifecycle/interaction.ts
240
+ var interacted = false;
241
+ function setupInteraction(client) {
242
+ const markInteraction = (type) => {
243
+ if (interacted)
244
+ return;
245
+ interacted = true;
246
+ client.emit("first_interaction", {
247
+ interaction_type: type
248
+ });
249
+ cleanup();
250
+ };
251
+ const cleanup = () => {
252
+ window.removeEventListener("mousemove", onMouse);
253
+ window.removeEventListener("keydown", onKey);
254
+ window.removeEventListener("touchstart", onTouch);
255
+ };
256
+ const onMouse = () => markInteraction("mouse");
257
+ const onKey = () => markInteraction("keyboard");
258
+ const onTouch = () => markInteraction("touch");
259
+ window.addEventListener("mousemove", onMouse, { passive: true });
260
+ window.addEventListener("keydown", onKey);
261
+ window.addEventListener("touchstart", onTouch, { passive: true });
262
+ }
263
+
264
+ // src/lifecycle/page.ts
265
+ function setupPageLifecycle(client) {
266
+ client.emit("page_view");
267
+ window.addEventListener("load", () => {
268
+ client.emit("page_loaded");
269
+ });
270
+ }
271
+
272
+ // src/lifecycle/scroll.ts
273
+ var maxScroll = 0;
274
+ var thresholds = [25, 50, 75, 100];
275
+ var sent = new Set;
276
+ function setupScroll(client) {
277
+ window.addEventListener("scroll", () => {
278
+ const scrollTop = window.scrollY;
279
+ const height = document.documentElement.scrollHeight - window.innerHeight;
280
+ if (height <= 0)
281
+ return;
282
+ const percent = Math.round(scrollTop / height * 100);
283
+ maxScroll = Math.max(maxScroll, percent);
284
+ for (const t of thresholds) {
285
+ if (percent >= t && !sent.has(t)) {
286
+ sent.add(t);
287
+ client.emit("scroll_depth", { percent: t });
288
+ }
289
+ }
290
+ }, { passive: true });
291
+ }
292
+
293
+ // src/lifecycle/unload.ts
294
+ function setupUnload(client) {
295
+ window.addEventListener("beforeunload", () => {
296
+ client.emit("session_end");
297
+ });
298
+ window.addEventListener("pagehide", () => {
299
+ client.emit("session_end");
300
+ });
301
+ }
302
+
303
+ // src/lifecycle/visibility.ts
304
+ function setupVisibility(client) {
305
+ document.addEventListener("visibilitychange", () => {
306
+ if (document.visibilityState === "hidden") {
307
+ client.emit("page_hidden");
308
+ } else if (document.visibilityState === "visible") {
309
+ client.emit("page_visible");
310
+ }
311
+ });
312
+ }
313
+
314
+ // src/client.ts
315
+ function emitDebug(payload) {
316
+ window.dispatchEvent(new CustomEvent("openrisk:debug", {
317
+ detail: payload
318
+ }));
319
+ }
320
+
321
+ class OpenRiskClient {
322
+ options;
323
+ constructor(options) {
324
+ this.options = options;
325
+ this.emit("session_start");
326
+ const setupPageLifecycleFn = this.options.setupPageLifecycle || setupPageLifecycle;
327
+ const setupVisibilityFn = this.options.setupVisibility || setupVisibility;
328
+ const setupInteractionFn = this.options.setupInteraction || setupInteraction;
329
+ const setupScrollFn = this.options.setupScroll || setupScroll;
330
+ const setupUnloadFn = this.options.setupUnload || setupUnload;
331
+ setupPageLifecycleFn(this);
332
+ setupVisibilityFn(this);
333
+ setupInteractionFn(this);
334
+ setupScrollFn(this);
335
+ setupUnloadFn(this);
336
+ }
337
+ emit(eventName, extra) {
338
+ this.track(eventName, extra);
339
+ }
340
+ track(eventName, extra) {
341
+ recordEventTimestamp(eventName);
342
+ const payload = buildPayload(eventName, extra, this.options.signalsOverride);
343
+ if (this.options.debug) {
344
+ emitDebug({
345
+ event: eventName,
346
+ payload
347
+ });
348
+ }
349
+ this.send(payload);
350
+ }
351
+ send(payload) {
352
+ try {
353
+ const body = JSON.stringify(payload);
354
+ if (navigator.sendBeacon) {
355
+ navigator.sendBeacon(this.options.endpoint, body);
356
+ return;
357
+ }
358
+ fetch(this.options.endpoint, {
359
+ method: "POST",
360
+ headers: {
361
+ "content-type": "application/json"
362
+ },
363
+ body,
364
+ keepalive: true
365
+ }).catch(() => {});
366
+ } catch {}
367
+ }
368
+ }
369
+
370
+ class RiskClient extends OpenRiskClient {
371
+ }
372
+ export {
373
+ RiskClient,
374
+ OpenRiskClient
375
+ };
@@ -0,0 +1,10 @@
1
+ export declare function buildPayload(eventName: string, extra?: Record<string, unknown>, signalsOverride?: Record<string, unknown>): {
2
+ event: {
3
+ name: string;
4
+ timestamp: string;
5
+ event_id: `${string}-${string}-${string}-${string}-${string}`;
6
+ };
7
+ signals: Record<string, unknown>;
8
+ extra: Record<string, unknown> | undefined;
9
+ };
10
+ //# sourceMappingURL=payload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../src/payload.ts"],"names":[],"mappings":"AAWA,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;EA0B1C"}
@@ -0,0 +1,10 @@
1
+ export declare function collectBehavior(): {
2
+ mouse_entropy: null;
3
+ avg_mouse_interval_ms?: undefined;
4
+ } | {
5
+ mouse_entropy: number;
6
+ avg_mouse_interval_ms: number;
7
+ };
8
+ export declare function simulateMouseMove(): void;
9
+ export declare function resetBehaviorState(): void;
10
+ //# sourceMappingURL=behavior.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behavior.d.ts","sourceRoot":"","sources":["../../src/signals/behavior.ts"],"names":[],"mappings":"AAWA,wBAAgB,eAAe;;;;;;EAa9B;AAGD,wBAAgB,iBAAiB,SAKhC;AAGD,wBAAgB,kBAAkB,SAIjC"}
@@ -0,0 +1,12 @@
1
+ export declare function recordEventTimestamp(eventName: string): void;
2
+ export declare function collectCadence(): {
3
+ events_per_minute: number;
4
+ avg_event_interval_ms: null;
5
+ burst_detected: boolean;
6
+ } | {
7
+ events_per_minute: number;
8
+ avg_event_interval_ms: number;
9
+ burst_detected: boolean;
10
+ };
11
+ export declare function resetCadenceTimestamps(): void;
12
+ //# sourceMappingURL=cadence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cadence.d.ts","sourceRoot":"","sources":["../../src/signals/cadence.ts"],"names":[],"mappings":"AASA,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,QAcrD;AAED,wBAAgB,cAAc;;;;;;;;EAuB7B;AAGD,wBAAgB,sBAAsB,SAErC"}
@@ -0,0 +1,20 @@
1
+ export declare function collectDevice(): {
2
+ platform: "MacIntel" | "Win32" | "Linux x86_64";
3
+ user_agent: string;
4
+ cpu_cores: number;
5
+ memory_gb: any;
6
+ touch_support: boolean;
7
+ screen: {
8
+ width: number;
9
+ height: number;
10
+ pixel_ratio: number;
11
+ };
12
+ fingerprint: {
13
+ mode: string;
14
+ version: string;
15
+ data: {
16
+ canvas_fp: string | null;
17
+ };
18
+ };
19
+ };
20
+ //# sourceMappingURL=device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../../src/signals/device.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa;;;;;;;;;;;;;;;;;;EAmB5B"}
@@ -0,0 +1,6 @@
1
+ export declare function collectEnvironment(): {
2
+ timezone: string;
3
+ locale: string;
4
+ languages: readonly string[];
5
+ };
6
+ //# sourceMappingURL=environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/signals/environment.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB;;;;EAMjC"}
@@ -0,0 +1,8 @@
1
+ export declare function collectFeatures(): {
2
+ webdriver: boolean;
3
+ plugins_count: number;
4
+ languages_count: number;
5
+ permissions_api: boolean;
6
+ hardware_concurrency_present: boolean;
7
+ };
8
+ //# sourceMappingURL=features.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../../src/signals/features.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe;;;;;;EAS9B"}
@@ -0,0 +1,7 @@
1
+ export declare function collectIntegrity(): {
2
+ sdk_version: string;
3
+ schema_version: string;
4
+ timestamp_skew_ms: number;
5
+ telemetry_blocked: boolean;
6
+ };
7
+ //# sourceMappingURL=integrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.d.ts","sourceRoot":"","sources":["../../src/signals/integrity.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB;;;;;EAO/B"}
@@ -0,0 +1,5 @@
1
+ export declare function collectMultiTabs(): {
2
+ concurrent_tabs_estimate: number;
3
+ multiple_tabs_detected: boolean;
4
+ };
5
+ //# sourceMappingURL=multitabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multitabs.d.ts","sourceRoot":"","sources":["../../src/signals/multitabs.ts"],"names":[],"mappings":"AA+BA,wBAAgB,gBAAgB;;;EAK/B"}
@@ -0,0 +1,6 @@
1
+ export declare function collectNavigation(): {
2
+ entry_type: string;
3
+ referrer_type: string;
4
+ path_depth: number;
5
+ };
6
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/signals/navigation.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB;;;;EAUhC"}
@@ -0,0 +1,7 @@
1
+ export declare function collectSession(): {
2
+ session_age_ms: number;
3
+ event_count: number;
4
+ page_count: number;
5
+ tab_visibility_changes: number;
6
+ };
7
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/signals/session.ts"],"names":[],"mappings":"AASA,wBAAgB,cAAc;;;;;EAS7B"}
@@ -0,0 +1,6 @@
1
+ export declare function collectTiming(): {
2
+ page_load_ms: number | null;
3
+ dom_interactive_ms: number | null;
4
+ network_rtt_estimate_ms: any;
5
+ };
6
+ //# sourceMappingURL=timing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../../src/signals/timing.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa;;;;EAoB5B"}
@@ -0,0 +1,24 @@
1
+ export type RiskEventName = string;
2
+ export interface RiskEvent {
3
+ name: RiskEventName;
4
+ timestamp: string;
5
+ event_id: string;
6
+ correlation_id?: string;
7
+ }
8
+ export interface RiskPayload {
9
+ event: RiskEvent;
10
+ signals: Record<string, unknown>;
11
+ custom?: Record<string, unknown>;
12
+ }
13
+ export type RiskClientOptions = {
14
+ projectKey: string;
15
+ endpoint: string;
16
+ debug?: boolean;
17
+ setupPageLifecycle?: (client: any) => void;
18
+ setupVisibility?: (client: any) => void;
19
+ setupInteraction?: (client: any) => void;
20
+ setupScroll?: (client: any) => void;
21
+ setupUnload?: (client: any) => void;
22
+ signalsOverride?: Record<string, unknown>;
23
+ };
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IAEjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAGhB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAGpC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@arirocha/openrisk-client",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./browser": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/openrisk.js"
17
+ }
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "sideEffects": false,
26
+ "private": false,
27
+ "devDependencies": {
28
+ "@types/bun": "latest",
29
+ "@playwright/test": "^1.58.0",
30
+ "jsdom": "^27.4.0",
31
+ "typescript": "^5.5.4"
32
+ },
33
+ "scripts": {
34
+ "dev": "bun build src/index.ts --outfile=../dist/openrisk.js --target=browser --format=esm --watch",
35
+ "build:esm": "bun build src/index.ts --outdir=dist --target=browser --format=esm",
36
+ "build:cjs": "bun build src/index.ts --outdir=dist --target=browser --format=cjs",
37
+ "build:browser": "bun build src/index.ts --outfile=dist/openrisk.js --target=browser --format=esm",
38
+ "build:types": "bunx tsc -p tsconfig.json --emitDeclarationOnly",
39
+ "build": "bun run build:esm && bun run build:cjs && bun run build:browser && bun run build:types",
40
+ "test": "bun test",
41
+ "test:e2e": "npm run build && playwright test",
42
+ "test:e2e:ui": "playwright test --ui",
43
+ "test:e2e:headed": "playwright test --headed",
44
+ "test:e2e:debug": "playwright test --debug"
45
+ },
46
+ "peerDependencies": {
47
+ "typescript": "^5"
48
+ }
49
+ }