solo-analytics 0.2.0 → 0.3.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/utils/features.ts","../src/utils/location.ts","../src/utils/network.ts","../src/utils/performance.ts","../src/utils/scheduleIdle.ts","../src/utils/screen.ts","../src/utils/userAgent.ts","../src/useSoloAnalytics.ts"],"sourcesContent":["export async function detectIncognito(): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n if (typeof window === \"undefined\" || typeof indexedDB === \"undefined\") {\n resolve(false);\n return;\n }\n\n let settled = false;\n const finish = (value: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n resolve(value);\n };\n\n const timeoutId = setTimeout(() => finish(false), 1000);\n\n const db = indexedDB.open(\"test\");\n db.onerror = () => finish(true);\n db.onsuccess = () => {\n db.result?.close();\n finish(false);\n };\n });\n}\n\nexport async function checkMediaCapabilities(): Promise<{\n hasCamera: boolean | null;\n hasMicrophone: boolean | null;\n}> {\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n !navigator.mediaDevices.enumerateDevices\n ) {\n return { hasCamera: null, hasMicrophone: null };\n }\n\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n const hasCamera = devices.some((device) => device.kind === \"videoinput\");\n const hasMicrophone = devices.some((device) => device.kind === \"audioinput\");\n\n return { hasCamera, hasMicrophone };\n } catch {\n return { hasCamera: null, hasMicrophone: null };\n }\n}\n\nexport async function getBatteryInfo(): Promise<{\n hasBattery: boolean | null;\n level: number | null;\n charging: boolean | null;\n}> {\n // @ts-expect-error: getBattery method is non-standard\n if (typeof navigator === \"undefined\" || !navigator.getBattery) {\n return { hasBattery: null, level: null, charging: null };\n }\n\n try {\n // @ts-expect-error\n const battery = await navigator.getBattery();\n return {\n hasBattery: true,\n level: battery.level,\n charging: battery.charging,\n };\n } catch {\n return { hasBattery: null, level: null, charging: null };\n }\n}\n\nexport async function checkPermissions(): Promise<Record<string, string>> {\n if (typeof navigator === \"undefined\" || !navigator.permissions || !navigator.permissions.query) {\n return {};\n }\n\n const permissions: Record<string, string> = {};\n const featuresToCheck = [\n \"geolocation\",\n \"notifications\",\n \"push\",\n \"midi\",\n \"camera\",\n \"microphone\",\n \"background-sync\",\n \"accelerometer\",\n \"gyroscope\",\n \"magnetometer\",\n ];\n\n const results = await Promise.all(\n featuresToCheck.map(async (feature) => {\n try {\n const result = await navigator.permissions!.query({ name: feature as PermissionName });\n return [feature, result.state] as const;\n } catch {\n return [feature, \"not-supported\"] as const;\n }\n }),\n );\n\n for (const [feature, state] of results) {\n permissions[feature] = state;\n }\n\n return permissions;\n}\n","import type { LocationInfo } from \"../types/soloAnalytics\";\n\nexport function getLocationInfo(): LocationInfo {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return {\n timeZone: \"\",\n language: \"\",\n languages: [],\n isRestricted: false,\n doNotTrack: null,\n cookiesEnabled: false,\n localStorage: false,\n sessionStorage: false,\n };\n }\n\n // Get timezone\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n // Get language info\n const language = navigator.language || \"\";\n const languages = navigator.languages ? Array.from(navigator.languages) : [language];\n\n // Check if window is restricted (e.g., iframe with restricted access)\n let isRestricted = false;\n try {\n isRestricted = window.self !== window.top;\n } catch {\n isRestricted = true;\n }\n\n // Check Do Not Track setting\n let doNotTrack = null;\n if (navigator.doNotTrack === \"1\" || navigator.doNotTrack === \"yes\") {\n doNotTrack = true;\n } else if (navigator.doNotTrack === \"0\" || navigator.doNotTrack === \"no\") {\n doNotTrack = false;\n }\n\n // Check if cookies are enabled\n const cookiesEnabled = navigator.cookieEnabled;\n\n // Check for storage availability\n const checkStorage = (type: \"localStorage\" | \"sessionStorage\"): boolean => {\n try {\n const storage = window[type];\n const testKey = `__test_${type}__`;\n storage.setItem(testKey, \"test\");\n storage.removeItem(testKey);\n return true;\n } catch {\n return false;\n }\n };\n\n return {\n timeZone,\n language,\n languages,\n isRestricted,\n doNotTrack,\n cookiesEnabled,\n localStorage: checkStorage(\"localStorage\"),\n sessionStorage: checkStorage(\"sessionStorage\"),\n };\n}\n","import type { NetworkInfo } from \"../types/soloAnalytics\";\n\nexport function getNetworkInfo(): NetworkInfo {\n const online = typeof navigator !== \"undefined\" ? navigator.onLine : false;\n\n // Default values\n let effectiveType = \"unknown\";\n let downlink = 0;\n let rtt = 0;\n let saveData = false;\n\n // NetworkInformation API (limited browser support)\n // @ts-expect-error: connection property is non-standard\n if (navigator?.connection) {\n // @ts-expect-error\n const connection = navigator.connection;\n\n effectiveType = connection.effectiveType || effectiveType;\n downlink = connection.downlink || downlink;\n rtt = connection.rtt || rtt;\n saveData = connection.saveData || saveData;\n }\n\n return {\n online,\n effectiveType,\n downlink,\n rtt,\n saveData,\n };\n}\n","import type { PerformanceInfo } from \"../types/soloAnalytics\";\n\nexport function getPerformanceInfo(): PerformanceInfo {\n if (typeof window === \"undefined\") {\n return {\n memory: null,\n navigation: { type: \"Unknown\", redirectCount: 0 },\n timing: {\n loadTime: 0,\n domContentLoaded: 0,\n firstPaint: null,\n firstContentfulPaint: null,\n },\n };\n }\n\n const getNavigationInfo = () => {\n if (!window.performance?.navigation) {\n return { type: \"Unknown\", redirectCount: 0 };\n }\n\n const navTypes = [\"navigate\", \"reload\", \"back_forward\", \"prerender\"];\n const navType = navTypes[window.performance.navigation.type] || \"Unknown\";\n\n return {\n type: navType,\n redirectCount: window.performance.navigation.redirectCount,\n };\n };\n\n const getTimingInfo = () => {\n if (!window.performance?.timing) {\n return {\n loadTime: 0,\n domContentLoaded: 0,\n firstPaint: null,\n firstContentfulPaint: null,\n };\n }\n\n const timing = window.performance.timing;\n const loadTime = timing.loadEventEnd - timing.navigationStart;\n const domContentLoaded = timing.domContentLoadedEventEnd - timing.navigationStart;\n\n // Get first paint and first contentful paint\n let firstPaint = null;\n let firstContentfulPaint = null;\n\n if (window.performance && typeof window.performance.getEntriesByType === \"function\") {\n const paintMetrics = window.performance.getEntriesByType(\"paint\");\n\n const fp = paintMetrics.find((entry) => entry.name === \"first-paint\");\n const fcp = paintMetrics.find((entry) => entry.name === \"first-contentful-paint\");\n\n if (fp) firstPaint = fp.startTime;\n if (fcp) firstContentfulPaint = fcp.startTime;\n }\n\n return {\n loadTime,\n domContentLoaded,\n firstPaint,\n firstContentfulPaint,\n };\n };\n\n const getMemoryInfo = () => {\n // @ts-expect-error: performance.memory is non-standard (Chrome only)\n if (window.performance?.memory) {\n // @ts-expect-error\n const memory = window.performance.memory;\n return {\n jsHeapSizeLimit: memory.jsHeapSizeLimit,\n totalJSHeapSize: memory.totalJSHeapSize,\n usedJSHeapSize: memory.usedJSHeapSize,\n };\n }\n return null;\n };\n\n return {\n memory: getMemoryInfo(),\n navigation: getNavigationInfo(),\n timing: getTimingInfo(),\n };\n}\n","/**\n * Schedules work during browser idle time to avoid blocking the main thread.\n * Falls back to setTimeout when requestIdleCallback is unavailable.\n */\nexport function scheduleIdle(callback: () => void | Promise<void>, timeout = 2000): void {\n if (typeof window === \"undefined\") {\n void Promise.resolve().then(callback);\n return;\n }\n\n const run = (): void => {\n void Promise.resolve(callback());\n };\n\n if (\"requestIdleCallback\" in window) {\n window.requestIdleCallback(() => run(), { timeout });\n return;\n }\n\n setTimeout(run, 0);\n}\n","import type { ScreenInfo } from \"../types/soloAnalytics\";\n\nexport function getScreenInfo(): ScreenInfo {\n if (typeof window === \"undefined\" || typeof screen === \"undefined\") {\n return {\n width: 0,\n height: 0,\n availWidth: 0,\n availHeight: 0,\n colorDepth: 0,\n orientation: \"unknown\",\n pixelRatio: 1,\n touchPoints: 0,\n };\n }\n\n // Get screen orientation\n let orientation = \"unknown\";\n if (window.innerHeight > window.innerWidth) {\n orientation = \"portrait\";\n } else {\n orientation = \"landscape\";\n }\n\n // Try to get more precise orientation if available\n if (screen.orientation?.type) {\n orientation = screen.orientation.type;\n }\n\n // Get pixel ratio\n const pixelRatio = window.devicePixelRatio || 1;\n\n // Get touch points\n const touchPoints = navigator.maxTouchPoints || 0;\n\n return {\n width: screen.width,\n height: screen.height,\n availWidth: screen.availWidth,\n availHeight: screen.availHeight,\n colorDepth: screen.colorDepth,\n orientation,\n pixelRatio,\n touchPoints,\n };\n}\n","import type { BrowserInfo, DeviceInfo, OSInfo } from \"../types/soloAnalytics\";\n\nexport interface UserAgentOptions {\n vendor?: string;\n innerWidth?: number;\n innerHeight?: number;\n maxTouchPoints?: number;\n}\n\nexport function parseUserAgent(\n ua: string,\n options: UserAgentOptions = {},\n): {\n browser: BrowserInfo;\n os: OSInfo;\n device: DeviceInfo;\n} {\n const vendor = options.vendor ?? (typeof navigator !== \"undefined\" ? navigator.vendor : \"\");\n const innerWidth =\n options.innerWidth ?? (typeof window !== \"undefined\" ? window.innerWidth : 1024);\n const innerHeight =\n options.innerHeight ?? (typeof window !== \"undefined\" ? window.innerHeight : 768);\n const maxTouchPoints =\n options.maxTouchPoints ??\n (typeof navigator !== \"undefined\" && \"maxTouchPoints\" in navigator\n ? navigator.maxTouchPoints\n : 0);\n\n // Browser detection\n const getBrowser = (): BrowserInfo => {\n const browsers = [\n { name: \"Edge\", regex: /Edg(?:e|A|iOS)?\\/([0-9.]+)/ },\n { name: \"Samsung Browser\", regex: /SamsungBrowser\\/([0-9.]+)/ },\n { name: \"Opera\", regex: /(?:Opera|OPR)\\/([0-9.]+)/ },\n { name: \"Firefox\", regex: /Firefox\\/([0-9.]+)/ },\n { name: \"Chrome\", regex: /Chrome\\/([0-9.]+)/ },\n { name: \"Safari\", regex: /Version\\/([0-9.]+).*Safari/ },\n { name: \"IE\", regex: /MSIE|Trident/ },\n ];\n\n for (const browser of browsers) {\n const match = ua.match(browser.regex);\n if (match) {\n const version = match[1] || \"\";\n const major = version.split(\".\")[0] || \"\";\n\n // Engine detection\n let engine = \"Unknown\";\n let engineVersion = \"\";\n\n if (ua.includes(\"AppleWebKit\")) {\n const webkitMatch = ua.match(/AppleWebKit\\/([0-9.]+)/);\n engine = \"WebKit\";\n engineVersion = webkitMatch ? webkitMatch[1] : \"\";\n } else if (ua.includes(\"Gecko\")) {\n engine = \"Gecko\";\n const geckoMatch = ua.match(/rv:([0-9.]+)/);\n engineVersion = geckoMatch ? geckoMatch[1] : \"\";\n } else if (ua.includes(\"Trident\")) {\n engine = \"Trident\";\n const tridentMatch = ua.match(/Trident\\/([0-9.]+)/);\n engineVersion = tridentMatch ? tridentMatch[1] : \"\";\n }\n\n return {\n name: browser.name,\n version,\n major,\n userAgent: ua,\n vendor,\n engine,\n engineVersion,\n };\n }\n }\n\n return {\n name: \"Unknown\",\n version: \"\",\n major: \"\",\n userAgent: ua,\n vendor,\n engine: \"Unknown\",\n engineVersion: \"\",\n };\n };\n\n // OS detection\n const getOS = (): OSInfo => {\n const osMatchers = [\n { name: \"iOS\", regex: /iPhone|iPad|iPod/ },\n { name: \"Android\", regex: /Android ([0-9.]+)/ },\n { name: \"Windows\", regex: /Windows NT ([0-9.]+)/ },\n { name: \"macOS\", regex: /Mac OS X ([0-9_.]+)/ },\n { name: \"Linux\", regex: /Linux/ },\n ];\n\n for (const os of osMatchers) {\n const match = ua.match(os.regex);\n if (match) {\n let version = \"\";\n if (match[1]) {\n version = os.name === \"macOS\" ? match[1].replace(/_/g, \".\") : match[1];\n }\n\n return {\n name: os.name,\n version,\n architecture: ua.includes(\"x64\") || ua.includes(\"x86_64\") ? \"64-bit\" : \"32-bit\",\n };\n }\n }\n\n return {\n name: \"Unknown\",\n version: \"\",\n architecture: ua.includes(\"x64\") || ua.includes(\"x86_64\") ? \"64-bit\" : \"32-bit\",\n };\n };\n\n // Device detection\n const getDevice = (): DeviceInfo => {\n const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n ua,\n );\n const isTablet = /iPad|Android(?!.*Mobile)/i.test(ua);\n\n let vendor = \"Unknown\";\n let model = \"Unknown\";\n\n if (ua.includes(\"iPhone\") || ua.includes(\"iPad\") || ua.includes(\"iPod\")) {\n vendor = \"Apple\";\n if (ua.includes(\"iPhone\")) model = \"iPhone\";\n if (ua.includes(\"iPad\")) model = \"iPad\";\n if (ua.includes(\"iPod\")) model = \"iPod\";\n } else if (ua.includes(\"Samsung\")) {\n vendor = \"Samsung\";\n } else if (ua.includes(\"Pixel\")) {\n vendor = \"Google\";\n const pixelMatch = ua.match(/Pixel ([0-9XL]+)/);\n if (pixelMatch) model = `Pixel ${pixelMatch[1]}`;\n }\n\n const deviceType = isTablet ? \"tablet\" : isMobileDevice ? \"mobile\" : \"desktop\";\n\n return {\n type: deviceType,\n vendor,\n model,\n orientation: innerHeight > innerWidth ? \"portrait\" : \"landscape\",\n isMobile: deviceType === \"mobile\",\n isTablet: deviceType === \"tablet\",\n isDesktop: deviceType === \"desktop\",\n touch: maxTouchPoints > 0,\n };\n };\n\n return {\n browser: getBrowser(),\n os: getOS(),\n device: getDevice(),\n };\n}\n","import type { SoloAnalyticsInfo } from \"./types/soloAnalytics\";\nimport {\n checkMediaCapabilities,\n checkPermissions,\n detectIncognito,\n getBatteryInfo,\n} from \"./utils/features\";\nimport { getLocationInfo } from \"./utils/location\";\nimport { getNetworkInfo } from \"./utils/network\";\nimport { getPerformanceInfo } from \"./utils/performance\";\nimport { scheduleIdle } from \"./utils/scheduleIdle\";\nimport { getScreenInfo } from \"./utils/screen\";\nimport { parseUserAgent } from \"./utils/userAgent\";\n\nexport interface SoloAnalyticsOptions {\n /** Automatically refresh dynamic data (network, performance) */\n autoRefresh?: boolean;\n /** Refresh interval in milliseconds (default: 30000) */\n refreshInterval?: number;\n /** Track page visibility changes */\n trackVisibility?: boolean;\n /**\n * Run expensive async probes (incognito, battery, media, permissions).\n * Set to `false` to skip entirely and reduce main-thread work.\n * @default true\n */\n detectFeatures?: boolean;\n /**\n * Defer async feature probes to idle time via requestIdleCallback.\n * Sync data (UA, screen, network) is collected immediately.\n * @default true\n */\n lazyFeatures?: boolean;\n}\n\nexport interface SoloAnalyticsReturn {\n data: SoloAnalyticsInfo;\n refresh: () => Promise<void>;\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n isOnline: boolean;\n isVisible: boolean;\n browserName: string;\n osName: string;\n destroy: () => void;\n}\n\nconst createInitialData = (): SoloAnalyticsInfo => ({\n browser: {\n name: \"\",\n version: \"\",\n major: \"\",\n userAgent: \"\",\n vendor: \"\",\n engine: \"\",\n engineVersion: \"\",\n },\n os: {\n name: \"\",\n version: \"\",\n architecture: \"\",\n },\n device: {\n type: \"unknown\",\n vendor: \"\",\n model: \"\",\n orientation: \"portrait\",\n isMobile: false,\n isTablet: false,\n isDesktop: false,\n touch: false,\n },\n network: {\n online: false,\n effectiveType: \"\",\n downlink: 0,\n rtt: 0,\n saveData: false,\n },\n screen: {\n width: 0,\n height: 0,\n availWidth: 0,\n availHeight: 0,\n colorDepth: 0,\n orientation: \"\",\n pixelRatio: 1,\n touchPoints: 0,\n },\n performance: {\n memory: null,\n navigation: {\n type: \"\",\n redirectCount: 0,\n },\n timing: {\n loadTime: 0,\n domContentLoaded: 0,\n firstPaint: null,\n firstContentfulPaint: null,\n },\n },\n location: {\n timeZone: \"\",\n language: \"\",\n languages: [],\n isRestricted: false,\n doNotTrack: null,\n cookiesEnabled: false,\n localStorage: false,\n sessionStorage: false,\n },\n pageVisibility: \"visible\",\n referrer: \"\",\n isIncognito: false,\n hasCamera: null,\n hasMicrophone: null,\n hasBattery: null,\n batteryLevel: null,\n batteryCharging: null,\n permissions: {},\n});\n\nexport function useSoloAnalytics(options: SoloAnalyticsOptions = {}): SoloAnalyticsReturn {\n const {\n autoRefresh = false,\n refreshInterval = 30000,\n trackVisibility = true,\n detectFeatures = true,\n lazyFeatures = true,\n } = options;\n\n const analyticsData = createInitialData();\n\n let visibilityChangeListener: (() => void) | null = null;\n let refreshTimer: ReturnType<typeof setInterval> | null = null;\n let destroyed = false;\n\n const collectSyncData = (): void => {\n if (typeof navigator === \"undefined\" || typeof window === \"undefined\") {\n return;\n }\n\n const userAgent = navigator.userAgent;\n const { browser, os, device } = parseUserAgent(userAgent);\n\n Object.assign(analyticsData, {\n browser,\n os,\n device,\n screen: getScreenInfo(),\n location: getLocationInfo(),\n referrer: document.referrer,\n });\n };\n\n const collectAsyncFeatures = async (): Promise<void> => {\n if (!detectFeatures || destroyed) {\n return;\n }\n\n const [isIncognito, media, battery, permissions] = await Promise.all([\n detectIncognito(),\n checkMediaCapabilities(),\n getBatteryInfo(),\n checkPermissions(),\n ]);\n\n if (destroyed) {\n return;\n }\n\n Object.assign(analyticsData, {\n isIncognito,\n hasCamera: media.hasCamera,\n hasMicrophone: media.hasMicrophone,\n hasBattery: battery.hasBattery,\n batteryLevel: battery.level,\n batteryCharging: battery.charging,\n permissions,\n });\n };\n\n const collectDynamicData = (): void => {\n if (typeof navigator === \"undefined\" || typeof window === \"undefined\") {\n return;\n }\n\n Object.assign(analyticsData, {\n network: getNetworkInfo(),\n performance: getPerformanceInfo(),\n pageVisibility: document.visibilityState === \"visible\" ? \"visible\" : \"hidden\",\n });\n };\n\n const runFeatureDetection = (): void => {\n if (!detectFeatures) {\n return;\n }\n\n const run = (): void => {\n void collectAsyncFeatures();\n };\n\n if (lazyFeatures) {\n scheduleIdle(run);\n return;\n }\n\n run();\n };\n\n if (trackVisibility && typeof document !== \"undefined\") {\n const handleVisibilityChange = (): void => {\n analyticsData.pageVisibility = document.visibilityState === \"visible\" ? \"visible\" : \"hidden\";\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n visibilityChangeListener = handleVisibilityChange;\n }\n\n if (autoRefresh && typeof window !== \"undefined\") {\n refreshTimer = setInterval(collectDynamicData, refreshInterval);\n }\n\n const init = (): void => {\n collectSyncData();\n collectDynamicData();\n runFeatureDetection();\n };\n\n init();\n\n const refresh = async (): Promise<void> => {\n collectSyncData();\n collectDynamicData();\n await collectAsyncFeatures();\n };\n\n const destroy = (): void => {\n destroyed = true;\n\n if (refreshTimer !== null) {\n clearInterval(refreshTimer);\n refreshTimer = null;\n }\n\n if (visibilityChangeListener && typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", visibilityChangeListener);\n visibilityChangeListener = null;\n }\n };\n\n return {\n data: analyticsData,\n refresh,\n destroy,\n get isMobile(): boolean {\n return analyticsData.device.isMobile;\n },\n get isTablet(): boolean {\n return analyticsData.device.isTablet;\n },\n get isDesktop(): boolean {\n return analyticsData.device.isDesktop;\n },\n get isOnline(): boolean {\n return analyticsData.network.online;\n },\n get isVisible(): boolean {\n return analyticsData.pageVisibility === \"visible\";\n },\n get browserName(): string {\n return analyticsData.browser.name;\n },\n get osName(): string {\n return analyticsData.os.name;\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ //#region src/types/soloAnalytics.d.ts
2
+ interface BrowserInfo {
3
+ name: string;
4
+ version: string;
5
+ major: string;
6
+ userAgent: string;
7
+ vendor: string;
8
+ engine: string;
9
+ engineVersion: string;
10
+ }
11
+ interface OSInfo {
12
+ name: string;
13
+ version: string;
14
+ architecture: string;
15
+ }
16
+ interface DeviceInfo {
17
+ type: "mobile" | "tablet" | "desktop" | "unknown";
18
+ vendor: string;
19
+ model: string;
20
+ orientation: "portrait" | "landscape";
21
+ isMobile: boolean;
22
+ isTablet: boolean;
23
+ isDesktop: boolean;
24
+ touch: boolean;
25
+ }
26
+ interface NetworkInfo {
27
+ online: boolean;
28
+ effectiveType: string;
29
+ downlink: number;
30
+ rtt: number;
31
+ saveData: boolean;
32
+ }
33
+ interface ScreenInfo {
34
+ width: number;
35
+ height: number;
36
+ availWidth: number;
37
+ availHeight: number;
38
+ colorDepth: number;
39
+ orientation: string;
40
+ pixelRatio: number;
41
+ touchPoints: number;
42
+ }
43
+ interface PerformanceInfo {
44
+ memory: {
45
+ jsHeapSizeLimit: number;
46
+ totalJSHeapSize: number;
47
+ usedJSHeapSize: number;
48
+ } | null;
49
+ navigation: {
50
+ type: string;
51
+ redirectCount: number;
52
+ };
53
+ timing: {
54
+ loadTime: number;
55
+ domContentLoaded: number;
56
+ firstPaint: number | null;
57
+ firstContentfulPaint: number | null;
58
+ };
59
+ }
60
+ interface LocationInfo {
61
+ timeZone: string;
62
+ language: string;
63
+ languages: string[];
64
+ isRestricted: boolean;
65
+ doNotTrack: boolean | null;
66
+ cookiesEnabled: boolean;
67
+ localStorage: boolean;
68
+ sessionStorage: boolean;
69
+ }
70
+ interface SoloAnalyticsInfo {
71
+ browser: BrowserInfo;
72
+ os: OSInfo;
73
+ device: DeviceInfo;
74
+ network: NetworkInfo;
75
+ screen: ScreenInfo;
76
+ performance: PerformanceInfo;
77
+ location: LocationInfo;
78
+ pageVisibility: "visible" | "hidden";
79
+ referrer: string;
80
+ isIncognito: boolean;
81
+ hasCamera: boolean | null;
82
+ hasMicrophone: boolean | null;
83
+ hasBattery: boolean | null;
84
+ batteryLevel: number | null;
85
+ batteryCharging: boolean | null;
86
+ permissions: Record<string, string>;
87
+ }
88
+ //#endregion
89
+ //#region src/useSoloAnalytics.d.ts
90
+ interface SoloAnalyticsOptions {
91
+ /** Automatically refresh dynamic data (network, performance) */
92
+ autoRefresh?: boolean;
93
+ /** Refresh interval in milliseconds (default: 30000) */
94
+ refreshInterval?: number;
95
+ /** Track page visibility changes */
96
+ trackVisibility?: boolean;
97
+ /**
98
+ * Run expensive async probes (incognito, battery, media, permissions).
99
+ * Set to `false` to skip entirely and reduce main-thread work.
100
+ * @default true
101
+ */
102
+ detectFeatures?: boolean;
103
+ /**
104
+ * Defer async feature probes to idle time via requestIdleCallback.
105
+ * Sync data (UA, screen, network) is collected immediately.
106
+ * @default true
107
+ */
108
+ lazyFeatures?: boolean;
109
+ }
110
+ interface SoloAnalyticsReturn {
111
+ data: SoloAnalyticsInfo;
112
+ refresh: () => Promise<void>;
113
+ isMobile: boolean;
114
+ isTablet: boolean;
115
+ isDesktop: boolean;
116
+ isOnline: boolean;
117
+ isVisible: boolean;
118
+ browserName: string;
119
+ osName: string;
120
+ destroy: () => void;
121
+ }
122
+ declare function useSoloAnalytics(options?: SoloAnalyticsOptions): SoloAnalyticsReturn;
123
+ //#endregion
124
+ export { type BrowserInfo, type DeviceInfo, type LocationInfo, type NetworkInfo, type OSInfo, type PerformanceInfo, type ScreenInfo, type SoloAnalyticsInfo, type SoloAnalyticsOptions, type SoloAnalyticsReturn, useSoloAnalytics };
125
+ //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,4 +1,125 @@
1
- export { useSoloAnalytics } from './composables/useSoloAnalytics';
2
- export type { SoloAnalyticsOptions, SoloAnalyticsReturn } from './composables/useSoloAnalytics';
3
- export type { SoloAnalyticsInfo, BrowserInfo, OSInfo, DeviceInfo, NetworkInfo, ScreenInfo, PerformanceInfo, LocationInfo } from './types/analytics';
1
+ //#region src/types/soloAnalytics.d.ts
2
+ interface BrowserInfo {
3
+ name: string;
4
+ version: string;
5
+ major: string;
6
+ userAgent: string;
7
+ vendor: string;
8
+ engine: string;
9
+ engineVersion: string;
10
+ }
11
+ interface OSInfo {
12
+ name: string;
13
+ version: string;
14
+ architecture: string;
15
+ }
16
+ interface DeviceInfo {
17
+ type: "mobile" | "tablet" | "desktop" | "unknown";
18
+ vendor: string;
19
+ model: string;
20
+ orientation: "portrait" | "landscape";
21
+ isMobile: boolean;
22
+ isTablet: boolean;
23
+ isDesktop: boolean;
24
+ touch: boolean;
25
+ }
26
+ interface NetworkInfo {
27
+ online: boolean;
28
+ effectiveType: string;
29
+ downlink: number;
30
+ rtt: number;
31
+ saveData: boolean;
32
+ }
33
+ interface ScreenInfo {
34
+ width: number;
35
+ height: number;
36
+ availWidth: number;
37
+ availHeight: number;
38
+ colorDepth: number;
39
+ orientation: string;
40
+ pixelRatio: number;
41
+ touchPoints: number;
42
+ }
43
+ interface PerformanceInfo {
44
+ memory: {
45
+ jsHeapSizeLimit: number;
46
+ totalJSHeapSize: number;
47
+ usedJSHeapSize: number;
48
+ } | null;
49
+ navigation: {
50
+ type: string;
51
+ redirectCount: number;
52
+ };
53
+ timing: {
54
+ loadTime: number;
55
+ domContentLoaded: number;
56
+ firstPaint: number | null;
57
+ firstContentfulPaint: number | null;
58
+ };
59
+ }
60
+ interface LocationInfo {
61
+ timeZone: string;
62
+ language: string;
63
+ languages: string[];
64
+ isRestricted: boolean;
65
+ doNotTrack: boolean | null;
66
+ cookiesEnabled: boolean;
67
+ localStorage: boolean;
68
+ sessionStorage: boolean;
69
+ }
70
+ interface SoloAnalyticsInfo {
71
+ browser: BrowserInfo;
72
+ os: OSInfo;
73
+ device: DeviceInfo;
74
+ network: NetworkInfo;
75
+ screen: ScreenInfo;
76
+ performance: PerformanceInfo;
77
+ location: LocationInfo;
78
+ pageVisibility: "visible" | "hidden";
79
+ referrer: string;
80
+ isIncognito: boolean;
81
+ hasCamera: boolean | null;
82
+ hasMicrophone: boolean | null;
83
+ hasBattery: boolean | null;
84
+ batteryLevel: number | null;
85
+ batteryCharging: boolean | null;
86
+ permissions: Record<string, string>;
87
+ }
88
+ //#endregion
89
+ //#region src/useSoloAnalytics.d.ts
90
+ interface SoloAnalyticsOptions {
91
+ /** Automatically refresh dynamic data (network, performance) */
92
+ autoRefresh?: boolean;
93
+ /** Refresh interval in milliseconds (default: 30000) */
94
+ refreshInterval?: number;
95
+ /** Track page visibility changes */
96
+ trackVisibility?: boolean;
97
+ /**
98
+ * Run expensive async probes (incognito, battery, media, permissions).
99
+ * Set to `false` to skip entirely and reduce main-thread work.
100
+ * @default true
101
+ */
102
+ detectFeatures?: boolean;
103
+ /**
104
+ * Defer async feature probes to idle time via requestIdleCallback.
105
+ * Sync data (UA, screen, network) is collected immediately.
106
+ * @default true
107
+ */
108
+ lazyFeatures?: boolean;
109
+ }
110
+ interface SoloAnalyticsReturn {
111
+ data: SoloAnalyticsInfo;
112
+ refresh: () => Promise<void>;
113
+ isMobile: boolean;
114
+ isTablet: boolean;
115
+ isDesktop: boolean;
116
+ isOnline: boolean;
117
+ isVisible: boolean;
118
+ browserName: string;
119
+ osName: string;
120
+ destroy: () => void;
121
+ }
122
+ declare function useSoloAnalytics(options?: SoloAnalyticsOptions): SoloAnalyticsReturn;
123
+ //#endregion
124
+ export { type BrowserInfo, type DeviceInfo, type LocationInfo, type NetworkInfo, type OSInfo, type PerformanceInfo, type ScreenInfo, type SoloAnalyticsInfo, type SoloAnalyticsOptions, type SoloAnalyticsReturn, useSoloAnalytics };
4
125
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ async function e(){return new Promise(e=>{if(typeof window>`u`||typeof indexedDB>`u`){e(!1);return}let t=!1,n=n=>{t||(t=!0,clearTimeout(r),e(n))},r=setTimeout(()=>n(!1),1e3),i=indexedDB.open(`test`);i.onerror=()=>n(!0),i.onsuccess=()=>{i.result?.close(),n(!1)}})}async function t(){if(typeof navigator>`u`||!navigator.mediaDevices||!navigator.mediaDevices.enumerateDevices)return{hasCamera:null,hasMicrophone:null};try{let e=await navigator.mediaDevices.enumerateDevices();return{hasCamera:e.some(e=>e.kind===`videoinput`),hasMicrophone:e.some(e=>e.kind===`audioinput`)}}catch{return{hasCamera:null,hasMicrophone:null}}}async function n(){if(typeof navigator>`u`||!navigator.getBattery)return{hasBattery:null,level:null,charging:null};try{let e=await navigator.getBattery();return{hasBattery:!0,level:e.level,charging:e.charging}}catch{return{hasBattery:null,level:null,charging:null}}}async function r(){if(typeof navigator>`u`||!navigator.permissions||!navigator.permissions.query)return{};let e={},t=await Promise.all([`geolocation`,`notifications`,`push`,`midi`,`camera`,`microphone`,`background-sync`,`accelerometer`,`gyroscope`,`magnetometer`].map(async e=>{try{return[e,(await navigator.permissions.query({name:e})).state]}catch{return[e,`not-supported`]}}));for(let[n,r]of t)e[n]=r;return e}function i(){if(typeof window>`u`||typeof navigator>`u`)return{timeZone:``,language:``,languages:[],isRestricted:!1,doNotTrack:null,cookiesEnabled:!1,localStorage:!1,sessionStorage:!1};let e=Intl.DateTimeFormat().resolvedOptions().timeZone,t=navigator.language||``,n=navigator.languages?Array.from(navigator.languages):[t],r=!1;try{r=window.self!==window.top}catch{r=!0}let i=null;navigator.doNotTrack===`1`||navigator.doNotTrack===`yes`?i=!0:(navigator.doNotTrack===`0`||navigator.doNotTrack===`no`)&&(i=!1);let a=navigator.cookieEnabled,o=e=>{try{let t=window[e],n=`__test_${e}__`;return t.setItem(n,`test`),t.removeItem(n),!0}catch{return!1}};return{timeZone:e,language:t,languages:n,isRestricted:r,doNotTrack:i,cookiesEnabled:a,localStorage:o(`localStorage`),sessionStorage:o(`sessionStorage`)}}function a(){let e=typeof navigator<`u`?navigator.onLine:!1,t=`unknown`,n=0,r=0,i=!1;if(navigator?.connection){let e=navigator.connection;t=e.effectiveType||t,n=e.downlink||n,r=e.rtt||r,i=e.saveData||i}return{online:e,effectiveType:t,downlink:n,rtt:r,saveData:i}}function o(){return typeof window>`u`?{memory:null,navigation:{type:`Unknown`,redirectCount:0},timing:{loadTime:0,domContentLoaded:0,firstPaint:null,firstContentfulPaint:null}}:{memory:(()=>{if(window.performance?.memory){let e=window.performance.memory;return{jsHeapSizeLimit:e.jsHeapSizeLimit,totalJSHeapSize:e.totalJSHeapSize,usedJSHeapSize:e.usedJSHeapSize}}return null})(),navigation:window.performance?.navigation?{type:[`navigate`,`reload`,`back_forward`,`prerender`][window.performance.navigation.type]||`Unknown`,redirectCount:window.performance.navigation.redirectCount}:{type:`Unknown`,redirectCount:0},timing:(()=>{if(!window.performance?.timing)return{loadTime:0,domContentLoaded:0,firstPaint:null,firstContentfulPaint:null};let e=window.performance.timing,t=e.loadEventEnd-e.navigationStart,n=e.domContentLoadedEventEnd-e.navigationStart,r=null,i=null;if(window.performance&&typeof window.performance.getEntriesByType==`function`){let e=window.performance.getEntriesByType(`paint`),t=e.find(e=>e.name===`first-paint`),n=e.find(e=>e.name===`first-contentful-paint`);t&&(r=t.startTime),n&&(i=n.startTime)}return{loadTime:t,domContentLoaded:n,firstPaint:r,firstContentfulPaint:i}})()}}function s(e,t=2e3){if(typeof window>`u`){Promise.resolve().then(e);return}let n=()=>{Promise.resolve(e())};if(`requestIdleCallback`in window){window.requestIdleCallback(()=>n(),{timeout:t});return}setTimeout(n,0)}function c(){if(typeof window>`u`||typeof screen>`u`)return{width:0,height:0,availWidth:0,availHeight:0,colorDepth:0,orientation:`unknown`,pixelRatio:1,touchPoints:0};let e=`unknown`;e=window.innerHeight>window.innerWidth?`portrait`:`landscape`,screen.orientation?.type&&(e=screen.orientation.type);let t=window.devicePixelRatio||1,n=navigator.maxTouchPoints||0;return{width:screen.width,height:screen.height,availWidth:screen.availWidth,availHeight:screen.availHeight,colorDepth:screen.colorDepth,orientation:e,pixelRatio:t,touchPoints:n}}function l(e,t={}){let n=t.vendor??(typeof navigator<`u`?navigator.vendor:``),r=t.innerWidth??(typeof window<`u`?window.innerWidth:1024),i=t.innerHeight??(typeof window<`u`?window.innerHeight:768),a=t.maxTouchPoints??(typeof navigator<`u`&&`maxTouchPoints`in navigator?navigator.maxTouchPoints:0);return{browser:(()=>{for(let t of[{name:`Edge`,regex:/Edg(?:e|A|iOS)?\/([0-9.]+)/},{name:`Samsung Browser`,regex:/SamsungBrowser\/([0-9.]+)/},{name:`Opera`,regex:/(?:Opera|OPR)\/([0-9.]+)/},{name:`Firefox`,regex:/Firefox\/([0-9.]+)/},{name:`Chrome`,regex:/Chrome\/([0-9.]+)/},{name:`Safari`,regex:/Version\/([0-9.]+).*Safari/},{name:`IE`,regex:/MSIE|Trident/}]){let r=e.match(t.regex);if(r){let i=r[1]||``,a=i.split(`.`)[0]||``,o=`Unknown`,s=``;if(e.includes(`AppleWebKit`)){let t=e.match(/AppleWebKit\/([0-9.]+)/);o=`WebKit`,s=t?t[1]:``}else if(e.includes(`Gecko`)){o=`Gecko`;let t=e.match(/rv:([0-9.]+)/);s=t?t[1]:``}else if(e.includes(`Trident`)){o=`Trident`;let t=e.match(/Trident\/([0-9.]+)/);s=t?t[1]:``}return{name:t.name,version:i,major:a,userAgent:e,vendor:n,engine:o,engineVersion:s}}}return{name:`Unknown`,version:``,major:``,userAgent:e,vendor:n,engine:`Unknown`,engineVersion:``}})(),os:(()=>{for(let t of[{name:`iOS`,regex:/iPhone|iPad|iPod/},{name:`Android`,regex:/Android ([0-9.]+)/},{name:`Windows`,regex:/Windows NT ([0-9.]+)/},{name:`macOS`,regex:/Mac OS X ([0-9_.]+)/},{name:`Linux`,regex:/Linux/}]){let n=e.match(t.regex);if(n){let r=``;return n[1]&&(r=t.name===`macOS`?n[1].replace(/_/g,`.`):n[1]),{name:t.name,version:r,architecture:e.includes(`x64`)||e.includes(`x86_64`)?`64-bit`:`32-bit`}}}return{name:`Unknown`,version:``,architecture:e.includes(`x64`)||e.includes(`x86_64`)?`64-bit`:`32-bit`}})(),device:(()=>{let t=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(e),n=/iPad|Android(?!.*Mobile)/i.test(e),o=`Unknown`,s=`Unknown`;if(e.includes(`iPhone`)||e.includes(`iPad`)||e.includes(`iPod`))o=`Apple`,e.includes(`iPhone`)&&(s=`iPhone`),e.includes(`iPad`)&&(s=`iPad`),e.includes(`iPod`)&&(s=`iPod`);else if(e.includes(`Samsung`))o=`Samsung`;else if(e.includes(`Pixel`)){o=`Google`;let t=e.match(/Pixel ([0-9XL]+)/);t&&(s=`Pixel ${t[1]}`)}let c=n?`tablet`:t?`mobile`:`desktop`;return{type:c,vendor:o,model:s,orientation:i>r?`portrait`:`landscape`,isMobile:c===`mobile`,isTablet:c===`tablet`,isDesktop:c===`desktop`,touch:a>0}})()}}const u=()=>({browser:{name:``,version:``,major:``,userAgent:``,vendor:``,engine:``,engineVersion:``},os:{name:``,version:``,architecture:``},device:{type:`unknown`,vendor:``,model:``,orientation:`portrait`,isMobile:!1,isTablet:!1,isDesktop:!1,touch:!1},network:{online:!1,effectiveType:``,downlink:0,rtt:0,saveData:!1},screen:{width:0,height:0,availWidth:0,availHeight:0,colorDepth:0,orientation:``,pixelRatio:1,touchPoints:0},performance:{memory:null,navigation:{type:``,redirectCount:0},timing:{loadTime:0,domContentLoaded:0,firstPaint:null,firstContentfulPaint:null}},location:{timeZone:``,language:``,languages:[],isRestricted:!1,doNotTrack:null,cookiesEnabled:!1,localStorage:!1,sessionStorage:!1},pageVisibility:`visible`,referrer:``,isIncognito:!1,hasCamera:null,hasMicrophone:null,hasBattery:null,batteryLevel:null,batteryCharging:null,permissions:{}});function d(d={}){let{autoRefresh:f=!1,refreshInterval:p=3e4,trackVisibility:m=!0,detectFeatures:h=!0,lazyFeatures:g=!0}=d,_=u(),v=null,y=null,b=!1,x=()=>{if(typeof navigator>`u`||typeof window>`u`)return;let e=navigator.userAgent,{browser:t,os:n,device:r}=l(e);Object.assign(_,{browser:t,os:n,device:r,screen:c(),location:i(),referrer:document.referrer})},S=async()=>{if(!h||b)return;let[i,a,o,s]=await Promise.all([e(),t(),n(),r()]);b||Object.assign(_,{isIncognito:i,hasCamera:a.hasCamera,hasMicrophone:a.hasMicrophone,hasBattery:o.hasBattery,batteryLevel:o.level,batteryCharging:o.charging,permissions:s})},C=()=>{typeof navigator>`u`||typeof window>`u`||Object.assign(_,{network:a(),performance:o(),pageVisibility:document.visibilityState===`visible`?`visible`:`hidden`})},w=()=>{if(!h)return;let e=()=>{S()};if(g){s(e);return}e()};if(m&&typeof document<`u`){let e=()=>{_.pageVisibility=document.visibilityState===`visible`?`visible`:`hidden`};document.addEventListener(`visibilitychange`,e),v=e}return f&&typeof window<`u`&&(y=setInterval(C,p)),x(),C(),w(),{data:_,refresh:async()=>{x(),C(),await S()},destroy:()=>{b=!0,y!==null&&(clearInterval(y),y=null),v&&typeof document<`u`&&(document.removeEventListener(`visibilitychange`,v),v=null)},get isMobile(){return _.device.isMobile},get isTablet(){return _.device.isTablet},get isDesktop(){return _.device.isDesktop},get isOnline(){return _.network.online},get isVisible(){return _.pageVisibility===`visible`},get browserName(){return _.browser.name},get osName(){return _.os.name}}}export{d as useSoloAnalytics};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/utils/features.ts","../src/utils/location.ts","../src/utils/network.ts","../src/utils/performance.ts","../src/utils/scheduleIdle.ts","../src/utils/screen.ts","../src/utils/userAgent.ts","../src/useSoloAnalytics.ts"],"sourcesContent":["export async function detectIncognito(): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n if (typeof window === \"undefined\" || typeof indexedDB === \"undefined\") {\n resolve(false);\n return;\n }\n\n let settled = false;\n const finish = (value: boolean): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutId);\n resolve(value);\n };\n\n const timeoutId = setTimeout(() => finish(false), 1000);\n\n const db = indexedDB.open(\"test\");\n db.onerror = () => finish(true);\n db.onsuccess = () => {\n db.result?.close();\n finish(false);\n };\n });\n}\n\nexport async function checkMediaCapabilities(): Promise<{\n hasCamera: boolean | null;\n hasMicrophone: boolean | null;\n}> {\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n !navigator.mediaDevices.enumerateDevices\n ) {\n return { hasCamera: null, hasMicrophone: null };\n }\n\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n const hasCamera = devices.some((device) => device.kind === \"videoinput\");\n const hasMicrophone = devices.some((device) => device.kind === \"audioinput\");\n\n return { hasCamera, hasMicrophone };\n } catch {\n return { hasCamera: null, hasMicrophone: null };\n }\n}\n\nexport async function getBatteryInfo(): Promise<{\n hasBattery: boolean | null;\n level: number | null;\n charging: boolean | null;\n}> {\n // @ts-expect-error: getBattery method is non-standard\n if (typeof navigator === \"undefined\" || !navigator.getBattery) {\n return { hasBattery: null, level: null, charging: null };\n }\n\n try {\n // @ts-expect-error\n const battery = await navigator.getBattery();\n return {\n hasBattery: true,\n level: battery.level,\n charging: battery.charging,\n };\n } catch {\n return { hasBattery: null, level: null, charging: null };\n }\n}\n\nexport async function checkPermissions(): Promise<Record<string, string>> {\n if (typeof navigator === \"undefined\" || !navigator.permissions || !navigator.permissions.query) {\n return {};\n }\n\n const permissions: Record<string, string> = {};\n const featuresToCheck = [\n \"geolocation\",\n \"notifications\",\n \"push\",\n \"midi\",\n \"camera\",\n \"microphone\",\n \"background-sync\",\n \"accelerometer\",\n \"gyroscope\",\n \"magnetometer\",\n ];\n\n const results = await Promise.all(\n featuresToCheck.map(async (feature) => {\n try {\n const result = await navigator.permissions!.query({ name: feature as PermissionName });\n return [feature, result.state] as const;\n } catch {\n return [feature, \"not-supported\"] as const;\n }\n }),\n );\n\n for (const [feature, state] of results) {\n permissions[feature] = state;\n }\n\n return permissions;\n}\n","import type { LocationInfo } from \"../types/soloAnalytics\";\n\nexport function getLocationInfo(): LocationInfo {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return {\n timeZone: \"\",\n language: \"\",\n languages: [],\n isRestricted: false,\n doNotTrack: null,\n cookiesEnabled: false,\n localStorage: false,\n sessionStorage: false,\n };\n }\n\n // Get timezone\n const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n // Get language info\n const language = navigator.language || \"\";\n const languages = navigator.languages ? Array.from(navigator.languages) : [language];\n\n // Check if window is restricted (e.g., iframe with restricted access)\n let isRestricted = false;\n try {\n isRestricted = window.self !== window.top;\n } catch {\n isRestricted = true;\n }\n\n // Check Do Not Track setting\n let doNotTrack = null;\n if (navigator.doNotTrack === \"1\" || navigator.doNotTrack === \"yes\") {\n doNotTrack = true;\n } else if (navigator.doNotTrack === \"0\" || navigator.doNotTrack === \"no\") {\n doNotTrack = false;\n }\n\n // Check if cookies are enabled\n const cookiesEnabled = navigator.cookieEnabled;\n\n // Check for storage availability\n const checkStorage = (type: \"localStorage\" | \"sessionStorage\"): boolean => {\n try {\n const storage = window[type];\n const testKey = `__test_${type}__`;\n storage.setItem(testKey, \"test\");\n storage.removeItem(testKey);\n return true;\n } catch {\n return false;\n }\n };\n\n return {\n timeZone,\n language,\n languages,\n isRestricted,\n doNotTrack,\n cookiesEnabled,\n localStorage: checkStorage(\"localStorage\"),\n sessionStorage: checkStorage(\"sessionStorage\"),\n };\n}\n","import type { NetworkInfo } from \"../types/soloAnalytics\";\n\nexport function getNetworkInfo(): NetworkInfo {\n const online = typeof navigator !== \"undefined\" ? navigator.onLine : false;\n\n // Default values\n let effectiveType = \"unknown\";\n let downlink = 0;\n let rtt = 0;\n let saveData = false;\n\n // NetworkInformation API (limited browser support)\n // @ts-expect-error: connection property is non-standard\n if (navigator?.connection) {\n // @ts-expect-error\n const connection = navigator.connection;\n\n effectiveType = connection.effectiveType || effectiveType;\n downlink = connection.downlink || downlink;\n rtt = connection.rtt || rtt;\n saveData = connection.saveData || saveData;\n }\n\n return {\n online,\n effectiveType,\n downlink,\n rtt,\n saveData,\n };\n}\n","import type { PerformanceInfo } from \"../types/soloAnalytics\";\n\nexport function getPerformanceInfo(): PerformanceInfo {\n if (typeof window === \"undefined\") {\n return {\n memory: null,\n navigation: { type: \"Unknown\", redirectCount: 0 },\n timing: {\n loadTime: 0,\n domContentLoaded: 0,\n firstPaint: null,\n firstContentfulPaint: null,\n },\n };\n }\n\n const getNavigationInfo = () => {\n if (!window.performance?.navigation) {\n return { type: \"Unknown\", redirectCount: 0 };\n }\n\n const navTypes = [\"navigate\", \"reload\", \"back_forward\", \"prerender\"];\n const navType = navTypes[window.performance.navigation.type] || \"Unknown\";\n\n return {\n type: navType,\n redirectCount: window.performance.navigation.redirectCount,\n };\n };\n\n const getTimingInfo = () => {\n if (!window.performance?.timing) {\n return {\n loadTime: 0,\n domContentLoaded: 0,\n firstPaint: null,\n firstContentfulPaint: null,\n };\n }\n\n const timing = window.performance.timing;\n const loadTime = timing.loadEventEnd - timing.navigationStart;\n const domContentLoaded = timing.domContentLoadedEventEnd - timing.navigationStart;\n\n // Get first paint and first contentful paint\n let firstPaint = null;\n let firstContentfulPaint = null;\n\n if (window.performance && typeof window.performance.getEntriesByType === \"function\") {\n const paintMetrics = window.performance.getEntriesByType(\"paint\");\n\n const fp = paintMetrics.find((entry) => entry.name === \"first-paint\");\n const fcp = paintMetrics.find((entry) => entry.name === \"first-contentful-paint\");\n\n if (fp) firstPaint = fp.startTime;\n if (fcp) firstContentfulPaint = fcp.startTime;\n }\n\n return {\n loadTime,\n domContentLoaded,\n firstPaint,\n firstContentfulPaint,\n };\n };\n\n const getMemoryInfo = () => {\n // @ts-expect-error: performance.memory is non-standard (Chrome only)\n if (window.performance?.memory) {\n // @ts-expect-error\n const memory = window.performance.memory;\n return {\n jsHeapSizeLimit: memory.jsHeapSizeLimit,\n totalJSHeapSize: memory.totalJSHeapSize,\n usedJSHeapSize: memory.usedJSHeapSize,\n };\n }\n return null;\n };\n\n return {\n memory: getMemoryInfo(),\n navigation: getNavigationInfo(),\n timing: getTimingInfo(),\n };\n}\n","/**\n * Schedules work during browser idle time to avoid blocking the main thread.\n * Falls back to setTimeout when requestIdleCallback is unavailable.\n */\nexport function scheduleIdle(callback: () => void | Promise<void>, timeout = 2000): void {\n if (typeof window === \"undefined\") {\n void Promise.resolve().then(callback);\n return;\n }\n\n const run = (): void => {\n void Promise.resolve(callback());\n };\n\n if (\"requestIdleCallback\" in window) {\n window.requestIdleCallback(() => run(), { timeout });\n return;\n }\n\n setTimeout(run, 0);\n}\n","import type { ScreenInfo } from \"../types/soloAnalytics\";\n\nexport function getScreenInfo(): ScreenInfo {\n if (typeof window === \"undefined\" || typeof screen === \"undefined\") {\n return {\n width: 0,\n height: 0,\n availWidth: 0,\n availHeight: 0,\n colorDepth: 0,\n orientation: \"unknown\",\n pixelRatio: 1,\n touchPoints: 0,\n };\n }\n\n // Get screen orientation\n let orientation = \"unknown\";\n if (window.innerHeight > window.innerWidth) {\n orientation = \"portrait\";\n } else {\n orientation = \"landscape\";\n }\n\n // Try to get more precise orientation if available\n if (screen.orientation?.type) {\n orientation = screen.orientation.type;\n }\n\n // Get pixel ratio\n const pixelRatio = window.devicePixelRatio || 1;\n\n // Get touch points\n const touchPoints = navigator.maxTouchPoints || 0;\n\n return {\n width: screen.width,\n height: screen.height,\n availWidth: screen.availWidth,\n availHeight: screen.availHeight,\n colorDepth: screen.colorDepth,\n orientation,\n pixelRatio,\n touchPoints,\n };\n}\n","import type { BrowserInfo, DeviceInfo, OSInfo } from \"../types/soloAnalytics\";\n\nexport interface UserAgentOptions {\n vendor?: string;\n innerWidth?: number;\n innerHeight?: number;\n maxTouchPoints?: number;\n}\n\nexport function parseUserAgent(\n ua: string,\n options: UserAgentOptions = {},\n): {\n browser: BrowserInfo;\n os: OSInfo;\n device: DeviceInfo;\n} {\n const vendor = options.vendor ?? (typeof navigator !== \"undefined\" ? navigator.vendor : \"\");\n const innerWidth =\n options.innerWidth ?? (typeof window !== \"undefined\" ? window.innerWidth : 1024);\n const innerHeight =\n options.innerHeight ?? (typeof window !== \"undefined\" ? window.innerHeight : 768);\n const maxTouchPoints =\n options.maxTouchPoints ??\n (typeof navigator !== \"undefined\" && \"maxTouchPoints\" in navigator\n ? navigator.maxTouchPoints\n : 0);\n\n // Browser detection\n const getBrowser = (): BrowserInfo => {\n const browsers = [\n { name: \"Edge\", regex: /Edg(?:e|A|iOS)?\\/([0-9.]+)/ },\n { name: \"Samsung Browser\", regex: /SamsungBrowser\\/([0-9.]+)/ },\n { name: \"Opera\", regex: /(?:Opera|OPR)\\/([0-9.]+)/ },\n { name: \"Firefox\", regex: /Firefox\\/([0-9.]+)/ },\n { name: \"Chrome\", regex: /Chrome\\/([0-9.]+)/ },\n { name: \"Safari\", regex: /Version\\/([0-9.]+).*Safari/ },\n { name: \"IE\", regex: /MSIE|Trident/ },\n ];\n\n for (const browser of browsers) {\n const match = ua.match(browser.regex);\n if (match) {\n const version = match[1] || \"\";\n const major = version.split(\".\")[0] || \"\";\n\n // Engine detection\n let engine = \"Unknown\";\n let engineVersion = \"\";\n\n if (ua.includes(\"AppleWebKit\")) {\n const webkitMatch = ua.match(/AppleWebKit\\/([0-9.]+)/);\n engine = \"WebKit\";\n engineVersion = webkitMatch ? webkitMatch[1] : \"\";\n } else if (ua.includes(\"Gecko\")) {\n engine = \"Gecko\";\n const geckoMatch = ua.match(/rv:([0-9.]+)/);\n engineVersion = geckoMatch ? geckoMatch[1] : \"\";\n } else if (ua.includes(\"Trident\")) {\n engine = \"Trident\";\n const tridentMatch = ua.match(/Trident\\/([0-9.]+)/);\n engineVersion = tridentMatch ? tridentMatch[1] : \"\";\n }\n\n return {\n name: browser.name,\n version,\n major,\n userAgent: ua,\n vendor,\n engine,\n engineVersion,\n };\n }\n }\n\n return {\n name: \"Unknown\",\n version: \"\",\n major: \"\",\n userAgent: ua,\n vendor,\n engine: \"Unknown\",\n engineVersion: \"\",\n };\n };\n\n // OS detection\n const getOS = (): OSInfo => {\n const osMatchers = [\n { name: \"iOS\", regex: /iPhone|iPad|iPod/ },\n { name: \"Android\", regex: /Android ([0-9.]+)/ },\n { name: \"Windows\", regex: /Windows NT ([0-9.]+)/ },\n { name: \"macOS\", regex: /Mac OS X ([0-9_.]+)/ },\n { name: \"Linux\", regex: /Linux/ },\n ];\n\n for (const os of osMatchers) {\n const match = ua.match(os.regex);\n if (match) {\n let version = \"\";\n if (match[1]) {\n version = os.name === \"macOS\" ? match[1].replace(/_/g, \".\") : match[1];\n }\n\n return {\n name: os.name,\n version,\n architecture: ua.includes(\"x64\") || ua.includes(\"x86_64\") ? \"64-bit\" : \"32-bit\",\n };\n }\n }\n\n return {\n name: \"Unknown\",\n version: \"\",\n architecture: ua.includes(\"x64\") || ua.includes(\"x86_64\") ? \"64-bit\" : \"32-bit\",\n };\n };\n\n // Device detection\n const getDevice = (): DeviceInfo => {\n const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n ua,\n );\n const isTablet = /iPad|Android(?!.*Mobile)/i.test(ua);\n\n let vendor = \"Unknown\";\n let model = \"Unknown\";\n\n if (ua.includes(\"iPhone\") || ua.includes(\"iPad\") || ua.includes(\"iPod\")) {\n vendor = \"Apple\";\n if (ua.includes(\"iPhone\")) model = \"iPhone\";\n if (ua.includes(\"iPad\")) model = \"iPad\";\n if (ua.includes(\"iPod\")) model = \"iPod\";\n } else if (ua.includes(\"Samsung\")) {\n vendor = \"Samsung\";\n } else if (ua.includes(\"Pixel\")) {\n vendor = \"Google\";\n const pixelMatch = ua.match(/Pixel ([0-9XL]+)/);\n if (pixelMatch) model = `Pixel ${pixelMatch[1]}`;\n }\n\n const deviceType = isTablet ? \"tablet\" : isMobileDevice ? \"mobile\" : \"desktop\";\n\n return {\n type: deviceType,\n vendor,\n model,\n orientation: innerHeight > innerWidth ? \"portrait\" : \"landscape\",\n isMobile: deviceType === \"mobile\",\n isTablet: deviceType === \"tablet\",\n isDesktop: deviceType === \"desktop\",\n touch: maxTouchPoints > 0,\n };\n };\n\n return {\n browser: getBrowser(),\n os: getOS(),\n device: getDevice(),\n };\n}\n","import type { SoloAnalyticsInfo } from \"./types/soloAnalytics\";\nimport {\n checkMediaCapabilities,\n checkPermissions,\n detectIncognito,\n getBatteryInfo,\n} from \"./utils/features\";\nimport { getLocationInfo } from \"./utils/location\";\nimport { getNetworkInfo } from \"./utils/network\";\nimport { getPerformanceInfo } from \"./utils/performance\";\nimport { scheduleIdle } from \"./utils/scheduleIdle\";\nimport { getScreenInfo } from \"./utils/screen\";\nimport { parseUserAgent } from \"./utils/userAgent\";\n\nexport interface SoloAnalyticsOptions {\n /** Automatically refresh dynamic data (network, performance) */\n autoRefresh?: boolean;\n /** Refresh interval in milliseconds (default: 30000) */\n refreshInterval?: number;\n /** Track page visibility changes */\n trackVisibility?: boolean;\n /**\n * Run expensive async probes (incognito, battery, media, permissions).\n * Set to `false` to skip entirely and reduce main-thread work.\n * @default true\n */\n detectFeatures?: boolean;\n /**\n * Defer async feature probes to idle time via requestIdleCallback.\n * Sync data (UA, screen, network) is collected immediately.\n * @default true\n */\n lazyFeatures?: boolean;\n}\n\nexport interface SoloAnalyticsReturn {\n data: SoloAnalyticsInfo;\n refresh: () => Promise<void>;\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n isOnline: boolean;\n isVisible: boolean;\n browserName: string;\n osName: string;\n destroy: () => void;\n}\n\nconst createInitialData = (): SoloAnalyticsInfo => ({\n browser: {\n name: \"\",\n version: \"\",\n major: \"\",\n userAgent: \"\",\n vendor: \"\",\n engine: \"\",\n engineVersion: \"\",\n },\n os: {\n name: \"\",\n version: \"\",\n architecture: \"\",\n },\n device: {\n type: \"unknown\",\n vendor: \"\",\n model: \"\",\n orientation: \"portrait\",\n isMobile: false,\n isTablet: false,\n isDesktop: false,\n touch: false,\n },\n network: {\n online: false,\n effectiveType: \"\",\n downlink: 0,\n rtt: 0,\n saveData: false,\n },\n screen: {\n width: 0,\n height: 0,\n availWidth: 0,\n availHeight: 0,\n colorDepth: 0,\n orientation: \"\",\n pixelRatio: 1,\n touchPoints: 0,\n },\n performance: {\n memory: null,\n navigation: {\n type: \"\",\n redirectCount: 0,\n },\n timing: {\n loadTime: 0,\n domContentLoaded: 0,\n firstPaint: null,\n firstContentfulPaint: null,\n },\n },\n location: {\n timeZone: \"\",\n language: \"\",\n languages: [],\n isRestricted: false,\n doNotTrack: null,\n cookiesEnabled: false,\n localStorage: false,\n sessionStorage: false,\n },\n pageVisibility: \"visible\",\n referrer: \"\",\n isIncognito: false,\n hasCamera: null,\n hasMicrophone: null,\n hasBattery: null,\n batteryLevel: null,\n batteryCharging: null,\n permissions: {},\n});\n\nexport function useSoloAnalytics(options: SoloAnalyticsOptions = {}): SoloAnalyticsReturn {\n const {\n autoRefresh = false,\n refreshInterval = 30000,\n trackVisibility = true,\n detectFeatures = true,\n lazyFeatures = true,\n } = options;\n\n const analyticsData = createInitialData();\n\n let visibilityChangeListener: (() => void) | null = null;\n let refreshTimer: ReturnType<typeof setInterval> | null = null;\n let destroyed = false;\n\n const collectSyncData = (): void => {\n if (typeof navigator === \"undefined\" || typeof window === \"undefined\") {\n return;\n }\n\n const userAgent = navigator.userAgent;\n const { browser, os, device } = parseUserAgent(userAgent);\n\n Object.assign(analyticsData, {\n browser,\n os,\n device,\n screen: getScreenInfo(),\n location: getLocationInfo(),\n referrer: document.referrer,\n });\n };\n\n const collectAsyncFeatures = async (): Promise<void> => {\n if (!detectFeatures || destroyed) {\n return;\n }\n\n const [isIncognito, media, battery, permissions] = await Promise.all([\n detectIncognito(),\n checkMediaCapabilities(),\n getBatteryInfo(),\n checkPermissions(),\n ]);\n\n if (destroyed) {\n return;\n }\n\n Object.assign(analyticsData, {\n isIncognito,\n hasCamera: media.hasCamera,\n hasMicrophone: media.hasMicrophone,\n hasBattery: battery.hasBattery,\n batteryLevel: battery.level,\n batteryCharging: battery.charging,\n permissions,\n });\n };\n\n const collectDynamicData = (): void => {\n if (typeof navigator === \"undefined\" || typeof window === \"undefined\") {\n return;\n }\n\n Object.assign(analyticsData, {\n network: getNetworkInfo(),\n performance: getPerformanceInfo(),\n pageVisibility: document.visibilityState === \"visible\" ? \"visible\" : \"hidden\",\n });\n };\n\n const runFeatureDetection = (): void => {\n if (!detectFeatures) {\n return;\n }\n\n const run = (): void => {\n void collectAsyncFeatures();\n };\n\n if (lazyFeatures) {\n scheduleIdle(run);\n return;\n }\n\n run();\n };\n\n if (trackVisibility && typeof document !== \"undefined\") {\n const handleVisibilityChange = (): void => {\n analyticsData.pageVisibility = document.visibilityState === \"visible\" ? \"visible\" : \"hidden\";\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n visibilityChangeListener = handleVisibilityChange;\n }\n\n if (autoRefresh && typeof window !== \"undefined\") {\n refreshTimer = setInterval(collectDynamicData, refreshInterval);\n }\n\n const init = (): void => {\n collectSyncData();\n collectDynamicData();\n runFeatureDetection();\n };\n\n init();\n\n const refresh = async (): Promise<void> => {\n collectSyncData();\n collectDynamicData();\n await collectAsyncFeatures();\n };\n\n const destroy = (): void => {\n destroyed = true;\n\n if (refreshTimer !== null) {\n clearInterval(refreshTimer);\n refreshTimer = null;\n }\n\n if (visibilityChangeListener && typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", visibilityChangeListener);\n visibilityChangeListener = null;\n }\n };\n\n return {\n data: analyticsData,\n refresh,\n destroy,\n get isMobile(): boolean {\n return analyticsData.device.isMobile;\n },\n get isTablet(): boolean {\n return analyticsData.device.isTablet;\n },\n get isDesktop(): boolean {\n return analyticsData.device.isDesktop;\n },\n get isOnline(): boolean {\n return analyticsData.network.online;\n },\n get isVisible(): boolean {\n return analyticsData.pageVisibility === \"visible\";\n },\n get browserName(): string {\n return analyticsData.browser.name;\n },\n get osName(): string {\n return analyticsData.os.name;\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,59 +1,75 @@
1
1
  {
2
2
  "name": "solo-analytics",
3
- "version": "0.2.0",
4
- "description": "A lightweight, tree-shakeable TypeScript library for comprehensive browser and device analytics",
5
- "type": "module",
6
- "sideEffects": false,
3
+ "version": "0.3.2",
4
+ "description": "Collect structured browser, device, and network context on the client. Typed, tree-shakeable, no vendor lock-in.",
5
+ "keywords": [
6
+ "analytics",
7
+ "browser",
8
+ "browser-detection",
9
+ "composable",
10
+ "device",
11
+ "device-detection",
12
+ "typescript",
13
+ "user-agent"
14
+ ],
15
+ "homepage": "https://github.com/cesswhite/solo-analytics#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/cesswhite/solo-analytics/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Céss White",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/cesswhite/solo-analytics.git"
24
+ },
7
25
  "files": [
8
26
  "dist",
9
27
  "LICENSE",
10
28
  "README.md"
11
29
  ],
12
- "main": "./dist/solo-analytics.umd.cjs",
13
- "module": "./dist/solo-analytics.js",
30
+ "type": "module",
31
+ "sideEffects": false,
32
+ "main": "./dist/index.cjs",
33
+ "module": "./dist/index.js",
14
34
  "types": "./dist/index.d.ts",
15
35
  "exports": {
16
36
  ".": {
17
- "types": "./dist/index.d.ts",
18
- "import": "./dist/solo-analytics.js",
19
- "require": "./dist/solo-analytics.umd.cjs"
37
+ "import": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/index.d.cts",
43
+ "default": "./dist/index.cjs"
44
+ }
20
45
  }
21
46
  },
22
47
  "scripts": {
23
- "dev": "vite",
24
- "build": "vite build && tsc -p tsconfig.build.json",
25
- "preview": "vite preview",
26
- "test": "vitest run",
27
- "test:watch": "vitest",
28
- "prepublishOnly": "npm run build && npm test"
48
+ "dev": "vp dev",
49
+ "build": "vp pack",
50
+ "preview": "vp preview",
51
+ "test": "vp test run",
52
+ "test:coverage": "vp test run --coverage",
53
+ "test:watch": "vp test watch",
54
+ "check": "vp check",
55
+ "check:fix": "vp check --fix",
56
+ "lint": "vp lint",
57
+ "fmt": "vp fmt",
58
+ "prepublishOnly": "vp check && vp pack && vp test run --coverage"
29
59
  },
30
60
  "devDependencies": {
61
+ "@arethetypeswrong/cli": "^0.18.4",
31
62
  "@types/node": "^22.15.21",
63
+ "@vitest/coverage-v8": "4.1.9",
32
64
  "happy-dom": "^17.4.7",
65
+ "publint": "^0.3.21",
33
66
  "typescript": "~5.7.2",
34
- "vite": "^6.3.1",
35
- "vitest": "^3.1.4"
67
+ "vite-plus": "^0.2.1"
36
68
  },
37
- "keywords": [
38
- "analytics",
39
- "browser",
40
- "device",
41
- "user-agent",
42
- "typescript",
43
- "composable",
44
- "device-detection",
45
- "browser-detection"
46
- ],
47
- "author": "Céss White",
48
- "license": "MIT",
49
- "repository": {
50
- "type": "git",
51
- "url": "git+https://github.com/cesswhite/solo-analytics.git"
69
+ "overrides": {
70
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
71
+ "vitest": "4.1.9"
52
72
  },
53
- "bugs": {
54
- "url": "https://github.com/cesswhite/solo-analytics/issues"
55
- },
56
- "homepage": "https://github.com/cesswhite/solo-analytics#readme",
57
73
  "engines": {
58
74
  "node": ">=18"
59
75
  }
@@ -1,46 +0,0 @@
1
- import { SoloAnalyticsInfo } from '../types/analytics';
2
- /**
3
- * Type definition for analytics options
4
- */
5
- export interface SoloAnalyticsOptions {
6
- /** Automatically refresh dynamic data (network, performance) */
7
- autoRefresh?: boolean;
8
- /** Refresh interval in milliseconds (default: 30000) */
9
- refreshInterval?: number;
10
- /** Track page visibility changes */
11
- trackVisibility?: boolean;
12
- /**
13
- * Run expensive async probes (incognito, battery, media, permissions).
14
- * Set to `false` to skip entirely and reduce main-thread work.
15
- * @default true
16
- */
17
- detectFeatures?: boolean;
18
- /**
19
- * Defer async feature probes to idle time via requestIdleCallback.
20
- * Sync data (UA, screen, network) is collected immediately.
21
- * @default true
22
- */
23
- lazyFeatures?: boolean;
24
- }
25
- /**
26
- * Type definition for the return value of useSoloAnalytics
27
- */
28
- export interface SoloAnalyticsReturn {
29
- data: SoloAnalyticsInfo;
30
- refresh: () => Promise<void>;
31
- isMobile: boolean;
32
- isTablet: boolean;
33
- isDesktop: boolean;
34
- isOnline: boolean;
35
- isVisible: boolean;
36
- browserName: string;
37
- osName: string;
38
- destroy: () => void;
39
- }
40
- /**
41
- * A function that provides comprehensive browser, device, and performance analytics.
42
- * @param options Configuration options
43
- * @returns Object with analytics data and helper methods
44
- */
45
- export declare function useSoloAnalytics(options?: SoloAnalyticsOptions): SoloAnalyticsReturn;
46
- //# sourceMappingURL=useSoloAnalytics.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useSoloAnalytics.d.ts","sourceRoot":"","sources":["../../src/composables/useSoloAnalytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAevD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oCAAoC;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,iBAAiB,CAAC;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA8ED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CA6JxF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAGhG,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,YAAY,EACb,MAAM,mBAAmB,CAAC"}