@umituz/web-traffic 1.0.6

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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -0
  3. package/package.json +61 -0
  4. package/src/domains/affiliate/aggregates/affiliate.aggregate.ts +98 -0
  5. package/src/domains/affiliate/entities/affiliate-visit.entity.ts +52 -0
  6. package/src/domains/affiliate/index.ts +22 -0
  7. package/src/domains/affiliate/repositories/affiliate.repository.interface.ts +26 -0
  8. package/src/domains/affiliate/value-objects/affiliate-id.vo.ts +35 -0
  9. package/src/domains/affiliate/value-objects/site-id.vo.ts +33 -0
  10. package/src/domains/analytics/entities/analytics.entity.ts +33 -0
  11. package/src/domains/analytics/index.ts +18 -0
  12. package/src/domains/analytics/repositories/analytics.repository.interface.ts +19 -0
  13. package/src/domains/conversion/aggregates/order.aggregate.ts +85 -0
  14. package/src/domains/conversion/entities/order-item.entity.ts +27 -0
  15. package/src/domains/conversion/events/conversion-recorded.domain-event.ts +30 -0
  16. package/src/domains/conversion/index.ts +21 -0
  17. package/src/domains/conversion/repositories/conversion.repository.interface.ts +13 -0
  18. package/src/domains/conversion/value-objects/money.vo.ts +55 -0
  19. package/src/domains/tracking/aggregates/session.aggregate.ts +154 -0
  20. package/src/domains/tracking/application/tracking-command.service.ts +109 -0
  21. package/src/domains/tracking/entities/event.entity.ts +48 -0
  22. package/src/domains/tracking/entities/pageview.entity.ts +52 -0
  23. package/src/domains/tracking/events/event-tracked.domain-event.ts +28 -0
  24. package/src/domains/tracking/events/pageview-tracked.domain-event.ts +31 -0
  25. package/src/domains/tracking/index.ts +37 -0
  26. package/src/domains/tracking/repositories/event.repository.interface.ts +29 -0
  27. package/src/domains/tracking/value-objects/device-info.vo.ts +163 -0
  28. package/src/domains/tracking/value-objects/event-id.vo.ts +36 -0
  29. package/src/domains/tracking/value-objects/session-id.vo.ts +36 -0
  30. package/src/domains/tracking/value-objects/utm-parameters.vo.ts +75 -0
  31. package/src/index.ts +16 -0
  32. package/src/infrastructure/analytics/http-analytics.repository.impl.ts +60 -0
  33. package/src/infrastructure/index.ts +19 -0
  34. package/src/infrastructure/repositories/http-event.repository.impl.ts +160 -0
  35. package/src/infrastructure/tracking/web-traffic.service.ts +188 -0
  36. package/src/presentation/context.tsx +43 -0
  37. package/src/presentation/hooks.ts +78 -0
  38. package/src/presentation/index.ts +11 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Web Traffic Service
3
+ * @description Main service for web-traffic (Facade pattern)
4
+ */
5
+
6
+ import { EventId } from '../../domains/tracking/value-objects/event-id.vo';
7
+ import { SessionId } from '../../domains/tracking/value-objects/session-id.vo';
8
+ import { SiteId } from '../../domains/affiliate/value-objects/site-id.vo';
9
+ import { DeviceInfo } from '../../domains/tracking/value-objects/device-info.vo';
10
+ import { Session } from '../../domains/tracking/aggregates/session.aggregate';
11
+ import type { Event } from '../../domains/tracking/entities/event.entity';
12
+ import type { Pageview } from '../../domains/tracking/entities/pageview.entity';
13
+ import type { TrackingCommandResult } from '../../domains/tracking/application/tracking-command.service';
14
+ import { TrackingCommandService } from '../../domains/tracking/application/tracking-command.service';
15
+ import {
16
+ HTTPEventRepository,
17
+ HTTPPageviewRepository,
18
+ LocalSessionRepository,
19
+ } from '../repositories/http-event.repository.impl';
20
+ import { HTTPAnalyticsRepository } from '../analytics/http-analytics.repository.impl';
21
+
22
+ export interface WebTrafficConfig {
23
+ readonly apiKey: string;
24
+ readonly apiUrl?: string;
25
+ readonly autoTrack?: boolean;
26
+ }
27
+
28
+ class WebTrafficService {
29
+ private initialized = false;
30
+ private commandService: TrackingCommandService | null = null;
31
+ private currentSession: Session | null = null;
32
+ private eventRepo: HTTPEventRepository | null = null;
33
+ private pageviewRepo: HTTPPageviewRepository | null = null;
34
+ private sessionRepo: LocalSessionRepository | null = null;
35
+
36
+ initialize(config: WebTrafficConfig): void {
37
+ if (this.initialized) {
38
+ console.warn('WebTrafficService already initialized');
39
+ return;
40
+ }
41
+
42
+ const apiUrl = config.apiUrl ?? 'https://analytics.umituz.com';
43
+
44
+ // Initialize repositories
45
+ this.eventRepo = new HTTPEventRepository({ apiUrl, apiKey: config.apiKey });
46
+ this.pageviewRepo = new HTTPPageviewRepository({ apiUrl, apiKey: config.apiKey });
47
+ this.sessionRepo = new LocalSessionRepository();
48
+
49
+ // Initialize command service
50
+ this.commandService = new TrackingCommandService(
51
+ this.sessionRepo,
52
+ this.eventRepo,
53
+ this.pageviewRepo
54
+ );
55
+
56
+ // Get or create session
57
+ this.initializeSession();
58
+
59
+ // Setup auto-tracking if enabled
60
+ if (config.autoTrack && typeof window !== 'undefined') {
61
+ this.setupAutoTrack();
62
+ }
63
+
64
+ this.initialized = true;
65
+ }
66
+
67
+ isInitialized(): boolean {
68
+ return this.initialized;
69
+ }
70
+
71
+ async trackEvent(
72
+ name: string,
73
+ properties: Record<string, unknown> = {}
74
+ ): Promise<TrackingCommandResult> {
75
+ if (!this.commandService || !this.currentSession) {
76
+ return { success: false, error: 'Service not initialized' };
77
+ }
78
+
79
+ return this.commandService.trackEvent(
80
+ this.currentSession.id.toString(),
81
+ name,
82
+ properties
83
+ );
84
+ }
85
+
86
+ async trackPageView(path?: string): Promise<TrackingCommandResult> {
87
+ if (!this.commandService || !this.currentSession) {
88
+ return { success: false, error: 'Service not initialized' };
89
+ }
90
+
91
+ const currentPath = path ?? (typeof window !== 'undefined' ? window.location.pathname : '/');
92
+ const referrer = typeof window !== 'undefined' ? document.referrer : null;
93
+
94
+ return this.commandService.trackPageview(
95
+ this.currentSession.id.toString(),
96
+ currentPath,
97
+ referrer,
98
+ this.getUTMFromURL()
99
+ );
100
+ }
101
+
102
+ private async initializeSession(): Promise<void> {
103
+ if (!this.sessionRepo) return;
104
+
105
+ const deviceId = this.getOrCreateDeviceId();
106
+ const existingSession = await this.sessionRepo.findActive(deviceId);
107
+
108
+ if (existingSession) {
109
+ this.currentSession = existingSession;
110
+ } else {
111
+ const sessionId = SessionId.generate();
112
+ const siteId = SiteId.generate();
113
+ const deviceInfo = DeviceInfo.fromUserAgent(
114
+ typeof window !== 'undefined' ? navigator.userAgent : '',
115
+ typeof window !== 'undefined' ? window.screen.width : undefined
116
+ );
117
+
118
+ this.currentSession = new Session({
119
+ id: sessionId,
120
+ deviceId,
121
+ siteId,
122
+ deviceInfo,
123
+ });
124
+ await this.sessionRepo.save(this.currentSession);
125
+ }
126
+ }
127
+
128
+ private getOrCreateDeviceId(): string {
129
+ if (typeof window === 'undefined') return '';
130
+
131
+ let deviceId = localStorage.getItem('wt_device_id');
132
+ if (!deviceId) {
133
+ deviceId = `device-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
134
+ localStorage.setItem('wt_device_id', deviceId);
135
+ }
136
+
137
+ return deviceId;
138
+ }
139
+
140
+ private getUTMFromURL() {
141
+ if (typeof window === 'undefined') return undefined;
142
+
143
+ const searchParams = new URLSearchParams(window.location.search);
144
+ const source = searchParams.get('utm_source') || undefined;
145
+ const medium = searchParams.get('utm_medium') || undefined;
146
+ const campaign = searchParams.get('utm_campaign') || undefined;
147
+ const term = searchParams.get('utm_term') || undefined;
148
+ const content = searchParams.get('utm_content') || undefined;
149
+
150
+ if (!source && !medium && !campaign && !term && !content) {
151
+ return undefined;
152
+ }
153
+
154
+ return { source, medium, campaign, term, content };
155
+ }
156
+
157
+ private setupAutoTrack(): void {
158
+ if (typeof window === 'undefined') return;
159
+
160
+ // Track initial pageview
161
+ this.trackPageView();
162
+
163
+ // Track SPA navigation
164
+ const originalPushState = history.pushState;
165
+ const originalReplaceState = history.replaceState;
166
+
167
+ history.pushState = (...args) => {
168
+ originalPushState.apply(history, args);
169
+ this.trackPageView();
170
+ };
171
+
172
+ history.replaceState = (...args) => {
173
+ originalReplaceState.apply(history, args);
174
+ this.trackPageView();
175
+ };
176
+
177
+ window.addEventListener('popstate', () => {
178
+ this.trackPageView();
179
+ });
180
+ }
181
+
182
+ destroy(): void {
183
+ this.eventRepo?.destroy();
184
+ this.initialized = false;
185
+ }
186
+ }
187
+
188
+ export const webTrafficService = new WebTrafficService();
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Web Traffic React Context
3
+ * @description React context provider for web-traffic tracking
4
+ */
5
+
6
+ import type { createContext } from 'react';
7
+ import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';
8
+ import { webTrafficService, type WebTrafficConfig } from '../infrastructure/tracking/web-traffic.service';
9
+ import type { WebTrafficContextValue, TrackingCommandResult } from './hooks';
10
+
11
+ const TrackingContext = createContext<WebTrafficContextValue | null>(null);
12
+
13
+ export interface WebTrafficProviderProps {
14
+ readonly children: ReactNode;
15
+ readonly config: WebTrafficConfig;
16
+ }
17
+
18
+ export function WebTrafficProvider({ children, config }: WebTrafficProviderProps): React.ReactElement {
19
+ useEffect(() => {
20
+ webTrafficService.initialize(config);
21
+
22
+ return () => {
23
+ webTrafficService.destroy();
24
+ };
25
+ }, [config]);
26
+
27
+ const value = useMemo<WebTrafficContextValue>(
28
+ () => ({
29
+ trackEvent: async (name: string, properties?: Record<string, unknown>) => {
30
+ return webTrafficService.trackEvent(name, properties);
31
+ },
32
+ trackPageView: async (path?: string) => {
33
+ return webTrafficService.trackPageView(path);
34
+ },
35
+ isInitialized: webTrafficService.isInitialized(),
36
+ }),
37
+ []
38
+ );
39
+
40
+ return <TrackingContext.Provider value={value}>{children}</TrackingContext.Provider>;
41
+ }
42
+
43
+ export { TrackingContext as WebTrafficContext };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Web Traffic React Hooks
3
+ * @description React hooks for web-traffic tracking
4
+ */
5
+
6
+ import { useContext, useCallback, useEffect, useState } from 'react';
7
+ import { WebTrafficContext } from './context';
8
+ import { webTrafficService } from '../infrastructure/tracking/web-traffic.service';
9
+ import { HTTPAnalyticsRepository } from '../infrastructure/analytics/http-analytics.repository.impl';
10
+ import type { AnalyticsQuery } from '../domains/analytics/repositories/analytics.repository.interface';
11
+ import type { AnalyticsData } from '../domains/analytics/entities/analytics.entity';
12
+
13
+ export interface TrackingCommandResult {
14
+ success: boolean;
15
+ eventId?: string;
16
+ error?: string;
17
+ }
18
+
19
+ export interface WebTrafficContextValue {
20
+ readonly trackEvent: (name: string, properties?: Record<string, unknown>) => Promise<TrackingCommandResult>;
21
+ readonly trackPageView: (path?: string) => Promise<TrackingCommandResult>;
22
+ readonly isInitialized: boolean;
23
+ }
24
+
25
+ export function useWebTraffic(): WebTrafficContextValue {
26
+ const context = useContext(WebTrafficContext);
27
+
28
+ if (!context) {
29
+ // If no context, use service directly
30
+ return {
31
+ trackEvent: useCallback(async (name: string, properties?: Record<string, unknown>) => {
32
+ return webTrafficService.trackEvent(name, properties);
33
+ }, []),
34
+ trackPageView: useCallback(async (path?: string) => {
35
+ return webTrafficService.trackPageView(path);
36
+ }, []),
37
+ isInitialized: webTrafficService.isInitialized(),
38
+ };
39
+ }
40
+
41
+ return context;
42
+ }
43
+
44
+ export function useAnalytics(query: AnalyticsQuery) {
45
+ const [data, setData] = useState<AnalyticsData | null>(null);
46
+ const [loading, setLoading] = useState(true);
47
+ const [error, setError] = useState<Error | null>(null);
48
+
49
+ useEffect(() => {
50
+ let cancelled = false;
51
+
52
+ async function fetchAnalytics() {
53
+ setLoading(true);
54
+ setError(null);
55
+
56
+ // Note: You'll need to initialize this with proper config
57
+ const repo = new HTTPAnalyticsRepository({
58
+ apiUrl: 'https://analytics.umituz.com',
59
+ apiKey: 'your-api-key', // This should come from config
60
+ });
61
+
62
+ const result = await repo.getAnalytics(query);
63
+
64
+ if (!cancelled) {
65
+ setData(result);
66
+ setLoading(false);
67
+ }
68
+ }
69
+
70
+ fetchAnalytics();
71
+
72
+ return () => {
73
+ cancelled = true;
74
+ };
75
+ }, [query]);
76
+
77
+ return { data, loading, error };
78
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Presentation Layer Export
3
+ * Subpath: @umituz/web-traffic/presentation
4
+ */
5
+
6
+ export { useWebTraffic, useAnalytics } from './hooks';
7
+ export type { WebTrafficContextValue } from './hooks';
8
+
9
+ export { WebTrafficProvider } from './context';
10
+ export { WebTrafficContext } from './context';
11
+ export type { WebTrafficProviderProps } from './context';