onedollarstats 0.0.20 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/expo.js +2 -1
- package/dist/expo.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ event("Purchase", "/product", { amount: 1, color: "green" });
|
|
|
119
119
|
- `pathOrProps` – Optional, **string** represents the path, **object** represents custom properties.
|
|
120
120
|
- `props` – Optional, properties if the second argument is a path string.
|
|
121
121
|
|
|
122
|
-
## Expo
|
|
122
|
+
## Expo
|
|
123
123
|
|
|
124
124
|
`onedollarstats/expo` is a dedicated entry point for Expo apps using `expo-router`. It auto-collects pageviews on route change and on app foreground, supports dynamic-route templates (`/profile/[id]` instead of `/profile/abc123`), and sends events natively on iOS/Android and via image beacon + `sendBeacon` on web.
|
|
125
125
|
|
package/dist/expo.js
CHANGED
|
@@ -142,7 +142,8 @@ function send(eventName, path, config, props) {
|
|
|
142
142
|
if (config.devmode) devLog(eventName, url, props);
|
|
143
143
|
const body = JSON.stringify({
|
|
144
144
|
u: url,
|
|
145
|
-
e: [{ t: eventName, ...props && { p: props } }]
|
|
145
|
+
e: [{ t: eventName, ...props && { p: props } }],
|
|
146
|
+
debug: config.devmode
|
|
146
147
|
});
|
|
147
148
|
if (Platform.OS === "web") {
|
|
148
149
|
sendWeb(config.collectorUrl, body);
|
package/dist/expo.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/expo.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type MutableRefObject,\n type ReactNode\n} from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport { usePathname, useSegments } from 'expo-router';\n\nexport type ExpoAnalyticsConfig = {\n hostname: string;\n collectorUrl?: string;\n excludePages?: string[];\n includePages?: string[];\n autocollect?: boolean;\n devmode?: boolean;\n collapseDynamicRoutes?: boolean;\n};\n\ntype InternalConfig = {\n hostname: string;\n collectorUrl: string;\n autocollect: boolean;\n devmode: boolean;\n collapseDynamicRoutes: boolean;\n excludePages?: string[];\n includePages?: string[];\n};\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\ntype OverrideSource = 'hook' | 'component';\ntype Override = { realPath: string; customPath: string; source: OverrideSource } | null;\ntype PropsOverride =\n | { realPath: string; props: Record<string, string>; source: OverrideSource }\n | null;\n*/\n\ntype ContextValue = {\n config: InternalConfig;\n lastPathRef: MutableRefObject<string | null>;\n // TODO(page-scope): restore when page-scope detection is designed.\n // overrideRef: MutableRefObject<Override>;\n // propsOverrideRef: MutableRefObject<PropsOverride>;\n};\n\nconst Context = createContext<ContextValue | null>(null);\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\nfunction resolvePath(pathname: string, overrideRef: MutableRefObject<Override>): string {\n const o = overrideRef.current;\n return o && o.realPath === pathname ? o.customPath : pathname;\n}\n\nfunction resolveProps(\n pathname: string,\n propsOverrideRef: MutableRefObject<PropsOverride>\n): Record<string, string> | undefined {\n const o = propsOverrideRef.current;\n return o && o.realPath === pathname ? o.props : undefined;\n}\n\nfunction mergeProps(\n screenProps: Record<string, string> | undefined,\n explicitProps: Record<string, string> | undefined\n): Record<string, string> | undefined {\n if (!screenProps && !explicitProps) return undefined;\n if (!screenProps) return explicitProps;\n if (!explicitProps) return screenProps;\n return { ...screenProps, ...explicitProps };\n}\n*/\n\nfunction mergeConfig(config: ExpoAnalyticsConfig): InternalConfig {\n return {\n hostname: config.hostname,\n collectorUrl: config.collectorUrl ?? 'https://collector.onedollarstats.com/events',\n autocollect: config.autocollect ?? true,\n devmode: config.devmode ?? false,\n collapseDynamicRoutes: config.collapseDynamicRoutes ?? true,\n excludePages: config.excludePages,\n includePages: config.includePages\n };\n}\n\nfunction isGroupSegment(segment: string): boolean {\n return /^\\(.+\\)$/.test(segment);\n}\n\nfunction collapsePath(segments: readonly string[]): string {\n const visible = segments.filter(s => !isGroupSegment(s));\n if (visible.length === 0) return '/';\n return '/' + visible.join('/');\n}\n\nfunction useTrackedPath(config: InternalConfig): string {\n const pathname = usePathname();\n const segments = useSegments();\n return config.collapseDynamicRoutes ? collapsePath(segments as readonly string[]) : pathname;\n}\n\nfunction isWebLocalhost(): boolean {\n if (Platform.OS !== 'web') return false;\n if (typeof window === 'undefined' || !window.location) return false;\n const { hostname, protocol } = window.location;\n return (\n /^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(hostname) &&\n (protocol === 'http:' || protocol === 'https:')\n );\n}\n\nfunction useRequiredContext(caller: string): ContextValue {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `[onedollarstats] ${caller} must be used inside <OneDollarStatsProvider>. ` +\n `Wrap your root layout with the provider.`\n );\n }\n return ctx;\n}\n\nexport type OneDollarStatsProviderProps = {\n config: ExpoAnalyticsConfig;\n children: ReactNode;\n};\n\nexport function OneDollarStatsProvider({ config, children }: OneDollarStatsProviderProps) {\n const merged = useMemo(\n () => mergeConfig(config),\n [\n config.hostname,\n config.collectorUrl,\n config.autocollect,\n config.devmode,\n config.collapseDynamicRoutes,\n config.excludePages,\n config.includePages\n ]\n );\n\n const lastPathRef = useRef<string | null>(null);\n // TODO(page-scope): restore when page-scope detection is designed.\n // const overrideRef = useRef<Override>(null);\n // const propsOverrideRef = useRef<PropsOverride>(null);\n const announcedRef = useRef(false);\n const trackedPath = useTrackedPath(merged);\n\n useEffect(() => {\n if (announcedRef.current) return;\n if (merged.devmode && isWebLocalhost()) {\n console.log(\n `[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${merged.hostname}`\n );\n }\n announcedRef.current = true;\n }, [merged.devmode, merged.hostname]);\n\n useEffect(() => {\n if (!merged.autocollect) return;\n if (isExcluded(trackedPath, merged)) return;\n if (lastPathRef.current === trackedPath) return;\n lastPathRef.current = trackedPath;\n send('PageView', trackedPath, merged);\n }, [trackedPath, merged]);\n\n useEffect(() => {\n const handler = (state: AppStateStatus) => {\n if (state !== 'active') return;\n if (!merged.autocollect) return;\n const current = lastPathRef.current;\n if (!current || isExcluded(current, merged)) return;\n send('PageView', current, merged);\n };\n const sub = AppState.addEventListener('change', handler);\n return () => sub.remove();\n }, [merged]);\n\n const value = useMemo<ContextValue>(\n () => ({ config: merged, lastPathRef }),\n [merged]\n );\n\n return createElement(Context.Provider, { value }, children);\n}\n\ntype Props = Record<string, string>;\n\nexport type AnalyticsAPI = {\n event(eventName: string, pathOrProps?: string | Props, props?: Props): void;\n view(pathOrProps?: string | Props, props?: Props): void;\n};\n\nexport function useAnalytics(): AnalyticsAPI {\n const ctx = useRequiredContext('useAnalytics');\n const trackedPath = useTrackedPath(ctx.config);\n\n const event = useCallback(\n (eventName: string, pathOrProps?: string | Props, props?: Props) => {\n const targetPath = typeof pathOrProps === 'string' ? pathOrProps : trackedPath;\n const eventProps = typeof pathOrProps === 'object' ? pathOrProps : props;\n send(eventName, targetPath, ctx.config, eventProps);\n },\n [ctx, trackedPath]\n );\n\n const view = useCallback(\n (pathOrProps?: string | Props, props?: Props) => {\n const targetPath = typeof pathOrProps === 'string' ? pathOrProps : trackedPath;\n const viewProps = typeof pathOrProps === 'object' ? pathOrProps : props;\n send('PageView', targetPath, ctx.config, viewProps);\n },\n [ctx, trackedPath]\n );\n\n return { event, view };\n}\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\nexport function useAnalyticsPath(customPath: string): void {\n const ctx = useRequiredContext('useAnalyticsPath');\n const pathname = usePathname();\n useEffect(() => {\n const entry: Override = { realPath: pathname, customPath, source: 'hook' };\n ctx.overrideRef.current = entry;\n return () => {\n if (ctx.overrideRef.current === entry) ctx.overrideRef.current = null;\n };\n }, [ctx, pathname, customPath]);\n}\n\nexport type AnalyticsPathProps = { path: string };\n\nexport function AnalyticsPath({ path }: AnalyticsPathProps): null {\n const ctx = useRequiredContext('AnalyticsPath');\n const pathname = usePathname();\n useEffect(() => {\n const existing = ctx.overrideRef.current;\n if (existing && existing.realPath === pathname && existing.source === 'hook') return;\n const entry: Override = { realPath: pathname, customPath: path, source: 'component' };\n ctx.overrideRef.current = entry;\n return () => {\n if (ctx.overrideRef.current === entry) ctx.overrideRef.current = null;\n };\n }, [ctx, pathname, path]);\n return null;\n}\n\nexport function useAnalyticsProps(props: Record<string, string>): void {\n const ctx = useRequiredContext('useAnalyticsProps');\n const pathname = usePathname();\n useEffect(() => {\n const entry: PropsOverride = { realPath: pathname, props, source: 'hook' };\n ctx.propsOverrideRef.current = entry;\n return () => {\n if (ctx.propsOverrideRef.current === entry) ctx.propsOverrideRef.current = null;\n };\n }, [ctx, pathname, props]);\n}\n\nexport type AnalyticsPropsProps = Record<string, string>;\n\nexport function AnalyticsProps(props: AnalyticsPropsProps): null {\n const ctx = useRequiredContext('AnalyticsProps');\n const pathname = usePathname();\n useEffect(() => {\n const existing = ctx.propsOverrideRef.current;\n if (existing && existing.realPath === pathname && existing.source === 'hook') return;\n const entry: PropsOverride = { realPath: pathname, props, source: 'component' };\n ctx.propsOverrideRef.current = entry;\n return () => {\n if (ctx.propsOverrideRef.current === entry) ctx.propsOverrideRef.current = null;\n };\n }, [ctx, pathname, props]);\n return null;\n}\n*/\n\nfunction shouldSkipSend(config: InternalConfig): boolean {\n if (Platform.OS !== 'web') return false;\n if (isWebLocalhost() && !config.devmode) return true;\n return false;\n}\n\nfunction devLog(label: string, url: string, props?: Record<string, string>): void {\n let msg = `[onedollarstats]\\nEvent name: ${label}\\nEvent collected from: ${url}`;\n if (props && Object.keys(props).length > 0) {\n msg += `\\nProps: ${JSON.stringify(props, null, 2)}`;\n }\n console.log(msg);\n}\n\nconst SAFE_GET_THRESHOLD = 1500;\n\nfunction send(\n eventName: string,\n path: string,\n config: InternalConfig,\n props?: Record<string, string>\n): void {\n if (shouldSkipSend(config)) return;\n const url = `https://${config.hostname}${path}`;\n if (config.devmode) devLog(eventName, url, props);\n\n const body = JSON.stringify({\n u: url,\n e: [{ t: eventName, ...(props && { p: props }) }]\n });\n\n if (Platform.OS === 'web') {\n sendWeb(config.collectorUrl, body);\n } else {\n sendNative(config.collectorUrl, body);\n }\n}\n\nfunction sendNative(collectorUrl: string, body: string): void {\n fetch(collectorUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body\n }).catch(() => {});\n}\n\nfunction sendWeb(collectorUrl: string, body: string): void {\n const bytes = new TextEncoder().encode(body);\n const bin = String.fromCharCode(...bytes);\n const payloadBase64 = btoa(bin);\n\n if (payloadBase64.length <= SAFE_GET_THRESHOLD) {\n const img = new Image(1, 1);\n img.onerror = () => sendBeaconOrFetch(collectorUrl, body);\n img.src = `${collectorUrl}?data=${payloadBase64}`;\n return;\n }\n\n sendBeaconOrFetch(collectorUrl, body);\n}\n\nfunction sendBeaconOrFetch(collectorUrl: string, body: string): void {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon?.(collectorUrl, body)) {\n return;\n }\n\n fetch(collectorUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true\n }).catch(() => {});\n}\n\nfunction pathMatches(path: string, prefix: string): boolean {\n return path === prefix || path.startsWith(prefix.endsWith('/') ? prefix : prefix + '/');\n}\n\nfunction isExcluded(path: string, config: InternalConfig): boolean {\n if (config.includePages?.length) {\n return !config.includePages.some(p => pathMatches(path, p));\n }\n if (config.excludePages?.length) {\n return config.excludePages.some(p => pathMatches(path, p));\n }\n return false;\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type MutableRefObject,\n type ReactNode\n} from 'react';\nimport { AppState, Platform, type AppStateStatus } from 'react-native';\nimport { usePathname, useSegments } from 'expo-router';\n\nexport type ExpoAnalyticsConfig = {\n hostname: string;\n collectorUrl?: string;\n excludePages?: string[];\n includePages?: string[];\n autocollect?: boolean;\n devmode?: boolean;\n collapseDynamicRoutes?: boolean;\n};\n\ntype InternalConfig = {\n hostname: string;\n collectorUrl: string;\n autocollect: boolean;\n devmode: boolean;\n collapseDynamicRoutes: boolean;\n excludePages?: string[];\n includePages?: string[];\n};\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\ntype OverrideSource = 'hook' | 'component';\ntype Override = { realPath: string; customPath: string; source: OverrideSource } | null;\ntype PropsOverride =\n | { realPath: string; props: Record<string, string>; source: OverrideSource }\n | null;\n*/\n\ntype ContextValue = {\n config: InternalConfig;\n lastPathRef: MutableRefObject<string | null>;\n // TODO(page-scope): restore when page-scope detection is designed.\n // overrideRef: MutableRefObject<Override>;\n // propsOverrideRef: MutableRefObject<PropsOverride>;\n};\n\nconst Context = createContext<ContextValue | null>(null);\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\nfunction resolvePath(pathname: string, overrideRef: MutableRefObject<Override>): string {\n const o = overrideRef.current;\n return o && o.realPath === pathname ? o.customPath : pathname;\n}\n\nfunction resolveProps(\n pathname: string,\n propsOverrideRef: MutableRefObject<PropsOverride>\n): Record<string, string> | undefined {\n const o = propsOverrideRef.current;\n return o && o.realPath === pathname ? o.props : undefined;\n}\n\nfunction mergeProps(\n screenProps: Record<string, string> | undefined,\n explicitProps: Record<string, string> | undefined\n): Record<string, string> | undefined {\n if (!screenProps && !explicitProps) return undefined;\n if (!screenProps) return explicitProps;\n if (!explicitProps) return screenProps;\n return { ...screenProps, ...explicitProps };\n}\n*/\n\nfunction mergeConfig(config: ExpoAnalyticsConfig): InternalConfig {\n return {\n hostname: config.hostname,\n collectorUrl: config.collectorUrl ?? 'https://collector.onedollarstats.com/events',\n autocollect: config.autocollect ?? true,\n devmode: config.devmode ?? false,\n collapseDynamicRoutes: config.collapseDynamicRoutes ?? true,\n excludePages: config.excludePages,\n includePages: config.includePages\n };\n}\n\nfunction isGroupSegment(segment: string): boolean {\n return /^\\(.+\\)$/.test(segment);\n}\n\nfunction collapsePath(segments: readonly string[]): string {\n const visible = segments.filter(s => !isGroupSegment(s));\n if (visible.length === 0) return '/';\n return '/' + visible.join('/');\n}\n\nfunction useTrackedPath(config: InternalConfig): string {\n const pathname = usePathname();\n const segments = useSegments();\n return config.collapseDynamicRoutes ? collapsePath(segments as readonly string[]) : pathname;\n}\n\nfunction isWebLocalhost(): boolean {\n if (Platform.OS !== 'web') return false;\n if (typeof window === 'undefined' || !window.location) return false;\n const { hostname, protocol } = window.location;\n return (\n /^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(hostname) &&\n (protocol === 'http:' || protocol === 'https:')\n );\n}\n\nfunction useRequiredContext(caller: string): ContextValue {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `[onedollarstats] ${caller} must be used inside <OneDollarStatsProvider>. ` +\n `Wrap your root layout with the provider.`\n );\n }\n return ctx;\n}\n\nexport type OneDollarStatsProviderProps = {\n config: ExpoAnalyticsConfig;\n children: ReactNode;\n};\n\nexport function OneDollarStatsProvider({ config, children }: OneDollarStatsProviderProps) {\n const merged = useMemo(\n () => mergeConfig(config),\n [\n config.hostname,\n config.collectorUrl,\n config.autocollect,\n config.devmode,\n config.collapseDynamicRoutes,\n config.excludePages,\n config.includePages\n ]\n );\n\n const lastPathRef = useRef<string | null>(null);\n // TODO(page-scope): restore when page-scope detection is designed.\n // const overrideRef = useRef<Override>(null);\n // const propsOverrideRef = useRef<PropsOverride>(null);\n const announcedRef = useRef(false);\n const trackedPath = useTrackedPath(merged);\n\n useEffect(() => {\n if (announcedRef.current) return;\n if (merged.devmode && isWebLocalhost()) {\n console.log(\n `[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${merged.hostname}`\n );\n }\n announcedRef.current = true;\n }, [merged.devmode, merged.hostname]);\n\n useEffect(() => {\n if (!merged.autocollect) return;\n if (isExcluded(trackedPath, merged)) return;\n if (lastPathRef.current === trackedPath) return;\n lastPathRef.current = trackedPath;\n send('PageView', trackedPath, merged);\n }, [trackedPath, merged]);\n\n useEffect(() => {\n const handler = (state: AppStateStatus) => {\n if (state !== 'active') return;\n if (!merged.autocollect) return;\n const current = lastPathRef.current;\n if (!current || isExcluded(current, merged)) return;\n send('PageView', current, merged);\n };\n const sub = AppState.addEventListener('change', handler);\n return () => sub.remove();\n }, [merged]);\n\n const value = useMemo<ContextValue>(\n () => ({ config: merged, lastPathRef }),\n [merged]\n );\n\n return createElement(Context.Provider, { value }, children);\n}\n\ntype Props = Record<string, string>;\n\nexport type AnalyticsAPI = {\n event(eventName: string, pathOrProps?: string | Props, props?: Props): void;\n view(pathOrProps?: string | Props, props?: Props): void;\n};\n\nexport function useAnalytics(): AnalyticsAPI {\n const ctx = useRequiredContext('useAnalytics');\n const trackedPath = useTrackedPath(ctx.config);\n\n const event = useCallback(\n (eventName: string, pathOrProps?: string | Props, props?: Props) => {\n const targetPath = typeof pathOrProps === 'string' ? pathOrProps : trackedPath;\n const eventProps = typeof pathOrProps === 'object' ? pathOrProps : props;\n send(eventName, targetPath, ctx.config, eventProps);\n },\n [ctx, trackedPath]\n );\n\n const view = useCallback(\n (pathOrProps?: string | Props, props?: Props) => {\n const targetPath = typeof pathOrProps === 'string' ? pathOrProps : trackedPath;\n const viewProps = typeof pathOrProps === 'object' ? pathOrProps : props;\n send('PageView', targetPath, ctx.config, viewProps);\n },\n [ctx, trackedPath]\n );\n\n return { event, view };\n}\n\n// TODO(page-scope): restore when page-scope detection is designed.\n/*\nexport function useAnalyticsPath(customPath: string): void {\n const ctx = useRequiredContext('useAnalyticsPath');\n const pathname = usePathname();\n useEffect(() => {\n const entry: Override = { realPath: pathname, customPath, source: 'hook' };\n ctx.overrideRef.current = entry;\n return () => {\n if (ctx.overrideRef.current === entry) ctx.overrideRef.current = null;\n };\n }, [ctx, pathname, customPath]);\n}\n\nexport type AnalyticsPathProps = { path: string };\n\nexport function AnalyticsPath({ path }: AnalyticsPathProps): null {\n const ctx = useRequiredContext('AnalyticsPath');\n const pathname = usePathname();\n useEffect(() => {\n const existing = ctx.overrideRef.current;\n if (existing && existing.realPath === pathname && existing.source === 'hook') return;\n const entry: Override = { realPath: pathname, customPath: path, source: 'component' };\n ctx.overrideRef.current = entry;\n return () => {\n if (ctx.overrideRef.current === entry) ctx.overrideRef.current = null;\n };\n }, [ctx, pathname, path]);\n return null;\n}\n\nexport function useAnalyticsProps(props: Record<string, string>): void {\n const ctx = useRequiredContext('useAnalyticsProps');\n const pathname = usePathname();\n useEffect(() => {\n const entry: PropsOverride = { realPath: pathname, props, source: 'hook' };\n ctx.propsOverrideRef.current = entry;\n return () => {\n if (ctx.propsOverrideRef.current === entry) ctx.propsOverrideRef.current = null;\n };\n }, [ctx, pathname, props]);\n}\n\nexport type AnalyticsPropsProps = Record<string, string>;\n\nexport function AnalyticsProps(props: AnalyticsPropsProps): null {\n const ctx = useRequiredContext('AnalyticsProps');\n const pathname = usePathname();\n useEffect(() => {\n const existing = ctx.propsOverrideRef.current;\n if (existing && existing.realPath === pathname && existing.source === 'hook') return;\n const entry: PropsOverride = { realPath: pathname, props, source: 'component' };\n ctx.propsOverrideRef.current = entry;\n return () => {\n if (ctx.propsOverrideRef.current === entry) ctx.propsOverrideRef.current = null;\n };\n }, [ctx, pathname, props]);\n return null;\n}\n*/\n\nfunction shouldSkipSend(config: InternalConfig): boolean {\n if (Platform.OS !== 'web') return false;\n if (isWebLocalhost() && !config.devmode) return true;\n return false;\n}\n\nfunction devLog(label: string, url: string, props?: Record<string, string>): void {\n let msg = `[onedollarstats]\\nEvent name: ${label}\\nEvent collected from: ${url}`;\n if (props && Object.keys(props).length > 0) {\n msg += `\\nProps: ${JSON.stringify(props, null, 2)}`;\n }\n console.log(msg);\n}\n\nconst SAFE_GET_THRESHOLD = 1500;\n\nfunction send(\n eventName: string,\n path: string,\n config: InternalConfig,\n props?: Record<string, string>\n): void {\n if (shouldSkipSend(config)) return;\n const url = `https://${config.hostname}${path}`;\n if (config.devmode) devLog(eventName, url, props);\n\n const body = JSON.stringify({\n u: url,\n e: [{ t: eventName, ...(props && { p: props }) }],\n debug: config.devmode\n });\n\n if (Platform.OS === 'web') {\n sendWeb(config.collectorUrl, body);\n } else {\n sendNative(config.collectorUrl, body);\n }\n}\n\nfunction sendNative(collectorUrl: string, body: string): void {\n fetch(collectorUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body\n }).catch(() => {});\n}\n\nfunction sendWeb(collectorUrl: string, body: string): void {\n const bytes = new TextEncoder().encode(body);\n const bin = String.fromCharCode(...bytes);\n const payloadBase64 = btoa(bin);\n\n if (payloadBase64.length <= SAFE_GET_THRESHOLD) {\n const img = new Image(1, 1);\n img.onerror = () => sendBeaconOrFetch(collectorUrl, body);\n img.src = `${collectorUrl}?data=${payloadBase64}`;\n return;\n }\n\n sendBeaconOrFetch(collectorUrl, body);\n}\n\nfunction sendBeaconOrFetch(collectorUrl: string, body: string): void {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon?.(collectorUrl, body)) {\n return;\n }\n\n fetch(collectorUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true\n }).catch(() => {});\n}\n\nfunction pathMatches(path: string, prefix: string): boolean {\n return path === prefix || path.startsWith(prefix.endsWith('/') ? prefix : prefix + '/');\n}\n\nfunction isExcluded(path: string, config: InternalConfig): boolean {\n if (config.includePages?.length) {\n return !config.includePages.some(p => pathMatches(path, p));\n }\n if (config.excludePages?.length) {\n return config.excludePages.some(p => pathMatches(path, p));\n }\n return false;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|