UNPKG

one

Version:

One is a React framework for web, iOS, and Android apps.

64 lines (56 loc) • 2.39 kB
import { Platform } from 'react-native' import { NitroModules } from 'react-native-nitro-modules' import { rethrowNativeError } from '../nativeError' import type { AppIntentInvocation, OneAppIntents } from '../specs/OneAppIntents.nitro' import { assertActionHandler } from './validate' import { AppIntents as unavailableAppIntents } from './unavailable' export type AppIntentHandler = (text: string | null) => string | Promise<string> let hybrid: OneAppIntents | undefined const handlers = new Map<string, AppIntentHandler>() function native(): OneAppIntents { if (Platform.OS !== 'ios') throw new Error('AppIntents requires an iOS native build') hybrid ??= NitroModules.createHybridObject<OneAppIntents>('OneAppIntents') return hybrid } function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error) } function defineAction(identifier: string, handler: AppIntentHandler): () => void { assertActionHandler(identifier, handler) if (handlers.has(identifier)) { throw new Error(`AppIntents.defineAction: ${identifier} is already defined`) } handlers.set(identifier, handler) const dispatch = (invocation: AppIntentInvocation): void => { Promise.resolve() .then(() => handler(invocation.text ?? null)) .then((result) => { if (typeof result !== 'string') { throw new TypeError('AppIntents action handler must return a string') } native().complete(invocation.executionId, true, result) }) .catch((error: unknown) => { native().complete(invocation.executionId, false, errorMessage(error)) }) } let remove: (() => void) | undefined try { // subscription is future-only; this explicit pull claims cold requests. remove = native().addInvocationListener(identifier, dispatch) for (const invocation of native().claimPending(identifier)) dispatch(invocation) } catch (error) { remove?.() handlers.delete(identifier) rethrowNativeError(error) } return () => { if (handlers.get(identifier) !== handler) return handlers.delete(identifier) remove?.() } } const nativeAppIntents = Object.freeze({ defineAction }) // AppIntents is iOS only, so Android keeps the unavailable contract export const AppIntents: typeof nativeAppIntents = Platform.OS === 'android' ? unavailableAppIntents : nativeAppIntents