mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
* Restore mobile push for delivery validation * fix(mobile): register push task before headless startup * Add authenticated mobile push test and fix iOS release entitlements * Mock push-test transport in notification consent tests * Fix slept workspace test for structured remount result * Fix mobile notification review findings * Pad Android notification icon to prevent square cropping * fix(mobile): present visible Android data pushes in foreground * test: use deterministic clock for teardown deadline * fix(mobile): present foreground pushes through Expo public APIs * fix(mobile): check push eligibility before foreground scheduling * fix(mobile): register push from shared host connection lifecycle
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import * as Notifications from 'expo-notifications'
|
|
import type {
|
|
MobilePushApnsEnvironment,
|
|
MobilePushPlatform
|
|
} from '../../../src/shared/mobile-push-contract'
|
|
|
|
// Why: the native APNs/FCM token, not an Expo push token — Orca's own gateway
|
|
// talks to Apple and Google directly, so it needs the raw device token.
|
|
|
|
export type MobilePushToken = {
|
|
readonly platform: MobilePushPlatform
|
|
readonly token: string
|
|
readonly apnsEnvironment?: MobilePushApnsEnvironment
|
|
}
|
|
|
|
// Dev-client builds are debug and get sandbox APNs; TestFlight and App Store are release.
|
|
function apnsEnvironment(): MobilePushApnsEnvironment {
|
|
return typeof __DEV__ !== 'undefined' && __DEV__ ? 'sandbox' : 'production'
|
|
}
|
|
|
|
function toMobilePushToken(raw: { type: string; data: unknown }): MobilePushToken | null {
|
|
if (typeof raw.data !== 'string' || raw.data.length === 0) {
|
|
return null
|
|
}
|
|
if (raw.type === 'ios') {
|
|
return { platform: 'ios', token: raw.data, apnsEnvironment: apnsEnvironment() }
|
|
}
|
|
// Web tokens carry an object payload and no Orca gateway path; only native counts.
|
|
return raw.type === 'android' ? { platform: 'android', token: raw.data } : null
|
|
}
|
|
|
|
/**
|
|
* The native push token, or null if registration is unavailable or fails.
|
|
*/
|
|
export async function getDevicePushToken(): Promise<MobilePushToken | null> {
|
|
try {
|
|
return toMobilePushToken(await Notifications.getDevicePushTokenAsync())
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Providers can roll a token while the app runs; the old one stops delivering. */
|
|
export function addPushTokenListener(listener: (token: MobilePushToken) => void): () => void {
|
|
try {
|
|
const subscription = Notifications.addPushTokenListener((raw) => {
|
|
const token = toMobilePushToken(raw)
|
|
if (token) {
|
|
listener(token)
|
|
}
|
|
})
|
|
return () => subscription.remove()
|
|
} catch {
|
|
// A shell with no push capability cannot subscribe; the caller is a root-level
|
|
// effect, so throwing here would take the whole app down over an optional feature.
|
|
return () => {}
|
|
}
|
|
}
|