@flareapp/js 2.2.1 → 2.4.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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # @flareapp/js
2
2
 
3
- The core JavaScript/TypeScript client for [Flare](https://flareapp.io) error tracking. Captures frontend errors, parses
4
- stack traces, collects browser context, and reports everything to the Flare backend.
3
+ The core JavaScript/TypeScript client for [Flare](https://flareapp.io) error tracking and logging. Captures frontend
4
+ errors, parses stack traces, collects browser context, sends structured logs, and reports everything to the Flare
5
+ backend.
5
6
 
6
7
  ## Installation
7
8
 
@@ -20,6 +21,20 @@ flare.light('YOUR_FLARE_API_KEY');
20
21
  That is all you need. The client automatically listens for uncaught exceptions and unhandled promise rejections,
21
22
  collects browser context, and sends error reports to Flare.
22
23
 
24
+ ## Logging
25
+
26
+ Beyond errors, the client can send structured logs. Logs are opt-in: enable them with `enableLogs`, then call any of
27
+ the eight syslog levels (`debug`, `info`, `notice`, `warning`, `error`, `critical`, `alert`, `emergency`).
28
+
29
+ ```js
30
+ flare.configure({ enableLogs: true });
31
+
32
+ flare.logger.info('Checkout started', { cartId: cart.id, total: cart.total });
33
+ ```
34
+
35
+ Logs are buffered and batched, and flushed when the tab is hidden so buffered logs survive a page unload. The optional
36
+ second argument is structured, searchable attributes.
37
+
23
38
  ## Documentation
24
39
 
25
40
  Full documentation on configuration, hooks, context, breadcrumbs, solution providers, and more is available
@@ -0,0 +1,43 @@
1
+ import { ContextCollector, FileReader, FlushFn, FlushScheduler } from "@flareapp/core";
2
+
3
+ //#region src/browser/catchWindowErrors.d.ts
4
+ declare function catchWindowErrors(): void;
5
+ //#endregion
6
+ //#region src/browser/context/collectBrowser.d.ts
7
+ declare const collectBrowser: ContextCollector;
8
+ //#endregion
9
+ //#region src/browser/FetchFileReader.d.ts
10
+ /**
11
+ * Browser `FileReader` implementation that fetches source files over HTTP(S).
12
+ *
13
+ * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
14
+ * the original source for each frame and render a code snippet around the
15
+ * offending line. The source URL comes from the frame (usually the URL of
16
+ * the JS bundle that produced the error, post-sourcemap resolution).
17
+ *
18
+ * Three safety gates:
19
+ *
20
+ * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
21
+ * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
22
+ * `null` immediately. This avoids surprise privilege boundaries (e.g.,
23
+ * pages should not read extension-internal files) and dodges CORS/CSP
24
+ * walls that would error noisily.
25
+ * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
26
+ * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
27
+ * snippet is simply omitted from the report.
28
+ * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
29
+ * return `null`. We never let a source-fetch failure leak as an error
30
+ * that the consumer page would see.
31
+ *
32
+ * The `read()` contract returns `null` on any failure path, never throws.
33
+ */
34
+ declare class FetchFileReader implements FileReader {
35
+ read(url: string): Promise<string | null>;
36
+ }
37
+ //#endregion
38
+ //#region src/browser/BrowserFlushScheduler.d.ts
39
+ declare class BrowserFlushScheduler implements FlushScheduler {
40
+ register(flush: FlushFn): void;
41
+ }
42
+ //#endregion
43
+ export { catchWindowErrors as i, FetchFileReader as n, collectBrowser as r, BrowserFlushScheduler as t };
@@ -0,0 +1,43 @@
1
+ import { ContextCollector, FileReader, FlushFn, FlushScheduler } from "@flareapp/core";
2
+
3
+ //#region src/browser/catchWindowErrors.d.ts
4
+ declare function catchWindowErrors(): void;
5
+ //#endregion
6
+ //#region src/browser/context/collectBrowser.d.ts
7
+ declare const collectBrowser: ContextCollector;
8
+ //#endregion
9
+ //#region src/browser/FetchFileReader.d.ts
10
+ /**
11
+ * Browser `FileReader` implementation that fetches source files over HTTP(S).
12
+ *
13
+ * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
14
+ * the original source for each frame and render a code snippet around the
15
+ * offending line. The source URL comes from the frame (usually the URL of
16
+ * the JS bundle that produced the error, post-sourcemap resolution).
17
+ *
18
+ * Three safety gates:
19
+ *
20
+ * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
21
+ * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
22
+ * `null` immediately. This avoids surprise privilege boundaries (e.g.,
23
+ * pages should not read extension-internal files) and dodges CORS/CSP
24
+ * walls that would error noisily.
25
+ * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
26
+ * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
27
+ * snippet is simply omitted from the report.
28
+ * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
29
+ * return `null`. We never let a source-fetch failure leak as an error
30
+ * that the consumer page would see.
31
+ *
32
+ * The `read()` contract returns `null` on any failure path, never throws.
33
+ */
34
+ declare class FetchFileReader implements FileReader {
35
+ read(url: string): Promise<string | null>;
36
+ }
37
+ //#endregion
38
+ //#region src/browser/BrowserFlushScheduler.d.ts
39
+ declare class BrowserFlushScheduler implements FlushScheduler {
40
+ register(flush: FlushFn): void;
41
+ }
42
+ //#endregion
43
+ export { catchWindowErrors as i, FetchFileReader as n, collectBrowser as r, BrowserFlushScheduler as t };
@@ -0,0 +1,21 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_catchWindowErrors = require('./catchWindowErrors-CTX8goVc.cjs');
3
+ let _flareapp_core = require("@flareapp/core");
4
+
5
+ //#region src/browser.ts
6
+ var Flare = class extends _flareapp_core.Flare {
7
+ constructor(api = new _flareapp_core.Api(), contextCollector = require_catchWindowErrors.collectBrowser, fileReader = new require_catchWindowErrors.FetchFileReader(), scopeProvider = new _flareapp_core.GlobalScopeProvider()) {
8
+ super(api, contextCollector, fileReader, scopeProvider, new require_catchWindowErrors.BrowserFlushScheduler());
9
+ this.setSdkInfo({
10
+ name: "@flareapp/js",
11
+ version: require_catchWindowErrors.CLIENT_VERSION
12
+ });
13
+ }
14
+ };
15
+
16
+ //#endregion
17
+ exports.BrowserFlushScheduler = require_catchWindowErrors.BrowserFlushScheduler;
18
+ exports.FetchFileReader = require_catchWindowErrors.FetchFileReader;
19
+ exports.Flare = Flare;
20
+ exports.catchWindowErrors = require_catchWindowErrors.catchWindowErrors;
21
+ exports.collectBrowser = require_catchWindowErrors.collectBrowser;
@@ -0,0 +1,9 @@
1
+ import { i as catchWindowErrors, n as FetchFileReader, r as collectBrowser, t as BrowserFlushScheduler } from "./BrowserFlushScheduler-CR7L8-z6.cjs";
2
+ import { Api, ContextCollector, FileReader, Flare as Flare$1, ScopeProvider } from "@flareapp/core";
3
+
4
+ //#region src/browser.d.ts
5
+ declare class Flare extends Flare$1 {
6
+ constructor(api?: Api, contextCollector?: ContextCollector, fileReader?: FileReader, scopeProvider?: ScopeProvider);
7
+ }
8
+ //#endregion
9
+ export { BrowserFlushScheduler, FetchFileReader, Flare, catchWindowErrors, collectBrowser };
@@ -0,0 +1,9 @@
1
+ import { i as catchWindowErrors, n as FetchFileReader, r as collectBrowser, t as BrowserFlushScheduler } from "./BrowserFlushScheduler-CZiDvxtf.mjs";
2
+ import { Api, ContextCollector, FileReader, Flare as Flare$1, ScopeProvider } from "@flareapp/core";
3
+
4
+ //#region src/browser.d.ts
5
+ declare class Flare extends Flare$1 {
6
+ constructor(api?: Api, contextCollector?: ContextCollector, fileReader?: FileReader, scopeProvider?: ScopeProvider);
7
+ }
8
+ //#endregion
9
+ export { BrowserFlushScheduler, FetchFileReader, Flare, catchWindowErrors, collectBrowser };
@@ -0,0 +1,16 @@
1
+ import { a as BrowserFlushScheduler, i as collectBrowser, n as CLIENT_VERSION, r as FetchFileReader, t as catchWindowErrors } from "./catchWindowErrors-DKjw0DIA.mjs";
2
+ import { Api, Flare as Flare$1, GlobalScopeProvider } from "@flareapp/core";
3
+
4
+ //#region src/browser.ts
5
+ var Flare = class extends Flare$1 {
6
+ constructor(api = new Api(), contextCollector = collectBrowser, fileReader = new FetchFileReader(), scopeProvider = new GlobalScopeProvider()) {
7
+ super(api, contextCollector, fileReader, scopeProvider, new BrowserFlushScheduler());
8
+ this.setSdkInfo({
9
+ name: "@flareapp/js",
10
+ version: CLIENT_VERSION
11
+ });
12
+ }
13
+ };
14
+
15
+ //#endregion
16
+ export { BrowserFlushScheduler, FetchFileReader, Flare, catchWindowErrors, collectBrowser };
@@ -0,0 +1,180 @@
1
+ let _flareapp_core = require("@flareapp/core");
2
+
3
+ //#region src/browser/BrowserFlushScheduler.ts
4
+ var BrowserFlushScheduler = class {
5
+ register(flush) {
6
+ if (typeof document === "undefined" || !document) return;
7
+ document.addEventListener("visibilitychange", () => {
8
+ if (document.visibilityState === "hidden") flush({ keepalive: true });
9
+ });
10
+ }
11
+ };
12
+
13
+ //#endregion
14
+ //#region src/browser/context/cookie.ts
15
+ function cookie() {
16
+ if (!window.document.cookie) return {};
17
+ const cookies = {};
18
+ window.document.cookie.split("; ").forEach((rawCookie) => {
19
+ const idx = rawCookie.indexOf("=");
20
+ if (idx === -1) {
21
+ cookies[rawCookie] = "";
22
+ return;
23
+ }
24
+ const name = rawCookie.slice(0, idx);
25
+ cookies[name] = rawCookie.slice(idx + 1);
26
+ });
27
+ return { "http.request.cookies": cookies };
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/browser/context/request.ts
32
+ function request(urlDenylist) {
33
+ return {
34
+ "url.full": (0, _flareapp_core.redactUrlQuery)(window.location.href, urlDenylist),
35
+ "user_agent.original": window.navigator.userAgent,
36
+ "http.request.referrer": (0, _flareapp_core.redactUrlQuery)(window.document.referrer, urlDenylist),
37
+ "document.ready_state": window.document.readyState
38
+ };
39
+ }
40
+
41
+ //#endregion
42
+ //#region src/browser/context/requestData.ts
43
+ function requestData(urlDenylist) {
44
+ if (!window.location.search) return {};
45
+ return { "url.query": (0, _flareapp_core.redactUrlQuery)(window.location.search, urlDenylist).replace(/^\?/, "") };
46
+ }
47
+
48
+ //#endregion
49
+ //#region src/browser/context/collectBrowser.ts
50
+ const collectBrowser = (config) => {
51
+ if (typeof window === "undefined") return { "flare.entry_point.type": "server" };
52
+ const attrs = { "flare.entry_point.type": "web" };
53
+ if (window?.location?.href) {
54
+ attrs["flare.entry_point.value"] = (0, _flareapp_core.redactUrlQuery)(window.location.href, config.urlDenylist);
55
+ if (window.location.pathname) {
56
+ attrs["flare.entry_point.handler.identifier"] = window.location.pathname;
57
+ attrs["flare.entry_point.handler.type"] = "browser";
58
+ }
59
+ }
60
+ if (window?.location?.hostname) attrs["host.name"] = window.location.hostname;
61
+ Object.assign(attrs, request(config.urlDenylist));
62
+ Object.assign(attrs, requestData(config.urlDenylist));
63
+ Object.assign(attrs, cookie());
64
+ return attrs;
65
+ };
66
+
67
+ //#endregion
68
+ //#region src/browser/FetchFileReader.ts
69
+ /**
70
+ * Browser `FileReader` implementation that fetches source files over HTTP(S).
71
+ *
72
+ * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
73
+ * the original source for each frame and render a code snippet around the
74
+ * offending line. The source URL comes from the frame (usually the URL of
75
+ * the JS bundle that produced the error, post-sourcemap resolution).
76
+ *
77
+ * Three safety gates:
78
+ *
79
+ * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
80
+ * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
81
+ * `null` immediately. This avoids surprise privilege boundaries (e.g.,
82
+ * pages should not read extension-internal files) and dodges CORS/CSP
83
+ * walls that would error noisily.
84
+ * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
85
+ * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
86
+ * snippet is simply omitted from the report.
87
+ * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
88
+ * return `null`. We never let a source-fetch failure leak as an error
89
+ * that the consumer page would see.
90
+ *
91
+ * The `read()` contract returns `null` on any failure path, never throws.
92
+ */
93
+ var FetchFileReader = class {
94
+ read(url) {
95
+ if (!/^https?:\/\//i.test(url)) return Promise.resolve(null);
96
+ return fetch(url).then((response) => {
97
+ if (response.status !== 200) return null;
98
+ return response.text();
99
+ }).catch(() => null);
100
+ }
101
+ };
102
+
103
+ //#endregion
104
+ //#region src/env/index.ts
105
+ const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.4.0" : "?";
106
+
107
+ //#endregion
108
+ //#region src/browser/catchWindowErrors.ts
109
+ function catchWindowErrors() {
110
+ if (typeof window === "undefined") return;
111
+ window.addEventListener("error", (event) => {
112
+ const flare = window.flare;
113
+ if (!flare) return;
114
+ if (event.error instanceof Error) flare.reportSilently(event.error);
115
+ });
116
+ window.addEventListener("unhandledrejection", (event) => {
117
+ const flare = window.flare;
118
+ if (!flare) return;
119
+ const reason = event.reason;
120
+ if (reason instanceof Error) {
121
+ flare.reportSilently(reason);
122
+ return;
123
+ }
124
+ if (hasStack(reason)) {
125
+ const error = new Error(describeRejectionReason(reason));
126
+ error.stack = reason.stack;
127
+ flare.reportSilently(error);
128
+ return;
129
+ }
130
+ Promise.resolve(flare.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
131
+ });
132
+ }
133
+ function describeRejectionReason(reason) {
134
+ if (typeof reason === "string") return reason;
135
+ if (reason && typeof reason === "object") {
136
+ const message = reason.message;
137
+ if (typeof message === "string") return message;
138
+ try {
139
+ return JSON.stringify(reason);
140
+ } catch {
141
+ return "Unhandled promise rejection (non-serializable reason)";
142
+ }
143
+ }
144
+ return String(reason);
145
+ }
146
+ function hasStack(value) {
147
+ return typeof value === "object" && value !== null && "stack" in value && typeof value.stack === "string";
148
+ }
149
+
150
+ //#endregion
151
+ Object.defineProperty(exports, 'BrowserFlushScheduler', {
152
+ enumerable: true,
153
+ get: function () {
154
+ return BrowserFlushScheduler;
155
+ }
156
+ });
157
+ Object.defineProperty(exports, 'CLIENT_VERSION', {
158
+ enumerable: true,
159
+ get: function () {
160
+ return CLIENT_VERSION;
161
+ }
162
+ });
163
+ Object.defineProperty(exports, 'FetchFileReader', {
164
+ enumerable: true,
165
+ get: function () {
166
+ return FetchFileReader;
167
+ }
168
+ });
169
+ Object.defineProperty(exports, 'catchWindowErrors', {
170
+ enumerable: true,
171
+ get: function () {
172
+ return catchWindowErrors;
173
+ }
174
+ });
175
+ Object.defineProperty(exports, 'collectBrowser', {
176
+ enumerable: true,
177
+ get: function () {
178
+ return collectBrowser;
179
+ }
180
+ });
@@ -0,0 +1,151 @@
1
+ import { redactUrlQuery } from "@flareapp/core";
2
+
3
+ //#region src/browser/BrowserFlushScheduler.ts
4
+ var BrowserFlushScheduler = class {
5
+ register(flush) {
6
+ if (typeof document === "undefined" || !document) return;
7
+ document.addEventListener("visibilitychange", () => {
8
+ if (document.visibilityState === "hidden") flush({ keepalive: true });
9
+ });
10
+ }
11
+ };
12
+
13
+ //#endregion
14
+ //#region src/browser/context/cookie.ts
15
+ function cookie() {
16
+ if (!window.document.cookie) return {};
17
+ const cookies = {};
18
+ window.document.cookie.split("; ").forEach((rawCookie) => {
19
+ const idx = rawCookie.indexOf("=");
20
+ if (idx === -1) {
21
+ cookies[rawCookie] = "";
22
+ return;
23
+ }
24
+ const name = rawCookie.slice(0, idx);
25
+ cookies[name] = rawCookie.slice(idx + 1);
26
+ });
27
+ return { "http.request.cookies": cookies };
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/browser/context/request.ts
32
+ function request(urlDenylist) {
33
+ return {
34
+ "url.full": redactUrlQuery(window.location.href, urlDenylist),
35
+ "user_agent.original": window.navigator.userAgent,
36
+ "http.request.referrer": redactUrlQuery(window.document.referrer, urlDenylist),
37
+ "document.ready_state": window.document.readyState
38
+ };
39
+ }
40
+
41
+ //#endregion
42
+ //#region src/browser/context/requestData.ts
43
+ function requestData(urlDenylist) {
44
+ if (!window.location.search) return {};
45
+ return { "url.query": redactUrlQuery(window.location.search, urlDenylist).replace(/^\?/, "") };
46
+ }
47
+
48
+ //#endregion
49
+ //#region src/browser/context/collectBrowser.ts
50
+ const collectBrowser = (config) => {
51
+ if (typeof window === "undefined") return { "flare.entry_point.type": "server" };
52
+ const attrs = { "flare.entry_point.type": "web" };
53
+ if (window?.location?.href) {
54
+ attrs["flare.entry_point.value"] = redactUrlQuery(window.location.href, config.urlDenylist);
55
+ if (window.location.pathname) {
56
+ attrs["flare.entry_point.handler.identifier"] = window.location.pathname;
57
+ attrs["flare.entry_point.handler.type"] = "browser";
58
+ }
59
+ }
60
+ if (window?.location?.hostname) attrs["host.name"] = window.location.hostname;
61
+ Object.assign(attrs, request(config.urlDenylist));
62
+ Object.assign(attrs, requestData(config.urlDenylist));
63
+ Object.assign(attrs, cookie());
64
+ return attrs;
65
+ };
66
+
67
+ //#endregion
68
+ //#region src/browser/FetchFileReader.ts
69
+ /**
70
+ * Browser `FileReader` implementation that fetches source files over HTTP(S).
71
+ *
72
+ * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
73
+ * the original source for each frame and render a code snippet around the
74
+ * offending line. The source URL comes from the frame (usually the URL of
75
+ * the JS bundle that produced the error, post-sourcemap resolution).
76
+ *
77
+ * Three safety gates:
78
+ *
79
+ * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
80
+ * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
81
+ * `null` immediately. This avoids surprise privilege boundaries (e.g.,
82
+ * pages should not read extension-internal files) and dodges CORS/CSP
83
+ * walls that would error noisily.
84
+ * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
85
+ * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
86
+ * snippet is simply omitted from the report.
87
+ * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
88
+ * return `null`. We never let a source-fetch failure leak as an error
89
+ * that the consumer page would see.
90
+ *
91
+ * The `read()` contract returns `null` on any failure path, never throws.
92
+ */
93
+ var FetchFileReader = class {
94
+ read(url) {
95
+ if (!/^https?:\/\//i.test(url)) return Promise.resolve(null);
96
+ return fetch(url).then((response) => {
97
+ if (response.status !== 200) return null;
98
+ return response.text();
99
+ }).catch(() => null);
100
+ }
101
+ };
102
+
103
+ //#endregion
104
+ //#region src/env/index.ts
105
+ const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.4.0" : "?";
106
+
107
+ //#endregion
108
+ //#region src/browser/catchWindowErrors.ts
109
+ function catchWindowErrors() {
110
+ if (typeof window === "undefined") return;
111
+ window.addEventListener("error", (event) => {
112
+ const flare = window.flare;
113
+ if (!flare) return;
114
+ if (event.error instanceof Error) flare.reportSilently(event.error);
115
+ });
116
+ window.addEventListener("unhandledrejection", (event) => {
117
+ const flare = window.flare;
118
+ if (!flare) return;
119
+ const reason = event.reason;
120
+ if (reason instanceof Error) {
121
+ flare.reportSilently(reason);
122
+ return;
123
+ }
124
+ if (hasStack(reason)) {
125
+ const error = new Error(describeRejectionReason(reason));
126
+ error.stack = reason.stack;
127
+ flare.reportSilently(error);
128
+ return;
129
+ }
130
+ Promise.resolve(flare.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
131
+ });
132
+ }
133
+ function describeRejectionReason(reason) {
134
+ if (typeof reason === "string") return reason;
135
+ if (reason && typeof reason === "object") {
136
+ const message = reason.message;
137
+ if (typeof message === "string") return message;
138
+ try {
139
+ return JSON.stringify(reason);
140
+ } catch {
141
+ return "Unhandled promise rejection (non-serializable reason)";
142
+ }
143
+ }
144
+ return String(reason);
145
+ }
146
+ function hasStack(value) {
147
+ return typeof value === "object" && value !== null && "stack" in value && typeof value.stack === "string";
148
+ }
149
+
150
+ //#endregion
151
+ export { BrowserFlushScheduler as a, collectBrowser as i, CLIENT_VERSION as n, FetchFileReader as r, catchWindowErrors as t };
package/dist/index.cjs CHANGED
@@ -1,156 +1,13 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_catchWindowErrors = require('./catchWindowErrors-CTX8goVc.cjs');
3
+ const require_browser = require('./browser.cjs');
2
4
  let _flareapp_core = require("@flareapp/core");
3
5
 
4
- //#region src/browser/catchWindowErrors.ts
5
- function catchWindowErrors() {
6
- if (typeof window === "undefined") return;
7
- window.addEventListener("error", (event) => {
8
- const flare = window.flare;
9
- if (!flare) return;
10
- if (event.error instanceof Error) flare.reportSilently(event.error);
11
- });
12
- window.addEventListener("unhandledrejection", (event) => {
13
- const flare = window.flare;
14
- if (!flare) return;
15
- const reason = event.reason;
16
- if (reason instanceof Error) {
17
- flare.reportSilently(reason);
18
- return;
19
- }
20
- if (hasStack(reason)) {
21
- const error = new Error(describeRejectionReason(reason));
22
- error.stack = reason.stack;
23
- flare.reportSilently(error);
24
- return;
25
- }
26
- Promise.resolve(flare.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
27
- });
28
- }
29
- function describeRejectionReason(reason) {
30
- if (typeof reason === "string") return reason;
31
- if (reason && typeof reason === "object") {
32
- const message = reason.message;
33
- if (typeof message === "string") return message;
34
- try {
35
- return JSON.stringify(reason);
36
- } catch {
37
- return "Unhandled promise rejection (non-serializable reason)";
38
- }
39
- }
40
- return String(reason);
41
- }
42
- function hasStack(value) {
43
- return typeof value === "object" && value !== null && "stack" in value && typeof value.stack === "string";
44
- }
45
-
46
- //#endregion
47
- //#region src/browser/context/cookie.ts
48
- function cookie() {
49
- if (!window.document.cookie) return {};
50
- const cookies = {};
51
- window.document.cookie.split("; ").forEach((rawCookie) => {
52
- const idx = rawCookie.indexOf("=");
53
- if (idx === -1) {
54
- cookies[rawCookie] = "";
55
- return;
56
- }
57
- const name = rawCookie.slice(0, idx);
58
- cookies[name] = rawCookie.slice(idx + 1);
59
- });
60
- return { "http.request.cookies": cookies };
61
- }
62
-
63
- //#endregion
64
- //#region src/browser/context/request.ts
65
- function request(urlDenylist) {
66
- return {
67
- "url.full": (0, _flareapp_core.redactUrlQuery)(window.location.href, urlDenylist),
68
- "user_agent.original": window.navigator.userAgent,
69
- "http.request.referrer": (0, _flareapp_core.redactUrlQuery)(window.document.referrer, urlDenylist),
70
- "document.ready_state": window.document.readyState
71
- };
72
- }
73
-
74
- //#endregion
75
- //#region src/browser/context/requestData.ts
76
- function requestData(urlDenylist) {
77
- if (!window.location.search) return {};
78
- return { "url.query": (0, _flareapp_core.redactUrlQuery)(window.location.search, urlDenylist).replace(/^\?/, "") };
79
- }
80
-
81
- //#endregion
82
- //#region src/browser/context/collectBrowser.ts
83
- const collectBrowser = (config) => {
84
- if (typeof window === "undefined") return { "flare.entry_point.type": "server" };
85
- const attrs = { "flare.entry_point.type": "web" };
86
- if (window?.location?.href) {
87
- attrs["flare.entry_point.value"] = (0, _flareapp_core.redactUrlQuery)(window.location.href, config.urlDenylist);
88
- if (window.location.pathname) {
89
- attrs["flare.entry_point.handler.identifier"] = window.location.pathname;
90
- attrs["flare.entry_point.handler.type"] = "browser";
91
- }
92
- }
93
- Object.assign(attrs, request(config.urlDenylist));
94
- Object.assign(attrs, requestData(config.urlDenylist));
95
- Object.assign(attrs, cookie());
96
- return attrs;
97
- };
98
-
99
- //#endregion
100
- //#region src/browser/FetchFileReader.ts
101
- /**
102
- * Browser `FileReader` implementation that fetches source files over HTTP(S).
103
- *
104
- * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
105
- * the original source for each frame and render a code snippet around the
106
- * offending line. The source URL comes from the frame (usually the URL of
107
- * the JS bundle that produced the error, post-sourcemap resolution).
108
- *
109
- * Three safety gates:
110
- *
111
- * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
112
- * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
113
- * `null` immediately. This avoids surprise privilege boundaries (e.g.,
114
- * pages should not read extension-internal files) and dodges CORS/CSP
115
- * walls that would error noisily.
116
- * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
117
- * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
118
- * snippet is simply omitted from the report.
119
- * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
120
- * return `null`. We never let a source-fetch failure leak as an error
121
- * that the consumer page would see.
122
- *
123
- * The `read()` contract returns `null` on any failure path, never throws.
124
- */
125
- var FetchFileReader = class {
126
- read(url) {
127
- if (!/^https?:\/\//i.test(url)) return Promise.resolve(null);
128
- return fetch(url).then((response) => {
129
- if (response.status !== 200) return null;
130
- return response.text();
131
- }).catch(() => null);
132
- }
133
- };
134
-
135
- //#endregion
136
- //#region src/env/index.ts
137
- const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.2.1" : "?";
138
-
139
- //#endregion
140
6
  //#region src/index.ts
141
- var Flare = class extends _flareapp_core.Flare {
142
- constructor(api = new _flareapp_core.Api(), contextCollector = collectBrowser, fileReader = new FetchFileReader(), scopeProvider = new _flareapp_core.GlobalScopeProvider()) {
143
- super(api, contextCollector, fileReader, scopeProvider);
144
- this.setSdkInfo({
145
- name: "@flareapp/js",
146
- version: CLIENT_VERSION
147
- });
148
- }
149
- };
150
- const flare = new Flare();
7
+ const flare = new require_browser.Flare();
151
8
  if (typeof window !== "undefined" && window) {
152
9
  window.flare = flare;
153
- catchWindowErrors();
10
+ require_catchWindowErrors.catchWindowErrors();
154
11
  }
155
12
 
156
13
  //#endregion
@@ -160,13 +17,19 @@ Object.defineProperty(exports, 'DEFAULT_URL_DENYLIST', {
160
17
  return _flareapp_core.DEFAULT_URL_DENYLIST;
161
18
  }
162
19
  });
163
- exports.Flare = Flare;
20
+ exports.Flare = require_browser.Flare;
164
21
  Object.defineProperty(exports, 'GlobalScopeProvider', {
165
22
  enumerable: true,
166
23
  get: function () {
167
24
  return _flareapp_core.GlobalScopeProvider;
168
25
  }
169
26
  });
27
+ Object.defineProperty(exports, 'Logger', {
28
+ enumerable: true,
29
+ get: function () {
30
+ return _flareapp_core.Logger;
31
+ }
32
+ });
170
33
  Object.defineProperty(exports, 'NullFileReader', {
171
34
  enumerable: true,
172
35
  get: function () {
package/dist/index.d.cts CHANGED
@@ -1,9 +1,7 @@
1
- import { Api, AttributeValue, Attributes, Config, ContextCollector, ContextCollector as ContextCollector$1, DEFAULT_URL_DENYLIST, EntryPointHandler, FileReader, FileReader as FileReader$1, Flare as Flare$1, Framework, GlobalScopeProvider, Glow, MessageLevel, NullFileReader, OverriddenGrouping, Report, Scope, ScopeProvider, ScopeProvider as ScopeProvider$1, SdkInfo, SpanEvent, StackFrame, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, resolveDenylist } from "@flareapp/core";
1
+ import { Flare } from "./browser.cjs";
2
+ import { AttributeValue, Attributes, Config, ContextCollector, DEFAULT_URL_DENYLIST, EntryPointHandler, FileReader, FlushFn, FlushScheduler, Framework, GlobalScopeProvider, Glow, Logger, MessageLevel, NullFileReader, OverriddenGrouping, Report, Scope, ScopeProvider, SdkInfo, SpanEvent, StackFrame, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, resolveDenylist } from "@flareapp/core";
2
3
 
3
4
  //#region src/index.d.ts
4
- declare class Flare extends Flare$1 {
5
- constructor(api?: Api, contextCollector?: ContextCollector$1, fileReader?: FileReader$1, scopeProvider?: ScopeProvider$1);
6
- }
7
5
  declare const flare: Flare;
8
6
  //#endregion
9
- export { type AttributeValue, type Attributes, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type Framework, GlobalScopeProvider, type Glow, type MessageLevel, NullFileReader, type OverriddenGrouping, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
7
+ export { type AttributeValue, type Attributes, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, Logger, type MessageLevel, NullFileReader, type OverriddenGrouping, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- import { Api, AttributeValue, Attributes, Config, ContextCollector, ContextCollector as ContextCollector$1, DEFAULT_URL_DENYLIST, EntryPointHandler, FileReader, FileReader as FileReader$1, Flare as Flare$1, Framework, GlobalScopeProvider, Glow, MessageLevel, NullFileReader, OverriddenGrouping, Report, Scope, ScopeProvider, ScopeProvider as ScopeProvider$1, SdkInfo, SpanEvent, StackFrame, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, resolveDenylist } from "@flareapp/core";
1
+ import { Flare } from "./browser.mjs";
2
+ import { AttributeValue, Attributes, Config, ContextCollector, DEFAULT_URL_DENYLIST, EntryPointHandler, FileReader, FlushFn, FlushScheduler, Framework, GlobalScopeProvider, Glow, Logger, MessageLevel, NullFileReader, OverriddenGrouping, Report, Scope, ScopeProvider, SdkInfo, SpanEvent, StackFrame, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, resolveDenylist } from "@flareapp/core";
2
3
 
3
4
  //#region src/index.d.ts
4
- declare class Flare extends Flare$1 {
5
- constructor(api?: Api, contextCollector?: ContextCollector$1, fileReader?: FileReader$1, scopeProvider?: ScopeProvider$1);
6
- }
7
5
  declare const flare: Flare;
8
6
  //#endregion
9
- export { type AttributeValue, type Attributes, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type Framework, GlobalScopeProvider, type Glow, type MessageLevel, NullFileReader, type OverriddenGrouping, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
7
+ export { type AttributeValue, type Attributes, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, Logger, type MessageLevel, NullFileReader, type OverriddenGrouping, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
package/dist/index.mjs CHANGED
@@ -1,151 +1,8 @@
1
- import { Api, DEFAULT_URL_DENYLIST, Flare as Flare$1, GlobalScopeProvider, GlobalScopeProvider as GlobalScopeProvider$1, NullFileReader, Scope, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, redactUrlQuery as redactUrlQuery$1, resolveDenylist } from "@flareapp/core";
1
+ import { t as catchWindowErrors } from "./catchWindowErrors-DKjw0DIA.mjs";
2
+ import { Flare } from "./browser.mjs";
3
+ import { DEFAULT_URL_DENYLIST, GlobalScopeProvider, Logger, NullFileReader, Scope, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, resolveDenylist } from "@flareapp/core";
2
4
 
3
- //#region src/browser/catchWindowErrors.ts
4
- function catchWindowErrors() {
5
- if (typeof window === "undefined") return;
6
- window.addEventListener("error", (event) => {
7
- const flare = window.flare;
8
- if (!flare) return;
9
- if (event.error instanceof Error) flare.reportSilently(event.error);
10
- });
11
- window.addEventListener("unhandledrejection", (event) => {
12
- const flare = window.flare;
13
- if (!flare) return;
14
- const reason = event.reason;
15
- if (reason instanceof Error) {
16
- flare.reportSilently(reason);
17
- return;
18
- }
19
- if (hasStack(reason)) {
20
- const error = new Error(describeRejectionReason(reason));
21
- error.stack = reason.stack;
22
- flare.reportSilently(error);
23
- return;
24
- }
25
- Promise.resolve(flare.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
26
- });
27
- }
28
- function describeRejectionReason(reason) {
29
- if (typeof reason === "string") return reason;
30
- if (reason && typeof reason === "object") {
31
- const message = reason.message;
32
- if (typeof message === "string") return message;
33
- try {
34
- return JSON.stringify(reason);
35
- } catch {
36
- return "Unhandled promise rejection (non-serializable reason)";
37
- }
38
- }
39
- return String(reason);
40
- }
41
- function hasStack(value) {
42
- return typeof value === "object" && value !== null && "stack" in value && typeof value.stack === "string";
43
- }
44
-
45
- //#endregion
46
- //#region src/browser/context/cookie.ts
47
- function cookie() {
48
- if (!window.document.cookie) return {};
49
- const cookies = {};
50
- window.document.cookie.split("; ").forEach((rawCookie) => {
51
- const idx = rawCookie.indexOf("=");
52
- if (idx === -1) {
53
- cookies[rawCookie] = "";
54
- return;
55
- }
56
- const name = rawCookie.slice(0, idx);
57
- cookies[name] = rawCookie.slice(idx + 1);
58
- });
59
- return { "http.request.cookies": cookies };
60
- }
61
-
62
- //#endregion
63
- //#region src/browser/context/request.ts
64
- function request(urlDenylist) {
65
- return {
66
- "url.full": redactUrlQuery$1(window.location.href, urlDenylist),
67
- "user_agent.original": window.navigator.userAgent,
68
- "http.request.referrer": redactUrlQuery$1(window.document.referrer, urlDenylist),
69
- "document.ready_state": window.document.readyState
70
- };
71
- }
72
-
73
- //#endregion
74
- //#region src/browser/context/requestData.ts
75
- function requestData(urlDenylist) {
76
- if (!window.location.search) return {};
77
- return { "url.query": redactUrlQuery$1(window.location.search, urlDenylist).replace(/^\?/, "") };
78
- }
79
-
80
- //#endregion
81
- //#region src/browser/context/collectBrowser.ts
82
- const collectBrowser = (config) => {
83
- if (typeof window === "undefined") return { "flare.entry_point.type": "server" };
84
- const attrs = { "flare.entry_point.type": "web" };
85
- if (window?.location?.href) {
86
- attrs["flare.entry_point.value"] = redactUrlQuery$1(window.location.href, config.urlDenylist);
87
- if (window.location.pathname) {
88
- attrs["flare.entry_point.handler.identifier"] = window.location.pathname;
89
- attrs["flare.entry_point.handler.type"] = "browser";
90
- }
91
- }
92
- Object.assign(attrs, request(config.urlDenylist));
93
- Object.assign(attrs, requestData(config.urlDenylist));
94
- Object.assign(attrs, cookie());
95
- return attrs;
96
- };
97
-
98
- //#endregion
99
- //#region src/browser/FetchFileReader.ts
100
- /**
101
- * Browser `FileReader` implementation that fetches source files over HTTP(S).
102
- *
103
- * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
104
- * the original source for each frame and render a code snippet around the
105
- * offending line. The source URL comes from the frame (usually the URL of
106
- * the JS bundle that produced the error, post-sourcemap resolution).
107
- *
108
- * Three safety gates:
109
- *
110
- * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
111
- * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
112
- * `null` immediately. This avoids surprise privilege boundaries (e.g.,
113
- * pages should not read extension-internal files) and dodges CORS/CSP
114
- * walls that would error noisily.
115
- * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
116
- * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
117
- * snippet is simply omitted from the report.
118
- * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
119
- * return `null`. We never let a source-fetch failure leak as an error
120
- * that the consumer page would see.
121
- *
122
- * The `read()` contract returns `null` on any failure path, never throws.
123
- */
124
- var FetchFileReader = class {
125
- read(url) {
126
- if (!/^https?:\/\//i.test(url)) return Promise.resolve(null);
127
- return fetch(url).then((response) => {
128
- if (response.status !== 200) return null;
129
- return response.text();
130
- }).catch(() => null);
131
- }
132
- };
133
-
134
- //#endregion
135
- //#region src/env/index.ts
136
- const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.2.1" : "?";
137
-
138
- //#endregion
139
5
  //#region src/index.ts
140
- var Flare = class extends Flare$1 {
141
- constructor(api = new Api(), contextCollector = collectBrowser, fileReader = new FetchFileReader(), scopeProvider = new GlobalScopeProvider$1()) {
142
- super(api, contextCollector, fileReader, scopeProvider);
143
- this.setSdkInfo({
144
- name: "@flareapp/js",
145
- version: CLIENT_VERSION
146
- });
147
- }
148
- };
149
6
  const flare = new Flare();
150
7
  if (typeof window !== "undefined" && window) {
151
8
  window.flare = flare;
@@ -153,4 +10,4 @@ if (typeof window !== "undefined" && window) {
153
10
  }
154
11
 
155
12
  //#endregion
156
- export { DEFAULT_URL_DENYLIST, Flare, GlobalScopeProvider, NullFileReader, Scope, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
13
+ export { DEFAULT_URL_DENYLIST, Flare, GlobalScopeProvider, Logger, NullFileReader, Scope, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flareapp/js",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "description": "JavaScript client for flareapp.io",
5
5
  "homepage": "https://flareapp.io",
6
6
  "bugs": {
@@ -39,17 +39,27 @@
39
39
  "types": "./dist/index.d.cts",
40
40
  "default": "./dist/index.cjs"
41
41
  }
42
+ },
43
+ "./browser": {
44
+ "import": {
45
+ "types": "./dist/browser.d.mts",
46
+ "default": "./dist/browser.mjs"
47
+ },
48
+ "require": {
49
+ "types": "./dist/browser.d.cts",
50
+ "default": "./dist/browser.cjs"
51
+ }
42
52
  }
43
53
  },
44
54
  "scripts": {
45
55
  "prepublishOnly": "npm run build",
46
- "build": "tsdown src/index.ts --format cjs,esm --dts --env.FLARE_JS_CLIENT_VERSION=$(node -p \"require('./package.json').version\") --clean",
56
+ "build": "tsdown src/index.ts src/browser.ts --format cjs,esm --dts --env.FLARE_JS_CLIENT_VERSION=$(node -p \"require('./package.json').version\") --clean",
47
57
  "test": "vitest run",
48
58
  "typescript": "tsc --noEmit",
49
59
  "release": "release-it"
50
60
  },
51
61
  "dependencies": {
52
- "@flareapp/core": "2.2.1"
62
+ "@flareapp/core": "2.4.0"
53
63
  },
54
64
  "devDependencies": {
55
65
  "error-stack-parser": "^2.0.2",