@linkzly/web-sdk 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 ADDED
@@ -0,0 +1,330 @@
1
+ # @linkzly/web-sdk
2
+
3
+ [![Platform](https://img.shields.io/badge/platform-Web-blue.svg)](https://developer.mozilla.org)
4
+ [![TypeScript](https://img.shields.io/badge/language-TypeScript-blue.svg)](https://www.typescriptlang.org)
5
+ [![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](LICENSE)
6
+
7
+ Full-featured browser SDK for Linkzly — event tracking, attribution, session management, and deep linking for web applications. Zero dependencies, < 6KB gzipped.
8
+
9
+ ## Features
10
+
11
+ - **Event Tracking** — Custom events, page views, purchases with automatic batching
12
+ - **Session Management** — Automatic visibility-based sessions (30s timeout, matching mobile SDKs)
13
+ - **Attribution Tracking** — UTM parameters, referrer capture, smart link handling
14
+ - **Deep Link / Smart Link** — Parse and handle Linkzly smart links with SPA support
15
+ - **User Identification** — Persistent visitor ID + custom user ID
16
+ - **Privacy Controls** — DNT/GPC respect, opt-out controls, GDPR-friendly
17
+ - **Offline Queue** — localStorage-backed event queue with retry and sendBeacon on unload
18
+ - **Scroll Depth** — Automatic max scroll depth tracking per page
19
+ - **Outbound Click Tracking** — Automatic external link click tracking
20
+ - **Time on Page** — Automatic session duration tracking
21
+ - **SPA Support** — History API hooks for pushState/popstate navigation
22
+
23
+ ## Requirements
24
+
25
+ - Any modern browser (Chrome 66+, Firefox 60+, Safari 12+, Edge 79+)
26
+ - No dependencies
27
+
28
+ ## Installation
29
+
30
+ ### npm / yarn
31
+
32
+ ```bash
33
+ npm install @linkzly/web-sdk
34
+ # or
35
+ yarn add @linkzly/web-sdk
36
+ ```
37
+
38
+ ### CDN (UMD)
39
+
40
+ ```html
41
+ <script src="https://cdn.linkzly.com/web-sdk/latest/index.js"></script>
42
+ <script>
43
+ LinkzlySDK.configure('YOUR_SDK_KEY');
44
+ </script>
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```typescript
50
+ import LinkzlySDK from '@linkzly/web-sdk';
51
+
52
+ // Initialize
53
+ LinkzlySDK.configure('YOUR_SDK_KEY');
54
+
55
+ // Track events
56
+ LinkzlySDK.trackEvent('button_click', { button_id: 'signup' });
57
+
58
+ // Track purchases
59
+ LinkzlySDK.trackPurchase({
60
+ order_id: 'ORD-123',
61
+ amount: 99.99,
62
+ currency: 'USD',
63
+ });
64
+
65
+ // Set user ID after login
66
+ LinkzlySDK.setUserID('user_12345');
67
+ ```
68
+
69
+ ### Named Imports (Tree-Shakeable)
70
+
71
+ ```typescript
72
+ import { configure, trackEvent, trackPageView, setUserID } from '@linkzly/web-sdk';
73
+
74
+ configure('YOUR_SDK_KEY');
75
+ trackEvent('signup_completed', { method: 'google' });
76
+ ```
77
+
78
+ ## API Reference
79
+
80
+ ### Configuration
81
+
82
+ #### `configure(sdkKey, environment?, options?)`
83
+
84
+ Initialize the SDK. Must be called before any other method.
85
+
86
+ ```typescript
87
+ import LinkzlySDK, { Environment } from '@linkzly/web-sdk';
88
+
89
+ LinkzlySDK.configure('YOUR_SDK_KEY', Environment.PRODUCTION, {
90
+ autoTrackPageViews: false, // opt-in SPA page view tracking (default: false)
91
+ autoTrackSessions: true, // visibility-based sessions (default: true)
92
+ autoExtractUTM: true, // auto-capture UTM params (default: true)
93
+ autoCaptureReferrer: true, // auto-capture document.referrer (default: true)
94
+ respectDNT: true, // honor Do Not Track / GPC (default: true)
95
+ batchSize: 20, // events per batch (default: 20)
96
+ flushIntervalMs: 30000, // flush timer (default: 30s)
97
+ sessionTimeoutMs: 30000, // session timeout (default: 30s)
98
+ debug: false, // console logging (default: false, auto-true in DEVELOPMENT)
99
+ });
100
+ ```
101
+
102
+ **Environments:**
103
+ - `Environment.PRODUCTION` — Production endpoint (default)
104
+ - `Environment.STAGING` — Staging endpoint
105
+ - `Environment.DEVELOPMENT` — Development endpoint with verbose logging
106
+
107
+ ### Event Tracking
108
+
109
+ #### `trackEvent(eventName, parameters?)`
110
+
111
+ Track a custom event.
112
+
113
+ ```typescript
114
+ LinkzlySDK.trackEvent('add_to_cart', {
115
+ product_id: 'SKU-123',
116
+ quantity: 2,
117
+ price: 49.99,
118
+ });
119
+ ```
120
+
121
+ #### `trackPageView(pageUrl?, parameters?)`
122
+
123
+ Track a page view. Called automatically if `autoTrackPageViews: true`.
124
+
125
+ ```typescript
126
+ LinkzlySDK.trackPageView(); // current page
127
+ LinkzlySDK.trackPageView('/products/123', { category: 'electronics' });
128
+ ```
129
+
130
+ #### `trackPurchase(parameters?)`
131
+
132
+ Track a purchase event.
133
+
134
+ ```typescript
135
+ LinkzlySDK.trackPurchase({
136
+ order_id: 'ORD-98765',
137
+ total: 599.99,
138
+ currency: 'USD',
139
+ items: 3,
140
+ });
141
+ ```
142
+
143
+ #### `trackEventBatch(events)`
144
+
145
+ Track multiple events in one call.
146
+
147
+ ```typescript
148
+ LinkzlySDK.trackEventBatch([
149
+ { eventName: 'screen_view', parameters: { screen: 'home' } },
150
+ { eventName: 'button_click', parameters: { button: 'signup' } },
151
+ ]);
152
+ ```
153
+
154
+ #### `flushEvents()`
155
+
156
+ Manually flush all queued events to the server.
157
+
158
+ ```typescript
159
+ await LinkzlySDK.flushEvents();
160
+ ```
161
+
162
+ #### `getPendingEventCount()`
163
+
164
+ Get the number of events waiting in the queue.
165
+
166
+ ```typescript
167
+ const count = LinkzlySDK.getPendingEventCount();
168
+ ```
169
+
170
+ ### Deep Link / Smart Link
171
+
172
+ #### `handleSmartLink(url?)`
173
+
174
+ Parse a URL into deep link data (matching mobile SDK pattern).
175
+
176
+ ```typescript
177
+ const data = LinkzlySDK.handleSmartLink();
178
+ // { url, path, parameters, smartLinkId, clickId }
179
+
180
+ const data = LinkzlySDK.handleSmartLink('https://example.com/product?id=123&slid=abc');
181
+ ```
182
+
183
+ #### `addDeepLinkListener(listener)`
184
+
185
+ Listen for SPA navigation events (requires `autoTrackPageViews: true`).
186
+
187
+ ```typescript
188
+ const unsubscribe = LinkzlySDK.addDeepLinkListener((data) => {
189
+ console.log('Navigation:', data.path, data.parameters);
190
+ });
191
+
192
+ // Cleanup
193
+ unsubscribe();
194
+ ```
195
+
196
+ ### User Management
197
+
198
+ #### `setUserID(userID)` / `getUserID()` / `clearUserID()`
199
+
200
+ ```typescript
201
+ LinkzlySDK.setUserID('user_12345');
202
+ const id = LinkzlySDK.getUserID(); // 'user_12345'
203
+ LinkzlySDK.clearUserID();
204
+ ```
205
+
206
+ #### `getVisitorID()` / `resetVisitorID()`
207
+
208
+ Persistent device/browser identifier (survives across sessions).
209
+
210
+ ```typescript
211
+ const visitorId = LinkzlySDK.getVisitorID(); // UUID persisted in localStorage
212
+ const newId = LinkzlySDK.resetVisitorID(); // generates new UUID
213
+ ```
214
+
215
+ ### Session Management
216
+
217
+ ```typescript
218
+ const sessionId = LinkzlySDK.getSessionId();
219
+ LinkzlySDK.startSession(); // force new session
220
+ LinkzlySDK.endSession(); // end current session
221
+ ```
222
+
223
+ Sessions auto-manage via the Page Visibility API — a new session starts when the page becomes visible after 30+ seconds of being hidden.
224
+
225
+ ### Privacy Controls
226
+
227
+ ```typescript
228
+ LinkzlySDK.setTrackingEnabled(false); // disable all tracking
229
+ const enabled = LinkzlySDK.isTrackingEnabled();
230
+ ```
231
+
232
+ The SDK respects `navigator.doNotTrack` and `navigator.globalPrivacyControl` by default. Set `respectDNT: false` in config to override.
233
+
234
+ ### Cleanup
235
+
236
+ ```typescript
237
+ LinkzlySDK.destroy(); // remove all listeners, flush events, stop timers
238
+ ```
239
+
240
+ ## Framework Examples
241
+
242
+ ### React / Next.js
243
+
244
+ ```tsx
245
+ import { useEffect } from 'react';
246
+ import LinkzlySDK, { Environment } from '@linkzly/web-sdk';
247
+
248
+ export default function App({ children }) {
249
+ useEffect(() => {
250
+ LinkzlySDK.configure('YOUR_SDK_KEY', Environment.PRODUCTION, {
251
+ autoTrackPageViews: true, // enable for Next.js SPA navigation
252
+ });
253
+
254
+ return () => LinkzlySDK.destroy();
255
+ }, []);
256
+
257
+ return <>{children}</>;
258
+ }
259
+ ```
260
+
261
+ ### Vue 3
262
+
263
+ ```typescript
264
+ // main.ts
265
+ import { createApp } from 'vue';
266
+ import LinkzlySDK from '@linkzly/web-sdk';
267
+ import App from './App.vue';
268
+
269
+ LinkzlySDK.configure('YOUR_SDK_KEY');
270
+ createApp(App).mount('#app');
271
+ ```
272
+
273
+ ### Vanilla JS
274
+
275
+ ```html
276
+ <script type="module">
277
+ import LinkzlySDK from '@linkzly/web-sdk';
278
+
279
+ LinkzlySDK.configure('YOUR_SDK_KEY');
280
+
281
+ document.getElementById('buy-btn').addEventListener('click', () => {
282
+ LinkzlySDK.trackEvent('button_click', { button: 'buy' });
283
+ });
284
+ </script>
285
+ ```
286
+
287
+ ## Auto-Tracked Web Features
288
+
289
+ These features use the existing event payload structure (`customData` field) — no database changes required.
290
+
291
+ | Feature | Event Name | Data Collected |
292
+ |---------|-----------|----------------|
293
+ | **Scroll Depth** | `scroll_depth` | `max_depth_percent`, `page_url` |
294
+ | **Time on Page** | `time_on_page` | `duration_seconds`, `page_url` |
295
+ | **Outbound Clicks** | `outbound_click` | `destination_url`, `link_text`, `page_url` |
296
+
297
+ Scroll depth and time on page are sent automatically on page unload via `sendBeacon`. Outbound clicks are tracked in real-time.
298
+
299
+ ## Event Delivery
300
+
301
+ - **Batching**: Up to 20 events per batch (configurable), max 100 per network request
302
+ - **Queue**: Events persisted in localStorage (up to 500, 24h expiry)
303
+ - **Flush triggers**: Batch size reached, 30s timer, `flushEvents()`, page unload
304
+ - **Unload**: `navigator.sendBeacon()` for reliable delivery on page close
305
+ - **Retry**: Exponential backoff (1s, 2s, 4s) with max 3 retries on 5xx/429
306
+
307
+ ## Data Collected
308
+
309
+ When tracking is enabled, the SDK collects:
310
+ - Browser information (user agent, language, screen size, viewport, timezone)
311
+ - Page URL and referrer (external only)
312
+ - UTM parameters from URL
313
+ - Smart link / click attribution IDs
314
+ - Custom event data (provided by your application)
315
+
316
+ **Not collected:**
317
+ - Personal user information (unless explicitly provided via `setUserID`)
318
+ - Location data (beyond IP-based geo-location server-side)
319
+
320
+ > **Note:** For affiliate attribution tracking (cookie-based click IDs, checkout form helpers, S2S conversion payloads), use the separate [`@linkzly/affiliate-web`](../linkzly-affiliate-web) package.
321
+
322
+ ## License
323
+
324
+ MIT
325
+
326
+ ## Support
327
+
328
+ - Documentation: https://docs.linkzly.com
329
+ - Issues: https://github.com/linkzly/linkzly-web-sdk/issues
330
+ - Email: support@linkzly.com
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var x=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var W=(r,e)=>{for(var t in e)x(r,t,{get:e[t],enumerable:!0})},j=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Y(e))!G.call(r,i)&&i!==t&&x(r,i,{get:()=>e[i],enumerable:!(n=X(e,i))||n.enumerable});return r};var Z=r=>j(x({},"__esModule",{value:!0}),r);var De={};W(De,{Environment:()=>f,LinkzlyWebSDK:()=>v,addDeepLinkListener:()=>he,clearUserID:()=>ve,configure:()=>ie,default:()=>Ee,destroy:()=>Ie,endSession:()=>we,flushEvents:()=>ce,getPendingEventCount:()=>de,getSessionId:()=>Se,getUserID:()=>me,getVisitorID:()=>ke,handleSmartLink:()=>ge,isTrackingEnabled:()=>Le,removeAllListeners:()=>pe,resetVisitorID:()=>be,setTrackingEnabled:()=>Te,setUserID:()=>fe,startSession:()=>ye,trackEvent:()=>ae,trackEventBatch:()=>ue,trackFirstVisit:()=>re,trackOpen:()=>se,trackPageView:()=>oe,trackPurchase:()=>le});module.exports=Z(De);var f=(n=>(n[n.PRODUCTION=0]="PRODUCTION",n[n.STAGING=1]="STAGING",n[n.DEVELOPMENT=2]="DEVELOPMENT",n))(f||{});var ee={0:"https://ske.linkzly.com",1:"https://ske-staging.linkzly.com",2:"https://ske-dev.linkzly.com"},d={autoTrackPageViews:!1,autoTrackSessions:!0,autoExtractUTM:!0,autoCaptureReferrer:!0,respectDNT:!0,batchSize:20,flushIntervalMs:3e4,sessionTimeoutMs:3e4,debug:!1};function N(r,e,t){return{sdkKey:r,environment:e,baseURL:ee[e],autoTrackPageViews:t?.autoTrackPageViews??d.autoTrackPageViews,autoTrackSessions:t?.autoTrackSessions??d.autoTrackSessions,autoExtractUTM:t?.autoExtractUTM??d.autoExtractUTM,autoCaptureReferrer:t?.autoCaptureReferrer??d.autoCaptureReferrer,respectDNT:t?.respectDNT??d.respectDNT,batchSize:t?.batchSize??d.batchSize,flushIntervalMs:t?.flushIntervalMs??d.flushIntervalMs,sessionTimeoutMs:t?.sessionTimeoutMs??d.sessionTimeoutMs,debug:t?.debug??e===2}}var y=class{constructor(e){this.enabled=e}info(...e){this.enabled&&console.log("[LinkzlySDK]",...e)}debug(...e){this.enabled&&console.debug("[LinkzlySDK]",...e)}warn(...e){this.enabled&&console.warn("[LinkzlySDK]",...e)}error(...e){console.error("[LinkzlySDK]",...e)}};function z(){let r=typeof navigator<"u"?navigator:null,e=typeof window<"u"?window:null,t=typeof document<"u"?document:null,n=typeof window<"u"?window.screen:null;return{userAgent:r?.userAgent??"unknown",language:r?.language??"unknown",languages:r?.languages?.join(",")??"",screenSize:n?`${n.width}x${n.height}`:"unknown",viewportSize:e?`${e.innerWidth}x${e.innerHeight}`:"unknown",timezone:Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone??"unknown",timezoneOffset:new Date().getTimezoneOffset(),colorDepth:n?.colorDepth??0,pixelRatio:e?.devicePixelRatio??1,platform:r?.platform??"unknown",cookiesEnabled:r?.cookieEnabled??!1,online:r?.onLine??!0,referrer:t?.referrer??"",pageUrl:e?.location?.href??""}}function O(r){return{userAgent:r.userAgent,language:r.language,languages:r.languages,screenSize:r.screenSize,viewportSize:r.viewportSize,timezone:r.timezone,timezoneOffset:r.timezoneOffset,colorDepth:r.colorDepth,pixelRatio:r.pixelRatio,platform:r.platform,cookiesEnabled:r.cookiesEnabled,online:r.online,referrer:r.referrer,pageUrl:r.pageUrl}}function l(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return(r==="x"?e:e&3|8).toString(16)})}function m(r){if(!r)return!1;try{let e="__lz_test__";return r.setItem(e,"1"),r.removeItem(e),!0}catch{return!1}}function h(r){if(typeof localStorage>"u"||!m(localStorage))return null;try{return localStorage.getItem("lz_"+r)}catch{return null}}function c(r,e){if(!(typeof localStorage>"u"||!m(localStorage)))try{localStorage.setItem("lz_"+r,e)}catch{}}function q(r){if(!(typeof localStorage>"u"||!m(localStorage)))try{localStorage.removeItem("lz_"+r)}catch{}}function p(r){if(typeof sessionStorage>"u"||!m(sessionStorage))return null;try{return sessionStorage.getItem("lz_"+r)}catch{return null}}function u(r,e){if(!(typeof sessionStorage>"u"||!m(sessionStorage)))try{sessionStorage.setItem("lz_"+r,e)}catch{}}function R(r){if(!(typeof sessionStorage>"u"||!m(sessionStorage)))try{sessionStorage.removeItem("lz_"+r)}catch{}}function B(r){let e=h(r);if(!e)return null;try{return JSON.parse(e)}catch{return null}}function H(r,e){try{c(r,JSON.stringify(e))}catch{}}var w=3,F=1e3,te="Bearer linkzly-production-shared-secret-key-2024-secure-min-64",S=class{constructor(e,t){this.config=e,this.logger=t}async sendEvent(e){let t=`${this.config.baseURL}/v1/sdk/events`;return this.logger.debug("Sending event:",e.eventType,e.eventName??""),this.postJSON(t,e)}async sendBatch(e){if(e.length===0)return{success:!0};let t=`${this.config.baseURL}/v1/sdk/events/batch`,n={type:"sdk_event",events:e,batchId:l(),clientTimestamp:Date.now()};return this.logger.debug(`Sending batch of ${e.length} events`),this.postJSON(t,n)}sendBeacon(e){if(e.length===0)return!0;if(typeof navigator>"u"||!navigator.sendBeacon)return!1;let t=`${this.config.baseURL}/v1/sdk/events/batch`,n={type:"sdk_event",events:e,batchId:l(),clientTimestamp:Date.now()};try{let i=new Blob([JSON.stringify(n)],{type:"application/json"}),o=navigator.sendBeacon(t,i);return this.logger.debug(`Beacon ${o?"accepted":"rejected"}: ${e.length} events`),o}catch(i){return this.logger.error("sendBeacon failed:",i),!1}}async postJSON(e,t,n=0){try{let i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","X-Linkzly-SDK":"web/1.0.0",Authorization:te},body:JSON.stringify(t),keepalive:!0});if(!i.ok){if((i.status>=500||i.status===429)&&n<w){let a=F*Math.pow(2,n);return this.logger.warn(`HTTP ${i.status}, retrying in ${a}ms (attempt ${n+1}/${w})`),await this.sleep(a),this.postJSON(e,t,n+1)}return this.logger.error(`HTTP ${i.status}: ${i.statusText}`),{success:!1,message:`HTTP ${i.status}`}}let o=await i.json().catch(()=>({}));return this.logger.debug("Response:",i.status),{success:!0,...o}}catch(i){if(n<w){let o=F*Math.pow(2,n);return this.logger.warn(`Network error, retrying in ${o}ms (attempt ${n+1}/${w}):`,i),await this.sleep(o),this.postJSON(e,t,n+1)}return this.logger.error("Network request failed after retries:",i),{success:!1,message:String(i)}}}sleep(e){return new Promise(t=>setTimeout(t,e))}};var V="event_queue",C=500,$=100,T=class{constructor(e,t,n){this.queue=[];this.flushTimer=null;this.isFlushing=!1;this.config=e,this.logger=t,this.network=n,this.restoreQueue(),this.startFlushTimer(),typeof window<"u"&&(window.addEventListener("beforeunload",()=>this.flushBeacon()),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flushBeacon()}))}enqueue(e){let t={id:l(),payload:e,createdAt:Date.now(),retryCount:0};this.queue.push(t),this.queue.length>C&&(this.queue=this.queue.slice(-C)),this.persistQueue(),this.logger.debug(`Event queued (${this.queue.length} pending): ${e.eventType}/${e.eventName??""}`),this.queue.length>=this.config.batchSize&&this.flush().catch(n=>this.logger.error("Auto-flush failed:",n))}async flush(){if(this.isFlushing||this.queue.length===0)return!0;this.isFlushing=!0;try{let e=this.queue.slice(0,$),t=e.map(i=>i.payload);if((await this.network.sendBatch(t)).success){let i=new Set(e.map(o=>o.id));return this.queue=this.queue.filter(o=>!i.has(o.id)),this.persistQueue(),this.logger.debug(`Flushed ${e.length} events, ${this.queue.length} remaining`),!0}return this.logger.warn(`Batch send failed, ${this.queue.length} events remain in queue`),!1}finally{this.isFlushing=!1}}getPendingCount(){return this.queue.length}destroy(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null)}startFlushTimer(){this.flushTimer||(this.flushTimer=setInterval(()=>{this.flush().catch(e=>this.logger.error("Periodic flush failed:",e))},this.config.flushIntervalMs))}flushBeacon(){if(this.queue.length===0)return;let e=this.queue.slice(0,$),t=e.map(i=>i.payload);if(this.network.sendBeacon(t)){let i=new Set(e.map(o=>o.id));this.queue=this.queue.filter(o=>!i.has(o.id)),this.persistQueue()}}persistQueue(){let e=this.queue.slice(-C);H(V,e)}restoreQueue(){let e=B(V);if(e&&Array.isArray(e)){let t=Date.now()-864e5;this.queue=e.filter(n=>n.createdAt>t),this.queue.length!==e.length&&(this.logger.debug(`Pruned ${e.length-this.queue.length} expired events from queue`),this.persistQueue()),this.queue.length>0&&this.logger.debug(`Restored ${this.queue.length} queued events from storage`)}}};var U="session_id",L="session_last_active",K="first_visit_done",I=class{constructor(e,t){this.callback=null;this.visibilityHandler=null;this.config=e,this.logger=t,this.lastActiveTime=Date.now();let n=p(U),i=p(L);n&&i?Date.now()-parseInt(i,10)<this.config.sessionTimeoutMs?(this.sessionId=n,this.lastActiveTime=parseInt(i,10),this.logger.debug("Resumed session:",this.sessionId)):this.sessionId=this.createNewSession():this.sessionId=this.createNewSession(),this.config.autoTrackSessions&&this.setupVisibilityTracking()}getSessionId(){return this.sessionId}isFirstVisit(){return h(K)===null}markFirstVisitDone(){c(K,String(Date.now()))}onSessionEvent(e){this.callback=e}touch(){this.lastActiveTime=Date.now(),u(L,String(this.lastActiveTime))}startSession(){return this.callback&&this.callback("end",this.sessionId),this.sessionId=this.createNewSession(),this.sessionId}endSession(){this.callback&&this.callback("end",this.sessionId),R(U),R(L)}destroy(){this.visibilityHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null)}createNewSession(){let e=l();return u(U,e),this.lastActiveTime=Date.now(),u(L,String(this.lastActiveTime)),this.logger.debug("New session started:",e),this.callback&&this.callback("start",e),e}setupVisibilityTracking(){typeof document>"u"||(this.visibilityHandler=()=>{if(document.visibilityState==="visible"){let e=Date.now()-this.lastActiveTime;e>=this.config.sessionTimeoutMs?(this.logger.debug(`Session timeout (${Math.round(e/1e3)}s idle), starting new session`),this.sessionId=this.createNewSession()):this.touch()}else this.touch()},document.addEventListener("visibilitychange",this.visibilityHandler))}};var Q="utm_",E="referrer",J="landing_page",ne=[{key:"utmSource",urlParam:"utm_source"},{key:"utmMedium",urlParam:"utm_medium"},{key:"utmCampaign",urlParam:"utm_campaign"},{key:"utmTerm",urlParam:"utm_term"},{key:"utmContent",urlParam:"utm_content"}],D=class{constructor(e,t){this.smartLinkId=null;this.clickId=null;this.deepLinkListeners=new Set;this.popStateHandler=null;this.config=e,this.logger=t,this.utm=this.initUTM(),this.referrer=this.initReferrer(),this.landingPage=this.initLandingPage(),this.initSmartLinkParams(),this.config.autoTrackPageViews&&this.setupSPATracking()}getUTM(){return{...this.utm}}getReferrer(){return this.referrer}getLandingPage(){return this.landingPage}getSmartLinkId(){return this.smartLinkId}getClickId(){return this.clickId}getAttributionData(){return{utm:this.getUTM(),referrer:this.referrer,smartLinkId:this.smartLinkId,clickId:this.clickId,landingPage:this.landingPage}}parseDeepLink(e){let t=e??(typeof window<"u"?window.location.href:""),n={},i="/";try{let g=new URL(t,"https://placeholder.com");i=g.pathname,g.searchParams.forEach((b,k)=>{n[k]=b})}catch{let g=t.split("?");g[0]&&(i=g[0].match(/^[^:]+:\/\/[^/]+(\/.*)?$/)?.[1]??"/"),g[1]&&g[1].split("&").forEach(b=>{let[k,P]=b.split("=");if(k&&P)try{n[decodeURIComponent(k)]=decodeURIComponent(P)}catch{n[k]=P}})}let o=n.slid??n.smartLinkId??null,a=n.cid??n.clickId??null;return delete n.slid,delete n.smartLinkId,delete n.cid,delete n.clickId,{url:t,path:i,parameters:n,smartLinkId:o,clickId:a}}addDeepLinkListener(e){return this.deepLinkListeners.add(e),()=>{this.deepLinkListeners.delete(e)}}removeAllListeners(){this.deepLinkListeners.clear()}destroy(){this.deepLinkListeners.clear(),typeof window<"u"&&this.popStateHandler&&window.removeEventListener("popstate",this.popStateHandler)}initUTM(){let e={utmSource:null,utmMedium:null,utmCampaign:null,utmTerm:null,utmContent:null};if(!this.config.autoExtractUTM)return e;let t=typeof window<"u"?new URLSearchParams(window.location.search):null;for(let{key:n,urlParam:i}of ne){let o=t?.get(i)??null;o?(e[n]=o,u(Q+i,o)):e[n]=p(Q+i)}return e.utmSource&&this.logger.debug("UTM parameters captured:",e),e}initReferrer(){if(!this.config.autoCaptureReferrer)return null;let e=p(E);if(e!==null)return e||null;let t=typeof document<"u"?document.referrer:"";if(t){try{let n=new URL(t).hostname,i=typeof window<"u"?window.location.hostname:"";if(n===i)return u(E,""),null}catch{}return u(E,t),this.logger.debug("Referrer captured:",t),t}return u(E,""),null}initLandingPage(){let e=p(J);if(e)return e;let t=typeof window<"u"?window.location.href:null;return t&&u(J,t),t}initSmartLinkParams(){if(typeof window>"u")return;let e=new URLSearchParams(window.location.search);this.smartLinkId=e.get("slid")??e.get("smartLinkId")??null,this.clickId=e.get("cid")??e.get("clickId")??null,this.smartLinkId&&this.logger.debug("Smart link ID captured:",this.smartLinkId)}setupSPATracking(){if(typeof window>"u")return;let e=history.pushState.bind(history),t=history.replaceState.bind(history),n=()=>{let i=this.parseDeepLink();this.deepLinkListeners.forEach(o=>{try{o(i)}catch(a){this.logger.error("Deep link listener error:",a)}})};history.pushState=(...i)=>{e(...i),n()},history.replaceState=(...i)=>{t(...i),n()},this.popStateHandler=n,window.addEventListener("popstate",this.popStateHandler)}};var _="visitor_id",M="user_id",A="tracking_enabled",v=class{constructor(){this.config=null;this.isConfigured=!1;this.scrollDepthHandler=null;this.maxScrollDepth=0;this.pageEntryTime=0;this.outboundClickHandler=null}configure(e,t=0,n){if(this.isConfigured){this.logger?.warn("SDK already configured, ignoring reconfiguration");return}this.config=N(e,t,n),this.logger=new y(this.config.debug),this.config.respectDNT&&this.isDNTEnabled()&&(this.logger.info("DNT/GPC detected \u2014 tracking disabled by default"),c(A,"false")),this.network=new S(this.config,this.logger),this.eventQueue=new T(this.config,this.logger,this.network),this.session=new I(this.config,this.logger),this.attribution=new D(this.config,this.logger),this.session.onSessionEvent((i,o)=>{i==="start"&&(this.logger.debug("Session started, auto-tracking open"),this.trackOpen().catch(a=>this.logger.error("Auto trackOpen failed:",a)))}),this.getVisitorID(),this.isConfigured=!0,this.logger.info(`SDK configured (env: ${f[t]}, key: ${e.substring(0,8)}...)`),this.session.isFirstVisit()&&(this.trackFirstVisit().catch(i=>this.logger.error("trackFirstVisit failed:",i)),this.session.markFirstVisitDone()),this.config.autoTrackPageViews&&this.trackPageView().catch(i=>this.logger.error("Auto trackPageView failed:",i)),this.setupScrollDepthTracking(),this.setupOutboundClickTracking(),this.pageEntryTime=Date.now()}async trackFirstVisit(){this.ensureConfigured();let e=this.buildPayload("install",null);this.eventQueue.enqueue(e),this.logger.info("First visit tracked")}async trackOpen(){this.ensureConfigured();let e=this.buildPayload("open",null);this.eventQueue.enqueue(e),this.logger.debug("App open tracked")}async trackPageView(e,t){this.ensureConfigured();let n=e??(typeof window<"u"?window.location.href:""),i=this.buildPayload("page_view","page_view",{page_url:n,page_title:typeof document<"u"?document.title:"",...t});this.eventQueue.enqueue(i),this.session.touch(),this.logger.debug("Page view tracked:",n)}async trackEvent(e,t){this.ensureConfigured();let n=this.buildPayload("custom",e,t);this.eventQueue.enqueue(n),this.session.touch(),this.logger.debug("Event tracked:",e)}async trackPurchase(e){this.ensureConfigured();let t=this.buildPayload("purchase","purchase",e);this.eventQueue.enqueue(t),this.session.touch(),this.logger.debug("Purchase tracked")}async trackEventBatch(e){this.ensureConfigured();for(let t of e){let n=this.buildPayload("custom",t.eventName,t.parameters);this.eventQueue.enqueue(n)}return this.session.touch(),this.logger.debug(`Batch of ${e.length} events queued`),!0}async flushEvents(){return this.ensureConfigured(),this.eventQueue.flush()}getPendingEventCount(){return this.ensureConfigured(),this.eventQueue.getPendingCount()}handleSmartLink(e){return this.ensureConfigured(),this.attribution.parseDeepLink(e)}addDeepLinkListener(e){return this.ensureConfigured(),this.attribution.addDeepLinkListener(e)}removeAllListeners(){this.attribution&&this.attribution.removeAllListeners()}setUserID(e){c(M,e),this.logger?.debug("User ID set:",e)}getUserID(){return h(M)}clearUserID(){q(M),this.logger?.debug("User ID cleared")}getVisitorID(){let e=h(_);return e||(e=l(),c(_,e),this.logger?.debug("New visitor ID generated:",e)),e}resetVisitorID(){let e=l();return c(_,e),this.logger?.debug("Visitor ID reset:",e),e}startSession(){return this.ensureConfigured(),this.session.startSession()}endSession(){this.ensureConfigured(),this.trackTimeOnPage(),this.session.endSession()}getSessionId(){return this.ensureConfigured(),this.session.getSessionId()}setTrackingEnabled(e){c(A,String(e)),this.logger?.info("Tracking",e?"enabled":"disabled")}isTrackingEnabled(){return h(A)!=="false"}setupScrollDepthTracking(){typeof window>"u"||typeof document>"u"||(this.maxScrollDepth=0,this.scrollDepthHandler=()=>{let e=window.scrollY||document.documentElement.scrollTop,t=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)-window.innerHeight;if(t<=0)return;let n=Math.round(e/t*100);n>this.maxScrollDepth&&(this.maxScrollDepth=n)},window.addEventListener("scroll",this.scrollDepthHandler,{passive:!0}),window.addEventListener("beforeunload",()=>{if(this.maxScrollDepth>0&&this.isTrackingEnabled()){let e=this.buildPayload("custom","scroll_depth",{max_depth_percent:this.maxScrollDepth,page_url:window.location.href});this.network?.sendBeacon([e])}}))}trackTimeOnPage(){if(!this.pageEntryTime||!this.isTrackingEnabled())return;let e=Math.round((Date.now()-this.pageEntryTime)/1e3);if(e>0){let t=this.buildPayload("custom","time_on_page",{duration_seconds:e,page_url:typeof window<"u"?window.location.href:""});this.eventQueue?.enqueue(t)}}setupOutboundClickTracking(){typeof document>"u"||typeof window>"u"||(this.outboundClickHandler=e=>{let t=e.target?.closest?.("a");if(t?.href)try{let n=new URL(t.href).hostname,i=window.location.hostname;if(n!==i&&this.isTrackingEnabled()){let o=this.buildPayload("custom","outbound_click",{destination_url:t.href,link_text:t.textContent?.trim()?.substring(0,100)??"",page_url:window.location.href});this.eventQueue?.enqueue(o)}}catch{}},document.addEventListener("click",this.outboundClickHandler))}destroy(){this.trackTimeOnPage(),this.scrollDepthHandler&&typeof window<"u"&&(window.removeEventListener("scroll",this.scrollDepthHandler),this.scrollDepthHandler=null),this.outboundClickHandler&&typeof document<"u"&&(document.removeEventListener("click",this.outboundClickHandler),this.outboundClickHandler=null),this.eventQueue?.destroy(),this.session?.destroy(),this.attribution?.destroy(),this.isConfigured=!1,this.logger?.info("SDK destroyed")}ensureConfigured(){if(!this.isConfigured||!this.config)throw new Error("LinkzlySDK is not configured. Call LinkzlySDK.configure() first.")}buildPayload(e,t,n){if(!this.config)throw new Error("SDK not configured");if(!this.isTrackingEnabled())return{type:"sdk_event",sdkKey:this.config.sdkKey,eventType:e,eventName:t,appUserId:null,sessionId:"",visitorId:"",platform:"web",deviceFingerprint:{},deepLinkMatched:!1,deepLinkData:null,customData:null,utmSource:null,utmMedium:null,utmCampaign:null,utmTerm:null,utmContent:null,smartLinkId:null,clickId:null,timestamp:Date.now()};let i=z(),o=this.attribution.getUTM(),a=this.attribution.getAttributionData();return{type:"sdk_event",sdkKey:this.config.sdkKey,eventType:e,eventName:t,appUserId:this.getUserID(),sessionId:this.session.getSessionId(),visitorId:this.getVisitorID(),platform:"web",deviceFingerprint:O(i),deepLinkMatched:!!(a.smartLinkId||a.clickId),deepLinkData:a.smartLinkId||a.clickId?{...a.smartLinkId?{slid:a.smartLinkId}:{},...a.clickId?{cid:a.clickId}:{}}:null,customData:n??null,utmSource:o.utmSource,utmMedium:o.utmMedium,utmCampaign:o.utmCampaign,utmTerm:o.utmTerm,utmContent:o.utmContent,smartLinkId:a.smartLinkId,clickId:a.clickId,timestamp:Date.now()}}isDNTEnabled(){return typeof navigator>"u"?!1:navigator.doNotTrack==="1"||navigator.globalPrivacyControl===!0}};var s=new v,ie=s.configure.bind(s),re=s.trackFirstVisit.bind(s),se=s.trackOpen.bind(s),oe=s.trackPageView.bind(s),ae=s.trackEvent.bind(s),le=s.trackPurchase.bind(s),ue=s.trackEventBatch.bind(s),ce=s.flushEvents.bind(s),de=s.getPendingEventCount.bind(s),ge=s.handleSmartLink.bind(s),he=s.addDeepLinkListener.bind(s),pe=s.removeAllListeners.bind(s),fe=s.setUserID.bind(s),me=s.getUserID.bind(s),ve=s.clearUserID.bind(s),ke=s.getVisitorID.bind(s),be=s.resetVisitorID.bind(s),ye=s.startSession.bind(s),we=s.endSession.bind(s),Se=s.getSessionId.bind(s),Te=s.setTrackingEnabled.bind(s),Le=s.isTrackingEnabled.bind(s),Ie=s.destroy.bind(s),Ee=s;0&&(module.exports={Environment,LinkzlyWebSDK,addDeepLinkListener,clearUserID,configure,destroy,endSession,flushEvents,getPendingEventCount,getSessionId,getUserID,getVisitorID,handleSmartLink,isTrackingEnabled,removeAllListeners,resetVisitorID,setTrackingEnabled,setUserID,startSession,trackEvent,trackEventBatch,trackFirstVisit,trackOpen,trackPageView,trackPurchase});
@@ -0,0 +1,163 @@
1
+ declare enum Environment {
2
+ PRODUCTION = 0,
3
+ STAGING = 1,
4
+ DEVELOPMENT = 2
5
+ }
6
+ interface LinkzlyWebConfig {
7
+ /** Auto-track page views on SPA navigation (default: false — industry standard opt-in) */
8
+ autoTrackPageViews?: boolean;
9
+ /** Auto-track session start/end via visibility API (default: true) */
10
+ autoTrackSessions?: boolean;
11
+ /** Auto-extract UTM parameters from URL (default: true) */
12
+ autoExtractUTM?: boolean;
13
+ /** Auto-capture referrer (default: true) */
14
+ autoCaptureReferrer?: boolean;
15
+ /** Respect navigator.doNotTrack / globalPrivacyControl (default: true) */
16
+ respectDNT?: boolean;
17
+ /** Event batch size before auto-flush (default: 20) */
18
+ batchSize?: number;
19
+ /** Flush interval in milliseconds (default: 30000) */
20
+ flushIntervalMs?: number;
21
+ /** Session timeout in milliseconds (default: 30000 — 30s, matching mobile SDKs) */
22
+ sessionTimeoutMs?: number;
23
+ /** Enable debug logging (default: false) */
24
+ debug?: boolean;
25
+ }
26
+ type EventType = 'install' | 'open' | 'page_view' | 'purchase' | 'custom';
27
+ interface EventParameters {
28
+ [key: string]: string | number | boolean | null | undefined | EventParameters | Array<string | number | boolean>;
29
+ }
30
+ interface BatchEvent {
31
+ eventName: string;
32
+ parameters?: EventParameters;
33
+ }
34
+ interface TrackingPayload {
35
+ type: 'sdk_event';
36
+ sdkKey: string;
37
+ eventType: EventType;
38
+ eventName: string | null;
39
+ appUserId: string | null;
40
+ sessionId: string;
41
+ visitorId: string;
42
+ platform: 'web';
43
+ deviceFingerprint: Record<string, string | number | boolean | null>;
44
+ deepLinkMatched: boolean;
45
+ deepLinkData: Record<string, string> | null;
46
+ customData: EventParameters | null;
47
+ utmSource: string | null;
48
+ utmMedium: string | null;
49
+ utmCampaign: string | null;
50
+ utmTerm: string | null;
51
+ utmContent: string | null;
52
+ smartLinkId: string | null;
53
+ clickId: string | null;
54
+ timestamp: number;
55
+ }
56
+ interface DeepLinkData {
57
+ url: string;
58
+ path: string;
59
+ parameters: Record<string, string>;
60
+ smartLinkId: string | null;
61
+ clickId: string | null;
62
+ }
63
+ type DeepLinkListener = (data: DeepLinkData) => void;
64
+ interface UTMParameters {
65
+ utmSource: string | null;
66
+ utmMedium: string | null;
67
+ utmCampaign: string | null;
68
+ utmTerm: string | null;
69
+ utmContent: string | null;
70
+ }
71
+ interface AttributionData {
72
+ utm: UTMParameters;
73
+ referrer: string | null;
74
+ smartLinkId: string | null;
75
+ clickId: string | null;
76
+ landingPage: string | null;
77
+ }
78
+ interface BrowserFingerprint {
79
+ userAgent: string;
80
+ language: string;
81
+ languages: string;
82
+ screenSize: string;
83
+ viewportSize: string;
84
+ timezone: string;
85
+ timezoneOffset: number;
86
+ colorDepth: number;
87
+ pixelRatio: number;
88
+ platform: string;
89
+ cookiesEnabled: boolean;
90
+ online: boolean;
91
+ referrer: string;
92
+ pageUrl: string;
93
+ }
94
+
95
+ declare class LinkzlyWebSDK {
96
+ private config;
97
+ private logger;
98
+ private network;
99
+ private eventQueue;
100
+ private session;
101
+ private attribution;
102
+ private isConfigured;
103
+ private scrollDepthHandler;
104
+ private maxScrollDepth;
105
+ private pageEntryTime;
106
+ private outboundClickHandler;
107
+ configure(sdkKey: string, environment?: Environment, options?: LinkzlyWebConfig): void;
108
+ trackFirstVisit(): Promise<void>;
109
+ trackOpen(): Promise<void>;
110
+ trackPageView(pageUrl?: string, params?: EventParameters): Promise<void>;
111
+ trackEvent(eventName: string, parameters?: EventParameters): Promise<void>;
112
+ trackPurchase(parameters?: EventParameters): Promise<void>;
113
+ trackEventBatch(events: BatchEvent[]): Promise<boolean>;
114
+ flushEvents(): Promise<boolean>;
115
+ getPendingEventCount(): number;
116
+ handleSmartLink(url?: string): DeepLinkData;
117
+ addDeepLinkListener(listener: DeepLinkListener): () => void;
118
+ removeAllListeners(): void;
119
+ setUserID(userID: string): void;
120
+ getUserID(): string | null;
121
+ clearUserID(): void;
122
+ getVisitorID(): string;
123
+ resetVisitorID(): string;
124
+ startSession(): string;
125
+ endSession(): void;
126
+ getSessionId(): string;
127
+ setTrackingEnabled(enabled: boolean): void;
128
+ isTrackingEnabled(): boolean;
129
+ private setupScrollDepthTracking;
130
+ private trackTimeOnPage;
131
+ private setupOutboundClickTracking;
132
+ destroy(): void;
133
+ private ensureConfigured;
134
+ private buildPayload;
135
+ private isDNTEnabled;
136
+ }
137
+
138
+ declare const instance: LinkzlyWebSDK;
139
+ declare const configure: (sdkKey: string, environment?: Environment, options?: LinkzlyWebConfig) => void;
140
+ declare const trackFirstVisit: () => Promise<void>;
141
+ declare const trackOpen: () => Promise<void>;
142
+ declare const trackPageView: (pageUrl?: string, params?: EventParameters) => Promise<void>;
143
+ declare const trackEvent: (eventName: string, parameters?: EventParameters) => Promise<void>;
144
+ declare const trackPurchase: (parameters?: EventParameters) => Promise<void>;
145
+ declare const trackEventBatch: (events: BatchEvent[]) => Promise<boolean>;
146
+ declare const flushEvents: () => Promise<boolean>;
147
+ declare const getPendingEventCount: () => number;
148
+ declare const handleSmartLink: (url?: string) => DeepLinkData;
149
+ declare const addDeepLinkListener: (listener: DeepLinkListener) => () => void;
150
+ declare const removeAllListeners: () => void;
151
+ declare const setUserID: (userID: string) => void;
152
+ declare const getUserID: () => string | null;
153
+ declare const clearUserID: () => void;
154
+ declare const getVisitorID: () => string;
155
+ declare const resetVisitorID: () => string;
156
+ declare const startSession: () => string;
157
+ declare const endSession: () => void;
158
+ declare const getSessionId: () => string;
159
+ declare const setTrackingEnabled: (enabled: boolean) => void;
160
+ declare const isTrackingEnabled: () => boolean;
161
+ declare const destroy: () => void;
162
+
163
+ export { type AttributionData, type BatchEvent, type BrowserFingerprint, type DeepLinkData, type DeepLinkListener, Environment, type EventParameters, type EventType, type LinkzlyWebConfig, LinkzlyWebSDK, type TrackingPayload, type UTMParameters, addDeepLinkListener, clearUserID, configure, instance as default, destroy, endSession, flushEvents, getPendingEventCount, getSessionId, getUserID, getVisitorID, handleSmartLink, isTrackingEnabled, removeAllListeners, resetVisitorID, setTrackingEnabled, setUserID, startSession, trackEvent, trackEventBatch, trackFirstVisit, trackOpen, trackPageView, trackPurchase };
@@ -0,0 +1,163 @@
1
+ declare enum Environment {
2
+ PRODUCTION = 0,
3
+ STAGING = 1,
4
+ DEVELOPMENT = 2
5
+ }
6
+ interface LinkzlyWebConfig {
7
+ /** Auto-track page views on SPA navigation (default: false — industry standard opt-in) */
8
+ autoTrackPageViews?: boolean;
9
+ /** Auto-track session start/end via visibility API (default: true) */
10
+ autoTrackSessions?: boolean;
11
+ /** Auto-extract UTM parameters from URL (default: true) */
12
+ autoExtractUTM?: boolean;
13
+ /** Auto-capture referrer (default: true) */
14
+ autoCaptureReferrer?: boolean;
15
+ /** Respect navigator.doNotTrack / globalPrivacyControl (default: true) */
16
+ respectDNT?: boolean;
17
+ /** Event batch size before auto-flush (default: 20) */
18
+ batchSize?: number;
19
+ /** Flush interval in milliseconds (default: 30000) */
20
+ flushIntervalMs?: number;
21
+ /** Session timeout in milliseconds (default: 30000 — 30s, matching mobile SDKs) */
22
+ sessionTimeoutMs?: number;
23
+ /** Enable debug logging (default: false) */
24
+ debug?: boolean;
25
+ }
26
+ type EventType = 'install' | 'open' | 'page_view' | 'purchase' | 'custom';
27
+ interface EventParameters {
28
+ [key: string]: string | number | boolean | null | undefined | EventParameters | Array<string | number | boolean>;
29
+ }
30
+ interface BatchEvent {
31
+ eventName: string;
32
+ parameters?: EventParameters;
33
+ }
34
+ interface TrackingPayload {
35
+ type: 'sdk_event';
36
+ sdkKey: string;
37
+ eventType: EventType;
38
+ eventName: string | null;
39
+ appUserId: string | null;
40
+ sessionId: string;
41
+ visitorId: string;
42
+ platform: 'web';
43
+ deviceFingerprint: Record<string, string | number | boolean | null>;
44
+ deepLinkMatched: boolean;
45
+ deepLinkData: Record<string, string> | null;
46
+ customData: EventParameters | null;
47
+ utmSource: string | null;
48
+ utmMedium: string | null;
49
+ utmCampaign: string | null;
50
+ utmTerm: string | null;
51
+ utmContent: string | null;
52
+ smartLinkId: string | null;
53
+ clickId: string | null;
54
+ timestamp: number;
55
+ }
56
+ interface DeepLinkData {
57
+ url: string;
58
+ path: string;
59
+ parameters: Record<string, string>;
60
+ smartLinkId: string | null;
61
+ clickId: string | null;
62
+ }
63
+ type DeepLinkListener = (data: DeepLinkData) => void;
64
+ interface UTMParameters {
65
+ utmSource: string | null;
66
+ utmMedium: string | null;
67
+ utmCampaign: string | null;
68
+ utmTerm: string | null;
69
+ utmContent: string | null;
70
+ }
71
+ interface AttributionData {
72
+ utm: UTMParameters;
73
+ referrer: string | null;
74
+ smartLinkId: string | null;
75
+ clickId: string | null;
76
+ landingPage: string | null;
77
+ }
78
+ interface BrowserFingerprint {
79
+ userAgent: string;
80
+ language: string;
81
+ languages: string;
82
+ screenSize: string;
83
+ viewportSize: string;
84
+ timezone: string;
85
+ timezoneOffset: number;
86
+ colorDepth: number;
87
+ pixelRatio: number;
88
+ platform: string;
89
+ cookiesEnabled: boolean;
90
+ online: boolean;
91
+ referrer: string;
92
+ pageUrl: string;
93
+ }
94
+
95
+ declare class LinkzlyWebSDK {
96
+ private config;
97
+ private logger;
98
+ private network;
99
+ private eventQueue;
100
+ private session;
101
+ private attribution;
102
+ private isConfigured;
103
+ private scrollDepthHandler;
104
+ private maxScrollDepth;
105
+ private pageEntryTime;
106
+ private outboundClickHandler;
107
+ configure(sdkKey: string, environment?: Environment, options?: LinkzlyWebConfig): void;
108
+ trackFirstVisit(): Promise<void>;
109
+ trackOpen(): Promise<void>;
110
+ trackPageView(pageUrl?: string, params?: EventParameters): Promise<void>;
111
+ trackEvent(eventName: string, parameters?: EventParameters): Promise<void>;
112
+ trackPurchase(parameters?: EventParameters): Promise<void>;
113
+ trackEventBatch(events: BatchEvent[]): Promise<boolean>;
114
+ flushEvents(): Promise<boolean>;
115
+ getPendingEventCount(): number;
116
+ handleSmartLink(url?: string): DeepLinkData;
117
+ addDeepLinkListener(listener: DeepLinkListener): () => void;
118
+ removeAllListeners(): void;
119
+ setUserID(userID: string): void;
120
+ getUserID(): string | null;
121
+ clearUserID(): void;
122
+ getVisitorID(): string;
123
+ resetVisitorID(): string;
124
+ startSession(): string;
125
+ endSession(): void;
126
+ getSessionId(): string;
127
+ setTrackingEnabled(enabled: boolean): void;
128
+ isTrackingEnabled(): boolean;
129
+ private setupScrollDepthTracking;
130
+ private trackTimeOnPage;
131
+ private setupOutboundClickTracking;
132
+ destroy(): void;
133
+ private ensureConfigured;
134
+ private buildPayload;
135
+ private isDNTEnabled;
136
+ }
137
+
138
+ declare const instance: LinkzlyWebSDK;
139
+ declare const configure: (sdkKey: string, environment?: Environment, options?: LinkzlyWebConfig) => void;
140
+ declare const trackFirstVisit: () => Promise<void>;
141
+ declare const trackOpen: () => Promise<void>;
142
+ declare const trackPageView: (pageUrl?: string, params?: EventParameters) => Promise<void>;
143
+ declare const trackEvent: (eventName: string, parameters?: EventParameters) => Promise<void>;
144
+ declare const trackPurchase: (parameters?: EventParameters) => Promise<void>;
145
+ declare const trackEventBatch: (events: BatchEvent[]) => Promise<boolean>;
146
+ declare const flushEvents: () => Promise<boolean>;
147
+ declare const getPendingEventCount: () => number;
148
+ declare const handleSmartLink: (url?: string) => DeepLinkData;
149
+ declare const addDeepLinkListener: (listener: DeepLinkListener) => () => void;
150
+ declare const removeAllListeners: () => void;
151
+ declare const setUserID: (userID: string) => void;
152
+ declare const getUserID: () => string | null;
153
+ declare const clearUserID: () => void;
154
+ declare const getVisitorID: () => string;
155
+ declare const resetVisitorID: () => string;
156
+ declare const startSession: () => string;
157
+ declare const endSession: () => void;
158
+ declare const getSessionId: () => string;
159
+ declare const setTrackingEnabled: (enabled: boolean) => void;
160
+ declare const isTrackingEnabled: () => boolean;
161
+ declare const destroy: () => void;
162
+
163
+ export { type AttributionData, type BatchEvent, type BrowserFingerprint, type DeepLinkData, type DeepLinkListener, Environment, type EventParameters, type EventType, type LinkzlyWebConfig, LinkzlyWebSDK, type TrackingPayload, type UTMParameters, addDeepLinkListener, clearUserID, configure, instance as default, destroy, endSession, flushEvents, getPendingEventCount, getSessionId, getUserID, getVisitorID, handleSmartLink, isTrackingEnabled, removeAllListeners, resetVisitorID, setTrackingEnabled, setUserID, startSession, trackEvent, trackEventBatch, trackFirstVisit, trackOpen, trackPageView, trackPurchase };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var v=(n=>(n[n.PRODUCTION=0]="PRODUCTION",n[n.STAGING=1]="STAGING",n[n.DEVELOPMENT=2]="DEVELOPMENT",n))(v||{});var J={0:"https://ske.linkzly.com",1:"https://ske-staging.linkzly.com",2:"https://ske-dev.linkzly.com"},d={autoTrackPageViews:!1,autoTrackSessions:!0,autoExtractUTM:!0,autoCaptureReferrer:!0,respectDNT:!0,batchSize:20,flushIntervalMs:3e4,sessionTimeoutMs:3e4,debug:!1};function A(r,e,t){return{sdkKey:r,environment:e,baseURL:J[e],autoTrackPageViews:t?.autoTrackPageViews??d.autoTrackPageViews,autoTrackSessions:t?.autoTrackSessions??d.autoTrackSessions,autoExtractUTM:t?.autoExtractUTM??d.autoExtractUTM,autoCaptureReferrer:t?.autoCaptureReferrer??d.autoCaptureReferrer,respectDNT:t?.respectDNT??d.respectDNT,batchSize:t?.batchSize??d.batchSize,flushIntervalMs:t?.flushIntervalMs??d.flushIntervalMs,sessionTimeoutMs:t?.sessionTimeoutMs??d.sessionTimeoutMs,debug:t?.debug??e===2}}var y=class{constructor(e){this.enabled=e}info(...e){this.enabled&&console.log("[LinkzlySDK]",...e)}debug(...e){this.enabled&&console.debug("[LinkzlySDK]",...e)}warn(...e){this.enabled&&console.warn("[LinkzlySDK]",...e)}error(...e){console.error("[LinkzlySDK]",...e)}};function N(){let r=typeof navigator<"u"?navigator:null,e=typeof window<"u"?window:null,t=typeof document<"u"?document:null,n=typeof window<"u"?window.screen:null;return{userAgent:r?.userAgent??"unknown",language:r?.language??"unknown",languages:r?.languages?.join(",")??"",screenSize:n?`${n.width}x${n.height}`:"unknown",viewportSize:e?`${e.innerWidth}x${e.innerHeight}`:"unknown",timezone:Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone??"unknown",timezoneOffset:new Date().getTimezoneOffset(),colorDepth:n?.colorDepth??0,pixelRatio:e?.devicePixelRatio??1,platform:r?.platform??"unknown",cookiesEnabled:r?.cookieEnabled??!1,online:r?.onLine??!0,referrer:t?.referrer??"",pageUrl:e?.location?.href??""}}function z(r){return{userAgent:r.userAgent,language:r.language,languages:r.languages,screenSize:r.screenSize,viewportSize:r.viewportSize,timezone:r.timezone,timezoneOffset:r.timezoneOffset,colorDepth:r.colorDepth,pixelRatio:r.pixelRatio,platform:r.platform,cookiesEnabled:r.cookiesEnabled,online:r.online,referrer:r.referrer,pageUrl:r.pageUrl}}function l(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return(r==="x"?e:e&3|8).toString(16)})}function f(r){if(!r)return!1;try{let e="__lz_test__";return r.setItem(e,"1"),r.removeItem(e),!0}catch{return!1}}function h(r){if(typeof localStorage>"u"||!f(localStorage))return null;try{return localStorage.getItem("lz_"+r)}catch{return null}}function c(r,e){if(!(typeof localStorage>"u"||!f(localStorage)))try{localStorage.setItem("lz_"+r,e)}catch{}}function O(r){if(!(typeof localStorage>"u"||!f(localStorage)))try{localStorage.removeItem("lz_"+r)}catch{}}function p(r){if(typeof sessionStorage>"u"||!f(sessionStorage))return null;try{return sessionStorage.getItem("lz_"+r)}catch{return null}}function u(r,e){if(!(typeof sessionStorage>"u"||!f(sessionStorage)))try{sessionStorage.setItem("lz_"+r,e)}catch{}}function x(r){if(!(typeof sessionStorage>"u"||!f(sessionStorage)))try{sessionStorage.removeItem("lz_"+r)}catch{}}function q(r){let e=h(r);if(!e)return null;try{return JSON.parse(e)}catch{return null}}function B(r,e){try{c(r,JSON.stringify(e))}catch{}}var w=3,H=1e3,X="Bearer linkzly-production-shared-secret-key-2024-secure-min-64",S=class{constructor(e,t){this.config=e,this.logger=t}async sendEvent(e){let t=`${this.config.baseURL}/v1/sdk/events`;return this.logger.debug("Sending event:",e.eventType,e.eventName??""),this.postJSON(t,e)}async sendBatch(e){if(e.length===0)return{success:!0};let t=`${this.config.baseURL}/v1/sdk/events/batch`,n={type:"sdk_event",events:e,batchId:l(),clientTimestamp:Date.now()};return this.logger.debug(`Sending batch of ${e.length} events`),this.postJSON(t,n)}sendBeacon(e){if(e.length===0)return!0;if(typeof navigator>"u"||!navigator.sendBeacon)return!1;let t=`${this.config.baseURL}/v1/sdk/events/batch`,n={type:"sdk_event",events:e,batchId:l(),clientTimestamp:Date.now()};try{let i=new Blob([JSON.stringify(n)],{type:"application/json"}),o=navigator.sendBeacon(t,i);return this.logger.debug(`Beacon ${o?"accepted":"rejected"}: ${e.length} events`),o}catch(i){return this.logger.error("sendBeacon failed:",i),!1}}async postJSON(e,t,n=0){try{let i=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","X-Linkzly-SDK":"web/1.0.0",Authorization:X},body:JSON.stringify(t),keepalive:!0});if(!i.ok){if((i.status>=500||i.status===429)&&n<w){let a=H*Math.pow(2,n);return this.logger.warn(`HTTP ${i.status}, retrying in ${a}ms (attempt ${n+1}/${w})`),await this.sleep(a),this.postJSON(e,t,n+1)}return this.logger.error(`HTTP ${i.status}: ${i.statusText}`),{success:!1,message:`HTTP ${i.status}`}}let o=await i.json().catch(()=>({}));return this.logger.debug("Response:",i.status),{success:!0,...o}}catch(i){if(n<w){let o=H*Math.pow(2,n);return this.logger.warn(`Network error, retrying in ${o}ms (attempt ${n+1}/${w}):`,i),await this.sleep(o),this.postJSON(e,t,n+1)}return this.logger.error("Network request failed after retries:",i),{success:!1,message:String(i)}}}sleep(e){return new Promise(t=>setTimeout(t,e))}};var F="event_queue",R=500,V=100,T=class{constructor(e,t,n){this.queue=[];this.flushTimer=null;this.isFlushing=!1;this.config=e,this.logger=t,this.network=n,this.restoreQueue(),this.startFlushTimer(),typeof window<"u"&&(window.addEventListener("beforeunload",()=>this.flushBeacon()),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flushBeacon()}))}enqueue(e){let t={id:l(),payload:e,createdAt:Date.now(),retryCount:0};this.queue.push(t),this.queue.length>R&&(this.queue=this.queue.slice(-R)),this.persistQueue(),this.logger.debug(`Event queued (${this.queue.length} pending): ${e.eventType}/${e.eventName??""}`),this.queue.length>=this.config.batchSize&&this.flush().catch(n=>this.logger.error("Auto-flush failed:",n))}async flush(){if(this.isFlushing||this.queue.length===0)return!0;this.isFlushing=!0;try{let e=this.queue.slice(0,V),t=e.map(i=>i.payload);if((await this.network.sendBatch(t)).success){let i=new Set(e.map(o=>o.id));return this.queue=this.queue.filter(o=>!i.has(o.id)),this.persistQueue(),this.logger.debug(`Flushed ${e.length} events, ${this.queue.length} remaining`),!0}return this.logger.warn(`Batch send failed, ${this.queue.length} events remain in queue`),!1}finally{this.isFlushing=!1}}getPendingCount(){return this.queue.length}destroy(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null)}startFlushTimer(){this.flushTimer||(this.flushTimer=setInterval(()=>{this.flush().catch(e=>this.logger.error("Periodic flush failed:",e))},this.config.flushIntervalMs))}flushBeacon(){if(this.queue.length===0)return;let e=this.queue.slice(0,V),t=e.map(i=>i.payload);if(this.network.sendBeacon(t)){let i=new Set(e.map(o=>o.id));this.queue=this.queue.filter(o=>!i.has(o.id)),this.persistQueue()}}persistQueue(){let e=this.queue.slice(-R);B(F,e)}restoreQueue(){let e=q(F);if(e&&Array.isArray(e)){let t=Date.now()-864e5;this.queue=e.filter(n=>n.createdAt>t),this.queue.length!==e.length&&(this.logger.debug(`Pruned ${e.length-this.queue.length} expired events from queue`),this.persistQueue()),this.queue.length>0&&this.logger.debug(`Restored ${this.queue.length} queued events from storage`)}}};var C="session_id",L="session_last_active",$="first_visit_done",I=class{constructor(e,t){this.callback=null;this.visibilityHandler=null;this.config=e,this.logger=t,this.lastActiveTime=Date.now();let n=p(C),i=p(L);n&&i?Date.now()-parseInt(i,10)<this.config.sessionTimeoutMs?(this.sessionId=n,this.lastActiveTime=parseInt(i,10),this.logger.debug("Resumed session:",this.sessionId)):this.sessionId=this.createNewSession():this.sessionId=this.createNewSession(),this.config.autoTrackSessions&&this.setupVisibilityTracking()}getSessionId(){return this.sessionId}isFirstVisit(){return h($)===null}markFirstVisitDone(){c($,String(Date.now()))}onSessionEvent(e){this.callback=e}touch(){this.lastActiveTime=Date.now(),u(L,String(this.lastActiveTime))}startSession(){return this.callback&&this.callback("end",this.sessionId),this.sessionId=this.createNewSession(),this.sessionId}endSession(){this.callback&&this.callback("end",this.sessionId),x(C),x(L)}destroy(){this.visibilityHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null)}createNewSession(){let e=l();return u(C,e),this.lastActiveTime=Date.now(),u(L,String(this.lastActiveTime)),this.logger.debug("New session started:",e),this.callback&&this.callback("start",e),e}setupVisibilityTracking(){typeof document>"u"||(this.visibilityHandler=()=>{if(document.visibilityState==="visible"){let e=Date.now()-this.lastActiveTime;e>=this.config.sessionTimeoutMs?(this.logger.debug(`Session timeout (${Math.round(e/1e3)}s idle), starting new session`),this.sessionId=this.createNewSession()):this.touch()}else this.touch()},document.addEventListener("visibilitychange",this.visibilityHandler))}};var K="utm_",E="referrer",Q="landing_page",Y=[{key:"utmSource",urlParam:"utm_source"},{key:"utmMedium",urlParam:"utm_medium"},{key:"utmCampaign",urlParam:"utm_campaign"},{key:"utmTerm",urlParam:"utm_term"},{key:"utmContent",urlParam:"utm_content"}],D=class{constructor(e,t){this.smartLinkId=null;this.clickId=null;this.deepLinkListeners=new Set;this.popStateHandler=null;this.config=e,this.logger=t,this.utm=this.initUTM(),this.referrer=this.initReferrer(),this.landingPage=this.initLandingPage(),this.initSmartLinkParams(),this.config.autoTrackPageViews&&this.setupSPATracking()}getUTM(){return{...this.utm}}getReferrer(){return this.referrer}getLandingPage(){return this.landingPage}getSmartLinkId(){return this.smartLinkId}getClickId(){return this.clickId}getAttributionData(){return{utm:this.getUTM(),referrer:this.referrer,smartLinkId:this.smartLinkId,clickId:this.clickId,landingPage:this.landingPage}}parseDeepLink(e){let t=e??(typeof window<"u"?window.location.href:""),n={},i="/";try{let g=new URL(t,"https://placeholder.com");i=g.pathname,g.searchParams.forEach((b,m)=>{n[m]=b})}catch{let g=t.split("?");g[0]&&(i=g[0].match(/^[^:]+:\/\/[^/]+(\/.*)?$/)?.[1]??"/"),g[1]&&g[1].split("&").forEach(b=>{let[m,P]=b.split("=");if(m&&P)try{n[decodeURIComponent(m)]=decodeURIComponent(P)}catch{n[m]=P}})}let o=n.slid??n.smartLinkId??null,a=n.cid??n.clickId??null;return delete n.slid,delete n.smartLinkId,delete n.cid,delete n.clickId,{url:t,path:i,parameters:n,smartLinkId:o,clickId:a}}addDeepLinkListener(e){return this.deepLinkListeners.add(e),()=>{this.deepLinkListeners.delete(e)}}removeAllListeners(){this.deepLinkListeners.clear()}destroy(){this.deepLinkListeners.clear(),typeof window<"u"&&this.popStateHandler&&window.removeEventListener("popstate",this.popStateHandler)}initUTM(){let e={utmSource:null,utmMedium:null,utmCampaign:null,utmTerm:null,utmContent:null};if(!this.config.autoExtractUTM)return e;let t=typeof window<"u"?new URLSearchParams(window.location.search):null;for(let{key:n,urlParam:i}of Y){let o=t?.get(i)??null;o?(e[n]=o,u(K+i,o)):e[n]=p(K+i)}return e.utmSource&&this.logger.debug("UTM parameters captured:",e),e}initReferrer(){if(!this.config.autoCaptureReferrer)return null;let e=p(E);if(e!==null)return e||null;let t=typeof document<"u"?document.referrer:"";if(t){try{let n=new URL(t).hostname,i=typeof window<"u"?window.location.hostname:"";if(n===i)return u(E,""),null}catch{}return u(E,t),this.logger.debug("Referrer captured:",t),t}return u(E,""),null}initLandingPage(){let e=p(Q);if(e)return e;let t=typeof window<"u"?window.location.href:null;return t&&u(Q,t),t}initSmartLinkParams(){if(typeof window>"u")return;let e=new URLSearchParams(window.location.search);this.smartLinkId=e.get("slid")??e.get("smartLinkId")??null,this.clickId=e.get("cid")??e.get("clickId")??null,this.smartLinkId&&this.logger.debug("Smart link ID captured:",this.smartLinkId)}setupSPATracking(){if(typeof window>"u")return;let e=history.pushState.bind(history),t=history.replaceState.bind(history),n=()=>{let i=this.parseDeepLink();this.deepLinkListeners.forEach(o=>{try{o(i)}catch(a){this.logger.error("Deep link listener error:",a)}})};history.pushState=(...i)=>{e(...i),n()},history.replaceState=(...i)=>{t(...i),n()},this.popStateHandler=n,window.addEventListener("popstate",this.popStateHandler)}};var U="visitor_id",_="user_id",M="tracking_enabled",k=class{constructor(){this.config=null;this.isConfigured=!1;this.scrollDepthHandler=null;this.maxScrollDepth=0;this.pageEntryTime=0;this.outboundClickHandler=null}configure(e,t=0,n){if(this.isConfigured){this.logger?.warn("SDK already configured, ignoring reconfiguration");return}this.config=A(e,t,n),this.logger=new y(this.config.debug),this.config.respectDNT&&this.isDNTEnabled()&&(this.logger.info("DNT/GPC detected \u2014 tracking disabled by default"),c(M,"false")),this.network=new S(this.config,this.logger),this.eventQueue=new T(this.config,this.logger,this.network),this.session=new I(this.config,this.logger),this.attribution=new D(this.config,this.logger),this.session.onSessionEvent((i,o)=>{i==="start"&&(this.logger.debug("Session started, auto-tracking open"),this.trackOpen().catch(a=>this.logger.error("Auto trackOpen failed:",a)))}),this.getVisitorID(),this.isConfigured=!0,this.logger.info(`SDK configured (env: ${v[t]}, key: ${e.substring(0,8)}...)`),this.session.isFirstVisit()&&(this.trackFirstVisit().catch(i=>this.logger.error("trackFirstVisit failed:",i)),this.session.markFirstVisitDone()),this.config.autoTrackPageViews&&this.trackPageView().catch(i=>this.logger.error("Auto trackPageView failed:",i)),this.setupScrollDepthTracking(),this.setupOutboundClickTracking(),this.pageEntryTime=Date.now()}async trackFirstVisit(){this.ensureConfigured();let e=this.buildPayload("install",null);this.eventQueue.enqueue(e),this.logger.info("First visit tracked")}async trackOpen(){this.ensureConfigured();let e=this.buildPayload("open",null);this.eventQueue.enqueue(e),this.logger.debug("App open tracked")}async trackPageView(e,t){this.ensureConfigured();let n=e??(typeof window<"u"?window.location.href:""),i=this.buildPayload("page_view","page_view",{page_url:n,page_title:typeof document<"u"?document.title:"",...t});this.eventQueue.enqueue(i),this.session.touch(),this.logger.debug("Page view tracked:",n)}async trackEvent(e,t){this.ensureConfigured();let n=this.buildPayload("custom",e,t);this.eventQueue.enqueue(n),this.session.touch(),this.logger.debug("Event tracked:",e)}async trackPurchase(e){this.ensureConfigured();let t=this.buildPayload("purchase","purchase",e);this.eventQueue.enqueue(t),this.session.touch(),this.logger.debug("Purchase tracked")}async trackEventBatch(e){this.ensureConfigured();for(let t of e){let n=this.buildPayload("custom",t.eventName,t.parameters);this.eventQueue.enqueue(n)}return this.session.touch(),this.logger.debug(`Batch of ${e.length} events queued`),!0}async flushEvents(){return this.ensureConfigured(),this.eventQueue.flush()}getPendingEventCount(){return this.ensureConfigured(),this.eventQueue.getPendingCount()}handleSmartLink(e){return this.ensureConfigured(),this.attribution.parseDeepLink(e)}addDeepLinkListener(e){return this.ensureConfigured(),this.attribution.addDeepLinkListener(e)}removeAllListeners(){this.attribution&&this.attribution.removeAllListeners()}setUserID(e){c(_,e),this.logger?.debug("User ID set:",e)}getUserID(){return h(_)}clearUserID(){O(_),this.logger?.debug("User ID cleared")}getVisitorID(){let e=h(U);return e||(e=l(),c(U,e),this.logger?.debug("New visitor ID generated:",e)),e}resetVisitorID(){let e=l();return c(U,e),this.logger?.debug("Visitor ID reset:",e),e}startSession(){return this.ensureConfigured(),this.session.startSession()}endSession(){this.ensureConfigured(),this.trackTimeOnPage(),this.session.endSession()}getSessionId(){return this.ensureConfigured(),this.session.getSessionId()}setTrackingEnabled(e){c(M,String(e)),this.logger?.info("Tracking",e?"enabled":"disabled")}isTrackingEnabled(){return h(M)!=="false"}setupScrollDepthTracking(){typeof window>"u"||typeof document>"u"||(this.maxScrollDepth=0,this.scrollDepthHandler=()=>{let e=window.scrollY||document.documentElement.scrollTop,t=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)-window.innerHeight;if(t<=0)return;let n=Math.round(e/t*100);n>this.maxScrollDepth&&(this.maxScrollDepth=n)},window.addEventListener("scroll",this.scrollDepthHandler,{passive:!0}),window.addEventListener("beforeunload",()=>{if(this.maxScrollDepth>0&&this.isTrackingEnabled()){let e=this.buildPayload("custom","scroll_depth",{max_depth_percent:this.maxScrollDepth,page_url:window.location.href});this.network?.sendBeacon([e])}}))}trackTimeOnPage(){if(!this.pageEntryTime||!this.isTrackingEnabled())return;let e=Math.round((Date.now()-this.pageEntryTime)/1e3);if(e>0){let t=this.buildPayload("custom","time_on_page",{duration_seconds:e,page_url:typeof window<"u"?window.location.href:""});this.eventQueue?.enqueue(t)}}setupOutboundClickTracking(){typeof document>"u"||typeof window>"u"||(this.outboundClickHandler=e=>{let t=e.target?.closest?.("a");if(t?.href)try{let n=new URL(t.href).hostname,i=window.location.hostname;if(n!==i&&this.isTrackingEnabled()){let o=this.buildPayload("custom","outbound_click",{destination_url:t.href,link_text:t.textContent?.trim()?.substring(0,100)??"",page_url:window.location.href});this.eventQueue?.enqueue(o)}}catch{}},document.addEventListener("click",this.outboundClickHandler))}destroy(){this.trackTimeOnPage(),this.scrollDepthHandler&&typeof window<"u"&&(window.removeEventListener("scroll",this.scrollDepthHandler),this.scrollDepthHandler=null),this.outboundClickHandler&&typeof document<"u"&&(document.removeEventListener("click",this.outboundClickHandler),this.outboundClickHandler=null),this.eventQueue?.destroy(),this.session?.destroy(),this.attribution?.destroy(),this.isConfigured=!1,this.logger?.info("SDK destroyed")}ensureConfigured(){if(!this.isConfigured||!this.config)throw new Error("LinkzlySDK is not configured. Call LinkzlySDK.configure() first.")}buildPayload(e,t,n){if(!this.config)throw new Error("SDK not configured");if(!this.isTrackingEnabled())return{type:"sdk_event",sdkKey:this.config.sdkKey,eventType:e,eventName:t,appUserId:null,sessionId:"",visitorId:"",platform:"web",deviceFingerprint:{},deepLinkMatched:!1,deepLinkData:null,customData:null,utmSource:null,utmMedium:null,utmCampaign:null,utmTerm:null,utmContent:null,smartLinkId:null,clickId:null,timestamp:Date.now()};let i=N(),o=this.attribution.getUTM(),a=this.attribution.getAttributionData();return{type:"sdk_event",sdkKey:this.config.sdkKey,eventType:e,eventName:t,appUserId:this.getUserID(),sessionId:this.session.getSessionId(),visitorId:this.getVisitorID(),platform:"web",deviceFingerprint:z(i),deepLinkMatched:!!(a.smartLinkId||a.clickId),deepLinkData:a.smartLinkId||a.clickId?{...a.smartLinkId?{slid:a.smartLinkId}:{},...a.clickId?{cid:a.clickId}:{}}:null,customData:n??null,utmSource:o.utmSource,utmMedium:o.utmMedium,utmCampaign:o.utmCampaign,utmTerm:o.utmTerm,utmContent:o.utmContent,smartLinkId:a.smartLinkId,clickId:a.clickId,timestamp:Date.now()}}isDNTEnabled(){return typeof navigator>"u"?!1:navigator.doNotTrack==="1"||navigator.globalPrivacyControl===!0}};var s=new k,Te=s.configure.bind(s),Le=s.trackFirstVisit.bind(s),Ie=s.trackOpen.bind(s),Ee=s.trackPageView.bind(s),De=s.trackEvent.bind(s),Pe=s.trackPurchase.bind(s),xe=s.trackEventBatch.bind(s),Re=s.flushEvents.bind(s),Ce=s.getPendingEventCount.bind(s),Ue=s.handleSmartLink.bind(s),_e=s.addDeepLinkListener.bind(s),Me=s.removeAllListeners.bind(s),Ae=s.setUserID.bind(s),Ne=s.getUserID.bind(s),ze=s.clearUserID.bind(s),Oe=s.getVisitorID.bind(s),qe=s.resetVisitorID.bind(s),Be=s.startSession.bind(s),He=s.endSession.bind(s),Fe=s.getSessionId.bind(s),Ve=s.setTrackingEnabled.bind(s),$e=s.isTrackingEnabled.bind(s),Ke=s.destroy.bind(s),Qe=s;export{v as Environment,k as LinkzlyWebSDK,_e as addDeepLinkListener,ze as clearUserID,Te as configure,Qe as default,Ke as destroy,He as endSession,Re as flushEvents,Ce as getPendingEventCount,Fe as getSessionId,Ne as getUserID,Oe as getVisitorID,Ue as handleSmartLink,$e as isTrackingEnabled,Me as removeAllListeners,qe as resetVisitorID,Ve as setTrackingEnabled,Ae as setUserID,Be as startSession,De as trackEvent,xe as trackEventBatch,Le as trackFirstVisit,Ie as trackOpen,Ee as trackPageView,Pe as trackPurchase};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@linkzly/web-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Full-featured browser SDK for Linkzly — event tracking, attribution, sessions, deep linking, and affiliate tracking",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format esm,cjs --dts --minify",
22
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "devDependencies": {
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5.4.0"
28
+ },
29
+ "keywords": [
30
+ "linkzly",
31
+ "analytics",
32
+ "attribution",
33
+ "tracking",
34
+ "deep-linking",
35
+ "affiliate",
36
+ "events",
37
+ "sessions",
38
+ "web-sdk"
39
+ ],
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/linkzly/linkzly-sdks"
44
+ }
45
+ }