@sharib/geopulse-react-native 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1188,7 +1188,10 @@ function ExperienceContainer({ content, style, children }) {
|
|
|
1188
1188
|
// "auto" means no explicit height at all — RN sizes a View to its content by default, so
|
|
1189
1189
|
// omitting the property (rather than passing "auto", which RN's height type doesn't accept)
|
|
1190
1190
|
// is what actually produces that behavior.
|
|
1191
|
-
|
|
1191
|
+
// The button is absolutely positioned, so it takes no layout space — without a floor, an
|
|
1192
|
+
// "auto" container shrinks to title+body and the button overlaps them. 160 matches the
|
|
1193
|
+
// admin-web preview (experience-preview.tsx) so the device renders what the portal shows.
|
|
1194
|
+
...style.container.height === "auto" ? { minHeight: 160 } : { height: style.container.height }
|
|
1192
1195
|
};
|
|
1193
1196
|
const backgroundImage = isSafeExternalUrl(style.container.backgroundImageUrl) ? /* @__PURE__ */ import_react.default.createElement(
|
|
1194
1197
|
import_react_native.Image,
|
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 * Base URL of the deployed GeoPulse Cloud Functions (e.g.\n * `https://us-central1-<project-id>.cloudfunctions.net`, or the local\n * Functions emulator's URL during development). Not part of the minimal\n * `{ apiKey, applicationId }` example in the docs because it's\n * deployment-specific, but required in practice — `initialize()` throws a\n * clear error if it's missing.\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\nexport const SDK_VERSION = \"0.3.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 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 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 ?? \"\", 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 if (!config.functionsBaseUrl) {\n throw new Error(\n \"GeoPulse.initialize requires functionsBaseUrl (the deployed Cloud Functions base URL, or the local emulator's URL in development)\",\n );\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 ...(style.container.height === \"auto\" ? {} : { 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/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 * Base URL of the deployed GeoPulse Cloud Functions (e.g.\n * `https://us-central1-<project-id>.cloudfunctions.net`, or the local\n * Functions emulator's URL during development). Not part of the minimal\n * `{ apiKey, applicationId }` example in the docs because it's\n * deployment-specific, but required in practice — `initialize()` throws a\n * clear error if it's missing.\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\nexport const SDK_VERSION = \"0.3.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 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 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 ?? \"\", 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 if (!config.functionsBaseUrl) {\n throw new Error(\n \"GeoPulse.initialize requires functionsBaseUrl (the deployed Cloud Functions base URL, or the local emulator's URL in development)\",\n );\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"]}
|