userpath-js 0.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/dist/pixel.js ADDED
@@ -0,0 +1 @@
1
+ class n{namespace;constructor({namespace:r}){this.namespace=r}getItem(r){let t=localStorage.getItem(`${this.namespace}.${r}`)||"";try{return JSON.parse(t)}catch(e){return null}}setItem(r,t){localStorage.setItem(`${this.namespace}.${r}`,JSON.stringify(t))}removeItem(r){localStorage.removeItem(`${this.namespace}.${r}`)}}var u=new n({namespace:"userpath"});class c{baseUrl;siteId;queue=[];flushInterval=null;constructor(r){this.baseUrl=r.baseUrl||"",this.siteId=r.siteId,this.flushInterval=window.setInterval(()=>this.flush(),5000)}track(r){if(this.queue.push(r),this.queue.length>=10)this.flush()}flush(){if(this.queue.length===0)return;let r=[...this.queue];this.queue=[],fetch(`${this.baseUrl}/events`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({siteId:this.siteId,sessionId:this.getSessionId(),timestamp:new Date().toISOString(),userAgent:navigator.userAgent,screenWidth:window.screen.width,screenHeight:window.screen.height,language:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,events:r}),keepalive:!0}).catch((t)=>{this.queue=[...r,...this.queue].slice(0,100),console.error("Error sending analytics data:",t)})}getSessionId(){let r=u.getItem("session_id");if(!r)r=this.generateId(),u.setItem("session_id",r);return r}generateId(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(r){let t=Math.random()*16|0;return(r==="x"?t:t&3|8).toString(16)})}}class o{api;constructor(r){this.api=r}trackPageView(){let r={type:"pageview",url:window.location.href,referrer:document.referrer,title:document.title};this.api.track(r)}trackCustomEvent(r,t={}){let e={type:"event",name:r,properties:t};this.api.track(e)}trackPurchase(r){let t={type:"purchase",...r};this.api.track(t)}}class g{api;anonymousId=null;currentUser=null;constructor(r){this.api=r;this.anonymousId=this.getStoredAnonymousId()}getAnonymousId(){if(!this.anonymousId)this.anonymousId=this.api.generateId(),localStorage.setItem("up_anonymous_id",this.anonymousId);return this.anonymousId}getUserId(){return this.currentUser?.userId||null}identify(r){this.currentUser=r,this.api.track({type:"identify",anonymousId:this.getAnonymousId(),...r})}getStoredAnonymousId(){return localStorage.getItem("up_anonymous_id")}}class p{api;events;constructor(r){this.api=r;this.events=new o(r),this.initialize()}initialize(){this.events.trackPageView(),this.setupHistoryChange()}setupHistoryChange(){let{pushState:r,replaceState:t}=history;history.pushState=(...e)=>{r.apply(history,e),this.events.trackPageView()},history.replaceState=(...e)=>{t.apply(history,e),this.events.trackPageView()},window.addEventListener("popstate",()=>{this.events.trackPageView()})}}class m{events;session;identity;constructor(r){let t=new c(r);this.events=new o(t),this.session=new p(t),this.identity=new g(t)}getAnonymousId(){return this.identity.getAnonymousId()}getUserId(){return this.identity.getUserId()}identify(r){this.identity.identify(r)}trackPageView(){this.events.trackPageView()}trackEvent(r,t={}){this.events.trackCustomEvent(r,t)}trackPurchase(r){this.events.trackPurchase(r)}}var w=typeof window!=="undefined";document.addEventListener("DOMContentLoaded",()=>{if(!w)return;try{let r=document.querySelector("[data-up]").getAttribute("data-up")||"",t=document.querySelector("[data-up]").getAttribute("src")||"",a=new URL(t).origin+"/v1",d=new m({siteId:r,baseUrl:a});window.userpath=d,setTimeout(()=>{console.log(r,a,d)},1000)}catch(r){console.error(r)}});
@@ -0,0 +1,83 @@
1
+ // Example code showing how the client SDK should handle user identity resolution
2
+
3
+ import { UserPath } from '../src';
4
+
5
+ // Initialize the tracking SDK
6
+ const userpath = new UserPath({
7
+ siteId: 'site_123', // Required
8
+ baseUrl: 'https://api.userpath.to/v1', // Optional
9
+ });
10
+
11
+ // When a user first visits the site, they get an anonymous ID
12
+ // This is stored in local storage
13
+ const anonymousId = userpath.getAnonymousId();
14
+ console.log('Anonymous visitor ID:', anonymousId);
15
+
16
+ // Example: Track a page view (this is done automatically on initialization)
17
+ // But you can also track it manually if needed
18
+ userpath.trackPageView();
19
+
20
+ // Example: Track a custom event (like clicking the purchase button)
21
+ function onPurchaseButtonClick() {
22
+ userpath.trackEvent('click_purchase', {
23
+ buttonId: 'purchase-btn',
24
+ plan: 'premium',
25
+ });
26
+ }
27
+
28
+ // Example: When user identifies themselves (e.g., signs up or logs in)
29
+ function onUserIdentified(user: { id: string; email: string; name: string }) {
30
+ // Identify the user and connect them to their anonymous activity
31
+ userpath.identify({
32
+ userId: user.id,
33
+ email: user.email,
34
+ name: user.name,
35
+ });
36
+
37
+ console.log('User identified:', userpath.getUserId());
38
+ }
39
+
40
+ // Example: Track a purchase event
41
+ function trackPurchase(purchaseDetails: {
42
+ productId: string;
43
+ price: number;
44
+ currency: string;
45
+ plan: string;
46
+ couponApplied?: string;
47
+ }) {
48
+ userpath.trackPurchase({
49
+ productId: purchaseDetails.productId,
50
+ price: purchaseDetails.price,
51
+ currency: purchaseDetails.currency,
52
+ properties: {
53
+ plan: purchaseDetails.plan,
54
+ couponApplied: purchaseDetails.couponApplied,
55
+ // Include user identification if available
56
+ userId: userpath.getUserId(),
57
+ anonymousId: userpath.getAnonymousId(),
58
+ },
59
+ });
60
+ }
61
+
62
+ // Example usage flow:
63
+
64
+ // 1. SDK is initialized and first pageview is tracked automatically
65
+
66
+ // 2. User clicks purchase button while still anonymous
67
+ onPurchaseButtonClick();
68
+
69
+ // 3. User signs up or logs in
70
+ onUserIdentified({
71
+ id: 'user_123',
72
+ email: 'john@example.com',
73
+ name: 'John Doe',
74
+ });
75
+
76
+ // 4. User completes a purchase (now as an identified user)
77
+ trackPurchase({
78
+ productId: 'plan_premium',
79
+ price: 1337,
80
+ currency: 'USD',
81
+ plan: 'Premium Annual',
82
+ couponApplied: 'WELCOME20',
83
+ });
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "userpath-js",
3
+ "version": "0.0.0",
4
+ "private": false,
5
+ "main": "dist/pixel.js",
6
+ "scripts": {
7
+ "dev": "bun build src/index.ts --outdir ./dist --watch",
8
+ "build": "bun build src/index.ts --outdir ./dist --minify",
9
+ "build.pixel": "bun build src/pixel.ts --outdir ./dist",
10
+ "build.pixel.prod": "bun build src/pixel.ts --outdir ./dist --minify",
11
+ "prepublish": "bun run build.pixel.prod"
12
+ },
13
+ "devDependencies": {
14
+ "bun-types": "latest",
15
+ "typescript": "^5.0.0"
16
+ }
17
+ }
@@ -0,0 +1,92 @@
1
+ import { memory } from '@userpath/core/browser';
2
+
3
+ import { Config } from '../schemas/config';
4
+
5
+ /**
6
+ * Base API client for UserPath SDK
7
+ */
8
+ export class ApiClient {
9
+ protected baseUrl: string;
10
+ protected siteId: string;
11
+ protected queue: any[] = [];
12
+ protected flushInterval: number | null = null;
13
+
14
+ constructor(config: Config) {
15
+ this.baseUrl = config.baseUrl || '';
16
+ this.siteId = config.siteId;
17
+
18
+ // Set up automatic flushing
19
+ this.flushInterval = window.setInterval(() => this.flush(), 5000);
20
+ }
21
+
22
+ /**
23
+ * Add an event to the queue and flush if needed
24
+ */
25
+ public track(event: any): void {
26
+ this.queue.push(event);
27
+
28
+ if (this.queue.length >= 10) {
29
+ this.flush();
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Flush queued events to the server
35
+ */
36
+ protected flush(): void {
37
+ if (this.queue.length === 0) return;
38
+
39
+ const eventsToSend = [...this.queue];
40
+ this.queue = [];
41
+
42
+ fetch(`${this.baseUrl}/events`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ },
47
+ body: JSON.stringify({
48
+ siteId: this.siteId,
49
+ sessionId: this.getSessionId(),
50
+ timestamp: new Date().toISOString(),
51
+ userAgent: navigator.userAgent,
52
+ screenWidth: window.screen.width,
53
+ screenHeight: window.screen.height,
54
+ language: navigator.language,
55
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
56
+ events: eventsToSend,
57
+ }),
58
+ // Use beacon API for more reliable delivery
59
+ keepalive: true,
60
+ }).catch((error) => {
61
+ // If sending fails, add back to queue for retry
62
+ this.queue = [...eventsToSend, ...this.queue].slice(0, 100);
63
+ console.error('Error sending analytics data:', error);
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Get or generate a session ID
69
+ */
70
+ protected getSessionId(): string {
71
+ let sessionId = memory.getItem<string>('session_id');
72
+ if (!sessionId) {
73
+ sessionId = this.generateId();
74
+ memory.setItem('session_id', sessionId);
75
+ }
76
+ return sessionId;
77
+ }
78
+
79
+ /**
80
+ * Generate a UUID v4
81
+ */
82
+ public generateId(): string {
83
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
84
+ /[xy]/g,
85
+ function (c) {
86
+ const r = (Math.random() * 16) | 0;
87
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
88
+ return v.toString(16);
89
+ }
90
+ );
91
+ }
92
+ }
@@ -0,0 +1,55 @@
1
+ import { ApiClient } from './api';
2
+
3
+ /**
4
+ * Events client for handling different types of tracking events
5
+ */
6
+ export class EventsClient {
7
+ constructor(private api: ApiClient) {}
8
+
9
+ /**
10
+ * Track a page view event
11
+ */
12
+ public trackPageView(): void {
13
+ const event = {
14
+ type: 'pageview',
15
+ url: window.location.href,
16
+ referrer: document.referrer,
17
+ title: document.title,
18
+ };
19
+
20
+ this.api.track(event);
21
+ }
22
+
23
+ /**
24
+ * Track a custom event
25
+ */
26
+ public trackCustomEvent(
27
+ name: string,
28
+ properties: Record<string, any> = {}
29
+ ): void {
30
+ const event = {
31
+ type: 'event',
32
+ name,
33
+ properties,
34
+ };
35
+
36
+ this.api.track(event);
37
+ }
38
+
39
+ /**
40
+ * Track a purchase event
41
+ */
42
+ public trackPurchase(params: {
43
+ productId: string;
44
+ price: number;
45
+ currency: string;
46
+ properties?: Record<string, any>;
47
+ }): void {
48
+ const event = {
49
+ type: 'purchase',
50
+ ...params,
51
+ };
52
+
53
+ this.api.track(event);
54
+ }
55
+ }
@@ -0,0 +1,54 @@
1
+ import { UserIdentity } from '../schemas/identity';
2
+ import { ApiClient } from './api';
3
+
4
+ /**
5
+ * Identity client for managing user identification
6
+ */
7
+ export class IdentityClient {
8
+ private anonymousId: string | null = null;
9
+ private currentUser: UserIdentity | null = null;
10
+
11
+ constructor(private api: ApiClient) {
12
+ this.anonymousId = this.getStoredAnonymousId();
13
+ }
14
+
15
+ /**
16
+ * Get the anonymous ID for the current visitor
17
+ */
18
+ public getAnonymousId(): string {
19
+ if (!this.anonymousId) {
20
+ this.anonymousId = this.api.generateId();
21
+ localStorage.setItem('up_anonymous_id', this.anonymousId);
22
+ }
23
+ return this.anonymousId;
24
+ }
25
+
26
+ /**
27
+ * Get the current identified user's ID if available
28
+ */
29
+ public getUserId(): string | null {
30
+ return this.currentUser?.userId || null;
31
+ }
32
+
33
+ /**
34
+ * Identify a user and associate them with their anonymous ID
35
+ */
36
+ public identify(identity: UserIdentity): void {
37
+ // Store the current user
38
+ this.currentUser = identity;
39
+
40
+ // Track the identity mapping
41
+ this.api.track({
42
+ type: 'identify',
43
+ anonymousId: this.getAnonymousId(),
44
+ ...identity,
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Get the stored anonymous ID if it exists
50
+ */
51
+ private getStoredAnonymousId(): string | null {
52
+ return localStorage.getItem('up_anonymous_id');
53
+ }
54
+ }
@@ -0,0 +1,47 @@
1
+ import { ApiClient } from './api';
2
+ import { EventsClient } from './events';
3
+
4
+ /**
5
+ * Session client for managing user sessions and automatic tracking
6
+ */
7
+ export class SessionClient {
8
+ private events: EventsClient;
9
+
10
+ constructor(private api: ApiClient) {
11
+ this.events = new EventsClient(api);
12
+ this.initialize();
13
+ }
14
+
15
+ /**
16
+ * Initialize session tracking
17
+ */
18
+ private initialize(): void {
19
+ // Track initial pageview
20
+ this.events.trackPageView();
21
+
22
+ // Set up history change tracking
23
+ this.setupHistoryChange();
24
+ }
25
+
26
+ /**
27
+ * Set up tracking for history/navigation changes
28
+ */
29
+ private setupHistoryChange(): void {
30
+ const originalPushState = history.pushState;
31
+ const originalReplaceState = history.replaceState;
32
+
33
+ history.pushState = (...args) => {
34
+ originalPushState.apply(history, args);
35
+ this.events.trackPageView();
36
+ };
37
+
38
+ history.replaceState = (...args) => {
39
+ originalReplaceState.apply(history, args);
40
+ this.events.trackPageView();
41
+ };
42
+
43
+ window.addEventListener('popstate', () => {
44
+ this.events.trackPageView();
45
+ });
46
+ }
47
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ import { ApiClient } from './clients/api';
2
+ import { EventsClient } from './clients/events';
3
+ import { IdentityClient } from './clients/identity';
4
+ import { SessionClient } from './clients/session';
5
+ import { Config } from './schemas/config';
6
+ import { UserIdentity } from './schemas/identity';
7
+
8
+ export class UserPath {
9
+ private events: EventsClient;
10
+ private session: SessionClient;
11
+ private identity: IdentityClient;
12
+
13
+ constructor(config: Config) {
14
+ // Initialize clients
15
+ const baseClient = new ApiClient(config);
16
+ this.events = new EventsClient(baseClient);
17
+ this.session = new SessionClient(baseClient);
18
+ this.identity = new IdentityClient(baseClient);
19
+ }
20
+
21
+ /**
22
+ * Get the anonymous ID for the current visitor
23
+ */
24
+ public getAnonymousId(): string {
25
+ return this.identity.getAnonymousId();
26
+ }
27
+
28
+ /**
29
+ * Get the current identified user's ID if available
30
+ */
31
+ public getUserId(): string | null {
32
+ return this.identity.getUserId();
33
+ }
34
+
35
+ /**
36
+ * Identify a user
37
+ */
38
+ public identify(identity: UserIdentity): void {
39
+ this.identity.identify(identity);
40
+ }
41
+
42
+ /**
43
+ * Track a page view event
44
+ */
45
+ public trackPageView(): void {
46
+ this.events.trackPageView();
47
+ }
48
+
49
+ /**
50
+ * Track a custom event
51
+ */
52
+ public trackEvent(name: string, properties: Record<string, any> = {}): void {
53
+ this.events.trackCustomEvent(name, properties);
54
+ }
55
+
56
+ /**
57
+ * Track a purchase event
58
+ */
59
+ public trackPurchase(params: {
60
+ productId: string;
61
+ price: number;
62
+ currency: string;
63
+ properties?: Record<string, any>;
64
+ }): void {
65
+ this.events.trackPurchase(params);
66
+ }
67
+ }
68
+
69
+ // Export types
70
+ export * from './schemas/events';
71
+ export * from './schemas/config';
72
+ export * from './schemas/identity';
package/src/pixel.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { UserPath } from './index';
2
+
3
+ const isBrowser = typeof window !== 'undefined';
4
+
5
+ document.addEventListener('DOMContentLoaded', () => {
6
+ if (!isBrowser) return;
7
+ try {
8
+ const siteId =
9
+ document!.querySelector('[data-up]')!.getAttribute('data-up') || '';
10
+ const src = document!.querySelector('[data-up]')!.getAttribute('src') || '';
11
+ const url = new URL(src);
12
+ const baseUrl = url.origin + '/v1';
13
+
14
+ const userpath = new UserPath({
15
+ siteId,
16
+ baseUrl,
17
+ });
18
+
19
+ // @ts-ignore
20
+ window.userpath = userpath;
21
+
22
+ setTimeout(() => {
23
+ console.log(siteId, baseUrl, userpath);
24
+ }, 1000);
25
+ } catch (err) {
26
+ console.error(err);
27
+ }
28
+ });
@@ -0,0 +1,4 @@
1
+ export interface Config {
2
+ siteId: string;
3
+ baseUrl?: string;
4
+ }
@@ -0,0 +1,31 @@
1
+ export interface PageViewEvent {
2
+ type: 'pageview';
3
+ url: string;
4
+ referrer: string;
5
+ title: string;
6
+ }
7
+
8
+ export interface CustomEvent {
9
+ type: 'event';
10
+ name: string;
11
+ properties: Record<string, any>;
12
+ }
13
+
14
+ export interface PurchaseEvent {
15
+ type: 'purchase';
16
+ productId: string;
17
+ price: number;
18
+ currency: string;
19
+ properties?: Record<string, any>;
20
+ }
21
+
22
+ export interface IdentifyEvent {
23
+ type: 'identify';
24
+ anonymousId: string;
25
+ userId: string;
26
+ email?: string;
27
+ name?: string;
28
+ properties?: Record<string, any>;
29
+ }
30
+
31
+ export type Event = PageViewEvent | CustomEvent | PurchaseEvent | IdentifyEvent;
@@ -0,0 +1,6 @@
1
+ export interface UserIdentity {
2
+ userId: string;
3
+ email?: string;
4
+ name?: string;
5
+ properties?: Record<string, any>;
6
+ }