@jovid1242/appready 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.
package/dist/index.js ADDED
@@ -0,0 +1,415 @@
1
+ // src/scrub.ts
2
+ var SENSITIVE_KEYS = [
3
+ "token",
4
+ "access_token",
5
+ "refresh_token",
6
+ "id_token",
7
+ "apikey",
8
+ "api_key",
9
+ "key",
10
+ "secret",
11
+ "password",
12
+ "passwd",
13
+ "pwd",
14
+ "auth",
15
+ "authorization",
16
+ "session",
17
+ "sid",
18
+ "signature",
19
+ "sig",
20
+ "credential",
21
+ "code"
22
+ ];
23
+ var REDACTED = "[redacted]";
24
+ var PATTERNS = [
25
+ // Email addresses. Common in "user X not found" messages.
26
+ [/\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g, "[email]"],
27
+ // Bearer and similar header values that ended up in a message.
28
+ [/\b(bearer|basic|token)\s+[A-Za-z0-9._~+/-]{12,}=*/gi, "$1 [redacted]"],
29
+ // JWTs, which are three base64url segments and unmistakable.
30
+ [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, "[jwt]"],
31
+ // Known key shapes. Not exhaustive, and not meant to be — the generic
32
+ // high-entropy rule below is the net underneath.
33
+ [/\bsk_(live|test)_[A-Za-z0-9]{8,}\b/g, "[stripe-key]"],
34
+ [/\b(gh[pousr]|github_pat)_[A-Za-z0-9_]{20,}\b/g, "[github-token]"],
35
+ [/\bAKIA[0-9A-Z]{16}\b/g, "[aws-key]"],
36
+ [/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, "[slack-token]"],
37
+ // Card-shaped digit runs, spaced or not.
38
+ [/\b(?:\d[ -]*?){13,19}\b/g, "[redacted-number]"]
39
+ ];
40
+ var ASSIGNED_SECRET = /\b([\w.-]{2,40})\s*[=:]\s*["']?([A-Za-z0-9_\-+/]{24,})["']?/g;
41
+ function scrubText(input, maxLength = 2e3) {
42
+ if (!input) return "";
43
+ let out = String(input).slice(0, maxLength * 2);
44
+ for (const [pattern, replacement] of PATTERNS) {
45
+ out = out.replace(pattern, replacement);
46
+ }
47
+ out = out.replace(
48
+ ASSIGNED_SECRET,
49
+ (whole, name) => SENSITIVE_KEYS.some((key) => name.toLowerCase().includes(key)) ? `${name}=${REDACTED}` : whole
50
+ );
51
+ return out.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").slice(0, maxLength);
52
+ }
53
+ function scrubUrl(raw) {
54
+ try {
55
+ const url = new URL(raw, "http://localhost");
56
+ url.username = "";
57
+ url.password = "";
58
+ url.hash = "";
59
+ for (const key of Array.from(url.searchParams.keys())) {
60
+ const lower = key.toLowerCase();
61
+ if (SENSITIVE_KEYS.some((sensitive) => lower === sensitive || lower.includes(sensitive))) {
62
+ url.searchParams.set(key, REDACTED);
63
+ }
64
+ }
65
+ return url.toString().slice(0, 500);
66
+ } catch {
67
+ return String(raw).split("?")[0].slice(0, 500);
68
+ }
69
+ }
70
+ function scrubStack(stack, maxFrames = 25) {
71
+ if (!stack) return "";
72
+ const lines = String(stack).split("\n").slice(0, maxFrames + 1);
73
+ return scrubText(lines.map((line) => scrubFrame(line)).join("\n"), 4e3);
74
+ }
75
+ var scrubFrame = (line) => line.replace(/https?:\/\/[^\s)]+/g, (url) => scrubUrl(url));
76
+
77
+ // src/context.ts
78
+ function collectContext() {
79
+ const nav = safeNavigator();
80
+ const agent = nav?.userAgent ?? "";
81
+ const { name, version } = parseBrowser(agent);
82
+ return {
83
+ url: scrubUrl(safeLocation()?.href ?? ""),
84
+ route: routeOf(safeLocation()?.pathname ?? "/"),
85
+ browser: name,
86
+ browserVersion: version,
87
+ os: parseOs(agent),
88
+ viewport: viewport()
89
+ };
90
+ }
91
+ function routeOf(pathname) {
92
+ if (!pathname) return "/";
93
+ const collapsed = pathname.split("/").map((segment) => {
94
+ if (!segment) return segment;
95
+ if (/^\d+$/.test(segment)) return ":id";
96
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) {
97
+ return ":uuid";
98
+ }
99
+ if (/^[0-9A-HJKMNP-TV-Z]{26}$/i.test(segment)) return ":id";
100
+ if (segment.length > 24 && !/[.\-_]/.test(segment)) return ":id";
101
+ return segment;
102
+ }).join("/");
103
+ return collapsed.slice(0, 200) || "/";
104
+ }
105
+ var safeNavigator = () => typeof navigator === "undefined" ? void 0 : navigator;
106
+ var safeLocation = () => typeof location === "undefined" ? void 0 : location;
107
+ function viewport() {
108
+ if (typeof window === "undefined") return "";
109
+ const width = window.innerWidth || 0;
110
+ const height = window.innerHeight || 0;
111
+ return width && height ? `${width}x${height}` : "";
112
+ }
113
+ function parseBrowser(agent) {
114
+ const checks = [
115
+ ["Edge", /Edg(?:e|A|iOS)?\/([\d.]+)/],
116
+ ["Opera", /OPR\/([\d.]+)/],
117
+ ["Samsung Internet", /SamsungBrowser\/([\d.]+)/],
118
+ ["Firefox", /(?:Firefox|FxiOS)\/([\d.]+)/],
119
+ ["Chrome", /(?:Chrome|CriOS)\/([\d.]+)/],
120
+ ["Safari", /Version\/([\d.]+).*Safari/]
121
+ ];
122
+ for (const [name, pattern] of checks) {
123
+ const match = pattern.exec(agent);
124
+ if (match) return { name, version: (match[1] ?? "").split(".")[0] ?? "" };
125
+ }
126
+ return { name: "Unknown", version: "" };
127
+ }
128
+ function parseOs(agent) {
129
+ if (/iPhone|iPad|iPod/.test(agent)) return "iOS";
130
+ if (/Android/.test(agent)) return "Android";
131
+ if (/Mac OS X|Macintosh/.test(agent)) return "macOS";
132
+ if (/Windows/.test(agent)) return "Windows";
133
+ if (/CrOS/.test(agent)) return "ChromeOS";
134
+ if (/Linux/.test(agent)) return "Linux";
135
+ return "Unknown";
136
+ }
137
+
138
+ // src/transport.ts
139
+ var Transport = class {
140
+ constructor(options) {
141
+ this.options = options;
142
+ this.queue = [];
143
+ this.timer = null;
144
+ this.dropped = 0;
145
+ /** Set after a 4xx that will not improve — stop talking rather than hammer. */
146
+ this.disabled = false;
147
+ this.installUnloadFlush();
148
+ }
149
+ /** Queues an event. Returns false when it was dropped. */
150
+ enqueue(event, immediate = false) {
151
+ if (this.disabled) return false;
152
+ if (this.queue.length >= this.options.maxBatchSize * 4) {
153
+ this.dropped += 1;
154
+ return false;
155
+ }
156
+ this.queue.push(event);
157
+ if (immediate || this.queue.length >= this.options.maxBatchSize) {
158
+ this.flush();
159
+ return true;
160
+ }
161
+ this.schedule();
162
+ return true;
163
+ }
164
+ schedule() {
165
+ if (this.timer !== null) return;
166
+ this.timer = setTimeout(() => {
167
+ this.timer = null;
168
+ this.flush();
169
+ }, this.options.flushIntervalMs);
170
+ this.timer.unref?.();
171
+ }
172
+ flush() {
173
+ if (this.timer !== null) {
174
+ clearTimeout(this.timer);
175
+ this.timer = null;
176
+ }
177
+ if (this.queue.length === 0 || this.disabled) return;
178
+ const batch = this.queue.splice(0, this.options.maxBatchSize);
179
+ const dropped = this.dropped;
180
+ this.dropped = 0;
181
+ void this.send({
182
+ projectKey: this.options.projectKey,
183
+ events: batch,
184
+ ...dropped > 0 ? { dropped } : {}
185
+ });
186
+ }
187
+ async send(body) {
188
+ try {
189
+ if (typeof fetch !== "function") return;
190
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
191
+ const timer = controller ? setTimeout(() => controller.abort(), this.options.timeoutMs) : null;
192
+ const response = await fetch(this.options.endpoint, {
193
+ method: "POST",
194
+ // `text/plain` avoids a CORS preflight on every batch. The endpoint
195
+ // parses the body itself; it never trusts the content type.
196
+ headers: { "content-type": "text/plain;charset=UTF-8" },
197
+ body: JSON.stringify(body),
198
+ // Survives the page being closed mid-request, which is exactly when the
199
+ // interesting errors happen.
200
+ keepalive: true,
201
+ mode: "cors",
202
+ // No cookies, ever. The endpoint answers `Allow-Origin: *`, and sending
203
+ // credentials to a wildcard origin is both refused and wrong.
204
+ credentials: "omit",
205
+ ...controller ? { signal: controller.signal } : {}
206
+ });
207
+ if (timer) clearTimeout(timer);
208
+ if (response.status === 401 || response.status === 403 || response.status === 404) {
209
+ this.disabled = true;
210
+ }
211
+ } catch {
212
+ }
213
+ }
214
+ /**
215
+ * A last flush when the page goes away.
216
+ *
217
+ * `visibilitychange` rather than `unload`: mobile browsers frequently never
218
+ * fire `unload`, and `pagehide` is not reliable on iOS either.
219
+ */
220
+ installUnloadFlush() {
221
+ try {
222
+ if (typeof document === "undefined" || typeof addEventListener !== "function") return;
223
+ addEventListener(
224
+ "visibilitychange",
225
+ () => {
226
+ if (document.visibilityState === "hidden") this.flush();
227
+ },
228
+ { capture: true }
229
+ );
230
+ addEventListener("pagehide", () => this.flush(), { capture: true });
231
+ } catch {
232
+ }
233
+ }
234
+ };
235
+
236
+ // src/client.ts
237
+ var SDK_VERSION = "0.1.0";
238
+ var DEFAULT_ENDPOINT = "https://appready.tech/api/runtime/v1/events";
239
+ var DEDUPE_WINDOW_MS = 5e3;
240
+ var MAX_EVENTS_PER_MINUTE = 60;
241
+ var AppReadyClient = class {
242
+ constructor() {
243
+ this.transport = null;
244
+ this.options = {
245
+ projectKey: "",
246
+ environment: "production",
247
+ enabled: true
248
+ };
249
+ this.started = false;
250
+ this.seen = /* @__PURE__ */ new Map();
251
+ this.minuteBucket = { startedAt: 0, count: 0 };
252
+ }
253
+ init(options) {
254
+ try {
255
+ if (this.started) return;
256
+ if (!options?.projectKey) return;
257
+ if (options.enabled === false) return;
258
+ if (typeof window === "undefined") return;
259
+ this.options = { environment: "production", enabled: true, ...options };
260
+ this.transport = new Transport({
261
+ endpoint: options.endpoint ?? DEFAULT_ENDPOINT,
262
+ projectKey: options.projectKey,
263
+ flushIntervalMs: 3e3,
264
+ maxBatchSize: 20,
265
+ timeoutMs: 8e3
266
+ });
267
+ this.installHandlers();
268
+ this.started = true;
269
+ this.capture("integration_check", "AppReadyIntegration", "SDK initialised", "", true);
270
+ } catch {
271
+ }
272
+ }
273
+ captureException(error) {
274
+ try {
275
+ const { name, message, stack } = describe(error);
276
+ this.capture("error", name, message, stack, true);
277
+ } catch {
278
+ }
279
+ }
280
+ captureMessage(message) {
281
+ try {
282
+ this.capture("message", "Message", String(message), "");
283
+ } catch {
284
+ }
285
+ }
286
+ /**
287
+ * Associates events with one of the customer's users.
288
+ *
289
+ * Optional, and takes an opaque id only. Anything that looks like an email is
290
+ * refused rather than scrubbed later — the SDK should not be the reason a
291
+ * customer's user list ends up on our servers.
292
+ */
293
+ setUser(user) {
294
+ try {
295
+ if (!user?.id) {
296
+ this.userId = void 0;
297
+ return;
298
+ }
299
+ const id = String(user.id).slice(0, 64);
300
+ this.userId = /@|\s/.test(id) ? void 0 : id;
301
+ } catch {
302
+ }
303
+ }
304
+ /** Sends anything queued. Useful right before a deliberate navigation. */
305
+ flush() {
306
+ try {
307
+ this.transport?.flush();
308
+ } catch {
309
+ }
310
+ }
311
+ capture(kind, errorType, message, stack, immediate = false) {
312
+ if (!this.started && kind !== "integration_check") return;
313
+ if (!this.transport) return;
314
+ const cleanMessage = scrubText(message, 500);
315
+ const cleanStack = scrubStack(stack);
316
+ const key = `${kind}|${errorType}|${cleanMessage}`;
317
+ const now = Date.now();
318
+ const previous = this.seen.get(key);
319
+ if (previous && now - previous.firstAt < DEDUPE_WINDOW_MS) {
320
+ previous.count += 1;
321
+ return;
322
+ }
323
+ const carriedCount = previous && previous.sent ? previous.count : 1;
324
+ this.seen.set(key, { firstAt: now, count: 1, sent: true });
325
+ this.pruneSeen(now);
326
+ if (!this.withinRateLimit(now)) return;
327
+ const context = collectContext();
328
+ const event = {
329
+ kind,
330
+ timestamp: new Date(now).toISOString(),
331
+ errorType: scrubText(errorType, 120) || "Error",
332
+ message: cleanMessage,
333
+ stack: cleanStack,
334
+ sdkVersion: SDK_VERSION,
335
+ environment: this.options.environment ?? "production",
336
+ ...this.options.release ? { release: String(this.options.release).slice(0, 80) } : {},
337
+ ...this.userId ? { userId: this.userId } : {},
338
+ ...carriedCount > 1 ? { count: carriedCount } : {},
339
+ ...context
340
+ };
341
+ const final = this.options.beforeSend ? safeBeforeSend(this.options.beforeSend, event) : event;
342
+ if (!final) return;
343
+ this.transport.enqueue(final, immediate);
344
+ }
345
+ /** A hard ceiling, independent of dedupe — different errors also loop. */
346
+ withinRateLimit(now) {
347
+ if (now - this.minuteBucket.startedAt > 6e4) {
348
+ this.minuteBucket = { startedAt: now, count: 0 };
349
+ }
350
+ if (this.minuteBucket.count >= MAX_EVENTS_PER_MINUTE) return false;
351
+ this.minuteBucket.count += 1;
352
+ return true;
353
+ }
354
+ /** The dedupe map is unbounded otherwise, and this runs in someone's tab. */
355
+ pruneSeen(now) {
356
+ if (this.seen.size < 200) return;
357
+ for (const [key, entry] of this.seen) {
358
+ if (now - entry.firstAt > DEDUPE_WINDOW_MS * 4) this.seen.delete(key);
359
+ }
360
+ if (this.seen.size >= 500) this.seen.clear();
361
+ }
362
+ installHandlers() {
363
+ try {
364
+ addEventListener(
365
+ "error",
366
+ (event) => {
367
+ if (!event.error && event.target && event.target !== window) return;
368
+ const { name, message, stack } = describe(event.error ?? event.message);
369
+ this.capture("error", name, message, stack, true);
370
+ },
371
+ // Capture phase, so an app's own handler cannot swallow it first.
372
+ true
373
+ );
374
+ addEventListener("unhandledrejection", (event) => {
375
+ const { name, message, stack } = describe(event.reason);
376
+ this.capture("unhandled_rejection", name, message, stack, true);
377
+ });
378
+ } catch {
379
+ }
380
+ }
381
+ };
382
+ function describe(value) {
383
+ if (value instanceof Error) {
384
+ return { name: value.name || "Error", message: value.message || "", stack: value.stack ?? "" };
385
+ }
386
+ if (typeof value === "string") return { name: "Error", message: value, stack: "" };
387
+ if (value && typeof value === "object") {
388
+ const record = value;
389
+ return {
390
+ name: typeof record.name === "string" ? record.name : "Error",
391
+ message: typeof record.message === "string" ? record.message : safeStringify(value),
392
+ stack: typeof record.stack === "string" ? record.stack : ""
393
+ };
394
+ }
395
+ return { name: "Error", message: String(value), stack: "" };
396
+ }
397
+ function safeStringify(value) {
398
+ try {
399
+ return JSON.stringify(value)?.slice(0, 500) ?? String(value);
400
+ } catch {
401
+ return "[unserializable]";
402
+ }
403
+ }
404
+ function safeBeforeSend(hook, event) {
405
+ try {
406
+ return hook(event);
407
+ } catch {
408
+ return event;
409
+ }
410
+ }
411
+ var AppReady = new AppReadyClient();
412
+ export {
413
+ AppReady,
414
+ SDK_VERSION
415
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Removing things that must never leave the customer's browser.
3
+ *
4
+ * This runs in the SDK, and the backend runs its own pass over everything it
5
+ * receives. That duplication is deliberate: anyone can POST to the ingestion
6
+ * endpoint directly, so client-side scrubbing is a courtesy to the customer's
7
+ * users, never a security control. Neither layer is allowed to assume the other
8
+ * ran.
9
+ *
10
+ * The bias is towards over-removal. A redacted token in a stack trace costs
11
+ * someone a little context; a real one costs them their account.
12
+ */
13
+ export declare function scrubText(input: string, maxLength?: number): string;
14
+ /**
15
+ * Keeps a URL useful for grouping while removing anything credential-shaped.
16
+ * The origin and path identify the page; the query rarely does and often carries
17
+ * a session token.
18
+ */
19
+ export declare function scrubUrl(raw: string): string;
20
+ /**
21
+ * A stack trace, scrubbed and bounded.
22
+ *
23
+ * Only the top frames matter for grouping and for a person reading it, and an
24
+ * unbounded stack is how one event becomes a hundred kilobytes.
25
+ */
26
+ export declare function scrubStack(stack: string | undefined, maxFrames?: number): string;
27
+ //# sourceMappingURL=scrub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrub.d.ts","sourceRoot":"","sources":["../src/scrub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAqDH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,MAAM,CAkBjE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAiB5C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,SAAK,GAAG,MAAM,CAM5E"}
@@ -0,0 +1,45 @@
1
+ import type { RuntimeEventPayload } from './types';
2
+ export interface TransportOptions {
3
+ endpoint: string;
4
+ projectKey: string;
5
+ /** Events are held this long before a batch is sent. */
6
+ flushIntervalMs: number;
7
+ maxBatchSize: number;
8
+ timeoutMs: number;
9
+ }
10
+ /**
11
+ * Getting events out of the browser without getting in the way.
12
+ *
13
+ * Three rules, in priority order:
14
+ *
15
+ * 1. **Never break the host app.** Every path is wrapped, nothing rejects, and
16
+ * a failed send is dropped rather than retried forever. A monitoring SDK
17
+ * that takes down the app it monitors is worse than no monitoring.
18
+ * 2. **Never block.** Sends are fire-and-forget with `keepalive`, so a
19
+ * navigation mid-flush does not cancel them and does not delay the page.
20
+ * 3. **Never amplify.** A page in an error loop can produce thousands of events
21
+ * a second; the queue is bounded and the excess is dropped, counted, and
22
+ * reported once rather than sent.
23
+ */
24
+ export declare class Transport {
25
+ private readonly options;
26
+ private queue;
27
+ private timer;
28
+ private dropped;
29
+ /** Set after a 4xx that will not improve — stop talking rather than hammer. */
30
+ private disabled;
31
+ constructor(options: TransportOptions);
32
+ /** Queues an event. Returns false when it was dropped. */
33
+ enqueue(event: RuntimeEventPayload, immediate?: boolean): boolean;
34
+ private schedule;
35
+ flush(): void;
36
+ private send;
37
+ /**
38
+ * A last flush when the page goes away.
39
+ *
40
+ * `visibilitychange` rather than `unload`: mobile browsers frequently never
41
+ * fire `unload`, and `pagehide` is not reliable on iOS either.
42
+ */
43
+ private installUnloadFlush;
44
+ }
45
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAS;IAOR,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAK;IACpB,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAS;gBAEI,OAAO,EAAE,gBAAgB;IAItD,0DAA0D;IAC1D,OAAO,CAAC,KAAK,EAAE,mBAAmB,EAAE,SAAS,UAAQ,GAAG,OAAO;IAqB/D,OAAO,CAAC,QAAQ;IAUhB,KAAK,IAAI,IAAI;YAkBC,IAAI;IAsClB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;CAe3B"}
@@ -0,0 +1,46 @@
1
+ /** The SDK's public options. Everything except `projectKey` has a sane default. */
2
+ export interface AppReadyOptions {
3
+ /**
4
+ * The project's public key, `pub_...`.
5
+ *
6
+ * Public on purpose: it identifies a project so events can be attributed, and
7
+ * grants nothing else. It appears in the customer's HTML, and that is fine.
8
+ */
9
+ projectKey: string;
10
+ /** Where events go. Overridden only for self-hosting or local development. */
11
+ endpoint?: string;
12
+ /** Free-form label — `production`, `staging`. Defaults to `production`. */
13
+ environment?: string;
14
+ /** Version or commit of the customer's app, if they have one to give. */
15
+ release?: string;
16
+ /** Off by default in local development, where errors are expected. */
17
+ enabled?: boolean;
18
+ /**
19
+ * Last word on whether an event is sent. Return null to drop it, or a modified
20
+ * event to redact something only the customer knows is sensitive.
21
+ */
22
+ beforeSend?: (event: RuntimeEventPayload) => RuntimeEventPayload | null;
23
+ }
24
+ export type RuntimeEventKind = 'error' | 'unhandled_rejection' | 'message' | 'integration_check';
25
+ export interface RuntimeEventPayload {
26
+ kind: RuntimeEventKind;
27
+ /** ISO 8601, from the browser's clock. The server records its own too. */
28
+ timestamp: string;
29
+ errorType: string;
30
+ message: string;
31
+ stack: string;
32
+ url: string;
33
+ route: string;
34
+ browser: string;
35
+ browserVersion: string;
36
+ os: string;
37
+ viewport: string;
38
+ environment: string;
39
+ release?: string;
40
+ sdkVersion: string;
41
+ /** Only ever an opaque id the customer chose to set. Never a name or email. */
42
+ userId?: string;
43
+ /** How many identical events this one stands for, when a loop was collapsed. */
44
+ count?: number;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,mBAAmB,GAAG,IAAI,CAAC;CACzE;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,qBAAqB,GAAG,SAAS,GAAG,mBAAmB,CAAC;AAEjG,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@jovid1242/appready",
3
+ "version": "0.1.0",
4
+ "description": "Runtime error monitoring for apps built with AI. Two lines to install, plain-English explanations, and a fix prompt for the tool you built with.",
5
+ "license": "MIT",
6
+ "homepage": "https://appready.tech",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/jovid1242/appready.git",
10
+ "directory": "packages/browser-sdk"
11
+ },
12
+ "keywords": [
13
+ "error-monitoring",
14
+ "runtime-errors",
15
+ "browser",
16
+ "appready",
17
+ "observability"
18
+ ],
19
+ "sideEffects": false,
20
+ "type": "module",
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "require": "./dist/index.cjs"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md"
34
+ ],
35
+ "scripts": {
36
+ "build": "node build.mjs && node publish-cdn.mjs",
37
+ "typecheck": "tsc -p tsconfig.json --noEmit",
38
+ "test": "vitest run",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "devDependencies": {
42
+ "esbuild": "^0.28.2",
43
+ "happy-dom": "^20.14.0",
44
+ "typescript": "^5.9.0",
45
+ "vitest": "^3.2.7"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "engines": {
51
+ "node": ">=18"
52
+ }
53
+ }