@adminide-stack/yantra-mobile 12.0.125 → 12.0.127-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/features/health/HealthSyncRunner.js +9 -1
- package/lib/features/health/HealthSyncRunner.js.map +1 -1
- package/lib/features/health/backgroundSync.js +41 -0
- package/lib/features/health/backgroundSync.js.map +1 -0
- package/lib/features/health/healthConnectBackground.js +32 -0
- package/lib/features/health/healthConnectBackground.js.map +1 -0
- package/lib/features/health/healthConnectReader.js +16 -2
- package/lib/features/health/healthConnectReader.js.map +1 -1
- package/lib/features/health/healthSync.js +31 -3
- package/lib/features/health/healthSync.js.map +1 -1
- package/lib/features/health/healthSyncStore.js +89 -5
- package/lib/features/health/healthSyncStore.js.map +1 -1
- package/lib/features/health/healthkitBackground.js +17 -0
- package/lib/features/health/healthkitBackground.js.map +1 -0
- package/lib/features/health/reader.js.map +1 -1
- package/lib/features/health/sessionVault.js +38 -0
- package/lib/features/health/sessionVault.js.map +1 -0
- package/lib/features/health/syncState.js.map +1 -1
- package/lib/features/messaging/useChatViewer.js +14 -18
- package/lib/features/messaging/useChatViewer.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/screens/Health/index.js +12 -2
- package/lib/screens/Health/index.js.map +1 -1
- package/package.json +6 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"healthSyncStore.js","sources":["../../../src/features/health/healthSyncStore.ts"],"sourcesContent":["/**\n * App-wide health sync state: one instance shared by the Health screen and\n * the root HealthSyncRunner (which owns the session and the foreground\n * trigger). A plain external store (useSyncExternalStore) instead of a React\n * context, so the runner can live at the app root while the screen mounts and\n * unmounts.\n *\n * Every sync is single-flight: a second trigger while one runs joins it.\n */\nimport { createFitnessClient } from './fitnessClient';\nimport type { HealthAvailability, HealthDataType, HealthReader } from './reader';\nimport { describeSyncError, runHealthSync } from './syncEngine';\nimport { INITIAL_SYNC_STATE, loadSyncState, saveSyncState, type HealthSyncState, type StorageLike } from './syncState';\nimport { deviceTimeZone, zoneClock } from './time';\n\n/** Foreground syncs are at most this frequent. \"Sync now\" ignores it. */\nexport const FOREGROUND_THROTTLE_MS = 15 * 60_000;\n/** How long a sync waits for the sign-in session before giving up. */\nconst SESSION_WAIT_MS = 30_000;\n\nexport interface HealthSession {\n token: string;\n url: string;\n /** Account the token belongs to. */\n owner?: string;\n}\n\nexport interface HealthSyncSnapshot {\n loaded: boolean;\n sync: HealthSyncState;\n running: boolean;\n availability?: HealthAvailability;\n /** Granted types; null = the platform does not tell (Apple Health). */\n granted?: HealthDataType[] | null;\n /** The data types this phone's health store is read for (platform and OS version). */\n types?: readonly HealthDataType[];\n /** Plain words, when signing in for sync failed. */\n sessionError?: string | null;\n /** A connect / disconnect action in progress. */\n busy?: 'connecting' | 'disconnecting';\n}\n\nexport interface HealthSyncStoreDeps {\n storage: StorageLike;\n /** Resolves null when this build has no health store (no native module, unsupported OS). */\n createReader: () => Promise<HealthReader | null>;\n fetchImpl?: typeof fetch;\n now?: () => number;\n timeZone?: () => string;\n}\n\nexport class HealthSyncStore {\n private snapshot: HealthSyncSnapshot = { loaded: false, sync: { ...INITIAL_SYNC_STATE }, running: false };\n\n private listeners = new Set<() => void>();\n\n private loading: Promise<void> | null = null;\n\n private inFlight: Promise<boolean> | null = null;\n\n private reader: Promise<HealthReader | null> | null = null;\n\n private session: HealthSession | null = null;\n\n private sessionWaiters: Array<(s: HealthSession | null) => void> = [];\n\n /** Bumped by disconnect, so a sync still finishing cannot turn sync back on. */\n private generation = 0;\n\n private readonly deps: HealthSyncStoreDeps;\n\n constructor(deps: HealthSyncStoreDeps) {\n this.deps = deps;\n }\n\n subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): HealthSyncSnapshot => this.snapshot;\n\n private set(patch: Partial<HealthSyncSnapshot>) {\n this.snapshot = { ...this.snapshot, ...patch };\n this.listeners.forEach((l) => l());\n }\n\n private getReader(): Promise<HealthReader | null> {\n if (!this.reader) this.reader = this.deps.createReader().catch(() => null);\n return this.reader;\n }\n\n /** Opens the platform's own permission settings (Health Connect); false when there is none. */\n async openStoreSettings(): Promise<boolean> {\n const reader = await this.getReader();\n if (!reader?.openSettings) return false;\n try {\n await reader.openSettings();\n return true;\n } catch {\n return false;\n }\n }\n\n /** Load the saved state once; safe to call from many places. */\n load(): Promise<void> {\n if (!this.loading) {\n this.loading = (async () => {\n const sync = await loadSyncState(this.deps.storage);\n this.set({ loaded: true, sync });\n })();\n }\n return this.loading;\n }\n\n private async persist(sync: HealthSyncState) {\n this.set({ sync });\n await saveSyncState(this.deps.storage, sync);\n }\n\n /** The root runner hands over the sign-in session (or null when it is gone). */\n setSession(session: HealthSession | null, error?: string | null) {\n this.session = session;\n this.set({ sessionError: session ? null : (error ?? null) });\n if (session) {\n const waiters = this.sessionWaiters;\n this.sessionWaiters = [];\n waiters.forEach((w) => w(session));\n }\n }\n\n private waitForSession(): Promise<HealthSession | null> {\n if (this.session) return Promise.resolve(this.session);\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.sessionWaiters = this.sessionWaiters.filter((w) => w !== done);\n resolve(null);\n }, SESSION_WAIT_MS);\n const done = (s: HealthSession | null) => {\n clearTimeout(timer);\n resolve(s);\n };\n this.sessionWaiters.push(done);\n });\n }\n\n async refreshStatus(): Promise<void> {\n await this.load();\n const reader = await this.getReader();\n if (!reader) {\n this.set({\n availability: {\n status: 'unsupported',\n reason: 'Health data is not available in this version of the app. Update Yantra to connect.',\n },\n });\n return;\n }\n this.set({ types: reader.types });\n const availability = await reader.availability();\n this.set({ availability });\n if (availability.status === 'available' && this.snapshot.sync.connected) {\n try {\n this.set({ granted: await reader.grantedTypes() });\n } catch {\n this.set({ granted: undefined });\n }\n }\n }\n\n /** Ask for access, turn sync on, run the 30-day backfill. Resolves false if not connected. */\n async connect(): Promise<boolean> {\n await this.load();\n const reader = await this.getReader();\n if (!reader) return false;\n this.set({ busy: 'connecting' });\n try {\n const availability = await reader.availability();\n this.set({ availability });\n if (availability.status !== 'available') return false;\n const granted = await reader.requestAccess();\n this.set({ granted });\n if (granted && granted.length === 0) {\n await this.persist({\n ...this.snapshot.sync,\n lastError: `No data types were allowed. Allow them in ${reader.storeName} to sync.`,\n });\n return false;\n }\n const now = new Date(this.now()).toISOString();\n await this.persist({\n ...this.snapshot.sync,\n connected: true,\n source: reader.source,\n connectedAt: this.snapshot.sync.connectedAt ?? now,\n lastError: undefined,\n });\n } catch (err) {\n await this.persist({ ...this.snapshot.sync, lastError: describeSyncError(err) });\n return false;\n } finally {\n this.set({ busy: undefined });\n }\n void this.syncNow({ interactive: false });\n return true;\n }\n\n /**\n * Stop syncing. With `deleteData`, also delete everything this phone sent\n * to Yantra (the coach then forgets it). Health Connect access is given\n * back; Apple Health access can only be removed in the Health app.\n */\n async disconnect(opts: { deleteData: boolean }): Promise<{ ok: boolean; message?: string }> {\n await this.load();\n const reader = await this.getReader();\n this.set({ busy: 'disconnecting' });\n try {\n if (opts.deleteData && reader) {\n const session = await this.waitForSession();\n if (!session) {\n return { ok: false, message: 'Could not sign in to delete the data. Try again in a moment.' };\n }\n try {\n await createFitnessClient({\n url: session.url,\n token: session.token,\n fetchImpl: this.deps.fetchImpl,\n }).deleteSource(reader.source);\n } catch (err) {\n return { ok: false, message: describeSyncError(err) };\n }\n }\n this.generation += 1;\n await reader?.revoke();\n await this.persist({ ...INITIAL_SYNC_STATE });\n this.set({ granted: undefined });\n return { ok: true };\n } finally {\n this.set({ busy: undefined });\n }\n }\n\n /** Foreground trigger: syncs when connected and the last attempt is old enough. */\n async syncIfDue(): Promise<void> {\n await this.load();\n const { sync } = this.snapshot;\n if (!sync.connected) return;\n const last = sync.lastAttemptAt ? Date.parse(sync.lastAttemptAt) : 0;\n if (this.now() - last < FOREGROUND_THROTTLE_MS) return;\n await this.syncNow({ interactive: false });\n }\n\n /**\n * Run a sync now (joins one already running). `interactive` = the person\n * tapped \"Sync now\": on Apple Health this re-shows the permission sheet\n * if HealthKit still needs it, which an automatic sync never does.\n */\n syncNow(opts: { interactive: boolean } = { interactive: true }): Promise<boolean> {\n if (this.inFlight) return this.inFlight;\n this.inFlight = (async () => {\n this.set({ running: true });\n const { generation } = this;\n try {\n await this.load();\n const reader = await this.getReader();\n if (!reader || !this.snapshot.sync.connected) return false;\n if (opts.interactive) {\n try {\n const granted = await reader.requestAccess();\n this.set({ granted });\n } catch (err) {\n // The read would only repeat \"tap Sync now\"; say why the sheet did not open.\n const message = err instanceof Error ? err.message : String(err);\n await this.persist({\n ...this.snapshot.sync,\n lastAttemptAt: new Date(this.now()).toISOString(),\n lastError: `Could not ask ${reader.storeName} for access: ${message}`,\n });\n return false;\n }\n }\n const session = await this.waitForSession();\n if (generation !== this.generation) return false;\n if (!session) {\n await this.persist({\n ...this.snapshot.sync,\n lastAttemptAt: new Date(this.now()).toISOString(),\n lastError:\n this.snapshot.sessionError ||\n 'Could not sign in to Yantra to sync. Check your connection and try again.',\n });\n return false;\n }\n const outcome = await runHealthSync(this.snapshot.sync, {\n reader,\n client: createFitnessClient({\n url: session.url,\n token: session.token,\n fetchImpl: this.deps.fetchImpl,\n }),\n clock: zoneClock((this.deps.timeZone ?? deviceTimeZone)()),\n owner: session.owner,\n now: () => this.now(),\n onProgress: async (state) => {\n if (generation === this.generation) await this.persist(state);\n },\n });\n return outcome.ok;\n } finally {\n this.inFlight = null;\n this.set({ running: false });\n }\n })();\n return this.inFlight;\n }\n\n private now(): number {\n return (this.deps.now ?? Date.now)();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"healthSyncStore.js","sources":["../../../src/features/health/healthSyncStore.ts"],"sourcesContent":["/**\n * App-wide health sync state: one instance shared by the Health screen and\n * the root HealthSyncRunner (which owns the session and the foreground\n * trigger). A plain external store (useSyncExternalStore) instead of a React\n * context, so the runner can live at the app root while the screen mounts and\n * unmounts.\n *\n * Every sync is single-flight: a second trigger while one runs joins it.\n *\n * In the background (backgroundSync.ts: iOS woke the app because the watch\n * wrote new sleep, HRV or a workout, or Android's hourly job ran) there is no\n * React tree and no way to sign in, so `syncInBackground` uses the session the\n * runner last handed over, kept in the secure store (`vault`). The platform\n * system token lasts days; one close to expiring waits for the next time the\n * app is opened, as before.\n */\nimport { tokenExpiryMs } from '../canvas/sessionIdentity';\nimport { createFitnessClient } from './fitnessClient';\nimport type { HealthAvailability, HealthDataType, HealthReader } from './reader';\nimport { describeSyncError, runHealthSync } from './syncEngine';\nimport { INITIAL_SYNC_STATE, loadSyncState, saveSyncState, type HealthSyncState, type StorageLike } from './syncState';\nimport { deviceTimeZone, zoneClock } from './time';\n\n/** Foreground syncs are at most this frequent. \"Sync now\" ignores it. */\nexport const FOREGROUND_THROTTLE_MS = 15 * 60_000;\n/** How long a sync waits for the sign-in session before giving up. */\nconst SESSION_WAIT_MS = 30_000;\n/**\n * A background wake this soon after the last attempt is the same burst: sleep,\n * HRV and resting heart rate land within seconds of each other.\n */\nexport const BACKGROUND_MIN_GAP_MS = 2 * 60_000;\n/** A saved session this close to expiring is left for the app to replace. */\nexport const SESSION_MARGIN_MS = 5 * 60_000;\n\nexport interface HealthSession {\n token: string;\n url: string;\n /** Account the token belongs to. */\n owner?: string;\n}\n\nexport interface HealthSyncSnapshot {\n loaded: boolean;\n sync: HealthSyncState;\n running: boolean;\n availability?: HealthAvailability;\n /** Granted types; null = the platform does not tell (Apple Health). */\n granted?: HealthDataType[] | null;\n /** Health Connect lets Yantra read while closed. Unset where that needs no permission (Apple Health). */\n backgroundAccess?: boolean;\n /** The data types this phone's health store is read for (platform and OS version). */\n types?: readonly HealthDataType[];\n /** Plain words, when signing in for sync failed. */\n sessionError?: string | null;\n /** A connect / disconnect action in progress. */\n busy?: 'connecting' | 'disconnecting';\n}\n\n/**\n * Where the last session is kept for background syncs. `read` THROWS when the\n * phone is locked (the secure store is unreadable then, and so is Apple\n * Health), which is how a background wake knows to wait.\n */\nexport interface SessionVault {\n read(): Promise<HealthSession | null>;\n write(session: HealthSession): Promise<void>;\n clear(): Promise<void>;\n}\n\n/** The platform's background delivery: on after a connect, off after a disconnect. */\nexport interface BackgroundDelivery {\n enable(): Promise<void>;\n disable(): Promise<void>;\n}\n\n/** Background delivery, and the wakes it brings (backgroundSync.ts). */\nexport interface BackgroundSource extends BackgroundDelivery {\n /**\n * Calls onChange whenever there may be new data: a watched type got samples\n * (iOS), the hourly job ran (Android). It resolves once the sync is done,\n * for a job that must not finish before it. Returns the unsubscribe.\n */\n watch(onChange: () => Promise<void>): () => void;\n}\n\nexport interface HealthSyncStoreDeps {\n storage: StorageLike;\n /** Resolves null when this build has no health store (no native module, unsupported OS). */\n createReader: () => Promise<HealthReader | null>;\n fetchImpl?: typeof fetch;\n now?: () => number;\n timeZone?: () => string;\n vault?: SessionVault;\n background?: BackgroundDelivery;\n /** Still signed in to the app (the login token is there). Only asked when the phone is unlocked. */\n signedIn?: () => Promise<boolean>;\n}\n\nexport class HealthSyncStore {\n private snapshot: HealthSyncSnapshot = { loaded: false, sync: { ...INITIAL_SYNC_STATE }, running: false };\n\n private listeners = new Set<() => void>();\n\n private loading: Promise<void> | null = null;\n\n private inFlight: Promise<boolean> | null = null;\n\n private reader: Promise<HealthReader | null> | null = null;\n\n private session: HealthSession | null = null;\n\n private sessionWaiters: Array<(s: HealthSession | null) => void> = [];\n\n /** Bumped by disconnect, so a sync still finishing cannot turn sync back on. */\n private generation = 0;\n\n private readonly deps: HealthSyncStoreDeps;\n\n constructor(deps: HealthSyncStoreDeps) {\n this.deps = deps;\n }\n\n subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): HealthSyncSnapshot => this.snapshot;\n\n private set(patch: Partial<HealthSyncSnapshot>) {\n this.snapshot = { ...this.snapshot, ...patch };\n this.listeners.forEach((l) => l());\n }\n\n private getReader(): Promise<HealthReader | null> {\n if (!this.reader) this.reader = this.deps.createReader().catch(() => null);\n return this.reader;\n }\n\n /** Opens the platform's own permission settings (Health Connect); false when there is none. */\n async openStoreSettings(): Promise<boolean> {\n const reader = await this.getReader();\n if (!reader?.openSettings) return false;\n try {\n await reader.openSettings();\n return true;\n } catch {\n return false;\n }\n }\n\n /** Load the saved state once; safe to call from many places. */\n load(): Promise<void> {\n if (!this.loading) {\n this.loading = (async () => {\n const sync = await loadSyncState(this.deps.storage);\n this.set({ loaded: true, sync });\n })();\n }\n return this.loading;\n }\n\n private async persist(sync: HealthSyncState) {\n this.set({ sync });\n await saveSyncState(this.deps.storage, sync);\n }\n\n /** The root runner hands over the sign-in session (or null when it is gone). */\n setSession(session: HealthSession | null, error?: string | null) {\n this.session = session;\n this.set({ sessionError: session ? null : (error ?? null) });\n // Kept for background syncs. A null session is a failed or pending mint, not a sign-out:\n // the saved one stays until it expires, is replaced, or forgetSavedSession clears it.\n if (session) void this.deps.vault?.write(session).catch(() => undefined);\n if (session) {\n const waiters = this.sessionWaiters;\n this.sessionWaiters = [];\n waiters.forEach((w) => w(session));\n }\n }\n\n /** Signed out, or sync turned off: no background sync may use the saved session. */\n async forgetSavedSession(): Promise<void> {\n await this.deps.vault?.clear().catch(() => undefined);\n }\n\n /**\n * Turn background delivery on for someone already connected - at every\n * launch, so people who connected before this existed get it too.\n */\n async ensureBackgroundDelivery(): Promise<void> {\n await this.load();\n if (this.snapshot.sync.connected) await this.deps.background?.enable().catch(() => undefined);\n }\n\n /**\n * After anything that can change what was granted: delivery on (repeating\n * it is harmless), and Health Connect's answer to reading while closed kept\n * for the screen. `enable` schedules or drops Android's hourly job to\n * match, so access given in Health Connect's own settings counts once the\n * screen is back.\n */\n private async refreshBackgroundAccess(reader: HealthReader): Promise<void> {\n if (reader.canReadInBackground) {\n this.set({ backgroundAccess: await reader.canReadInBackground().catch(() => false) });\n }\n await this.deps.background?.enable().catch(() => undefined);\n }\n\n private waitForSession(): Promise<HealthSession | null> {\n if (this.session) return Promise.resolve(this.session);\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.sessionWaiters = this.sessionWaiters.filter((w) => w !== done);\n resolve(null);\n }, SESSION_WAIT_MS);\n const done = (s: HealthSession | null) => {\n clearTimeout(timer);\n resolve(s);\n };\n this.sessionWaiters.push(done);\n });\n }\n\n async refreshStatus(): Promise<void> {\n await this.load();\n const reader = await this.getReader();\n if (!reader) {\n this.set({\n availability: {\n status: 'unsupported',\n reason: 'Health data is not available in this version of the app. Update Yantra to connect.',\n },\n });\n return;\n }\n this.set({ types: reader.types });\n const availability = await reader.availability();\n this.set({ availability });\n if (availability.status === 'available' && this.snapshot.sync.connected) {\n try {\n this.set({ granted: await reader.grantedTypes() });\n } catch {\n this.set({ granted: undefined });\n }\n await this.refreshBackgroundAccess(reader);\n }\n }\n\n /** Ask for access, turn sync on, run the 30-day backfill. Resolves false if not connected. */\n async connect(): Promise<boolean> {\n await this.load();\n const reader = await this.getReader();\n if (!reader) return false;\n this.set({ busy: 'connecting' });\n try {\n const availability = await reader.availability();\n this.set({ availability });\n if (availability.status !== 'available') return false;\n const granted = await reader.requestAccess();\n this.set({ granted });\n if (granted && granted.length === 0) {\n await this.persist({\n ...this.snapshot.sync,\n lastError: `No data types were allowed. Allow them in ${reader.storeName} to sync.`,\n });\n return false;\n }\n const now = new Date(this.now()).toISOString();\n await this.persist({\n ...this.snapshot.sync,\n connected: true,\n source: reader.source,\n connectedAt: this.snapshot.sync.connectedAt ?? now,\n lastError: undefined,\n ...(reader.canReadInBackground ? { backgroundAskedAt: now } : {}),\n });\n void this.refreshBackgroundAccess(reader);\n } catch (err) {\n await this.persist({ ...this.snapshot.sync, lastError: describeSyncError(err) });\n return false;\n } finally {\n this.set({ busy: undefined });\n }\n void this.syncNow({ interactive: false });\n return true;\n }\n\n /**\n * Stop syncing. With `deleteData`, also delete everything this phone sent\n * to Yantra (the coach then forgets it). Health Connect access is given\n * back; Apple Health access can only be removed in the Health app.\n */\n async disconnect(opts: { deleteData: boolean }): Promise<{ ok: boolean; message?: string }> {\n await this.load();\n const reader = await this.getReader();\n this.set({ busy: 'disconnecting' });\n try {\n if (opts.deleteData && reader) {\n const session = await this.waitForSession();\n if (!session) {\n return { ok: false, message: 'Could not sign in to delete the data. Try again in a moment.' };\n }\n try {\n await createFitnessClient({\n url: session.url,\n token: session.token,\n fetchImpl: this.deps.fetchImpl,\n }).deleteSource(reader.source);\n } catch (err) {\n return { ok: false, message: describeSyncError(err) };\n }\n }\n this.generation += 1;\n await reader?.revoke();\n await this.deps.background?.disable().catch(() => undefined);\n await this.forgetSavedSession();\n await this.persist({ ...INITIAL_SYNC_STATE });\n this.set({ granted: undefined, backgroundAccess: undefined });\n return { ok: true };\n } finally {\n this.set({ busy: undefined });\n }\n }\n\n /** Foreground trigger: syncs when connected and the last attempt is old enough. */\n async syncIfDue(): Promise<void> {\n await this.load();\n const { sync } = this.snapshot;\n if (!sync.connected) return;\n const last = sync.lastAttemptAt ? Date.parse(sync.lastAttemptAt) : 0;\n if (this.now() - last < FOREGROUND_THROTTLE_MS) return;\n await this.syncNow({ interactive: false });\n }\n\n /**\n * A background wake: iOS woke the app because the watch saved new data, or\n * Android's hourly job ran, maybe with no screen and no sign-in. Syncs with\n * the session the app last had, unless the phone is locked (iOS: nothing\n * is readable then), the person signed out, the session is about to\n * expire, or this is the same burst as the last attempt. Every \"no\" is\n * silent: the next open syncs as it always did.\n */\n async syncInBackground(): Promise<boolean> {\n await this.load();\n const { sync } = this.snapshot;\n if (!sync.connected) return false;\n const last = sync.lastAttemptAt ? Date.parse(sync.lastAttemptAt) : 0;\n if (this.now() - last < BACKGROUND_MIN_GAP_MS) return false;\n let saved: HealthSession | null;\n try {\n saved = (await this.deps.vault?.read()) ?? null;\n } catch {\n // Locked: the secure store and Apple Health are both unreadable until it is unlocked.\n return false;\n }\n // Unlocked from here on, so a missing login token really means signed out.\n if (this.deps.signedIn && !(await this.deps.signedIn().catch(() => false))) {\n await this.forgetSavedSession();\n return false;\n }\n const session = this.session ?? saved;\n if (!session) return false;\n const expires = tokenExpiryMs(session.token);\n if (expires !== null && expires - this.now() < SESSION_MARGIN_MS) return false;\n if (!this.session) this.session = session;\n return this.syncNow({ interactive: false });\n }\n\n /**\n * Run a sync now (joins one already running). `interactive` = the person\n * tapped \"Sync now\": on Apple Health this re-shows the permission sheet\n * if HealthKit still needs it, which an automatic sync never does.\n */\n syncNow(opts: { interactive: boolean } = { interactive: true }): Promise<boolean> {\n if (this.inFlight) return this.inFlight;\n this.inFlight = (async () => {\n this.set({ running: true });\n const { generation } = this;\n try {\n await this.load();\n const reader = await this.getReader();\n if (!reader || !this.snapshot.sync.connected) return false;\n if (opts.interactive) {\n try {\n const granted = await reader.requestAccess();\n this.set({ granted });\n if (reader.canReadInBackground) {\n await this.persist({\n ...this.snapshot.sync,\n backgroundAskedAt: new Date(this.now()).toISOString(),\n });\n }\n void this.refreshBackgroundAccess(reader);\n } catch (err) {\n // The read would only repeat \"tap Sync now\"; say why the sheet did not open.\n const message = err instanceof Error ? err.message : String(err);\n await this.persist({\n ...this.snapshot.sync,\n lastAttemptAt: new Date(this.now()).toISOString(),\n lastError: `Could not ask ${reader.storeName} for access: ${message}`,\n });\n return false;\n }\n }\n const session = await this.waitForSession();\n if (generation !== this.generation) return false;\n if (!session) {\n await this.persist({\n ...this.snapshot.sync,\n lastAttemptAt: new Date(this.now()).toISOString(),\n lastError:\n this.snapshot.sessionError ||\n 'Could not sign in to Yantra to sync. Check your connection and try again.',\n });\n return false;\n }\n const outcome = await runHealthSync(this.snapshot.sync, {\n reader,\n client: createFitnessClient({\n url: session.url,\n token: session.token,\n fetchImpl: this.deps.fetchImpl,\n }),\n clock: zoneClock((this.deps.timeZone ?? deviceTimeZone)()),\n owner: session.owner,\n now: () => this.now(),\n onProgress: async (state) => {\n if (generation === this.generation) await this.persist(state);\n },\n });\n return outcome.ok;\n } finally {\n this.inFlight = null;\n this.set({ running: false });\n }\n })();\n return this.inFlight;\n }\n\n private now(): number {\n return (this.deps.now ?? Date.now)();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {subscribeToChanges,clearBackgroundTypes,disableAllBackgroundDelivery,configureBackgroundTypes}from'@kingstinct/react-native-healthkit';const BACKGROUND_TYPES = ["HKCategoryTypeIdentifierSleepAnalysis", "HKQuantityTypeIdentifierHeartRateVariabilitySDNN", "HKQuantityTypeIdentifierRestingHeartRate", "HKWorkoutTypeIdentifier", "HKQuantityTypeIdentifierStepCount", "HKQuantityTypeIdentifierBodyMass"];
|
|
2
|
+
const IMMEDIATE = 1;
|
|
3
|
+
const healthkitBackground = {
|
|
4
|
+
async enable() {
|
|
5
|
+
await configureBackgroundTypes([...BACKGROUND_TYPES], IMMEDIATE);
|
|
6
|
+
},
|
|
7
|
+
async disable() {
|
|
8
|
+
await clearBackgroundTypes();
|
|
9
|
+
await disableAllBackgroundDelivery();
|
|
10
|
+
},
|
|
11
|
+
watch(onChange) {
|
|
12
|
+
const subs = BACKGROUND_TYPES.map((type) => subscribeToChanges(type, () => {
|
|
13
|
+
void onChange();
|
|
14
|
+
}));
|
|
15
|
+
return () => subs.forEach((s) => s.remove());
|
|
16
|
+
}
|
|
17
|
+
};export{BACKGROUND_TYPES,healthkitBackground};//# sourceMappingURL=healthkitBackground.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"healthkitBackground.js","sources":["../../../src/features/health/healthkitBackground.ts"],"sourcesContent":["/**\n * Apple Health background delivery: iOS wakes Yantra when one of these types\n * gets new samples (the watch syncs last night's sleep, a workout ends, a\n * scale weighs in), the way Bevel stays current without being opened.\n *\n * The library does the native half: `configureBackgroundTypes` saves the list\n * and registers observer queries at every launch before JS loads, queuing\n * what fires until `subscribeToChanges` is called, and holds each wake open\n * about 25 seconds. It needs the background-delivery entitlement (the\n * HealthKit config plugin's `background` in app.json) - a build without it\n * still runs the observers in the foreground only.\n *\n * Loaded with a dynamic import on iOS only (healthSync.ts), like the reader.\n */\nimport {\n clearBackgroundTypes,\n configureBackgroundTypes,\n disableAllBackgroundDelivery,\n subscribeToChanges,\n} from '@kingstinct/react-native-healthkit';\n\ntype Frequency = Parameters<typeof configureBackgroundTypes>[1];\ntype SampleType = Parameters<typeof subscribeToChanges>[0];\n\n/**\n * What is worth waking for. Steps keep the day's strain current (iOS delivers\n * them at most hourly whatever is asked); the rest arrive a few times a day.\n */\nexport const BACKGROUND_TYPES = [\n 'HKCategoryTypeIdentifierSleepAnalysis',\n 'HKQuantityTypeIdentifierHeartRateVariabilitySDNN',\n 'HKQuantityTypeIdentifierRestingHeartRate',\n 'HKWorkoutTypeIdentifier',\n 'HKQuantityTypeIdentifierStepCount',\n 'HKQuantityTypeIdentifierBodyMass',\n] as const;\n\n/** HKUpdateFrequency.immediate. */\nconst IMMEDIATE = 1 as Frequency;\n\nexport const healthkitBackground = {\n async enable(): Promise<void> {\n // Safe to repeat: the library stops the observers it had before registering again.\n await configureBackgroundTypes([...BACKGROUND_TYPES], IMMEDIATE);\n },\n async disable(): Promise<void> {\n // Nothing is registered at the next launch, and HealthKit stops waking the app. Only\n // health sync uses background delivery, so all of it can go.\n await clearBackgroundTypes();\n await disableAllBackgroundDelivery();\n },\n watch(onChange: () => Promise<void>): () => void {\n // The library holds each wake open on its own, so nothing waits for the sync here.\n const subs = BACKGROUND_TYPES.map((type) =>\n subscribeToChanges(type as SampleType, () => {\n void onChange();\n }),\n );\n return () => subs.forEach((s) => s.remove());\n },\n};\n"],"names":[],"mappings":"+IAsBO,MAAM,mBAAmB,CAAC,uCAAA,EAAyC,oDAAoD,0CAA4C,EAAA,yBAAA,EAA2B,qCAAqC,kCAAkC;AAG5Q,MAAM,SAAY,GAAA,CAAA;AACX,MAAM,mBAAsB,GAAA;AAAA,EACjC,MAAM,MAAwB,GAAA;AAE5B,IAAA,MAAM,wBAAyB,CAAA,CAAC,GAAG,gBAAgB,GAAG,SAAS,CAAA;AAAA,GACjE;AAAA,EACA,MAAM,OAAyB,GAAA;AAG7B,IAAA,MAAM,oBAAqB,EAAA;AAC3B,IAAA,MAAM,4BAA6B,EAAA;AAAA,GACrC;AAAA,EACA,MAAM,QAA2C,EAAA;AAE/C,IAAA,MAAM,OAAO,gBAAiB,CAAA,GAAA,CAAI,CAAQ,IAAA,KAAA,kBAAA,CAAmB,MAAoB,MAAM;AACrF,MAAA,KAAK,QAAS,EAAA;AAAA,KACf,CAAC,CAAA;AACF,IAAA,OAAO,MAAM,IAAK,CAAA,OAAA,CAAQ,CAAK,CAAA,KAAA,CAAA,CAAE,QAAQ,CAAA;AAAA;AAE7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader.js","sources":["../../../src/features/health/reader.ts"],"sourcesContent":["/**\n * What the sync engine needs from a platform. healthkitReader.ts (iOS) and\n * healthConnectReader.ts (Android) implement it; tests use fakes.\n */\nimport type { RawHealthData } from './model';\nimport type { ZoneClock } from './time';\nimport type { DeviceHealthSource } from './types';\n\nexport type HealthDataType =\n | 'steps'\n | 'distance'\n | 'activeCalories'\n | 'restingCalories'\n | 'exercise'\n | 'heartRate'\n | 'restingHr'\n | 'hrv'\n | 'sleep'\n | 'wristTemp'\n | 'workouts'\n | 'elevation'\n | 'daylight'\n | 'mindful'\n | 'weight'\n | 'vo2Max'\n | 'spo2'\n | 'respiratoryRate'\n | 'bodyFat'\n | 'leanMass'\n | 'bmi'\n | 'waist'\n | 'bodyTemp'\n | 'bloodPressure'\n | 'bloodGlucose';\n\n/**\n * What the screen lists under \"Data Yantra reads\", in this order. Each\n * platform reads some of them (`HealthReader.types`); the screen lists those.\n */\nexport const HEALTH_DATA_TYPES: ReadonlyArray<{ id: HealthDataType; label: string }> = [\n { id: 'sleep', label: 'Sleep' },\n { id: 'wristTemp', label: 'Wrist temperature' },\n { id: 'heartRate', label: 'Heart rate' },\n { id: 'restingHr', label: 'Resting heart rate' },\n { id: 'hrv', label: 'Heart rate variability' },\n { id: 'steps', label: 'Steps' },\n { id: 'distance', label: 'Distance' },\n { id: 'activeCalories', label: 'Active energy' },\n { id: 'restingCalories', label: 'Resting energy' },\n { id: 'exercise', label: 'Exercise minutes' },\n { id: 'workouts', label: 'Workouts' },\n { id: 'elevation', label: 'Elevation gained' },\n { id: 'daylight', label: 'Time in daylight' },\n { id: 'mindful', label: 'Mindful minutes' },\n { id: 'weight', label: 'Weight' },\n { id: 'vo2Max', label: 'VO2 max' },\n { id: 'spo2', label: 'Blood oxygen' },\n { id: 'respiratoryRate', label: 'Respiratory rate' },\n { id: 'bodyFat', label: 'Body fat' },\n { id: 'leanMass', label: 'Lean body mass' },\n { id: 'bmi', label: 'BMI' },\n { id: 'waist', label: 'Waist' },\n { id: 'bodyTemp', label: 'Body temperature' },\n { id: 'bloodPressure', label: 'Blood pressure' },\n { id: 'bloodGlucose', label: 'Blood glucose' },\n];\n\nexport function labelOf(type: HealthDataType): string {\n return HEALTH_DATA_TYPES.find((t) => t.id === type)?.label ?? type;\n}\n\nexport type HealthAvailability =\n | { status: 'available' }\n /** Android: the Health Connect app is not installed (Android 13 and older). */\n | { status: 'not-installed' }\n /** Android: Health Connect is installed but needs an update. */\n | { status: 'needs-update' }\n | { status: 'unsupported'; reason: string };\n\nexport interface ReadResult {\n raw: RawHealthData;\n /** Types that could not be read (permission off, provider error). The rest still syncs. */\n unreadable: HealthDataType[];\n /**\n * Types this version of the app reads that the person was never asked\n * about (added in an update, or by a newer OS). They are not read until\n * the permission sheet shows them; the rest still syncs.\n */\n needsAccess?: HealthDataType[];\n}\n\nexport interface HealthReader {\n readonly source: DeviceHealthSource;\n /** What the platform calls its health store, for the screen. */\n readonly storeName: string;\n /** The data types read on this device (platform and OS version). */\n readonly types: readonly HealthDataType[];\n availability(): Promise<HealthAvailability>;\n /**\n * Shows the system permission sheet. Resolves with the types granted, or\n * null when the platform does not tell apps (HealthKit hides read access).\n */\n requestAccess(): Promise<HealthDataType[] | null>;\n /** Granted read types, or null when the platform does not tell apps. */\n grantedTypes(): Promise<HealthDataType[] | null>;\n /** Everything recorded between two instants. */\n read(range: { start: number; end: number }, clock: ZoneClock): Promise<ReadResult>;\n /** Give the permissions back where the platform allows it (Health Connect); a no-op on iOS. */\n revoke(): Promise<void>;\n /** The platform's own screen for this app's permissions, when it has one. */\n openSettings?(): Promise<void>;\n}\n"],"names":[],"mappings":"AAaO,MAAM,oBAGR,CAAC;AAAA,EACJ,EAAI,EAAA,OAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,KAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,OAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,gBAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,iBAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,SAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,QAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,QAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,MAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,iBAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,SAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,KAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,OAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,eAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,cAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAC;AACM,SAAS,QAAQ,IAA8B,EAAA;AA5FtD,EAAA,IAAA,EAAA,EAAA,EAAA;AA6FE,EAAO,OAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,iBAAA,CAAkB,KAAK,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,IAAI,CAAA,KAAzC,IAA4C,GAAA,MAAA,GAAA,EAAA,CAAA,KAAA,KAA5C,IAAqD,GAAA,EAAA,GAAA,IAAA;AAC9D"}
|
|
1
|
+
{"version":3,"file":"reader.js","sources":["../../../src/features/health/reader.ts"],"sourcesContent":["/**\n * What the sync engine needs from a platform. healthkitReader.ts (iOS) and\n * healthConnectReader.ts (Android) implement it; tests use fakes.\n */\nimport type { RawHealthData } from './model';\nimport type { ZoneClock } from './time';\nimport type { DeviceHealthSource } from './types';\n\nexport type HealthDataType =\n | 'steps'\n | 'distance'\n | 'activeCalories'\n | 'restingCalories'\n | 'exercise'\n | 'heartRate'\n | 'restingHr'\n | 'hrv'\n | 'sleep'\n | 'wristTemp'\n | 'workouts'\n | 'elevation'\n | 'daylight'\n | 'mindful'\n | 'weight'\n | 'vo2Max'\n | 'spo2'\n | 'respiratoryRate'\n | 'bodyFat'\n | 'leanMass'\n | 'bmi'\n | 'waist'\n | 'bodyTemp'\n | 'bloodPressure'\n | 'bloodGlucose';\n\n/**\n * What the screen lists under \"Data Yantra reads\", in this order. Each\n * platform reads some of them (`HealthReader.types`); the screen lists those.\n */\nexport const HEALTH_DATA_TYPES: ReadonlyArray<{ id: HealthDataType; label: string }> = [\n { id: 'sleep', label: 'Sleep' },\n { id: 'wristTemp', label: 'Wrist temperature' },\n { id: 'heartRate', label: 'Heart rate' },\n { id: 'restingHr', label: 'Resting heart rate' },\n { id: 'hrv', label: 'Heart rate variability' },\n { id: 'steps', label: 'Steps' },\n { id: 'distance', label: 'Distance' },\n { id: 'activeCalories', label: 'Active energy' },\n { id: 'restingCalories', label: 'Resting energy' },\n { id: 'exercise', label: 'Exercise minutes' },\n { id: 'workouts', label: 'Workouts' },\n { id: 'elevation', label: 'Elevation gained' },\n { id: 'daylight', label: 'Time in daylight' },\n { id: 'mindful', label: 'Mindful minutes' },\n { id: 'weight', label: 'Weight' },\n { id: 'vo2Max', label: 'VO2 max' },\n { id: 'spo2', label: 'Blood oxygen' },\n { id: 'respiratoryRate', label: 'Respiratory rate' },\n { id: 'bodyFat', label: 'Body fat' },\n { id: 'leanMass', label: 'Lean body mass' },\n { id: 'bmi', label: 'BMI' },\n { id: 'waist', label: 'Waist' },\n { id: 'bodyTemp', label: 'Body temperature' },\n { id: 'bloodPressure', label: 'Blood pressure' },\n { id: 'bloodGlucose', label: 'Blood glucose' },\n];\n\nexport function labelOf(type: HealthDataType): string {\n return HEALTH_DATA_TYPES.find((t) => t.id === type)?.label ?? type;\n}\n\nexport type HealthAvailability =\n | { status: 'available' }\n /** Android: the Health Connect app is not installed (Android 13 and older). */\n | { status: 'not-installed' }\n /** Android: Health Connect is installed but needs an update. */\n | { status: 'needs-update' }\n | { status: 'unsupported'; reason: string };\n\nexport interface ReadResult {\n raw: RawHealthData;\n /** Types that could not be read (permission off, provider error). The rest still syncs. */\n unreadable: HealthDataType[];\n /**\n * Types this version of the app reads that the person was never asked\n * about (added in an update, or by a newer OS). They are not read until\n * the permission sheet shows them; the rest still syncs.\n */\n needsAccess?: HealthDataType[];\n}\n\nexport interface HealthReader {\n readonly source: DeviceHealthSource;\n /** What the platform calls its health store, for the screen. */\n readonly storeName: string;\n /** The data types read on this device (platform and OS version). */\n readonly types: readonly HealthDataType[];\n availability(): Promise<HealthAvailability>;\n /**\n * Shows the system permission sheet. Resolves with the types granted, or\n * null when the platform does not tell apps (HealthKit hides read access).\n */\n requestAccess(): Promise<HealthDataType[] | null>;\n /** Granted read types, or null when the platform does not tell apps. */\n grantedTypes(): Promise<HealthDataType[] | null>;\n /** Everything recorded between two instants. */\n read(range: { start: number; end: number }, clock: ZoneClock): Promise<ReadResult>;\n /** Give the permissions back where the platform allows it (Health Connect); a no-op on iOS. */\n revoke(): Promise<void>;\n /**\n * Whether the platform lets Yantra read while it is closed, where that is a\n * permission of its own (Health Connect). Apple Health's background\n * delivery needs none, so its reader leaves this out.\n */\n canReadInBackground?(): Promise<boolean>;\n /** The platform's own screen for this app's permissions, when it has one. */\n openSettings?(): Promise<void>;\n}\n"],"names":[],"mappings":"AAaO,MAAM,oBAGR,CAAC;AAAA,EACJ,EAAI,EAAA,OAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,KAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,OAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,gBAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,iBAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,WAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,SAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,QAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,QAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,MAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,iBAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,SAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,KAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,OAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,UAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,eAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAG,EAAA;AAAA,EACD,EAAI,EAAA,cAAA;AAAA,EACJ,KAAO,EAAA;AACT,CAAC;AACM,SAAS,QAAQ,IAA8B,EAAA;AA5FtD,EAAA,IAAA,EAAA,EAAA,EAAA;AA6FE,EAAO,OAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,iBAAA,CAAkB,KAAK,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,IAAI,CAAA,KAAzC,IAA4C,GAAA,MAAA,GAAA,EAAA,CAAA,KAAA,KAA5C,IAAqD,GAAA,EAAA,GAAA,IAAA;AAC9D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import*as SecureStore from'expo-secure-store';var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
const KEY = "yantra.health.session";
|
|
18
|
+
function parseSession(raw) {
|
|
19
|
+
if (!raw) return null;
|
|
20
|
+
try {
|
|
21
|
+
const o = JSON.parse(raw);
|
|
22
|
+
if (typeof o.token !== "string" || !o.token || typeof o.url !== "string" || !o.url) return null;
|
|
23
|
+
return __spreadValues({
|
|
24
|
+
token: o.token,
|
|
25
|
+
url: o.url
|
|
26
|
+
}, typeof o.owner === "string" ? {
|
|
27
|
+
owner: o.owner
|
|
28
|
+
} : {});
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const secureSessionVault = {
|
|
34
|
+
// Not caught: a locked phone must surface as a throw.
|
|
35
|
+
read: async () => parseSession(await SecureStore.getItemAsync(KEY)),
|
|
36
|
+
write: (session) => SecureStore.setItemAsync(KEY, JSON.stringify(session)),
|
|
37
|
+
clear: () => SecureStore.deleteItemAsync(KEY)
|
|
38
|
+
};export{parseSession,secureSessionVault};//# sourceMappingURL=sessionVault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionVault.js","sources":["../../../src/features/health/sessionVault.ts"],"sourcesContent":["/**\n * The Fitness session, kept in the secure store for syncs that run while\n * Yantra is closed (healthSyncStore `syncInBackground`).\n *\n * The secure store's default protection (readable only while the phone is\n * unlocked) is deliberate: Apple Health is unreadable while locked anyway, and\n * a read that throws is how a background wake knows to wait.\n */\nimport * as SecureStore from 'expo-secure-store';\nimport type { HealthSession, SessionVault } from './healthSyncStore';\n\nconst KEY = 'yantra.health.session';\n\nexport function parseSession(raw: string | null): HealthSession | null {\n if (!raw) return null;\n try {\n const o = JSON.parse(raw) as Partial<HealthSession>;\n if (typeof o.token !== 'string' || !o.token || typeof o.url !== 'string' || !o.url) return null;\n return { token: o.token, url: o.url, ...(typeof o.owner === 'string' ? { owner: o.owner } : {}) };\n } catch {\n return null;\n }\n}\n\nexport const secureSessionVault: SessionVault = {\n // Not caught: a locked phone must surface as a throw.\n read: async () => parseSession(await SecureStore.getItemAsync(KEY)),\n write: (session) => SecureStore.setItemAsync(KEY, JSON.stringify(session)),\n clear: () => SecureStore.deleteItemAsync(KEY),\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAUA,MAAM,GAAM,GAAA,uBAAA;AACL,SAAS,aAAa,GAA0C,EAAA;AACrE,EAAI,IAAA,CAAC,KAAY,OAAA,IAAA;AACjB,EAAI,IAAA;AACF,IAAM,MAAA,CAAA,GAAI,IAAK,CAAA,KAAA,CAAM,GAAG,CAAA;AACxB,IAAA,IAAI,OAAO,CAAA,CAAE,KAAU,KAAA,QAAA,IAAY,CAAC,CAAE,CAAA,KAAA,IAAS,OAAO,CAAA,CAAE,GAAQ,KAAA,QAAA,IAAY,CAAC,CAAA,CAAE,KAAY,OAAA,IAAA;AAC3F,IAAO,OAAA,cAAA,CAAA;AAAA,MACL,OAAO,CAAE,CAAA,KAAA;AAAA,MACT,KAAK,CAAE,CAAA;AAAA,KACH,EAAA,OAAO,CAAE,CAAA,KAAA,KAAU,QAAW,GAAA;AAAA,MAChC,OAAO,CAAE,CAAA;AAAA,QACP,EAAC,CAAA;AAAA,GAED,CAAA,OAAA,CAAA,EAAA;AACN,IAAO,OAAA,IAAA;AAAA;AAEX;AACO,MAAM,kBAAmC,GAAA;AAAA;AAAA,EAE9C,MAAM,YAAY,YAAA,CAAa,MAAM,WAAY,CAAA,YAAA,CAAa,GAAG,CAAC,CAAA;AAAA,EAClE,KAAA,EAAO,aAAW,WAAY,CAAA,YAAA,CAAa,KAAK,IAAK,CAAA,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,EACvE,KAAO,EAAA,MAAM,WAAY,CAAA,eAAA,CAAgB,GAAG;AAC9C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncState.js","sources":["../../../src/features/health/syncState.ts"],"sourcesContent":["/**\n * The small local record that drives health sync: whether the person turned\n * it on, the cursor (last local date fully sent), and what the screen shows\n * (last sync, days synced, the last problem in plain words).\n *\n * The cursor is scoped to the signed-in account (`owner`): another account\n * on the same phone starts its own backfill instead of inheriting one.\n */\nimport type { DeviceHealthSource } from './types';\nimport { addDays, daysBetween } from './time';\n\nexport const HEALTH_SYNC_STORAGE_KEY = 'yantra.health.sync.v1';\n\n/** First connect: this many days, today included. */\nexport const BACKFILL_DAYS = 30;\n/** Every sync re-sends this many days before the cursor (watches sync late). */\nexport const RESEND_DAYS = 2;\n/** After a long absence, catch up at most this far back. */\nexport const MAX_CATCH_UP_DAYS = 90;\n/** Dates remembered for the \"days synced\" count. */\nconst MAX_REMEMBERED_DATES = 400;\n\nexport interface HealthSyncState {\n version: 1;\n connected: boolean;\n source?: DeviceHealthSource;\n connectedAt?: string;\n /** Account the cursor belongs to (token subject). */\n owner?: string;\n /** Last local date a sync covered; the next sync re-sends from RESEND_DAYS before it. */\n lastSyncedDate?: string;\n /** When the last sync finished without error (ISO). */\n lastSyncAt?: string;\n lastAttemptAt?: string;\n /** Days the backend stored in the last sync. */\n lastRunDays?: number;\n /** Distinct dates the backend has stored from this phone (bounded). */\n syncedDates: string[];\n /** Plain-words problem from the last attempt; cleared by a good sync. */\n lastError?: string;\n /** Data types that could not be read last time (permission off, provider error). */\n unreadableTypes?: string[];\n /** Data types the person was never asked about (added since they connected); \"Sync now\" asks. */\n needsAccessTypes?: string[];\n /** Values the app or the backend dropped last time, for the \"why\" line. */\n lastDropped?: string[];\n}\n\nexport const INITIAL_SYNC_STATE: HealthSyncState = { version: 1, connected: false, syncedDates: [] };\n\nexport interface StorageLike {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nexport async function loadSyncState(storage: StorageLike): Promise<HealthSyncState> {\n try {\n const raw = await storage.getItem(HEALTH_SYNC_STORAGE_KEY);\n if (!raw) return { ...INITIAL_SYNC_STATE };\n const parsed = JSON.parse(raw) as Partial<HealthSyncState>;\n if (parsed?.version !== 1) return { ...INITIAL_SYNC_STATE };\n return {\n ...INITIAL_SYNC_STATE,\n ...parsed,\n syncedDates: Array.isArray(parsed.syncedDates) ? parsed.syncedDates.filter(isDateString) : [],\n };\n } catch {\n return { ...INITIAL_SYNC_STATE };\n }\n}\n\nexport async function saveSyncState(storage: StorageLike, state: HealthSyncState): Promise<void> {\n try {\n await storage.setItem(HEALTH_SYNC_STORAGE_KEY, JSON.stringify(state));\n } catch {\n // A lost write only costs a re-send next time (upserts are idempotent).\n }\n}\n\nconst isDateString = (v: unknown): v is string => typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(v);\n\nexport function rememberDates(existing: readonly string[], added: readonly string[]): string[] {\n const all = [...new Set([...existing, ...added])].sort();\n return all.slice(Math.max(0, all.length - MAX_REMEMBERED_DATES));\n}\n\n/**\n * Which local dates the next sync covers, both included.\n * - Never synced: the last BACKFILL_DAYS days.\n * - Otherwise: RESEND_DAYS before the cursor through today, but no further\n * back than MAX_CATCH_UP_DAYS.\n */\nexport function syncWindow(\n state: Pick<HealthSyncState, 'lastSyncedDate'>,\n today: string,\n): { from: string; to: string } {\n const backfillFrom = addDays(today, -(BACKFILL_DAYS - 1));\n const cursor = state.lastSyncedDate;\n if (!cursor || cursor > today) return { from: backfillFrom, to: today };\n let from = addDays(cursor, -RESEND_DAYS);\n if (daysBetween(from, today) >= MAX_CATCH_UP_DAYS) from = addDays(today, -(MAX_CATCH_UP_DAYS - 1));\n return { from, to: today };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAUO,MAAM,uBAA0B,GAAA;AAOhC,MAAM,iBAAoB,GAAA;AAEjC,MAAM,oBAAuB,GAAA,GAAA;
|
|
1
|
+
{"version":3,"file":"syncState.js","sources":["../../../src/features/health/syncState.ts"],"sourcesContent":["/**\n * The small local record that drives health sync: whether the person turned\n * it on, the cursor (last local date fully sent), and what the screen shows\n * (last sync, days synced, the last problem in plain words).\n *\n * The cursor is scoped to the signed-in account (`owner`): another account\n * on the same phone starts its own backfill instead of inheriting one.\n */\nimport type { DeviceHealthSource } from './types';\nimport { addDays, daysBetween } from './time';\n\nexport const HEALTH_SYNC_STORAGE_KEY = 'yantra.health.sync.v1';\n\n/** First connect: this many days, today included. */\nexport const BACKFILL_DAYS = 30;\n/** Every sync re-sends this many days before the cursor (watches sync late). */\nexport const RESEND_DAYS = 2;\n/** After a long absence, catch up at most this far back. */\nexport const MAX_CATCH_UP_DAYS = 90;\n/** Dates remembered for the \"days synced\" count. */\nconst MAX_REMEMBERED_DATES = 400;\n\nexport interface HealthSyncState {\n version: 1;\n connected: boolean;\n source?: DeviceHealthSource;\n connectedAt?: string;\n /** Account the cursor belongs to (token subject). */\n owner?: string;\n /** Last local date a sync covered; the next sync re-sends from RESEND_DAYS before it. */\n lastSyncedDate?: string;\n /** When the last sync finished without error (ISO). */\n lastSyncAt?: string;\n lastAttemptAt?: string;\n /** Days the backend stored in the last sync. */\n lastRunDays?: number;\n /** Distinct dates the backend has stored from this phone (bounded). */\n syncedDates: string[];\n /** Plain-words problem from the last attempt; cleared by a good sync. */\n lastError?: string;\n /** Data types that could not be read last time (permission off, provider error). */\n unreadableTypes?: string[];\n /** Data types the person was never asked about (added since they connected); \"Sync now\" asks. */\n needsAccessTypes?: string[];\n /** Health Connect: when it was asked to let Yantra read while closed (connect, \"Sync now\"). */\n backgroundAskedAt?: string;\n /** Values the app or the backend dropped last time, for the \"why\" line. */\n lastDropped?: string[];\n}\n\nexport const INITIAL_SYNC_STATE: HealthSyncState = { version: 1, connected: false, syncedDates: [] };\n\nexport interface StorageLike {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nexport async function loadSyncState(storage: StorageLike): Promise<HealthSyncState> {\n try {\n const raw = await storage.getItem(HEALTH_SYNC_STORAGE_KEY);\n if (!raw) return { ...INITIAL_SYNC_STATE };\n const parsed = JSON.parse(raw) as Partial<HealthSyncState>;\n if (parsed?.version !== 1) return { ...INITIAL_SYNC_STATE };\n return {\n ...INITIAL_SYNC_STATE,\n ...parsed,\n syncedDates: Array.isArray(parsed.syncedDates) ? parsed.syncedDates.filter(isDateString) : [],\n };\n } catch {\n return { ...INITIAL_SYNC_STATE };\n }\n}\n\nexport async function saveSyncState(storage: StorageLike, state: HealthSyncState): Promise<void> {\n try {\n await storage.setItem(HEALTH_SYNC_STORAGE_KEY, JSON.stringify(state));\n } catch {\n // A lost write only costs a re-send next time (upserts are idempotent).\n }\n}\n\nconst isDateString = (v: unknown): v is string => typeof v === 'string' && /^\\d{4}-\\d{2}-\\d{2}$/.test(v);\n\nexport function rememberDates(existing: readonly string[], added: readonly string[]): string[] {\n const all = [...new Set([...existing, ...added])].sort();\n return all.slice(Math.max(0, all.length - MAX_REMEMBERED_DATES));\n}\n\n/**\n * Which local dates the next sync covers, both included.\n * - Never synced: the last BACKFILL_DAYS days.\n * - Otherwise: RESEND_DAYS before the cursor through today, but no further\n * back than MAX_CATCH_UP_DAYS.\n */\nexport function syncWindow(\n state: Pick<HealthSyncState, 'lastSyncedDate'>,\n today: string,\n): { from: string; to: string } {\n const backfillFrom = addDays(today, -(BACKFILL_DAYS - 1));\n const cursor = state.lastSyncedDate;\n if (!cursor || cursor > today) return { from: backfillFrom, to: today };\n let from = addDays(cursor, -RESEND_DAYS);\n if (daysBetween(from, today) >= MAX_CATCH_UP_DAYS) from = addDays(today, -(MAX_CATCH_UP_DAYS - 1));\n return { from, to: today };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAUO,MAAM,uBAA0B,GAAA;AAOhC,MAAM,iBAAoB,GAAA;AAEjC,MAAM,oBAAuB,GAAA,GAAA;AA4BtB,MAAM,kBAAsC,GAAA;AAAA,EACjD,OAAS,EAAA,CAAA;AAAA,EACT,SAAW,EAAA,KAAA;AAAA,EACX,aAAa;AACf;AAMA,eAAsB,cAAc,OAAgD,EAAA;AAClF,EAAI,IAAA;AACF,IAAA,MAAM,GAAM,GAAA,MAAM,OAAQ,CAAA,OAAA,CAAQ,uBAAuB,CAAA;AACzD,IAAI,IAAA,CAAC,GAAK,EAAA,OAAO,cACZ,CAAA,EAAA,EAAA,kBAAA,CAAA;AAEL,IAAM,MAAA,MAAA,GAAS,IAAK,CAAA,KAAA,CAAM,GAAG,CAAA;AAC7B,IAAA,IAAA,CAAI,MAAQ,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,MAAA,CAAA,OAAA,MAAY,CAAG,EAAA,OAAO,cAC7B,CAAA,EAAA,EAAA,kBAAA,CAAA;AAEL,IAAO,OAAA,aAAA,CAAA,cAAA,CAAA,cAAA,CAAA,EAAA,EACF,qBACA,MAFE,CAAA,EAAA;AAAA,MAGL,WAAA,EAAa,KAAM,CAAA,OAAA,CAAQ,MAAO,CAAA,WAAW,CAAI,GAAA,MAAA,CAAO,WAAY,CAAA,MAAA,CAAO,YAAY,CAAA,GAAI;AAAC,KAC9F,CAAA;AAAA,GACM,CAAA,OAAA,CAAA,EAAA;AACN,IAAA,OAAO,cACF,CAAA,EAAA,EAAA,kBAAA,CAAA;AAAA;AAGT;AACsB,eAAA,aAAA,CAAc,SAAsB,KAAuC,EAAA;AAC/F,EAAI,IAAA;AACF,IAAA,MAAM,QAAQ,OAAQ,CAAA,uBAAA,EAAyB,IAAK,CAAA,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,GAC9D,CAAA,OAAA,CAAA,EAAA;AAAA;AAGV;AACA,MAAM,YAAA,GAAe,CAAC,CAA4B,KAAA,OAAO,MAAM,QAAY,IAAA,qBAAA,CAAsB,KAAK,CAAC,CAAA;AACvF,SAAA,aAAA,CAAc,UAA6B,KAAoC,EAAA;AAC7F,EAAA,MAAM,GAAM,GAAA,CAAC,mBAAG,IAAI,GAAI,CAAA,CAAC,GAAG,QAAA,EAAU,GAAG,KAAK,CAAC,CAAC,EAAE,IAAK,EAAA;AACvD,EAAO,OAAA,GAAA,CAAI,MAAM,IAAK,CAAA,GAAA,CAAI,GAAG,GAAI,CAAA,MAAA,GAAS,oBAAoB,CAAC,CAAA;AACjE;AAQgB,SAAA,UAAA,CAAW,OAAgD,KAGzE,EAAA;AACA,EAAA,MAAM,YAAe,GAAA,OAAA,CAAQ,KAAO,EAAA,GAAoB,CAAA;AACxD,EAAA,MAAM,SAAS,KAAM,CAAA,cAAA;AACrB,EAAA,IAAI,CAAC,MAAA,IAAU,MAAS,GAAA,KAAA,EAAc,OAAA;AAAA,IACpC,IAAM,EAAA,YAAA;AAAA,IACN,EAAI,EAAA;AAAA,GACN;AACA,EAAA,IAAI,IAAO,GAAA,OAAA,CAAQ,MAAQ,EAAA,EAAY,CAAA;AACvC,EAAI,IAAA,WAAA,CAAY,IAAM,EAAA,KAAK,CAAK,IAAA,iBAAA,SAA0B,OAAQ,CAAA,KAAA,EAAO,GAAwB,CAAA;AACjG,EAAO,OAAA;AAAA,IACL,IAAA;AAAA,IACA,EAAI,EAAA;AAAA,GACN;AACF"}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
import {useEffect,useMemo}from'react';import {useSelector,useDispatch}from'react-redux';import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import {useEffect,useMemo}from'react';import {useSelector,useDispatch}from'react-redux';import {userSelector,setUserAccountDetails}from'@adminide-stack/user-auth0-client';import {useAccountProfileQuery}from'common/graphql';import {pickPrimaryDisplayName,pickAuthPicture}from'../../utils/authUserDisplay.js';import {useYantraChatMembership}from'./useYantraChatMembership.js';import {YANTRA_CHAT_ORG_SLUG}from'./yantraChatOrg.js';function toRegistrationStatus(phoneE164, loading) {
|
|
2
|
+
if (phoneE164) return "registered";
|
|
3
|
+
return loading ? "registering" : "not-registered";
|
|
4
|
+
}
|
|
5
|
+
function describeAccountError(auth0Id, error) {
|
|
6
|
+
if (!auth0Id) return "You are signed out. Sign in again to use Chat.";
|
|
7
|
+
return error ? `Could not load your account: ${error.message}` : "Could not load your account.";
|
|
8
|
+
}
|
|
8
9
|
function useChatViewer() {
|
|
9
|
-
var _a, _b;
|
|
10
|
+
var _a, _b, _c;
|
|
10
11
|
const authUser = useSelector(userSelector);
|
|
11
12
|
const rehydrated = useSelector((state) => state._persist ? Boolean(state._persist.rehydrated) : true);
|
|
12
13
|
const dispatch = useDispatch();
|
|
13
14
|
const storedUserId = (authUser == null ? void 0 : authUser.id) || null;
|
|
14
15
|
const auth0Id = (authUser == null ? void 0 : authUser.authUserId) || null;
|
|
15
|
-
const account =
|
|
16
|
-
|
|
17
|
-
auth0Id
|
|
18
|
-
},
|
|
19
|
-
skip: !rehydrated || Boolean(storedUserId) || !auth0Id,
|
|
20
|
-
fetchPolicy: "network-only"
|
|
16
|
+
const account = useAccountProfileQuery({
|
|
17
|
+
skip: !rehydrated || Boolean(storedUserId) || !auth0Id
|
|
21
18
|
});
|
|
22
|
-
const recoveredUserId = ((_b = (_a = account.data) == null ? void 0 : _a.
|
|
19
|
+
const recoveredUserId = ((_c = (_b = (_a = account.data) == null ? void 0 : _a.accountProfile) == null ? void 0 : _b.user) == null ? void 0 : _c.id) || null;
|
|
23
20
|
useEffect(() => {
|
|
24
21
|
if (!storedUserId && recoveredUserId) dispatch(setUserAccountDetails({
|
|
25
22
|
id: recoveredUserId
|
|
@@ -29,7 +26,7 @@ function useChatViewer() {
|
|
|
29
26
|
const displayName = useMemo(() => pickPrimaryDisplayName(authUser), [authUser]);
|
|
30
27
|
const avatarUrl = useMemo(() => pickAuthPicture(authUser), [authUser]);
|
|
31
28
|
const membership = useYantraChatMembership(userId, displayName);
|
|
32
|
-
const registrationStatus = membership.phoneE164
|
|
29
|
+
const registrationStatus = toRegistrationStatus(membership.phoneE164, membership.loading);
|
|
33
30
|
const viewer = useMemo(() => userId ? {
|
|
34
31
|
userId,
|
|
35
32
|
displayName,
|
|
@@ -39,8 +36,7 @@ function useChatViewer() {
|
|
|
39
36
|
registrationStatus
|
|
40
37
|
} : null, [userId, displayName, avatarUrl, membership.phoneE164, registrationStatus]);
|
|
41
38
|
const ready = Boolean(authUser) && rehydrated && !membership.loading && !account.loading;
|
|
42
|
-
const
|
|
43
|
-
const error = membership.error || (!viewer && ready ? accountError : null);
|
|
39
|
+
const error = membership.error || (!viewer && ready ? describeAccountError(auth0Id, account.error) : null);
|
|
44
40
|
return {
|
|
45
41
|
viewer,
|
|
46
42
|
error,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatViewer.js","sources":["../../../src/features/messaging/useChatViewer.ts"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport
|
|
1
|
+
{"version":3,"file":"useChatViewer.js","sources":["../../../src/features/messaging/useChatViewer.ts"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport type { IUserState } from '@adminide-stack/core';\nimport { Store, setUserAccountDetails, userSelector } from '@adminide-stack/user-auth0-client';\nimport { useAccountProfileQuery } from 'common/graphql';\nimport { pickAuthPicture, pickPrimaryDisplayName } from '../../utils/authUserDisplay';\nimport type { ChatViewer, ChatRegistrationStatus } from './chatHost';\nimport { useYantraChatMembership } from './useYantraChatMembership';\nimport { YANTRA_CHAT_ORG_SLUG } from './yantraChatOrg';\n\nexport interface ChatViewerState {\n viewer: ChatViewer | null;\n error: string | null;\n ready: boolean;\n needsRegistration: boolean;\n membershipLoading: boolean;\n registerForChat: (phoneE164: string) => Promise<string>;\n refreshMembership: () => Promise<void>;\n}\n\nfunction toRegistrationStatus(phoneE164: string | null, loading: boolean): ChatRegistrationStatus {\n if (phoneE164) return 'registered';\n return loading ? 'registering' : 'not-registered';\n}\n\nfunction describeAccountError(auth0Id: string | null, error?: { message: string }): string {\n if (!auth0Id) return 'You are signed out. Sign in again to use Chat.';\n return error ? `Could not load your account: ${error.message}` : 'Could not load your account.';\n}\n\n/**\n * The account id for the signed-in user. The Login screen stores it in `user.id`, and the store is\n * persisted - so a session restored without it (an older build, or a lookup that failed at login)\n * keeps `id: ''` for good. Chat then asks the server for the session's own account, through the\n * same `accountProfile` query Home resolves it from (usePrerequisiteIds), so the cache is usually\n * warm by the time Chat opens.\n */\nexport function useChatViewer(): ChatViewerState {\n const authUser = useSelector<Store.Auth, IUserState>(userSelector);\n // The store is restored from disk after launch (redux-persist, no gate in front of the UI):\n // until then `user` is the empty initial state, which must not read as \"no account\".\n const rehydrated = useSelector((state: { _persist?: { rehydrated?: boolean } }) =>\n state._persist ? Boolean(state._persist.rehydrated) : true,\n );\n const dispatch = useDispatch();\n const storedUserId = authUser?.id || null;\n const auth0Id = (authUser as { authUserId?: string | null } | undefined)?.authUserId || null;\n\n const account = useAccountProfileQuery({\n skip: !rehydrated || Boolean(storedUserId) || !auth0Id,\n });\n const recoveredUserId = account.data?.accountProfile?.user?.id || null;\n\n // Repair the store, so every screen reading `user.id` (not only Chat) gets the account.\n useEffect(() => {\n if (!storedUserId && recoveredUserId) dispatch(setUserAccountDetails({ id: recoveredUserId }));\n }, [storedUserId, recoveredUserId, dispatch]);\n\n const userId = storedUserId || recoveredUserId;\n const displayName = useMemo(() => pickPrimaryDisplayName(authUser), [authUser]);\n const avatarUrl = useMemo(() => pickAuthPicture(authUser), [authUser]);\n\n const membership = useYantraChatMembership(userId, displayName);\n\n const registrationStatus = toRegistrationStatus(membership.phoneE164, membership.loading);\n\n const viewer = useMemo<ChatViewer | null>(\n () =>\n userId\n ? {\n userId,\n displayName,\n avatarUrl,\n orgName: YANTRA_CHAT_ORG_SLUG,\n phoneE164: membership.phoneE164,\n registrationStatus,\n }\n : null,\n [userId, displayName, avatarUrl, membership.phoneE164, registrationStatus],\n );\n\n const ready = Boolean(authUser) && rehydrated && !membership.loading && !account.loading;\n\n const error = membership.error || (!viewer && ready ? describeAccountError(auth0Id, account.error) : null);\n\n return {\n viewer,\n error,\n ready,\n needsRegistration: membership.needsRegistration,\n membershipLoading: membership.loading,\n registerForChat: membership.register,\n refreshMembership: membership.refresh,\n };\n}\n"],"names":[],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {Feature}from'@common-stack/client-react';import YantraMobileModule from'./module.js';export{default as Home}from'./screens/Home/HomeScreen.js';export{default as NewChat}from'./screens/NewChat/index.js';export{default as Account}from'./screens/Account/index.js';export{default as ChatHistory}from'./screens/ChatHistory/index.js';export{default as Apps}from'./screens/Apps/index.js';export{default as WebBuilder}from'./screens/WebBuilder/index.js';export{GatewayContextProvider,useGatewayContext}from'./contexts/GatewayContext.js';export{CdecliConnectionProvider,useCdecliConnection}from'./contexts/CdecliConnectionContext.js';export{ChatLiveStreamEngine}from'./contexts/ChatLiveStreamContext.js';export{MemoryPressureResponder}from'./features/memory/MemoryPressureResponder.js';export{BUILTIN_MOBILE_GATEWAYS,MAX_CONCURRENT_TASKS,STORAGE_KEYS}from'./config/constants.js';export{config}from'./config/env-config.js';export{useCdecliAutoConnect}from'./hooks/useCdecliAutoConnect.js';export{useObtainSystemToken}from'./hooks/useObtainSystemToken.js';export{mintSurfaceSystemToken,resolveSurfaceCatalogBackend}from'./hooks/mintSurfaceSystemToken.js';export{buildCdecliTokenInjection,buildSurfaceGateRecoveryScript}from'./hooks/surfaceHandoffScript.js';export{isInstanceNotFoundError,useGatewayConnection}from'./hooks/useGatewayConnection.js';export{usePrerequisiteIds}from'./hooks/usePrerequisiteIds.js';export{useWorkspaceProvisioner}from'./hooks/useWorkspaceProvisioner.js';export{useGatewayRegistry}from'./hooks/useGatewayRegistry.js';export{agentSessionManager}from'./services/agentSessionManager.js';export{ensureGatewayApiKeys,resetGatewayApiKeyBridge}from'./services/gatewayApiKeyBridge.js';export{GatewayClient}from'./services/gatewayClient.js';export{BRIDGE_PROTOCOL_VERSION,BRIDGE_READY_ACTION,DEFAULT_BRIDGE_TIMEOUT_MS,DEFAULT_CHUNK_SIZE,MessageType}from'./bridge/types.js';export{WebViewManager}from'./bridge/WebViewManager.js';export{NativeWebViewBridge}from'./bridge/NativeWebViewBridge.js';export{TypedBridge}from'./bridge/TypedBridge.js';export{CancellableRequest}from'./bridge/CancellableRequest.js';export{ChunkedMessageHandler}from'./bridge/ChunkedMessageHandler.js';export{BridgeDebugger}from'./bridge/BridgeDebugger.js';export{WEB_BRIDGE_BOOTSTRAP}from'./bridge/webBridgeBootstrap.js';export{AuthenticatedFetch}from'./bridge/AuthenticatedFetch.js';export{AuthBridgeHandler}from'./bridge/handlers/AuthBridgeHandler.js';export{NavigationBridgeHandler}from'./bridge/handlers/NavigationBridgeHandler.js';export{AnalyticsBridgeHandler}from'./bridge/handlers/AnalyticsBridgeHandler.js';export{SURFACE_HOST_ACTIONS,SurfaceHostBridgeHandler}from'./bridge/handlers/SurfaceHostBridgeHandler.js';export{classifySurfaceNavigation}from'./bridge/surfaceLinkRouting.js';export{getPackagerHost,resolveMobileWebUrl}from'./bridge/resolveMobileWebUrl.js';export{YANTRA_LOADER_SIZE_COMPACT,YANTRA_LOADER_SIZE_DEFAULT,YantraBrandLoader}from'./components/YantraBrandLoader.js';export{default as CustomDrawer}from'./components/CustomDrawer.js';export{AppThemeProvider,useAppTheme}from'./theme/AppThemeProvider.js';export{InviteDeepLink}from'./features/invite/InviteDeepLink.js';export{ChatInviteDeepLink}from'./features/messaging/ChatInviteDeepLink.js';export{HealthSyncRunner}from'./features/health/HealthSyncRunner.js';export{FitnessWidgetRunner}from'./features/fitness-widgets/FitnessWidgetRunner.js';export{FitnessDeepLink}from'./features/fitness-widgets/FitnessDeepLink.js';export{inviteTokenFromUrl,isInviteUrl}from'./features/invite/inviteLink.js';export{isKeyboardControllerAvailable}from'./utils/keyboardController.js';export{getNativeCanvasExperience,getNativeCanvasViewer,preloadNativeCanvasExperience,registerCanvasExperiencePreload,registerNativeCanvasExperience,registerNativeCanvasViewer}from'./features/canvas/nativeViewerRegistry.js';export{getNativeCharactersExperience,registerNativeCharactersExperience}from'./features/characters/nativeCharactersRegistry.js';export{getNativeChatExperience,registerNativeChatExperience}from'./features/messaging/nativeChatRegistry.js';export{forgetChatPushToken,rememberChatPushToken}from'./features/messaging/chatPushToken.js';export{CHAT_NOTIFICATION_CONFIG_KEY,CHAT_NOTIFICATION_KEYS,chatPrefsFromSettings}from'./features/messaging/chatNotificationPrefs.js';export{settingsUriForAccount}from'./utils/cdecodeUri.js';export{getNativeShoppingExperience,preloadNativeShoppingExperience,registerNativeShoppingExperience,registerShoppingExperiencePreload}from'./features/shopping/nativeShoppingRegistry.js';export{getNativeTravelExperience,preloadNativeTravelExperience,registerNativeTravelExperience,registerTravelExperiencePreload}from'./features/travel/nativeTravelRegistry.js';export{getNativeJobSearchExperience,preloadNativeJobSearchExperience,registerJobSearchExperiencePreload,registerNativeJobSearchExperience}from'./features/job-search/nativeJobSearchRegistry.js';export{getNativeResumeExperience,preloadNativeResumeExperience,registerNativeResumeExperience,registerResumeExperiencePreload}from'./features/resume/nativeResumeRegistry.js';export{getNativeInboxExperience,preloadNativeInboxExperience,registerInboxExperiencePreload,registerNativeInboxExperience}from'./features/inbox/nativeInboxRegistry.js';export{getNativeStudyExperience,preloadNativeStudyExperience,registerNativeStudyExperience,registerStudyExperiencePreload}from'./features/study/nativeStudyRegistry.js';export{getNativeLocalServicesExperience,preloadNativeLocalServicesExperience,registerLocalServicesExperiencePreload,registerNativeLocalServicesExperience}from'./features/local-services/nativeLocalServicesRegistry.js';export{getNativeMoviesExperience,preloadNativeMoviesExperience,registerMoviesExperiencePreload,registerNativeMoviesExperience}from'./features/movies/nativeMoviesRegistry.js';export{getNativeExpensesExperience,preloadNativeExpensesExperience,registerExpensesExperiencePreload,registerNativeExpensesExperience}from'./features/expenses/nativeExpensesRegistry.js';export{getNativeWebBuilderExperience,preloadNativeWebBuilderExperience,registerNativeWebBuilderExperience,registerWebBuilderExperiencePreload}from'./features/webbuilder/nativeWebBuilderRegistry.js';export{NativeAgentChat}from'./features/agent-chat/NativeAgentChat.js';export{ChatLinkOpenerProvider}from'./features/links/ChatLinkOpenerProvider.js';export{resolveSurfaceAuthGraphqlUrl}from'./hooks/resolveSurfaceAuthGraphqlUrl.js';var index = new Feature(YantraMobileModule);export{index as default};//# sourceMappingURL=index.js.map
|
|
1
|
+
import {Feature}from'@common-stack/client-react';import YantraMobileModule from'./module.js';export{default as Home}from'./screens/Home/HomeScreen.js';export{default as NewChat}from'./screens/NewChat/index.js';export{default as Account}from'./screens/Account/index.js';export{default as ChatHistory}from'./screens/ChatHistory/index.js';export{default as Apps}from'./screens/Apps/index.js';export{default as WebBuilder}from'./screens/WebBuilder/index.js';export{GatewayContextProvider,useGatewayContext}from'./contexts/GatewayContext.js';export{CdecliConnectionProvider,useCdecliConnection}from'./contexts/CdecliConnectionContext.js';export{ChatLiveStreamEngine}from'./contexts/ChatLiveStreamContext.js';export{MemoryPressureResponder}from'./features/memory/MemoryPressureResponder.js';export{BUILTIN_MOBILE_GATEWAYS,MAX_CONCURRENT_TASKS,STORAGE_KEYS}from'./config/constants.js';export{config}from'./config/env-config.js';export{useCdecliAutoConnect}from'./hooks/useCdecliAutoConnect.js';export{useObtainSystemToken}from'./hooks/useObtainSystemToken.js';export{mintSurfaceSystemToken,resolveSurfaceCatalogBackend}from'./hooks/mintSurfaceSystemToken.js';export{buildCdecliTokenInjection,buildSurfaceGateRecoveryScript}from'./hooks/surfaceHandoffScript.js';export{isInstanceNotFoundError,useGatewayConnection}from'./hooks/useGatewayConnection.js';export{usePrerequisiteIds}from'./hooks/usePrerequisiteIds.js';export{useWorkspaceProvisioner}from'./hooks/useWorkspaceProvisioner.js';export{useGatewayRegistry}from'./hooks/useGatewayRegistry.js';export{agentSessionManager}from'./services/agentSessionManager.js';export{ensureGatewayApiKeys,resetGatewayApiKeyBridge}from'./services/gatewayApiKeyBridge.js';export{GatewayClient}from'./services/gatewayClient.js';export{BRIDGE_PROTOCOL_VERSION,BRIDGE_READY_ACTION,DEFAULT_BRIDGE_TIMEOUT_MS,DEFAULT_CHUNK_SIZE,MessageType}from'./bridge/types.js';export{WebViewManager}from'./bridge/WebViewManager.js';export{NativeWebViewBridge}from'./bridge/NativeWebViewBridge.js';export{TypedBridge}from'./bridge/TypedBridge.js';export{CancellableRequest}from'./bridge/CancellableRequest.js';export{ChunkedMessageHandler}from'./bridge/ChunkedMessageHandler.js';export{BridgeDebugger}from'./bridge/BridgeDebugger.js';export{WEB_BRIDGE_BOOTSTRAP}from'./bridge/webBridgeBootstrap.js';export{AuthenticatedFetch}from'./bridge/AuthenticatedFetch.js';export{AuthBridgeHandler}from'./bridge/handlers/AuthBridgeHandler.js';export{NavigationBridgeHandler}from'./bridge/handlers/NavigationBridgeHandler.js';export{AnalyticsBridgeHandler}from'./bridge/handlers/AnalyticsBridgeHandler.js';export{SURFACE_HOST_ACTIONS,SurfaceHostBridgeHandler}from'./bridge/handlers/SurfaceHostBridgeHandler.js';export{classifySurfaceNavigation}from'./bridge/surfaceLinkRouting.js';export{getPackagerHost,resolveMobileWebUrl}from'./bridge/resolveMobileWebUrl.js';export{YANTRA_LOADER_SIZE_COMPACT,YANTRA_LOADER_SIZE_DEFAULT,YantraBrandLoader}from'./components/YantraBrandLoader.js';export{default as CustomDrawer}from'./components/CustomDrawer.js';export{AppThemeProvider,useAppTheme}from'./theme/AppThemeProvider.js';export{InviteDeepLink}from'./features/invite/InviteDeepLink.js';export{ChatInviteDeepLink}from'./features/messaging/ChatInviteDeepLink.js';export{HealthSyncRunner}from'./features/health/HealthSyncRunner.js';export{startHealthBackgroundSync}from'./features/health/backgroundSync.js';export{FitnessWidgetRunner}from'./features/fitness-widgets/FitnessWidgetRunner.js';export{FitnessDeepLink}from'./features/fitness-widgets/FitnessDeepLink.js';export{inviteTokenFromUrl,isInviteUrl}from'./features/invite/inviteLink.js';export{isKeyboardControllerAvailable}from'./utils/keyboardController.js';export{getNativeCanvasExperience,getNativeCanvasViewer,preloadNativeCanvasExperience,registerCanvasExperiencePreload,registerNativeCanvasExperience,registerNativeCanvasViewer}from'./features/canvas/nativeViewerRegistry.js';export{getNativeCharactersExperience,registerNativeCharactersExperience}from'./features/characters/nativeCharactersRegistry.js';export{getNativeChatExperience,registerNativeChatExperience}from'./features/messaging/nativeChatRegistry.js';export{forgetChatPushToken,rememberChatPushToken}from'./features/messaging/chatPushToken.js';export{CHAT_NOTIFICATION_CONFIG_KEY,CHAT_NOTIFICATION_KEYS,chatPrefsFromSettings}from'./features/messaging/chatNotificationPrefs.js';export{settingsUriForAccount}from'./utils/cdecodeUri.js';export{getNativeShoppingExperience,preloadNativeShoppingExperience,registerNativeShoppingExperience,registerShoppingExperiencePreload}from'./features/shopping/nativeShoppingRegistry.js';export{getNativeTravelExperience,preloadNativeTravelExperience,registerNativeTravelExperience,registerTravelExperiencePreload}from'./features/travel/nativeTravelRegistry.js';export{getNativeJobSearchExperience,preloadNativeJobSearchExperience,registerJobSearchExperiencePreload,registerNativeJobSearchExperience}from'./features/job-search/nativeJobSearchRegistry.js';export{getNativeResumeExperience,preloadNativeResumeExperience,registerNativeResumeExperience,registerResumeExperiencePreload}from'./features/resume/nativeResumeRegistry.js';export{getNativeInboxExperience,preloadNativeInboxExperience,registerInboxExperiencePreload,registerNativeInboxExperience}from'./features/inbox/nativeInboxRegistry.js';export{getNativeStudyExperience,preloadNativeStudyExperience,registerNativeStudyExperience,registerStudyExperiencePreload}from'./features/study/nativeStudyRegistry.js';export{getNativeLocalServicesExperience,preloadNativeLocalServicesExperience,registerLocalServicesExperiencePreload,registerNativeLocalServicesExperience}from'./features/local-services/nativeLocalServicesRegistry.js';export{getNativeMoviesExperience,preloadNativeMoviesExperience,registerMoviesExperiencePreload,registerNativeMoviesExperience}from'./features/movies/nativeMoviesRegistry.js';export{getNativeExpensesExperience,preloadNativeExpensesExperience,registerExpensesExperiencePreload,registerNativeExpensesExperience}from'./features/expenses/nativeExpensesRegistry.js';export{getNativeWebBuilderExperience,preloadNativeWebBuilderExperience,registerNativeWebBuilderExperience,registerWebBuilderExperiencePreload}from'./features/webbuilder/nativeWebBuilderRegistry.js';export{NativeAgentChat}from'./features/agent-chat/NativeAgentChat.js';export{ChatLinkOpenerProvider}from'./features/links/ChatLinkOpenerProvider.js';export{resolveSurfaceAuthGraphqlUrl}from'./hooks/resolveSurfaceAuthGraphqlUrl.js';var index = new Feature(YantraMobileModule);export{index as default};//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { Feature } from '@common-stack/client-react';\nimport YantraMobileModule from './module';\n\nexport default new Feature(YantraMobileModule);\nexport * from './screens';\nexport { GatewayContextProvider, useGatewayContext } from './contexts/GatewayContext';\nexport { CdecliConnectionProvider, useCdecliConnection } from './contexts/CdecliConnectionContext';\nexport { ChatLiveStreamEngine } from './contexts/ChatLiveStreamContext';\nexport { MemoryPressureResponder } from './features/memory/MemoryPressureResponder';\nexport * from './config/constants';\nexport { config } from './config/env-config';\nexport { useCdecliAutoConnect } from './hooks/useCdecliAutoConnect';\nexport { useObtainSystemToken } from './hooks/useObtainSystemToken';\nexport {\n mintSurfaceSystemToken,\n resolveSurfaceCatalogBackend,\n resolveSurfaceAuthGraphqlUrl,\n} from './hooks/mintSurfaceSystemToken';\nexport { buildCdecliTokenInjection, buildSurfaceGateRecoveryScript } from './hooks/surfaceHandoffScript';\nexport { useGatewayConnection, isInstanceNotFoundError } from './hooks/useGatewayConnection';\nexport { usePrerequisiteIds } from './hooks/usePrerequisiteIds';\nexport { useWorkspaceProvisioner } from './hooks/useWorkspaceProvisioner';\nexport { useGatewayRegistry } from './hooks/useGatewayRegistry';\nexport * from './services';\nexport * from './bridge';\nexport {\n YantraBrandLoader,\n YANTRA_LOADER_SIZE_DEFAULT,\n YANTRA_LOADER_SIZE_COMPACT,\n} from './components/YantraBrandLoader';\nexport { default as CustomDrawer } from './components/CustomDrawer';\nexport { AppThemeProvider, useAppTheme } from './theme/AppThemeProvider';\n// Invite deep links. Mounted by the app at the root (see portable-devices/mobile\n// App.tsx) rather than routed to: the invitee has neither an org nor a session,\n// and every mobile route requires both.\nexport { InviteDeepLink } from './features/invite/InviteDeepLink';\nexport { ChatInviteDeepLink } from './features/messaging/ChatInviteDeepLink';\n// Apple Health / Health Connect sync. Mounted at the app root like the invite\n// handlers: it syncs on every return to the foreground, whatever screen is open.\nexport { HealthSyncRunner } from './features/health/HealthSyncRunner';\n// Fitness widgets (iOS): keeps the home / lock screen snapshot fresh, and opens the\n// Fitness surface when a widget or the workout Live Activity is tapped. Both at the root.\nexport { FitnessWidgetRunner } from './features/fitness-widgets/FitnessWidgetRunner';\nexport { FitnessDeepLink } from './features/fitness-widgets/FitnessDeepLink';\nexport { inviteTokenFromUrl, isInviteUrl } from './features/invite/inviteLink';\nexport { isKeyboardControllerAvailable } from './utils/keyboardController';\n// The canvas viewer seam. Exported from the index on purpose: the app (the MF\n// host) registers the federated viewer here and the CanvasBoard screen reads\n// it. Reachability from this entry is what makes rollup emit the registry as a\n// real shared module - as an internal-only file, `registerNativeCanvasViewer`\n// is never called inside the package, so rollup tree-shakes it and constant-\n// folds the screen down to its fallback branch.\nexport {\n registerNativeCanvasViewer,\n getNativeCanvasViewer,\n registerNativeCanvasExperience,\n getNativeCanvasExperience,\n registerCanvasExperiencePreload,\n preloadNativeCanvasExperience,\n} from './features/canvas/nativeViewerRegistry';\n// Characters seam - same reachability contract as the canvas registry above.\nexport {\n registerNativeCharactersExperience,\n getNativeCharactersExperience,\n} from './features/characters/nativeCharactersRegistry';\n// Chat seam - same reachability contract as the two registries above.\nexport { registerNativeChatExperience, getNativeChatExperience } from './features/messaging/nativeChatRegistry';\n// Chat push + notification preferences: the app (which owns expo-notifications and the\n// settings session) registers the token and serves the preferences through these.\nexport { rememberChatPushToken, forgetChatPushToken } from './features/messaging/chatPushToken';\nexport {\n CHAT_NOTIFICATION_CONFIG_KEY,\n CHAT_NOTIFICATION_KEYS,\n chatPrefsFromSettings,\n} from './features/messaging/chatNotificationPrefs';\nexport { settingsUriForAccount } from './utils/cdecodeUri';\nexport {\n registerNativeShoppingExperience,\n getNativeShoppingExperience,\n registerShoppingExperiencePreload,\n preloadNativeShoppingExperience,\n} from './features/shopping/nativeShoppingRegistry';\nexport type { ShoppingExperienceProps } from './features/shopping/ShoppingExperience';\nexport type { ShoppingHostChat } from './features/shopping/hostChat';\nexport {\n registerNativeTravelExperience,\n getNativeTravelExperience,\n registerTravelExperiencePreload,\n preloadNativeTravelExperience,\n} from './features/travel/nativeTravelRegistry';\nexport type { TravelExperienceProps } from './features/travel/TravelExperience';\nexport type { TravelHostChat } from './features/travel/hostChat';\nexport {\n registerNativeJobSearchExperience,\n getNativeJobSearchExperience,\n registerJobSearchExperiencePreload,\n preloadNativeJobSearchExperience,\n} from './features/job-search/nativeJobSearchRegistry';\nexport type { JobSearchExperienceProps } from './features/job-search/JobSearchExperience';\nexport type { JobSearchHostChat } from './features/job-search/hostChat';\nexport {\n registerNativeResumeExperience,\n getNativeResumeExperience,\n registerResumeExperiencePreload,\n preloadNativeResumeExperience,\n} from './features/resume/nativeResumeRegistry';\nexport type { ResumeExperienceProps } from './features/resume/ResumeExperience';\nexport type { ResumeHostChat } from './features/resume/hostChat';\nexport {\n registerNativeInboxExperience,\n getNativeInboxExperience,\n registerInboxExperiencePreload,\n preloadNativeInboxExperience,\n} from './features/inbox/nativeInboxRegistry';\nexport type { InboxExperienceProps } from './features/inbox/InboxExperience';\nexport type { InboxHostChat } from './features/inbox/hostChat';\nexport {\n registerNativeStudyExperience,\n getNativeStudyExperience,\n registerStudyExperiencePreload,\n preloadNativeStudyExperience,\n} from './features/study/nativeStudyRegistry';\nexport type { StudyExperienceProps } from './features/study/StudyExperience';\nexport type { StudyHostChat } from './features/study/hostChat';\nexport {\n registerNativeLocalServicesExperience,\n getNativeLocalServicesExperience,\n registerLocalServicesExperiencePreload,\n preloadNativeLocalServicesExperience,\n} from './features/local-services/nativeLocalServicesRegistry';\nexport type { LocalServicesExperienceProps } from './features/local-services/LocalServicesExperience';\nexport type { LocalServicesHostChat } from './features/local-services/hostChat';\nexport {\n registerNativeMoviesExperience,\n getNativeMoviesExperience,\n registerMoviesExperiencePreload,\n preloadNativeMoviesExperience,\n} from './features/movies/nativeMoviesRegistry';\nexport type { MoviesExperienceProps } from './features/movies/MoviesExperience';\nexport type { MoviesHostChat } from './features/movies/hostChat';\nexport {\n registerNativeExpensesExperience,\n getNativeExpensesExperience,\n registerExpensesExperiencePreload,\n preloadNativeExpensesExperience,\n} from './features/expenses/nativeExpensesRegistry';\nexport type { ExpensesExperienceProps } from './features/expenses/ExpensesExperience';\nexport type { ExpensesHostChat } from './features/expenses/hostChat';\nexport {\n registerNativeWebBuilderExperience,\n getNativeWebBuilderExperience,\n registerWebBuilderExperiencePreload,\n preloadNativeWebBuilderExperience,\n} from './features/webbuilder/nativeWebBuilderRegistry';\nexport type { WebBuilderExperienceProps } from './features/webbuilder/WebBuilderExperience';\nexport type { WebBuilderHostChat } from './features/webbuilder/hostChat';\nexport { NativeAgentChat } from './features/agent-chat';\nexport { ChatLinkOpenerProvider } from './features/links/ChatLinkOpenerProvider';\nexport type { AgentChatMessage, AgentChatSuggestion, NativeAgentChatProps } from './features/agent-chat';\nexport type {\n ChatHostServices,\n ChatViewer,\n FederatedChatProps,\n NativeChatExperienceProps,\n} from './features/messaging/chatHost';\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { Feature } from '@common-stack/client-react';\nimport YantraMobileModule from './module';\n\nexport default new Feature(YantraMobileModule);\nexport * from './screens';\nexport { GatewayContextProvider, useGatewayContext } from './contexts/GatewayContext';\nexport { CdecliConnectionProvider, useCdecliConnection } from './contexts/CdecliConnectionContext';\nexport { ChatLiveStreamEngine } from './contexts/ChatLiveStreamContext';\nexport { MemoryPressureResponder } from './features/memory/MemoryPressureResponder';\nexport * from './config/constants';\nexport { config } from './config/env-config';\nexport { useCdecliAutoConnect } from './hooks/useCdecliAutoConnect';\nexport { useObtainSystemToken } from './hooks/useObtainSystemToken';\nexport {\n mintSurfaceSystemToken,\n resolveSurfaceCatalogBackend,\n resolveSurfaceAuthGraphqlUrl,\n} from './hooks/mintSurfaceSystemToken';\nexport { buildCdecliTokenInjection, buildSurfaceGateRecoveryScript } from './hooks/surfaceHandoffScript';\nexport { useGatewayConnection, isInstanceNotFoundError } from './hooks/useGatewayConnection';\nexport { usePrerequisiteIds } from './hooks/usePrerequisiteIds';\nexport { useWorkspaceProvisioner } from './hooks/useWorkspaceProvisioner';\nexport { useGatewayRegistry } from './hooks/useGatewayRegistry';\nexport * from './services';\nexport * from './bridge';\nexport {\n YantraBrandLoader,\n YANTRA_LOADER_SIZE_DEFAULT,\n YANTRA_LOADER_SIZE_COMPACT,\n} from './components/YantraBrandLoader';\nexport { default as CustomDrawer } from './components/CustomDrawer';\nexport { AppThemeProvider, useAppTheme } from './theme/AppThemeProvider';\n// Invite deep links. Mounted by the app at the root (see portable-devices/mobile\n// App.tsx) rather than routed to: the invitee has neither an org nor a session,\n// and every mobile route requires both.\nexport { InviteDeepLink } from './features/invite/InviteDeepLink';\nexport { ChatInviteDeepLink } from './features/messaging/ChatInviteDeepLink';\n// Apple Health / Health Connect sync. Mounted at the app root like the invite\n// handlers: it syncs on every return to the foreground, whatever screen is open.\nexport { HealthSyncRunner } from './features/health/HealthSyncRunner';\n// Apple Health while the app is closed: started once at launch, before anything renders.\nexport { startHealthBackgroundSync } from './features/health/backgroundSync';\n// Fitness widgets (iOS): keeps the home / lock screen snapshot fresh, and opens the\n// Fitness surface when a widget or the workout Live Activity is tapped. Both at the root.\nexport { FitnessWidgetRunner } from './features/fitness-widgets/FitnessWidgetRunner';\nexport { FitnessDeepLink } from './features/fitness-widgets/FitnessDeepLink';\nexport { inviteTokenFromUrl, isInviteUrl } from './features/invite/inviteLink';\nexport { isKeyboardControllerAvailable } from './utils/keyboardController';\n// The canvas viewer seam. Exported from the index on purpose: the app (the MF\n// host) registers the federated viewer here and the CanvasBoard screen reads\n// it. Reachability from this entry is what makes rollup emit the registry as a\n// real shared module - as an internal-only file, `registerNativeCanvasViewer`\n// is never called inside the package, so rollup tree-shakes it and constant-\n// folds the screen down to its fallback branch.\nexport {\n registerNativeCanvasViewer,\n getNativeCanvasViewer,\n registerNativeCanvasExperience,\n getNativeCanvasExperience,\n registerCanvasExperiencePreload,\n preloadNativeCanvasExperience,\n} from './features/canvas/nativeViewerRegistry';\n// Characters seam - same reachability contract as the canvas registry above.\nexport {\n registerNativeCharactersExperience,\n getNativeCharactersExperience,\n} from './features/characters/nativeCharactersRegistry';\n// Chat seam - same reachability contract as the two registries above.\nexport { registerNativeChatExperience, getNativeChatExperience } from './features/messaging/nativeChatRegistry';\n// Chat push + notification preferences: the app (which owns expo-notifications and the\n// settings session) registers the token and serves the preferences through these.\nexport { rememberChatPushToken, forgetChatPushToken } from './features/messaging/chatPushToken';\nexport {\n CHAT_NOTIFICATION_CONFIG_KEY,\n CHAT_NOTIFICATION_KEYS,\n chatPrefsFromSettings,\n} from './features/messaging/chatNotificationPrefs';\nexport { settingsUriForAccount } from './utils/cdecodeUri';\nexport {\n registerNativeShoppingExperience,\n getNativeShoppingExperience,\n registerShoppingExperiencePreload,\n preloadNativeShoppingExperience,\n} from './features/shopping/nativeShoppingRegistry';\nexport type { ShoppingExperienceProps } from './features/shopping/ShoppingExperience';\nexport type { ShoppingHostChat } from './features/shopping/hostChat';\nexport {\n registerNativeTravelExperience,\n getNativeTravelExperience,\n registerTravelExperiencePreload,\n preloadNativeTravelExperience,\n} from './features/travel/nativeTravelRegistry';\nexport type { TravelExperienceProps } from './features/travel/TravelExperience';\nexport type { TravelHostChat } from './features/travel/hostChat';\nexport {\n registerNativeJobSearchExperience,\n getNativeJobSearchExperience,\n registerJobSearchExperiencePreload,\n preloadNativeJobSearchExperience,\n} from './features/job-search/nativeJobSearchRegistry';\nexport type { JobSearchExperienceProps } from './features/job-search/JobSearchExperience';\nexport type { JobSearchHostChat } from './features/job-search/hostChat';\nexport {\n registerNativeResumeExperience,\n getNativeResumeExperience,\n registerResumeExperiencePreload,\n preloadNativeResumeExperience,\n} from './features/resume/nativeResumeRegistry';\nexport type { ResumeExperienceProps } from './features/resume/ResumeExperience';\nexport type { ResumeHostChat } from './features/resume/hostChat';\nexport {\n registerNativeInboxExperience,\n getNativeInboxExperience,\n registerInboxExperiencePreload,\n preloadNativeInboxExperience,\n} from './features/inbox/nativeInboxRegistry';\nexport type { InboxExperienceProps } from './features/inbox/InboxExperience';\nexport type { InboxHostChat } from './features/inbox/hostChat';\nexport {\n registerNativeStudyExperience,\n getNativeStudyExperience,\n registerStudyExperiencePreload,\n preloadNativeStudyExperience,\n} from './features/study/nativeStudyRegistry';\nexport type { StudyExperienceProps } from './features/study/StudyExperience';\nexport type { StudyHostChat } from './features/study/hostChat';\nexport {\n registerNativeLocalServicesExperience,\n getNativeLocalServicesExperience,\n registerLocalServicesExperiencePreload,\n preloadNativeLocalServicesExperience,\n} from './features/local-services/nativeLocalServicesRegistry';\nexport type { LocalServicesExperienceProps } from './features/local-services/LocalServicesExperience';\nexport type { LocalServicesHostChat } from './features/local-services/hostChat';\nexport {\n registerNativeMoviesExperience,\n getNativeMoviesExperience,\n registerMoviesExperiencePreload,\n preloadNativeMoviesExperience,\n} from './features/movies/nativeMoviesRegistry';\nexport type { MoviesExperienceProps } from './features/movies/MoviesExperience';\nexport type { MoviesHostChat } from './features/movies/hostChat';\nexport {\n registerNativeExpensesExperience,\n getNativeExpensesExperience,\n registerExpensesExperiencePreload,\n preloadNativeExpensesExperience,\n} from './features/expenses/nativeExpensesRegistry';\nexport type { ExpensesExperienceProps } from './features/expenses/ExpensesExperience';\nexport type { ExpensesHostChat } from './features/expenses/hostChat';\nexport {\n registerNativeWebBuilderExperience,\n getNativeWebBuilderExperience,\n registerWebBuilderExperiencePreload,\n preloadNativeWebBuilderExperience,\n} from './features/webbuilder/nativeWebBuilderRegistry';\nexport type { WebBuilderExperienceProps } from './features/webbuilder/WebBuilderExperience';\nexport type { WebBuilderHostChat } from './features/webbuilder/hostChat';\nexport { NativeAgentChat } from './features/agent-chat';\nexport { ChatLinkOpenerProvider } from './features/links/ChatLinkOpenerProvider';\nexport type { AgentChatMessage, AgentChatSuggestion, NativeAgentChatProps } from './features/agent-chat';\nexport type {\n ChatHostServices,\n ChatViewer,\n FederatedChatProps,\n NativeChatExperienceProps,\n} from './features/messaging/chatHost';\n"],"names":[],"mappings":"m3MAEA,YAAe,IAAI,QAAQ,kBAAkB,CAAA"}
|
|
@@ -33,7 +33,8 @@ function HealthScreen() {
|
|
|
33
33
|
granted,
|
|
34
34
|
running,
|
|
35
35
|
busy,
|
|
36
|
-
types
|
|
36
|
+
types,
|
|
37
|
+
backgroundAccess
|
|
37
38
|
} = snapshot;
|
|
38
39
|
const [now, setNow] = useState(() => Date.now());
|
|
39
40
|
const orgName = useMemo(() => {
|
|
@@ -137,6 +138,7 @@ function HealthScreen() {
|
|
|
137
138
|
var _a2;
|
|
138
139
|
return (_a2 = sync.needsAccessTypes) == null ? void 0 : _a2.includes(t.id);
|
|
139
140
|
});
|
|
141
|
+
const backgroundOff = sync.connected && backgroundAccess === false;
|
|
140
142
|
return /* @__PURE__ */ jsxs(SafeAreaView, { edges: ["top", "left", "right"], style: [styles.safe, {
|
|
141
143
|
backgroundColor: colors.bg
|
|
142
144
|
}], children: [
|
|
@@ -177,6 +179,7 @@ function HealthScreen() {
|
|
|
177
179
|
sync.lastError ? /* @__PURE__ */ jsx(Notice, { tone: "danger", colors, icon: "alert-circle", text: sync.lastError }) : null,
|
|
178
180
|
sync.connected && unreadable.length ? /* @__PURE__ */ jsx(Notice, { tone: "warning", colors, icon: "information-circle", text: `Could not read ${unreadable.map(labelOf).join(", ")} last time. Everything else synced.` }) : null,
|
|
179
181
|
sync.connected && needsAccess.length ? /* @__PURE__ */ jsx(Notice, { tone: "info", colors, icon: "add-circle", text: `Yantra can now also read ${needsAccess.map((t) => t.label).join(", ")}. Tap Sync now to allow ${needsAccess.length === 1 ? "it" : "them"}.` }) : null,
|
|
182
|
+
backgroundOff && !sync.backgroundAskedAt ? /* @__PURE__ */ jsx(Notice, { tone: "info", colors, icon: "add-circle", text: "Yantra can now sync while it is closed. Tap Sync now to allow it." }) : null,
|
|
180
183
|
/* @__PURE__ */ jsx(View, { style: styles.actions, children: renderActions() }),
|
|
181
184
|
/* @__PURE__ */ jsx(Text, { style: [styles.sectionTitle, {
|
|
182
185
|
color: colors.textMuted
|
|
@@ -202,7 +205,7 @@ function HealthScreen() {
|
|
|
202
205
|
color: colors.textMuted
|
|
203
206
|
}], children: [
|
|
204
207
|
IS_IOS ? "Apple Health does not tell apps which types you allowed; types you turned off are left out. " : "",
|
|
205
|
-
|
|
208
|
+
describeCadence(backgroundAccess, backgroundOff && Boolean(sync.backgroundAskedAt))
|
|
206
209
|
] }),
|
|
207
210
|
/* @__PURE__ */ jsx(Text, { style: [styles.sectionTitle, {
|
|
208
211
|
color: colors.textMuted
|
|
@@ -293,6 +296,13 @@ function describeStatus(loaded, running, connected) {
|
|
|
293
296
|
if (running) return "Syncing...";
|
|
294
297
|
return connected ? "Connected" : "Off";
|
|
295
298
|
}
|
|
299
|
+
function describeCadence(backgroundAccess, howToAllow) {
|
|
300
|
+
const first = "The last 30 days sync first, then";
|
|
301
|
+
if (IS_IOS) return `${first} new data as your watch saves it, even while Yantra is closed.`;
|
|
302
|
+
if (backgroundAccess) return `${first} new data about every hour, even while Yantra is closed.`;
|
|
303
|
+
const onOpen = `${first} new days each time you open Yantra.`;
|
|
304
|
+
return howToAllow ? `${onOpen} To also sync while it is closed, allow background access under Choose what Yantra reads.` : onOpen;
|
|
305
|
+
}
|
|
296
306
|
function typeState(connected, granted, id) {
|
|
297
307
|
if (!connected) return "idle";
|
|
298
308
|
if (granted === null || granted === void 0) return "requested";
|