@layers/client 0.1.1-alpha.1 → 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.
- package/README.md +200 -12
- package/dist/index-C1d8NEFV.d.ts +306 -0
- package/dist/index-C1d8NEFV.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -616
- package/dist/react.d.ts +105 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +142 -0
- package/dist/react.js.map +1 -0
- package/dist/src-SyPoXjEy.js +399 -0
- package/dist/src-SyPoXjEy.js.map +1 -0
- package/package.json +32 -8
- package/dist/index-NMBS3y1V.d.ts +0 -372
- package/dist/index-NMBS3y1V.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/react.d.ts
ADDED
|
@@ -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
|