one
Version:
One is a React framework for web, iOS, and Android apps.
78 lines (69 loc) • 2.79 kB
text/typescript
import type {
Notification,
NotificationBehavior,
NotificationHandlerInput,
} from './types'
// expo shows an arriving foreground notification until the app says
// otherwise, and keeps showing it when the handler stalls, so a slow
// handler never drops one. the null handler is the only quiet mode.
export const showAllBehavior: NotificationBehavior = {
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: true,
}
export const suppressBehavior: NotificationBehavior = {
shouldShowBanner: false,
shouldShowList: false,
shouldPlaySound: false,
shouldSetBadge: false,
}
export function normalizeBehavior(input: unknown): NotificationBehavior {
if (!input || typeof input !== 'object') return { ...suppressBehavior }
return {
shouldShowBanner: 'shouldShowBanner' in input && input.shouldShowBanner === true,
shouldShowList: 'shouldShowList' in input && input.shouldShowList === true,
shouldPlaySound: 'shouldPlaySound' in input && input.shouldPlaySound === true,
shouldSetBadge: 'shouldSetBadge' in input && input.shouldSetBadge === true,
}
}
// pairs foreground arrivals with their presentation answers by request id.
// each id settles exactly once; duplicates of an in-flight id are ignored.
// native shows everything after 3s on its own, so js never times out: a
// stalled handler resolves natively, and a late js answer finds no pending
// record and stays a no-op. no react-native imports: unit-tested.
export class ForegroundHandler {
private handler: NotificationHandlerInput | null = null
private nulled = false
private pending = new Set<string>()
constructor(
private present: (requestId: string, behavior: NotificationBehavior) => void
) {}
setHandler(handler: NotificationHandlerInput | null) {
this.handler = handler
this.nulled = handler === null
}
// returns false for a duplicate of an in-flight id; the caller skips
// listener delivery for those.
receive(requestId: string, notification: Notification): boolean {
if (this.pending.has(requestId)) return false
if (!this.handler) {
// never set: show, like expo. explicitly nulled: stay quiet.
this.present(requestId, { ...(this.nulled ? suppressBehavior : showAllBehavior) })
return true
}
const handler = this.handler
this.pending.add(requestId)
Promise.resolve()
.then(() => handler.handleNotification(notification))
.then(
(behavior) => this.settle(requestId, normalizeBehavior(behavior)),
() => this.settle(requestId, showAllBehavior)
)
return true
}
private settle(requestId: string, behavior: NotificationBehavior) {
if (!this.pending.delete(requestId)) return
this.present(requestId, { ...behavior })
}
}