vorzelajs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +188 -0
  2. package/bin/vorzelajs.mjs +2 -0
  3. package/dist/analytics.d.ts +132 -0
  4. package/dist/analytics.d.ts.map +1 -0
  5. package/dist/analytics.js +690 -0
  6. package/dist/cli/build.d.ts +2 -0
  7. package/dist/cli/build.d.ts.map +1 -0
  8. package/dist/cli/build.js +22 -0
  9. package/dist/cli/dev.d.ts +2 -0
  10. package/dist/cli/dev.d.ts.map +1 -0
  11. package/dist/cli/dev.js +93 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +29 -0
  15. package/dist/cli/serve.d.ts +2 -0
  16. package/dist/cli/serve.d.ts.map +1 -0
  17. package/dist/cli/serve.js +43 -0
  18. package/dist/cookie.d.ts +33 -0
  19. package/dist/cookie.d.ts.map +1 -0
  20. package/dist/cookie.js +198 -0
  21. package/dist/debug/error-stack.d.ts +14 -0
  22. package/dist/debug/error-stack.d.ts.map +1 -0
  23. package/dist/debug/error-stack.js +52 -0
  24. package/dist/internal/document.d.ts +12 -0
  25. package/dist/internal/document.d.ts.map +1 -0
  26. package/dist/internal/document.jsx +56 -0
  27. package/dist/internal/entry-client.d.ts +2 -0
  28. package/dist/internal/entry-client.d.ts.map +1 -0
  29. package/dist/internal/entry-client.jsx +8 -0
  30. package/dist/internal/entry-server.d.ts +14 -0
  31. package/dist/internal/entry-server.d.ts.map +1 -0
  32. package/dist/internal/entry-server.jsx +71 -0
  33. package/dist/runtime/create-route.d.ts +8 -0
  34. package/dist/runtime/create-route.d.ts.map +1 -0
  35. package/dist/runtime/create-route.js +18 -0
  36. package/dist/runtime/head.d.ts +10 -0
  37. package/dist/runtime/head.d.ts.map +1 -0
  38. package/dist/runtime/head.js +111 -0
  39. package/dist/runtime/index.d.ts +6 -0
  40. package/dist/runtime/index.d.ts.map +1 -0
  41. package/dist/runtime/index.jsx +4 -0
  42. package/dist/runtime/navigation.d.ts +36 -0
  43. package/dist/runtime/navigation.d.ts.map +1 -0
  44. package/dist/runtime/navigation.js +70 -0
  45. package/dist/runtime/path.d.ts +11 -0
  46. package/dist/runtime/path.d.ts.map +1 -0
  47. package/dist/runtime/path.js +80 -0
  48. package/dist/runtime/resolve.d.ts +11 -0
  49. package/dist/runtime/resolve.d.ts.map +1 -0
  50. package/dist/runtime/resolve.js +449 -0
  51. package/dist/runtime/runtime.d.ts +40 -0
  52. package/dist/runtime/runtime.d.ts.map +1 -0
  53. package/dist/runtime/runtime.jsx +779 -0
  54. package/dist/runtime/search.d.ts +23 -0
  55. package/dist/runtime/search.d.ts.map +1 -0
  56. package/dist/runtime/search.js +178 -0
  57. package/dist/runtime/server.d.ts +10 -0
  58. package/dist/runtime/server.d.ts.map +1 -0
  59. package/dist/runtime/server.js +5 -0
  60. package/dist/runtime/types.d.ts +248 -0
  61. package/dist/runtime/types.d.ts.map +1 -0
  62. package/dist/runtime/types.js +1 -0
  63. package/dist/seo.d.ts +16 -0
  64. package/dist/seo.d.ts.map +1 -0
  65. package/dist/seo.js +69 -0
  66. package/dist/server/index.d.ts +53 -0
  67. package/dist/server/index.d.ts.map +1 -0
  68. package/dist/server/index.js +268 -0
  69. package/dist/session.d.ts +23 -0
  70. package/dist/session.d.ts.map +1 -0
  71. package/dist/session.js +58 -0
  72. package/dist/vite/index.d.ts +13 -0
  73. package/dist/vite/index.d.ts.map +1 -0
  74. package/dist/vite/index.js +174 -0
  75. package/dist/vite/routes-plugin.d.ts +4 -0
  76. package/dist/vite/routes-plugin.d.ts.map +1 -0
  77. package/dist/vite/routes-plugin.js +345 -0
  78. package/dist/vite/server-only.d.ts +3 -0
  79. package/dist/vite/server-only.d.ts.map +1 -0
  80. package/dist/vite/server-only.js +342 -0
  81. package/package.json +76 -0
  82. package/templates/bare/README.md +22 -0
  83. package/templates/bare/src/components/counter-card.tsx +19 -0
  84. package/templates/bare/src/routes/__root.tsx +24 -0
  85. package/templates/bare/src/routes/index.tsx +36 -0
  86. package/templates/base/gitignore +4 -0
  87. package/templates/base/public/favicon.svg +5 -0
  88. package/templates/base/tsconfig.json +18 -0
  89. package/templates/modern/README.md +28 -0
  90. package/templates/modern/src/components/counter-card.tsx +19 -0
  91. package/templates/modern/src/routes/__root.tsx +42 -0
  92. package/templates/modern/src/routes/about.tsx +55 -0
  93. package/templates/modern/src/routes/index.tsx +51 -0
  94. package/templates/styling/css/styles.css +269 -0
  95. package/templates/styling/css-modules/styles.css +269 -0
  96. package/templates/styling/tailwind/styles.css +271 -0
@@ -0,0 +1,690 @@
1
+ import { cookiePolicies, createCookie, setCookie } from './cookie';
2
+ const DEFAULT_VISITOR_COOKIE_MAX_AGE = 60 * 60 * 24 * 90;
3
+ const INTERNAL_NAVIGATION_EVENT = 'vorzelajs:analytics:navigation';
4
+ const CLICK_ID_PLATFORMS = {
5
+ dclid: 'google',
6
+ epik: 'pinterest',
7
+ fbclid: 'facebook',
8
+ gbraid: 'google',
9
+ gclid: 'google',
10
+ li_fat_id: 'linkedin',
11
+ msclkid: 'microsoft',
12
+ ttclid: 'tiktok',
13
+ twclid: 'x',
14
+ wbraid: 'google',
15
+ };
16
+ const PLATFORM_HOST_PATTERNS = [
17
+ { host: 'google.', platform: 'google' },
18
+ { host: 'googleadservices.com', platform: 'google' },
19
+ { host: 'bing.com', platform: 'microsoft' },
20
+ { host: 'msn.com', platform: 'microsoft' },
21
+ { host: 'duckduckgo.com', platform: 'duckduckgo' },
22
+ { host: 'search.yahoo.com', platform: 'yahoo' },
23
+ { host: 'facebook.com', platform: 'facebook' },
24
+ { host: 'fb.com', platform: 'facebook' },
25
+ { host: 'instagram.com', platform: 'instagram' },
26
+ { host: 'linkedin.com', platform: 'linkedin' },
27
+ { host: 'lnkd.in', platform: 'linkedin' },
28
+ { host: 'tiktok.com', platform: 'tiktok' },
29
+ { host: 'twitter.com', platform: 'x' },
30
+ { host: 'x.com', platform: 'x' },
31
+ { host: 't.co', platform: 'x' },
32
+ { host: 'reddit.com', platform: 'reddit' },
33
+ { host: 'redd.it', platform: 'reddit' },
34
+ { host: 'youtube.com', platform: 'youtube' },
35
+ { host: 'youtu.be', platform: 'youtube' },
36
+ { host: 'pinterest.com', platform: 'pinterest' },
37
+ { host: 'pin.it', platform: 'pinterest' },
38
+ { host: 'whatsapp.com', platform: 'whatsapp' },
39
+ ];
40
+ const PLATFORM_SOURCE_ALIASES = {
41
+ 'adwords': 'google',
42
+ 'bing': 'microsoft',
43
+ 'facebook': 'facebook',
44
+ 'fb': 'facebook',
45
+ 'google': 'google',
46
+ 'google-ads': 'google',
47
+ 'googleads': 'google',
48
+ 'ig': 'instagram',
49
+ 'instagram': 'instagram',
50
+ 'linkedin': 'linkedin',
51
+ 'meta': 'facebook',
52
+ 'msn': 'microsoft',
53
+ 'newsletter': 'email',
54
+ 'pinterest': 'pinterest',
55
+ 'reddit': 'reddit',
56
+ 'tiktok': 'tiktok',
57
+ 'twitter': 'x',
58
+ 'x': 'x',
59
+ 'youtube': 'youtube',
60
+ };
61
+ const EMAIL_MEDIA = new Set(['drip', 'email', 'email-marketing', 'newsletter']);
62
+ const PAID_MEDIA = new Set(['affiliate', 'cpa', 'cpc', 'cpp', 'cpm', 'display', 'paid', 'paid-social', 'paid_social', 'paidsocial', 'ppc', 'retargeting']);
63
+ const SEARCH_MEDIA = new Set(['organic', 'search', 'seo']);
64
+ const SOCIAL_MEDIA = new Set(['organic-social', 'social', 'social-media', 'social_media']);
65
+ const SEARCH_PLATFORMS = new Set(['duckduckgo', 'google', 'microsoft', 'yahoo']);
66
+ const SOCIAL_PLATFORMS = new Set(['facebook', 'instagram', 'linkedin', 'pinterest', 'reddit', 'tiktok', 'whatsapp', 'x', 'youtube']);
67
+ export const DEFAULT_ANALYTICS_ENDPOINT = '/__vorzela/analytics';
68
+ function isRecord(value) {
69
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
70
+ }
71
+ function toPlainRecord(value) {
72
+ return isRecord(value) ? value : {};
73
+ }
74
+ function trimToNull(value) {
75
+ if (typeof value !== 'string') {
76
+ return null;
77
+ }
78
+ const trimmed = value.trim();
79
+ return trimmed === '' ? null : trimmed;
80
+ }
81
+ function normalizeMedium(value) {
82
+ return value?.toLowerCase().replace(/[_\s]+/gu, '-') ?? null;
83
+ }
84
+ function matchesHostPattern(host, pattern) {
85
+ return host === pattern || host.endsWith(`.${pattern}`) || host.includes(pattern);
86
+ }
87
+ function normalizePlatform(value) {
88
+ if (!value) {
89
+ return null;
90
+ }
91
+ const normalized = value.trim().toLowerCase().replace(/[_\s]+/gu, '-');
92
+ return PLATFORM_SOURCE_ALIASES[normalized] ?? normalized;
93
+ }
94
+ function detectPlatformFromHost(host) {
95
+ if (!host) {
96
+ return null;
97
+ }
98
+ const normalizedHost = host.toLowerCase();
99
+ for (const candidate of PLATFORM_HOST_PATTERNS) {
100
+ if (matchesHostPattern(normalizedHost, candidate.host)) {
101
+ return candidate.platform;
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ function toUrl(value, base) {
107
+ if (!value) {
108
+ return null;
109
+ }
110
+ try {
111
+ return value instanceof URL
112
+ ? value
113
+ : new URL(value, base);
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ function randomId() {
120
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
121
+ return crypto.randomUUID();
122
+ }
123
+ return `vrz_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
124
+ }
125
+ function readClickIds(searchParams) {
126
+ const clickIds = {};
127
+ for (const [key, platform] of Object.entries(CLICK_ID_PLATFORMS)) {
128
+ const value = trimToNull(searchParams.get(key));
129
+ if (value) {
130
+ clickIds[key] = value;
131
+ if (!clickIds.platform) {
132
+ clickIds.platform = platform;
133
+ }
134
+ }
135
+ }
136
+ return clickIds;
137
+ }
138
+ function isSameHost(left, right) {
139
+ return Boolean(left && right && left.toLowerCase() === right.toLowerCase());
140
+ }
141
+ function getTrafficChannel(input) {
142
+ if (!input.source && !input.medium && !input.referrerHost) {
143
+ return 'direct';
144
+ }
145
+ if (isSameHost(input.landingHost, input.referrerHost)) {
146
+ return 'internal';
147
+ }
148
+ if (input.medium && EMAIL_MEDIA.has(input.medium)) {
149
+ return 'email';
150
+ }
151
+ if (input.isPaid && (input.medium === 'search' || (input.platform && SEARCH_PLATFORMS.has(input.platform)))) {
152
+ return 'paid-search';
153
+ }
154
+ if (input.isPaid && (input.medium === 'social' || input.medium === 'organic-social' || (input.platform && SOCIAL_PLATFORMS.has(input.platform)))) {
155
+ return 'paid-social';
156
+ }
157
+ if (!input.isPaid && (input.medium ? SEARCH_MEDIA.has(input.medium) : false || (input.platform && SEARCH_PLATFORMS.has(input.platform)))) {
158
+ return 'organic-search';
159
+ }
160
+ if (!input.isPaid && (input.medium ? SOCIAL_MEDIA.has(input.medium) : false || (input.platform && SOCIAL_PLATFORMS.has(input.platform)))) {
161
+ return 'organic-social';
162
+ }
163
+ if (input.referrerHost) {
164
+ return 'referral';
165
+ }
166
+ return 'unknown';
167
+ }
168
+ export function classifyAnalyticsTraffic(input) {
169
+ const landingUrl = toUrl(input.landingUrl ?? null, 'http://localhost');
170
+ const referrerUrl = toUrl(input.referrer ?? null);
171
+ const landingHost = landingUrl?.host ?? null;
172
+ const referrerHost = referrerUrl?.host ?? null;
173
+ const source = trimToNull(landingUrl?.searchParams.get('utm_source') ?? null);
174
+ const medium = trimToNull(landingUrl?.searchParams.get('utm_medium') ?? null);
175
+ const campaign = trimToNull(landingUrl?.searchParams.get('utm_campaign') ?? null);
176
+ const term = trimToNull(landingUrl?.searchParams.get('utm_term') ?? null);
177
+ const content = trimToNull(landingUrl?.searchParams.get('utm_content') ?? null);
178
+ const clickIds = landingUrl ? readClickIds(landingUrl.searchParams) : {};
179
+ const mediumKey = normalizeMedium(medium);
180
+ const clickPlatform = normalizePlatform(clickIds.platform ?? null);
181
+ const sourcePlatform = normalizePlatform(source);
182
+ const referrerPlatform = detectPlatformFromHost(referrerHost);
183
+ const platform = clickPlatform ?? sourcePlatform ?? referrerPlatform;
184
+ const isPaid = Object.keys(clickIds).some((key) => key !== 'platform') || (mediumKey ? PAID_MEDIA.has(mediumKey) : false);
185
+ const channel = getTrafficChannel({
186
+ isPaid,
187
+ landingHost,
188
+ medium: mediumKey,
189
+ platform,
190
+ referrerHost,
191
+ source,
192
+ });
193
+ return {
194
+ campaign,
195
+ capturedAt: new Date().toISOString(),
196
+ channel,
197
+ clickIds: Object.fromEntries(Object.entries(clickIds).filter(([key]) => key !== 'platform')),
198
+ content,
199
+ entryUrl: landingUrl?.toString() ?? '/',
200
+ isPaid,
201
+ medium,
202
+ platform,
203
+ referrer: referrerUrl?.toString() ?? trimToNull(input.referrer) ?? null,
204
+ referrerHost,
205
+ source: source ?? referrerPlatform,
206
+ term,
207
+ };
208
+ }
209
+ export function extractAnalyticsTouchPoint(landingUrl, referrer) {
210
+ return classifyAnalyticsTraffic({
211
+ landingUrl,
212
+ referrer,
213
+ });
214
+ }
215
+ function parseUserAgent(userAgent, platformHint) {
216
+ const raw = userAgent ?? null;
217
+ const normalized = raw?.toLowerCase() ?? '';
218
+ const normalizedPlatformHint = trimToNull(platformHint?.replace(/^"|"$/gu, '') ?? null);
219
+ const isBot = /bot|crawler|spider|curl|postman|headless/iu.test(normalized);
220
+ let browser = null;
221
+ let browserVersion = null;
222
+ const browserMatchers = [
223
+ { browser: 'edge', regex: /edg(?:a|ios)?\/(\d+(?:\.\d+)*)/iu },
224
+ { browser: 'opera', regex: /opr\/(\d+(?:\.\d+)*)/iu },
225
+ { browser: 'firefox', regex: /firefox\/(\d+(?:\.\d+)*)/iu },
226
+ { browser: 'chrome', regex: /(?:chrome|crios)\/(\d+(?:\.\d+)*)/iu },
227
+ { browser: 'safari', regex: /version\/(\d+(?:\.\d+)*)[\s\S]*safari/iu },
228
+ ];
229
+ for (const matcher of browserMatchers) {
230
+ const result = raw?.match(matcher.regex);
231
+ if (result) {
232
+ browser = matcher.browser;
233
+ browserVersion = result[1] ?? null;
234
+ break;
235
+ }
236
+ }
237
+ let os = normalizedPlatformHint?.toLowerCase() ?? null;
238
+ if (!os) {
239
+ if (normalized.includes('android')) {
240
+ os = 'android';
241
+ }
242
+ else if (normalized.includes('iphone') || normalized.includes('ipad') || normalized.includes('ios')) {
243
+ os = 'ios';
244
+ }
245
+ else if (normalized.includes('mac os x') || normalized.includes('macintosh')) {
246
+ os = 'macos';
247
+ }
248
+ else if (normalized.includes('windows')) {
249
+ os = 'windows';
250
+ }
251
+ else if (normalized.includes('linux')) {
252
+ os = 'linux';
253
+ }
254
+ }
255
+ let deviceType = 'unknown';
256
+ if (isBot) {
257
+ deviceType = 'bot';
258
+ }
259
+ else if (normalized.includes('ipad') || normalized.includes('tablet')) {
260
+ deviceType = 'tablet';
261
+ }
262
+ else if (normalized.includes('mobile') || normalized.includes('iphone') || normalized.includes('android')) {
263
+ deviceType = 'mobile';
264
+ }
265
+ else if (os) {
266
+ deviceType = 'desktop';
267
+ }
268
+ return {
269
+ browser,
270
+ browserVersion,
271
+ deviceType,
272
+ isBot,
273
+ os,
274
+ platformHint: normalizedPlatformHint,
275
+ raw,
276
+ };
277
+ }
278
+ function extractGeoSummary(request) {
279
+ const headers = request.headers;
280
+ const geo = {
281
+ city: trimToNull(headers.get('x-vercel-ip-city') ?? headers.get('x-city')),
282
+ country: trimToNull(headers.get('cf-ipcountry')
283
+ ?? headers.get('cloudfront-viewer-country')
284
+ ?? headers.get('x-vercel-ip-country')
285
+ ?? headers.get('x-country-code')),
286
+ region: trimToNull(headers.get('x-vercel-ip-country-region') ?? headers.get('x-region')),
287
+ };
288
+ return geo.city || geo.country || geo.region ? geo : null;
289
+ }
290
+ function getStorage(storageKind) {
291
+ if (typeof window === 'undefined') {
292
+ return null;
293
+ }
294
+ try {
295
+ return window[storageKind];
296
+ }
297
+ catch {
298
+ return null;
299
+ }
300
+ }
301
+ function readStoredTouchPoint(storage, key) {
302
+ if (!storage) {
303
+ return null;
304
+ }
305
+ try {
306
+ const raw = storage.getItem(key);
307
+ if (!raw) {
308
+ return null;
309
+ }
310
+ const parsed = JSON.parse(raw);
311
+ return isRecord(parsed) ? parsed : null;
312
+ }
313
+ catch {
314
+ return null;
315
+ }
316
+ }
317
+ function writeStoredTouchPoint(storage, key, value) {
318
+ if (!storage) {
319
+ return;
320
+ }
321
+ try {
322
+ storage.setItem(key, JSON.stringify(value));
323
+ }
324
+ catch {
325
+ return;
326
+ }
327
+ }
328
+ function readStoredValue(storage, key) {
329
+ if (!storage) {
330
+ return null;
331
+ }
332
+ try {
333
+ return trimToNull(storage.getItem(key));
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ }
339
+ function writeStoredValue(storage, key, value) {
340
+ if (!storage) {
341
+ return;
342
+ }
343
+ try {
344
+ storage.setItem(key, value);
345
+ }
346
+ catch {
347
+ return;
348
+ }
349
+ }
350
+ function ensureStoredId(storage, key) {
351
+ const existing = readStoredValue(storage, key);
352
+ if (existing) {
353
+ return existing;
354
+ }
355
+ const nextValue = randomId();
356
+ writeStoredValue(storage, key, nextValue);
357
+ return nextValue;
358
+ }
359
+ function createClientContext(options) {
360
+ if (typeof window === 'undefined') {
361
+ return {};
362
+ }
363
+ const localStorage = getStorage('localStorage');
364
+ const sessionStorage = getStorage('sessionStorage');
365
+ const storageKey = options.storageKey ?? 'vorzelajs:analytics:visitor-id';
366
+ const sessionKey = options.sessionKey ?? 'vorzelajs:analytics:session-id';
367
+ return {
368
+ language: options.includeLanguage === false ? null : navigator.language ?? null,
369
+ screen: options.includeScreen === false
370
+ ? null
371
+ : {
372
+ height: window.screen.height,
373
+ width: window.screen.width,
374
+ },
375
+ sessionId: ensureStoredId(sessionStorage, sessionKey),
376
+ timezone: options.includeTimezone === false
377
+ ? null
378
+ : Intl.DateTimeFormat().resolvedOptions().timeZone ?? null,
379
+ visitorId: ensureStoredId(localStorage, storageKey),
380
+ };
381
+ }
382
+ function ensureTouchPoints(options) {
383
+ if (typeof window === 'undefined') {
384
+ return {
385
+ firstTouch: null,
386
+ lastTouch: null,
387
+ };
388
+ }
389
+ const storage = getStorage('localStorage');
390
+ const firstTouchKey = options.firstTouchKey ?? 'vorzelajs:analytics:first-touch';
391
+ const lastTouchKey = options.lastTouchKey ?? 'vorzelajs:analytics:last-touch';
392
+ const currentTouch = extractAnalyticsTouchPoint(window.location.href, document.referrer || null);
393
+ const storedFirstTouch = readStoredTouchPoint(storage, firstTouchKey);
394
+ const storedLastTouch = readStoredTouchPoint(storage, lastTouchKey);
395
+ if (!storedFirstTouch) {
396
+ writeStoredTouchPoint(storage, firstTouchKey, currentTouch);
397
+ }
398
+ const shouldUpdateLastTouch = currentTouch.channel !== 'internal'
399
+ && (currentTouch.channel !== 'direct' || !storedLastTouch);
400
+ if (shouldUpdateLastTouch) {
401
+ writeStoredTouchPoint(storage, lastTouchKey, currentTouch);
402
+ }
403
+ return {
404
+ firstTouch: readStoredTouchPoint(storage, firstTouchKey) ?? currentTouch,
405
+ lastTouch: readStoredTouchPoint(storage, lastTouchKey) ?? currentTouch,
406
+ };
407
+ }
408
+ function resolveRequestAllowed(origin, request, allowedOrigins) {
409
+ if (!allowedOrigins) {
410
+ return true;
411
+ }
412
+ if (typeof allowedOrigins === 'function') {
413
+ return allowedOrigins(origin, request);
414
+ }
415
+ if (!origin) {
416
+ return false;
417
+ }
418
+ return allowedOrigins.includes(origin);
419
+ }
420
+ function withCorsHeaders(headers, origin, request, analytics) {
421
+ if (!resolveRequestAllowed(origin, request, analytics.allowedOrigins)) {
422
+ return false;
423
+ }
424
+ if (origin) {
425
+ headers.set('Access-Control-Allow-Origin', origin);
426
+ headers.set('Access-Control-Allow-Credentials', 'true');
427
+ headers.set('Access-Control-Allow-Headers', 'Content-Type');
428
+ headers.set('Access-Control-Allow-Methods', 'OPTIONS, POST');
429
+ headers.append('Vary', 'Origin');
430
+ }
431
+ return true;
432
+ }
433
+ function buildEventPage(payload, request) {
434
+ const requestUrl = new URL(request.url);
435
+ const pageUrl = toUrl(payload.url ?? request.headers.get('Referer') ?? null, `${requestUrl.protocol}//${requestUrl.host}`);
436
+ return {
437
+ pathname: trimToNull(payload.pathname) ?? pageUrl?.pathname ?? null,
438
+ referrer: trimToNull(payload.referrer) ?? null,
439
+ search: trimToNull(payload.search) ?? pageUrl?.search ?? '',
440
+ title: trimToNull(payload.title) ?? null,
441
+ url: pageUrl?.toString() ?? trimToNull(payload.url) ?? null,
442
+ };
443
+ }
444
+ function getVisitorCookie(analytics) {
445
+ if (analytics.visitorCookie === false) {
446
+ return null;
447
+ }
448
+ return createCookie(analytics.visitorCookie?.name ?? '__Host-vrz_aid', {
449
+ ...cookiePolicies.host({
450
+ maxAge: DEFAULT_VISITOR_COOKIE_MAX_AGE,
451
+ }),
452
+ ...analytics.visitorCookie?.options,
453
+ });
454
+ }
455
+ function buildAnalyticsEvent(payload, request, visitorId) {
456
+ const page = buildEventPage(payload, request);
457
+ const attribution = classifyAnalyticsTraffic({
458
+ landingUrl: page.url,
459
+ referrer: page.referrer,
460
+ });
461
+ return {
462
+ attribution,
463
+ client: {
464
+ language: trimToNull(payload.context?.language ?? null),
465
+ screen: payload.context?.screen ?? null,
466
+ timezone: trimToNull(payload.context?.timezone ?? null),
467
+ },
468
+ firstTouch: payload.firstTouch ?? null,
469
+ geo: extractGeoSummary(request),
470
+ id: randomId(),
471
+ lastTouch: payload.lastTouch ?? null,
472
+ name: trimToNull(payload.name) ?? (payload.type === 'pageview' ? 'pageview' : 'event'),
473
+ occurredAt: trimToNull(payload.timestamp) ?? new Date().toISOString(),
474
+ page,
475
+ properties: toPlainRecord(payload.properties),
476
+ receivedAt: new Date().toISOString(),
477
+ request: {
478
+ host: trimToNull(request.headers.get('Host')),
479
+ origin: trimToNull(request.headers.get('Origin')),
480
+ referer: trimToNull(request.headers.get('Referer')),
481
+ secFetchSite: trimToNull(request.headers.get('Sec-Fetch-Site')),
482
+ },
483
+ sessionId: trimToNull(payload.context?.sessionId ?? null),
484
+ type: payload.type ?? 'event',
485
+ userAgent: parseUserAgent(trimToNull(request.headers.get('User-Agent')), trimToNull(request.headers.get('Sec-CH-UA-Platform'))),
486
+ visitorId,
487
+ };
488
+ }
489
+ async function parseAnalyticsPayload(request) {
490
+ const rawBody = await request.text();
491
+ if (!rawBody.trim()) {
492
+ return {};
493
+ }
494
+ const parsed = JSON.parse(rawBody);
495
+ if (!isRecord(parsed)) {
496
+ throw new Error('Analytics payload must be a JSON object');
497
+ }
498
+ return parsed;
499
+ }
500
+ export function defineAnalytics(options) {
501
+ return options;
502
+ }
503
+ export async function handleAnalyticsRequest(request, analytics) {
504
+ const origin = trimToNull(request.headers.get('Origin'));
505
+ const headers = new Headers({
506
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0, private',
507
+ 'Content-Type': 'application/json; charset=utf-8',
508
+ 'Pragma': 'no-cache',
509
+ });
510
+ if (!withCorsHeaders(headers, origin, request, analytics)) {
511
+ return new Response(JSON.stringify({ message: 'Forbidden' }), {
512
+ headers,
513
+ status: 403,
514
+ });
515
+ }
516
+ if (request.method === 'OPTIONS') {
517
+ return new Response(null, {
518
+ headers,
519
+ status: 204,
520
+ });
521
+ }
522
+ if (request.method !== 'POST') {
523
+ return new Response(JSON.stringify({ message: 'Method Not Allowed' }), {
524
+ headers,
525
+ status: 405,
526
+ });
527
+ }
528
+ let payload;
529
+ try {
530
+ payload = await parseAnalyticsPayload(request);
531
+ }
532
+ catch (error) {
533
+ return new Response(JSON.stringify({ message: error.message }), {
534
+ headers,
535
+ status: 400,
536
+ });
537
+ }
538
+ const visitorCookie = getVisitorCookie(analytics);
539
+ const cookieHeader = request.headers.get('Cookie');
540
+ const cookieVisitorId = visitorCookie ? await visitorCookie.parse(cookieHeader) : null;
541
+ const payloadVisitorId = trimToNull(payload.context?.visitorId ?? null);
542
+ const visitorId = cookieVisitorId ?? payloadVisitorId ?? randomId();
543
+ if (visitorCookie && cookieVisitorId !== visitorId) {
544
+ await setCookie(headers, visitorCookie, visitorId);
545
+ }
546
+ const event = buildAnalyticsEvent(payload, request, visitorId);
547
+ await analytics.onEvent?.(event, { payload, request });
548
+ return new Response(null, {
549
+ headers,
550
+ status: 204,
551
+ });
552
+ }
553
+ function shouldUseBeacon(endpoint, options) {
554
+ if (typeof window === 'undefined' || typeof navigator === 'undefined' || typeof navigator.sendBeacon !== 'function') {
555
+ return false;
556
+ }
557
+ if (options.transport === 'fetch') {
558
+ return false;
559
+ }
560
+ const endpointUrl = new URL(endpoint, window.location.origin);
561
+ if (endpointUrl.origin !== window.location.origin) {
562
+ return false;
563
+ }
564
+ return options.credentials !== 'include';
565
+ }
566
+ async function sendAnalyticsPayload(endpoint, payload, options) {
567
+ const body = JSON.stringify(payload);
568
+ if (shouldUseBeacon(endpoint, options)) {
569
+ const blob = new Blob([body], {
570
+ type: 'text/plain;charset=UTF-8',
571
+ });
572
+ const queued = navigator.sendBeacon(endpoint, blob);
573
+ if (queued || options.transport === 'beacon') {
574
+ return queued;
575
+ }
576
+ }
577
+ if (typeof fetch !== 'function') {
578
+ return false;
579
+ }
580
+ const response = await fetch(endpoint, {
581
+ body,
582
+ credentials: options.credentials ?? 'same-origin',
583
+ headers: {
584
+ 'Content-Type': 'text/plain;charset=UTF-8',
585
+ },
586
+ keepalive: true,
587
+ method: 'POST',
588
+ mode: typeof window === 'undefined'
589
+ ? 'same-origin'
590
+ : new URL(endpoint, window.location.origin).origin === window.location.origin
591
+ ? 'same-origin'
592
+ : 'cors',
593
+ });
594
+ return response.ok;
595
+ }
596
+ function ensureNavigationEventsPatched() {
597
+ if (typeof window === 'undefined') {
598
+ return;
599
+ }
600
+ const patchedKey = '__vorzelajsAnalyticsPatched';
601
+ if (window[patchedKey]) {
602
+ return;
603
+ }
604
+ for (const methodName of ['pushState', 'replaceState']) {
605
+ const original = window.history[methodName];
606
+ window.history[methodName] = function patchedHistoryState(...args) {
607
+ const result = original.apply(this, args);
608
+ window.dispatchEvent(new Event(INTERNAL_NAVIGATION_EVENT));
609
+ return result;
610
+ };
611
+ }
612
+ ;
613
+ window[patchedKey] = true;
614
+ }
615
+ export function createAnalyticsClient(options = {}) {
616
+ const endpoint = options.endpoint ?? DEFAULT_ANALYTICS_ENDPOINT;
617
+ const buildPayload = (payload) => {
618
+ if (typeof window === 'undefined') {
619
+ return payload;
620
+ }
621
+ const touchPoints = ensureTouchPoints(options);
622
+ const context = createClientContext(options);
623
+ return {
624
+ ...payload,
625
+ context: {
626
+ ...context,
627
+ ...payload.context,
628
+ },
629
+ firstTouch: payload.firstTouch ?? touchPoints.firstTouch,
630
+ lastTouch: payload.lastTouch ?? touchPoints.lastTouch,
631
+ pathname: payload.pathname ?? window.location.pathname,
632
+ referrer: payload.referrer ?? document.referrer ?? null,
633
+ search: payload.search ?? window.location.search,
634
+ timestamp: payload.timestamp ?? new Date().toISOString(),
635
+ title: payload.title ?? document.title,
636
+ url: payload.url ?? window.location.href,
637
+ };
638
+ };
639
+ const track = async (payload) => {
640
+ return sendAnalyticsPayload(endpoint, buildPayload(payload), options);
641
+ };
642
+ const pageview = async (payload = {}) => {
643
+ return track({
644
+ ...payload,
645
+ name: 'pageview',
646
+ type: 'pageview',
647
+ });
648
+ };
649
+ const event = async (name, properties = {}, payload = {}) => {
650
+ return track({
651
+ ...payload,
652
+ name,
653
+ properties,
654
+ type: 'event',
655
+ });
656
+ };
657
+ const startAutoPageviews = (startOptions = {}) => {
658
+ if (typeof window === 'undefined') {
659
+ return () => undefined;
660
+ }
661
+ ensureNavigationEventsPatched();
662
+ let lastTrackedUrl = '';
663
+ const trackCurrentPage = () => {
664
+ const currentUrl = window.location.href;
665
+ if (currentUrl === lastTrackedUrl) {
666
+ return;
667
+ }
668
+ lastTrackedUrl = currentUrl;
669
+ void pageview();
670
+ };
671
+ const handleNavigation = () => {
672
+ queueMicrotask(trackCurrentPage);
673
+ };
674
+ window.addEventListener(INTERNAL_NAVIGATION_EVENT, handleNavigation);
675
+ window.addEventListener('popstate', handleNavigation);
676
+ if (startOptions.trackInitialPageview !== false) {
677
+ trackCurrentPage();
678
+ }
679
+ return () => {
680
+ window.removeEventListener(INTERNAL_NAVIGATION_EVENT, handleNavigation);
681
+ window.removeEventListener('popstate', handleNavigation);
682
+ };
683
+ };
684
+ return {
685
+ event,
686
+ pageview,
687
+ startAutoPageviews,
688
+ track,
689
+ };
690
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runBuild(): Promise<void>;
2
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAKA,wBAAsB,QAAQ,kBA0B7B"}