@faststats/web 0.13.0 → 0.13.2

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.
@@ -1,4 +1,3 @@
1
- //#region src/extensions.d.ts
2
1
  type ExtensionHooks = {
3
2
  pageChange: {
4
3
  url: string;
@@ -40,5 +39,4 @@ interface AnalyticsExtension {
40
39
  type ExtensionCleanup = (options: {
41
40
  discard: boolean;
42
41
  }) => void;
43
- //#endregion
44
42
  export { ExtensionHook as a, ExtensionHandler as i, AnalyticsExtensionContext as n, ExtensionHooks as o, ExtensionCleanup as r, AnalyticsExtension as t };
@@ -0,0 +1,15 @@
1
+ import { o as getStorageItem, s as setStorageItem, t as createId } from "./session-manager-DBWYTyfd.js";
2
+ // src/utils/identifiers.ts
3
+ const key = (siteKey) => `faststats_anon_id_${siteKey}`;
4
+ /** The visitor's persisted anonymous ID, or "" when it cannot be stored. */
5
+ function getAnonymousId(siteKey, cookieless = false) {
6
+ if (cookieless) return "";
7
+ const existing = getStorageItem("localStorage", key(siteKey));
8
+ if (existing) return existing;
9
+ const id = createId();
10
+ return setStorageItem("localStorage", key(siteKey), id) ? id : "";
11
+ }
12
+ function resetAnonymousId(siteKey, cookieless = false) {
13
+ return !cookieless && setStorageItem("localStorage", key(siteKey), null) ? getAnonymousId(siteKey) : "";
14
+ }
15
+ export { resetAnonymousId as n, getAnonymousId as t };
@@ -0,0 +1,174 @@
1
+ // src/extensions.ts
2
+ /** Runs a built-in tracker with the client's settings and page lifecycle. */
3
+ function trackerExtension(name, create, hook) {
4
+ return {
5
+ name,
6
+ setup(context) {
7
+ const { siteKey, baseUrl, debug, trackHash, sdkName, sdkVersion } = context;
8
+ const tracker = create({
9
+ siteKey,
10
+ baseUrl,
11
+ debug,
12
+ trackHash,
13
+ cookieless: context.isCookieless(),
14
+ sdkName,
15
+ sdkVersion
16
+ });
17
+ context.on("pageHide", ({ persisted, terminal }) => tracker.onPageHidden(persisted, terminal));
18
+ context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
19
+ hook?.(tracker, context);
20
+ tracker.start();
21
+ return ({ discard }) => tracker.stop(discard);
22
+ }
23
+ };
24
+ }
25
+ var ExtensionHost = class {
26
+ context;
27
+ log;
28
+ handlers = /* @__PURE__ */ new Map();
29
+ cleanups = [];
30
+ stopped = false;
31
+ constructor(extensions, context, log) {
32
+ this.context = context;
33
+ this.log = log;
34
+ for (const extension of extensions) this.setup(extension);
35
+ }
36
+ emit(hook, event) {
37
+ if (this.stopped) return;
38
+ for (const handler of this.handlers.get(hook) ?? []) try {
39
+ handler(event)?.catch((error) => this.log(`${hook} failed`, error));
40
+ } catch (error) {
41
+ this.log(`${hook} failed`, error);
42
+ }
43
+ }
44
+ stop(discard = false) {
45
+ if (this.stopped) return;
46
+ this.stopped = true;
47
+ for (const cleanup of this.cleanups.reverse()) try {
48
+ cleanup({ discard });
49
+ } catch (error) {
50
+ this.log("cleanup failed", error);
51
+ }
52
+ this.cleanups.length = 0;
53
+ this.handlers.clear();
54
+ }
55
+ async setup(extension) {
56
+ if (this.stopped) return;
57
+ const subscriptions = [];
58
+ try {
59
+ const cleanup = await extension.setup({
60
+ ...this.context,
61
+ on: (hook, handler) => {
62
+ if (this.stopped) return () => {};
63
+ const handlers = this.handlers.get(hook) ?? /* @__PURE__ */ new Set();
64
+ this.handlers.set(hook, handlers);
65
+ handlers.add(handler);
66
+ const off = () => handlers.delete(handler);
67
+ subscriptions.push(off);
68
+ return off;
69
+ }
70
+ });
71
+ if (this.stopped) cleanup?.({ discard: true });
72
+ else if (cleanup) this.cleanups.push(cleanup);
73
+ } catch (error) {
74
+ for (const off of subscriptions) off();
75
+ this.log(`${extension.name} setup failed`, error);
76
+ }
77
+ }
78
+ };
79
+ // src/utils/api-urls.ts
80
+ function resolveBaseUrl(baseUrl) {
81
+ return baseUrl?.replace(/\/+$/, "") || "https://metrics.faststats.dev";
82
+ }
83
+ const URLS = {
84
+ events: "/v1/web",
85
+ identify: "/v1/identify",
86
+ replay: "/v1/replay",
87
+ vitals: "/v1/vitals"
88
+ };
89
+ function sanitizeUrl(url, includeHash = false) {
90
+ try {
91
+ const parsed = new URL(url, location.href);
92
+ return `${parsed.origin}${parsed.pathname}${includeHash ? parsed.hash : ""}`;
93
+ } catch {
94
+ return url.split(includeHash ? "?" : /[?#]/, 1)[0] ?? "";
95
+ }
96
+ }
97
+ const UTM_KEYS = [
98
+ "utm_source",
99
+ "utm_medium",
100
+ "utm_campaign",
101
+ "utm_term",
102
+ "utm_content"
103
+ ];
104
+ function getReferrer() {
105
+ const raw = document.referrer;
106
+ if (!raw) return null;
107
+ try {
108
+ if (new URL(raw, location.href).origin === location.origin) return null;
109
+ } catch {}
110
+ return sanitizeUrl(raw);
111
+ }
112
+ /** Visitor environment sent with every event; unavailable values are omitted. */
113
+ function getClientContext() {
114
+ let timezone;
115
+ try {
116
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
117
+ } catch {}
118
+ const { width, height } = globalThis.screen ?? {};
119
+ return {
120
+ timezone,
121
+ language: globalThis.navigator?.language,
122
+ screen: width && height ? `${width}x${height}` : void 0
123
+ };
124
+ }
125
+ function getPageContext(includeHash = false) {
126
+ const hash = includeHash ? location.hash : "";
127
+ const ctx = {
128
+ page: `${location.pathname}${hash}`,
129
+ url: sanitizeUrl(location.href, includeHash),
130
+ referrer: getReferrer(),
131
+ title: document.title
132
+ };
133
+ if (!location.search) return ctx;
134
+ const sp = new URLSearchParams(location.search);
135
+ for (const key of UTM_KEYS) {
136
+ const value = sp.get(key);
137
+ if (value) ctx[key] = value;
138
+ }
139
+ return ctx;
140
+ }
141
+ // src/utils/send-data.ts
142
+ async function sendData(options) {
143
+ const { url, data, contentType = "application/json", debug = false, debugPrefix = "[Analytics]", useBeacon = true, keepalive = true, settleRejected } = options;
144
+ const log = (...details) => {
145
+ if (debug) console.log(debugPrefix, ...details);
146
+ };
147
+ if (useBeacon) try {
148
+ const body = data instanceof Blob ? data : new Blob([data], { type: contentType });
149
+ if (navigator.sendBeacon(url, body)) {
150
+ log("Sent via beacon");
151
+ return true;
152
+ }
153
+ } catch {}
154
+ const post = (keepalive) => fetch(url, {
155
+ method: "POST",
156
+ body: data,
157
+ headers: { "Content-Type": contentType },
158
+ keepalive,
159
+ credentials: "omit"
160
+ });
161
+ try {
162
+ const useKeepalive = keepalive && new Blob([data]).size <= 61440;
163
+ const { ok, status } = await post(useKeepalive).catch((error) => {
164
+ if (!useKeepalive) throw error;
165
+ return post(false);
166
+ });
167
+ log(ok ? "Sent via fetch" : status);
168
+ return ok || !!settleRejected && status < 500 && status !== 429;
169
+ } catch {
170
+ log("Failed to send");
171
+ return false;
172
+ }
173
+ }
174
+ export { resolveBaseUrl as a, trackerExtension as c, getPageContext as i, URLS as n, sanitizeUrl as o, getClientContext as r, ExtensionHost as s, sendData as t };
@@ -0,0 +1,114 @@
1
+ // src/utils/storage.ts
2
+ function getStorageItem(name, key) {
3
+ try {
4
+ return globalThis[name]?.getItem(key) ?? null;
5
+ } catch {
6
+ return null;
7
+ }
8
+ }
9
+ /** Stores `value`, or removes the item for null. False when storage fails. */
10
+ function setStorageItem(name, key, value) {
11
+ try {
12
+ const storage = globalThis[name];
13
+ if (!storage) return false;
14
+ if (value === null) storage.removeItem(key);
15
+ else storage.setItem(key, value);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ // src/utils/session-manager.ts
22
+ const IDLE_MS = 18e5;
23
+ const MAX_AGE_MS = 864e5;
24
+ const SESSION_KEY = "faststats_session_";
25
+ const WINDOW_KEY = "faststats_window_id_";
26
+ const memory = /* @__PURE__ */ new Map();
27
+ const listeners = /* @__PURE__ */ new Map();
28
+ const NO_STORAGE = {};
29
+ const slot = (siteKey, cookieless) => `${siteKey}:${cookieless ? "memory" : "persistent"}`;
30
+ function storageScope() {
31
+ try {
32
+ return globalThis.localStorage ?? NO_STORAGE;
33
+ } catch {
34
+ return NO_STORAGE;
35
+ }
36
+ }
37
+ function createId() {
38
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
39
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
40
+ }
41
+ function load(siteKey, cookieless) {
42
+ if (!cookieless) try {
43
+ const session = JSON.parse(getStorageItem("localStorage", `${SESSION_KEY}${siteKey}`) ?? "null");
44
+ if (typeof session?.id === "string" && session.id && Number.isFinite(session.start) && Number.isFinite(session.activity)) return session;
45
+ } catch {}
46
+ const entry = memory.get(slot(siteKey, cookieless));
47
+ return entry?.scope === storageScope() ? entry.session : null;
48
+ }
49
+ function save(siteKey, cookieless, session) {
50
+ const json = JSON.stringify(session);
51
+ if (cookieless || !setStorageItem("localStorage", SESSION_KEY + siteKey, json)) memory.set(slot(siteKey, cookieless), {
52
+ scope: storageScope(),
53
+ session
54
+ });
55
+ }
56
+ function toContext(siteKey, cookieless, { id, start }) {
57
+ let windowId = cookieless ? id : getStorageItem("sessionStorage", WINDOW_KEY + siteKey);
58
+ if (!windowId) {
59
+ windowId = createId();
60
+ if (!setStorageItem("sessionStorage", WINDOW_KEY + siteKey, windowId)) windowId = id;
61
+ }
62
+ return {
63
+ sessionId: id,
64
+ windowId,
65
+ sessionStart: start
66
+ };
67
+ }
68
+ function emit(siteKey, cookieless, prev, next) {
69
+ for (const listener of listeners.get(slot(siteKey, cookieless)) ?? []) listener(prev, next);
70
+ }
71
+ function resolveSession(siteKey, cookieless, touch) {
72
+ const now = Date.now();
73
+ const existing = load(siteKey, cookieless);
74
+ const expired = !!existing && (now - existing.activity >= IDLE_MS || now - existing.start >= MAX_AGE_MS);
75
+ const session = existing && !expired ? existing : {
76
+ id: createId(),
77
+ start: now,
78
+ activity: now
79
+ };
80
+ if (touch) session.activity = now;
81
+ if (touch || session !== existing) save(siteKey, cookieless, session);
82
+ const context = toContext(siteKey, cookieless, session);
83
+ if (existing && expired) emit(siteKey, cookieless, toContext(siteKey, cookieless, existing), context);
84
+ return context;
85
+ }
86
+ /** The current session, replacing an expired one without counting activity. */
87
+ function getSessionContext(siteKey, cookieless = false) {
88
+ return resolveSession(siteKey, cookieless, false);
89
+ }
90
+ /** Counts visitor activity in the current session, which it returns. */
91
+ function touchActivity(siteKey, cookieless = false) {
92
+ return resolveSession(siteKey, cookieless, true);
93
+ }
94
+ function resetSession(siteKey, cookieless = false) {
95
+ const prev = getSessionContext(siteKey, cookieless);
96
+ memory.delete(slot(siteKey, cookieless));
97
+ if (!cookieless) {
98
+ setStorageItem("localStorage", SESSION_KEY + siteKey, null);
99
+ setStorageItem("sessionStorage", WINDOW_KEY + siteKey, null);
100
+ }
101
+ const next = getSessionContext(siteKey, cookieless);
102
+ if (prev.sessionId !== next.sessionId) emit(siteKey, cookieless, prev, next);
103
+ return next;
104
+ }
105
+ function onSessionRotated(siteKey, cookieless, listener) {
106
+ const key = slot(siteKey, cookieless);
107
+ const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
108
+ listeners.set(key, set.add(listener));
109
+ return () => {
110
+ set.delete(listener);
111
+ if (!set.size) listeners.delete(key);
112
+ };
113
+ }
114
+ export { touchActivity as a, resetSession as i, getSessionContext as n, getStorageItem as o, onSessionRotated as r, setStorageItem as s, createId as t };
package/dist/error.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { t as AnalyticsExtension } from "./chunks/extensions-BfubKIzk.js";
2
- //#region src/error.d.ts
1
+ import { t as AnalyticsExtension } from "./chunks/extensions-C2k9Im6I.js";
3
2
  export type ErrorEntry = {
4
3
  error: string;
5
4
  message?: string;
@@ -39,6 +38,7 @@ export default class ErrorTracker {
39
38
  private onError;
40
39
  private onRejection;
41
40
  private enqueue;
41
+ /** Returns true when the error was already queued. */
42
+ private add;
42
43
  private flush;
43
- }
44
- //#endregion
44
+ }
package/dist/error.js CHANGED
@@ -1,30 +1,14 @@
1
- import { a as resolveBaseUrl, i as getPageContext, n as URLS, t as sendData } from "./chunks/send-data-C76HbZ8d.js";
2
- import { n as getSessionContext } from "./chunks/session-manager-DUpF3mi3.js";
3
- import { t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
4
- //#region src/error.ts
1
+ import { a as resolveBaseUrl, c as trackerExtension, i as getPageContext, n as URLS, t as sendData } from "./chunks/send-data-BCVUgwry.js";
2
+ import { n as getSessionContext } from "./chunks/session-manager-DBWYTyfd.js";
3
+ import { t as getAnonymousId } from "./chunks/identifiers-BHYhM7DM.js";
4
+ // src/error.ts
5
5
  const SDK_NAME = "@faststats/web";
6
- const SDK_VERSION = "0.13.0";
6
+ const SDK_VERSION = "0.13.2";
7
7
  function errorTracking(options = {}) {
8
- return {
9
- name: "error-tracking",
10
- setup(context) {
11
- const tracker = new ErrorTracker({
12
- ...options,
13
- siteKey: context.siteKey,
14
- baseUrl: context.baseUrl,
15
- debug: context.debug,
16
- trackHash: context.trackHash,
17
- cookieless: context.isCookieless(),
18
- sdkName: context.sdkName,
19
- sdkVersion: context.sdkVersion
20
- });
21
- context.on("pageHide", ({ persisted }) => tracker.onPageHidden(persisted));
22
- context.on("error", ({ error }) => tracker.captureError(error));
23
- context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
24
- tracker.start();
25
- return ({ discard }) => tracker.stop(discard);
26
- }
27
- };
8
+ return trackerExtension("error-tracking", (context) => new ErrorTracker({
9
+ ...options,
10
+ ...context
11
+ }), (tracker, context) => context.on("error", ({ error }) => tracker.captureError(error)));
28
12
  }
29
13
  const EXTENSION_URL = /(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//;
30
14
  function toEntry(kind, source, handled, fallback) {
@@ -103,23 +87,34 @@ var ErrorTracker = class {
103
87
  const buildId = globalThis.__SOURCEMAPS_BUILD__?.buildId;
104
88
  const userId = getAnonymousId(this.options.siteKey, this.cookieless);
105
89
  const ctx = getSessionContext(this.options.siteKey, this.cookieless);
106
- this.queue.push({
90
+ const context = {
91
+ token: this.options.siteKey,
92
+ ...userId ? { userId } : {},
93
+ sessionId: ctx.sessionId,
94
+ windowId: ctx.windowId,
95
+ ...typeof buildId === "string" && buildId.trim() ? { buildId } : {},
96
+ sdkName: this.options.sdkName ?? SDK_NAME,
97
+ sdkVersion: this.options.sdkVersion ?? SDK_VERSION,
98
+ event: "error",
99
+ ...getPageContext(this.options.trackHash ?? false),
100
+ properties: {}
101
+ };
102
+ const key = JSON.stringify([context, error]);
103
+ if (this.add({
104
+ key,
105
+ context,
107
106
  error,
108
- context: {
109
- token: this.options.siteKey,
110
- ...userId ? { userId } : {},
111
- sessionId: ctx.sessionId,
112
- windowId: ctx.windowId,
113
- ...typeof buildId === "string" && buildId.trim() ? { buildId } : {},
114
- sdkName: this.options.sdkName ?? SDK_NAME,
115
- sdkVersion: this.options.sdkVersion ?? SDK_VERSION,
116
- event: "error",
117
- ...getPageContext(this.options.trackHash ?? false),
118
- properties: {}
119
- }
120
- });
107
+ count: 1
108
+ })) return;
121
109
  if (this.queue.length >= this.maxQueueSize) this.flush();
122
110
  }
111
+ /** Returns true when the error was already queued. */
112
+ add(item) {
113
+ const queued = this.queue.find(({ key }) => key === item.key);
114
+ if (queued) queued.count += item.count;
115
+ else this.queue.push(item);
116
+ return !!queued;
117
+ }
123
118
  async flush() {
124
119
  if (this.flushing || !this.queue.length) return;
125
120
  this.flushing = true;
@@ -129,44 +124,36 @@ var ErrorTracker = class {
129
124
  const groups = /* @__PURE__ */ new Map();
130
125
  for (const item of batch) {
131
126
  const key = JSON.stringify(item.context);
132
- const group = groups.get(key) ?? {
133
- context: item.context,
134
- items: []
135
- };
136
- group.items.push(item);
137
- groups.set(key, group);
127
+ const group = groups.get(key);
128
+ if (group) group.push(item);
129
+ else groups.set(key, [item]);
138
130
  }
139
131
  let failed = [];
140
132
  try {
141
- failed = (await Promise.all([...groups.values()].map(async (group) => {
142
- const errors = /* @__PURE__ */ new Map();
143
- for (const { error } of group.items) {
144
- const key = JSON.stringify(error);
145
- const existing = errors.get(key);
146
- if (existing) existing.count++;
147
- else errors.set(key, {
148
- ...error,
149
- count: 1
150
- });
151
- }
133
+ failed = (await Promise.all([...groups.values()].map(async (items) => {
152
134
  return await sendData({
153
135
  url: this.endpoint,
154
136
  data: JSON.stringify({
155
- ...group.context,
156
- errors: [...errors.values()]
137
+ ...items[0]?.context,
138
+ errors: items.map(({ error, count }) => ({
139
+ ...error,
140
+ count
141
+ }))
157
142
  }),
158
143
  debug: this.options.debug,
159
144
  debugPrefix: "[ErrorTracker]",
160
145
  settleRejected: true
161
- }) ? [] : group.items;
146
+ }) ? [] : items;
162
147
  }))).flat();
163
148
  } finally {
164
149
  this.flushing = false;
165
150
  }
166
151
  if (generation !== this.generation) return;
167
- this.queue = [...failed, ...this.queue].slice(-this.maxQueueSize);
152
+ const captured = this.queue;
153
+ this.queue = [...failed];
154
+ for (const item of captured) this.add(item);
155
+ this.queue = this.queue.slice(-this.maxQueueSize);
168
156
  if (!this.started && !failed.length) this.flush();
169
157
  }
170
158
  };
171
- //#endregion
172
159
  export { ErrorTracker as default, errorTracking };
package/dist/flags.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { n as AnalyticsExtensionContext, r as ExtensionCleanup, t as AnalyticsExtension } from "./chunks/extensions-BfubKIzk.js";
2
- //#region src/flags.d.ts
1
+ import { n as AnalyticsExtensionContext, r as ExtensionCleanup, t as AnalyticsExtension } from "./chunks/extensions-C2k9Im6I.js";
3
2
  export interface FeatureFlagsOptions {
4
3
  /**
5
4
  * Flag keys this app reads. Every loaded flag is recorded as an exposure,
@@ -75,5 +74,4 @@ export declare class FeatureFlags implements AnalyticsExtension {
75
74
  private evaluate;
76
75
  private update;
77
76
  }
78
- export declare function featureFlags(options?: FeatureFlagsOptions): FeatureFlags;
79
- //#endregion
77
+ export declare function featureFlags(options?: FeatureFlagsOptions): FeatureFlags;
package/dist/flags.js CHANGED
@@ -1,6 +1,6 @@
1
- import { n as getSessionContext } from "./chunks/session-manager-DUpF3mi3.js";
2
- import { t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
3
- //#region src/flags.ts
1
+ import { n as getSessionContext } from "./chunks/session-manager-DBWYTyfd.js";
2
+ import { t as getAnonymousId } from "./chunks/identifiers-BHYhM7DM.js";
3
+ // src/flags.ts
4
4
  const DEFAULT_ENDPOINT = "https://flags.faststats.dev";
5
5
  const DEFAULT_TIMEOUT_MS = 3e3;
6
6
  /**
@@ -141,5 +141,4 @@ var FeatureFlags = class {
141
141
  function featureFlags(options) {
142
142
  return new FeatureFlags(options);
143
143
  }
144
- //#endregion
145
144
  export { FeatureFlags, featureFlags };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { a as ExtensionHook, i as ExtensionHandler, n as AnalyticsExtensionContext, o as ExtensionHooks, r as ExtensionCleanup, t as AnalyticsExtension } from "./chunks/extensions-BfubKIzk.js";
2
- //#region src/analytics.d.ts
1
+ import { a as ExtensionHook, i as ExtensionHandler, n as AnalyticsExtensionContext, o as ExtensionHooks, r as ExtensionCleanup, t as AnalyticsExtension } from "./chunks/extensions-C2k9Im6I.js";
3
2
  type ConsentMode = "anonymous" | "granted" | "denied";
4
3
  interface IdentifyOptions {
5
4
  name?: string | null;
@@ -66,20 +65,15 @@ export declare class WebAnalytics {
66
65
  private readonly onPageHide;
67
66
  private readonly onPageShow;
68
67
  private readonly onResume;
68
+ private readonly onBlur;
69
69
  private readonly onActivity;
70
70
  private isIdle;
71
71
  private leavePage;
72
- /**
73
- * Reports the visit's running totals: as a `page_ping` while it may still
74
- * continue, and as its single `page_leave` once it has ended.
75
- */
76
72
  private checkpoint;
77
73
  private pauseVisit;
78
74
  private resumeVisit;
79
75
  private send;
80
76
  }
81
- //#endregion
82
- //#region src/client.d.ts
83
77
  /** Creates an independent client. Call `start()` when it should begin tracking. */
84
78
  export declare function createClient(options: WebAnalyticsOptions): WebAnalytics;
85
79
  /** Initializes and starts the shared client used by the convenience functions. */
@@ -93,5 +87,4 @@ export declare function identify(externalId: string, email?: string, options?: I
93
87
  export declare function logout(resetAnonymousIdentity?: boolean): void;
94
88
  export declare function setConsentMode(mode: ConsentMode): void;
95
89
  export declare function reportError(error: Error): void;
96
- //#endregion
97
90
  export type { AnalyticsExtension, AnalyticsExtensionContext, ConsentMode, ExtensionCleanup, ExtensionHandler, ExtensionHook, ExtensionHooks, IdentifyOptions, IdentifyUser, WebAnalyticsOptions };