@mosovn/echo 0.0.0 → 0.2.1
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 +115 -34
- package/dist/native.d.ts +108 -0
- package/dist/native.js +2 -0
- package/dist/{index.cjs → web.cjs} +1 -1
- package/dist/{index.d.cts → web.d.cts} +38 -18
- package/dist/{index.d.ts → web.d.ts} +38 -18
- package/dist/{index.js → web.js} +1 -1
- package/package.json +34 -9
package/README.md
CHANGED
|
@@ -1,55 +1,52 @@
|
|
|
1
1
|
# @mosovn/echo
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
self-contained — works with any Echo-compatible backend.
|
|
3
|
+
SDK for the Echo event-tracking system. **Works in both browser and React Native** — bundler auto-picks the right implementation via conditional exports.
|
|
5
4
|
|
|
6
5
|
```bash
|
|
7
|
-
|
|
6
|
+
# Web (Next.js / Vite / Webpack / etc.)
|
|
7
|
+
npm install @mosovn/echo
|
|
8
|
+
|
|
9
|
+
# React Native — also install the platform polyfills
|
|
10
|
+
npm install @mosovn/echo \
|
|
11
|
+
@react-native-async-storage/async-storage \
|
|
12
|
+
react-native-get-random-values
|
|
13
|
+
|
|
14
|
+
# iOS only
|
|
15
|
+
cd ios && pod install && cd ..
|
|
8
16
|
```
|
|
9
17
|
|
|
18
|
+
## Web usage
|
|
19
|
+
|
|
10
20
|
```ts
|
|
11
21
|
import { createEcho } from '@mosovn/echo';
|
|
12
22
|
|
|
13
23
|
const echo = createEcho({
|
|
14
24
|
writeKey: 'pk_xxxxxxxxxxxx',
|
|
15
|
-
endpoint: 'https://events.moso.vn', // optional, this is the default
|
|
16
25
|
});
|
|
17
26
|
|
|
18
|
-
// Custom events
|
|
19
27
|
echo.track('Sign Up', { plan: 'pro' });
|
|
20
|
-
|
|
21
|
-
// Identify a logged-in user
|
|
22
28
|
echo.identify('user_123', { email: 'foo@moso.vn' });
|
|
23
|
-
|
|
24
|
-
// Update traits later
|
|
25
|
-
echo.setUserProperties({ company: 'moso' });
|
|
26
|
-
|
|
27
|
-
// Logout
|
|
28
|
-
echo.reset();
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
### Web autocapture (default ON)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- `$
|
|
36
|
-
- `$
|
|
37
|
-
- `$form_submit` — form submissions (never field values)
|
|
38
|
-
- `$scroll` — scroll depth, throttled to 250 ms + ≥10% delta
|
|
33
|
+
- `$pageview` — page load + SPA navigation (pushState/popstate/hashchange)
|
|
34
|
+
- `$click` — every click; use `data-no-track` to exclude
|
|
35
|
+
- `$form_submit` — form submit (never captures field values)
|
|
36
|
+
- `$scroll` — throttled to 250 ms + ≥10% delta
|
|
39
37
|
- `$visibility` — tab focus/blur
|
|
40
|
-
- `$hover` — mouseenter on
|
|
41
|
-
|
|
42
|
-
- `$error` — `window.onerror` + `unhandledrejection`, deduped 5 s
|
|
38
|
+
- `$hover` — mouseenter on a/button/[id]/[role=button], 500 ms throttle
|
|
39
|
+
- `$error` — `window.onerror` + `unhandledrejection`, 5 s dedup
|
|
43
40
|
- `$web_vital` — LCP, FID, CLS
|
|
44
41
|
- `$session_start` / `$session_end` — 30 min inactivity timeout
|
|
45
42
|
|
|
46
|
-
Configure or disable
|
|
43
|
+
Configure or disable:
|
|
47
44
|
|
|
48
45
|
```ts
|
|
49
46
|
createEcho({
|
|
50
47
|
writeKey: 'pk_xxx',
|
|
51
48
|
autoCapture: {
|
|
52
|
-
clicks: 'instrumented',
|
|
49
|
+
clicks: 'instrumented', // only a/button/[id]/[role=button]
|
|
53
50
|
scroll: false,
|
|
54
51
|
},
|
|
55
52
|
maskTextSelectors: ['input', '[data-private]'],
|
|
@@ -57,22 +54,106 @@ createEcho({
|
|
|
57
54
|
});
|
|
58
55
|
```
|
|
59
56
|
|
|
57
|
+
## React Native usage
|
|
58
|
+
|
|
59
|
+
In your app entry (`index.js` or `App.tsx`), import the polyfill **FIRST**:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// index.js — TOP OF FILE
|
|
63
|
+
import 'react-native-get-random-values';
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then anywhere:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { createEcho } from '@mosovn/echo';
|
|
70
|
+
|
|
71
|
+
const echo = createEcho({
|
|
72
|
+
writeKey: 'pk_xxxxxxxxxxxx',
|
|
73
|
+
app: {
|
|
74
|
+
name: 'MOSO Mobile',
|
|
75
|
+
version: '2.4.1',
|
|
76
|
+
build: '124',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
echo.track('Sign Up', { plan: 'pro' });
|
|
81
|
+
echo.identify('user_123', { email: 'foo@moso.vn' });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### RN autocapture
|
|
85
|
+
|
|
86
|
+
Limited (no DOM in RN):
|
|
87
|
+
|
|
88
|
+
- `$session_start` / `$session_end` — `AppState` foreground/background + 30 min inactivity
|
|
89
|
+
- `$error` — `ErrorUtils.setGlobalHandler` + `unhandledrejection`
|
|
90
|
+
|
|
91
|
+
You must call manually:
|
|
92
|
+
|
|
93
|
+
- Screen views — hook into React Navigation:
|
|
94
|
+
```tsx
|
|
95
|
+
<NavigationContainer
|
|
96
|
+
ref={navRef}
|
|
97
|
+
onStateChange={() => {
|
|
98
|
+
const r = navRef.getCurrentRoute();
|
|
99
|
+
if (r) echo.screen?.(r.name);
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
```
|
|
103
|
+
- Button taps, form submits, network errors — call `echo.track()` in your handlers
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
| Method | Web | RN | Notes |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `createEcho(config)` | ✅ | ✅ | Returns `EchoClient` |
|
|
110
|
+
| `echo.track(name, props?)` | ✅ | ✅ | Custom event |
|
|
111
|
+
| `echo.identify(userId, traits?)` | ✅ | ✅ | Set user identity |
|
|
112
|
+
| `echo.setUserProperties(traits)` | ✅ | ✅ | Update traits |
|
|
113
|
+
| `echo.page(props?)` | ✅ | n/a | Web only — URL-based |
|
|
114
|
+
| `echo.screen(name, props?)` | n/a | ✅ | RN only — call from navigation |
|
|
115
|
+
| `echo.reset()` | sync | async | Logout; rotates anonymousId |
|
|
116
|
+
| `echo.flush()` | ✅ | ✅ | Force send queued events |
|
|
117
|
+
| `echo.getAnonymousId()` | ✅ | ✅ | |
|
|
118
|
+
| `echo.getUserId()` | ✅ | ✅ | |
|
|
119
|
+
| `echo.getSessionId()` | ✅ | ✅ | |
|
|
120
|
+
|
|
121
|
+
## How bundler picks the right entry
|
|
122
|
+
|
|
123
|
+
`@mosovn/echo` ships two builds in `dist/`:
|
|
124
|
+
|
|
125
|
+
- `dist/web.js` — uses `localStorage`, `document`, `window`, `MutationObserver`, `PerformanceObserver`
|
|
126
|
+
- `dist/native.js` — uses `AsyncStorage`, `AppState`, `ErrorUtils`, `react-native-get-random-values`
|
|
127
|
+
|
|
128
|
+
`package.json` `exports` field routes:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"exports": {
|
|
133
|
+
".": {
|
|
134
|
+
"react-native": "./dist/native.js", // ← Metro picks this
|
|
135
|
+
"browser": "./dist/web.js", // ← Webpack/Vite picks this
|
|
136
|
+
"import": "./dist/web.js", // ← ESM default
|
|
137
|
+
"require": "./dist/web.cjs" // ← CJS default
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"react-native": "./dist/native.js" // ← older Metro fallback
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Same `import { createEcho } from '@mosovn/echo'` in your code works in both environments.
|
|
145
|
+
|
|
60
146
|
## PII
|
|
61
147
|
|
|
62
|
-
Form field values are NEVER captured. Text inside `<input>`, `<textarea>`,
|
|
63
|
-
`<select>`, and `[data-private]` is masked from autocaptured events by default.
|
|
148
|
+
Form field values are NEVER captured. Text inside `<input>`, `<textarea>`, `<select>`, and `[data-private]` is masked from autocaptured events by default.
|
|
64
149
|
|
|
65
|
-
|
|
66
|
-
descendants) from all autocapture.
|
|
150
|
+
`data-no-track` attribute on any element excludes it (and its descendants) from all autocapture.
|
|
67
151
|
|
|
68
152
|
## Identity
|
|
69
153
|
|
|
70
|
-
- **anonymous_id** —
|
|
71
|
-
on the apex domain so subdomains share it.
|
|
154
|
+
- **anonymous_id** — UUIDv7, persistent across sessions. Web: localStorage + cookie on apex domain. RN: AsyncStorage.
|
|
72
155
|
- **user_id** — set by `identify()`, cleared by `reset()`.
|
|
73
|
-
- **session_id** — rotates after 30 min
|
|
74
|
-
|
|
75
|
-
`echo.reset()` rotates the anonymous_id too, so use it on logout.
|
|
156
|
+
- **session_id** — rotates after 30 min inactivity. Web: per-tab. RN: per-app.
|
|
76
157
|
|
|
77
158
|
## License
|
|
78
159
|
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for both `@mosovn/echo` entries (web + native).
|
|
3
|
+
* Platform-specific options are tagged in JSDoc; setting a web-only option in
|
|
4
|
+
* RN (or vice versa) is silently ignored, not an error.
|
|
5
|
+
*/
|
|
6
|
+
interface EchoConfig {
|
|
7
|
+
/** Project's public write key (e.g. `pk_xxx`). */
|
|
8
|
+
writeKey: string;
|
|
9
|
+
/** API endpoint base, defaults to `https://events.moso.vn`. */
|
|
10
|
+
endpoint?: string;
|
|
11
|
+
/** Number of events to buffer before forcing a flush. Default 50. */
|
|
12
|
+
flushAt?: number;
|
|
13
|
+
/** Max time (ms) to wait before flushing the buffer. Default 10000. */
|
|
14
|
+
flushInterval?: number;
|
|
15
|
+
/** Optional middleware called on every event before queueing. Return `null` to drop. */
|
|
16
|
+
beforeSend?: (event: EchoEvent) => EchoEvent | null;
|
|
17
|
+
/** Inactivity timeout (ms) to end a session. Default 30 min. */
|
|
18
|
+
sessionTimeoutMs?: number;
|
|
19
|
+
/** If true, log internal diagnostic info to console. */
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
/** [Web only] Where to persist the queue. Default 'localstorage'. */
|
|
22
|
+
storage?: 'indexeddb' | 'localstorage' | 'memory';
|
|
23
|
+
/** [Web only] Autocapture configuration (DOM-based). See AutoCaptureConfig. */
|
|
24
|
+
autoCapture?: AutoCaptureConfig | boolean;
|
|
25
|
+
/** [Web only] CSS selectors whose text content is masked from captured events. */
|
|
26
|
+
maskTextSelectors?: string[];
|
|
27
|
+
/** [Web only] CSS selectors to skip during autocapture. */
|
|
28
|
+
blockSelectors?: string[];
|
|
29
|
+
/** [Native only] App info attached to event context. */
|
|
30
|
+
app?: {
|
|
31
|
+
name?: string;
|
|
32
|
+
version?: string;
|
|
33
|
+
build?: string;
|
|
34
|
+
};
|
|
35
|
+
/** [Native only] Track AppState transitions as $session_start/end. Default true. */
|
|
36
|
+
trackSessions?: boolean;
|
|
37
|
+
/** [Native only] Track JS errors via ErrorUtils + unhandledrejection. Default true. */
|
|
38
|
+
trackErrors?: boolean;
|
|
39
|
+
}
|
|
40
|
+
interface AutoCaptureConfig {
|
|
41
|
+
pageviews?: boolean;
|
|
42
|
+
/** 'instrumented' = a/button/[role=button]/[id]. 'all' = every click. */
|
|
43
|
+
clicks?: boolean | 'instrumented' | 'all';
|
|
44
|
+
forms?: boolean;
|
|
45
|
+
scroll?: boolean;
|
|
46
|
+
visibility?: boolean;
|
|
47
|
+
mouseover?: boolean;
|
|
48
|
+
errors?: boolean;
|
|
49
|
+
vitals?: boolean;
|
|
50
|
+
sessions?: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface EchoEvent {
|
|
53
|
+
/** UUID v7. Client-generated. Doubles as dedup key on the server. */
|
|
54
|
+
_id: string;
|
|
55
|
+
eventName: string;
|
|
56
|
+
eventType: 'auto' | 'custom';
|
|
57
|
+
anonymousId: string;
|
|
58
|
+
userId?: string | null;
|
|
59
|
+
timestamp: string;
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
properties?: Record<string, unknown>;
|
|
62
|
+
context?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
interface EchoClient {
|
|
65
|
+
/** Track a custom event. */
|
|
66
|
+
track(eventName: string, properties?: Record<string, unknown>): void;
|
|
67
|
+
/** Identify the current user. Call after login. */
|
|
68
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
69
|
+
/** Update user traits. */
|
|
70
|
+
setUserProperties(traits: Record<string, unknown>): void;
|
|
71
|
+
/** [Web only] Record a page view (URL-based). No-op on native. */
|
|
72
|
+
page?(properties?: Record<string, unknown>): void;
|
|
73
|
+
/** [Native only] Record a screen view. No-op on web. */
|
|
74
|
+
screen?(screenName: string, properties?: Record<string, unknown>): void;
|
|
75
|
+
/** Clear identity (call on logout). Rotates anonymousId. */
|
|
76
|
+
reset(): void | Promise<void>;
|
|
77
|
+
/** Force flush queued events now. */
|
|
78
|
+
flush(): Promise<void>;
|
|
79
|
+
getAnonymousId(): string;
|
|
80
|
+
getUserId(): string | null;
|
|
81
|
+
getSessionId(): string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @mosovn/echo — React Native entry.
|
|
86
|
+
*
|
|
87
|
+
* Metro picks this file via the `react-native` field in `package.json`.
|
|
88
|
+
* Web bundlers (Webpack / Vite / Next.js) load `./web.ts` instead.
|
|
89
|
+
*
|
|
90
|
+
* Usage:
|
|
91
|
+
*
|
|
92
|
+
* // In your app entry — FIRST line, before other imports:
|
|
93
|
+
* import 'react-native-get-random-values';
|
|
94
|
+
*
|
|
95
|
+
* import { createEcho } from '@mosovn/echo';
|
|
96
|
+
*
|
|
97
|
+
* const echo = createEcho({
|
|
98
|
+
* writeKey: 'pk_xxx',
|
|
99
|
+
* app: { name: 'MOSO Mobile', version: '2.4.1' },
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* echo.track('Sign Up', { plan: 'pro' });
|
|
103
|
+
* echo.identify('user_123', { email: 'foo@moso.vn' });
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
declare function createEcho(config: EchoConfig): EchoClient;
|
|
107
|
+
|
|
108
|
+
export { type AutoCaptureConfig, type EchoClient, type EchoConfig, type EchoEvent, createEcho };
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {Platform,AppState}from'react-native';import E from'@react-native-async-storage/async-storage';var f=new Map;async function w(s){try{let t=await E.multiGet(s);for(let[e,i]of t)i!==null&&f.set(e,i);}catch{}}function u(s){return f.get(s)??null}function r(s,t){f.set(s,t),E.setItem(s,t).catch(()=>{});}function l(s){f.delete(s),E.removeItem(s).catch(()=>{});}function a(){let s=BigInt(Date.now()),t=new Uint8Array(10);crypto.getRandomValues(t);let e=new Uint8Array(16);e[0]=Number(s>>40n&0xffn),e[1]=Number(s>>32n&0xffn),e[2]=Number(s>>24n&0xffn),e[3]=Number(s>>16n&0xffn),e[4]=Number(s>>8n&0xffn),e[5]=Number(s&0xffn),e[6]=t[0]&15|112,e[7]=t[1],e[8]=t[2]&63|128,e[9]=t[3],e[10]=t[4],e[11]=t[5],e[12]=t[6],e[13]=t[7],e[14]=t[8],e[15]=t[9];let i=Array.from(e,n=>n.toString(16).padStart(2,"0")).join("");return `${i.slice(0,8)}-${i.slice(8,12)}-${i.slice(12,16)}-${i.slice(16,20)}-${i.slice(20)}`}var g="echo_anonymous_id",p="echo_user_id",h="echo_session_id",d="echo_last_activity",b=[g,p,h,d],m=class{constructor(t){this.sessionTimeoutMs=t;let e=u(g);e||(e=a(),r(g,e)),this.anonymousId=e,this.userId=u(p);let i=Date.now(),n=u(h),o=parseInt(u(d)||"0",10);n&&i-o<t?this.sessionId=n:(this.sessionId=a(),r(h,this.sessionId)),this.lastActivity=i,r(d,String(i));}touch(){let t=Date.now(),e=t-this.lastActivity>=this.sessionTimeoutMs,i;return e&&(i=this.sessionId,this.sessionId=a(),r(h,this.sessionId)),this.lastActivity=t,r(d,String(t)),{sessionStarted:e,oldSessionId:i}}setUserId(t){this.userId=t,r(p,t);}reset(){this.userId=null,l(p),this.anonymousId=a(),r(g,this.anonymousId),this.sessionId=a(),r(h,this.sessionId),this.lastActivity=Date.now(),r(d,String(this.lastActivity));}getAnonymousId(){return this.anonymousId}getUserId(){return this.userId}getSessionId(){return this.sessionId}};var I="echo_queue_v1",_=1e3,y=class{constructor(){this.buffer=[];}restore(){try{let t=u(I);t&&(this.buffer=JSON.parse(t));}catch{this.buffer=[];}}persist(){try{r(I,JSON.stringify(this.buffer));}catch{}}enqueue(t){this.buffer.length>=_&&this.buffer.shift(),this.buffer.push(t),this.persist();}size(){return this.buffer.length}drain(){let t=this.buffer;return this.buffer=[],l(I),t}requeue(t){this.buffer=t.concat(this.buffer).slice(0,_),this.persist();}};async function A(s,t){if(s.length===0)return true;let e=t.endpoint.replace(/\/+$/,"")+"/v1/events";try{return (await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":t.writeKey},body:JSON.stringify({events:s})})).ok}catch{return false}}async function k(s,t){let e=t.endpoint.replace(/\/+$/,"")+"/v1/identify";try{return (await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":t.writeKey},body:JSON.stringify(s)})).ok}catch{return false}}var U="https://events.moso.vn",q=50,x=1e4,K=1800*1e3,C=["echo_queue_v1"],v=class{constructor(t){this.flushTimer=null;this.flushing=false;this.ready=false;if(!t.writeKey)throw new Error("@mosovn/echo-rn: writeKey is required");this.config={writeKey:t.writeKey,endpoint:t.endpoint??U,flushAt:t.flushAt??q,flushInterval:t.flushInterval??x,sessionTimeoutMs:t.sessionTimeoutMs??K,debug:t.debug??false,trackSessions:t.trackSessions??true,trackErrors:t.trackErrors??true,...t},this.init();}debugLog(...t){this.config.debug&&console.log("[echo-rn]",...t);}async init(){await w([...b,...C]),this.identity=new m(this.config.sessionTimeoutMs),this.queue=new y,this.queue.restore(),this.ready=true,this.debugLog("init complete",{anon:this.identity.getAnonymousId(),session:this.identity.getSessionId()}),this.startFlushTimer(),this.config.trackSessions&&this.installAppStateTracking(),this.config.trackErrors&&this.installErrorTracking();}track(t,e){this.enqueueEvent(t,"custom",e);}identify(t,e){if(!this.ready){this.waitReady().then(()=>this.identify(t,e));return}this.identity.setUserId(t),this.enqueueEvent("$identify","auto",e),k({userId:t,anonymousId:this.identity.getAnonymousId(),traits:e||{}},{writeKey:this.config.writeKey,endpoint:this.config.endpoint});}setUserProperties(t){if(!this.ready){this.waitReady().then(()=>this.setUserProperties(t));return}let e=this.identity.getUserId();e?this.identify(e,t):this.enqueueEvent("$set_user_properties","auto",t);}screen(t,e){this.enqueueEvent("$screen","auto",{name:t,...e});}async reset(){await this.waitReady(),this.identity.reset(),this.queue.drain(),this.debugLog("reset done");}async flush(){if(await this.waitReady(),this.flushing||this.queue.size()===0)return;this.flushing=true;let t=this.queue.drain();await A(t,{writeKey:this.config.writeKey,endpoint:this.config.endpoint})?this.debugLog("flush ok",t.length):(this.queue.requeue(t),this.debugLog("flush failed, requeued",t.length)),this.flushing=false;}getAnonymousId(){return this.ready?this.identity.getAnonymousId():""}getUserId(){return this.ready?this.identity.getUserId():null}getSessionId(){return this.ready?this.identity.getSessionId():""}waitReady(){return this.ready?Promise.resolve():new Promise(t=>{let e=()=>this.ready?t():setTimeout(e,50);e();})}enqueueEvent(t,e,i){if(!this.ready){this.waitReady().then(()=>this.enqueueEvent(t,e,i));return}let{sessionStarted:n,oldSessionId:o}=this.identity.touch();n&&o&&(this.pushRaw("$session_end","auto",void 0,o),this.pushRaw("$session_start","auto")),this.pushRaw(t,e,i);}pushRaw(t,e,i,n){let o={_id:a(),eventName:t,eventType:e,anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:n??this.identity.getSessionId(),properties:i,context:this.collectContext()},S=this.config.beforeSend,c=S?S(o):o;c&&(this.queue.enqueue(c),this.debugLog("enqueue",c.eventName,c._id),this.queue.size()>=this.config.flushAt&&this.flush());}collectContext(){let t={lib:{name:"@mosovn/echo",version:"0.2.0"},os:{name:Platform.OS,version:String(Platform.Version)}};return this.config.app&&(t.app=this.config.app),t}startFlushTimer(){this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval);}installAppStateTracking(){let t=AppState.currentState;this.appStateSub=AppState.addEventListener("change",e=>{e!==t&&(this.debugLog("appstate",t,"\u2192",e),(e==="background"||e==="inactive")&&this.flush(),t=e);});}installErrorTracking(){let t=globalThis.ErrorUtils;if(t){let i=t.getGlobalHandler();t.setGlobalHandler((n,o)=>{this.track("$error",{message:n?.message,stack:n?.stack,fatal:!!o}),i?.(n,o);});}let e=i=>{let n=i?.reason;this.track("$error",{kind:"unhandledrejection",message:n instanceof Error?n.message:String(n),stack:n instanceof Error?n.stack:void 0});};globalThis.addEventListener?.("unhandledrejection",e);}};function X(s){return new v(s)}
|
|
2
|
+
export{X as createEcho};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';function a(){let i=BigInt(Date.now()),e=crypto.getRandomValues(new Uint8Array(10)),t=new Uint8Array(16);t[0]=Number(i>>40n&0xffn),t[1]=Number(i>>32n&0xffn),t[2]=Number(i>>24n&0xffn),t[3]=Number(i>>16n&0xffn),t[4]=Number(i>>8n&0xffn),t[5]=Number(i&0xffn),t[6]=e[0]&15|112,t[7]=e[1],t[8]=e[2]&63|128,t[9]=e[3],t[10]=e[4],t[11]=e[5],t[12]=e[6],t[13]=e[7],t[14]=e[8],t[15]=e[9];let n=Array.from(t,r=>r.toString(16).padStart(2,"0")).join("");return `${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20)}`}var f="echo_anonymous_id",w="echo_user_id",d="echo_session_id",h="echo_last_activity";function O(){try{return typeof localStorage>"u"?null:(localStorage.setItem("__echo_probe__","1"),localStorage.removeItem("__echo_probe__"),localStorage)}catch{return null}}function D(){if(typeof location>"u")return null;let i=location.hostname;if(i==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(i))return null;let e=i.split(".");return e.length<2?null:"."+e.slice(-2).join(".")}function N(i){if(typeof document>"u")return null;let e=document.cookie.match(new RegExp("(?:^|; )"+i+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}function P(i,e,t=365){if(typeof document>"u")return;let n=D(),r=new Date(Date.now()+t*864e5).toUTCString(),o=[`${i}=${encodeURIComponent(e)}`,`expires=${r}`,"path=/","SameSite=Lax"];n&&o.push(`domain=${n}`),typeof location<"u"&&location.protocol==="https:"&&o.push("Secure"),document.cookie=o.join("; ");}var m=class{constructor(e){this.storage=O();this.sessionTimeoutMs=e;let t=N(f)||this.storage?.getItem(f)||null;t||(t=a()),this.anonymousId=t,this.persistAnon(),this.userId=this.storage?.getItem(w)||null;let n=Date.now(),r=this.storage?.getItem(d),o=parseInt(this.storage?.getItem(h)||"0",10);r&&n-o<e?this.sessionId=r:(this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=n,this.storage?.setItem(h,String(n));}persistAnon(){this.storage?.setItem(f,this.anonymousId),P(f,this.anonymousId);}touch(){let e=Date.now(),t=e-this.lastActivity>=this.sessionTimeoutMs,n;return t&&(n=this.sessionId,this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=e,this.storage?.setItem(h,String(e)),{sessionStarted:t,oldSessionId:n}}setUserId(e){this.userId=e,this.storage?.setItem(w,e);}reset(){this.userId=null,this.storage?.removeItem(w),this.anonymousId=a(),this.persistAnon(),this.sessionId=a(),this.storage?.setItem(d,this.sessionId),this.lastActivity=Date.now(),this.storage?.setItem(h,String(this.lastActivity));}getAnonymousId(){return this.anonymousId}getUserId(){return this.userId}getSessionId(){return this.sessionId}};var p="echo_queue_v1";var g=class{constructor(e){this.buffer=[];this.storage=e==="localstorage"&&typeof localStorage<"u"?localStorage:null,this.restore();}restore(){if(this.storage)try{let e=this.storage.getItem(p);e&&(this.buffer=JSON.parse(e));}catch{this.buffer=[];}}persist(){if(this.storage)try{this.storage.setItem(p,JSON.stringify(this.buffer));}catch{for(;this.buffer.length>50;){this.buffer.shift();try{this.storage.setItem(p,JSON.stringify(this.buffer));break}catch{}}}}enqueue(e){this.buffer.length>=1e3&&this.buffer.shift(),this.buffer.push(e),this.persist();}size(){return this.buffer.length}drain(){let e=this.buffer;return this.buffer=[],this.storage&&this.storage.removeItem(p),e}requeue(e){this.buffer=e.concat(this.buffer).slice(0,1e3),this.persist();}};async function I(i,e){if(i.length===0)return true;let t=e.endpoint.replace(/\/+$/,"")+"/v1/events",n=JSON.stringify({events:i});if(e.useBeacon&&typeof navigator<"u"&&navigator.sendBeacon){let r=`${t}?writeKey=${encodeURIComponent(e.writeKey)}`,o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(r,o)}try{return (await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":e.writeKey},body:n,keepalive:!0,credentials:"omit"})).ok}catch{return false}}var K="https://events.moso.vn",F=50,H=1e4,B=1800*1e3,y=class{constructor(e){this.flushTimer=null;this.flushing=false;this.currentUrl="";this.previousUrl="";if(!e.writeKey)throw new Error("@mosovn/echo: writeKey is required");this.config={writeKey:e.writeKey,endpoint:e.endpoint??K,flushAt:e.flushAt??F,flushInterval:e.flushInterval??H,sessionTimeoutMs:e.sessionTimeoutMs??B,debug:e.debug??false,...e},this.identity=new m(this.config.sessionTimeoutMs);let t=e.storage==="memory"?"memory":"localstorage";this.queue=new g(t),typeof location<"u"&&(this.currentUrl=location.href),typeof document<"u"&&(this.previousUrl=document.referrer||""),this.startFlushTimer(),this.installPageHideFlush();}markPageview(e){e!==this.currentUrl&&(this.previousUrl=this.currentUrl,this.currentUrl=e);}getPreviousUrl(){return this.previousUrl}debugLog(...e){this.config.debug&&console.log("[echo]",...e);}buildEvent(e,t,n){let{sessionStarted:r,oldSessionId:o}=this.identity.touch();return r&&o&&(this.enqueueRaw({_id:a(),eventName:"$session_end",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:o}),this.enqueueRaw({_id:a(),eventName:"$session_start",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId()})),{_id:a(),eventName:e,eventType:t,anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId(),properties:n,context:this.collectContext()}}collectContext(){let e={lib:{name:"@mosovn/echo",version:"0.0.0"}};return typeof location<"u"&&(e.page={url:location.href,path:location.pathname,referrer:this.previousUrl,title:typeof document<"u"?document.title:""}),typeof navigator<"u"&&(e.locale=navigator.language),typeof screen<"u"&&(e.screen={width:screen.width,height:screen.height}),e}enqueueRaw(e){let t=this.config.beforeSend,n=t?t(e):e;n&&(this.queue.enqueue(n),this.debugLog("enqueue",n.eventName,n._id),this.queue.size()>=this.config.flushAt&&this.flush());}enqueueEvent(e,t,n){let r=this.buildEvent(e,t,n);this.enqueueRaw(r);}track(e,t){this.enqueueEvent(e,"custom",t);}identify(e,t){this.identity.setUserId(e),this.enqueueEvent("$identify","auto",t),fetch(this.config.endpoint.replace(/\/+$/,"")+"/v1/identify",{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":this.config.writeKey},body:JSON.stringify({userId:e,anonymousId:this.identity.getAnonymousId(),traits:t||{}}),keepalive:true,credentials:"omit"}).catch(()=>{});}setUserProperties(e){let t=this.identity.getUserId();t?this.identify(t,e):this.enqueueEvent("$set_user_properties","auto",e);}page(e){this.enqueueEvent("$pageview","auto",e);}reset(){this.identity.reset(),this.queue.drain();}async flush(){if(this.flushing||this.queue.size()===0)return;this.flushing=true;let e=this.queue.drain();await I(e,{writeKey:this.config.writeKey,endpoint:this.config.endpoint})?this.debugLog("flush ok",e.length):(this.queue.requeue(e),this.debugLog("flush failed, requeued",e.length)),this.flushing=false;}getAnonymousId(){return this.identity.getAnonymousId()}getUserId(){return this.identity.getUserId()}getSessionId(){return this.identity.getSessionId()}startFlushTimer(){typeof setInterval>"u"||(this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval));}installPageHideFlush(){if(typeof document>"u")return;let e=()=>{let t=this.queue.drain();t.length!==0&&I(t,{writeKey:this.config.writeKey,endpoint:this.config.endpoint,useBeacon:true});};document.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();});}};var j=["input","textarea","select","[data-private]"],V=["[data-no-track]","script","style"];function T(i,e){for(let t of e)try{if(i.matches(t))return !0}catch{}return false}function l(i,e){let t=e.blockSelectors&&e.blockSelectors.length?e.blockSelectors:V;return T(i,t)}function Y(i,e){let t=e.maskTextSelectors&&e.maskTextSelectors.length?e.maskTextSelectors:j;return T(i,t)}function J(i,e){return Y(i,e)?"":(i.innerText||i.textContent||"").replace(/\s+/g," ").trim().slice(0,200)}function S(i){let e=[],t=i,n=0;for(;t&&t.nodeType===1&&n<6;){let r=t.tagName.toLowerCase();if(t.id){e.unshift(`${r}#${t.id}`);break}let o=(t.getAttribute("class")||"").split(/\s+/).filter(Boolean).slice(0,2).map(u=>"."+u).join(""),s="";if(t.parentElement){let u=Array.from(t.parentElement.children).filter(c=>c.tagName===t.tagName);u.length>1&&(s=`:nth-of-type(${u.indexOf(t)+1})`);}e.unshift(`${r}${o}${s}`),t=t.parentElement,n++;}return e.join(" > ")}function E(i,e){let t=i.tagName.toLowerCase(),n={selector:S(i),tag:t,text:J(i,e)},r=i.id;r&&(n.id=r);let o=i.getAttribute&&i.getAttribute("name");if(o&&(n.name=o),t==="a"){let u=i.href;u&&(n.href=u);}let s=i.getAttribute&&i.getAttribute("aria-label");return s&&(n.ariaLabel=s),n}function v(i){let e=i.tagName?.toLowerCase();if(!e)return false;if(e==="a"||e==="button")return true;let t=i.getAttribute&&i.getAttribute("role");return t==="button"||t==="link"||t==="tab"||t==="menuitem"}function k(i,e,t){typeof document>"u"||document.addEventListener("click",n=>{let r=n.target;if(r){if(e==="instrumented"){let o=r;for(;o&&o!==document.body&&!v(o);)o=o.parentElement;if(!o||o===document.body)return;r=o;}l(r,t)||i.enqueueEvent("$click","auto",E(r,t));}},true);}function A(i){if(typeof window>"u")return;let e=[],t=n=>{let r=Date.now();for(;e.length&&r-e[0].at>5e3;)e.shift();return e.some(o=>o.key===n)?false:(e.push({key:n,at:r}),true)};window.addEventListener("error",n=>{let r=n.message||String(n.error||"unknown"),o=`${r}|${n.filename}|${n.lineno}`;t(o)&&i.enqueueEvent("$error","auto",{message:r,source:n.filename,line:n.lineno,column:n.colno,stack:n.error&&n.error.stack||void 0});}),window.addEventListener("unhandledrejection",n=>{let r=n.reason,o=r instanceof Error?r.message:String(r),s=r instanceof Error?r.stack:void 0,u=`unhandled:${o}`;t(u)&&i.enqueueEvent("$error","auto",{kind:"unhandledrejection",message:o,stack:s});});}function _(i,e){typeof document>"u"||document.addEventListener("submit",t=>{let n=t.target;if(!n||n.tagName!=="FORM"||l(n,e))return;let r=n.querySelectorAll("input, select, textarea").length;i.enqueueEvent("$form_submit","auto",{selector:S(n),id:n.id||void 0,name:n.getAttribute("name")||void 0,action:n.getAttribute("action")||void 0,method:(n.getAttribute("method")||"get").toUpperCase(),fieldCount:r});},true);}var X=500;function x(i,e){if(typeof document>"u")return;let t=null,n=0;document.addEventListener("mouseover",r=>{let o=r.target;if(!o)return;let s=o;for(;s&&s!==document.body&&!v(s);)s=s.parentElement;if(!s||s===document.body||s===t||l(s,e))return;let u=Date.now();if(u-n<X){t=s;return}t=s,n=u,i.enqueueEvent("$hover","auto",E(s,e));},true);}function R(i){if(typeof window>"u"||typeof history>"u")return;let e=location.href,t=0,n=()=>{let o=Date.now();o-t<100||location.href===e&&t!==0||(e=location.href,t=o,i.markPageview(location.href),i.enqueueEvent("$pageview","auto",{url:location.href,path:location.pathname,title:document?.title,referrer:i.getPreviousUrl()}));};n();let r=o=>{let s=history[o];history[o]=function(...u){let c=s.apply(history,u);return n(),c};};r("pushState"),r("replaceState"),window.addEventListener("popstate",n),window.addEventListener("hashchange",n);}function U(i){if(typeof window>"u"||typeof document>"u")return;let e=0,t=0,n=location.href,r=()=>{location.href!==n&&(n=location.href,e=0,t=0);let o=Date.now();if(o-t<250)return;let s=document.documentElement,u=window.scrollY||s.scrollTop||0,c=window.innerHeight||s.clientHeight,C=s.scrollHeight-c;if(C<=0)return;let b=Math.max(0,Math.min(100,Math.round(u/C*100)));b-e<10||(e=b,t=o,i.enqueueEvent("$scroll","auto",{percent:b,pixels:u}));};window.addEventListener("scroll",r,{passive:true});}function L(i){if(typeof document>"u")return;let e=document.visibilityState;document.addEventListener("visibilitychange",()=>{let t=document.visibilityState;t!==e&&(e=t,i.enqueueEvent("$visibility","auto",{state:t}));});}function M(i){if(typeof PerformanceObserver>"u")return;let e=(t,n,r)=>{i.enqueueEvent("$web_vital","auto",{name:t,value:n,...r});};try{new PerformanceObserver(n=>{let r=n.getEntries(),o=r[r.length-1];o&&e("LCP",Math.round(o.startTime));}).observe({type:"largest-contentful-paint",buffered:!0});}catch{}try{let t=0;new PerformanceObserver(r=>{for(let o of r.getEntries())o.hadRecentInput||(t+=o.value);}).observe({type:"layout-shift",buffered:!0}),addEventListener("pagehide",()=>e("CLS",Math.round(t*1e3)/1e3));}catch{}try{new PerformanceObserver(n=>{let r=n.getEntries()[0];r&&e("FID",Math.round(r.processingStart-r.startTime));}).observe({type:"first-input",buffered:!0});}catch{}}var q={pageviews:true,clicks:"all",forms:true,scroll:true,visibility:true,mouseover:true,errors:true,vitals:true,sessions:true};function Q(i){return i===false?null:i===void 0||i===true?q:{...q,...i}}function $(i,e){let t=Q(e.autoCapture);if(!t)return;let n={maskTextSelectors:e.maskTextSelectors,blockSelectors:e.blockSelectors};if(t.pageviews&&R(i),t.clicks){let r=t.clicks==="instrumented"?"instrumented":"all";k(i,r,n);}t.forms&&_(i,n),t.scroll&&U(i),t.visibility&&L(i),t.mouseover&&x(i,n),t.errors&&A(i),t.vitals&&M(i);}function xe(i){let e=new y(i);return $(e,i),e}
|
|
1
|
+
'use strict';function a(){let i=BigInt(Date.now()),e=crypto.getRandomValues(new Uint8Array(10)),t=new Uint8Array(16);t[0]=Number(i>>40n&0xffn),t[1]=Number(i>>32n&0xffn),t[2]=Number(i>>24n&0xffn),t[3]=Number(i>>16n&0xffn),t[4]=Number(i>>8n&0xffn),t[5]=Number(i&0xffn),t[6]=e[0]&15|112,t[7]=e[1],t[8]=e[2]&63|128,t[9]=e[3],t[10]=e[4],t[11]=e[5],t[12]=e[6],t[13]=e[7],t[14]=e[8],t[15]=e[9];let n=Array.from(t,r=>r.toString(16).padStart(2,"0")).join("");return `${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20)}`}var f="echo_anonymous_id",w="echo_user_id",d="echo_session_id",h="echo_last_activity";function O(){try{return typeof localStorage>"u"?null:(localStorage.setItem("__echo_probe__","1"),localStorage.removeItem("__echo_probe__"),localStorage)}catch{return null}}function D(){if(typeof location>"u")return null;let i=location.hostname;if(i==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(i))return null;let e=i.split(".");return e.length<2?null:"."+e.slice(-2).join(".")}function N(i){if(typeof document>"u")return null;let e=document.cookie.match(new RegExp("(?:^|; )"+i+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}function P(i,e,t=365){if(typeof document>"u")return;let n=D(),r=new Date(Date.now()+t*864e5).toUTCString(),o=[`${i}=${encodeURIComponent(e)}`,`expires=${r}`,"path=/","SameSite=Lax"];n&&o.push(`domain=${n}`),typeof location<"u"&&location.protocol==="https:"&&o.push("Secure"),document.cookie=o.join("; ");}var m=class{constructor(e){this.storage=O();this.sessionTimeoutMs=e;let t=N(f)||this.storage?.getItem(f)||null;t||(t=a()),this.anonymousId=t,this.persistAnon(),this.userId=this.storage?.getItem(w)||null;let n=Date.now(),r=this.storage?.getItem(d),o=parseInt(this.storage?.getItem(h)||"0",10);r&&n-o<e?this.sessionId=r:(this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=n,this.storage?.setItem(h,String(n));}persistAnon(){this.storage?.setItem(f,this.anonymousId),P(f,this.anonymousId);}touch(){let e=Date.now(),t=e-this.lastActivity>=this.sessionTimeoutMs,n;return t&&(n=this.sessionId,this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=e,this.storage?.setItem(h,String(e)),{sessionStarted:t,oldSessionId:n}}setUserId(e){this.userId=e,this.storage?.setItem(w,e);}reset(){this.userId=null,this.storage?.removeItem(w),this.anonymousId=a(),this.persistAnon(),this.sessionId=a(),this.storage?.setItem(d,this.sessionId),this.lastActivity=Date.now(),this.storage?.setItem(h,String(this.lastActivity));}getAnonymousId(){return this.anonymousId}getUserId(){return this.userId}getSessionId(){return this.sessionId}};var p="echo_queue_v1";var g=class{constructor(e){this.buffer=[];this.storage=e==="localstorage"&&typeof localStorage<"u"?localStorage:null,this.restore();}restore(){if(this.storage)try{let e=this.storage.getItem(p);e&&(this.buffer=JSON.parse(e));}catch{this.buffer=[];}}persist(){if(this.storage)try{this.storage.setItem(p,JSON.stringify(this.buffer));}catch{for(;this.buffer.length>50;){this.buffer.shift();try{this.storage.setItem(p,JSON.stringify(this.buffer));break}catch{}}}}enqueue(e){this.buffer.length>=1e3&&this.buffer.shift(),this.buffer.push(e),this.persist();}size(){return this.buffer.length}drain(){let e=this.buffer;return this.buffer=[],this.storage&&this.storage.removeItem(p),e}requeue(e){this.buffer=e.concat(this.buffer).slice(0,1e3),this.persist();}};async function I(i,e){if(i.length===0)return true;let t=e.endpoint.replace(/\/+$/,"")+"/v1/events",n=JSON.stringify({events:i});if(e.useBeacon&&typeof navigator<"u"&&navigator.sendBeacon){let r=`${t}?writeKey=${encodeURIComponent(e.writeKey)}`,o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(r,o)}try{return (await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":e.writeKey},body:n,keepalive:!0,credentials:"omit"})).ok}catch{return false}}var K="https://events.moso.vn",F=50,H=1e4,B=1800*1e3,y=class{constructor(e){this.flushTimer=null;this.flushing=false;this.currentUrl="";this.previousUrl="";if(!e.writeKey)throw new Error("@mosovn/echo: writeKey is required");this.config={writeKey:e.writeKey,endpoint:e.endpoint??K,flushAt:e.flushAt??F,flushInterval:e.flushInterval??H,sessionTimeoutMs:e.sessionTimeoutMs??B,debug:e.debug??false,...e},this.identity=new m(this.config.sessionTimeoutMs);let t=e.storage==="memory"?"memory":"localstorage";this.queue=new g(t),typeof location<"u"&&(this.currentUrl=location.href),typeof document<"u"&&(this.previousUrl=document.referrer||""),this.startFlushTimer(),this.installPageHideFlush();}markPageview(e){e!==this.currentUrl&&(this.previousUrl=this.currentUrl,this.currentUrl=e);}getPreviousUrl(){return this.previousUrl}debugLog(...e){this.config.debug&&console.log("[echo]",...e);}buildEvent(e,t,n){let{sessionStarted:r,oldSessionId:o}=this.identity.touch();return r&&o&&(this.enqueueRaw({_id:a(),eventName:"$session_end",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:o}),this.enqueueRaw({_id:a(),eventName:"$session_start",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId()})),{_id:a(),eventName:e,eventType:t,anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId(),properties:n,context:this.collectContext()}}collectContext(){let e={lib:{name:"@mosovn/echo",version:"0.2.0"}};return typeof location<"u"&&(e.page={url:location.href,path:location.pathname,referrer:this.previousUrl,title:typeof document<"u"?document.title:""}),typeof navigator<"u"&&(e.locale=navigator.language),typeof screen<"u"&&(e.screen={width:screen.width,height:screen.height}),e}enqueueRaw(e){let t=this.config.beforeSend,n=t?t(e):e;n&&(this.queue.enqueue(n),this.debugLog("enqueue",n.eventName,n._id),this.queue.size()>=this.config.flushAt&&this.flush());}enqueueEvent(e,t,n){let r=this.buildEvent(e,t,n);this.enqueueRaw(r);}track(e,t){this.enqueueEvent(e,"custom",t);}identify(e,t){this.identity.setUserId(e),this.enqueueEvent("$identify","auto",t),fetch(this.config.endpoint.replace(/\/+$/,"")+"/v1/identify",{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":this.config.writeKey},body:JSON.stringify({userId:e,anonymousId:this.identity.getAnonymousId(),traits:t||{}}),keepalive:true,credentials:"omit"}).catch(()=>{});}setUserProperties(e){let t=this.identity.getUserId();t?this.identify(t,e):this.enqueueEvent("$set_user_properties","auto",e);}page(e){this.enqueueEvent("$pageview","auto",e);}reset(){this.identity.reset(),this.queue.drain();}async flush(){if(this.flushing||this.queue.size()===0)return;this.flushing=true;let e=this.queue.drain();await I(e,{writeKey:this.config.writeKey,endpoint:this.config.endpoint})?this.debugLog("flush ok",e.length):(this.queue.requeue(e),this.debugLog("flush failed, requeued",e.length)),this.flushing=false;}getAnonymousId(){return this.identity.getAnonymousId()}getUserId(){return this.identity.getUserId()}getSessionId(){return this.identity.getSessionId()}startFlushTimer(){typeof setInterval>"u"||(this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval));}installPageHideFlush(){if(typeof document>"u")return;let e=()=>{let t=this.queue.drain();t.length!==0&&I(t,{writeKey:this.config.writeKey,endpoint:this.config.endpoint,useBeacon:true});};document.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();});}};var j=["input","textarea","select","[data-private]"],V=["[data-no-track]","script","style"];function T(i,e){for(let t of e)try{if(i.matches(t))return !0}catch{}return false}function l(i,e){let t=e.blockSelectors&&e.blockSelectors.length?e.blockSelectors:V;return T(i,t)}function Y(i,e){let t=e.maskTextSelectors&&e.maskTextSelectors.length?e.maskTextSelectors:j;return T(i,t)}function J(i,e){return Y(i,e)?"":(i.innerText||i.textContent||"").replace(/\s+/g," ").trim().slice(0,200)}function S(i){let e=[],t=i,n=0;for(;t&&t.nodeType===1&&n<6;){let r=t.tagName.toLowerCase();if(t.id){e.unshift(`${r}#${t.id}`);break}let o=(t.getAttribute("class")||"").split(/\s+/).filter(Boolean).slice(0,2).map(u=>"."+u).join(""),s="";if(t.parentElement){let u=Array.from(t.parentElement.children).filter(c=>c.tagName===t.tagName);u.length>1&&(s=`:nth-of-type(${u.indexOf(t)+1})`);}e.unshift(`${r}${o}${s}`),t=t.parentElement,n++;}return e.join(" > ")}function E(i,e){let t=i.tagName.toLowerCase(),n={selector:S(i),tag:t,text:J(i,e)},r=i.id;r&&(n.id=r);let o=i.getAttribute&&i.getAttribute("name");if(o&&(n.name=o),t==="a"){let u=i.href;u&&(n.href=u);}let s=i.getAttribute&&i.getAttribute("aria-label");return s&&(n.ariaLabel=s),n}function v(i){let e=i.tagName?.toLowerCase();if(!e)return false;if(e==="a"||e==="button")return true;let t=i.getAttribute&&i.getAttribute("role");return t==="button"||t==="link"||t==="tab"||t==="menuitem"}function _(i,e,t){typeof document>"u"||document.addEventListener("click",n=>{let r=n.target;if(r){if(e==="instrumented"){let o=r;for(;o&&o!==document.body&&!v(o);)o=o.parentElement;if(!o||o===document.body)return;r=o;}l(r,t)||i.enqueueEvent("$click","auto",E(r,t));}},true);}function k(i){if(typeof window>"u")return;let e=[],t=n=>{let r=Date.now();for(;e.length&&r-e[0].at>5e3;)e.shift();return e.some(o=>o.key===n)?false:(e.push({key:n,at:r}),true)};window.addEventListener("error",n=>{let r=n.message||String(n.error||"unknown"),o=`${r}|${n.filename}|${n.lineno}`;t(o)&&i.enqueueEvent("$error","auto",{message:r,source:n.filename,line:n.lineno,column:n.colno,stack:n.error&&n.error.stack||void 0});}),window.addEventListener("unhandledrejection",n=>{let r=n.reason,o=r instanceof Error?r.message:String(r),s=r instanceof Error?r.stack:void 0,u=`unhandled:${o}`;t(u)&&i.enqueueEvent("$error","auto",{kind:"unhandledrejection",message:o,stack:s});});}function A(i,e){typeof document>"u"||document.addEventListener("submit",t=>{let n=t.target;if(!n||n.tagName!=="FORM"||l(n,e))return;let r=n.querySelectorAll("input, select, textarea").length;i.enqueueEvent("$form_submit","auto",{selector:S(n),id:n.id||void 0,name:n.getAttribute("name")||void 0,action:n.getAttribute("action")||void 0,method:(n.getAttribute("method")||"get").toUpperCase(),fieldCount:r});},true);}var X=500;function x(i,e){if(typeof document>"u")return;let t=null,n=0;document.addEventListener("mouseover",r=>{let o=r.target;if(!o)return;let s=o;for(;s&&s!==document.body&&!v(s);)s=s.parentElement;if(!s||s===document.body||s===t||l(s,e))return;let u=Date.now();if(u-n<X){t=s;return}t=s,n=u,i.enqueueEvent("$hover","auto",E(s,e));},true);}function R(i){if(typeof window>"u"||typeof history>"u")return;let e=location.href,t=0,n=()=>{let o=Date.now();o-t<100||location.href===e&&t!==0||(e=location.href,t=o,i.markPageview(location.href),i.enqueueEvent("$pageview","auto",{url:location.href,path:location.pathname,title:document?.title,referrer:i.getPreviousUrl()}));};n();let r=o=>{let s=history[o];history[o]=function(...u){let c=s.apply(history,u);return n(),c};};r("pushState"),r("replaceState"),window.addEventListener("popstate",n),window.addEventListener("hashchange",n);}function U(i){if(typeof window>"u"||typeof document>"u")return;let e=0,t=0,n=location.href,r=()=>{location.href!==n&&(n=location.href,e=0,t=0);let o=Date.now();if(o-t<250)return;let s=document.documentElement,u=window.scrollY||s.scrollTop||0,c=window.innerHeight||s.clientHeight,C=s.scrollHeight-c;if(C<=0)return;let b=Math.max(0,Math.min(100,Math.round(u/C*100)));b-e<10||(e=b,t=o,i.enqueueEvent("$scroll","auto",{percent:b,pixels:u}));};window.addEventListener("scroll",r,{passive:true});}function L(i){if(typeof document>"u")return;let e=document.visibilityState;document.addEventListener("visibilitychange",()=>{let t=document.visibilityState;t!==e&&(e=t,i.enqueueEvent("$visibility","auto",{state:t}));});}function M(i){if(typeof PerformanceObserver>"u")return;let e=(t,n,r)=>{i.enqueueEvent("$web_vital","auto",{name:t,value:n,...r});};try{new PerformanceObserver(n=>{let r=n.getEntries(),o=r[r.length-1];o&&e("LCP",Math.round(o.startTime));}).observe({type:"largest-contentful-paint",buffered:!0});}catch{}try{let t=0;new PerformanceObserver(r=>{for(let o of r.getEntries())o.hadRecentInput||(t+=o.value);}).observe({type:"layout-shift",buffered:!0}),addEventListener("pagehide",()=>e("CLS",Math.round(t*1e3)/1e3));}catch{}try{new PerformanceObserver(n=>{let r=n.getEntries()[0];r&&e("FID",Math.round(r.processingStart-r.startTime));}).observe({type:"first-input",buffered:!0});}catch{}}var q={pageviews:true,clicks:"all",forms:true,scroll:true,visibility:true,mouseover:true,errors:true,vitals:true,sessions:true};function Q(i){return i===false?null:i===void 0||i===true?q:{...q,...i}}function $(i,e){let t=Q(e.autoCapture);if(!t)return;let n={maskTextSelectors:e.maskTextSelectors,blockSelectors:e.blockSelectors};if(t.pageviews&&R(i),t.clicks){let r=t.clicks==="instrumented"?"instrumented":"all";_(i,r,n);}t.forms&&A(i,n),t.scroll&&U(i),t.visibility&&L(i),t.mouseover&&x(i,n),t.errors&&k(i),t.vitals&&M(i);}function xe(i){let e=new y(i);return $(e,i),e}
|
|
2
2
|
exports.createEcho=xe;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for both `@mosovn/echo` entries (web + native).
|
|
3
|
+
* Platform-specific options are tagged in JSDoc; setting a web-only option in
|
|
4
|
+
* RN (or vice versa) is silently ignored, not an error.
|
|
5
|
+
*/
|
|
1
6
|
interface EchoConfig {
|
|
2
7
|
/** Project's public write key (e.g. `pk_xxx`). */
|
|
3
8
|
writeKey: string;
|
|
@@ -9,25 +14,32 @@ interface EchoConfig {
|
|
|
9
14
|
flushInterval?: number;
|
|
10
15
|
/** Optional middleware called on every event before queueing. Return `null` to drop. */
|
|
11
16
|
beforeSend?: (event: EchoEvent) => EchoEvent | null;
|
|
12
|
-
/** Where to persist the queue between page loads. Default 'indexeddb'. */
|
|
13
|
-
storage?: 'indexeddb' | 'localstorage' | 'memory';
|
|
14
|
-
/** Autocapture configuration. See AutoCaptureConfig. */
|
|
15
|
-
autoCapture?: AutoCaptureConfig | boolean;
|
|
16
|
-
/**
|
|
17
|
-
* CSS selectors whose text content should be masked when captured (e.g. for
|
|
18
|
-
* autocaptured click events on inputs). Defaults cover form fields.
|
|
19
|
-
*/
|
|
20
|
-
maskTextSelectors?: string[];
|
|
21
|
-
/** CSS selectors to fully skip during autocapture. */
|
|
22
|
-
blockSelectors?: string[];
|
|
23
17
|
/** Inactivity timeout (ms) to end a session. Default 30 min. */
|
|
24
18
|
sessionTimeoutMs?: number;
|
|
25
19
|
/** If true, log internal diagnostic info to console. */
|
|
26
20
|
debug?: boolean;
|
|
21
|
+
/** [Web only] Where to persist the queue. Default 'localstorage'. */
|
|
22
|
+
storage?: 'indexeddb' | 'localstorage' | 'memory';
|
|
23
|
+
/** [Web only] Autocapture configuration (DOM-based). See AutoCaptureConfig. */
|
|
24
|
+
autoCapture?: AutoCaptureConfig | boolean;
|
|
25
|
+
/** [Web only] CSS selectors whose text content is masked from captured events. */
|
|
26
|
+
maskTextSelectors?: string[];
|
|
27
|
+
/** [Web only] CSS selectors to skip during autocapture. */
|
|
28
|
+
blockSelectors?: string[];
|
|
29
|
+
/** [Native only] App info attached to event context. */
|
|
30
|
+
app?: {
|
|
31
|
+
name?: string;
|
|
32
|
+
version?: string;
|
|
33
|
+
build?: string;
|
|
34
|
+
};
|
|
35
|
+
/** [Native only] Track AppState transitions as $session_start/end. Default true. */
|
|
36
|
+
trackSessions?: boolean;
|
|
37
|
+
/** [Native only] Track JS errors via ErrorUtils + unhandledrejection. Default true. */
|
|
38
|
+
trackErrors?: boolean;
|
|
27
39
|
}
|
|
28
40
|
interface AutoCaptureConfig {
|
|
29
41
|
pageviews?: boolean;
|
|
30
|
-
/** 'instrumented' = a/button/[
|
|
42
|
+
/** 'instrumented' = a/button/[role=button]/[id]. 'all' = every click. */
|
|
31
43
|
clicks?: boolean | 'instrumented' | 'all';
|
|
32
44
|
forms?: boolean;
|
|
33
45
|
scroll?: boolean;
|
|
@@ -50,11 +62,19 @@ interface EchoEvent {
|
|
|
50
62
|
context?: Record<string, unknown>;
|
|
51
63
|
}
|
|
52
64
|
interface EchoClient {
|
|
65
|
+
/** Track a custom event. */
|
|
53
66
|
track(eventName: string, properties?: Record<string, unknown>): void;
|
|
67
|
+
/** Identify the current user. Call after login. */
|
|
54
68
|
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
69
|
+
/** Update user traits. */
|
|
55
70
|
setUserProperties(traits: Record<string, unknown>): void;
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
/** [Web only] Record a page view (URL-based). No-op on native. */
|
|
72
|
+
page?(properties?: Record<string, unknown>): void;
|
|
73
|
+
/** [Native only] Record a screen view. No-op on web. */
|
|
74
|
+
screen?(screenName: string, properties?: Record<string, unknown>): void;
|
|
75
|
+
/** Clear identity (call on logout). Rotates anonymousId. */
|
|
76
|
+
reset(): void | Promise<void>;
|
|
77
|
+
/** Force flush queued events now. */
|
|
58
78
|
flush(): Promise<void>;
|
|
59
79
|
getAnonymousId(): string;
|
|
60
80
|
getUserId(): string | null;
|
|
@@ -62,7 +82,10 @@ interface EchoClient {
|
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
/**
|
|
65
|
-
* @mosovn/echo —
|
|
85
|
+
* @mosovn/echo — web entry.
|
|
86
|
+
*
|
|
87
|
+
* Bundlers resolve this entry via the `browser` / default condition in
|
|
88
|
+
* `package.json` exports. React Native (Metro) loads `./native.ts` instead.
|
|
66
89
|
*
|
|
67
90
|
* Usage:
|
|
68
91
|
*
|
|
@@ -71,9 +94,6 @@ interface EchoClient {
|
|
|
71
94
|
* const echo = createEcho({ writeKey: 'pk_xxx' });
|
|
72
95
|
* echo.track('Sign Up', { plan: 'pro' });
|
|
73
96
|
* echo.identify('user_123', { email: 'foo@moso.vn' });
|
|
74
|
-
*
|
|
75
|
-
* Functional API — no module-level globals. You can create multiple clients
|
|
76
|
-
* targeting different projects (e.g. for multi-tenant test apps).
|
|
77
97
|
*/
|
|
78
98
|
|
|
79
99
|
declare function createEcho(config: EchoConfig): EchoClient;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for both `@mosovn/echo` entries (web + native).
|
|
3
|
+
* Platform-specific options are tagged in JSDoc; setting a web-only option in
|
|
4
|
+
* RN (or vice versa) is silently ignored, not an error.
|
|
5
|
+
*/
|
|
1
6
|
interface EchoConfig {
|
|
2
7
|
/** Project's public write key (e.g. `pk_xxx`). */
|
|
3
8
|
writeKey: string;
|
|
@@ -9,25 +14,32 @@ interface EchoConfig {
|
|
|
9
14
|
flushInterval?: number;
|
|
10
15
|
/** Optional middleware called on every event before queueing. Return `null` to drop. */
|
|
11
16
|
beforeSend?: (event: EchoEvent) => EchoEvent | null;
|
|
12
|
-
/** Where to persist the queue between page loads. Default 'indexeddb'. */
|
|
13
|
-
storage?: 'indexeddb' | 'localstorage' | 'memory';
|
|
14
|
-
/** Autocapture configuration. See AutoCaptureConfig. */
|
|
15
|
-
autoCapture?: AutoCaptureConfig | boolean;
|
|
16
|
-
/**
|
|
17
|
-
* CSS selectors whose text content should be masked when captured (e.g. for
|
|
18
|
-
* autocaptured click events on inputs). Defaults cover form fields.
|
|
19
|
-
*/
|
|
20
|
-
maskTextSelectors?: string[];
|
|
21
|
-
/** CSS selectors to fully skip during autocapture. */
|
|
22
|
-
blockSelectors?: string[];
|
|
23
17
|
/** Inactivity timeout (ms) to end a session. Default 30 min. */
|
|
24
18
|
sessionTimeoutMs?: number;
|
|
25
19
|
/** If true, log internal diagnostic info to console. */
|
|
26
20
|
debug?: boolean;
|
|
21
|
+
/** [Web only] Where to persist the queue. Default 'localstorage'. */
|
|
22
|
+
storage?: 'indexeddb' | 'localstorage' | 'memory';
|
|
23
|
+
/** [Web only] Autocapture configuration (DOM-based). See AutoCaptureConfig. */
|
|
24
|
+
autoCapture?: AutoCaptureConfig | boolean;
|
|
25
|
+
/** [Web only] CSS selectors whose text content is masked from captured events. */
|
|
26
|
+
maskTextSelectors?: string[];
|
|
27
|
+
/** [Web only] CSS selectors to skip during autocapture. */
|
|
28
|
+
blockSelectors?: string[];
|
|
29
|
+
/** [Native only] App info attached to event context. */
|
|
30
|
+
app?: {
|
|
31
|
+
name?: string;
|
|
32
|
+
version?: string;
|
|
33
|
+
build?: string;
|
|
34
|
+
};
|
|
35
|
+
/** [Native only] Track AppState transitions as $session_start/end. Default true. */
|
|
36
|
+
trackSessions?: boolean;
|
|
37
|
+
/** [Native only] Track JS errors via ErrorUtils + unhandledrejection. Default true. */
|
|
38
|
+
trackErrors?: boolean;
|
|
27
39
|
}
|
|
28
40
|
interface AutoCaptureConfig {
|
|
29
41
|
pageviews?: boolean;
|
|
30
|
-
/** 'instrumented' = a/button/[
|
|
42
|
+
/** 'instrumented' = a/button/[role=button]/[id]. 'all' = every click. */
|
|
31
43
|
clicks?: boolean | 'instrumented' | 'all';
|
|
32
44
|
forms?: boolean;
|
|
33
45
|
scroll?: boolean;
|
|
@@ -50,11 +62,19 @@ interface EchoEvent {
|
|
|
50
62
|
context?: Record<string, unknown>;
|
|
51
63
|
}
|
|
52
64
|
interface EchoClient {
|
|
65
|
+
/** Track a custom event. */
|
|
53
66
|
track(eventName: string, properties?: Record<string, unknown>): void;
|
|
67
|
+
/** Identify the current user. Call after login. */
|
|
54
68
|
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
69
|
+
/** Update user traits. */
|
|
55
70
|
setUserProperties(traits: Record<string, unknown>): void;
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
/** [Web only] Record a page view (URL-based). No-op on native. */
|
|
72
|
+
page?(properties?: Record<string, unknown>): void;
|
|
73
|
+
/** [Native only] Record a screen view. No-op on web. */
|
|
74
|
+
screen?(screenName: string, properties?: Record<string, unknown>): void;
|
|
75
|
+
/** Clear identity (call on logout). Rotates anonymousId. */
|
|
76
|
+
reset(): void | Promise<void>;
|
|
77
|
+
/** Force flush queued events now. */
|
|
58
78
|
flush(): Promise<void>;
|
|
59
79
|
getAnonymousId(): string;
|
|
60
80
|
getUserId(): string | null;
|
|
@@ -62,7 +82,10 @@ interface EchoClient {
|
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
/**
|
|
65
|
-
* @mosovn/echo —
|
|
85
|
+
* @mosovn/echo — web entry.
|
|
86
|
+
*
|
|
87
|
+
* Bundlers resolve this entry via the `browser` / default condition in
|
|
88
|
+
* `package.json` exports. React Native (Metro) loads `./native.ts` instead.
|
|
66
89
|
*
|
|
67
90
|
* Usage:
|
|
68
91
|
*
|
|
@@ -71,9 +94,6 @@ interface EchoClient {
|
|
|
71
94
|
* const echo = createEcho({ writeKey: 'pk_xxx' });
|
|
72
95
|
* echo.track('Sign Up', { plan: 'pro' });
|
|
73
96
|
* echo.identify('user_123', { email: 'foo@moso.vn' });
|
|
74
|
-
*
|
|
75
|
-
* Functional API — no module-level globals. You can create multiple clients
|
|
76
|
-
* targeting different projects (e.g. for multi-tenant test apps).
|
|
77
97
|
*/
|
|
78
98
|
|
|
79
99
|
declare function createEcho(config: EchoConfig): EchoClient;
|
package/dist/{index.js → web.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function a(){let i=BigInt(Date.now()),e=crypto.getRandomValues(new Uint8Array(10)),t=new Uint8Array(16);t[0]=Number(i>>40n&0xffn),t[1]=Number(i>>32n&0xffn),t[2]=Number(i>>24n&0xffn),t[3]=Number(i>>16n&0xffn),t[4]=Number(i>>8n&0xffn),t[5]=Number(i&0xffn),t[6]=e[0]&15|112,t[7]=e[1],t[8]=e[2]&63|128,t[9]=e[3],t[10]=e[4],t[11]=e[5],t[12]=e[6],t[13]=e[7],t[14]=e[8],t[15]=e[9];let n=Array.from(t,r=>r.toString(16).padStart(2,"0")).join("");return `${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20)}`}var f="echo_anonymous_id",w="echo_user_id",d="echo_session_id",h="echo_last_activity";function O(){try{return typeof localStorage>"u"?null:(localStorage.setItem("__echo_probe__","1"),localStorage.removeItem("__echo_probe__"),localStorage)}catch{return null}}function D(){if(typeof location>"u")return null;let i=location.hostname;if(i==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(i))return null;let e=i.split(".");return e.length<2?null:"."+e.slice(-2).join(".")}function N(i){if(typeof document>"u")return null;let e=document.cookie.match(new RegExp("(?:^|; )"+i+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}function P(i,e,t=365){if(typeof document>"u")return;let n=D(),r=new Date(Date.now()+t*864e5).toUTCString(),o=[`${i}=${encodeURIComponent(e)}`,`expires=${r}`,"path=/","SameSite=Lax"];n&&o.push(`domain=${n}`),typeof location<"u"&&location.protocol==="https:"&&o.push("Secure"),document.cookie=o.join("; ");}var m=class{constructor(e){this.storage=O();this.sessionTimeoutMs=e;let t=N(f)||this.storage?.getItem(f)||null;t||(t=a()),this.anonymousId=t,this.persistAnon(),this.userId=this.storage?.getItem(w)||null;let n=Date.now(),r=this.storage?.getItem(d),o=parseInt(this.storage?.getItem(h)||"0",10);r&&n-o<e?this.sessionId=r:(this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=n,this.storage?.setItem(h,String(n));}persistAnon(){this.storage?.setItem(f,this.anonymousId),P(f,this.anonymousId);}touch(){let e=Date.now(),t=e-this.lastActivity>=this.sessionTimeoutMs,n;return t&&(n=this.sessionId,this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=e,this.storage?.setItem(h,String(e)),{sessionStarted:t,oldSessionId:n}}setUserId(e){this.userId=e,this.storage?.setItem(w,e);}reset(){this.userId=null,this.storage?.removeItem(w),this.anonymousId=a(),this.persistAnon(),this.sessionId=a(),this.storage?.setItem(d,this.sessionId),this.lastActivity=Date.now(),this.storage?.setItem(h,String(this.lastActivity));}getAnonymousId(){return this.anonymousId}getUserId(){return this.userId}getSessionId(){return this.sessionId}};var p="echo_queue_v1";var g=class{constructor(e){this.buffer=[];this.storage=e==="localstorage"&&typeof localStorage<"u"?localStorage:null,this.restore();}restore(){if(this.storage)try{let e=this.storage.getItem(p);e&&(this.buffer=JSON.parse(e));}catch{this.buffer=[];}}persist(){if(this.storage)try{this.storage.setItem(p,JSON.stringify(this.buffer));}catch{for(;this.buffer.length>50;){this.buffer.shift();try{this.storage.setItem(p,JSON.stringify(this.buffer));break}catch{}}}}enqueue(e){this.buffer.length>=1e3&&this.buffer.shift(),this.buffer.push(e),this.persist();}size(){return this.buffer.length}drain(){let e=this.buffer;return this.buffer=[],this.storage&&this.storage.removeItem(p),e}requeue(e){this.buffer=e.concat(this.buffer).slice(0,1e3),this.persist();}};async function I(i,e){if(i.length===0)return true;let t=e.endpoint.replace(/\/+$/,"")+"/v1/events",n=JSON.stringify({events:i});if(e.useBeacon&&typeof navigator<"u"&&navigator.sendBeacon){let r=`${t}?writeKey=${encodeURIComponent(e.writeKey)}`,o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(r,o)}try{return (await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":e.writeKey},body:n,keepalive:!0,credentials:"omit"})).ok}catch{return false}}var K="https://events.moso.vn",F=50,H=1e4,B=1800*1e3,y=class{constructor(e){this.flushTimer=null;this.flushing=false;this.currentUrl="";this.previousUrl="";if(!e.writeKey)throw new Error("@mosovn/echo: writeKey is required");this.config={writeKey:e.writeKey,endpoint:e.endpoint??K,flushAt:e.flushAt??F,flushInterval:e.flushInterval??H,sessionTimeoutMs:e.sessionTimeoutMs??B,debug:e.debug??false,...e},this.identity=new m(this.config.sessionTimeoutMs);let t=e.storage==="memory"?"memory":"localstorage";this.queue=new g(t),typeof location<"u"&&(this.currentUrl=location.href),typeof document<"u"&&(this.previousUrl=document.referrer||""),this.startFlushTimer(),this.installPageHideFlush();}markPageview(e){e!==this.currentUrl&&(this.previousUrl=this.currentUrl,this.currentUrl=e);}getPreviousUrl(){return this.previousUrl}debugLog(...e){this.config.debug&&console.log("[echo]",...e);}buildEvent(e,t,n){let{sessionStarted:r,oldSessionId:o}=this.identity.touch();return r&&o&&(this.enqueueRaw({_id:a(),eventName:"$session_end",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:o}),this.enqueueRaw({_id:a(),eventName:"$session_start",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId()})),{_id:a(),eventName:e,eventType:t,anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId(),properties:n,context:this.collectContext()}}collectContext(){let e={lib:{name:"@mosovn/echo",version:"0.0.0"}};return typeof location<"u"&&(e.page={url:location.href,path:location.pathname,referrer:this.previousUrl,title:typeof document<"u"?document.title:""}),typeof navigator<"u"&&(e.locale=navigator.language),typeof screen<"u"&&(e.screen={width:screen.width,height:screen.height}),e}enqueueRaw(e){let t=this.config.beforeSend,n=t?t(e):e;n&&(this.queue.enqueue(n),this.debugLog("enqueue",n.eventName,n._id),this.queue.size()>=this.config.flushAt&&this.flush());}enqueueEvent(e,t,n){let r=this.buildEvent(e,t,n);this.enqueueRaw(r);}track(e,t){this.enqueueEvent(e,"custom",t);}identify(e,t){this.identity.setUserId(e),this.enqueueEvent("$identify","auto",t),fetch(this.config.endpoint.replace(/\/+$/,"")+"/v1/identify",{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":this.config.writeKey},body:JSON.stringify({userId:e,anonymousId:this.identity.getAnonymousId(),traits:t||{}}),keepalive:true,credentials:"omit"}).catch(()=>{});}setUserProperties(e){let t=this.identity.getUserId();t?this.identify(t,e):this.enqueueEvent("$set_user_properties","auto",e);}page(e){this.enqueueEvent("$pageview","auto",e);}reset(){this.identity.reset(),this.queue.drain();}async flush(){if(this.flushing||this.queue.size()===0)return;this.flushing=true;let e=this.queue.drain();await I(e,{writeKey:this.config.writeKey,endpoint:this.config.endpoint})?this.debugLog("flush ok",e.length):(this.queue.requeue(e),this.debugLog("flush failed, requeued",e.length)),this.flushing=false;}getAnonymousId(){return this.identity.getAnonymousId()}getUserId(){return this.identity.getUserId()}getSessionId(){return this.identity.getSessionId()}startFlushTimer(){typeof setInterval>"u"||(this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval));}installPageHideFlush(){if(typeof document>"u")return;let e=()=>{let t=this.queue.drain();t.length!==0&&I(t,{writeKey:this.config.writeKey,endpoint:this.config.endpoint,useBeacon:true});};document.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();});}};var j=["input","textarea","select","[data-private]"],V=["[data-no-track]","script","style"];function T(i,e){for(let t of e)try{if(i.matches(t))return !0}catch{}return false}function l(i,e){let t=e.blockSelectors&&e.blockSelectors.length?e.blockSelectors:V;return T(i,t)}function Y(i,e){let t=e.maskTextSelectors&&e.maskTextSelectors.length?e.maskTextSelectors:j;return T(i,t)}function J(i,e){return Y(i,e)?"":(i.innerText||i.textContent||"").replace(/\s+/g," ").trim().slice(0,200)}function S(i){let e=[],t=i,n=0;for(;t&&t.nodeType===1&&n<6;){let r=t.tagName.toLowerCase();if(t.id){e.unshift(`${r}#${t.id}`);break}let o=(t.getAttribute("class")||"").split(/\s+/).filter(Boolean).slice(0,2).map(u=>"."+u).join(""),s="";if(t.parentElement){let u=Array.from(t.parentElement.children).filter(c=>c.tagName===t.tagName);u.length>1&&(s=`:nth-of-type(${u.indexOf(t)+1})`);}e.unshift(`${r}${o}${s}`),t=t.parentElement,n++;}return e.join(" > ")}function E(i,e){let t=i.tagName.toLowerCase(),n={selector:S(i),tag:t,text:J(i,e)},r=i.id;r&&(n.id=r);let o=i.getAttribute&&i.getAttribute("name");if(o&&(n.name=o),t==="a"){let u=i.href;u&&(n.href=u);}let s=i.getAttribute&&i.getAttribute("aria-label");return s&&(n.ariaLabel=s),n}function v(i){let e=i.tagName?.toLowerCase();if(!e)return false;if(e==="a"||e==="button")return true;let t=i.getAttribute&&i.getAttribute("role");return t==="button"||t==="link"||t==="tab"||t==="menuitem"}function k(i,e,t){typeof document>"u"||document.addEventListener("click",n=>{let r=n.target;if(r){if(e==="instrumented"){let o=r;for(;o&&o!==document.body&&!v(o);)o=o.parentElement;if(!o||o===document.body)return;r=o;}l(r,t)||i.enqueueEvent("$click","auto",E(r,t));}},true);}function A(i){if(typeof window>"u")return;let e=[],t=n=>{let r=Date.now();for(;e.length&&r-e[0].at>5e3;)e.shift();return e.some(o=>o.key===n)?false:(e.push({key:n,at:r}),true)};window.addEventListener("error",n=>{let r=n.message||String(n.error||"unknown"),o=`${r}|${n.filename}|${n.lineno}`;t(o)&&i.enqueueEvent("$error","auto",{message:r,source:n.filename,line:n.lineno,column:n.colno,stack:n.error&&n.error.stack||void 0});}),window.addEventListener("unhandledrejection",n=>{let r=n.reason,o=r instanceof Error?r.message:String(r),s=r instanceof Error?r.stack:void 0,u=`unhandled:${o}`;t(u)&&i.enqueueEvent("$error","auto",{kind:"unhandledrejection",message:o,stack:s});});}function _(i,e){typeof document>"u"||document.addEventListener("submit",t=>{let n=t.target;if(!n||n.tagName!=="FORM"||l(n,e))return;let r=n.querySelectorAll("input, select, textarea").length;i.enqueueEvent("$form_submit","auto",{selector:S(n),id:n.id||void 0,name:n.getAttribute("name")||void 0,action:n.getAttribute("action")||void 0,method:(n.getAttribute("method")||"get").toUpperCase(),fieldCount:r});},true);}var X=500;function x(i,e){if(typeof document>"u")return;let t=null,n=0;document.addEventListener("mouseover",r=>{let o=r.target;if(!o)return;let s=o;for(;s&&s!==document.body&&!v(s);)s=s.parentElement;if(!s||s===document.body||s===t||l(s,e))return;let u=Date.now();if(u-n<X){t=s;return}t=s,n=u,i.enqueueEvent("$hover","auto",E(s,e));},true);}function R(i){if(typeof window>"u"||typeof history>"u")return;let e=location.href,t=0,n=()=>{let o=Date.now();o-t<100||location.href===e&&t!==0||(e=location.href,t=o,i.markPageview(location.href),i.enqueueEvent("$pageview","auto",{url:location.href,path:location.pathname,title:document?.title,referrer:i.getPreviousUrl()}));};n();let r=o=>{let s=history[o];history[o]=function(...u){let c=s.apply(history,u);return n(),c};};r("pushState"),r("replaceState"),window.addEventListener("popstate",n),window.addEventListener("hashchange",n);}function U(i){if(typeof window>"u"||typeof document>"u")return;let e=0,t=0,n=location.href,r=()=>{location.href!==n&&(n=location.href,e=0,t=0);let o=Date.now();if(o-t<250)return;let s=document.documentElement,u=window.scrollY||s.scrollTop||0,c=window.innerHeight||s.clientHeight,C=s.scrollHeight-c;if(C<=0)return;let b=Math.max(0,Math.min(100,Math.round(u/C*100)));b-e<10||(e=b,t=o,i.enqueueEvent("$scroll","auto",{percent:b,pixels:u}));};window.addEventListener("scroll",r,{passive:true});}function L(i){if(typeof document>"u")return;let e=document.visibilityState;document.addEventListener("visibilitychange",()=>{let t=document.visibilityState;t!==e&&(e=t,i.enqueueEvent("$visibility","auto",{state:t}));});}function M(i){if(typeof PerformanceObserver>"u")return;let e=(t,n,r)=>{i.enqueueEvent("$web_vital","auto",{name:t,value:n,...r});};try{new PerformanceObserver(n=>{let r=n.getEntries(),o=r[r.length-1];o&&e("LCP",Math.round(o.startTime));}).observe({type:"largest-contentful-paint",buffered:!0});}catch{}try{let t=0;new PerformanceObserver(r=>{for(let o of r.getEntries())o.hadRecentInput||(t+=o.value);}).observe({type:"layout-shift",buffered:!0}),addEventListener("pagehide",()=>e("CLS",Math.round(t*1e3)/1e3));}catch{}try{new PerformanceObserver(n=>{let r=n.getEntries()[0];r&&e("FID",Math.round(r.processingStart-r.startTime));}).observe({type:"first-input",buffered:!0});}catch{}}var q={pageviews:true,clicks:"all",forms:true,scroll:true,visibility:true,mouseover:true,errors:true,vitals:true,sessions:true};function Q(i){return i===false?null:i===void 0||i===true?q:{...q,...i}}function $(i,e){let t=Q(e.autoCapture);if(!t)return;let n={maskTextSelectors:e.maskTextSelectors,blockSelectors:e.blockSelectors};if(t.pageviews&&R(i),t.clicks){let r=t.clicks==="instrumented"?"instrumented":"all";k(i,r,n);}t.forms&&_(i,n),t.scroll&&U(i),t.visibility&&L(i),t.mouseover&&x(i,n),t.errors&&A(i),t.vitals&&M(i);}function xe(i){let e=new y(i);return $(e,i),e}
|
|
1
|
+
function a(){let i=BigInt(Date.now()),e=crypto.getRandomValues(new Uint8Array(10)),t=new Uint8Array(16);t[0]=Number(i>>40n&0xffn),t[1]=Number(i>>32n&0xffn),t[2]=Number(i>>24n&0xffn),t[3]=Number(i>>16n&0xffn),t[4]=Number(i>>8n&0xffn),t[5]=Number(i&0xffn),t[6]=e[0]&15|112,t[7]=e[1],t[8]=e[2]&63|128,t[9]=e[3],t[10]=e[4],t[11]=e[5],t[12]=e[6],t[13]=e[7],t[14]=e[8],t[15]=e[9];let n=Array.from(t,r=>r.toString(16).padStart(2,"0")).join("");return `${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20)}`}var f="echo_anonymous_id",w="echo_user_id",d="echo_session_id",h="echo_last_activity";function O(){try{return typeof localStorage>"u"?null:(localStorage.setItem("__echo_probe__","1"),localStorage.removeItem("__echo_probe__"),localStorage)}catch{return null}}function D(){if(typeof location>"u")return null;let i=location.hostname;if(i==="localhost"||/^\d+\.\d+\.\d+\.\d+$/.test(i))return null;let e=i.split(".");return e.length<2?null:"."+e.slice(-2).join(".")}function N(i){if(typeof document>"u")return null;let e=document.cookie.match(new RegExp("(?:^|; )"+i+"=([^;]*)"));return e?decodeURIComponent(e[1]):null}function P(i,e,t=365){if(typeof document>"u")return;let n=D(),r=new Date(Date.now()+t*864e5).toUTCString(),o=[`${i}=${encodeURIComponent(e)}`,`expires=${r}`,"path=/","SameSite=Lax"];n&&o.push(`domain=${n}`),typeof location<"u"&&location.protocol==="https:"&&o.push("Secure"),document.cookie=o.join("; ");}var m=class{constructor(e){this.storage=O();this.sessionTimeoutMs=e;let t=N(f)||this.storage?.getItem(f)||null;t||(t=a()),this.anonymousId=t,this.persistAnon(),this.userId=this.storage?.getItem(w)||null;let n=Date.now(),r=this.storage?.getItem(d),o=parseInt(this.storage?.getItem(h)||"0",10);r&&n-o<e?this.sessionId=r:(this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=n,this.storage?.setItem(h,String(n));}persistAnon(){this.storage?.setItem(f,this.anonymousId),P(f,this.anonymousId);}touch(){let e=Date.now(),t=e-this.lastActivity>=this.sessionTimeoutMs,n;return t&&(n=this.sessionId,this.sessionId=a(),this.storage?.setItem(d,this.sessionId)),this.lastActivity=e,this.storage?.setItem(h,String(e)),{sessionStarted:t,oldSessionId:n}}setUserId(e){this.userId=e,this.storage?.setItem(w,e);}reset(){this.userId=null,this.storage?.removeItem(w),this.anonymousId=a(),this.persistAnon(),this.sessionId=a(),this.storage?.setItem(d,this.sessionId),this.lastActivity=Date.now(),this.storage?.setItem(h,String(this.lastActivity));}getAnonymousId(){return this.anonymousId}getUserId(){return this.userId}getSessionId(){return this.sessionId}};var p="echo_queue_v1";var g=class{constructor(e){this.buffer=[];this.storage=e==="localstorage"&&typeof localStorage<"u"?localStorage:null,this.restore();}restore(){if(this.storage)try{let e=this.storage.getItem(p);e&&(this.buffer=JSON.parse(e));}catch{this.buffer=[];}}persist(){if(this.storage)try{this.storage.setItem(p,JSON.stringify(this.buffer));}catch{for(;this.buffer.length>50;){this.buffer.shift();try{this.storage.setItem(p,JSON.stringify(this.buffer));break}catch{}}}}enqueue(e){this.buffer.length>=1e3&&this.buffer.shift(),this.buffer.push(e),this.persist();}size(){return this.buffer.length}drain(){let e=this.buffer;return this.buffer=[],this.storage&&this.storage.removeItem(p),e}requeue(e){this.buffer=e.concat(this.buffer).slice(0,1e3),this.persist();}};async function I(i,e){if(i.length===0)return true;let t=e.endpoint.replace(/\/+$/,"")+"/v1/events",n=JSON.stringify({events:i});if(e.useBeacon&&typeof navigator<"u"&&navigator.sendBeacon){let r=`${t}?writeKey=${encodeURIComponent(e.writeKey)}`,o=new Blob([n],{type:"application/json"});return navigator.sendBeacon(r,o)}try{return (await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":e.writeKey},body:n,keepalive:!0,credentials:"omit"})).ok}catch{return false}}var K="https://events.moso.vn",F=50,H=1e4,B=1800*1e3,y=class{constructor(e){this.flushTimer=null;this.flushing=false;this.currentUrl="";this.previousUrl="";if(!e.writeKey)throw new Error("@mosovn/echo: writeKey is required");this.config={writeKey:e.writeKey,endpoint:e.endpoint??K,flushAt:e.flushAt??F,flushInterval:e.flushInterval??H,sessionTimeoutMs:e.sessionTimeoutMs??B,debug:e.debug??false,...e},this.identity=new m(this.config.sessionTimeoutMs);let t=e.storage==="memory"?"memory":"localstorage";this.queue=new g(t),typeof location<"u"&&(this.currentUrl=location.href),typeof document<"u"&&(this.previousUrl=document.referrer||""),this.startFlushTimer(),this.installPageHideFlush();}markPageview(e){e!==this.currentUrl&&(this.previousUrl=this.currentUrl,this.currentUrl=e);}getPreviousUrl(){return this.previousUrl}debugLog(...e){this.config.debug&&console.log("[echo]",...e);}buildEvent(e,t,n){let{sessionStarted:r,oldSessionId:o}=this.identity.touch();return r&&o&&(this.enqueueRaw({_id:a(),eventName:"$session_end",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:o}),this.enqueueRaw({_id:a(),eventName:"$session_start",eventType:"auto",anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId()})),{_id:a(),eventName:e,eventType:t,anonymousId:this.identity.getAnonymousId(),userId:this.identity.getUserId(),timestamp:new Date().toISOString(),sessionId:this.identity.getSessionId(),properties:n,context:this.collectContext()}}collectContext(){let e={lib:{name:"@mosovn/echo",version:"0.2.0"}};return typeof location<"u"&&(e.page={url:location.href,path:location.pathname,referrer:this.previousUrl,title:typeof document<"u"?document.title:""}),typeof navigator<"u"&&(e.locale=navigator.language),typeof screen<"u"&&(e.screen={width:screen.width,height:screen.height}),e}enqueueRaw(e){let t=this.config.beforeSend,n=t?t(e):e;n&&(this.queue.enqueue(n),this.debugLog("enqueue",n.eventName,n._id),this.queue.size()>=this.config.flushAt&&this.flush());}enqueueEvent(e,t,n){let r=this.buildEvent(e,t,n);this.enqueueRaw(r);}track(e,t){this.enqueueEvent(e,"custom",t);}identify(e,t){this.identity.setUserId(e),this.enqueueEvent("$identify","auto",t),fetch(this.config.endpoint.replace(/\/+$/,"")+"/v1/identify",{method:"POST",headers:{"Content-Type":"application/json","X-Echo-Key":this.config.writeKey},body:JSON.stringify({userId:e,anonymousId:this.identity.getAnonymousId(),traits:t||{}}),keepalive:true,credentials:"omit"}).catch(()=>{});}setUserProperties(e){let t=this.identity.getUserId();t?this.identify(t,e):this.enqueueEvent("$set_user_properties","auto",e);}page(e){this.enqueueEvent("$pageview","auto",e);}reset(){this.identity.reset(),this.queue.drain();}async flush(){if(this.flushing||this.queue.size()===0)return;this.flushing=true;let e=this.queue.drain();await I(e,{writeKey:this.config.writeKey,endpoint:this.config.endpoint})?this.debugLog("flush ok",e.length):(this.queue.requeue(e),this.debugLog("flush failed, requeued",e.length)),this.flushing=false;}getAnonymousId(){return this.identity.getAnonymousId()}getUserId(){return this.identity.getUserId()}getSessionId(){return this.identity.getSessionId()}startFlushTimer(){typeof setInterval>"u"||(this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval));}installPageHideFlush(){if(typeof document>"u")return;let e=()=>{let t=this.queue.drain();t.length!==0&&I(t,{writeKey:this.config.writeKey,endpoint:this.config.endpoint,useBeacon:true});};document.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&e();});}};var j=["input","textarea","select","[data-private]"],V=["[data-no-track]","script","style"];function T(i,e){for(let t of e)try{if(i.matches(t))return !0}catch{}return false}function l(i,e){let t=e.blockSelectors&&e.blockSelectors.length?e.blockSelectors:V;return T(i,t)}function Y(i,e){let t=e.maskTextSelectors&&e.maskTextSelectors.length?e.maskTextSelectors:j;return T(i,t)}function J(i,e){return Y(i,e)?"":(i.innerText||i.textContent||"").replace(/\s+/g," ").trim().slice(0,200)}function S(i){let e=[],t=i,n=0;for(;t&&t.nodeType===1&&n<6;){let r=t.tagName.toLowerCase();if(t.id){e.unshift(`${r}#${t.id}`);break}let o=(t.getAttribute("class")||"").split(/\s+/).filter(Boolean).slice(0,2).map(u=>"."+u).join(""),s="";if(t.parentElement){let u=Array.from(t.parentElement.children).filter(c=>c.tagName===t.tagName);u.length>1&&(s=`:nth-of-type(${u.indexOf(t)+1})`);}e.unshift(`${r}${o}${s}`),t=t.parentElement,n++;}return e.join(" > ")}function E(i,e){let t=i.tagName.toLowerCase(),n={selector:S(i),tag:t,text:J(i,e)},r=i.id;r&&(n.id=r);let o=i.getAttribute&&i.getAttribute("name");if(o&&(n.name=o),t==="a"){let u=i.href;u&&(n.href=u);}let s=i.getAttribute&&i.getAttribute("aria-label");return s&&(n.ariaLabel=s),n}function v(i){let e=i.tagName?.toLowerCase();if(!e)return false;if(e==="a"||e==="button")return true;let t=i.getAttribute&&i.getAttribute("role");return t==="button"||t==="link"||t==="tab"||t==="menuitem"}function _(i,e,t){typeof document>"u"||document.addEventListener("click",n=>{let r=n.target;if(r){if(e==="instrumented"){let o=r;for(;o&&o!==document.body&&!v(o);)o=o.parentElement;if(!o||o===document.body)return;r=o;}l(r,t)||i.enqueueEvent("$click","auto",E(r,t));}},true);}function k(i){if(typeof window>"u")return;let e=[],t=n=>{let r=Date.now();for(;e.length&&r-e[0].at>5e3;)e.shift();return e.some(o=>o.key===n)?false:(e.push({key:n,at:r}),true)};window.addEventListener("error",n=>{let r=n.message||String(n.error||"unknown"),o=`${r}|${n.filename}|${n.lineno}`;t(o)&&i.enqueueEvent("$error","auto",{message:r,source:n.filename,line:n.lineno,column:n.colno,stack:n.error&&n.error.stack||void 0});}),window.addEventListener("unhandledrejection",n=>{let r=n.reason,o=r instanceof Error?r.message:String(r),s=r instanceof Error?r.stack:void 0,u=`unhandled:${o}`;t(u)&&i.enqueueEvent("$error","auto",{kind:"unhandledrejection",message:o,stack:s});});}function A(i,e){typeof document>"u"||document.addEventListener("submit",t=>{let n=t.target;if(!n||n.tagName!=="FORM"||l(n,e))return;let r=n.querySelectorAll("input, select, textarea").length;i.enqueueEvent("$form_submit","auto",{selector:S(n),id:n.id||void 0,name:n.getAttribute("name")||void 0,action:n.getAttribute("action")||void 0,method:(n.getAttribute("method")||"get").toUpperCase(),fieldCount:r});},true);}var X=500;function x(i,e){if(typeof document>"u")return;let t=null,n=0;document.addEventListener("mouseover",r=>{let o=r.target;if(!o)return;let s=o;for(;s&&s!==document.body&&!v(s);)s=s.parentElement;if(!s||s===document.body||s===t||l(s,e))return;let u=Date.now();if(u-n<X){t=s;return}t=s,n=u,i.enqueueEvent("$hover","auto",E(s,e));},true);}function R(i){if(typeof window>"u"||typeof history>"u")return;let e=location.href,t=0,n=()=>{let o=Date.now();o-t<100||location.href===e&&t!==0||(e=location.href,t=o,i.markPageview(location.href),i.enqueueEvent("$pageview","auto",{url:location.href,path:location.pathname,title:document?.title,referrer:i.getPreviousUrl()}));};n();let r=o=>{let s=history[o];history[o]=function(...u){let c=s.apply(history,u);return n(),c};};r("pushState"),r("replaceState"),window.addEventListener("popstate",n),window.addEventListener("hashchange",n);}function U(i){if(typeof window>"u"||typeof document>"u")return;let e=0,t=0,n=location.href,r=()=>{location.href!==n&&(n=location.href,e=0,t=0);let o=Date.now();if(o-t<250)return;let s=document.documentElement,u=window.scrollY||s.scrollTop||0,c=window.innerHeight||s.clientHeight,C=s.scrollHeight-c;if(C<=0)return;let b=Math.max(0,Math.min(100,Math.round(u/C*100)));b-e<10||(e=b,t=o,i.enqueueEvent("$scroll","auto",{percent:b,pixels:u}));};window.addEventListener("scroll",r,{passive:true});}function L(i){if(typeof document>"u")return;let e=document.visibilityState;document.addEventListener("visibilitychange",()=>{let t=document.visibilityState;t!==e&&(e=t,i.enqueueEvent("$visibility","auto",{state:t}));});}function M(i){if(typeof PerformanceObserver>"u")return;let e=(t,n,r)=>{i.enqueueEvent("$web_vital","auto",{name:t,value:n,...r});};try{new PerformanceObserver(n=>{let r=n.getEntries(),o=r[r.length-1];o&&e("LCP",Math.round(o.startTime));}).observe({type:"largest-contentful-paint",buffered:!0});}catch{}try{let t=0;new PerformanceObserver(r=>{for(let o of r.getEntries())o.hadRecentInput||(t+=o.value);}).observe({type:"layout-shift",buffered:!0}),addEventListener("pagehide",()=>e("CLS",Math.round(t*1e3)/1e3));}catch{}try{new PerformanceObserver(n=>{let r=n.getEntries()[0];r&&e("FID",Math.round(r.processingStart-r.startTime));}).observe({type:"first-input",buffered:!0});}catch{}}var q={pageviews:true,clicks:"all",forms:true,scroll:true,visibility:true,mouseover:true,errors:true,vitals:true,sessions:true};function Q(i){return i===false?null:i===void 0||i===true?q:{...q,...i}}function $(i,e){let t=Q(e.autoCapture);if(!t)return;let n={maskTextSelectors:e.maskTextSelectors,blockSelectors:e.blockSelectors};if(t.pageviews&&R(i),t.clicks){let r=t.clicks==="instrumented"?"instrumented":"all";_(i,r,n);}t.forms&&A(i,n),t.scroll&&U(i),t.visibility&&L(i),t.mouseover&&x(i,n),t.errors&&k(i),t.vitals&&M(i);}function xe(i){let e=new y(i);return $(e,i),e}
|
|
2
2
|
export{xe as createEcho};
|
package/package.json
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mosovn/echo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Echo
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Echo SDK — event tracking for moso products. Works in browser and React Native (auto-picked by bundler).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/
|
|
8
|
+
"url": "https://github.com/LoanFactory-Inc/mosovn-echo",
|
|
9
|
+
"directory": "packages/sdk-web"
|
|
9
10
|
},
|
|
10
11
|
"type": "module",
|
|
11
|
-
"main": "./dist/
|
|
12
|
-
"module": "./dist/
|
|
13
|
-
"types": "./dist/
|
|
12
|
+
"main": "./dist/web.cjs",
|
|
13
|
+
"module": "./dist/web.js",
|
|
14
|
+
"types": "./dist/web.d.ts",
|
|
15
|
+
"react-native": "./dist/native.js",
|
|
14
16
|
"exports": {
|
|
15
17
|
".": {
|
|
16
|
-
"types": "./dist/
|
|
17
|
-
"
|
|
18
|
-
"
|
|
18
|
+
"types": "./dist/web.d.ts",
|
|
19
|
+
"react-native": "./dist/native.js",
|
|
20
|
+
"browser": "./dist/web.js",
|
|
21
|
+
"import": "./dist/web.js",
|
|
22
|
+
"require": "./dist/web.cjs"
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"files": [
|
|
@@ -29,7 +33,27 @@
|
|
|
29
33
|
"lint": "tsc --noEmit",
|
|
30
34
|
"test": "echo \"no tests yet\""
|
|
31
35
|
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@react-native-async-storage/async-storage": ">=1.19.0",
|
|
38
|
+
"react-native": ">=0.72.0",
|
|
39
|
+
"react-native-get-random-values": ">=1.10.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@react-native-async-storage/async-storage": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"react-native": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"react-native-get-random-values": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
32
52
|
"devDependencies": {
|
|
53
|
+
"@react-native-async-storage/async-storage": "^1.23.1",
|
|
54
|
+
"@types/react-native": "^0.73.0",
|
|
55
|
+
"react-native": "^0.74.0",
|
|
56
|
+
"react-native-get-random-values": "^1.11.0",
|
|
33
57
|
"tsup": "^8.3.0",
|
|
34
58
|
"typescript": "^5.6.0"
|
|
35
59
|
},
|
|
@@ -42,6 +66,7 @@
|
|
|
42
66
|
"events",
|
|
43
67
|
"autocapture",
|
|
44
68
|
"moso",
|
|
69
|
+
"react-native",
|
|
45
70
|
"amplitude-alternative"
|
|
46
71
|
]
|
|
47
72
|
}
|