@layers/client 0.1.1-alpha.0 → 1.0.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.
@@ -0,0 +1,105 @@
1
+ import { g as EventProperties, n as LayersClient, p as ConsentState, r as LayersConfig } from "./index-C1d8NEFV.js";
2
+ import { ReactNode } from "react";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/react.d.ts
6
+ interface LayersProviderProps {
7
+ config: LayersConfig;
8
+ children: ReactNode;
9
+ }
10
+ /**
11
+ * Provides a LayersClient instance to the React component tree.
12
+ * Initializes the client on mount and shuts it down on unmount.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * import { LayersProvider } from '@layers/client/react';
17
+ *
18
+ * function App() {
19
+ * return (
20
+ * <LayersProvider config={{ apiKey: 'key', appId: 'app', environment: 'production' }}>
21
+ * <MyApp />
22
+ * </LayersProvider>
23
+ * );
24
+ * }
25
+ * ```
26
+ */
27
+ declare function LayersProvider({
28
+ config,
29
+ children
30
+ }: LayersProviderProps): react_jsx_runtime0.JSX.Element;
31
+ /**
32
+ * Returns the LayersClient instance from the nearest LayersProvider.
33
+ * Throws if used outside a LayersProvider.
34
+ */
35
+ declare function useLayers(): LayersClient;
36
+ /**
37
+ * Returns a stable, memoized track function bound to the current LayersClient.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * import { useTrack } from '@layers/client/react';
42
+ *
43
+ * function SignupButton() {
44
+ * const track = useTrack();
45
+ * return <button onClick={() => track('signup_click', { source: 'hero' })}>Sign up</button>;
46
+ * }
47
+ * ```
48
+ */
49
+ declare function useTrack(): (eventName: string, properties?: EventProperties) => void;
50
+ /**
51
+ * Returns a stable, memoized screen function bound to the current LayersClient.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * import { useScreen } from '@layers/client/react';
56
+ * import { useEffect } from 'react';
57
+ *
58
+ * function Dashboard() {
59
+ * const screen = useScreen();
60
+ * useEffect(() => { screen('dashboard'); }, [screen]);
61
+ * return <div>Dashboard</div>;
62
+ * }
63
+ * ```
64
+ */
65
+ declare function useScreen(): (screenName: string, properties?: EventProperties) => void;
66
+ /**
67
+ * Returns a stable, memoized identify function that sets the app user ID on the current LayersClient.
68
+ * Pass `undefined` to clear the current user ID.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * import { useIdentify } from '@layers/client/react';
73
+ *
74
+ * function LoginForm() {
75
+ * const identify = useIdentify();
76
+ * const onLogin = (userId: string) => identify(userId);
77
+ * const onLogout = () => identify(undefined);
78
+ * }
79
+ * ```
80
+ */
81
+ declare function useIdentify(): (userId: string | undefined) => void;
82
+ /**
83
+ * Returns stable, memoized consent functions bound to the current LayersClient.
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * import { useConsent } from '@layers/client/react';
88
+ *
89
+ * function ConsentBanner() {
90
+ * const { setConsent, getConsentState } = useConsent();
91
+ * return (
92
+ * <button onClick={() => setConsent({ analytics: true, advertising: false })}>
93
+ * Accept Analytics Only
94
+ * </button>
95
+ * );
96
+ * }
97
+ * ```
98
+ */
99
+ declare function useConsent(): {
100
+ setConsent: (consent: ConsentState) => void;
101
+ getConsentState: () => ConsentState;
102
+ };
103
+ //#endregion
104
+ export { LayersProvider, LayersProviderProps, useConsent, useIdentify, useLayers, useScreen, useTrack };
105
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","names":[],"sources":["../src/react.tsx"],"sourcesContent":[],"mappings":";;;;;UAQiB,mBAAA;UACP;EADO,QAAA,EAEL,SAFK;AAsBjB;;;;;;AA8BA;AAqBA;AAyBA;AAyBA;AA2BA;;;;;;;;iBAhIgB,cAAA;;;GAAqC,sBAAmB,kBAAA,CAAA,GAAA,CAAA;;;;;iBA8BxD,SAAA,CAAA,GAAa;;;;;;;;;;;;;;iBAqBb,QAAA,CAAA,oCAA6C;;;;;;;;;;;;;;;;iBAyB7C,SAAA,CAAA,qCAA+C;;;;;;;;;;;;;;;;iBAyB/C,WAAA,CAAA;;;;;;;;;;;;;;;;;;iBA2BA,UAAA,CAAA;wBACQ;yBACC"}
package/dist/react.js ADDED
@@ -0,0 +1,142 @@
1
+ import { t as LayersClient } from "./src-SyPoXjEy.js";
2
+ import { createContext, useCallback, useContext, useEffect, useRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/react.tsx
6
+ const LayersContext = createContext(null);
7
+ /**
8
+ * Provides a LayersClient instance to the React component tree.
9
+ * Initializes the client on mount and shuts it down on unmount.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { LayersProvider } from '@layers/client/react';
14
+ *
15
+ * function App() {
16
+ * return (
17
+ * <LayersProvider config={{ apiKey: 'key', appId: 'app', environment: 'production' }}>
18
+ * <MyApp />
19
+ * </LayersProvider>
20
+ * );
21
+ * }
22
+ * ```
23
+ */
24
+ function LayersProvider({ config, children }) {
25
+ const clientRef = useRef(null);
26
+ if (clientRef.current === null) clientRef.current = new LayersClient(config);
27
+ useEffect(() => {
28
+ const client = clientRef.current;
29
+ if (!client) return;
30
+ client.init();
31
+ return () => {
32
+ client.shutdown();
33
+ clientRef.current = null;
34
+ };
35
+ }, []);
36
+ return /* @__PURE__ */ jsx(LayersContext.Provider, {
37
+ value: clientRef.current,
38
+ children
39
+ });
40
+ }
41
+ /**
42
+ * Returns the LayersClient instance from the nearest LayersProvider.
43
+ * Throws if used outside a LayersProvider.
44
+ */
45
+ function useLayers() {
46
+ const client = useContext(LayersContext);
47
+ if (!client) throw new Error("useLayers must be used within a <LayersProvider>");
48
+ return client;
49
+ }
50
+ /**
51
+ * Returns a stable, memoized track function bound to the current LayersClient.
52
+ *
53
+ * @example
54
+ * ```tsx
55
+ * import { useTrack } from '@layers/client/react';
56
+ *
57
+ * function SignupButton() {
58
+ * const track = useTrack();
59
+ * return <button onClick={() => track('signup_click', { source: 'hero' })}>Sign up</button>;
60
+ * }
61
+ * ```
62
+ */
63
+ function useTrack() {
64
+ const client = useLayers();
65
+ return useCallback((eventName, properties) => {
66
+ client.track(eventName, properties);
67
+ }, [client]);
68
+ }
69
+ /**
70
+ * Returns a stable, memoized screen function bound to the current LayersClient.
71
+ *
72
+ * @example
73
+ * ```tsx
74
+ * import { useScreen } from '@layers/client/react';
75
+ * import { useEffect } from 'react';
76
+ *
77
+ * function Dashboard() {
78
+ * const screen = useScreen();
79
+ * useEffect(() => { screen('dashboard'); }, [screen]);
80
+ * return <div>Dashboard</div>;
81
+ * }
82
+ * ```
83
+ */
84
+ function useScreen() {
85
+ const client = useLayers();
86
+ return useCallback((screenName, properties) => {
87
+ client.screen(screenName, properties);
88
+ }, [client]);
89
+ }
90
+ /**
91
+ * Returns a stable, memoized identify function that sets the app user ID on the current LayersClient.
92
+ * Pass `undefined` to clear the current user ID.
93
+ *
94
+ * @example
95
+ * ```tsx
96
+ * import { useIdentify } from '@layers/client/react';
97
+ *
98
+ * function LoginForm() {
99
+ * const identify = useIdentify();
100
+ * const onLogin = (userId: string) => identify(userId);
101
+ * const onLogout = () => identify(undefined);
102
+ * }
103
+ * ```
104
+ */
105
+ function useIdentify() {
106
+ const client = useLayers();
107
+ return useCallback((userId) => {
108
+ client.setAppUserId(userId);
109
+ }, [client]);
110
+ }
111
+ /**
112
+ * Returns stable, memoized consent functions bound to the current LayersClient.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * import { useConsent } from '@layers/client/react';
117
+ *
118
+ * function ConsentBanner() {
119
+ * const { setConsent, getConsentState } = useConsent();
120
+ * return (
121
+ * <button onClick={() => setConsent({ analytics: true, advertising: false })}>
122
+ * Accept Analytics Only
123
+ * </button>
124
+ * );
125
+ * }
126
+ * ```
127
+ */
128
+ function useConsent() {
129
+ const client = useLayers();
130
+ return {
131
+ setConsent: useCallback((consent) => {
132
+ client.setConsent(consent);
133
+ }, [client]),
134
+ getConsentState: useCallback(() => {
135
+ return client.getConsentState();
136
+ }, [client])
137
+ };
138
+ }
139
+
140
+ //#endregion
141
+ export { LayersProvider, useConsent, useIdentify, useLayers, useScreen, useTrack };
142
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\n\nimport { LayersClient } from './index.js';\nimport type { ConsentState, EventProperties, LayersConfig } from './index.js';\n\nconst LayersContext = createContext<LayersClient | null>(null);\n\nexport interface LayersProviderProps {\n config: LayersConfig;\n children: ReactNode;\n}\n\n/**\n * Provides a LayersClient instance to the React component tree.\n * Initializes the client on mount and shuts it down on unmount.\n *\n * @example\n * ```tsx\n * import { LayersProvider } from '@layers/client/react';\n *\n * function App() {\n * return (\n * <LayersProvider config={{ apiKey: 'key', appId: 'app', environment: 'production' }}>\n * <MyApp />\n * </LayersProvider>\n * );\n * }\n * ```\n */\nexport function LayersProvider({ config, children }: LayersProviderProps) {\n const clientRef = useRef<LayersClient | null>(null);\n\n if (clientRef.current === null) {\n clientRef.current = new LayersClient(config);\n }\n\n useEffect(() => {\n const client = clientRef.current;\n if (!client) return;\n\n void client.init();\n\n return () => {\n client.shutdown();\n clientRef.current = null;\n };\n // Config is intentionally captured once at mount time via useRef above.\n // Re-creating the client on every config change would lose in-flight events\n // and session state, so we suppress the exhaustive-deps warning here.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return <LayersContext.Provider value={clientRef.current}>{children}</LayersContext.Provider>;\n}\n\n/**\n * Returns the LayersClient instance from the nearest LayersProvider.\n * Throws if used outside a LayersProvider.\n */\nexport function useLayers(): LayersClient {\n const client = useContext(LayersContext);\n if (!client) {\n throw new Error('useLayers must be used within a <LayersProvider>');\n }\n return client;\n}\n\n/**\n * Returns a stable, memoized track function bound to the current LayersClient.\n *\n * @example\n * ```tsx\n * import { useTrack } from '@layers/client/react';\n *\n * function SignupButton() {\n * const track = useTrack();\n * return <button onClick={() => track('signup_click', { source: 'hero' })}>Sign up</button>;\n * }\n * ```\n */\nexport function useTrack(): (eventName: string, properties?: EventProperties) => void {\n const client = useLayers();\n return useCallback(\n (eventName: string, properties?: EventProperties) => {\n void client.track(eventName, properties);\n },\n [client]\n );\n}\n\n/**\n * Returns a stable, memoized screen function bound to the current LayersClient.\n *\n * @example\n * ```tsx\n * import { useScreen } from '@layers/client/react';\n * import { useEffect } from 'react';\n *\n * function Dashboard() {\n * const screen = useScreen();\n * useEffect(() => { screen('dashboard'); }, [screen]);\n * return <div>Dashboard</div>;\n * }\n * ```\n */\nexport function useScreen(): (screenName: string, properties?: EventProperties) => void {\n const client = useLayers();\n return useCallback(\n (screenName: string, properties?: EventProperties) => {\n void client.screen(screenName, properties);\n },\n [client]\n );\n}\n\n/**\n * Returns a stable, memoized identify function that sets the app user ID on the current LayersClient.\n * Pass `undefined` to clear the current user ID.\n *\n * @example\n * ```tsx\n * import { useIdentify } from '@layers/client/react';\n *\n * function LoginForm() {\n * const identify = useIdentify();\n * const onLogin = (userId: string) => identify(userId);\n * const onLogout = () => identify(undefined);\n * }\n * ```\n */\nexport function useIdentify(): (userId: string | undefined) => void {\n const client = useLayers();\n return useCallback(\n (userId: string | undefined) => {\n client.setAppUserId(userId);\n },\n [client]\n );\n}\n\n/**\n * Returns stable, memoized consent functions bound to the current LayersClient.\n *\n * @example\n * ```tsx\n * import { useConsent } from '@layers/client/react';\n *\n * function ConsentBanner() {\n * const { setConsent, getConsentState } = useConsent();\n * return (\n * <button onClick={() => setConsent({ analytics: true, advertising: false })}>\n * Accept Analytics Only\n * </button>\n * );\n * }\n * ```\n */\nexport function useConsent(): {\n setConsent: (consent: ConsentState) => void;\n getConsentState: () => ConsentState;\n} {\n const client = useLayers();\n const setConsent = useCallback(\n (consent: ConsentState) => {\n client.setConsent(consent);\n },\n [client]\n );\n const getConsentState = useCallback(() => {\n return client.getConsentState();\n }, [client]);\n return { setConsent, getConsentState };\n}\n"],"mappings":";;;;;AAMA,MAAM,gBAAgB,cAAmC,KAAK;;;;;;;;;;;;;;;;;;AAwB9D,SAAgB,eAAe,EAAE,QAAQ,YAAiC;CACxE,MAAM,YAAY,OAA4B,KAAK;AAEnD,KAAI,UAAU,YAAY,KACxB,WAAU,UAAU,IAAI,aAAa,OAAO;AAG9C,iBAAgB;EACd,MAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAQ;AAEb,EAAK,OAAO,MAAM;AAElB,eAAa;AACX,UAAO,UAAU;AACjB,aAAU,UAAU;;IAMrB,EAAE,CAAC;AAEN,QAAO,oBAAC,cAAc;EAAS,OAAO,UAAU;EAAU;GAAkC;;;;;;AAO9F,SAAgB,YAA0B;CACxC,MAAM,SAAS,WAAW,cAAc;AACxC,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,mDAAmD;AAErE,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,WAAsE;CACpF,MAAM,SAAS,WAAW;AAC1B,QAAO,aACJ,WAAmB,eAAiC;AACnD,EAAK,OAAO,MAAM,WAAW,WAAW;IAE1C,CAAC,OAAO,CACT;;;;;;;;;;;;;;;;;AAkBH,SAAgB,YAAwE;CACtF,MAAM,SAAS,WAAW;AAC1B,QAAO,aACJ,YAAoB,eAAiC;AACpD,EAAK,OAAO,OAAO,YAAY,WAAW;IAE5C,CAAC,OAAO,CACT;;;;;;;;;;;;;;;;;AAkBH,SAAgB,cAAoD;CAClE,MAAM,SAAS,WAAW;AAC1B,QAAO,aACJ,WAA+B;AAC9B,SAAO,aAAa,OAAO;IAE7B,CAAC,OAAO,CACT;;;;;;;;;;;;;;;;;;;AAoBH,SAAgB,aAGd;CACA,MAAM,SAAS,WAAW;AAU1B,QAAO;EAAE,YATU,aAChB,YAA0B;AACzB,UAAO,WAAW,QAAQ;KAE5B,CAAC,OAAO,CACT;EAIoB,iBAHG,kBAAkB;AACxC,UAAO,OAAO,iBAAiB;KAC9B,CAAC,OAAO,CAAC;EAC0B"}
@@ -0,0 +1,399 @@
1
+ import { FetchHttpClient, LayersCore, LayersError as LayersError$1, createDefaultPersistence } from "@layers/core-wasm";
2
+
3
+ //#region src/attribution.ts
4
+ const CLICK_ID_PARAMS = [
5
+ "fbclid",
6
+ "gclid",
7
+ "gbraid",
8
+ "wbraid",
9
+ "ttclid",
10
+ "msclkid",
11
+ "rclid"
12
+ ];
13
+ const UTM_PARAMS = [
14
+ "utm_source",
15
+ "utm_medium",
16
+ "utm_campaign",
17
+ "utm_content",
18
+ "utm_term"
19
+ ];
20
+ const STORAGE_KEY = "layers_attribution";
21
+ const TTL_MS = 720 * 60 * 60 * 1e3;
22
+ /**
23
+ * Capture attribution signals from the current page URL and referrer.
24
+ * Persists to localStorage with a 30-day TTL. Click IDs take priority:
25
+ * if a new click ID is present, the entire record is overwritten.
26
+ */
27
+ function captureAttribution() {
28
+ if (typeof window === "undefined" || typeof localStorage === "undefined") return;
29
+ let params;
30
+ try {
31
+ params = new URLSearchParams(window.location.search);
32
+ } catch {
33
+ return;
34
+ }
35
+ let clickIdParam;
36
+ let clickIdValue;
37
+ for (const param of CLICK_ID_PARAMS) {
38
+ const value = params.get(param);
39
+ if (value) {
40
+ clickIdParam = param;
41
+ clickIdValue = value;
42
+ break;
43
+ }
44
+ }
45
+ const utms = {};
46
+ let hasUtm = false;
47
+ for (const param of UTM_PARAMS) {
48
+ const value = params.get(param);
49
+ if (value) {
50
+ utms[param] = value;
51
+ hasUtm = true;
52
+ }
53
+ }
54
+ const referrer = typeof document !== "undefined" && document.referrer ? document.referrer : void 0;
55
+ if (!clickIdParam && !hasUtm && !referrer) return;
56
+ const existing = getAttribution();
57
+ if (clickIdParam) {
58
+ writeAttribution({
59
+ click_id_param: clickIdParam,
60
+ ...clickIdValue != null && { click_id_value: clickIdValue },
61
+ ...utms,
62
+ ...referrer != null && { referrer_url: referrer },
63
+ captured_at: Date.now()
64
+ });
65
+ return;
66
+ }
67
+ if (hasUtm) {
68
+ writeAttribution({
69
+ ...existing?.click_id_param != null && { click_id_param: existing.click_id_param },
70
+ ...existing?.click_id_value != null && { click_id_value: existing.click_id_value },
71
+ ...utms,
72
+ ...referrer != null && { referrer_url: referrer },
73
+ captured_at: Date.now()
74
+ });
75
+ return;
76
+ }
77
+ if (!existing) writeAttribution({
78
+ ...referrer != null && { referrer_url: referrer },
79
+ captured_at: Date.now()
80
+ });
81
+ }
82
+ /**
83
+ * Read stored attribution data, returning null if missing or expired.
84
+ */
85
+ function getAttribution() {
86
+ if (typeof localStorage === "undefined") return null;
87
+ try {
88
+ const raw = localStorage.getItem(STORAGE_KEY);
89
+ if (!raw) return null;
90
+ const data = JSON.parse(raw);
91
+ if (Date.now() - data.captured_at > TTL_MS) {
92
+ localStorage.removeItem(STORAGE_KEY);
93
+ return null;
94
+ }
95
+ return data;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Return a flat property bag suitable for merging into event properties.
102
+ * Keys are prefixed with `$attribution_` to avoid collisions.
103
+ */
104
+ function getAttributionProperties() {
105
+ const data = getAttribution();
106
+ if (!data) return {};
107
+ const props = {};
108
+ if (data.click_id_param) props["$attribution_click_id_param"] = data.click_id_param;
109
+ if (data.click_id_value) props["$attribution_click_id_value"] = data.click_id_value;
110
+ if (data.utm_source) props["$attribution_utm_source"] = data.utm_source;
111
+ if (data.utm_medium) props["$attribution_utm_medium"] = data.utm_medium;
112
+ if (data.utm_campaign) props["$attribution_utm_campaign"] = data.utm_campaign;
113
+ if (data.utm_content) props["$attribution_utm_content"] = data.utm_content;
114
+ if (data.utm_term) props["$attribution_utm_term"] = data.utm_term;
115
+ if (data.referrer_url) props["$attribution_referrer_url"] = data.referrer_url;
116
+ return props;
117
+ }
118
+ function writeAttribution(data) {
119
+ try {
120
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
121
+ } catch {}
122
+ }
123
+
124
+ //#endregion
125
+ //#region src/index.ts
126
+ var LayersClient = class {
127
+ core;
128
+ appUserId;
129
+ isOnline = true;
130
+ enableDebug;
131
+ baseUrl;
132
+ onlineListener = null;
133
+ offlineListener = null;
134
+ visibilityListener = null;
135
+ beforeUnloadListener = null;
136
+ errorListeners = /* @__PURE__ */ new Set();
137
+ constructor(config) {
138
+ this.enableDebug = config.enableDebug ?? false;
139
+ this.baseUrl = (config.baseUrl ?? "https://in.layers.com").replace(/\/$/, "");
140
+ this.appUserId = config.appUserId;
141
+ const persistence = createDefaultPersistence(config.appId);
142
+ const httpClient = new FetchHttpClient();
143
+ this.core = LayersCore.init({
144
+ config: {
145
+ apiKey: config.apiKey,
146
+ appId: config.appId,
147
+ environment: config.environment,
148
+ ...config.baseUrl != null && { baseUrl: config.baseUrl },
149
+ ...config.enableDebug != null && { enableDebug: config.enableDebug },
150
+ ...config.flushIntervalMs != null && { flushIntervalMs: config.flushIntervalMs },
151
+ ...config.flushThreshold != null && { flushThreshold: config.flushThreshold },
152
+ ...config.maxQueueSize != null && { maxQueueSize: config.maxQueueSize },
153
+ sdkVersion: `client/${SDK_VERSION}`
154
+ },
155
+ httpClient,
156
+ persistence
157
+ });
158
+ if (this.appUserId) this.core.identify(this.appUserId);
159
+ }
160
+ /** Initialize the client: detects device info, attaches lifecycle listeners, and fetches remote config. */
161
+ async init() {
162
+ this.initializeDeviceInfo();
163
+ this.setupNetworkListener();
164
+ this.setupLifecycleListeners();
165
+ captureAttribution();
166
+ await this.core.fetchRemoteConfig().catch(() => {});
167
+ }
168
+ /**
169
+ * Record a custom analytics event with an optional property bag.
170
+ *
171
+ * Events are batched and flushed automatically when the queue reaches
172
+ * `flushThreshold` or the periodic flush timer fires.
173
+ */
174
+ track(eventName, properties) {
175
+ if (this.enableDebug) console.log(`[Layers] track("${eventName}", ${Object.keys(properties ?? {}).length} properties)`);
176
+ try {
177
+ const merged = {
178
+ ...getAttributionProperties(),
179
+ ...properties
180
+ };
181
+ this.core.track(eventName, merged, this.appUserId);
182
+ } catch (e) {
183
+ this.emitError(e);
184
+ }
185
+ }
186
+ /**
187
+ * Record a screen view event with an optional property bag.
188
+ *
189
+ * Events are batched and flushed automatically when the queue reaches
190
+ * `flushThreshold` or the periodic flush timer fires.
191
+ */
192
+ screen(screenName, properties) {
193
+ if (this.enableDebug) console.log(`[Layers] screen("${screenName}", ${Object.keys(properties ?? {}).length} properties)`);
194
+ try {
195
+ const merged = {
196
+ ...getAttributionProperties(),
197
+ ...properties
198
+ };
199
+ this.core.screen(screenName, merged, this.appUserId);
200
+ } catch (e) {
201
+ this.emitError(e);
202
+ }
203
+ }
204
+ /** Set or update user-level properties that persist across sessions. */
205
+ setUserProperties(properties) {
206
+ this.core.setUserProperties(properties);
207
+ }
208
+ /** Update the user's consent state for analytics and advertising data collection. */
209
+ setConsent(consent) {
210
+ this.core.setConsent(consent);
211
+ }
212
+ /** Associate all subsequent events with the given user ID, or clear it with `undefined`. */
213
+ setAppUserId(appUserId) {
214
+ if (this.enableDebug) console.log(`[Layers] setAppUserId(${appUserId ? `"${appUserId}"` : "undefined"})`);
215
+ this.appUserId = appUserId;
216
+ if (appUserId) this.core.identify(appUserId);
217
+ else this.core.identify("");
218
+ }
219
+ /** @deprecated Use setAppUserId instead */
220
+ setUserId(userId) {
221
+ this.setAppUserId(userId);
222
+ }
223
+ /** Return the current app user ID, or `undefined` if not set. */
224
+ getAppUserId() {
225
+ return this.appUserId;
226
+ }
227
+ /** @deprecated Use getAppUserId instead */
228
+ getUserId() {
229
+ return this.appUserId;
230
+ }
231
+ /** Return the current anonymous session ID. */
232
+ getSessionId() {
233
+ return this.core.getSessionId();
234
+ }
235
+ /** Return the current consent state for analytics and advertising. */
236
+ getConsentState() {
237
+ return this.core.getConsentState();
238
+ }
239
+ /** Override device-level context fields (platform, OS, locale, etc.). */
240
+ setDeviceInfo(deviceInfo) {
241
+ this.core.setDeviceContext(deviceInfo);
242
+ }
243
+ /** Flush all queued events to the server. Falls back to synchronous persistence on failure. */
244
+ async flush() {
245
+ try {
246
+ await this.core.flushAsync();
247
+ } catch (e) {
248
+ this.emitError(e);
249
+ this.core.flush();
250
+ }
251
+ }
252
+ /** Immediately shut down the client, removing all event listeners. Queued events are persisted but not flushed. */
253
+ shutdown() {
254
+ this.cleanupListeners();
255
+ this.core.shutdown();
256
+ }
257
+ /**
258
+ * Async shutdown: flushes remaining events before shutting down.
259
+ * @param timeoutMs Maximum time to wait for flush (default 3000ms).
260
+ */
261
+ async shutdownAsync(timeoutMs = 3e3) {
262
+ this.cleanupListeners();
263
+ try {
264
+ await Promise.race([this.core.flushAsync(), new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
265
+ } catch {}
266
+ this.core.shutdown();
267
+ }
268
+ cleanupListeners() {
269
+ if (typeof window !== "undefined") {
270
+ if (this.onlineListener) {
271
+ window.removeEventListener("online", this.onlineListener);
272
+ this.onlineListener = null;
273
+ }
274
+ if (this.offlineListener) {
275
+ window.removeEventListener("offline", this.offlineListener);
276
+ this.offlineListener = null;
277
+ }
278
+ if (this.beforeUnloadListener) {
279
+ window.removeEventListener("beforeunload", this.beforeUnloadListener);
280
+ this.beforeUnloadListener = null;
281
+ }
282
+ }
283
+ if (typeof document !== "undefined" && this.visibilityListener) {
284
+ document.removeEventListener("visibilitychange", this.visibilityListener);
285
+ this.visibilityListener = null;
286
+ }
287
+ }
288
+ /** End the current session and start a new one with a fresh session ID. */
289
+ startNewSession() {
290
+ this.core.startNewSession();
291
+ }
292
+ /**
293
+ * Register an error listener. Errors from track/screen/flush
294
+ * that would otherwise be silently dropped are forwarded here.
295
+ */
296
+ on(event, listener) {
297
+ if (event === "error") this.errorListeners.add(listener);
298
+ return this;
299
+ }
300
+ /**
301
+ * Remove a previously registered error listener.
302
+ */
303
+ off(event, listener) {
304
+ if (event === "error") this.errorListeners.delete(listener);
305
+ return this;
306
+ }
307
+ emitError(error) {
308
+ const err = error instanceof Error ? error : new Error(String(error));
309
+ for (const listener of this.errorListeners) try {
310
+ listener(err);
311
+ } catch {}
312
+ if (this.enableDebug && this.errorListeners.size === 0) console.warn("[Layers]", err.message);
313
+ }
314
+ initializeDeviceInfo() {
315
+ const context = {
316
+ platform: "web",
317
+ osVersion: this.detectOS(),
318
+ appVersion: SDK_VERSION,
319
+ deviceModel: this.detectDeviceModel(),
320
+ locale: this.detectLocale(),
321
+ screenSize: this.detectScreenSize()
322
+ };
323
+ this.core.setDeviceContext(context);
324
+ }
325
+ detectOS() {
326
+ if (typeof navigator === "undefined") return "unknown";
327
+ const ua = navigator.userAgent;
328
+ if (ua.includes("Windows")) return "Windows";
329
+ if (ua.includes("Mac OS")) return "macOS";
330
+ if (ua.includes("Linux")) return "Linux";
331
+ if (ua.includes("Android")) return "Android";
332
+ if (ua.includes("iPhone") || ua.includes("iPad")) return "iOS";
333
+ return "unknown";
334
+ }
335
+ detectDeviceModel() {
336
+ if (typeof navigator === "undefined") return "unknown";
337
+ if ("userAgentData" in navigator) {
338
+ const uaData = navigator.userAgentData;
339
+ if (uaData?.platform) return uaData.platform;
340
+ }
341
+ return navigator.platform ?? "unknown";
342
+ }
343
+ detectLocale() {
344
+ if (typeof navigator === "undefined") return "en-US";
345
+ return navigator.language ?? "en-US";
346
+ }
347
+ detectScreenSize() {
348
+ if (typeof window === "undefined" || typeof screen === "undefined") return "unknown";
349
+ return `${screen.width}x${screen.height}`;
350
+ }
351
+ setupNetworkListener() {
352
+ if (typeof window === "undefined") return;
353
+ this.isOnline = navigator?.onLine ?? true;
354
+ this.onlineListener = () => {
355
+ this.isOnline = true;
356
+ this.core.flushAsync().catch(() => {});
357
+ };
358
+ this.offlineListener = () => {
359
+ this.isOnline = false;
360
+ };
361
+ window.addEventListener("online", this.onlineListener);
362
+ window.addEventListener("offline", this.offlineListener);
363
+ }
364
+ getBeaconUrl() {
365
+ return `${this.baseUrl}/events`;
366
+ }
367
+ setupLifecycleListeners() {
368
+ if (typeof window === "undefined" || typeof document === "undefined") return;
369
+ this.visibilityListener = () => {
370
+ if (document.visibilityState === "hidden") {
371
+ if (typeof navigator !== "undefined" && navigator.sendBeacon && this.core.queueDepth() > 0) try {
372
+ const batch = this.core.createBeaconPayload();
373
+ if (batch) {
374
+ if (navigator.sendBeacon(this.getBeaconUrl(), batch)) {
375
+ this.core.clearBeaconEvents();
376
+ return;
377
+ }
378
+ this.core.requeueBeaconEvents();
379
+ this.core.flush();
380
+ return;
381
+ }
382
+ } catch {
383
+ this.core.requeueBeaconEvents();
384
+ }
385
+ this.core.flush();
386
+ }
387
+ };
388
+ document.addEventListener("visibilitychange", this.visibilityListener);
389
+ this.beforeUnloadListener = () => {
390
+ this.core.flush();
391
+ };
392
+ window.addEventListener("beforeunload", this.beforeUnloadListener);
393
+ }
394
+ };
395
+ const SDK_VERSION = typeof __LAYERS_CLIENT_VERSION__ !== "undefined" ? __LAYERS_CLIENT_VERSION__ : "0.1.1-alpha.1";
396
+
397
+ //#endregion
398
+ export { getAttributionProperties as i, LayersError$1 as n, getAttribution as r, LayersClient as t };
399
+ //# sourceMappingURL=src-SyPoXjEy.js.map