@sharib/geopulse-react-native 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -36
- package/dist/index.d.ts +144 -11
- package/dist/index.js +522 -76
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config/async-storage-key-value-store.ts","../src/config/types.ts","../src/config/key-value-store.ts","../src/events/uuid.ts","../src/config/device-id.ts","../src/config/config-cache.ts","../src/protocol.ts","../src/firebase/api-client.ts","../src/events/factory.ts","../src/geofencing/state-machine.ts","../src/location/simulated-location-provider.ts","../src/permissions/simulated-permission-provider.ts","../src/sync/retry-policy.ts","../src/storage/memory-store.ts","../src/storage/queue.ts","../src/sync/network-monitor.ts","../src/sync/sync-manager.ts","../src/core/logger.ts","../src/core/geo-pulse-sdk.ts","../src/location/device-location-provider.ts","../src/permissions/expo-permission-provider.ts","../src/storage/async-storage-store.ts","../src/sync/netinfo-network-monitor.ts","../src/rendering/experience-components.tsx"],"sourcesContent":["import { Platform } from \"react-native\";\nimport type { ExperienceDeliveryPayload, GeofenceEventType, LocationEventPayload } from \"./protocol\";\n\nimport { AsyncStorageKeyValueStore } from \"./config/async-storage-key-value-store\";\nimport type { GeoPulseConfig } from \"./config/types\";\nimport { GeoPulseSdk } from \"./core/geo-pulse-sdk\";\nimport { DeviceLocationProvider } from \"./location/device-location-provider\";\nimport { SimulatedLocationProvider } from \"./location/simulated-location-provider\";\nimport { ExpoPermissionProvider } from \"./permissions/expo-permission-provider\";\nimport { SimulatedPermissionProvider } from \"./permissions/simulated-permission-provider\";\nimport { AsyncStorageEventQueueStore } from \"./storage/async-storage-store\";\nimport { PendingEventQueue } from \"./storage/queue\";\nimport { NetInfoNetworkMonitor } from \"./sync/netinfo-network-monitor\";\n\nconst isDev = typeof __DEV__ === \"undefined\" ? true : __DEV__;\n\n/**\n * Opt-in escape hatch for testing real GPS *during development* without\n * switching to a production build — set `EXPO_PUBLIC_GEOPULSE_USE_REAL_LOCATION=true`\n * in the host app's `.env` (Expo statically inlines `EXPO_PUBLIC_*` at\n * bundle time, including inside this workspace package, so this works\n * without the SDK needing any new config field). Defaults to false: dev\n * still means \"simulator\" unless a developer explicitly asks otherwise.\n */\nconst useRealLocationInDev = process.env.EXPO_PUBLIC_GEOPULSE_USE_REAL_LOCATION === \"true\";\nconst useSimulatedProviders = isDev && !useRealLocationInDev;\n\nfunction getPlatform(): \"ios\" | \"android\" | \"web\" | \"unknown\" {\n switch (Platform.OS) {\n case \"ios\":\n case \"android\":\n case \"web\":\n return Platform.OS;\n default:\n return \"unknown\";\n }\n}\n\n// Shared by device-id and config-cache — both are just distinctly-keyed\n// entries in AsyncStorage, so one instance is enough.\nconst persistentKeyValueStore = new AsyncStorageKeyValueStore();\n\n/**\n * Dev builds default to the simulated location + permission providers (see\n * the \"Simulator\" section of this package's README) so the whole\n * location -> geofence -> event flow works with zero device setup. A\n * release build — or a dev build with `EXPO_PUBLIC_GEOPULSE_USE_REAL_LOCATION=true`\n * set (see above) — uses real device location and real OS permission\n * prompts instead.\n */\nconst sdk = new GeoPulseSdk({\n locationProvider: useSimulatedProviders ? new SimulatedLocationProvider() : new DeviceLocationProvider(),\n permissionProvider: useSimulatedProviders ? new SimulatedPermissionProvider() : new ExpoPermissionProvider(),\n queue: new PendingEventQueue(new AsyncStorageEventQueueStore()),\n deviceIdStore: persistentKeyValueStore,\n configCacheStore: persistentKeyValueStore,\n networkMonitor: new NetInfoNetworkMonitor(),\n getPlatform,\n debug: isDev,\n});\n\n/** `sdk.on`/`sdk.off` are already overloaded this way (see core/geo-pulse-sdk.ts) — this just re-states the same public shape for the plain-object `GeoPulse` singleton below. */\ntype OnOff = {\n (eventName: GeofenceEventType, callback: (event: LocationEventPayload) => void): void;\n (eventName: \"experience.received\", callback: (payload: ExperienceDeliveryPayload) => void): void;\n};\n\nexport const GeoPulse = {\n initialize: (config: GeoPulseConfig) => sdk.initialize(config),\n start: () => sdk.start(),\n stop: () => sdk.stop(),\n getStatus: () => sdk.getStatus(),\n getPermissionStatus: () => sdk.getPermissionStatus(),\n on: sdk.on.bind(sdk) as OnOff,\n off: sdk.off.bind(sdk) as OnOff,\n\n // Small additions beyond the core 7-method API, for building a status UI\n // around the SDK (see apps/playground) — not part of the minimal public\n // surface the spec calls out, but plain read-only accessors for state the\n // SDK already tracks internally.\n getConfiguredGeofences: () => sdk.getConfiguredGeofences(),\n getApplicationId: () => sdk.getApplicationId(),\n getLastKnownLocation: () => sdk.getLastKnownLocation(),\n getPendingEventCount: () => sdk.getPendingEventCount(),\n getDeviceId: () => sdk.getDeviceId(),\n /** Rich developer-diagnostics snapshot — see core/types.ts's DiagnosticsSnapshot doc. */\n getDiagnostics: () => sdk.getDiagnostics(),\n\n /** Development-only. No-ops (with a console warning) in a production build — see GeoPulseSdk.simulateLocation. */\n simulator: {\n setLocation: (coords: { latitude: number; longitude: number }) => sdk.simulateLocation(coords),\n },\n};\n\n// Advanced/testing escape hatch — construct your own SDK instance instead\n// of using the `GeoPulse` singleton above (e.g. to force real device\n// location during development). Most consumers should just use `GeoPulse`.\nexport { ApplicationMismatchError, GeoPulseSdk } from \"./core/geo-pulse-sdk\";\nexport { DeviceLocationProvider } from \"./location/device-location-provider\";\nexport { SimulatedLocationProvider } from \"./location/simulated-location-provider\";\nexport { ExpoPermissionProvider } from \"./permissions/expo-permission-provider\";\nexport { SimulatedPermissionProvider } from \"./permissions/simulated-permission-provider\";\nexport { NetInfoNetworkMonitor } from \"./sync/netinfo-network-monitor\";\nexport { ManualNetworkMonitor } from \"./sync/network-monitor\";\n\nexport type { GeoPulseConfig } from \"./config/types\";\nexport type { ConfigurationStatus, DiagnosticsSnapshot, SdkStatus } from \"./core/types\";\nexport type { LocationProvider, LocationUpdate } from \"./location/types\";\nexport type { PermissionProvider, PermissionStatus } from \"./permissions/types\";\nexport type { NetworkMonitor, NetworkStatus } from \"./sync/network-monitor\";\nexport type { ExperienceDeliveryPayload, GeofenceEventType, LocationEventPayload, SdkGeofenceConfig } from \"./protocol\";\nexport { BannerExperience, CardExperience, ExperienceView, MessageExperience } from \"./rendering\";\n","import AsyncStorage from \"@react-native-async-storage/async-storage\";\n\nimport type { KeyValueStore } from \"./key-value-store\";\n\n/** Real persistence for the RN app. Isolated in its own file — see storage/async-storage-store.ts's comment for why. */\nexport class AsyncStorageKeyValueStore implements KeyValueStore {\n async getItem(key: string): Promise<string | null> {\n return AsyncStorage.getItem(key);\n }\n\n async setItem(key: string, value: string): Promise<void> {\n await AsyncStorage.setItem(key, value);\n }\n}\n","export interface GeoPulseConfig {\n apiKey: string;\n applicationId: string;\n /**\n * Internal override for the GeoPulse backend URL — only for GeoPulse's own\n * local development (e.g. apps/playground against a local backend).\n * Customers never set this; it defaults to `DEFAULT_BACKEND_URL`.\n */\n functionsBaseUrl?: string;\n /** How often to retry queued (previously failed) events, in milliseconds. Defaults to 30s. */\n retryIntervalMs?: number;\n /** How often to poll for pending campaign experiences (Phase 5B), in milliseconds. Defaults to 30s, mirroring `retryIntervalMs`. A host app that wants faster demo feedback (see apps/playground) can pass a smaller value. */\n experiencePollIntervalMs?: number;\n /** Enables the lifecycle/event debug log lines described in this package's README. Defaults to `__DEV__`. Never logs the SDK key or event payload bodies. */\n debug?: boolean;\n}\n\n/** The hosted GeoPulse backend every customer app talks to. */\nexport const DEFAULT_BACKEND_URL = \"https://geofends.onrender.com\";\n\nexport const SDK_VERSION = \"0.3.3\";\n","/**\n * Minimal persistent key-value abstraction, shared by device-id.ts and\n * config-cache.ts — deliberately smaller than `storage/EventQueueStore`\n * (which manages a whole collection) since these two only ever need \"get\n * one string\" / \"set one string\".\n */\nexport interface KeyValueStore {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n}\n\nexport class InMemoryKeyValueStore implements KeyValueStore {\n private readonly data = new Map<string, string>();\n\n async getItem(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async setItem(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n}\n","/**\n * RFC 4122 v4 UUID for event and device ids. Uses the platform CSPRNG\n * (`crypto.getRandomValues`, available in Hermes >= 0.12 and Node) when\n * present, falling back to `Math.random()` only on runtimes without it.\n * These ids aren't secrets, but a device id scopes which pending\n * experiences a request can fetch, so it shouldn't be predictable.\n */\nexport function generateEventId(): string {\n const bytes = new Uint8Array(16);\n const cryptoApi = (globalThis as { crypto?: { getRandomValues?: (array: Uint8Array) => Uint8Array } }).crypto;\n if (cryptoApi?.getRandomValues) {\n cryptoApi.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;\n bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;\n const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","import { generateEventId } from \"../events/uuid\";\nimport type { KeyValueStore } from \"./key-value-store\";\n\nconst DEVICE_ID_STORAGE_KEY = \"@geopulse/device-id\";\n\n/**\n * A stable, random, non-identifying installation id — generated once per\n * install and persisted thereafter, not derived from any hardware\n * identifier (spec: \"do not attempt invasive device fingerprinting\").\n * `generateEventId` is just a generic UUID v4 generator despite its name\n * (see events/uuid.ts); reused here rather than duplicated.\n */\nexport async function getOrCreateDeviceId(store: KeyValueStore): Promise<string> {\n const existing = await store.getItem(DEVICE_ID_STORAGE_KEY);\n if (existing) {\n return existing;\n }\n const deviceId = generateEventId();\n await store.setItem(DEVICE_ID_STORAGE_KEY, deviceId);\n return deviceId;\n}\n","import type { SdkProjectConfig } from \"../protocol\";\n\nimport type { KeyValueStore } from \"./key-value-store\";\n\nconst CONFIG_CACHE_STORAGE_KEY = \"@geopulse/config-cache\";\n\ninterface CachedConfig {\n config: SdkProjectConfig;\n fetchedAt: string;\n}\n\n/**\n * Persists the last successfully fetched `SdkProjectConfig` so\n * `initialize()` can still succeed — using stale-but-valid geofences —\n * when `getProjectConfig` is unreachable (spec: \"If the device temporarily\n * loses network access, the SDK should be able to continue using the last\n * valid configuration\"). There is no expiry: a geofence list that's a few\n * hours old is still far more useful than refusing to start at all, and\n * the next successful fetch overwrites it regardless of age.\n */\nexport class ConfigCache {\n constructor(private readonly store: KeyValueStore) {}\n\n async save(config: SdkProjectConfig): Promise<void> {\n const cached: CachedConfig = { config, fetchedAt: new Date().toISOString() };\n await this.store.setItem(CONFIG_CACHE_STORAGE_KEY, JSON.stringify(cached));\n }\n\n async load(): Promise<CachedConfig | null> {\n const raw = await this.store.getItem(CONFIG_CACHE_STORAGE_KEY);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as CachedConfig;\n } catch {\n return null;\n }\n }\n}\n","/**\n * The SDK's entire wire contract with the GeoPulse backend, copied from\n * `@geopulse/shared` rather than depending on it at runtime. `@geopulse/shared`\n * is a monorepo-internal package (also used by apps/admin-web and functions/)\n * that carries the *whole* platform's schemas — organizations, webhooks,\n * campaigns, auth forms, and more. None of that belongs inside a published,\n * on-device SDK: bundling the real package pulled in all of it (verified via\n * `npm pack` on the published tarball — dist/index.js contained the admin\n * portal's signup/login validation, webhook, and campaign schemas). This file\n * is the deliberately small subset the SDK actually touches. Keep it in sync\n * with the corresponding types in packages/shared/src/ by hand if the wire\n * contract changes — `src/core/geo-pulse-sdk.test.ts` and\n * `src/firebase/api-client.test.ts` still validate against the real\n * `@geopulse/shared` package (a devDependency, never published) precisely so\n * drift between this copy and the canonical schema gets caught.\n */\n\nexport const SDK_KEY_HEADER = \"x-geopulse-sdk-key\";\n\n/** The configured application id, sent with every request so the backend can reject a key bound to another application. */\nexport const APPLICATION_ID_HEADER = \"x-geopulse-application-id\";\n\nexport const SDK_ENDPOINTS = {\n GET_PROJECT_CONFIG: \"getProjectConfig\",\n INGEST_LOCATION_EVENT: \"ingestLocationEvent\",\n GET_PENDING_EXPERIENCES: \"getPendingExperiences\",\n ACKNOWLEDGE_EXPERIENCE_DELIVERY: \"acknowledgeExperienceDelivery\",\n} as const;\n\nconst EARTH_RADIUS_METERS = 6_371_000;\n\nfunction toRadians(degrees: number): number {\n return (degrees * Math.PI) / 180;\n}\n\n/** Haversine great-circle distance in meters — see packages/shared/src/utils/geo.ts for the full derivation comment. */\nexport function calculateDistanceMeters(\n from: { latitude: number; longitude: number },\n to: { latitude: number; longitude: number },\n): number {\n const lat1 = toRadians(from.latitude);\n const lat2 = toRadians(to.latitude);\n const deltaLat = toRadians(to.latitude - from.latitude);\n const deltaLng = toRadians(to.longitude - from.longitude);\n\n const a =\n Math.sin(deltaLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) ** 2;\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n return EARTH_RADIUS_METERS * c;\n}\n\nexport const GEOFENCE_EVENT_TYPES = [\"geofence.enter\", \"geofence.exit\"] as const;\nexport type GeofenceEventType = (typeof GEOFENCE_EVENT_TYPES)[number];\n\nexport const DEVICE_PLATFORMS = [\"ios\", \"android\", \"web\", \"unknown\"] as const;\nexport type DevicePlatform = (typeof DEVICE_PLATFORMS)[number];\n\nexport const EXPERIENCE_TYPES = [\"banner\", \"card\", \"message\"] as const;\nexport type ExperienceType = (typeof EXPERIENCE_TYPES)[number];\n\nexport const ANCHOR_POINTS = [\n \"top-left\",\n \"top-center\",\n \"top-right\",\n \"center-left\",\n \"center\",\n \"center-right\",\n \"bottom-left\",\n \"bottom-center\",\n \"bottom-right\",\n] as const;\nexport type AnchorPoint = (typeof ANCHOR_POINTS)[number];\n\nexport interface AnchoredPosition {\n anchor: AnchorPoint;\n offsetX?: number | null;\n offsetY?: number | null;\n}\n\nexport interface ContainerStyle {\n backgroundColor?: string | null;\n borderColor?: string | null;\n borderWidth?: number | null;\n borderRadius?: number | null;\n padding?: number | null;\n backgroundImageUrl?: string | null;\n backgroundImageFit?: \"cover\" | \"contain\" | \"stretch\";\n widthMode?: \"fill\" | \"fixed\";\n width?: number | null;\n heightMode?: \"auto\" | \"fixed\";\n height?: number | null;\n}\n\nexport interface TextStyle {\n visible?: boolean;\n color?: string | null;\n fontSize?: number | null;\n fontWeight?: \"normal\" | \"bold\";\n}\n\nexport interface ButtonStyle {\n visible?: boolean;\n backgroundColor?: string | null;\n textColor?: string | null;\n borderRadius?: number | null;\n position?: AnchoredPosition | null;\n}\n\nexport interface ExperienceStyleV2 {\n version: 2;\n container?: ContainerStyle;\n title?: TextStyle;\n body?: TextStyle;\n button?: ButtonStyle;\n}\n\nexport interface ExperienceStyleLegacy {\n backgroundColor: string | null;\n textColor: string | null;\n accentColor: string | null;\n}\n\nexport type ExperienceStyle = ExperienceStyleV2 | ExperienceStyleLegacy;\n\nexport function isLegacyExperienceStyle(style: ExperienceStyle): style is ExperienceStyleLegacy {\n return !(\"version\" in style);\n}\n\nexport interface ResolvedPosition {\n top: number | null;\n bottom: number | null;\n left: number | null;\n right: number | null;\n}\n\nexport interface ResolvedContainerStyle {\n backgroundColor: string;\n borderColor: string | null;\n borderWidth: number;\n borderRadius: number;\n padding: number;\n backgroundImageUrl: string | null;\n backgroundImageFit: \"cover\" | \"contain\" | \"stretch\";\n width: \"fill\" | number;\n height: \"auto\" | number;\n}\n\nexport interface ResolvedTextStyle {\n visible: boolean;\n color: string;\n fontSize: number;\n fontWeight: \"normal\" | \"bold\";\n}\n\nexport interface ResolvedButtonStyle {\n visible: boolean;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n position: ResolvedPosition;\n}\n\nexport interface ResolvedExperienceStyle {\n container: ResolvedContainerStyle;\n title: ResolvedTextStyle;\n body: ResolvedTextStyle;\n button: ResolvedButtonStyle;\n}\n\nconst HEX_COLOR_PATTERN = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n/** dp — a generous cap so a malformed/huge offset can't push an element fully off-screen. */\nconst OFFSET_LIMIT = 500;\n\nconst ANCHOR_BASE_POSITIONS: Record<AnchorPoint, ResolvedPosition> = {\n \"top-left\": { top: 0, left: 0, bottom: null, right: null },\n \"top-center\": { top: 0, left: null, bottom: null, right: null },\n \"top-right\": { top: 0, left: null, bottom: null, right: 0 },\n \"center-left\": { top: null, left: 0, bottom: null, right: null },\n center: { top: null, left: null, bottom: null, right: null },\n \"center-right\": { top: null, left: null, bottom: null, right: 0 },\n \"bottom-left\": { top: null, left: 0, bottom: 0, right: null },\n \"bottom-center\": { top: null, left: null, bottom: 0, right: null },\n \"bottom-right\": { top: null, left: null, bottom: 0, right: 0 },\n};\n\nconst DEFAULT_STYLES: Record<ExperienceType, ResolvedExperienceStyle> = {\n card: {\n container: { backgroundColor: \"#ffffff\", borderColor: null, borderWidth: 0, borderRadius: 16, padding: 20, backgroundImageUrl: null, backgroundImageFit: \"cover\", width: \"fill\", height: \"auto\" },\n title: { visible: true, color: \"#111827\", fontSize: 18, fontWeight: \"bold\" },\n body: { visible: true, color: \"#374151\", fontSize: 14, fontWeight: \"normal\" },\n button: { visible: true, backgroundColor: \"#2563eb\", textColor: \"#ffffff\", borderRadius: 8, position: { top: null, bottom: 16, left: 16, right: null } },\n },\n banner: {\n container: { backgroundColor: \"#111827\", borderColor: null, borderWidth: 0, borderRadius: 0, padding: 12, backgroundImageUrl: null, backgroundImageFit: \"cover\", width: \"fill\", height: \"auto\" },\n title: { visible: true, color: \"#ffffff\", fontSize: 15, fontWeight: \"bold\" },\n body: { visible: true, color: \"#e5e7eb\", fontSize: 13, fontWeight: \"normal\" },\n button: { visible: false, backgroundColor: \"#2563eb\", textColor: \"#ffffff\", borderRadius: 6, position: { top: null, bottom: null, left: null, right: 12 } },\n },\n message: {\n container: { backgroundColor: \"#1f2937\", borderColor: null, borderWidth: 0, borderRadius: 12, padding: 16, backgroundImageUrl: null, backgroundImageFit: \"cover\", width: \"fill\", height: \"auto\" },\n title: { visible: false, color: \"#ffffff\", fontSize: 16, fontWeight: \"bold\" },\n body: { visible: true, color: \"#ffffff\", fontSize: 14, fontWeight: \"normal\" },\n button: { visible: false, backgroundColor: \"#2563eb\", textColor: \"#ffffff\", borderRadius: 8, position: { top: null, bottom: 16, left: null, right: 16 } },\n },\n};\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction resolveColor(value: string | null | undefined, fallback: string): string {\n return typeof value === \"string\" && HEX_COLOR_PATTERN.test(value) ? value : fallback;\n}\n\nfunction resolveOptionalColor(value: string | null | undefined, fallback: string | null): string | null {\n return typeof value === \"string\" && HEX_COLOR_PATTERN.test(value) ? value : fallback;\n}\n\nfunction resolveNonNegativeNumber(value: number | null | undefined, fallback: number): number {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : fallback;\n}\n\nfunction resolvePosition(position: AnchoredPosition | null | undefined, fallback: ResolvedPosition): ResolvedPosition {\n if (!position || !ANCHOR_POINTS.includes(position.anchor)) return fallback;\n const offsetX = typeof position.offsetX === \"number\" && Number.isFinite(position.offsetX) ? clamp(position.offsetX, -OFFSET_LIMIT, OFFSET_LIMIT) : 0;\n const offsetY = typeof position.offsetY === \"number\" && Number.isFinite(position.offsetY) ? clamp(position.offsetY, -OFFSET_LIMIT, OFFSET_LIMIT) : 0;\n const base = ANCHOR_BASE_POSITIONS[position.anchor];\n return {\n top: base.top === null ? null : base.top + offsetY,\n bottom: base.bottom === null ? null : base.bottom + offsetY,\n left: base.left === null ? null : base.left + offsetX,\n right: base.right === null ? null : base.right + offsetX,\n };\n}\n\nfunction resolveWidth(mode: \"fill\" | \"fixed\" | undefined, value: number | null | undefined, fallback: \"fill\" | number): \"fill\" | number {\n if (mode === \"fixed\") {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : fallback;\n }\n if (mode === \"fill\") return \"fill\";\n return fallback;\n}\n\nfunction resolveHeight(mode: \"auto\" | \"fixed\" | undefined, value: number | null | undefined, fallback: \"auto\" | number): \"auto\" | number {\n if (mode === \"fixed\") {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : fallback;\n }\n if (mode === \"auto\") return \"auto\";\n return fallback;\n}\n\nfunction resolveContainer(custom: ContainerStyle | undefined, fallback: ResolvedContainerStyle): ResolvedContainerStyle {\n return {\n backgroundColor: resolveColor(custom?.backgroundColor, fallback.backgroundColor),\n borderColor: resolveOptionalColor(custom?.borderColor, fallback.borderColor),\n borderWidth: resolveNonNegativeNumber(custom?.borderWidth, fallback.borderWidth),\n borderRadius: resolveNonNegativeNumber(custom?.borderRadius, fallback.borderRadius),\n padding: resolveNonNegativeNumber(custom?.padding, fallback.padding),\n backgroundImageUrl: typeof custom?.backgroundImageUrl === \"string\" && custom.backgroundImageUrl.length > 0 ? custom.backgroundImageUrl : fallback.backgroundImageUrl,\n backgroundImageFit: custom?.backgroundImageFit ?? fallback.backgroundImageFit,\n width: resolveWidth(custom?.widthMode, custom?.width, fallback.width),\n height: resolveHeight(custom?.heightMode, custom?.height, fallback.height),\n };\n}\n\nfunction resolveText(custom: TextStyle | undefined, fallback: ResolvedTextStyle): ResolvedTextStyle {\n return {\n visible: custom?.visible ?? fallback.visible,\n color: resolveColor(custom?.color, fallback.color),\n fontSize: resolveNonNegativeNumber(custom?.fontSize, fallback.fontSize),\n fontWeight: custom?.fontWeight ?? fallback.fontWeight,\n };\n}\n\nfunction resolveButton(custom: ButtonStyle | undefined, fallback: ResolvedButtonStyle): ResolvedButtonStyle {\n return {\n visible: custom?.visible ?? fallback.visible,\n backgroundColor: resolveColor(custom?.backgroundColor, fallback.backgroundColor),\n textColor: resolveColor(custom?.textColor, fallback.textColor),\n borderRadius: resolveNonNegativeNumber(custom?.borderRadius, fallback.borderRadius),\n position: resolvePosition(custom?.position, fallback.position),\n };\n}\n\n/** Maps today's flat 3-color shape onto the v2 groups it corresponds to. */\nfunction upgradeLegacyStyle(legacy: ExperienceStyleLegacy): ExperienceStyleV2 {\n return {\n version: 2,\n container: { backgroundColor: legacy.backgroundColor },\n title: { color: legacy.textColor },\n body: { color: legacy.textColor },\n button: { backgroundColor: legacy.accentColor },\n };\n}\n\n/**\n * Cascades a type's built-in default under a campaign's custom style,\n * upgrading today's legacy 3-color shape on the fly, and resolves 9-point\n * anchor positions to concrete offsets. Never throws — any malformed or\n * missing field silently falls back to the type's default.\n */\nexport function resolveExperienceStyle(type: ExperienceType, custom: ExperienceStyle | null | undefined): ResolvedExperienceStyle {\n const fallback = DEFAULT_STYLES[type] ?? DEFAULT_STYLES.card;\n if (!custom) return fallback;\n const v2 = isLegacyExperienceStyle(custom) ? upgradeLegacyStyle(custom) : custom;\n return {\n container: resolveContainer(v2.container, fallback.container),\n title: resolveText(v2.title, fallback.title),\n body: resolveText(v2.body, fallback.body),\n button: resolveButton(v2.button, fallback.button),\n };\n}\n\nexport interface GeofenceCenter {\n latitude: number;\n longitude: number;\n}\n\nexport interface SdkGeofenceConfig {\n id: string;\n name: string;\n locationId: string;\n locationName: string;\n center: GeofenceCenter;\n radiusMeters: number;\n}\n\nexport interface SdkProjectConfig {\n applicationId: string;\n projectId: string;\n applicationEnabled: boolean;\n configVersion: string;\n geofences: SdkGeofenceConfig[];\n}\n\nexport interface LocationEventPayload {\n id: string;\n type: GeofenceEventType;\n projectId: string;\n applicationId: string;\n geofenceId: string;\n locationId: string;\n deviceId: string;\n timestamp: string;\n latitude: number;\n longitude: number;\n metadata: Record<string, string>;\n sdkVersion: string;\n attempts: number;\n}\n\nexport interface ExperienceDeliveryPayload {\n type: \"location.experience\";\n deliveryId: string;\n campaignId: string;\n experience: {\n id: string;\n type: ExperienceType;\n title: string;\n body: string;\n imageUrl: string | null;\n cardNavigationUrl: string | null;\n ctaLabel: string | null;\n ctaUrl: string | null;\n style: ExperienceStyle | null;\n };\n event: {\n id: string;\n type: GeofenceEventType;\n geofenceId: string;\n timestamp: string;\n };\n}\n","import { APPLICATION_ID_HEADER, SDK_ENDPOINTS, SDK_KEY_HEADER } from \"../protocol\";\nimport type { DevicePlatform, ExperienceDeliveryPayload, LocationEventPayload, SdkProjectConfig } from \"../protocol\";\n\nexport interface DeviceIdentity {\n deviceId: string;\n platform: DevicePlatform;\n sdkVersion: string;\n}\n\n/**\n * Without this, a request to an unresponsive/hung backend (e.g. a local\n * emulator that was restarted mid-request, or a real network stall) would\n * hang forever — `fetch` has no default timeout. That leaves an event\n * stuck `pending` indefinitely: `SyncManager.attemptDelivery`'s retry/backoff\n * logic only runs once a request actually *fails*, so a request that never\n * settles at all never gets retried either. Aborting after this long\n * converts \"hung forever\" into \"failed now,\" which the existing queue\n * already knows how to handle.\n */\nconst REQUEST_TIMEOUT_MS = 15_000;\n\nasync function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * A non-2xx response already throws a clear, endpoint-named error below —\n * but `fetch` itself rejecting (offline, DNS failure, or `fetchWithTimeout`'s\n * own abort) previously propagated as a bare, unattributed error like\n * \"Aborted\" or \"Network request failed\", with nothing indicating which SDK\n * call it came from (Phase 7 SDK audit finding). This wraps that case with\n * the same \"which call failed\" context the HTTP-error path already has,\n * without changing what the underlying error actually was — `error.message`\n * is still included verbatim.\n */\nasync function fetchOrThrow(callName: string, url: string, init: RequestInit): Promise<Response> {\n try {\n return await fetchWithTimeout(url, init);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`${callName} request failed: ${message}`);\n }\n}\n\n/**\n * Named `firebase/` per this project's spec (\"communication with GeoPulse\n * backend\"), but deliberately does NOT use the Firebase client SDK — it\n * talks to the Cloud Functions HTTPS endpoints over plain `fetch` (built\n * into both React Native's JS runtime and Node 18+, so this file is\n * testable without any RN runtime at all). That keeps the SDK's dependency\n * footprint small and means device requests carry only the app-scoped SDK\n * key, never a Firebase credential of any kind.\n */\nexport class ApiClient {\n constructor(\n private readonly baseUrl: string,\n private readonly sdkKey: string,\n /** Optional so existing callers keep working; when set, the backend rejects a key bound to a different application. */\n private readonly applicationId?: string,\n ) {}\n\n private headers(json = false): Record<string, string> {\n return {\n [SDK_KEY_HEADER]: this.sdkKey,\n ...(this.applicationId ? { [APPLICATION_ID_HEADER]: this.applicationId } : {}),\n ...(json ? { \"Content-Type\": \"application/json\" } : {}),\n };\n }\n\n /**\n * `identity` doubles as this device's registration/heartbeat (see\n * functions/src/events/get-project-config.ts) — sent as query params\n * since GET requests carry no body.\n */\n async getProjectConfig(identity: DeviceIdentity): Promise<SdkProjectConfig> {\n const query = new URLSearchParams({\n deviceId: identity.deviceId,\n platform: identity.platform,\n sdkVersion: identity.sdkVersion,\n });\n const response = await fetchOrThrow(\"getProjectConfig\", `${this.baseUrl}/${SDK_ENDPOINTS.GET_PROJECT_CONFIG}?${query.toString()}`, {\n method: \"GET\",\n headers: this.headers(),\n });\n if (!response.ok) {\n throw new Error(`getProjectConfig failed with status ${response.status}`);\n }\n return (await response.json()) as SdkProjectConfig;\n }\n\n async ingestEvent(payload: LocationEventPayload): Promise<{ id: string; deduplicated: boolean }> {\n const response = await fetchOrThrow(\"ingestLocationEvent\", `${this.baseUrl}/${SDK_ENDPOINTS.INGEST_LOCATION_EVENT}`, {\n method: \"POST\",\n headers: this.headers(true),\n body: JSON.stringify(payload),\n });\n if (!response.ok) {\n throw new Error(`ingestLocationEvent failed with status ${response.status}`);\n }\n return (await response.json()) as { id: string; deduplicated: boolean };\n }\n\n /** Polled by GeoPulseSdk's experience-polling loop — see core/geo-pulse-sdk.ts. */\n async getPendingExperiences(deviceId: string): Promise<ExperienceDeliveryPayload[]> {\n const query = new URLSearchParams({ deviceId });\n const response = await fetchOrThrow(\"getPendingExperiences\", `${this.baseUrl}/${SDK_ENDPOINTS.GET_PENDING_EXPERIENCES}?${query.toString()}`, {\n method: \"GET\",\n headers: this.headers(),\n });\n if (!response.ok) {\n throw new Error(`getPendingExperiences failed with status ${response.status}`);\n }\n const body = (await response.json()) as { experiences: ExperienceDeliveryPayload[] };\n return body.experiences;\n }\n\n /** Confirms a delivery was surfaced via `experience.received`, so it stops appearing in future polls. */\n async acknowledgeExperienceDelivery(deliveryId: string): Promise<void> {\n const response = await fetchOrThrow(\"acknowledgeExperienceDelivery\", `${this.baseUrl}/${SDK_ENDPOINTS.ACKNOWLEDGE_EXPERIENCE_DELIVERY}`, {\n method: \"POST\",\n headers: this.headers(true),\n body: JSON.stringify({ deliveryId }),\n });\n if (!response.ok) {\n throw new Error(`acknowledgeExperienceDelivery failed with status ${response.status}`);\n }\n }\n}\n","import type { GeofenceEventType, LocationEventPayload, SdkGeofenceConfig } from \"../protocol\";\n\nimport { SDK_VERSION } from \"../config/types\";\nimport { generateEventId } from \"./uuid\";\n\nexport function createEventPayload(params: {\n type: GeofenceEventType;\n projectId: string;\n applicationId: string;\n geofence: SdkGeofenceConfig;\n location: { latitude: number; longitude: number };\n deviceId: string;\n metadata?: Record<string, string>;\n}): LocationEventPayload {\n return {\n id: generateEventId(),\n type: params.type,\n projectId: params.projectId,\n applicationId: params.applicationId,\n geofenceId: params.geofence.id,\n locationId: params.geofence.locationId,\n deviceId: params.deviceId,\n timestamp: new Date().toISOString(),\n latitude: params.location.latitude,\n longitude: params.location.longitude,\n metadata: params.metadata ?? {},\n sdkVersion: SDK_VERSION,\n // First-try attempt count; SyncManager overwrites this with the real\n // count if the event ends up going through the retry queue (see\n // sync/sync-manager.ts#retryPending) — that's what makes the\n // \"Attempts\" column in the admin's Event Explorer meaningful.\n attempts: 1,\n };\n}\n","import { calculateDistanceMeters } from \"../protocol\";\nimport type { GeofenceEventType, SdkGeofenceConfig } from \"../protocol\";\n\nexport type GeofenceMembership = \"INSIDE\" | \"OUTSIDE\";\n\nexport interface GeofenceTransition {\n geofence: SdkGeofenceConfig;\n type: GeofenceEventType;\n}\n\n/**\n * Tracks INSIDE/OUTSIDE membership per geofence and emits a transition only\n * when membership actually changes:\n *\n * OUTSIDE -> INSIDE = geofence.enter\n * INSIDE -> OUTSIDE = geofence.exit\n * INSIDE -> INSIDE = nothing\n * OUTSIDE -> OUTSIDE = nothing\n *\n * A geofence with no prior recorded state is treated as OUTSIDE (so the\n * very first location update that lands inside a geofence correctly fires\n * one `geofence.enter`, not silence). State survives across repeated\n * `evaluate()` calls precisely so a location update that keeps the device\n * inside the same geofence produces no duplicate ENTER — that's the whole\n * point of this class existing instead of a stateless distance check.\n */\nexport class GeofenceStateMachine {\n private readonly membershipByGeofenceId = new Map<string, GeofenceMembership>();\n\n evaluate(location: { latitude: number; longitude: number }, geofences: SdkGeofenceConfig[]): GeofenceTransition[] {\n const transitions: GeofenceTransition[] = [];\n const activeIds = new Set<string>();\n\n for (const geofence of geofences) {\n activeIds.add(geofence.id);\n\n const distance = calculateDistanceMeters(location, geofence.center);\n const nextMembership: GeofenceMembership = distance <= geofence.radiusMeters ? \"INSIDE\" : \"OUTSIDE\";\n const previousMembership = this.membershipByGeofenceId.get(geofence.id) ?? \"OUTSIDE\";\n\n if (previousMembership !== nextMembership) {\n transitions.push({\n geofence,\n type: nextMembership === \"INSIDE\" ? \"geofence.enter\" : \"geofence.exit\",\n });\n }\n\n this.membershipByGeofenceId.set(geofence.id, nextMembership);\n }\n\n // Drop bookkeeping for geofences no longer in the active config (e.g.\n // deleted/disabled since the last `getProjectConfig` fetch) so it can't\n // silently grow forever or produce a stale transition if the same\n // geofence id ever reappears later.\n for (const id of this.membershipByGeofenceId.keys()) {\n if (!activeIds.has(id)) {\n this.membershipByGeofenceId.delete(id);\n }\n }\n\n return transitions;\n }\n\n getMembership(geofenceId: string): GeofenceMembership {\n return this.membershipByGeofenceId.get(geofenceId) ?? \"OUTSIDE\";\n }\n\n reset(): void {\n this.membershipByGeofenceId.clear();\n }\n}\n","import type { LocationProvider, LocationUpdate } from \"./types\";\n\n/**\n * The mandatory simulation provider (spec section 7) — lets the geofence\n * flow be exercised end to end without physically travelling anywhere.\n * `setLocation` is the playground simulator screen's only way to move the\n * \"user\"; everything downstream (geofence engine, event generation, sync)\n * runs exactly as it would for a real device update, because both\n * providers speak the same `LocationProvider` interface.\n */\nexport class SimulatedLocationProvider implements LocationProvider {\n private onUpdate: ((location: LocationUpdate) => void) | null = null;\n private currentLocation: LocationUpdate | null = null;\n\n start(onUpdate: (location: LocationUpdate) => void): void {\n this.onUpdate = onUpdate;\n // If a location was already set before `start()` (e.g. the simulator\n // screen was positioned before pressing \"Start\"), replay it immediately\n // rather than waiting for the next `setLocation` call.\n if (this.currentLocation) {\n onUpdate(this.currentLocation);\n }\n }\n\n stop(): void {\n this.onUpdate = null;\n }\n\n setLocation(coords: { latitude: number; longitude: number }): void {\n const location: LocationUpdate = { ...coords, timestamp: new Date().toISOString() };\n this.currentLocation = location;\n this.onUpdate?.(location);\n }\n\n getCurrentLocation(): LocationUpdate | null {\n return this.currentLocation;\n }\n}\n","import type { PermissionProvider, PermissionStatus } from \"./types\";\n\n/**\n * Used with the simulated location provider (see ../location) — a\n * simulated user obviously isn't gated by the OS location permission\n * dialog, so this always reports \"granted\" rather than requiring a real\n * device's permission state to test the geofence flow end to end.\n */\nexport class SimulatedPermissionProvider implements PermissionProvider {\n async getStatus(): Promise<PermissionStatus> {\n return \"granted\";\n }\n\n async request(): Promise<PermissionStatus> {\n return \"granted\";\n }\n}\n","/**\n * Centralized so retry behavior can be tuned in one place instead of\n * scattered magic numbers — see spec section 4 (\"Do not hard-code these\n * values throughout the codebase\").\n */\nexport const RETRY_POLICY = {\n /** Attempts beyond this stop increasing the delay — it plateaus at MAX_RETRY_DELAY_MS, but retrying never stops entirely (the SDK must never give up on an event; see storage/queue.ts). */\n MAX_RETRIES: 6,\n BASE_RETRY_DELAY_MS: 5_000,\n MAX_RETRY_DELAY_MS: 5 * 60_000,\n} as const;\n\n/**\n * Exponential backoff with a ceiling: attempt 1 -> ~5s, 2 -> ~10s, 3 -> ~20s,\n * ... capped at MAX_RETRY_DELAY_MS once `attempts` passes MAX_RETRIES. A\n * small jitter (±20%) avoids every queued event on a device — or every\n * device that lost connectivity at the same moment — retrying in lockstep.\n */\nexport function computeRetryDelayMs(attempts: number): number {\n const cappedAttempts = Math.min(attempts, RETRY_POLICY.MAX_RETRIES);\n const exponential = RETRY_POLICY.BASE_RETRY_DELAY_MS * 2 ** Math.max(0, cappedAttempts - 1);\n const delay = Math.min(exponential, RETRY_POLICY.MAX_RETRY_DELAY_MS);\n const jitter = delay * 0.2 * (Math.random() * 2 - 1);\n return Math.round(delay + jitter);\n}\n","import type { EventQueueStore, QueuedEvent } from \"./types\";\n\n/** Default store — also what unit tests use, since it needs no RN runtime. */\nexport class InMemoryEventQueueStore implements EventQueueStore {\n private readonly eventsById = new Map<string, QueuedEvent>();\n\n async getAll(): Promise<QueuedEvent[]> {\n return [...this.eventsById.values()];\n }\n\n async save(event: QueuedEvent): Promise<void> {\n this.eventsById.set(event.eventId, event);\n }\n\n async remove(eventId: string): Promise<void> {\n this.eventsById.delete(eventId);\n }\n}\n","import type { LocationEventPayload } from \"../protocol\";\n\nimport { computeRetryDelayMs } from \"../sync/retry-policy\";\nimport { InMemoryEventQueueStore } from \"./memory-store\";\nimport type { EventQueueStore, QueuedEvent } from \"./types\";\n\n/**\n * The local pending-event queue (spec: \"Offline queue\" / \"reliable SDK\n * event queue\"). An event is only ever in this queue between a failed send\n * attempt and a successful retry — there's no \"delivered\" status because a\n * delivered event is simply removed (`markSent`), which is also what makes\n * retrying an already-sent event harmless: it's not in the queue to retry.\n */\nexport class PendingEventQueue {\n constructor(private readonly store: EventQueueStore = new InMemoryEventQueueStore()) {}\n\n async enqueue(payload: LocationEventPayload): Promise<void> {\n await this.store.save({\n eventId: payload.id,\n type: payload.type,\n applicationId: payload.applicationId,\n deviceId: payload.deviceId,\n locationId: payload.locationId,\n geofenceId: payload.geofenceId,\n timestamp: payload.timestamp,\n payload,\n attempts: 0,\n status: \"pending\",\n createdAt: new Date().toISOString(),\n lastAttemptAt: null,\n nextAttemptAt: null,\n });\n }\n\n /** Every queued event, regardless of retry timing — used for diagnostics/pending counts. */\n async listPending(): Promise<QueuedEvent[]> {\n return this.store.getAll();\n }\n\n /** Only events whose backoff window has elapsed (or that have never been attempted) — used by SyncManager.retryPending(). */\n async listReadyForRetry(now: Date = new Date()): Promise<QueuedEvent[]> {\n const all = await this.store.getAll();\n return all.filter(\n (event) => event.status !== \"sending\" && (!event.nextAttemptAt || new Date(event.nextAttemptAt) <= now),\n );\n }\n\n /** Marks an event as in-flight so a concurrent retry pass won't also pick it up. */\n async markSending(eventId: string): Promise<void> {\n const existing = await this.get(eventId);\n if (!existing) return;\n await this.store.save({ ...existing, status: \"sending\" });\n }\n\n async markAttemptFailed(eventId: string): Promise<void> {\n const existing = await this.get(eventId);\n if (!existing) return;\n const attempts = existing.attempts + 1;\n const now = new Date();\n await this.store.save({\n ...existing,\n status: \"failed\",\n attempts,\n lastAttemptAt: now.toISOString(),\n nextAttemptAt: new Date(now.getTime() + computeRetryDelayMs(attempts)).toISOString(),\n });\n }\n\n async markSent(eventId: string): Promise<void> {\n await this.store.remove(eventId);\n }\n\n private async get(eventId: string): Promise<QueuedEvent | undefined> {\n return (await this.store.getAll()).find((event) => event.eventId === eventId);\n }\n}\n","export type NetworkStatus = \"online\" | \"offline\" | \"unknown\";\n\nexport interface NetworkMonitor {\n getStatus(): NetworkStatus;\n /** Calls `listener` whenever connectivity changes. Returns an unsubscribe function. */\n subscribe(listener: (status: NetworkStatus) => void): () => void;\n}\n\n/**\n * Default/test implementation — starts \"online\" (optimistic default: if we\n * can't observe connectivity, assume the network call itself is the\n * ultimate source of truth rather than blocking on a monitor). Tests (and\n * the Node/non-RN environment generally) drive it explicitly via\n * `setStatus`; see `sync/netinfo-network-monitor.ts` for the real RN\n * implementation, kept in its own file for the same reason\n * `storage/async-storage-store.ts` is — see that file's comment.\n */\nexport class ManualNetworkMonitor implements NetworkMonitor {\n private status: NetworkStatus = \"online\";\n private readonly listeners = new Set<(status: NetworkStatus) => void>();\n\n getStatus(): NetworkStatus {\n return this.status;\n }\n\n setStatus(status: NetworkStatus): void {\n if (status === this.status) return;\n this.status = status;\n this.listeners.forEach((listener) => listener(status));\n }\n\n subscribe(listener: (status: NetworkStatus) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n","import type { LocationEventPayload } from \"../protocol\";\n\nimport type { ApiClient } from \"../firebase/api-client\";\nimport type { Logger } from \"../core/logger\";\nimport type { PendingEventQueue } from \"../storage/queue\";\n\nconst DEFAULT_RETRY_INTERVAL_MS = 30_000;\n\n/**\n * Only the one method SyncManager actually calls — `Pick` rather than the\n * full `ApiClient` class specifically so tests can pass a plain object\n * (`{ ingestEvent: vi.fn() }`) instead of a real ApiClient instance. A\n * class's private fields make it nominally typed, so a plain object\n * wouldn't satisfy `ApiClient` itself even with matching public methods.\n */\ntype EventIngestionClient = Pick<ApiClient, \"ingestEvent\">;\n\nexport interface SyncResult {\n attempted: number;\n delivered: number;\n failed: number;\n}\n\n/**\n * Send-or-queue plus backoff-aware retry (spec: \"Event retry system\").\n * `retryPending()` only attempts events whose backoff window has elapsed\n * (see PendingEventQueue.listReadyForRetry/RETRY_POLICY) — it's still safe\n * to call as often as you like (on a timer, on reconnect, or manually);\n * events not yet due just get skipped this pass. A successful resend of an\n * already-delivered event is also a no-op server-side, since\n * `ingestLocationEvent` dedupes on the event's own id — so calling this\n * concurrently with an in-flight `sendOrQueue` is never harmful, only\n * occasionally redundant.\n */\nexport class SyncManager {\n private retryTimer: ReturnType<typeof setInterval> | null = null;\n private lastSyncAt: string | null = null;\n private lastUploadResult: \"success\" | \"failure\" | null = null;\n\n constructor(\n private readonly apiClient: EventIngestionClient,\n private readonly queue: PendingEventQueue,\n private readonly retryIntervalMs: number = DEFAULT_RETRY_INTERVAL_MS,\n private readonly logger?: Logger,\n ) {}\n\n /**\n * Write-ahead: the event is durably queued *before* any network attempt is\n * made, so an app kill mid-request can never lose it (spec: \"true\n * write-ahead event persistence\" — a prior version of this method tried\n * the network first and only queued on failure, which meant an event in\n * flight when the process died was gone for good). `attemptDelivery` then\n * runs the exact same success/failure bookkeeping a queued retry uses, so\n * there is only one code path for \"what happens when an upload settles.\"\n */\n async sendOrQueue(payload: LocationEventPayload): Promise<void> {\n await this.queue.enqueue(payload);\n await this.attemptDelivery(payload.id, payload);\n }\n\n /**\n * Attempts queued events. By default only ones whose backoff window has\n * elapsed (the timer-driven path, `startAutoRetry` — the whole point of\n * backoff is to not hammer the backend on a fixed sweep). Pass\n * `{ force: true }` to ignore that window and attempt every non-in-flight\n * queued event regardless — connectivity just being confirmed restored\n * (see `GeoPulseSdk.start`'s network-monitor subscription) is a stronger\n * signal than \"some fixed delay has passed,\" and a device that was\n * offline for a while shouldn't have to wait out a stale backoff timer\n * once it's actually back online.\n */\n async retryPending(options: { force?: boolean } = {}): Promise<SyncResult> {\n const ready = options.force\n ? (await this.queue.listPending()).filter((event) => event.status !== \"sending\")\n : await this.queue.listReadyForRetry();\n const result: SyncResult = { attempted: ready.length, delivered: 0, failed: 0 };\n\n for (const event of ready) {\n const attemptNumber = event.attempts + 1;\n // Reflects the true attempt count server-side (spec: Event Explorer's \"Attempts\" column) — the\n // payload as originally generated always says 1, since it doesn't yet know it'll be retried.\n const delivered = await this.attemptDelivery(event.eventId, { ...event.payload, attempts: attemptNumber });\n if (delivered) {\n result.delivered++;\n } else {\n result.failed++;\n }\n }\n\n return result;\n }\n\n /**\n * The single place that actually talks to the backend: marks the queued\n * record \"sending\" (so a concurrent retry pass skips it), attempts the\n * upload, and resolves it to either removed-from-queue (success — a\n * `deduplicated: true` response still counts, since it means the backend\n * already has this event id, which is exactly the outcome a retry is\n * supposed to produce) or a scheduled retry (failure). Returns whether it\n * was delivered, purely so `retryPending` can tally its `SyncResult`.\n */\n private async attemptDelivery(eventId: string, payload: LocationEventPayload): Promise<boolean> {\n await this.queue.markSending(eventId);\n this.logger?.log(\"Event upload started\", { eventId, attempt: payload.attempts });\n try {\n await this.apiClient.ingestEvent(payload);\n await this.queue.markSent(eventId);\n this.lastSyncAt = new Date().toISOString();\n this.lastUploadResult = \"success\";\n this.logger?.log(\"Event upload succeeded\", { eventId });\n return true;\n } catch (error) {\n await this.queue.markAttemptFailed(eventId);\n this.lastUploadResult = \"failure\";\n this.logger?.warn(\"Event upload failed — retry scheduled\", { eventId, error: String(error) });\n return false;\n }\n }\n\n startAutoRetry(): void {\n if (this.retryTimer) return;\n this.retryTimer = setInterval(() => {\n void this.retryPending();\n }, this.retryIntervalMs);\n }\n\n stopAutoRetry(): void {\n if (this.retryTimer) {\n clearInterval(this.retryTimer);\n this.retryTimer = null;\n }\n }\n\n getLastSyncAt(): string | null {\n return this.lastSyncAt;\n }\n\n getLastUploadResult(): \"success\" | \"failure\" | null {\n return this.lastUploadResult;\n }\n}\n","/**\n * Structured, easy-to-disable debug logging (spec section 22). Every call\n * site passes a short lifecycle label matching the examples in this\n * package's README (\"Configuration synced\", \"Event queued\", ...) plus\n * optional structured details — never the SDK key, never a raw event\n * payload body, never anything from `GeoPulseConfig` beyond `applicationId`.\n */\nexport class Logger {\n constructor(private enabled: boolean) {}\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n log(message: string, details?: Record<string, unknown>): void {\n if (!this.enabled) return;\n if (details) {\n console.log(`[GeoPulse] ${message}`, details);\n } else {\n console.log(`[GeoPulse] ${message}`);\n }\n }\n\n warn(message: string, details?: Record<string, unknown>): void {\n if (!this.enabled) return;\n if (details) {\n console.warn(`[GeoPulse] ${message}`, details);\n } else {\n console.warn(`[GeoPulse] ${message}`);\n }\n }\n}\n","import type {\n DevicePlatform,\n ExperienceDeliveryPayload,\n GeofenceEventType,\n LocationEventPayload,\n SdkGeofenceConfig,\n SdkProjectConfig,\n} from \"../protocol\";\n\nimport {\n ConfigCache,\n DEFAULT_BACKEND_URL,\n InMemoryKeyValueStore,\n getOrCreateDeviceId,\n SDK_VERSION,\n type GeoPulseConfig,\n type KeyValueStore,\n} from \"../config\";\nimport { ApiClient } from \"../firebase/api-client\";\nimport { createEventPayload } from \"../events/factory\";\nimport { GeofenceStateMachine } from \"../geofencing/state-machine\";\nimport type { LocationProvider, LocationUpdate } from \"../location/types\";\nimport { SimulatedLocationProvider } from \"../location/simulated-location-provider\";\nimport type { PermissionProvider, PermissionStatus } from \"../permissions/types\";\nimport { SimulatedPermissionProvider } from \"../permissions/simulated-permission-provider\";\nimport { PendingEventQueue } from \"../storage/queue\";\nimport { ManualNetworkMonitor, type NetworkMonitor } from \"../sync/network-monitor\";\nimport { SyncManager } from \"../sync/sync-manager\";\nimport { Logger } from \"./logger\";\nimport type { ConfigurationStatus, DiagnosticsSnapshot, SdkStatus } from \"./types\";\n\ntype EventListener = (event: LocationEventPayload) => void;\ntype ExperienceListener = (payload: ExperienceDeliveryPayload) => void;\n\n/** Mirrors SyncManager's own default retry interval — see sync/sync-manager.ts's DEFAULT_RETRY_INTERVAL_MS. A host app that wants faster demo feedback (see apps/playground) can override via GeoPulseConfig.experiencePollIntervalMs. */\n/** The SDK key resolved to a different application than `config.applicationId`. */\nexport class ApplicationMismatchError extends Error {\n constructor() {\n super(\"GeoPulse: the SDK key belongs to a different application than config.applicationId — check both values\");\n this.name = \"ApplicationMismatchError\";\n }\n}\n\nconst DEFAULT_EXPERIENCE_POLL_INTERVAL_MS = 30_000;\n/** Floor for any caller-supplied interval (retryIntervalMs/experiencePollIntervalMs) — a config of `0` or a negative number would otherwise turn into a tight, runaway retry/poll loop (Phase 7 SDK audit finding). */\nconst MIN_TIMER_INTERVAL_MS = 1_000;\n\n/** `undefined` passes through untouched (lets the caller's own default apply); a defined-but-too-small value is floored instead of silently creating a runaway timer. */\nfunction clampIntervalMs(value: number | undefined): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (!Number.isFinite(value) || value < MIN_TIMER_INTERVAL_MS) {\n return MIN_TIMER_INTERVAL_MS;\n }\n return value;\n}\n\n/**\n * Everything the SDK talks to outside of itself, injectable so\n * `GeoPulseSdk` (this whole lifecycle) is unit-testable without an RN\n * runtime — tests construct it with fakes; `src/index.ts` (the real public\n * entrypoint) constructs the default instance with the real RN adapters.\n */\nexport interface GeoPulseSdkDependencies {\n locationProvider?: LocationProvider;\n permissionProvider?: PermissionProvider;\n queue?: PendingEventQueue;\n createApiClient?: (config: GeoPulseConfig) => ApiClient;\n deviceIdStore?: KeyValueStore;\n configCacheStore?: KeyValueStore;\n networkMonitor?: NetworkMonitor;\n /** Defaults to \"unknown\" — src/index.ts supplies the real `Platform.OS` so this file stays RN-free. */\n getPlatform?: () => DevicePlatform;\n /** Overrides GeoPulseConfig.debug — mainly for tests that want to assert on log output. */\n debug?: boolean;\n}\n\nexport class GeoPulseSdk {\n private status: SdkStatus = \"idle\";\n private config: GeoPulseConfig | null = null;\n private apiClient: ApiClient | null = null;\n private syncManager: SyncManager | null = null;\n private resolvedProjectId: string | null = null;\n private resolvedApplicationId: string | null = null;\n private applicationEnabled = true;\n private configVersion: string | null = null;\n private configurationStatus: ConfigurationStatus = \"unsynced\";\n private geofences: SdkGeofenceConfig[] = [];\n private lastKnownLocation: LocationUpdate | null = null;\n private lastEvent: LocationEventPayload | null = null;\n private deviceId: string | null = null;\n private locationError: string | null = null;\n private unsubscribeNetwork: (() => void) | null = null;\n private readonly stateMachine = new GeofenceStateMachine();\n private readonly listeners = new Map<GeofenceEventType, Set<EventListener>>();\n private readonly experienceListeners = new Set<ExperienceListener>();\n /** Client-side duplicate protection (spec section 14) — belt-and-suspenders alongside the server-side ack; a delivery already acked but re-observed before the ack lands must not fire `experience.received` twice. */\n private readonly seenExperienceDeliveryIds = new Set<string>();\n private experiencePollTimer: ReturnType<typeof setInterval> | null = null;\n private experiencesReceivedCount = 0;\n private readonly logger: Logger;\n\n private readonly locationProvider: LocationProvider;\n private readonly permissionProvider: PermissionProvider;\n private readonly queue: PendingEventQueue;\n private readonly buildApiClient: (config: GeoPulseConfig) => ApiClient;\n private readonly deviceIdStore: KeyValueStore;\n private readonly configCache: ConfigCache;\n private readonly networkMonitor: NetworkMonitor;\n private readonly getPlatform: () => DevicePlatform;\n private experiencePollIntervalMs = DEFAULT_EXPERIENCE_POLL_INTERVAL_MS;\n\n constructor(dependencies: GeoPulseSdkDependencies = {}) {\n this.locationProvider = dependencies.locationProvider ?? new SimulatedLocationProvider();\n this.permissionProvider = dependencies.permissionProvider ?? new SimulatedPermissionProvider();\n this.queue = dependencies.queue ?? new PendingEventQueue();\n this.buildApiClient =\n dependencies.createApiClient ?? ((config) => new ApiClient(config.functionsBaseUrl || DEFAULT_BACKEND_URL, config.apiKey, config.applicationId));\n this.deviceIdStore = dependencies.deviceIdStore ?? new InMemoryKeyValueStore();\n this.configCache = new ConfigCache(dependencies.configCacheStore ?? new InMemoryKeyValueStore());\n this.networkMonitor = dependencies.networkMonitor ?? new ManualNetworkMonitor();\n this.getPlatform = dependencies.getPlatform ?? (() => \"unknown\");\n this.logger = new Logger(dependencies.debug ?? false);\n }\n\n /**\n * Resolves this device's persistent id, then fetches (or falls back to\n * cached) project configuration. Must succeed — fresh or cached — before\n * `start()`; if neither is available (first-ever run with no network),\n * this rethrows and `getStatus()` reports \"error\".\n */\n async initialize(config: GeoPulseConfig): Promise<void> {\n // Without this guard, calling initialize() while already \"running\" replaced\n // this.syncManager with a fresh instance, orphaning the old one's auto-retry timer (it kept\n // firing forever with no reference left to stop it) while location callbacks from the\n // still-running locationProvider could race against the new config being applied — a real\n // bug found in Phase 7's SDK audit, not a hypothetical. Re-initializing a live SDK has no\n // safe, well-defined meaning short of a full stop() first.\n if (this.status === \"running\") {\n throw new Error(\"GeoPulse.initialize() cannot be called while already running — call GeoPulse.stop() first\");\n }\n if (this.status === \"initializing\") {\n throw new Error(\"GeoPulse.initialize() is already in progress — wait for the first call to resolve\");\n }\n if (!config.apiKey || !config.applicationId) {\n throw new Error(\"GeoPulse.initialize requires both apiKey and applicationId\");\n }\n\n if (config.debug !== undefined) {\n this.logger.setEnabled(config.debug);\n }\n this.logger.log(\"GeoPulse.initialize()\", { applicationId: config.applicationId });\n\n this.status = \"initializing\";\n this.config = config;\n this.experiencePollIntervalMs = clampIntervalMs(config.experiencePollIntervalMs) ?? DEFAULT_EXPERIENCE_POLL_INTERVAL_MS;\n this.apiClient = this.buildApiClient(config);\n this.syncManager = new SyncManager(this.apiClient, this.queue, clampIntervalMs(config.retryIntervalMs), this.logger);\n this.deviceId = await getOrCreateDeviceId(this.deviceIdStore);\n\n try {\n const projectConfig = await this.apiClient.getProjectConfig({\n deviceId: this.deviceId,\n platform: this.getPlatform(),\n sdkVersion: SDK_VERSION,\n });\n // The key must belong to the application the integrator configured; never adopt another one.\n if (projectConfig.applicationId !== config.applicationId) {\n throw new ApplicationMismatchError();\n }\n this.applyProjectConfig(projectConfig);\n this.configurationStatus = \"synced\";\n await this.configCache.save(projectConfig);\n this.status = \"initialized\";\n this.logger.log(\"Configuration synced\", { geofenceCount: projectConfig.geofences.length, configVersion: projectConfig.configVersion });\n } catch (error) {\n if (error instanceof ApplicationMismatchError) {\n this.configurationStatus = \"unavailable\";\n this.status = \"error\";\n throw error;\n }\n const cached = await this.configCache.load();\n if (cached && cached.config.applicationId === config.applicationId) {\n this.applyProjectConfig(cached.config);\n this.configurationStatus = \"cached\";\n this.status = \"initialized\";\n this.logger.warn(\"getProjectConfig failed — using cached configuration\", { fetchedAt: cached.fetchedAt });\n return;\n }\n this.configurationStatus = \"unavailable\";\n this.status = \"error\";\n throw error;\n }\n }\n\n async start(): Promise<void> {\n if (this.status === \"running\") {\n // Previously fell into the same generic \"called before initialize succeeded\" message below\n // even though initialize DID succeed and the SDK IS running — factually wrong and would\n // send a developer debugging a double-start() down the wrong path (Phase 7 SDK audit).\n throw new Error(\"GeoPulse.start() called while already running — call GeoPulse.stop() first if you want to restart\");\n }\n if (this.status !== \"initialized\" && this.status !== \"stopped\") {\n throw new Error(\"GeoPulse.start called before GeoPulse.initialize succeeded\");\n }\n if (!this.applicationEnabled) {\n this.status = \"error\";\n throw new Error(\"This application has been disabled in the GeoPulse admin portal\");\n }\n\n const permission = await this.permissionProvider.request();\n if (permission !== \"granted\") {\n this.status = \"error\";\n throw new Error(`Location permission not granted (status: ${permission})`);\n }\n\n this.locationError = null;\n await this.locationProvider.start(\n (location) => {\n void this.handleLocationUpdate(location);\n },\n (error) => {\n this.locationError = error.message;\n this.logger.warn(\"Location provider failed to start\", { error: error.message });\n },\n );\n if (this.locationError) {\n // The provider caught its own failure and reported it via onError rather than rejecting\n // start() directly (see LocationProvider.start's doc) — surfaced here as a normal rejection\n // so callers get the same \"start() throws on failure\" contract as the permission-denied case.\n this.status = \"error\";\n throw new Error(`GeoPulse failed to start location tracking: ${this.locationError}`);\n }\n\n this.unsubscribeNetwork = this.networkMonitor.subscribe((networkStatus) => {\n if (networkStatus === \"online\") {\n this.logger.log(\"Network available — retrying queued events\");\n void this.syncManager?.retryPending({ force: true });\n }\n });\n\n this.syncManager?.startAutoRetry();\n this.startExperiencePolling();\n this.status = \"running\";\n this.logger.log(\"GeoPulse.start()\");\n }\n\n stop(): void {\n this.locationProvider.stop();\n this.syncManager?.stopAutoRetry();\n this.stopExperiencePolling();\n this.unsubscribeNetwork?.();\n this.unsubscribeNetwork = null;\n this.status = \"stopped\";\n this.logger.log(\"GeoPulse.stop()\");\n }\n\n getStatus(): SdkStatus {\n return this.status;\n }\n\n async getPermissionStatus(): Promise<PermissionStatus> {\n return this.permissionProvider.getStatus();\n }\n\n on(eventName: GeofenceEventType, callback: EventListener): void;\n on(eventName: \"experience.received\", callback: ExperienceListener): void;\n on(eventName: GeofenceEventType | \"experience.received\", callback: EventListener | ExperienceListener): void {\n if (eventName === \"experience.received\") {\n this.experienceListeners.add(callback as ExperienceListener);\n return;\n }\n if (!this.listeners.has(eventName)) {\n this.listeners.set(eventName, new Set());\n }\n this.listeners.get(eventName)?.add(callback as EventListener);\n }\n\n off(eventName: GeofenceEventType, callback: EventListener): void;\n off(eventName: \"experience.received\", callback: ExperienceListener): void;\n off(eventName: GeofenceEventType | \"experience.received\", callback: EventListener | ExperienceListener): void {\n if (eventName === \"experience.received\") {\n this.experienceListeners.delete(callback as ExperienceListener);\n return;\n }\n this.listeners.get(eventName)?.delete(callback as EventListener);\n }\n\n // --- Small read-only accessors beyond the core 7-method API, so a host\n // app (see apps/playground) can build a status UI around the SDK without\n // it needing to duplicate state the SDK already tracks internally. ---\n\n getConfiguredGeofences(): SdkGeofenceConfig[] {\n return this.geofences;\n }\n\n /** The server-resolved application id (from `getProjectConfig`), or null before `initialize()` completes. */\n getApplicationId(): string | null {\n return this.resolvedApplicationId;\n }\n\n getLastKnownLocation(): LocationUpdate | null {\n return this.lastKnownLocation;\n }\n\n async getPendingEventCount(): Promise<number> {\n return (await this.queue.listPending()).length;\n }\n\n /** Null until `initialize()` resolves it — persisted across app restarts (see config/device-id.ts). */\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n /**\n * The rich developer-diagnostics snapshot (spec section 10/11) — a\n * superset of `getStatus()`, not a replacement for it (see core/types.ts's\n * `DiagnosticsSnapshot` doc for why these are two separate methods).\n */\n async getDiagnostics(): Promise<DiagnosticsSnapshot> {\n const pending = await this.queue.listPending();\n const permissionStatus = this.status === \"idle\" || this.status === \"initializing\" ? \"unknown\" : await this.permissionProvider.getStatus();\n\n return {\n initialized: this.status !== \"idle\" && this.status !== \"initializing\",\n running: this.status === \"running\",\n locationAvailable: this.lastKnownLocation !== null,\n permissionStatus,\n networkStatus: this.networkMonitor.getStatus(),\n configurationStatus: this.configurationStatus,\n configVersion: this.configVersion,\n pendingEvents: pending.length,\n totalRetryAttempts: pending.reduce((sum, event) => sum + event.attempts, 0),\n locationError: this.locationError,\n lastEventAt: this.lastEvent?.timestamp ?? null,\n lastEventType: this.lastEvent?.type ?? null,\n lastSyncAt: this.syncManager?.getLastSyncAt() ?? null,\n lastUploadResult: this.syncManager?.getLastUploadResult() ?? null,\n deviceId: this.deviceId,\n sdkVersion: SDK_VERSION,\n experiencesReceivedCount: this.experiencesReceivedCount,\n };\n }\n\n /**\n * Only functional when the SDK is running the simulated location\n * provider (the default outside of production builds — see\n * src/index.ts) and gated by `__DEV__` so it's inert in a production\n * bundle even if a caller somehow held a reference to it.\n */\n simulateLocation(coords: { latitude: number; longitude: number }): void {\n const isDev = typeof __DEV__ === \"undefined\" ? true : __DEV__;\n if (!isDev) {\n console.warn(\"GeoPulse.simulator is unavailable in production builds.\");\n return;\n }\n if (!(this.locationProvider instanceof SimulatedLocationProvider)) {\n console.warn(\n \"GeoPulse.simulator.setLocation has no effect: the SDK is not running the simulated location provider.\",\n );\n return;\n }\n this.locationProvider.setLocation(coords);\n }\n\n /**\n * The Phase 5B delivery mechanism: poll immediately on `start()` (so a\n * demo doesn't wait a full interval for the first check), then on a\n * timer. A poll failure (network blip, backend down) is logged and\n * swallowed — it must never crash the SDK or the host app (spec section\n * 13), and the next tick tries again on its own.\n */\n private startExperiencePolling(): void {\n void this.pollForExperiences();\n this.experiencePollTimer = setInterval(() => {\n void this.pollForExperiences();\n }, this.experiencePollIntervalMs);\n }\n\n private stopExperiencePolling(): void {\n if (this.experiencePollTimer) {\n clearInterval(this.experiencePollTimer);\n this.experiencePollTimer = null;\n }\n }\n\n private async pollForExperiences(): Promise<void> {\n if (!this.apiClient || !this.deviceId) {\n return;\n }\n try {\n const experiences = await this.apiClient.getPendingExperiences(this.deviceId);\n for (const experience of experiences) {\n // Backend is the source of truth (spec section 14) — this is defense in depth against\n // observing the same still-\"pending\" delivery twice across two polls before the ack lands.\n if (this.seenExperienceDeliveryIds.has(experience.deliveryId)) {\n continue;\n }\n this.seenExperienceDeliveryIds.add(experience.deliveryId);\n this.experiencesReceivedCount += 1;\n this.logger.log(\"Experience received\", { deliveryId: experience.deliveryId, campaignId: experience.campaignId });\n this.experienceListeners.forEach((listener) => listener(experience));\n\n await this.apiClient.acknowledgeExperienceDelivery(experience.deliveryId).catch((error) => {\n this.logger.warn(\"Failed to acknowledge experience delivery\", {\n deliveryId: experience.deliveryId,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n } catch (error) {\n this.logger.warn(\"Polling for experiences failed\", { error: error instanceof Error ? error.message : String(error) });\n }\n }\n\n private applyProjectConfig(projectConfig: SdkProjectConfig): void {\n this.resolvedProjectId = projectConfig.projectId;\n this.resolvedApplicationId = projectConfig.applicationId;\n this.applicationEnabled = projectConfig.applicationEnabled;\n this.configVersion = projectConfig.configVersion;\n this.geofences = projectConfig.geofences;\n }\n\n private async handleLocationUpdate(location: LocationUpdate): Promise<void> {\n this.lastKnownLocation = location;\n if (!this.resolvedProjectId || !this.resolvedApplicationId || !this.syncManager || !this.deviceId) {\n return;\n }\n\n const transitions = this.stateMachine.evaluate(location, this.geofences);\n for (const transition of transitions) {\n this.logger.log(\"Geofence state changed\", { geofenceId: transition.geofence.id, type: transition.type });\n\n const event = createEventPayload({\n type: transition.type,\n projectId: this.resolvedProjectId,\n applicationId: this.resolvedApplicationId,\n geofence: transition.geofence,\n location,\n deviceId: this.deviceId,\n });\n this.lastEvent = event;\n this.logger.log(\"Event generated\", { eventId: event.id, type: event.type });\n\n this.listeners.get(transition.type)?.forEach((listener) => listener(event));\n await this.syncManager.sendOrQueue(event);\n }\n }\n}\n","import * as Location from \"expo-location\";\n\nimport type { LocationProvider, LocationUpdate } from \"./types\";\n\n/**\n * Real, foreground-only device location via expo-location. Does not\n * request or use background location — see this package's README for the\n * platform permission requirements and why background tracking is\n * explicitly out of scope for this phase.\n */\nexport class DeviceLocationProvider implements LocationProvider {\n private subscription: Location.LocationSubscription | null = null;\n\n /**\n * `watchPositionAsync` rejects if location services are off at the OS\n * level (distinct from the app's own permission grant, which is checked\n * before this ever runs) — caught here and reported via `onError` instead\n * of left to reject this method's own promise, so a caller that doesn't\n * wrap `GeoPulse.start()` in a try/catch still can't end up with an\n * unhandled rejection from deep inside a native module.\n */\n async start(onUpdate: (location: LocationUpdate) => void, onError?: (error: Error) => void): Promise<void> {\n try {\n this.subscription = await Location.watchPositionAsync(\n { accuracy: Location.Accuracy.Balanced, timeInterval: 5000, distanceInterval: 10 },\n (position) => {\n onUpdate({\n latitude: position.coords.latitude,\n longitude: position.coords.longitude,\n timestamp: new Date(position.timestamp).toISOString(),\n });\n },\n );\n } catch (error) {\n onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n stop(): void {\n this.subscription?.remove();\n this.subscription = null;\n }\n}\n","import * as Location from \"expo-location\";\n\nimport type { PermissionProvider, PermissionStatus } from \"./types\";\n\nfunction mapExpoStatus(status: Location.PermissionStatus): PermissionStatus {\n switch (status) {\n case Location.PermissionStatus.GRANTED:\n return \"granted\";\n case Location.PermissionStatus.DENIED:\n return \"denied\";\n default:\n return \"undetermined\";\n }\n}\n\n/**\n * Foreground-only — see this package's README (\"Permissions\" section) for\n * why background permission is explicitly out of scope for this phase.\n * Requests only the foreground permission; never touches\n * `requestBackgroundPermissionsAsync`.\n */\nexport class ExpoPermissionProvider implements PermissionProvider {\n async getStatus(): Promise<PermissionStatus> {\n const { status } = await Location.getForegroundPermissionsAsync();\n return mapExpoStatus(status);\n }\n\n async request(): Promise<PermissionStatus> {\n const { status } = await Location.requestForegroundPermissionsAsync();\n return mapExpoStatus(status);\n }\n}\n","import AsyncStorage from \"@react-native-async-storage/async-storage\";\n\nimport type { EventQueueStore, QueuedEvent } from \"./types\";\n\nconst STORAGE_KEY = \"@geopulse/pending-events\";\n\n/**\n * Persists the pending-event queue across app restarts using AsyncStorage —\n * required for the offline queue to actually survive the app being killed\n * before connectivity returns, not just a network blip while it's running.\n * Single-JSON-blob storage rather than one key per event: the expected\n * queue size (events waiting on a flaky connection) is small enough that\n * read/write-modify-write on one key is simpler and fine.\n */\nexport class AsyncStorageEventQueueStore implements EventQueueStore {\n async getAll(): Promise<QueuedEvent[]> {\n const raw = await AsyncStorage.getItem(STORAGE_KEY);\n if (!raw) return [];\n try {\n return JSON.parse(raw) as QueuedEvent[];\n } catch {\n return [];\n }\n }\n\n async save(event: QueuedEvent): Promise<void> {\n const all = await this.getAll();\n const next = [...all.filter((existing) => existing.eventId !== event.eventId), event];\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(next));\n }\n\n async remove(eventId: string): Promise<void> {\n const all = await this.getAll();\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(all.filter((existing) => existing.eventId !== eventId)));\n }\n}\n","import NetInfo from \"@react-native-community/netinfo\";\n\nimport type { NetworkMonitor, NetworkStatus } from \"./network-monitor\";\n\nfunction toStatus(isConnected: boolean | null): NetworkStatus {\n if (isConnected === null) return \"unknown\";\n return isConnected ? \"online\" : \"offline\";\n}\n\n/** Real connectivity detection for the RN app. Isolated in its own file — see storage/async-storage-store.ts's comment for why. */\nexport class NetInfoNetworkMonitor implements NetworkMonitor {\n private status: NetworkStatus = \"unknown\";\n\n constructor() {\n NetInfo.fetch().then((state) => {\n this.status = toStatus(state.isConnected);\n });\n }\n\n getStatus(): NetworkStatus {\n return this.status;\n }\n\n subscribe(listener: (status: NetworkStatus) => void): () => void {\n return NetInfo.addEventListener((state) => {\n this.status = toStatus(state.isConnected);\n listener(this.status);\n });\n }\n}\n","import React from \"react\";\nimport { Image, Linking, Text, TouchableOpacity, View } from \"react-native\";\n\nimport { resolveExperienceStyle, type ExperienceDeliveryPayload, type ResolvedExperienceStyle } from \"../protocol\";\n\ntype ExperienceContent = ExperienceDeliveryPayload[\"experience\"];\n\n/**\n * Experience URLs come from the backend. Only web URLs are opened: schemes\n * like `intent:`, `tel:`, `file:` or another app's deep links could trigger\n * actions outside this app (audit M-2).\n */\nexport function isSafeExternalUrl(url: string | null | undefined): url is string {\n return typeof url === \"string\" && /^https?:\\/\\/[^\\s]+$/i.test(url);\n}\n\nfunction openUrl(url: string | null): void {\n if (!isSafeExternalUrl(url)) return;\n // Linking.openURL hands off to the OS/system browser — no WebView, no\n // in-app script execution, matching the existing playground component's\n // rule this replaces (see apps/playground/src/components/ExperienceCard.tsx).\n Linking.openURL(url).catch(() => {\n // Swallow — an unreachable/malformed URL must never crash the host app.\n });\n}\n\nfunction ExperienceButton({ style, ctaLabel, ctaUrl }: { style: ResolvedExperienceStyle; ctaLabel: string | null; ctaUrl: string | null }) {\n if (!style.button.visible || !ctaLabel || !ctaUrl) return null;\n const { position } = style.button;\n return (\n <TouchableOpacity\n onPress={() => openUrl(ctaUrl)}\n style={{\n position: \"absolute\",\n top: position.top ?? undefined,\n bottom: position.bottom ?? undefined,\n left: position.left ?? undefined,\n right: position.right ?? undefined,\n backgroundColor: style.button.backgroundColor,\n borderRadius: style.button.borderRadius,\n paddingVertical: 10,\n paddingHorizontal: 16,\n }}\n >\n <Text style={{ color: style.button.textColor, fontWeight: \"600\" }}>{ctaLabel}</Text>\n </TouchableOpacity>\n );\n}\n\n/** Shared by all three experience types — full-bleed background image (if any) behind title/body/button, everything else laid out per-type by its caller. */\nfunction ExperienceContainer({ content, style, children }: { content: ExperienceContent; style: ResolvedExperienceStyle; children: React.ReactNode }): React.JSX.Element {\n const containerStyle = {\n backgroundColor: style.container.backgroundColor,\n borderColor: style.container.borderColor ?? undefined,\n borderWidth: style.container.borderWidth,\n borderRadius: style.container.borderRadius,\n padding: style.container.padding,\n overflow: \"hidden\" as const,\n width: style.container.width === \"fill\" ? (\"100%\" as const) : style.container.width,\n // \"auto\" means no explicit height at all — RN sizes a View to its content by default, so\n // omitting the property (rather than passing \"auto\", which RN's height type doesn't accept)\n // is what actually produces that behavior.\n // The button is absolutely positioned, so it takes no layout space — without a floor, an\n // \"auto\" container shrinks to title+body and the button overlaps them. 160 matches the\n // admin-web preview (experience-preview.tsx) so the device renders what the portal shows.\n ...(style.container.height === \"auto\" ? { minHeight: 160 } : { height: style.container.height }),\n };\n const backgroundImage = isSafeExternalUrl(style.container.backgroundImageUrl) ? (\n <Image\n source={{ uri: style.container.backgroundImageUrl }}\n resizeMode={style.container.backgroundImageFit}\n style={{ position: \"absolute\", top: 0, left: 0, right: 0, bottom: 0 }}\n />\n ) : null;\n\n // TouchableOpacity and View have different prop shapes, so this branches on\n // two full JSX returns rather than picking a dynamic component variable —\n // the latter can't be given a declaration-emittable type.\n if (content.cardNavigationUrl) {\n const cardNavigationUrl = content.cardNavigationUrl;\n return (\n <TouchableOpacity testID=\"experience-container\" onPress={() => openUrl(cardNavigationUrl)} style={containerStyle}>\n {backgroundImage}\n {children}\n </TouchableOpacity>\n );\n }\n return (\n <View testID=\"experience-container\" style={containerStyle}>\n {backgroundImage}\n {children}\n </View>\n );\n}\n\nfunction ExperienceText({ visible, text, color, fontSize, fontWeight }: { visible: boolean; text: string; color: string; fontSize: number; fontWeight: \"normal\" | \"bold\" }) {\n if (!visible) return null;\n return <Text style={{ color, fontSize, fontWeight }}>{text}</Text>;\n}\n\nexport function CardExperience({ content }: { content: ExperienceContent }): React.JSX.Element {\n const style = resolveExperienceStyle(\"card\", content.style);\n return (\n <ExperienceContainer content={content} style={style}>\n <ExperienceText visible={style.title.visible} text={content.title} color={style.title.color} fontSize={style.title.fontSize} fontWeight={style.title.fontWeight} />\n <ExperienceText visible={style.body.visible} text={content.body} color={style.body.color} fontSize={style.body.fontSize} fontWeight={style.body.fontWeight} />\n <ExperienceButton style={style} ctaLabel={content.ctaLabel} ctaUrl={content.ctaUrl} />\n </ExperienceContainer>\n );\n}\n\nexport function BannerExperience({ content }: { content: ExperienceContent }): React.JSX.Element {\n const style = resolveExperienceStyle(\"banner\", content.style);\n return (\n <ExperienceContainer content={content} style={style}>\n <ExperienceText visible={style.title.visible} text={content.title} color={style.title.color} fontSize={style.title.fontSize} fontWeight={style.title.fontWeight} />\n <ExperienceText visible={style.body.visible} text={content.body} color={style.body.color} fontSize={style.body.fontSize} fontWeight={style.body.fontWeight} />\n <ExperienceButton style={style} ctaLabel={content.ctaLabel} ctaUrl={content.ctaUrl} />\n </ExperienceContainer>\n );\n}\n\nexport function MessageExperience({ content }: { content: ExperienceContent }): React.JSX.Element {\n const style = resolveExperienceStyle(\"message\", content.style);\n return (\n <ExperienceContainer content={content} style={style}>\n <ExperienceText visible={style.title.visible} text={content.title} color={style.title.color} fontSize={style.title.fontSize} fontWeight={style.title.fontWeight} />\n <ExperienceText visible={style.body.visible} text={content.body} color={style.body.color} fontSize={style.body.fontSize} fontWeight={style.body.fontWeight} />\n <ExperienceButton style={style} ctaLabel={content.ctaLabel} ctaUrl={content.ctaUrl} />\n </ExperienceContainer>\n );\n}\n\n/** Picks the right renderer by `experience.type` — the one component most host apps need. */\nexport function ExperienceView({ experience }: { experience: ExperienceContent }): React.JSX.Element {\n switch (experience.type) {\n case \"banner\":\n return <BannerExperience content={experience} />;\n case \"message\":\n return <MessageExperience content={experience} />;\n case \"card\":\n default:\n return <CardExperience content={experience} />;\n }\n}\n"],"mappings":"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","names":["import_react_native","AsyncStorage","isDev","Location","import_async_storage","AsyncStorage","NetInfo","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config/async-storage-key-value-store.ts","../src/config/types.ts","../src/config/key-value-store.ts","../src/events/uuid.ts","../src/config/device-id.ts","../src/config/config-cache.ts","../src/protocol.ts","../src/firebase/api-client.ts","../src/events/factory.ts","../src/geofencing/state-machine.ts","../src/geofencing/state-store.ts","../src/location/simulated-location-provider.ts","../src/permissions/simulated-permission-provider.ts","../src/sync/retry-policy.ts","../src/storage/memory-store.ts","../src/storage/queue.ts","../src/sync/network-monitor.ts","../src/sync/sync-manager.ts","../src/core/logger.ts","../src/core/geo-pulse-sdk.ts","../src/location/device-location-provider.ts","../src/permissions/expo-permission-provider.ts","../src/storage/async-storage-store.ts","../src/realtime/realtime-client.ts","../src/sync/netinfo-network-monitor.ts","../src/rendering/experience-components.tsx"],"sourcesContent":["import { Platform } from \"react-native\";\nimport type { ExperienceDeliveryPayload, GeofenceEventType, LocationEventPayload, RevokedExperience } from \"./protocol\";\n\nimport { AsyncStorageKeyValueStore } from \"./config/async-storage-key-value-store\";\nimport type { GeoPulseConfig } from \"./config/types\";\nimport { GeoPulseSdk } from \"./core/geo-pulse-sdk\";\nimport { DeviceLocationProvider } from \"./location/device-location-provider\";\nimport { SimulatedLocationProvider } from \"./location/simulated-location-provider\";\nimport { ExpoPermissionProvider } from \"./permissions/expo-permission-provider\";\nimport { SimulatedPermissionProvider } from \"./permissions/simulated-permission-provider\";\nimport { AsyncStorageEventQueueStore } from \"./storage/async-storage-store\";\nimport { RealtimeClient } from \"./realtime/realtime-client\";\nimport { PendingEventQueue } from \"./storage/queue\";\nimport { NetInfoNetworkMonitor } from \"./sync/netinfo-network-monitor\";\n\nconst isDev = typeof __DEV__ === \"undefined\" ? true : __DEV__;\n\n/**\n * Opt-in escape hatch for testing real GPS *during development* without\n * switching to a production build — set `EXPO_PUBLIC_GEOPULSE_USE_REAL_LOCATION=true`\n * in the host app's `.env` (Expo statically inlines `EXPO_PUBLIC_*` at\n * bundle time, including inside this workspace package, so this works\n * without the SDK needing any new config field). Defaults to false: dev\n * still means \"simulator\" unless a developer explicitly asks otherwise.\n */\nconst useRealLocationInDev = process.env.EXPO_PUBLIC_GEOPULSE_USE_REAL_LOCATION === \"true\";\nconst useSimulatedProviders = isDev && !useRealLocationInDev;\n\nfunction getPlatform(): \"ios\" | \"android\" | \"web\" | \"unknown\" {\n switch (Platform.OS) {\n case \"ios\":\n case \"android\":\n case \"web\":\n return Platform.OS;\n default:\n return \"unknown\";\n }\n}\n\n// Shared by device-id, config-cache and the geofence state — all just distinctly-keyed\n// entries in AsyncStorage, so one instance is enough.\nconst persistentKeyValueStore = new AsyncStorageKeyValueStore();\n\n/**\n * Dev builds default to the simulated location + permission providers (see\n * the \"Simulator\" section of this package's README) so the whole\n * location -> geofence -> event flow works with zero device setup. A\n * release build — or a dev build with `EXPO_PUBLIC_GEOPULSE_USE_REAL_LOCATION=true`\n * set (see above) — uses real device location and real OS permission\n * prompts instead.\n */\nconst sdk = new GeoPulseSdk({\n locationProvider: useSimulatedProviders ? new SimulatedLocationProvider() : new DeviceLocationProvider(),\n permissionProvider: useSimulatedProviders ? new SimulatedPermissionProvider() : new ExpoPermissionProvider(),\n queue: new PendingEventQueue(new AsyncStorageEventQueueStore()),\n deviceIdStore: persistentKeyValueStore,\n configCacheStore: persistentKeyValueStore,\n // The simulator is for trying things out: no 2-minute cooldown, and no saved \"inside\" state surviving a reload\n // (otherwise \"enter\" would not fire again after Fast Refresh while the saved state says you are still inside).\n stateStore: useSimulatedProviders ? undefined : persistentKeyValueStore,\n stateMachineOptions: useSimulatedProviders ? { cooldownMs: 0 } : undefined,\n createRealtimeClient: (options) => new RealtimeClient(options),\n networkMonitor: new NetInfoNetworkMonitor(),\n getPlatform,\n debug: isDev,\n});\n\n/** `sdk.on`/`sdk.off` are already overloaded this way (see core/geo-pulse-sdk.ts) — this just re-states the same public shape for the plain-object `GeoPulse` singleton below. */\ntype OnOff = {\n (eventName: GeofenceEventType, callback: (event: LocationEventPayload) => void): void;\n (eventName: \"experience.received\", callback: (payload: ExperienceDeliveryPayload) => void): void;\n (eventName: \"experience.removed\", callback: (payload: RevokedExperience) => void): void;\n};\n\nexport const GeoPulse = {\n initialize: (config: GeoPulseConfig) => sdk.initialize(config),\n start: () => sdk.start(),\n stop: () => sdk.stop(),\n getStatus: () => sdk.getStatus(),\n getPermissionStatus: () => sdk.getPermissionStatus(),\n on: sdk.on.bind(sdk) as OnOff,\n off: sdk.off.bind(sdk) as OnOff,\n\n // Small additions beyond the core 7-method API, for building a status UI\n // around the SDK (see apps/playground) — not part of the minimal public\n // surface the spec calls out, but plain read-only accessors for state the\n // SDK already tracks internally.\n getConfiguredGeofences: () => sdk.getConfiguredGeofences(),\n getApplicationId: () => sdk.getApplicationId(),\n getLastKnownLocation: () => sdk.getLastKnownLocation(),\n getPendingEventCount: () => sdk.getPendingEventCount(),\n getDeviceId: () => sdk.getDeviceId(),\n /** Rich developer-diagnostics snapshot — see core/types.ts's DiagnosticsSnapshot doc. */\n getDiagnostics: () => sdk.getDiagnostics(),\n\n /** Development-only. No-ops (with a console warning) in a production build — see GeoPulseSdk.simulateLocation. */\n simulator: {\n setLocation: (coords: { latitude: number; longitude: number }) => sdk.simulateLocation(coords),\n },\n};\n\n// Advanced/testing escape hatch — construct your own SDK instance instead\n// of using the `GeoPulse` singleton above (e.g. to force real device\n// location during development). Most consumers should just use `GeoPulse`.\nexport { ApplicationMismatchError, GeoPulseSdk } from \"./core/geo-pulse-sdk\";\nexport { DeviceLocationProvider } from \"./location/device-location-provider\";\nexport { SimulatedLocationProvider } from \"./location/simulated-location-provider\";\nexport { ExpoPermissionProvider } from \"./permissions/expo-permission-provider\";\nexport { SimulatedPermissionProvider } from \"./permissions/simulated-permission-provider\";\nexport { NetInfoNetworkMonitor } from \"./sync/netinfo-network-monitor\";\nexport { ManualNetworkMonitor } from \"./sync/network-monitor\";\n\nexport type { GeoPulseConfig } from \"./config/types\";\nexport type { ConfigurationStatus, DiagnosticsSnapshot, SdkStatus } from \"./core/types\";\nexport type { LocationProvider, LocationUpdate } from \"./location/types\";\nexport type { PermissionProvider, PermissionStatus } from \"./permissions/types\";\nexport type { NetworkMonitor, NetworkStatus } from \"./sync/network-monitor\";\nexport type { ExperienceDeliveryPayload, GeofenceEventType, LocationEventPayload, RevokedExperience, SdkGeofenceConfig } from \"./protocol\";\nexport { BannerExperience, CardExperience, ExperienceView, MessageExperience } from \"./rendering\";\n","import AsyncStorage from \"@react-native-async-storage/async-storage\";\n\nimport type { KeyValueStore } from \"./key-value-store\";\n\n/** Real persistence for the RN app. Isolated in its own file — see storage/async-storage-store.ts's comment for why. */\nexport class AsyncStorageKeyValueStore implements KeyValueStore {\n async getItem(key: string): Promise<string | null> {\n return AsyncStorage.getItem(key);\n }\n\n async setItem(key: string, value: string): Promise<void> {\n await AsyncStorage.setItem(key, value);\n }\n}\n","export interface GeoPulseConfig {\n apiKey: string;\n applicationId: string;\n /**\n * Internal override for the GeoPulse backend URL — only for GeoPulse's own\n * local development (e.g. apps/playground against a local backend).\n * Customers never set this; it defaults to `DEFAULT_BACKEND_URL`.\n */\n functionsBaseUrl?: string;\n /** How often to retry queued (previously failed) events, in milliseconds. Defaults to 30s. */\n retryIntervalMs?: number;\n /**\n * Fallback interval for polling pending campaign experiences, in milliseconds. Defaults to 15 minutes.\n * Experiences are normally fetched right after an event the backend says created one, once on\n * `start()`, and when the server pushes a change to a phone inside a geofence, so this only catches anything missed. Lower it only if you need that safety net faster.\n */\n experiencePollIntervalMs?: number;\n /** Enables the lifecycle/event debug log lines described in this package's README. Defaults to `__DEV__`. Never logs the SDK key or event payload bodies. */\n debug?: boolean;\n}\n\n/** The hosted GeoPulse backend every customer app talks to. */\nexport const DEFAULT_BACKEND_URL = \"https://geofends.onrender.com\";\n\nexport const SDK_VERSION = \"0.4.0\";\n","/**\n * Minimal persistent key-value abstraction, shared by device-id.ts and\n * config-cache.ts — deliberately smaller than `storage/EventQueueStore`\n * (which manages a whole collection) since these two only ever need \"get\n * one string\" / \"set one string\".\n */\nexport interface KeyValueStore {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n}\n\nexport class InMemoryKeyValueStore implements KeyValueStore {\n private readonly data = new Map<string, string>();\n\n async getItem(key: string): Promise<string | null> {\n return this.data.get(key) ?? null;\n }\n\n async setItem(key: string, value: string): Promise<void> {\n this.data.set(key, value);\n }\n}\n","/**\n * RFC 4122 v4 UUID for event and device ids. Uses the platform CSPRNG\n * (`crypto.getRandomValues`, available in Hermes >= 0.12 and Node) when\n * present, falling back to `Math.random()` only on runtimes without it.\n * These ids aren't secrets, but a device id scopes which pending\n * experiences a request can fetch, so it shouldn't be predictable.\n */\nexport function generateEventId(): string {\n const bytes = new Uint8Array(16);\n const cryptoApi = (globalThis as { crypto?: { getRandomValues?: (array: Uint8Array) => Uint8Array } }).crypto;\n if (cryptoApi?.getRandomValues) {\n cryptoApi.getRandomValues(bytes);\n } else {\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;\n bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;\n const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","import { generateEventId } from \"../events/uuid\";\nimport type { KeyValueStore } from \"./key-value-store\";\n\nconst DEVICE_ID_STORAGE_KEY = \"@geopulse/device-id\";\n\n/**\n * A stable, random, non-identifying installation id — generated once per\n * install and persisted thereafter, not derived from any hardware\n * identifier (spec: \"do not attempt invasive device fingerprinting\").\n * `generateEventId` is just a generic UUID v4 generator despite its name\n * (see events/uuid.ts); reused here rather than duplicated.\n */\nexport async function getOrCreateDeviceId(store: KeyValueStore): Promise<string> {\n const existing = await store.getItem(DEVICE_ID_STORAGE_KEY);\n if (existing) {\n return existing;\n }\n const deviceId = generateEventId();\n await store.setItem(DEVICE_ID_STORAGE_KEY, deviceId);\n return deviceId;\n}\n","import type { SdkProjectConfig } from \"../protocol\";\n\nimport type { KeyValueStore } from \"./key-value-store\";\n\nconst CONFIG_CACHE_STORAGE_KEY = \"@geopulse/config-cache\";\n\ninterface CachedConfig {\n config: SdkProjectConfig;\n fetchedAt: string;\n}\n\n/**\n * Persists the last successfully fetched `SdkProjectConfig` so\n * `initialize()` can still succeed — using stale-but-valid geofences —\n * when `getProjectConfig` is unreachable (spec: \"If the device temporarily\n * loses network access, the SDK should be able to continue using the last\n * valid configuration\"). There is no expiry: a geofence list that's a few\n * hours old is still far more useful than refusing to start at all, and\n * the next successful fetch overwrites it regardless of age.\n */\nexport class ConfigCache {\n constructor(private readonly store: KeyValueStore) {}\n\n async save(config: SdkProjectConfig): Promise<void> {\n const cached: CachedConfig = { config, fetchedAt: new Date().toISOString() };\n await this.store.setItem(CONFIG_CACHE_STORAGE_KEY, JSON.stringify(cached));\n }\n\n async load(): Promise<CachedConfig | null> {\n const raw = await this.store.getItem(CONFIG_CACHE_STORAGE_KEY);\n if (!raw) return null;\n try {\n return JSON.parse(raw) as CachedConfig;\n } catch {\n return null;\n }\n }\n}\n","/**\n * The SDK's entire wire contract with the GeoPulse backend, copied from\n * `@geopulse/shared` rather than depending on it at runtime. `@geopulse/shared`\n * is a monorepo-internal package (also used by apps/admin-web and functions/)\n * that carries the *whole* platform's schemas — organizations, webhooks,\n * campaigns, auth forms, and more. None of that belongs inside a published,\n * on-device SDK: bundling the real package pulled in all of it (verified via\n * `npm pack` on the published tarball — dist/index.js contained the admin\n * portal's signup/login validation, webhook, and campaign schemas). This file\n * is the deliberately small subset the SDK actually touches. Keep it in sync\n * with the corresponding types in packages/shared/src/ by hand if the wire\n * contract changes — `src/core/geo-pulse-sdk.test.ts` and\n * `src/firebase/api-client.test.ts` still validate against the real\n * `@geopulse/shared` package (a devDependency, never published) precisely so\n * drift between this copy and the canonical schema gets caught.\n */\n\nexport const SDK_KEY_HEADER = \"x-geopulse-sdk-key\";\n\n/** The configured application id, sent with every request so the backend can reject a key bound to another application. */\nexport const APPLICATION_ID_HEADER = \"x-geopulse-application-id\";\n\nexport const SDK_ENDPOINTS = {\n GET_PROJECT_CONFIG: \"getProjectConfig\",\n INGEST_LOCATION_EVENT: \"ingestLocationEvent\",\n GET_PENDING_EXPERIENCES: \"getPendingExperiences\",\n ACKNOWLEDGE_EXPERIENCE_DELIVERY: \"acknowledgeExperienceDelivery\",\n} as const;\n\nconst EARTH_RADIUS_METERS = 6_371_000;\n\nfunction toRadians(degrees: number): number {\n return (degrees * Math.PI) / 180;\n}\n\n/** Haversine great-circle distance in meters — see packages/shared/src/utils/geo.ts for the full derivation comment. */\nexport function calculateDistanceMeters(\n from: { latitude: number; longitude: number },\n to: { latitude: number; longitude: number },\n): number {\n const lat1 = toRadians(from.latitude);\n const lat2 = toRadians(to.latitude);\n const deltaLat = toRadians(to.latitude - from.latitude);\n const deltaLng = toRadians(to.longitude - from.longitude);\n\n const a =\n Math.sin(deltaLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) ** 2;\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n return EARTH_RADIUS_METERS * c;\n}\n\nexport const GEOFENCE_EVENT_TYPES = [\"geofence.enter\", \"geofence.exit\"] as const;\nexport type GeofenceEventType = (typeof GEOFENCE_EVENT_TYPES)[number];\n\nexport const DEVICE_PLATFORMS = [\"ios\", \"android\", \"web\", \"unknown\"] as const;\nexport type DevicePlatform = (typeof DEVICE_PLATFORMS)[number];\n\nexport const EXPERIENCE_TYPES = [\"banner\", \"card\", \"message\"] as const;\nexport type ExperienceType = (typeof EXPERIENCE_TYPES)[number];\n\nexport const ANCHOR_POINTS = [\n \"top-left\",\n \"top-center\",\n \"top-right\",\n \"center-left\",\n \"center\",\n \"center-right\",\n \"bottom-left\",\n \"bottom-center\",\n \"bottom-right\",\n] as const;\nexport type AnchorPoint = (typeof ANCHOR_POINTS)[number];\n\nexport interface AnchoredPosition {\n anchor: AnchorPoint;\n offsetX?: number | null;\n offsetY?: number | null;\n}\n\nexport interface ContainerStyle {\n backgroundColor?: string | null;\n borderColor?: string | null;\n borderWidth?: number | null;\n borderRadius?: number | null;\n padding?: number | null;\n backgroundImageUrl?: string | null;\n backgroundImageFit?: \"cover\" | \"contain\" | \"stretch\";\n widthMode?: \"fill\" | \"fixed\";\n width?: number | null;\n heightMode?: \"auto\" | \"fixed\";\n height?: number | null;\n}\n\nexport interface TextStyle {\n visible?: boolean;\n color?: string | null;\n fontSize?: number | null;\n fontWeight?: \"normal\" | \"bold\";\n}\n\nexport interface ButtonStyle {\n visible?: boolean;\n backgroundColor?: string | null;\n textColor?: string | null;\n borderRadius?: number | null;\n position?: AnchoredPosition | null;\n}\n\nexport interface ExperienceStyleV2 {\n version: 2;\n container?: ContainerStyle;\n title?: TextStyle;\n body?: TextStyle;\n button?: ButtonStyle;\n}\n\nexport interface ExperienceStyleLegacy {\n backgroundColor: string | null;\n textColor: string | null;\n accentColor: string | null;\n}\n\nexport type ExperienceStyle = ExperienceStyleV2 | ExperienceStyleLegacy;\n\nexport function isLegacyExperienceStyle(style: ExperienceStyle): style is ExperienceStyleLegacy {\n return !(\"version\" in style);\n}\n\nexport interface ResolvedPosition {\n top: number | null;\n bottom: number | null;\n left: number | null;\n right: number | null;\n}\n\nexport interface ResolvedContainerStyle {\n backgroundColor: string;\n borderColor: string | null;\n borderWidth: number;\n borderRadius: number;\n padding: number;\n backgroundImageUrl: string | null;\n backgroundImageFit: \"cover\" | \"contain\" | \"stretch\";\n width: \"fill\" | number;\n height: \"auto\" | number;\n}\n\nexport interface ResolvedTextStyle {\n visible: boolean;\n color: string;\n fontSize: number;\n fontWeight: \"normal\" | \"bold\";\n}\n\nexport interface ResolvedButtonStyle {\n visible: boolean;\n backgroundColor: string;\n textColor: string;\n borderRadius: number;\n position: ResolvedPosition;\n}\n\nexport interface ResolvedExperienceStyle {\n container: ResolvedContainerStyle;\n title: ResolvedTextStyle;\n body: ResolvedTextStyle;\n button: ResolvedButtonStyle;\n}\n\nconst HEX_COLOR_PATTERN = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n/** dp — a generous cap so a malformed/huge offset can't push an element fully off-screen. */\nconst OFFSET_LIMIT = 500;\n\nconst ANCHOR_BASE_POSITIONS: Record<AnchorPoint, ResolvedPosition> = {\n \"top-left\": { top: 0, left: 0, bottom: null, right: null },\n \"top-center\": { top: 0, left: null, bottom: null, right: null },\n \"top-right\": { top: 0, left: null, bottom: null, right: 0 },\n \"center-left\": { top: null, left: 0, bottom: null, right: null },\n center: { top: null, left: null, bottom: null, right: null },\n \"center-right\": { top: null, left: null, bottom: null, right: 0 },\n \"bottom-left\": { top: null, left: 0, bottom: 0, right: null },\n \"bottom-center\": { top: null, left: null, bottom: 0, right: null },\n \"bottom-right\": { top: null, left: null, bottom: 0, right: 0 },\n};\n\nconst DEFAULT_STYLES: Record<ExperienceType, ResolvedExperienceStyle> = {\n card: {\n container: { backgroundColor: \"#ffffff\", borderColor: null, borderWidth: 0, borderRadius: 16, padding: 20, backgroundImageUrl: null, backgroundImageFit: \"cover\", width: \"fill\", height: \"auto\" },\n title: { visible: true, color: \"#111827\", fontSize: 18, fontWeight: \"bold\" },\n body: { visible: true, color: \"#374151\", fontSize: 14, fontWeight: \"normal\" },\n button: { visible: true, backgroundColor: \"#2563eb\", textColor: \"#ffffff\", borderRadius: 8, position: { top: null, bottom: 16, left: 16, right: null } },\n },\n banner: {\n container: { backgroundColor: \"#111827\", borderColor: null, borderWidth: 0, borderRadius: 0, padding: 12, backgroundImageUrl: null, backgroundImageFit: \"cover\", width: \"fill\", height: \"auto\" },\n title: { visible: true, color: \"#ffffff\", fontSize: 15, fontWeight: \"bold\" },\n body: { visible: true, color: \"#e5e7eb\", fontSize: 13, fontWeight: \"normal\" },\n button: { visible: false, backgroundColor: \"#2563eb\", textColor: \"#ffffff\", borderRadius: 6, position: { top: null, bottom: null, left: null, right: 12 } },\n },\n message: {\n container: { backgroundColor: \"#1f2937\", borderColor: null, borderWidth: 0, borderRadius: 12, padding: 16, backgroundImageUrl: null, backgroundImageFit: \"cover\", width: \"fill\", height: \"auto\" },\n title: { visible: false, color: \"#ffffff\", fontSize: 16, fontWeight: \"bold\" },\n body: { visible: true, color: \"#ffffff\", fontSize: 14, fontWeight: \"normal\" },\n button: { visible: false, backgroundColor: \"#2563eb\", textColor: \"#ffffff\", borderRadius: 8, position: { top: null, bottom: 16, left: null, right: 16 } },\n },\n};\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction resolveColor(value: string | null | undefined, fallback: string): string {\n return typeof value === \"string\" && HEX_COLOR_PATTERN.test(value) ? value : fallback;\n}\n\nfunction resolveOptionalColor(value: string | null | undefined, fallback: string | null): string | null {\n return typeof value === \"string\" && HEX_COLOR_PATTERN.test(value) ? value : fallback;\n}\n\nfunction resolveNonNegativeNumber(value: number | null | undefined, fallback: number): number {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : fallback;\n}\n\nfunction resolvePosition(position: AnchoredPosition | null | undefined, fallback: ResolvedPosition): ResolvedPosition {\n if (!position || !ANCHOR_POINTS.includes(position.anchor)) return fallback;\n const offsetX = typeof position.offsetX === \"number\" && Number.isFinite(position.offsetX) ? clamp(position.offsetX, -OFFSET_LIMIT, OFFSET_LIMIT) : 0;\n const offsetY = typeof position.offsetY === \"number\" && Number.isFinite(position.offsetY) ? clamp(position.offsetY, -OFFSET_LIMIT, OFFSET_LIMIT) : 0;\n const base = ANCHOR_BASE_POSITIONS[position.anchor];\n return {\n top: base.top === null ? null : base.top + offsetY,\n bottom: base.bottom === null ? null : base.bottom + offsetY,\n left: base.left === null ? null : base.left + offsetX,\n right: base.right === null ? null : base.right + offsetX,\n };\n}\n\nfunction resolveWidth(mode: \"fill\" | \"fixed\" | undefined, value: number | null | undefined, fallback: \"fill\" | number): \"fill\" | number {\n if (mode === \"fixed\") {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : fallback;\n }\n if (mode === \"fill\") return \"fill\";\n return fallback;\n}\n\nfunction resolveHeight(mode: \"auto\" | \"fixed\" | undefined, value: number | null | undefined, fallback: \"auto\" | number): \"auto\" | number {\n if (mode === \"fixed\") {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : fallback;\n }\n if (mode === \"auto\") return \"auto\";\n return fallback;\n}\n\nfunction resolveContainer(custom: ContainerStyle | undefined, fallback: ResolvedContainerStyle): ResolvedContainerStyle {\n return {\n backgroundColor: resolveColor(custom?.backgroundColor, fallback.backgroundColor),\n borderColor: resolveOptionalColor(custom?.borderColor, fallback.borderColor),\n borderWidth: resolveNonNegativeNumber(custom?.borderWidth, fallback.borderWidth),\n borderRadius: resolveNonNegativeNumber(custom?.borderRadius, fallback.borderRadius),\n padding: resolveNonNegativeNumber(custom?.padding, fallback.padding),\n backgroundImageUrl: typeof custom?.backgroundImageUrl === \"string\" && custom.backgroundImageUrl.length > 0 ? custom.backgroundImageUrl : fallback.backgroundImageUrl,\n backgroundImageFit: custom?.backgroundImageFit ?? fallback.backgroundImageFit,\n width: resolveWidth(custom?.widthMode, custom?.width, fallback.width),\n height: resolveHeight(custom?.heightMode, custom?.height, fallback.height),\n };\n}\n\nfunction resolveText(custom: TextStyle | undefined, fallback: ResolvedTextStyle): ResolvedTextStyle {\n return {\n visible: custom?.visible ?? fallback.visible,\n color: resolveColor(custom?.color, fallback.color),\n fontSize: resolveNonNegativeNumber(custom?.fontSize, fallback.fontSize),\n fontWeight: custom?.fontWeight ?? fallback.fontWeight,\n };\n}\n\nfunction resolveButton(custom: ButtonStyle | undefined, fallback: ResolvedButtonStyle): ResolvedButtonStyle {\n return {\n visible: custom?.visible ?? fallback.visible,\n backgroundColor: resolveColor(custom?.backgroundColor, fallback.backgroundColor),\n textColor: resolveColor(custom?.textColor, fallback.textColor),\n borderRadius: resolveNonNegativeNumber(custom?.borderRadius, fallback.borderRadius),\n position: resolvePosition(custom?.position, fallback.position),\n };\n}\n\n/** Maps today's flat 3-color shape onto the v2 groups it corresponds to. */\nfunction upgradeLegacyStyle(legacy: ExperienceStyleLegacy): ExperienceStyleV2 {\n return {\n version: 2,\n container: { backgroundColor: legacy.backgroundColor },\n title: { color: legacy.textColor },\n body: { color: legacy.textColor },\n button: { backgroundColor: legacy.accentColor },\n };\n}\n\n/**\n * Cascades a type's built-in default under a campaign's custom style,\n * upgrading today's legacy 3-color shape on the fly, and resolves 9-point\n * anchor positions to concrete offsets. Never throws — any malformed or\n * missing field silently falls back to the type's default.\n */\nexport function resolveExperienceStyle(type: ExperienceType, custom: ExperienceStyle | null | undefined): ResolvedExperienceStyle {\n const fallback = DEFAULT_STYLES[type] ?? DEFAULT_STYLES.card;\n if (!custom) return fallback;\n const v2 = isLegacyExperienceStyle(custom) ? upgradeLegacyStyle(custom) : custom;\n return {\n container: resolveContainer(v2.container, fallback.container),\n title: resolveText(v2.title, fallback.title),\n body: resolveText(v2.body, fallback.body),\n button: resolveButton(v2.button, fallback.button),\n };\n}\n\nexport interface GeofenceCenter {\n latitude: number;\n longitude: number;\n}\n\nexport interface SdkGeofenceConfig {\n id: string;\n name: string;\n locationId: string;\n locationName: string;\n center: GeofenceCenter;\n radiusMeters: number;\n}\n\nexport interface SdkProjectConfig {\n applicationId: string;\n projectId: string;\n applicationEnabled: boolean;\n configVersion: string;\n geofences: SdkGeofenceConfig[];\n}\n\nexport interface LocationEventPayload {\n id: string;\n type: GeofenceEventType;\n projectId: string;\n applicationId: string;\n geofenceId: string;\n locationId: string;\n deviceId: string;\n timestamp: string;\n latitude: number;\n longitude: number;\n metadata: Record<string, string>;\n sdkVersion: string;\n attempts: number;\n}\n\nexport interface ExperienceDeliveryPayload {\n type: \"location.experience\";\n deliveryId: string;\n campaignId: string;\n /** Changes when the experience's content is edited; absent on older backends. */\n revision?: number;\n /** Set by the SDK, never the backend: true when this replaces content the user was already shown for the same delivery. */\n isUpdate?: boolean;\n experience: {\n id: string;\n type: ExperienceType;\n title: string;\n body: string;\n imageUrl: string | null;\n cardNavigationUrl: string | null;\n ctaLabel: string | null;\n ctaUrl: string | null;\n style: ExperienceStyle | null;\n };\n event: {\n id: string;\n type: GeofenceEventType;\n geofenceId: string;\n timestamp: string;\n };\n}\n\n/** A delivery the user was already shown that the app must now remove (its campaign was deleted or disabled). */\nexport interface RevokedExperience {\n deliveryId: string;\n campaignId: string;\n}\n\n/** Response body of `getPendingExperiences`; `revoked` is absent on older backends. */\nexport interface PendingExperiencesResponse {\n experiences: ExperienceDeliveryPayload[];\n revoked?: RevokedExperience[];\n}\n","import { APPLICATION_ID_HEADER, SDK_ENDPOINTS, SDK_KEY_HEADER } from \"../protocol\";\nimport type { DevicePlatform, LocationEventPayload, PendingExperiencesResponse, SdkProjectConfig } from \"../protocol\";\n\nexport interface DeviceIdentity {\n deviceId: string;\n platform: DevicePlatform;\n sdkVersion: string;\n}\n\n/** The longest pause a `Retry-After` can ask of the SDK. The server may say \"tomorrow\" for a daily budget; the SDK checks again sooner. */\nexport const MAX_RETRY_AFTER_MS = 15 * 60_000;\n\n/** A non-2xx answer. `retryAfterMs` comes from the `Retry-After` header (seconds) on a 429. The message keeps the \"<call> failed with status N\" form. */\nexport class ApiError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly retryAfterMs: number | null = null,\n ) {\n super(message);\n this.name = \"ApiError\";\n }\n}\n\nfunction retryAfterMsFrom(response: Response): number | null {\n const seconds = Number(response.headers?.get?.(\"retry-after\"));\n return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds * 1000, MAX_RETRY_AFTER_MS) : null;\n}\n\nfunction failure(callName: string, response: Response): ApiError {\n return new ApiError(`${callName} failed with status ${response.status}`, response.status, response.status === 429 ? retryAfterMsFrom(response) : null);\n}\n\nexport interface IngestResult {\n id: string;\n deduplicated: boolean;\n /** False when the backend created no experience for this event, so polling for one would find nothing. Absent on older backends — treat as \"maybe\". */\n experiencesPending?: boolean;\n}\n\n/**\n * Without this, a request to an unresponsive/hung backend (e.g. a local\n * emulator that was restarted mid-request, or a real network stall) would\n * hang forever — `fetch` has no default timeout. That leaves an event\n * stuck `pending` indefinitely: `SyncManager.attemptDelivery`'s retry/backoff\n * logic only runs once a request actually *fails*, so a request that never\n * settles at all never gets retried either. Aborting after this long\n * converts \"hung forever\" into \"failed now,\" which the existing queue\n * already knows how to handle.\n */\nconst REQUEST_TIMEOUT_MS = 15_000;\n\nasync function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * A non-2xx response already throws a clear, endpoint-named error below —\n * but `fetch` itself rejecting (offline, DNS failure, or `fetchWithTimeout`'s\n * own abort) previously propagated as a bare, unattributed error like\n * \"Aborted\" or \"Network request failed\", with nothing indicating which SDK\n * call it came from (Phase 7 SDK audit finding). This wraps that case with\n * the same \"which call failed\" context the HTTP-error path already has,\n * without changing what the underlying error actually was — `error.message`\n * is still included verbatim.\n */\nasync function fetchOrThrow(callName: string, url: string, init: RequestInit): Promise<Response> {\n try {\n return await fetchWithTimeout(url, init);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`${callName} request failed: ${message}`);\n }\n}\n\n/**\n * Named `firebase/` per this project's spec (\"communication with GeoPulse\n * backend\"), but deliberately does NOT use the Firebase client SDK — it\n * talks to the Cloud Functions HTTPS endpoints over plain `fetch` (built\n * into both React Native's JS runtime and Node 18+, so this file is\n * testable without any RN runtime at all). That keeps the SDK's dependency\n * footprint small and means device requests carry only the app-scoped SDK\n * key, never a Firebase credential of any kind.\n */\nexport class ApiClient {\n constructor(\n private readonly baseUrl: string,\n private readonly sdkKey: string,\n /** Optional so existing callers keep working; when set, the backend rejects a key bound to a different application. */\n private readonly applicationId?: string,\n ) {}\n\n private headers(json = false): Record<string, string> {\n return {\n [SDK_KEY_HEADER]: this.sdkKey,\n ...(this.applicationId ? { [APPLICATION_ID_HEADER]: this.applicationId } : {}),\n ...(json ? { \"Content-Type\": \"application/json\" } : {}),\n };\n }\n\n /**\n * `identity` doubles as this device's registration/heartbeat (see\n * functions/src/events/get-project-config.ts) — sent as query params\n * since GET requests carry no body.\n */\n async getProjectConfig(identity: DeviceIdentity): Promise<SdkProjectConfig> {\n const query = new URLSearchParams({\n deviceId: identity.deviceId,\n platform: identity.platform,\n sdkVersion: identity.sdkVersion,\n });\n const response = await fetchOrThrow(\"getProjectConfig\", `${this.baseUrl}/${SDK_ENDPOINTS.GET_PROJECT_CONFIG}?${query.toString()}`, {\n method: \"GET\",\n headers: this.headers(),\n });\n if (!response.ok) {\n throw failure(\"getProjectConfig\", response);\n }\n return (await response.json()) as SdkProjectConfig;\n }\n\n async ingestEvent(payload: LocationEventPayload): Promise<IngestResult> {\n const response = await fetchOrThrow(\"ingestLocationEvent\", `${this.baseUrl}/${SDK_ENDPOINTS.INGEST_LOCATION_EVENT}`, {\n method: \"POST\",\n headers: this.headers(true),\n body: JSON.stringify(payload),\n });\n if (!response.ok) {\n throw failure(\"ingestLocationEvent\", response);\n }\n return (await response.json()) as IngestResult;\n }\n\n /** Polled by GeoPulseSdk's experience-polling loop — see core/geo-pulse-sdk.ts. */\n async getPendingExperiences(deviceId: string): Promise<Required<PendingExperiencesResponse>> {\n const query = new URLSearchParams({ deviceId });\n const response = await fetchOrThrow(\"getPendingExperiences\", `${this.baseUrl}/${SDK_ENDPOINTS.GET_PENDING_EXPERIENCES}?${query.toString()}`, {\n method: \"GET\",\n headers: this.headers(),\n });\n if (!response.ok) {\n throw failure(\"getPendingExperiences\", response);\n }\n const body = (await response.json()) as PendingExperiencesResponse;\n // An older backend sends no `revoked`.\n return { experiences: body.experiences ?? [], revoked: body.revoked ?? [] };\n }\n\n /** Confirms a delivery was surfaced via `experience.received`, so it stops appearing in future polls. */\n async acknowledgeExperienceDelivery(deliveryId: string): Promise<void> {\n const response = await fetchOrThrow(\"acknowledgeExperienceDelivery\", `${this.baseUrl}/${SDK_ENDPOINTS.ACKNOWLEDGE_EXPERIENCE_DELIVERY}`, {\n method: \"POST\",\n headers: this.headers(true),\n body: JSON.stringify({ deliveryId }),\n });\n if (!response.ok) {\n throw failure(\"acknowledgeExperienceDelivery\", response);\n }\n }\n}\n","import type { GeofenceEventType, LocationEventPayload, SdkGeofenceConfig } from \"../protocol\";\n\nimport { SDK_VERSION } from \"../config/types\";\nimport { generateEventId } from \"./uuid\";\n\nexport function createEventPayload(params: {\n type: GeofenceEventType;\n projectId: string;\n applicationId: string;\n geofence: SdkGeofenceConfig;\n location: { latitude: number; longitude: number };\n deviceId: string;\n metadata?: Record<string, string>;\n}): LocationEventPayload {\n return {\n id: generateEventId(),\n type: params.type,\n projectId: params.projectId,\n applicationId: params.applicationId,\n geofenceId: params.geofence.id,\n locationId: params.geofence.locationId,\n deviceId: params.deviceId,\n timestamp: new Date().toISOString(),\n latitude: params.location.latitude,\n longitude: params.location.longitude,\n metadata: params.metadata ?? {},\n sdkVersion: SDK_VERSION,\n // First-try attempt count; SyncManager overwrites this with the real\n // count if the event ends up going through the retry queue (see\n // sync/sync-manager.ts#retryPending) — that's what makes the\n // \"Attempts\" column in the admin's Event Explorer meaningful.\n attempts: 1,\n };\n}\n","import { calculateDistanceMeters } from \"../protocol\";\nimport type { GeofenceEventType, SdkGeofenceConfig } from \"../protocol\";\n\nexport type GeofenceMembership = \"INSIDE\" | \"OUTSIDE\";\n\nexport interface GeofenceTransition {\n geofence: SdkGeofenceConfig;\n type: GeofenceEventType;\n}\n\nexport interface StateMachineOptions {\n /** How far past the edge a position must be before an exit counts (GPS jitter at the boundary). Default 20 m, or the fix's accuracy if larger. */\n exitBufferMeters?: number;\n /** Fixes less accurate than this are ignored: they cannot tell inside from outside. Default 100 m. */\n maxAccuracyMeters?: number;\n /** Minimum time between two transitions of the same geofence. Default 2 minutes. */\n cooldownMs?: number;\n now?: () => number;\n}\n\n/** What survives an app restart, so reopening the app while still inside does not report a second \"enter\". */\nexport interface PersistedGeofenceState {\n membership: Record<string, GeofenceMembership>;\n lastTransitionAt: Record<string, number>;\n}\n\nexport const DEFAULT_EXIT_BUFFER_METERS = 20;\nexport const DEFAULT_MAX_ACCURACY_METERS = 100;\nexport const DEFAULT_TRANSITION_COOLDOWN_MS = 2 * 60_000;\n\n/**\n * Tracks INSIDE/OUTSIDE membership per geofence and emits a transition only\n * when membership actually changes:\n *\n * OUTSIDE -> INSIDE = geofence.enter (at distance <= radius)\n * INSIDE -> OUTSIDE = geofence.exit (only beyond radius + buffer)\n * INSIDE -> INSIDE = nothing\n * OUTSIDE -> OUTSIDE = nothing\n *\n * Standing near an edge makes GPS readings wander a few metres either side of it. Without protection that would send\n * an enter/exit pair every few seconds, so three things hold it back: the exit buffer (hysteresis: you must clearly\n * leave), ignoring low-accuracy fixes, and a per-geofence cooldown. A transition held back by the cooldown leaves\n * membership unchanged, so the next fix after the cooldown re-evaluates it; nothing is lost or stuck.\n *\n * A geofence with no prior recorded state is treated as OUTSIDE (so the very first location update that lands inside\n * a geofence correctly fires one `geofence.enter`, not silence). State survives across repeated `evaluate()` calls\n * precisely so a location update that keeps the device inside the same geofence produces no duplicate ENTER.\n */\nexport class GeofenceStateMachine {\n private readonly membershipByGeofenceId = new Map<string, GeofenceMembership>();\n private readonly lastTransitionAtByGeofenceId = new Map<string, number>();\n private readonly exitBufferMeters: number;\n private readonly maxAccuracyMeters: number;\n private readonly cooldownMs: number;\n private readonly now: () => number;\n\n constructor(options: StateMachineOptions = {}) {\n this.exitBufferMeters = options.exitBufferMeters ?? DEFAULT_EXIT_BUFFER_METERS;\n this.maxAccuracyMeters = options.maxAccuracyMeters ?? DEFAULT_MAX_ACCURACY_METERS;\n this.cooldownMs = options.cooldownMs ?? DEFAULT_TRANSITION_COOLDOWN_MS;\n this.now = options.now ?? Date.now;\n }\n\n evaluate(location: { latitude: number; longitude: number; accuracy?: number }, geofences: SdkGeofenceConfig[]): GeofenceTransition[] {\n // A fix this vague cannot say which side of an edge we are on; keep everything as it was.\n if (location.accuracy !== undefined && location.accuracy > this.maxAccuracyMeters) {\n return [];\n }\n\n const transitions: GeofenceTransition[] = [];\n const activeIds = new Set<string>();\n const exitBuffer = Math.max(this.exitBufferMeters, location.accuracy ?? 0);\n const now = this.now();\n\n for (const geofence of geofences) {\n activeIds.add(geofence.id);\n\n const distance = calculateDistanceMeters(location, geofence.center);\n const previousMembership = this.membershipByGeofenceId.get(geofence.id) ?? \"OUTSIDE\";\n const nextMembership: GeofenceMembership =\n previousMembership === \"INSIDE\"\n ? distance > geofence.radiusMeters + exitBuffer\n ? \"OUTSIDE\"\n : \"INSIDE\"\n : distance <= geofence.radiusMeters\n ? \"INSIDE\"\n : \"OUTSIDE\";\n\n if (previousMembership === nextMembership) {\n this.membershipByGeofenceId.set(geofence.id, nextMembership);\n continue;\n }\n\n const lastTransitionAt = this.lastTransitionAtByGeofenceId.get(geofence.id);\n if (lastTransitionAt !== undefined && now - lastTransitionAt < this.cooldownMs) {\n // Too soon after the last one: stay as we were, decide again on a later fix.\n this.membershipByGeofenceId.set(geofence.id, previousMembership);\n continue;\n }\n\n this.membershipByGeofenceId.set(geofence.id, nextMembership);\n this.lastTransitionAtByGeofenceId.set(geofence.id, now);\n transitions.push({ geofence, type: nextMembership === \"INSIDE\" ? \"geofence.enter\" : \"geofence.exit\" });\n }\n\n // Drop bookkeeping for geofences no longer in the active config (e.g.\n // deleted/disabled since the last `getProjectConfig` fetch) so it can't\n // silently grow forever or produce a stale transition if the same\n // geofence id ever reappears later.\n for (const id of [...this.membershipByGeofenceId.keys()]) {\n if (!activeIds.has(id)) {\n this.membershipByGeofenceId.delete(id);\n this.lastTransitionAtByGeofenceId.delete(id);\n }\n }\n\n return transitions;\n }\n\n getMembership(geofenceId: string): GeofenceMembership {\n return this.membershipByGeofenceId.get(geofenceId) ?? \"OUTSIDE\";\n }\n\n isInsideAny(): boolean {\n for (const membership of this.membershipByGeofenceId.values()) {\n if (membership === \"INSIDE\") return true;\n }\n return false;\n }\n\n exportState(): PersistedGeofenceState {\n return {\n membership: Object.fromEntries(this.membershipByGeofenceId),\n lastTransitionAt: Object.fromEntries(this.lastTransitionAtByGeofenceId),\n };\n }\n\n /** Only for a machine that has not evaluated anything yet. */\n restoreState(state: PersistedGeofenceState): void {\n this.membershipByGeofenceId.clear();\n this.lastTransitionAtByGeofenceId.clear();\n for (const [id, membership] of Object.entries(state.membership)) {\n if (membership === \"INSIDE\" || membership === \"OUTSIDE\") this.membershipByGeofenceId.set(id, membership);\n }\n for (const [id, at] of Object.entries(state.lastTransitionAt)) {\n if (typeof at === \"number\" && Number.isFinite(at)) this.lastTransitionAtByGeofenceId.set(id, at);\n }\n }\n\n reset(): void {\n this.membershipByGeofenceId.clear();\n this.lastTransitionAtByGeofenceId.clear();\n }\n}\n","import type { KeyValueStore } from \"../config/key-value-store\";\nimport type { PersistedGeofenceState } from \"./state-machine\";\n\nconst STORAGE_KEY = \"@geopulse/geofence-state\";\n\n/**\n * Older than this and the saved state is ignored. The server also stops counting a device as inside after 12 hours without\n * an event, so a person who is still there a day later is reported as entering again, which refreshes both sides.\n */\nexport const GEOFENCE_STATE_MAX_AGE_MS = 12 * 60 * 60 * 1000;\n\ninterface Saved extends PersistedGeofenceState {\n savedAt: number;\n}\n\n/** Keeps the state machine's memory across app restarts. A failure to read or write is never fatal: the machine just starts fresh. */\nexport class GeofenceStateStore {\n constructor(\n private readonly store: KeyValueStore,\n private readonly now: () => number = Date.now,\n ) {}\n\n async load(): Promise<PersistedGeofenceState | null> {\n try {\n const raw = await this.store.getItem(STORAGE_KEY);\n if (!raw) return null;\n const saved = JSON.parse(raw) as Partial<Saved>;\n if (typeof saved.savedAt !== \"number\" || this.now() - saved.savedAt > GEOFENCE_STATE_MAX_AGE_MS) return null;\n if (typeof saved.membership !== \"object\" || saved.membership === null) return null;\n return { membership: saved.membership, lastTransitionAt: saved.lastTransitionAt ?? {} };\n } catch {\n return null;\n }\n }\n\n async save(state: PersistedGeofenceState): Promise<void> {\n try {\n await this.store.setItem(STORAGE_KEY, JSON.stringify({ ...state, savedAt: this.now() } satisfies Saved));\n } catch {\n // Losing this only means one extra \"enter\" after the next restart.\n }\n }\n}\n","import type { LocationProvider, LocationUpdate } from \"./types\";\n\n/**\n * The mandatory simulation provider (spec section 7) — lets the geofence\n * flow be exercised end to end without physically travelling anywhere.\n * `setLocation` is the playground simulator screen's only way to move the\n * \"user\"; everything downstream (geofence engine, event generation, sync)\n * runs exactly as it would for a real device update, because both\n * providers speak the same `LocationProvider` interface.\n */\nexport class SimulatedLocationProvider implements LocationProvider {\n private onUpdate: ((location: LocationUpdate) => void) | null = null;\n private currentLocation: LocationUpdate | null = null;\n\n start(onUpdate: (location: LocationUpdate) => void): void {\n this.onUpdate = onUpdate;\n // If a location was already set before `start()` (e.g. the simulator\n // screen was positioned before pressing \"Start\"), replay it immediately\n // rather than waiting for the next `setLocation` call.\n if (this.currentLocation) {\n onUpdate(this.currentLocation);\n }\n }\n\n stop(): void {\n this.onUpdate = null;\n }\n\n setLocation(coords: { latitude: number; longitude: number }): void {\n const location: LocationUpdate = { ...coords, timestamp: new Date().toISOString() };\n this.currentLocation = location;\n this.onUpdate?.(location);\n }\n\n getCurrentLocation(): LocationUpdate | null {\n return this.currentLocation;\n }\n}\n","import type { PermissionProvider, PermissionStatus } from \"./types\";\n\n/**\n * Used with the simulated location provider (see ../location) — a\n * simulated user obviously isn't gated by the OS location permission\n * dialog, so this always reports \"granted\" rather than requiring a real\n * device's permission state to test the geofence flow end to end.\n */\nexport class SimulatedPermissionProvider implements PermissionProvider {\n async getStatus(): Promise<PermissionStatus> {\n return \"granted\";\n }\n\n async request(): Promise<PermissionStatus> {\n return \"granted\";\n }\n}\n","/**\n * Centralized so retry behavior can be tuned in one place instead of\n * scattered magic numbers — see spec section 4 (\"Do not hard-code these\n * values throughout the codebase\").\n */\nexport const RETRY_POLICY = {\n /** Attempts beyond this stop increasing the delay — it plateaus at MAX_RETRY_DELAY_MS, but retrying never stops entirely (the SDK must never give up on an event; see storage/queue.ts). */\n MAX_RETRIES: 6,\n BASE_RETRY_DELAY_MS: 5_000,\n MAX_RETRY_DELAY_MS: 5 * 60_000,\n} as const;\n\n/**\n * Exponential backoff with a ceiling: attempt 1 -> ~5s, 2 -> ~10s, 3 -> ~20s,\n * ... capped at MAX_RETRY_DELAY_MS once `attempts` passes MAX_RETRIES. A\n * small jitter (±20%) avoids every queued event on a device — or every\n * device that lost connectivity at the same moment — retrying in lockstep.\n */\nexport function computeRetryDelayMs(attempts: number): number {\n const cappedAttempts = Math.min(attempts, RETRY_POLICY.MAX_RETRIES);\n const exponential = RETRY_POLICY.BASE_RETRY_DELAY_MS * 2 ** Math.max(0, cappedAttempts - 1);\n const delay = Math.min(exponential, RETRY_POLICY.MAX_RETRY_DELAY_MS);\n const jitter = delay * 0.2 * (Math.random() * 2 - 1);\n return Math.round(delay + jitter);\n}\n","import type { EventQueueStore, QueuedEvent } from \"./types\";\n\n/** Default store — also what unit tests use, since it needs no RN runtime. */\nexport class InMemoryEventQueueStore implements EventQueueStore {\n private readonly eventsById = new Map<string, QueuedEvent>();\n\n async getAll(): Promise<QueuedEvent[]> {\n return [...this.eventsById.values()];\n }\n\n async save(event: QueuedEvent): Promise<void> {\n this.eventsById.set(event.eventId, event);\n }\n\n async remove(eventId: string): Promise<void> {\n this.eventsById.delete(eventId);\n }\n}\n","import type { LocationEventPayload } from \"../protocol\";\n\nimport { computeRetryDelayMs } from \"../sync/retry-policy\";\nimport { InMemoryEventQueueStore } from \"./memory-store\";\nimport type { EventQueueStore, QueuedEvent } from \"./types\";\n\n/** Most events kept at once. A phone offline for long enough to exceed this drops its oldest, not its newest. */\nexport const MAX_QUEUE_SIZE = 100;\n/** An event older than this is not sent: the backend rejects timestamps older than a day, so it could never be delivered. */\nexport const MAX_QUEUED_EVENT_AGE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * The local pending-event queue (spec: \"Offline queue\" / \"reliable SDK\n * event queue\"). An event is only ever in this queue between a failed send\n * attempt and a successful retry — there's no \"delivered\" status because a\n * delivered event is simply removed (`markSent`), which is also what makes\n * retrying an already-sent event harmless: it's not in the queue to retry.\n *\n * Every operation runs one at a time. The store keeps the whole queue in one value, so each change is read, modify,\n * write; two overlapping ones (a retry finishing while a new event is queued) would otherwise overwrite each other and\n * lose an event.\n */\nexport class PendingEventQueue {\n private chain: Promise<unknown> = Promise.resolve();\n\n constructor(private readonly store: EventQueueStore = new InMemoryEventQueueStore()) {}\n\n private serialized<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.chain.then(operation, operation);\n this.chain = result.catch(() => undefined);\n return result;\n }\n\n enqueue(payload: LocationEventPayload): Promise<void> {\n return this.serialized(async () => {\n await this.store.save({\n eventId: payload.id,\n type: payload.type,\n applicationId: payload.applicationId,\n deviceId: payload.deviceId,\n locationId: payload.locationId,\n geofenceId: payload.geofenceId,\n timestamp: payload.timestamp,\n payload,\n attempts: 0,\n status: \"pending\",\n createdAt: new Date().toISOString(),\n lastAttemptAt: null,\n nextAttemptAt: null,\n });\n await this.trim();\n });\n }\n\n /** Every queued event, regardless of retry timing — used for diagnostics/pending counts. */\n listPending(): Promise<QueuedEvent[]> {\n return this.serialized(() => this.store.getAll());\n }\n\n /** Only events whose backoff window has elapsed (or that have never been attempted) — used by SyncManager.retryPending(). Drops events too old to ever be accepted. */\n listReadyForRetry(now: Date = new Date()): Promise<QueuedEvent[]> {\n return this.serialized(async () => {\n let all = await this.store.getAll();\n for (const event of all.filter((candidate) => isTooOld(candidate, now.getTime()))) {\n await this.store.remove(event.eventId);\n }\n all = all.filter((event) => !isTooOld(event, now.getTime()));\n return all.filter((event) => event.status !== \"sending\" && (!event.nextAttemptAt || new Date(event.nextAttemptAt) <= now));\n });\n }\n\n /** Marks an event as in-flight so a concurrent retry pass won't also pick it up. */\n markSending(eventId: string): Promise<void> {\n return this.serialized(async () => {\n const existing = await this.get(eventId);\n if (!existing) return;\n await this.store.save({ ...existing, status: \"sending\" });\n });\n }\n\n /** `minDelayMs` lets a 429's Retry-After push the next attempt later than the normal backoff. */\n markAttemptFailed(eventId: string, minDelayMs = 0): Promise<void> {\n return this.serialized(async () => {\n const existing = await this.get(eventId);\n if (!existing) return;\n const attempts = existing.attempts + 1;\n const now = new Date();\n await this.store.save({\n ...existing,\n status: \"failed\",\n attempts,\n lastAttemptAt: now.toISOString(),\n nextAttemptAt: new Date(now.getTime() + Math.max(computeRetryDelayMs(attempts), minDelayMs)).toISOString(),\n });\n });\n }\n\n markSent(eventId: string): Promise<void> {\n return this.serialized(() => this.store.remove(eventId));\n }\n\n private async get(eventId: string): Promise<QueuedEvent | undefined> {\n return (await this.store.getAll()).find((event) => event.eventId === eventId);\n }\n\n /** Keeps the queue within MAX_QUEUE_SIZE by dropping the oldest events that are not being sent right now. */\n private async trim(): Promise<void> {\n const all = await this.store.getAll();\n if (all.length <= MAX_QUEUE_SIZE) return;\n const droppable = all\n .filter((event) => event.status !== \"sending\")\n .sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));\n for (const event of droppable.slice(0, all.length - MAX_QUEUE_SIZE)) {\n await this.store.remove(event.eventId);\n }\n }\n}\n\nfunction isTooOld(event: QueuedEvent, nowMs: number): boolean {\n const eventTime = Date.parse(event.timestamp);\n return !Number.isNaN(eventTime) && nowMs - eventTime > MAX_QUEUED_EVENT_AGE_MS;\n}\n","export type NetworkStatus = \"online\" | \"offline\" | \"unknown\";\n\nexport interface NetworkMonitor {\n getStatus(): NetworkStatus;\n /** Calls `listener` whenever connectivity changes. Returns an unsubscribe function. */\n subscribe(listener: (status: NetworkStatus) => void): () => void;\n}\n\n/**\n * Default/test implementation — starts \"online\" (optimistic default: if we\n * can't observe connectivity, assume the network call itself is the\n * ultimate source of truth rather than blocking on a monitor). Tests (and\n * the Node/non-RN environment generally) drive it explicitly via\n * `setStatus`; see `sync/netinfo-network-monitor.ts` for the real RN\n * implementation, kept in its own file for the same reason\n * `storage/async-storage-store.ts` is — see that file's comment.\n */\nexport class ManualNetworkMonitor implements NetworkMonitor {\n private status: NetworkStatus = \"online\";\n private readonly listeners = new Set<(status: NetworkStatus) => void>();\n\n getStatus(): NetworkStatus {\n return this.status;\n }\n\n setStatus(status: NetworkStatus): void {\n if (status === this.status) return;\n this.status = status;\n this.listeners.forEach((listener) => listener(status));\n }\n\n subscribe(listener: (status: NetworkStatus) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n}\n","import type { LocationEventPayload } from \"../protocol\";\n\nimport { ApiError, MAX_RETRY_AFTER_MS, type ApiClient, type IngestResult } from \"../firebase/api-client\";\nimport type { Logger } from \"../core/logger\";\nimport type { PendingEventQueue } from \"../storage/queue\";\n\nconst DEFAULT_RETRY_INTERVAL_MS = 30_000;\n\n/**\n * At most this many uploads per minute from one device, counting first attempts and retries. A person crosses a geofence\n * edge a few times an hour; this is a backstop against a loop, and it keeps a phone that was offline from flushing a\n * backlog in one burst (the rest waits for the next retry tick).\n */\nexport const MAX_UPLOADS_PER_MINUTE = 3;\n/** Even when the network flaps and reports \"online\" repeatedly, a forced retry pass runs at most this often. */\nexport const FORCED_RETRY_MIN_GAP_MS = 10_000;\n/** How long to stop sending after the backend says the application is disabled (403). */\nconst FORBIDDEN_PAUSE_MS = MAX_RETRY_AFTER_MS;\n/** Used when a 429 carries no Retry-After. */\nconst DEFAULT_RATE_LIMIT_PAUSE_MS = 60_000;\n\n/**\n * Only the one method SyncManager actually calls — `Pick` rather than the\n * full `ApiClient` class specifically so tests can pass a plain object\n * (`{ ingestEvent: vi.fn() }`) instead of a real ApiClient instance. A\n * class's private fields make it nominally typed, so a plain object\n * wouldn't satisfy `ApiClient` itself even with matching public methods.\n */\ntype EventIngestionClient = Pick<ApiClient, \"ingestEvent\">;\n\nexport interface SyncResult {\n attempted: number;\n delivered: number;\n failed: number;\n}\n\n/**\n * Send-or-queue plus backoff-aware retry (spec: \"Event retry system\").\n * `retryPending()` only attempts events whose backoff window has elapsed\n * (see PendingEventQueue.listReadyForRetry/RETRY_POLICY) — it's still safe\n * to call as often as you like (on a timer, on reconnect, or manually);\n * events not yet due just get skipped this pass. A successful resend of an\n * already-delivered event is also a no-op server-side, since\n * `ingestLocationEvent` dedupes on the event's own id — so calling this\n * concurrently with an in-flight `sendOrQueue` is never harmful, only\n * occasionally redundant.\n */\nexport class SyncManager {\n private retryTimer: ReturnType<typeof setInterval> | null = null;\n private lastSyncAt: string | null = null;\n private lastUploadResult: \"success\" | \"failure\" | null = null;\n private uploadTimes: number[] = [];\n private pausedUntil = 0;\n private lastForcedRetryAt = 0;\n\n constructor(\n private readonly apiClient: EventIngestionClient,\n private readonly queue: PendingEventQueue,\n private readonly retryIntervalMs: number = DEFAULT_RETRY_INTERVAL_MS,\n private readonly logger?: Logger,\n /** Called after every successful upload (first try or retry) with the backend's response. */\n private readonly onDelivered?: (result: IngestResult | undefined) => void,\n ) {}\n\n /**\n * Write-ahead: the event is durably queued *before* any network attempt is\n * made, so an app kill mid-request can never lose it (spec: \"true\n * write-ahead event persistence\" — a prior version of this method tried\n * the network first and only queued on failure, which meant an event in\n * flight when the process died was gone for good). `attemptDelivery` then\n * runs the exact same success/failure bookkeeping a queued retry uses, so\n * there is only one code path for \"what happens when an upload settles.\"\n */\n async sendOrQueue(payload: LocationEventPayload): Promise<void> {\n await this.queue.enqueue(payload);\n // Over the per-minute cap or told to wait: it is safely queued and the retry timer sends it later.\n if (!this.canUploadNow()) {\n this.logger?.log(\"Upload deferred (rate cap or server asked to wait)\", { eventId: payload.id });\n return;\n }\n await this.attemptDelivery(payload.id, payload);\n }\n\n private canUploadNow(): boolean {\n const now = Date.now();\n if (now < this.pausedUntil) return false;\n this.uploadTimes = this.uploadTimes.filter((at) => now - at < 60_000);\n return this.uploadTimes.length < MAX_UPLOADS_PER_MINUTE;\n }\n\n /**\n * Attempts queued events. By default only ones whose backoff window has\n * elapsed (the timer-driven path, `startAutoRetry` — the whole point of\n * backoff is to not hammer the backend on a fixed sweep). Pass\n * `{ force: true }` to ignore that window and attempt every non-in-flight\n * queued event regardless — connectivity just being confirmed restored\n * (see `GeoPulseSdk.start`'s network-monitor subscription) is a stronger\n * signal than \"some fixed delay has passed,\" and a device that was\n * offline for a while shouldn't have to wait out a stale backoff timer\n * once it's actually back online.\n */\n async retryPending(options: { force?: boolean } = {}): Promise<SyncResult> {\n // A flapping connection reports \"online\" over and over; only the first in a window gets to skip the backoff.\n let force = options.force === true;\n if (force) {\n const now = Date.now();\n if (now - this.lastForcedRetryAt < FORCED_RETRY_MIN_GAP_MS) force = false;\n else this.lastForcedRetryAt = now;\n }\n const ready = force\n ? (await this.queue.listPending()).filter((event) => event.status !== \"sending\")\n : await this.queue.listReadyForRetry();\n const result: SyncResult = { attempted: 0, delivered: 0, failed: 0 };\n\n for (const event of ready) {\n if (!this.canUploadNow()) break;\n result.attempted++;\n const attemptNumber = event.attempts + 1;\n // Reflects the true attempt count server-side (spec: Event Explorer's \"Attempts\" column) — the\n // payload as originally generated always says 1, since it doesn't yet know it'll be retried.\n const delivered = await this.attemptDelivery(event.eventId, { ...event.payload, attempts: attemptNumber });\n if (delivered) {\n result.delivered++;\n } else {\n result.failed++;\n }\n }\n\n return result;\n }\n\n /**\n * The single place that actually talks to the backend: marks the queued\n * record \"sending\" (so a concurrent retry pass skips it), attempts the\n * upload, and resolves it to either removed-from-queue (success — a\n * `deduplicated: true` response still counts, since it means the backend\n * already has this event id, which is exactly the outcome a retry is\n * supposed to produce) or a scheduled retry (failure). Returns whether it\n * was delivered, purely so `retryPending` can tally its `SyncResult`.\n */\n private async attemptDelivery(eventId: string, payload: LocationEventPayload): Promise<boolean> {\n this.uploadTimes.push(Date.now());\n await this.queue.markSending(eventId);\n this.logger?.log(\"Event upload started\", { eventId, attempt: payload.attempts });\n let result: IngestResult | undefined;\n try {\n result = await this.apiClient.ingestEvent(payload);\n await this.queue.markSent(eventId);\n this.lastSyncAt = new Date().toISOString();\n this.lastUploadResult = \"success\";\n this.logger?.log(\"Event upload succeeded\", { eventId });\n } catch (error) {\n this.lastUploadResult = \"failure\";\n if (error instanceof ApiError && error.status === 429) {\n // Over a server limit: stop sending for as long as it asks, and schedule this event after that.\n const waitMs = error.retryAfterMs ?? DEFAULT_RATE_LIMIT_PAUSE_MS;\n this.pausedUntil = Date.now() + waitMs;\n await this.queue.markAttemptFailed(eventId, waitMs);\n this.logger?.warn(\"Server rate limit — pausing uploads\", { eventId, waitMs });\n } else if (error instanceof ApiError && isPermanentRejection(error.status)) {\n // The backend will never accept this event (unknown/disabled geofence, malformed, implausible): retrying only adds load.\n await this.queue.markSent(eventId);\n if (error.status === 403) this.pausedUntil = Date.now() + FORBIDDEN_PAUSE_MS;\n this.logger?.warn(\"Event rejected permanently — dropped\", { eventId, status: error.status });\n } else {\n await this.queue.markAttemptFailed(eventId);\n this.logger?.warn(\"Event upload failed — retry scheduled\", { eventId, error: String(error) });\n }\n return false;\n }\n // Outside the try: a throwing listener must not turn an already-delivered event into a failed upload.\n try {\n this.onDelivered?.(result);\n } catch (error) {\n this.logger?.warn(\"onDelivered handler threw\", { eventId, error: String(error) });\n }\n return true;\n }\n\n startAutoRetry(): void {\n if (this.retryTimer) return;\n this.retryTimer = setInterval(() => {\n void this.retryPending();\n }, this.retryIntervalMs);\n }\n\n stopAutoRetry(): void {\n if (this.retryTimer) {\n clearInterval(this.retryTimer);\n this.retryTimer = null;\n }\n }\n\n getLastSyncAt(): string | null {\n return this.lastSyncAt;\n }\n\n getLastUploadResult(): \"success\" | \"failure\" | null {\n return this.lastUploadResult;\n }\n}\n\n/** A 4xx other than \"timed out\" (408) and \"slow down\" (429) means the request itself is wrong; sending it again cannot help. */\nfunction isPermanentRejection(status: number): boolean {\n return status >= 400 && status < 500 && status !== 408 && status !== 429;\n}\n","/**\n * Structured, easy-to-disable debug logging (spec section 22). Every call\n * site passes a short lifecycle label matching the examples in this\n * package's README (\"Configuration synced\", \"Event queued\", ...) plus\n * optional structured details — never the SDK key, never a raw event\n * payload body, never anything from `GeoPulseConfig` beyond `applicationId`.\n */\nexport class Logger {\n constructor(private enabled: boolean) {}\n\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n log(message: string, details?: Record<string, unknown>): void {\n if (!this.enabled) return;\n if (details) {\n console.log(`[GeoPulse] ${message}`, details);\n } else {\n console.log(`[GeoPulse] ${message}`);\n }\n }\n\n warn(message: string, details?: Record<string, unknown>): void {\n if (!this.enabled) return;\n if (details) {\n console.warn(`[GeoPulse] ${message}`, details);\n } else {\n console.warn(`[GeoPulse] ${message}`);\n }\n }\n}\n","import type {\n DevicePlatform,\n ExperienceDeliveryPayload,\n GeofenceEventType,\n LocationEventPayload,\n RevokedExperience,\n SdkGeofenceConfig,\n SdkProjectConfig,\n} from \"../protocol\";\n\nimport {\n ConfigCache,\n DEFAULT_BACKEND_URL,\n InMemoryKeyValueStore,\n getOrCreateDeviceId,\n SDK_VERSION,\n type GeoPulseConfig,\n type KeyValueStore,\n} from \"../config\";\nimport { ApiClient, ApiError, type IngestResult } from \"../firebase/api-client\";\nimport { createEventPayload } from \"../events/factory\";\nimport { GeofenceStateMachine, type StateMachineOptions } from \"../geofencing/state-machine\";\nimport { GeofenceStateStore } from \"../geofencing/state-store\";\nimport type { RealtimeConnection, RealtimeOptions } from \"../realtime/realtime-client\";\nimport type { LocationProvider, LocationUpdate } from \"../location/types\";\nimport { SimulatedLocationProvider } from \"../location/simulated-location-provider\";\nimport type { PermissionProvider, PermissionStatus } from \"../permissions/types\";\nimport { SimulatedPermissionProvider } from \"../permissions/simulated-permission-provider\";\nimport { PendingEventQueue } from \"../storage/queue\";\nimport { ManualNetworkMonitor, type NetworkMonitor } from \"../sync/network-monitor\";\nimport { SyncManager } from \"../sync/sync-manager\";\nimport { Logger } from \"./logger\";\nimport type { ConfigurationStatus, DiagnosticsSnapshot, SdkStatus } from \"./types\";\n\ntype EventListener = (event: LocationEventPayload) => void;\ntype ExperienceListener = (payload: ExperienceDeliveryPayload) => void;\ntype ExperienceRemovedListener = (payload: RevokedExperience) => void;\n\n/** The SDK key resolved to a different application than `config.applicationId`. */\nexport class ApplicationMismatchError extends Error {\n constructor() {\n super(\"GeoPulse: the SDK key belongs to a different application than config.applicationId — check both values\");\n this.name = \"ApplicationMismatchError\";\n }\n}\n\n/**\n * Experiences are fetched right after an event that created one (see `handleEventDelivered`), once on\n * start(), and whenever the server pushes \"changed\" to a phone that is inside a geofence (see realtime/).\n * This timer is only a safety net for anything missed — a lost response, an experience created while the\n * app was closed, a push that never arrived. A host app can override via GeoPulseConfig.experiencePollIntervalMs.\n */\nconst DEFAULT_EXPERIENCE_POLL_INTERVAL_MS = 15 * 60_000;\n/** The geofence list is re-fetched (on a poll tick) once it is this old, so admin edits reach a phone that stays open. */\nconst CONFIG_REFRESH_INTERVAL_MS = 60 * 60_000;\n/** Floor for any caller-supplied interval (retryIntervalMs/experiencePollIntervalMs) — a config of `0` or a negative number would otherwise turn into a tight, runaway retry/poll loop (Phase 7 SDK audit finding). */\nconst MIN_TIMER_INTERVAL_MS = 1_000;\n\n/** `undefined` passes through untouched (lets the caller's own default apply); a defined-but-too-small value is floored instead of silently creating a runaway timer. */\nfunction clampIntervalMs(value: number | undefined): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (!Number.isFinite(value) || value < MIN_TIMER_INTERVAL_MS) {\n return MIN_TIMER_INTERVAL_MS;\n }\n return value;\n}\n\n/**\n * Everything the SDK talks to outside of itself, injectable so\n * `GeoPulseSdk` (this whole lifecycle) is unit-testable without an RN\n * runtime — tests construct it with fakes; `src/index.ts` (the real public\n * entrypoint) constructs the default instance with the real RN adapters.\n */\nexport interface GeoPulseSdkDependencies {\n locationProvider?: LocationProvider;\n permissionProvider?: PermissionProvider;\n queue?: PendingEventQueue;\n createApiClient?: (config: GeoPulseConfig) => ApiClient;\n deviceIdStore?: KeyValueStore;\n configCacheStore?: KeyValueStore;\n networkMonitor?: NetworkMonitor;\n /** Defaults to \"unknown\" — src/index.ts supplies the real `Platform.OS` so this file stays RN-free. */\n getPlatform?: () => DevicePlatform;\n /** Overrides GeoPulseConfig.debug — mainly for tests that want to assert on log output. */\n debug?: boolean;\n /** Where the geofence state machine keeps its memory across restarts. Defaults to memory only (lost on restart). */\n stateStore?: KeyValueStore;\n /** Tuning for tests (cooldown, exit buffer, clock). */\n stateMachineOptions?: StateMachineOptions;\n /**\n * Builds the push connection used while the user is inside a geofence. Not set by default: the core has no\n * WebSocket dependency, and src/index.ts supplies the real one.\n */\n createRealtimeClient?: (options: RealtimeOptions) => RealtimeConnection;\n}\n\nexport class GeoPulseSdk {\n private status: SdkStatus = \"idle\";\n private config: GeoPulseConfig | null = null;\n private apiClient: ApiClient | null = null;\n private syncManager: SyncManager | null = null;\n private resolvedProjectId: string | null = null;\n private resolvedApplicationId: string | null = null;\n private applicationEnabled = true;\n private configVersion: string | null = null;\n private configurationStatus: ConfigurationStatus = \"unsynced\";\n private geofences: SdkGeofenceConfig[] = [];\n private lastKnownLocation: LocationUpdate | null = null;\n private lastEvent: LocationEventPayload | null = null;\n private deviceId: string | null = null;\n private locationError: string | null = null;\n private unsubscribeNetwork: (() => void) | null = null;\n private readonly stateMachine: GeofenceStateMachine;\n private readonly geofenceStateStore: GeofenceStateStore;\n private geofenceStateRestored = false;\n private realtime: RealtimeConnection | null = null;\n private readonly createRealtime: ((options: RealtimeOptions) => RealtimeConnection) | undefined;\n private lastConfigFetchAt = 0;\n private configRefreshInFlight = false;\n private readonly listeners = new Map<GeofenceEventType, Set<EventListener>>();\n private readonly experienceListeners = new Set<ExperienceListener>();\n /** Client-side duplicate protection (spec section 14) — belt-and-suspenders alongside the server-side ack; a delivery already acked but re-observed before the ack lands must not fire `experience.received` twice. */\n private readonly seenExperienceRevisions = new Map<string, number>();\n private readonly experienceRemovedListeners = new Set<ExperienceRemovedListener>();\n private readonly removedDeliveryIds = new Set<string>();\n private experiencePollTimer: ReturnType<typeof setInterval> | null = null;\n private experiencePollInFlight = false;\n /** Set while a poll that was refused with \"too many requests\" waits to be tried again. */\n private pollRetryTimer: ReturnType<typeof setTimeout> | null = null;\n /** Set when a poll was asked for while one was already running; that one may have queried before the new delivery existed. */\n private experiencePollRequested = false;\n private experiencesReceivedCount = 0;\n private readonly logger: Logger;\n\n private readonly locationProvider: LocationProvider;\n private readonly permissionProvider: PermissionProvider;\n private readonly queue: PendingEventQueue;\n private readonly buildApiClient: (config: GeoPulseConfig) => ApiClient;\n private readonly deviceIdStore: KeyValueStore;\n private readonly configCache: ConfigCache;\n private readonly networkMonitor: NetworkMonitor;\n private readonly getPlatform: () => DevicePlatform;\n private experiencePollIntervalMs = DEFAULT_EXPERIENCE_POLL_INTERVAL_MS;\n\n constructor(dependencies: GeoPulseSdkDependencies = {}) {\n this.locationProvider = dependencies.locationProvider ?? new SimulatedLocationProvider();\n this.permissionProvider = dependencies.permissionProvider ?? new SimulatedPermissionProvider();\n this.queue = dependencies.queue ?? new PendingEventQueue();\n this.buildApiClient =\n dependencies.createApiClient ?? ((config) => new ApiClient(config.functionsBaseUrl || DEFAULT_BACKEND_URL, config.apiKey, config.applicationId));\n this.deviceIdStore = dependencies.deviceIdStore ?? new InMemoryKeyValueStore();\n this.configCache = new ConfigCache(dependencies.configCacheStore ?? new InMemoryKeyValueStore());\n this.networkMonitor = dependencies.networkMonitor ?? new ManualNetworkMonitor();\n this.getPlatform = dependencies.getPlatform ?? (() => \"unknown\");\n this.logger = new Logger(dependencies.debug ?? false);\n this.stateMachine = new GeofenceStateMachine(dependencies.stateMachineOptions);\n this.geofenceStateStore = new GeofenceStateStore(dependencies.stateStore ?? new InMemoryKeyValueStore());\n this.createRealtime = dependencies.createRealtimeClient;\n }\n\n /**\n * Resolves this device's persistent id, then fetches (or falls back to\n * cached) project configuration. Must succeed — fresh or cached — before\n * `start()`; if neither is available (first-ever run with no network),\n * this rethrows and `getStatus()` reports \"error\".\n */\n async initialize(config: GeoPulseConfig): Promise<void> {\n // Without this guard, calling initialize() while already \"running\" replaced\n // this.syncManager with a fresh instance, orphaning the old one's auto-retry timer (it kept\n // firing forever with no reference left to stop it) while location callbacks from the\n // still-running locationProvider could race against the new config being applied — a real\n // bug found in Phase 7's SDK audit, not a hypothetical. Re-initializing a live SDK has no\n // safe, well-defined meaning short of a full stop() first.\n if (this.status === \"running\") {\n throw new Error(\"GeoPulse.initialize() cannot be called while already running — call GeoPulse.stop() first\");\n }\n if (this.status === \"initializing\") {\n throw new Error(\"GeoPulse.initialize() is already in progress — wait for the first call to resolve\");\n }\n if (!config.apiKey || !config.applicationId) {\n throw new Error(\"GeoPulse.initialize requires both apiKey and applicationId\");\n }\n\n if (config.debug !== undefined) {\n this.logger.setEnabled(config.debug);\n }\n this.logger.log(\"GeoPulse.initialize()\", { applicationId: config.applicationId });\n\n this.status = \"initializing\";\n this.config = config;\n this.experiencePollIntervalMs = clampIntervalMs(config.experiencePollIntervalMs) ?? DEFAULT_EXPERIENCE_POLL_INTERVAL_MS;\n this.apiClient = this.buildApiClient(config);\n this.syncManager = new SyncManager(this.apiClient, this.queue, clampIntervalMs(config.retryIntervalMs), this.logger, (result) =>\n this.handleEventDelivered(result),\n );\n this.deviceId = await getOrCreateDeviceId(this.deviceIdStore);\n this.realtime?.disconnect();\n this.realtime = this.createRealtime\n ? this.createRealtime({\n baseUrl: config.functionsBaseUrl || DEFAULT_BACKEND_URL,\n sdkKey: config.apiKey,\n applicationId: config.applicationId,\n deviceId: this.deviceId,\n onChanged: () => void this.pollForExperiences(),\n // Whatever changed while the socket was down is picked up now.\n onConnected: () => void this.pollForExperiences(),\n logger: this.logger,\n })\n : null;\n\n try {\n const projectConfig = await this.apiClient.getProjectConfig({\n deviceId: this.deviceId,\n platform: this.getPlatform(),\n sdkVersion: SDK_VERSION,\n });\n // The key must belong to the application the integrator configured; never adopt another one.\n if (projectConfig.applicationId !== config.applicationId) {\n throw new ApplicationMismatchError();\n }\n this.applyProjectConfig(projectConfig);\n this.configurationStatus = \"synced\";\n this.lastConfigFetchAt = Date.now();\n await this.configCache.save(projectConfig);\n this.status = \"initialized\";\n this.logger.log(\"Configuration synced\", { geofenceCount: projectConfig.geofences.length, configVersion: projectConfig.configVersion });\n } catch (error) {\n if (error instanceof ApplicationMismatchError) {\n this.configurationStatus = \"unavailable\";\n this.status = \"error\";\n throw error;\n }\n const cached = await this.configCache.load();\n if (cached && cached.config.applicationId === config.applicationId) {\n this.applyProjectConfig(cached.config);\n this.configurationStatus = \"cached\";\n this.status = \"initialized\";\n this.logger.warn(\"getProjectConfig failed — using cached configuration\", { fetchedAt: cached.fetchedAt });\n return;\n }\n this.configurationStatus = \"unavailable\";\n this.status = \"error\";\n throw error;\n }\n }\n\n async start(): Promise<void> {\n if (this.status === \"running\") {\n // Previously fell into the same generic \"called before initialize succeeded\" message below\n // even though initialize DID succeed and the SDK IS running — factually wrong and would\n // send a developer debugging a double-start() down the wrong path (Phase 7 SDK audit).\n throw new Error(\"GeoPulse.start() called while already running — call GeoPulse.stop() first if you want to restart\");\n }\n if (this.status !== \"initialized\" && this.status !== \"stopped\") {\n throw new Error(\"GeoPulse.start called before GeoPulse.initialize succeeded\");\n }\n if (!this.applicationEnabled) {\n this.status = \"error\";\n throw new Error(\"This application has been disabled in the GeoPulse admin portal\");\n }\n\n const permission = await this.permissionProvider.request();\n if (permission !== \"granted\") {\n this.status = \"error\";\n throw new Error(`Location permission not granted (status: ${permission})`);\n }\n\n // Reopening the app while still inside must not report a second \"enter\": pick up where the last run left off.\n if (!this.geofenceStateRestored) {\n this.geofenceStateRestored = true;\n const saved = await this.geofenceStateStore.load();\n if (saved) this.stateMachine.restoreState(saved);\n }\n\n this.locationError = null;\n await this.locationProvider.start(\n (location) => {\n void this.handleLocationUpdate(location);\n },\n (error) => {\n this.locationError = error.message;\n this.logger.warn(\"Location provider failed to start\", { error: error.message });\n },\n );\n if (this.locationError) {\n // The provider caught its own failure and reported it via onError rather than rejecting\n // start() directly (see LocationProvider.start's doc) — surfaced here as a normal rejection\n // so callers get the same \"start() throws on failure\" contract as the permission-denied case.\n this.status = \"error\";\n throw new Error(`GeoPulse failed to start location tracking: ${this.locationError}`);\n }\n\n this.unsubscribeNetwork = this.networkMonitor.subscribe((networkStatus) => {\n if (networkStatus === \"online\") {\n this.logger.log(\"Network available — retrying queued events\");\n void this.syncManager?.retryPending({ force: true });\n }\n });\n\n this.syncManager?.startAutoRetry();\n this.startExperiencePolling();\n this.status = \"running\";\n this.syncRealtime();\n this.logger.log(\"GeoPulse.start()\");\n }\n\n stop(): void {\n this.locationProvider.stop();\n this.syncManager?.stopAutoRetry();\n this.stopExperiencePolling();\n this.realtime?.disconnect();\n this.unsubscribeNetwork?.();\n this.unsubscribeNetwork = null;\n this.status = \"stopped\";\n this.logger.log(\"GeoPulse.stop()\");\n }\n\n getStatus(): SdkStatus {\n return this.status;\n }\n\n async getPermissionStatus(): Promise<PermissionStatus> {\n return this.permissionProvider.getStatus();\n }\n\n on(eventName: GeofenceEventType, callback: EventListener): void;\n on(eventName: \"experience.received\", callback: ExperienceListener): void;\n on(eventName: \"experience.removed\", callback: ExperienceRemovedListener): void;\n on(eventName: GeofenceEventType | \"experience.received\" | \"experience.removed\", callback: EventListener | ExperienceListener | ExperienceRemovedListener): void {\n if (eventName === \"experience.received\") {\n this.experienceListeners.add(callback as ExperienceListener);\n return;\n }\n if (eventName === \"experience.removed\") {\n this.experienceRemovedListeners.add(callback as ExperienceRemovedListener);\n return;\n }\n if (!this.listeners.has(eventName)) {\n this.listeners.set(eventName, new Set());\n }\n this.listeners.get(eventName)?.add(callback as EventListener);\n }\n\n off(eventName: GeofenceEventType, callback: EventListener): void;\n off(eventName: \"experience.received\", callback: ExperienceListener): void;\n off(eventName: \"experience.removed\", callback: ExperienceRemovedListener): void;\n off(eventName: GeofenceEventType | \"experience.received\" | \"experience.removed\", callback: EventListener | ExperienceListener | ExperienceRemovedListener): void {\n if (eventName === \"experience.received\") {\n this.experienceListeners.delete(callback as ExperienceListener);\n return;\n }\n if (eventName === \"experience.removed\") {\n this.experienceRemovedListeners.delete(callback as ExperienceRemovedListener);\n return;\n }\n this.listeners.get(eventName)?.delete(callback as EventListener);\n }\n\n // --- Small read-only accessors beyond the core 7-method API, so a host\n // app (see apps/playground) can build a status UI around the SDK without\n // it needing to duplicate state the SDK already tracks internally. ---\n\n getConfiguredGeofences(): SdkGeofenceConfig[] {\n return this.geofences;\n }\n\n /** The server-resolved application id (from `getProjectConfig`), or null before `initialize()` completes. */\n getApplicationId(): string | null {\n return this.resolvedApplicationId;\n }\n\n getLastKnownLocation(): LocationUpdate | null {\n return this.lastKnownLocation;\n }\n\n async getPendingEventCount(): Promise<number> {\n return (await this.queue.listPending()).length;\n }\n\n /** Null until `initialize()` resolves it — persisted across app restarts (see config/device-id.ts). */\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n /**\n * The rich developer-diagnostics snapshot (spec section 10/11) — a\n * superset of `getStatus()`, not a replacement for it (see core/types.ts's\n * `DiagnosticsSnapshot` doc for why these are two separate methods).\n */\n async getDiagnostics(): Promise<DiagnosticsSnapshot> {\n const pending = await this.queue.listPending();\n const permissionStatus = this.status === \"idle\" || this.status === \"initializing\" ? \"unknown\" : await this.permissionProvider.getStatus();\n\n return {\n initialized: this.status !== \"idle\" && this.status !== \"initializing\",\n running: this.status === \"running\",\n locationAvailable: this.lastKnownLocation !== null,\n permissionStatus,\n networkStatus: this.networkMonitor.getStatus(),\n configurationStatus: this.configurationStatus,\n configVersion: this.configVersion,\n pendingEvents: pending.length,\n totalRetryAttempts: pending.reduce((sum, event) => sum + event.attempts, 0),\n locationError: this.locationError,\n lastEventAt: this.lastEvent?.timestamp ?? null,\n lastEventType: this.lastEvent?.type ?? null,\n lastSyncAt: this.syncManager?.getLastSyncAt() ?? null,\n lastUploadResult: this.syncManager?.getLastUploadResult() ?? null,\n deviceId: this.deviceId,\n sdkVersion: SDK_VERSION,\n experiencesReceivedCount: this.experiencesReceivedCount,\n };\n }\n\n /**\n * Only functional when the SDK is running the simulated location\n * provider (the default outside of production builds — see\n * src/index.ts) and gated by `__DEV__` so it's inert in a production\n * bundle even if a caller somehow held a reference to it.\n */\n simulateLocation(coords: { latitude: number; longitude: number }): void {\n const isDev = typeof __DEV__ === \"undefined\" ? true : __DEV__;\n if (!isDev) {\n console.warn(\"GeoPulse.simulator is unavailable in production builds.\");\n return;\n }\n if (!(this.locationProvider instanceof SimulatedLocationProvider)) {\n console.warn(\n \"GeoPulse.simulator.setLocation has no effect: the SDK is not running the simulated location provider.\",\n );\n return;\n }\n this.locationProvider.setLocation(coords);\n }\n\n /**\n * The Phase 5B delivery mechanism: poll immediately on `start()` (so a\n * demo doesn't wait a full interval for the first check), then on a\n * timer. A poll failure (network blip, backend down) is logged and\n * swallowed — it must never crash the SDK or the host app (spec section\n * 13), and the next tick tries again on its own.\n */\n private startExperiencePolling(): void {\n void this.pollForExperiences();\n this.experiencePollTimer = setInterval(() => {\n void this.pollForExperiences();\n void this.refreshConfigIfStale();\n }, this.experiencePollIntervalMs);\n }\n\n private stopExperiencePolling(): void {\n if (this.pollRetryTimer) {\n clearTimeout(this.pollRetryTimer);\n this.pollRetryTimer = null;\n }\n if (this.experiencePollTimer) {\n clearInterval(this.experiencePollTimer);\n this.experiencePollTimer = null;\n }\n }\n\n /**\n * The backend says in its ingest response whether the event created an experience for this\n * device. Poll right away if so (or if it doesn't say — an older backend), skip if it says\n * there is nothing. Does nothing unless polling is active, so a stopped SDK never polls.\n */\n private handleEventDelivered(result: IngestResult | undefined): void {\n if (this.experiencePollTimer === null || result?.experiencesPending === false) {\n return;\n }\n void this.pollForExperiences();\n }\n\n /** One poll at a time: a second request while one is running makes it run once more afterwards, rather than overlapping. */\n private async pollForExperiences(): Promise<void> {\n if (this.experiencePollInFlight) {\n this.experiencePollRequested = true;\n return;\n }\n this.experiencePollInFlight = true;\n try {\n do {\n this.experiencePollRequested = false;\n await this.pollForExperiencesOnce();\n } while (this.experiencePollRequested);\n } finally {\n this.experiencePollInFlight = false;\n }\n }\n\n private async pollForExperiencesOnce(): Promise<void> {\n if (!this.apiClient || !this.deviceId) {\n return;\n }\n try {\n const { experiences, revoked } = await this.apiClient.getPendingExperiences(this.deviceId);\n for (const experience of experiences) {\n // Backend is the source of truth (spec section 14) — this is defense in depth against observing the same\n // still-\"pending\" delivery twice across two polls before the ack lands. A changed `revision` is the same\n // delivery with edited content, which the app must see again, flagged as an update.\n const revision = experience.revision ?? 0;\n const seenRevision = this.seenExperienceRevisions.get(experience.deliveryId);\n if (seenRevision === revision) {\n continue;\n }\n const isUpdate = seenRevision !== undefined;\n this.seenExperienceRevisions.set(experience.deliveryId, revision);\n this.removedDeliveryIds.delete(experience.deliveryId);\n this.experiencesReceivedCount += 1;\n this.logger.log(\"Experience received\", { deliveryId: experience.deliveryId, campaignId: experience.campaignId, isUpdate });\n this.experienceListeners.forEach((listener) => listener({ ...experience, isUpdate }));\n\n await this.apiClient.acknowledgeExperienceDelivery(experience.deliveryId).catch((error) => {\n this.logger.warn(\"Failed to acknowledge experience delivery\", {\n deliveryId: experience.deliveryId,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n\n for (const removed of revoked) {\n // Emit once, but always acknowledge so the backend can close the delivery.\n if (!this.removedDeliveryIds.has(removed.deliveryId)) {\n this.removedDeliveryIds.add(removed.deliveryId);\n this.seenExperienceRevisions.delete(removed.deliveryId);\n this.logger.log(\"Experience removed\", { deliveryId: removed.deliveryId, campaignId: removed.campaignId });\n this.experienceRemovedListeners.forEach((listener) => listener(removed));\n }\n await this.apiClient.acknowledgeExperienceDelivery(removed.deliveryId).catch((error) => {\n this.logger.warn(\"Failed to acknowledge experience removal\", {\n deliveryId: removed.deliveryId,\n error: error instanceof Error ? error.message : String(error),\n });\n });\n }\n } catch (error) {\n this.logger.warn(\"Polling for experiences failed\", { error: error instanceof Error ? error.message : String(error) });\n // When an admin changes something, every phone inside is told at once and they all fetch together; the backend caps\n // that per application. A refused phone tries once more after the wait it was given instead of waiting for the slow timer.\n if (error instanceof ApiError && error.status === 429) {\n this.schedulePollRetry(error.retryAfterMs ?? 30_000);\n }\n }\n }\n\n private schedulePollRetry(waitMs: number): void {\n if (this.pollRetryTimer !== null || this.experiencePollTimer === null) {\n return;\n }\n // Spread the retries so the phones that were refused together do not all come back in the same second.\n this.pollRetryTimer = setTimeout(() => {\n this.pollRetryTimer = null;\n void this.pollForExperiences();\n }, waitMs + Math.random() * 5_000);\n }\n\n /**\n * Re-reads the geofence list when it is an hour old, so a geofence an admin added, moved, resized or removed reaches a phone\n * that keeps the app open (otherwise only a restart picks it up). Runs from the poll timer, so it costs no timer of its own.\n * A failure keeps the list we have. A disabled application stops the SDK.\n */\n private async refreshConfigIfStale(): Promise<void> {\n if (!this.apiClient || !this.deviceId || !this.config || this.configRefreshInFlight) {\n return;\n }\n if (Date.now() - this.lastConfigFetchAt < CONFIG_REFRESH_INTERVAL_MS) {\n return;\n }\n this.configRefreshInFlight = true;\n try {\n const projectConfig = await this.apiClient.getProjectConfig({ deviceId: this.deviceId, platform: this.getPlatform(), sdkVersion: SDK_VERSION });\n if (projectConfig.applicationId !== this.config.applicationId) {\n return;\n }\n this.lastConfigFetchAt = Date.now();\n this.applyProjectConfig(projectConfig);\n this.configurationStatus = \"synced\";\n await this.configCache.save(projectConfig);\n this.logger.log(\"Configuration refreshed\", { geofenceCount: projectConfig.geofences.length, configVersion: projectConfig.configVersion });\n if (!projectConfig.applicationEnabled) {\n this.logger.warn(\"Application disabled — stopping\");\n this.stop();\n this.status = \"error\";\n }\n } catch (error) {\n this.logger.warn(\"Configuration refresh failed — keeping the current geofences\", { error: error instanceof Error ? error.message : String(error) });\n } finally {\n this.configRefreshInFlight = false;\n }\n }\n\n /** Keeps the push connection open exactly while the user is inside a geofence. */\n private syncRealtime(): void {\n if (!this.realtime || this.status === \"stopped\") {\n return;\n }\n if (this.stateMachine.isInsideAny()) {\n this.realtime.connect();\n } else {\n this.realtime.disconnect();\n }\n }\n\n private async persistGeofenceState(): Promise<void> {\n await this.geofenceStateStore.save(this.stateMachine.exportState());\n }\n\n private applyProjectConfig(projectConfig: SdkProjectConfig): void {\n this.resolvedProjectId = projectConfig.projectId;\n this.resolvedApplicationId = projectConfig.applicationId;\n this.applicationEnabled = projectConfig.applicationEnabled;\n this.configVersion = projectConfig.configVersion;\n this.geofences = projectConfig.geofences;\n }\n\n private async handleLocationUpdate(location: LocationUpdate): Promise<void> {\n this.lastKnownLocation = location;\n if (!this.resolvedProjectId || !this.resolvedApplicationId || !this.syncManager || !this.deviceId) {\n return;\n }\n\n const transitions = this.stateMachine.evaluate(location, this.geofences);\n if (transitions.length > 0) {\n void this.persistGeofenceState();\n }\n this.syncRealtime();\n for (const transition of transitions) {\n this.logger.log(\"Geofence state changed\", { geofenceId: transition.geofence.id, type: transition.type });\n\n const event = createEventPayload({\n type: transition.type,\n projectId: this.resolvedProjectId,\n applicationId: this.resolvedApplicationId,\n geofence: transition.geofence,\n location,\n deviceId: this.deviceId,\n });\n this.lastEvent = event;\n this.logger.log(\"Event generated\", { eventId: event.id, type: event.type });\n\n this.listeners.get(transition.type)?.forEach((listener) => listener(event));\n await this.syncManager.sendOrQueue(event);\n }\n }\n}\n","import * as Location from \"expo-location\";\n\nimport type { LocationProvider, LocationUpdate } from \"./types\";\n\n/**\n * Real, foreground-only device location via expo-location. Does not\n * request or use background location — see this package's README for the\n * platform permission requirements and why background tracking is\n * explicitly out of scope for this phase.\n */\nexport class DeviceLocationProvider implements LocationProvider {\n private subscription: Location.LocationSubscription | null = null;\n\n /**\n * `watchPositionAsync` rejects if location services are off at the OS\n * level (distinct from the app's own permission grant, which is checked\n * before this ever runs) — caught here and reported via `onError` instead\n * of left to reject this method's own promise, so a caller that doesn't\n * wrap `GeoPulse.start()` in a try/catch still can't end up with an\n * unhandled rejection from deep inside a native module.\n */\n async start(onUpdate: (location: LocationUpdate) => void, onError?: (error: Error) => void): Promise<void> {\n try {\n this.subscription = await Location.watchPositionAsync(\n { accuracy: Location.Accuracy.Balanced, timeInterval: 5000, distanceInterval: 10 },\n (position) => {\n onUpdate({\n latitude: position.coords.latitude,\n longitude: position.coords.longitude,\n timestamp: new Date(position.timestamp).toISOString(),\n ...(typeof position.coords.accuracy === \"number\" ? { accuracy: position.coords.accuracy } : {}),\n });\n },\n );\n } catch (error) {\n onError?.(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n stop(): void {\n this.subscription?.remove();\n this.subscription = null;\n }\n}\n","import * as Location from \"expo-location\";\n\nimport type { PermissionProvider, PermissionStatus } from \"./types\";\n\nfunction mapExpoStatus(status: Location.PermissionStatus): PermissionStatus {\n switch (status) {\n case Location.PermissionStatus.GRANTED:\n return \"granted\";\n case Location.PermissionStatus.DENIED:\n return \"denied\";\n default:\n return \"undetermined\";\n }\n}\n\n/**\n * Foreground-only — see this package's README (\"Permissions\" section) for\n * why background permission is explicitly out of scope for this phase.\n * Requests only the foreground permission; never touches\n * `requestBackgroundPermissionsAsync`.\n */\nexport class ExpoPermissionProvider implements PermissionProvider {\n async getStatus(): Promise<PermissionStatus> {\n const { status } = await Location.getForegroundPermissionsAsync();\n return mapExpoStatus(status);\n }\n\n async request(): Promise<PermissionStatus> {\n const { status } = await Location.requestForegroundPermissionsAsync();\n return mapExpoStatus(status);\n }\n}\n","import AsyncStorage from \"@react-native-async-storage/async-storage\";\n\nimport type { EventQueueStore, QueuedEvent } from \"./types\";\n\nconst STORAGE_KEY = \"@geopulse/pending-events\";\n\n/**\n * Persists the pending-event queue across app restarts using AsyncStorage —\n * required for the offline queue to actually survive the app being killed\n * before connectivity returns, not just a network blip while it's running.\n * Single-JSON-blob storage rather than one key per event: the expected\n * queue size (events waiting on a flaky connection) is small enough that\n * read/write-modify-write on one key is simpler and fine.\n */\nexport class AsyncStorageEventQueueStore implements EventQueueStore {\n async getAll(): Promise<QueuedEvent[]> {\n const raw = await AsyncStorage.getItem(STORAGE_KEY);\n if (!raw) return [];\n try {\n return JSON.parse(raw) as QueuedEvent[];\n } catch {\n return [];\n }\n }\n\n async save(event: QueuedEvent): Promise<void> {\n const all = await this.getAll();\n const next = [...all.filter((existing) => existing.eventId !== event.eventId), event];\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(next));\n }\n\n async remove(eventId: string): Promise<void> {\n const all = await this.getAll();\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(all.filter((existing) => existing.eventId !== eventId)));\n }\n}\n","import type { Logger } from \"../core/logger\";\n\n/** The part of a WebSocket the client uses; React Native's built-in `WebSocket` satisfies it, and tests pass a fake. */\nexport interface RealtimeSocket {\n onopen: ((event: unknown) => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: ((event: { code: number }) => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n}\n\nexport interface RealtimeOptions {\n /** Backend base URL (https://...). The socket goes to the same host over wss://. */\n baseUrl: string;\n sdkKey: string;\n applicationId: string;\n deviceId: string;\n /** The server says something changed for this device: fetch it. */\n onChanged: () => void;\n /** The server accepted the connection. Anything changed while we were disconnected is fetched now. */\n onConnected: () => void;\n createSocket?: (url: string) => RealtimeSocket;\n logger?: Logger;\n}\n\n/** What the host app (index.ts) hands to GeoPulseSdk: just connect and disconnect. */\nexport interface RealtimeConnection {\n connect(): void;\n disconnect(): void;\n isConnected(): boolean;\n}\n\nconst RECONNECT_BASE_MS = 5_000;\nconst RECONNECT_MAX_MS = 5 * 60_000;\n/** Close codes the server uses to say \"do not retry\": bad hello, bad key, disabled application. */\nconst NO_RETRY_CODES = new Set([4400, 4401, 4403]);\n\nexport function toRealtimeUrl(baseUrl: string): string {\n return `${baseUrl.replace(/\\/+$/, \"\").replace(/^http/, \"ws\")}/realtime`;\n}\n\n/**\n * One WebSocket, open only while the user is inside a geofence with the app open. The server uses it for one message,\n * \"changed\", sent when an admin edits a campaign or experience that affects this device; the SDK then fetches through\n * the normal request. It carries no location and the SDK never sends anything on it after the first message (the key,\n * sent as a message and not in the URL so it never lands in a log). If it drops, it reconnects with a growing delay;\n * if the connection never works, the periodic fallback poll still keeps the app correct.\n */\nexport class RealtimeClient implements RealtimeConnection {\n private socket: RealtimeSocket | null = null;\n private connected = false;\n private wanted = false;\n private failures = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly options: RealtimeOptions) {}\n\n isConnected(): boolean {\n return this.connected;\n }\n\n connect(): void {\n this.wanted = true;\n if (this.socket || this.reconnectTimer) return;\n this.open();\n }\n\n disconnect(): void {\n this.wanted = false;\n this.failures = 0;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n const socket = this.socket;\n this.socket = null;\n this.connected = false;\n if (socket) {\n socket.onclose = null;\n socket.onmessage = null;\n socket.onerror = null;\n try {\n socket.close(1000, \"done\");\n } catch {\n // Already closed.\n }\n }\n }\n\n private open(): void {\n const create = this.options.createSocket ?? defaultCreateSocket;\n let socket: RealtimeSocket | null;\n try {\n socket = create(toRealtimeUrl(this.options.baseUrl));\n } catch (error) {\n this.options.logger?.warn(\"Realtime unavailable\", { error: String(error) });\n return;\n }\n if (!socket) return;\n this.socket = socket;\n\n socket.onopen = () => {\n socket.send(JSON.stringify({ type: \"hello\", sdkKey: this.options.sdkKey, applicationId: this.options.applicationId, deviceId: this.options.deviceId }));\n };\n socket.onmessage = (event) => {\n let message: { type?: string } | null = null;\n try {\n message = JSON.parse(String(event.data)) as { type?: string };\n } catch {\n return;\n }\n if (message?.type === \"ready\") {\n this.connected = true;\n this.failures = 0;\n this.options.onConnected();\n } else if (message?.type === \"changed\") {\n this.options.onChanged();\n }\n };\n socket.onerror = () => {\n // 'close' follows and handles the retry.\n };\n socket.onclose = (event) => {\n this.socket = null;\n this.connected = false;\n if (!this.wanted) return;\n if (NO_RETRY_CODES.has(event.code)) {\n this.options.logger?.warn(\"Realtime rejected by the server; not retrying\", { code: event.code });\n return;\n }\n this.failures += 1;\n const delay = Math.min(RECONNECT_BASE_MS * 2 ** (this.failures - 1), RECONNECT_MAX_MS);\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n if (this.wanted) this.open();\n }, Math.round(delay * (0.8 + Math.random() * 0.4)));\n };\n }\n}\n\nfunction defaultCreateSocket(url: string): RealtimeSocket | null {\n const Ctor = (globalThis as { WebSocket?: new (url: string) => RealtimeSocket }).WebSocket;\n return Ctor ? new Ctor(url) : null;\n}\n","import NetInfo from \"@react-native-community/netinfo\";\n\nimport type { NetworkMonitor, NetworkStatus } from \"./network-monitor\";\n\nfunction toStatus(isConnected: boolean | null): NetworkStatus {\n if (isConnected === null) return \"unknown\";\n return isConnected ? \"online\" : \"offline\";\n}\n\n/** Real connectivity detection for the RN app. Isolated in its own file — see storage/async-storage-store.ts's comment for why. */\nexport class NetInfoNetworkMonitor implements NetworkMonitor {\n private status: NetworkStatus = \"unknown\";\n\n constructor() {\n NetInfo.fetch().then((state) => {\n this.status = toStatus(state.isConnected);\n });\n }\n\n getStatus(): NetworkStatus {\n return this.status;\n }\n\n subscribe(listener: (status: NetworkStatus) => void): () => void {\n return NetInfo.addEventListener((state) => {\n this.status = toStatus(state.isConnected);\n listener(this.status);\n });\n }\n}\n","import React from \"react\";\nimport { Image, Linking, Text, TouchableOpacity, View } from \"react-native\";\n\nimport { resolveExperienceStyle, type ExperienceDeliveryPayload, type ResolvedExperienceStyle } from \"../protocol\";\n\ntype ExperienceContent = ExperienceDeliveryPayload[\"experience\"];\n\n/**\n * Experience URLs come from the backend. Only web URLs are opened: schemes\n * like `intent:`, `tel:`, `file:` or another app's deep links could trigger\n * actions outside this app (audit M-2).\n */\nexport function isSafeExternalUrl(url: string | null | undefined): url is string {\n return typeof url === \"string\" && /^https?:\\/\\/[^\\s]+$/i.test(url);\n}\n\nfunction openUrl(url: string | null): void {\n if (!isSafeExternalUrl(url)) return;\n // Linking.openURL hands off to the OS/system browser — no WebView, no\n // in-app script execution, matching the existing playground component's\n // rule this replaces (see apps/playground/src/components/ExperienceCard.tsx).\n Linking.openURL(url).catch(() => {\n // Swallow — an unreachable/malformed URL must never crash the host app.\n });\n}\n\nfunction ExperienceButton({ style, ctaLabel, ctaUrl }: { style: ResolvedExperienceStyle; ctaLabel: string | null; ctaUrl: string | null }) {\n if (!style.button.visible || !ctaLabel || !ctaUrl) return null;\n const { position } = style.button;\n return (\n <TouchableOpacity\n onPress={() => openUrl(ctaUrl)}\n style={{\n position: \"absolute\",\n top: position.top ?? undefined,\n bottom: position.bottom ?? undefined,\n left: position.left ?? undefined,\n right: position.right ?? undefined,\n backgroundColor: style.button.backgroundColor,\n borderRadius: style.button.borderRadius,\n paddingVertical: 10,\n paddingHorizontal: 16,\n }}\n >\n <Text style={{ color: style.button.textColor, fontWeight: \"600\" }}>{ctaLabel}</Text>\n </TouchableOpacity>\n );\n}\n\n/** Shared by all three experience types — full-bleed background image (if any) behind title/body/button, everything else laid out per-type by its caller. */\nfunction ExperienceContainer({ content, style, children }: { content: ExperienceContent; style: ResolvedExperienceStyle; children: React.ReactNode }): React.JSX.Element {\n const containerStyle = {\n backgroundColor: style.container.backgroundColor,\n borderColor: style.container.borderColor ?? undefined,\n borderWidth: style.container.borderWidth,\n borderRadius: style.container.borderRadius,\n padding: style.container.padding,\n overflow: \"hidden\" as const,\n width: style.container.width === \"fill\" ? (\"100%\" as const) : style.container.width,\n // \"auto\" means no explicit height at all — RN sizes a View to its content by default, so\n // omitting the property (rather than passing \"auto\", which RN's height type doesn't accept)\n // is what actually produces that behavior.\n // The button is absolutely positioned, so it takes no layout space — without a floor, an\n // \"auto\" container shrinks to title+body and the button overlaps them. 160 matches the\n // admin-web preview (experience-preview.tsx) so the device renders what the portal shows.\n ...(style.container.height === \"auto\" ? { minHeight: 160 } : { height: style.container.height }),\n };\n const backgroundImage = isSafeExternalUrl(style.container.backgroundImageUrl) ? (\n <Image\n source={{ uri: style.container.backgroundImageUrl }}\n resizeMode={style.container.backgroundImageFit}\n style={{ position: \"absolute\", top: 0, left: 0, right: 0, bottom: 0 }}\n />\n ) : null;\n\n // TouchableOpacity and View have different prop shapes, so this branches on\n // two full JSX returns rather than picking a dynamic component variable —\n // the latter can't be given a declaration-emittable type.\n if (content.cardNavigationUrl) {\n const cardNavigationUrl = content.cardNavigationUrl;\n return (\n <TouchableOpacity testID=\"experience-container\" onPress={() => openUrl(cardNavigationUrl)} style={containerStyle}>\n {backgroundImage}\n {children}\n </TouchableOpacity>\n );\n }\n return (\n <View testID=\"experience-container\" style={containerStyle}>\n {backgroundImage}\n {children}\n </View>\n );\n}\n\nfunction ExperienceText({ visible, text, color, fontSize, fontWeight }: { visible: boolean; text: string; color: string; fontSize: number; fontWeight: \"normal\" | \"bold\" }) {\n if (!visible) return null;\n return <Text style={{ color, fontSize, fontWeight }}>{text}</Text>;\n}\n\nexport function CardExperience({ content }: { content: ExperienceContent }): React.JSX.Element {\n const style = resolveExperienceStyle(\"card\", content.style);\n return (\n <ExperienceContainer content={content} style={style}>\n <ExperienceText visible={style.title.visible} text={content.title} color={style.title.color} fontSize={style.title.fontSize} fontWeight={style.title.fontWeight} />\n <ExperienceText visible={style.body.visible} text={content.body} color={style.body.color} fontSize={style.body.fontSize} fontWeight={style.body.fontWeight} />\n <ExperienceButton style={style} ctaLabel={content.ctaLabel} ctaUrl={content.ctaUrl} />\n </ExperienceContainer>\n );\n}\n\nexport function BannerExperience({ content }: { content: ExperienceContent }): React.JSX.Element {\n const style = resolveExperienceStyle(\"banner\", content.style);\n return (\n <ExperienceContainer content={content} style={style}>\n <ExperienceText visible={style.title.visible} text={content.title} color={style.title.color} fontSize={style.title.fontSize} fontWeight={style.title.fontWeight} />\n <ExperienceText visible={style.body.visible} text={content.body} color={style.body.color} fontSize={style.body.fontSize} fontWeight={style.body.fontWeight} />\n <ExperienceButton style={style} ctaLabel={content.ctaLabel} ctaUrl={content.ctaUrl} />\n </ExperienceContainer>\n );\n}\n\nexport function MessageExperience({ content }: { content: ExperienceContent }): React.JSX.Element {\n const style = resolveExperienceStyle(\"message\", content.style);\n return (\n <ExperienceContainer content={content} style={style}>\n <ExperienceText visible={style.title.visible} text={content.title} color={style.title.color} fontSize={style.title.fontSize} fontWeight={style.title.fontWeight} />\n <ExperienceText visible={style.body.visible} text={content.body} color={style.body.color} fontSize={style.body.fontSize} fontWeight={style.body.fontWeight} />\n <ExperienceButton style={style} ctaLabel={content.ctaLabel} ctaUrl={content.ctaUrl} />\n </ExperienceContainer>\n );\n}\n\n/** Picks the right renderer by `experience.type` — the one component most host apps need. */\nexport function ExperienceView({ experience }: { experience: ExperienceContent }): React.JSX.Element {\n switch (experience.type) {\n case \"banner\":\n return <BannerExperience content={experience} />;\n case \"message\":\n return <MessageExperience content={experience} />;\n case \"card\":\n default:\n return <CardExperience content={experience} />;\n }\n}\n"],"mappings":"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","names":["import_react_native","AsyncStorage","isDev","Location","import_async_storage","STORAGE_KEY","AsyncStorage","NetInfo","React"]}
|