mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 16:03:53 +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
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
// Why two shapes: APNs nests Orca's fields under `orca` beside `aps`, while FCM
|
|
// carries them flat in `data` as strings. Both reach JS as the notification's
|
|
// `content.data`, so the reader accepts either and coerces the numeric fields.
|
|
export type OrcaPushPayload = {
|
|
readonly kind?: 'alert' | 'dismiss'
|
|
readonly hostFingerprint: string
|
|
readonly notificationId?: string
|
|
readonly notificationSeq?: number
|
|
readonly notificationEpoch?: string
|
|
readonly paneKey?: string
|
|
readonly worktreeId?: string
|
|
}
|
|
|
|
function readString(value: unknown): string | undefined {
|
|
return typeof value === 'string' && value.length > 0 ? value : undefined
|
|
}
|
|
|
|
function readSeq(value: unknown): number | undefined {
|
|
const raw = typeof value === 'number' ? value : Number(readString(value))
|
|
return Number.isFinite(raw) ? raw : undefined
|
|
}
|
|
|
|
export function readOrcaPushPayload(data: unknown): OrcaPushPayload | null {
|
|
if (!data || typeof data !== 'object') {
|
|
return null
|
|
}
|
|
const nested = (data as { orca?: unknown }).orca
|
|
const record = (nested && typeof nested === 'object' ? nested : data) as Record<string, unknown>
|
|
// The fingerprint is what makes this a gateway push; locally scheduled data never has one.
|
|
const hostFingerprint = readString(record.hostFingerprint)
|
|
if (!hostFingerprint) {
|
|
return null
|
|
}
|
|
return {
|
|
hostFingerprint,
|
|
...(record.kind === 'dismiss' || record.kind === 'alert' ? { kind: record.kind } : {}),
|
|
notificationId: readString(record.notificationId),
|
|
notificationSeq: readSeq(record.notificationSeq),
|
|
notificationEpoch: readString(record.notificationEpoch),
|
|
paneKey: readString(record.paneKey),
|
|
worktreeId: readString(record.worktreeId)
|
|
}
|
|
}
|