@ng-native/expo 0.2.0 → 0.3.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/README.md +32 -382
- package/dist/async-storage.d.ts +15 -0
- package/dist/async-storage.js +29 -0
- package/dist/async-storage.js.map +1 -0
- package/dist/audio.d.ts +10 -0
- package/dist/audio.js +26 -0
- package/dist/audio.js.map +1 -0
- package/dist/expo-ui-components.d.ts +142 -10
- package/dist/expo-ui-components.js +362 -39
- package/dist/expo-ui-components.js.map +1 -1
- package/dist/expo-ui.js +9 -6
- package/dist/expo-ui.js.map +1 -1
- package/dist/foldable.d.ts +78 -0
- package/dist/foldable.js +87 -0
- package/dist/foldable.js.map +1 -0
- package/dist/fonts.d.ts +3 -0
- package/dist/fonts.js +7 -2
- package/dist/fonts.js.map +1 -1
- package/dist/image-picker.js +3 -9
- package/dist/image-picker.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live-activity.d.ts +88 -0
- package/dist/live-activity.js +150 -0
- package/dist/live-activity.js.map +1 -0
- package/dist/location.js +2 -7
- package/dist/location.js.map +1 -1
- package/dist/media-library.js +1 -3
- package/dist/media-library.js.map +1 -1
- package/dist/native.d.ts +4 -1
- package/dist/native.js +60 -4
- package/dist/native.js.map +1 -1
- package/dist/notifications.js +2 -4
- package/dist/notifications.js.map +1 -1
- package/dist/permissions.d.ts +2 -0
- package/dist/permissions.js +6 -0
- package/dist/permissions.js.map +1 -1
- package/dist/player.d.ts +24 -11
- package/dist/player.js +7 -26
- package/dist/player.js.map +1 -1
- package/dist/register-expo-view.d.ts +5 -0
- package/dist/register-expo-view.js +5 -1
- package/dist/register-expo-view.js.map +1 -1
- package/dist/screen-capture.js +2 -4
- package/dist/screen-capture.js.map +1 -1
- package/dist/secure-store.d.ts +15 -0
- package/dist/secure-store.js +30 -0
- package/dist/secure-store.js.map +1 -0
- package/dist/store.d.ts +5 -10
- package/dist/store.js +5 -36
- package/dist/store.js.map +1 -1
- package/dist/tracking.d.ts +1 -1
- package/dist/tracking.js +3 -5
- package/dist/tracking.js.map +1 -1
- package/dist/video.d.ts +5 -0
- package/dist/video.js +21 -0
- package/dist/video.js.map +1 -0
- package/dist/watch.d.ts +103 -0
- package/dist/watch.js +228 -0
- package/dist/watch.js.map +1 -0
- package/package.json +49 -4
package/dist/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAI,IAAa;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAKD;;;;GAIG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACzB,IAAI,GAAG,oBAAoB,CAAC;IAC9C,uDAAuD;IAC9C,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,QAAwB,EAAE,KAAe;QACnE,KAAK,CACH,QAAQ,KAAK,KAAK;YAChB,CAAC,CAAC,GAAG,MAAM,wDAAwD,MAAM,iCAAiC;YAC1G,CAAC,CAAC,GAAG,MAAM,uEAAuE,MAAM,KAAK;gBACzF,uCAAuC,QAAQ,wCAAwC;gBACvF,2EAA2E,EACjF,EAAE,KAAK,EAAE,CACV,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,uFAAuF;AACvF,
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reaching an Expo module without importing one.\n *\n * A static `import ... from 'expo-battery'` makes the file unloadable by Node, which is what used\n * to force every service into two files: the behaviour somewhere the test suite could reach, and\n * the binding somewhere it could not. A `require` inside a function has neither problem. Metro\n * still resolves the string literal at build time, so the module is bundled exactly as before and\n * an app that has not installed it still gets a build error rather than a surprise at runtime;\n * Node never evaluates the call at all, because nothing in a test asks for the real platform.\n *\n * The `catch` is doing two jobs, and only one of them is Node. In an ESM test `require` is not\n * defined, so calling it throws `ReferenceError` and the service goes inert - the same state as\n * a device that has no such sensor. On a device the throw is a module that is present but not\n * linked, which is the same answer for the same reason.\n */\nexport function optional<T>(load: () => T): T | null {\n try {\n return load();\n } catch {\n return null;\n }\n}\n\n/** The platforms an Expo module is written for. */\nexport type ModulePlatform = 'ios' | 'android' | 'web';\n\n/**\n * A module the app needs and does not have: the package is not installed, or it is and the app was\n * not rebuilt since, so its native half is not in the binary. Development builds and Expo Go carry\n * only the native modules they were built with.\n */\nexport class MissingModuleError extends Error {\n override readonly name = 'MissingModuleError';\n /** The package, as it is installed: `expo-haptics`. */\n readonly module: string;\n\n constructor(module: string, platform: ModulePlatform, cause?: unknown) {\n super(\n platform === 'web'\n ? `${module} is not installed. Install it with \"npx expo install ${module}\", then restart the dev server.`\n : `${module} is not in this build of the app. Install it with \"npx expo install ${module}\", ` +\n `then rebuild the app (\"npx expo run:${platform}\", or a new EAS build): a development ` +\n `build, and Expo Go, contain only the native modules they were built with.`,\n { cause },\n );\n this.module = module;\n }\n}\n\n/** The platform the app is running on, or null in Node, where there is none to ask. */\nexport function currentPlatform(): ModulePlatform | null {\n return optional(\n () => (require('react-native') as { Platform: { OS: string } }).Platform.OS as ModulePlatform,\n );\n}\n\n/**\n * For each Expo package whose JavaScript requires a native module as it is evaluated, and so\n * throws when its native half is not in the build, one of those native modules. Any one of them\n * says whether the package's native half is there, and each of these is registered on both iOS and\n * Android.\n */\nconst NATIVE_MODULES: Readonly<Record<string, string>> = {\n 'expo-application': 'ExpoApplication',\n 'expo-asset': 'ExpoAsset',\n 'expo-audio': 'ExpoAudio',\n 'expo-background-task': 'ExpoBackgroundTask',\n 'expo-battery': 'ExpoBattery',\n 'expo-brightness': 'ExpoBrightness',\n 'expo-clipboard': 'ExpoClipboard',\n 'expo-crypto': 'ExpoCrypto',\n 'expo-device': 'ExpoDevice',\n 'expo-document-picker': 'ExpoDocumentPicker',\n 'expo-file-system': 'FileSystem',\n 'expo-foldables': 'ExpoFoldables',\n 'expo-font': 'ExpoFontLoader',\n 'expo-image-manipulator': 'ExpoImageManipulator',\n 'expo-image-picker': 'ExponentImagePicker',\n 'expo-keep-awake': 'ExpoKeepAwake',\n 'expo-local-authentication': 'ExpoLocalAuthentication',\n 'expo-local-llm': 'ExpoLocalLlm',\n 'expo-localization': 'ExpoLocalization',\n 'expo-location': 'ExpoLocation',\n 'expo-media-library': 'ExpoMediaLibrary',\n 'expo-network': 'ExpoNetwork',\n 'expo-notifications': 'ExpoNotificationPresenter',\n 'expo-screen-capture': 'ExpoScreenCapture',\n 'expo-screen-orientation': 'ExpoScreenOrientation',\n 'expo-secure-store': 'ExpoSecureStore',\n 'expo-sensors': 'ExponentAccelerometer',\n 'expo-sqlite': 'ExpoSQLite',\n 'expo-store-review': 'ExpoStoreReview',\n 'expo-tracking-transparency': 'ExpoTrackingTransparency',\n 'expo-updates': 'ExpoUpdates',\n 'expo-video': 'ExpoVideo',\n 'expo-web-browser': 'ExpoWebBrowser',\n};\n\n/**\n * Whether `module`'s native half is missing from the build on `platform`, asked of Expo before its\n * JavaScript is evaluated. That JavaScript throws while it is being evaluated when its native module\n * is not there, and Metro reports that as fatal, before any `catch` here, whenever it is not inside\n * another module's load: in a service's factory, say. Not on the web, where a package registers its\n * module only once it is evaluated.\n */\nfunction nativeHalfMissing(module: string, platform: ModulePlatform | null): boolean {\n const name = NATIVE_MODULES[module];\n if (!name || platform === null || platform === 'web') return false;\n const core = optional(() => require('expo-modules-core') as typeof import('expo-modules-core'));\n return core !== null && !core.requireOptionalNativeModule(name);\n}\n\n/**\n * The Expo module a service needs.\n *\n * Where the module exists for the platform the app is on, a module that is missing is a mistake\n * the developer can fix, so this throws a `MissingModuleError` that says how - rather than leave a\n * feature that silently does nothing. Anywhere it cannot exist - an iOS-only module on Android, the\n * web, and Node, where a test runs with no platform at all - this answers null, and the service\n * goes inert, as code shared across platforms needs it to.\n *\n * `load` answering null or undefined counts as missing too: `requireOptionalNativeModule` answers\n * null rather than throwing. So does a package whose native half is not in the build, which `load`\n * is not called for: see `nativeHalfMissing`.\n */\nexport function expoModule<T>(\n module: string,\n load: () => T | null | undefined,\n platforms: readonly ModulePlatform[] = ['ios', 'android'],\n): T | null {\n const platform = currentPlatform();\n let failure: unknown;\n try {\n const loaded = nativeHalfMissing(module, platform) ? null : load();\n if (loaded != null) return loaded;\n } catch (error) {\n failure = error;\n }\n if (platform === null || !platforms.includes(platform)) return null;\n throw new MissingModuleError(module, platform, failure);\n}\n"]}
|
package/dist/notifications.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import { DestroyRef, InjectionToken, Service, inject, signal } from '@angular/core';
|
|
38
38
|
import { expoModule } from "./native.js";
|
|
39
|
-
import { Permission } from "./permissions.js";
|
|
39
|
+
import { Permission, UNAVAILABLE } from "./permissions.js";
|
|
40
40
|
import * as i0 from "@angular/core";
|
|
41
41
|
/**
|
|
42
42
|
* The kinds of trigger a schedule takes, as `expo-notifications`' own enum. Importing the enum
|
|
@@ -52,8 +52,6 @@ export const TriggerType = {
|
|
|
52
52
|
DATE: 'date',
|
|
53
53
|
TIME_INTERVAL: 'timeInterval',
|
|
54
54
|
};
|
|
55
|
-
/** The permission answer without the module: nothing granted, and nothing to ask. */
|
|
56
|
-
const UNAVAILABLE = { status: 'denied', granted: false, canAskAgain: false };
|
|
57
55
|
export class Notifications {
|
|
58
56
|
/** Overridden in a test to deliver a notification nobody sent. */
|
|
59
57
|
static SOURCE = new InjectionToken('angular-native.notificationsSource', {
|
|
@@ -76,7 +74,7 @@ export class Notifications {
|
|
|
76
74
|
permission;
|
|
77
75
|
constructor() {
|
|
78
76
|
const native = this.native;
|
|
79
|
-
this.permission = Permission.of(() => native?.getPermissionsAsync() ?? Promise.resolve(UNAVAILABLE), () => native?.requestPermissionsAsync() ?? Promise.resolve(UNAVAILABLE));
|
|
77
|
+
this.permission = Permission.of(() => native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE), () => native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE));
|
|
80
78
|
if (!native)
|
|
81
79
|
return;
|
|
82
80
|
const subscriptions = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Notifications`, bound to `expo-notifications`: everything the module does, as one injectable\n * service.\n *\n * ```ts\n * import { Notifications, TriggerType } from '@ng-native/expo/notifications';\n *\n * private readonly notifications = inject(Notifications);\n *\n * async remind(): Promise<void> {\n * if (!(await this.notifications.permission.ensure())) return;\n * await this.notifications.schedule({\n * content: { title: 'Time to stretch' },\n * trigger: { type: TriggerType.DAILY, hour: 9, minute: 0 },\n * });\n * }\n *\n * constructor() {\n * const tapped = this.notifications.take();\n * if (tapped) this.router.navigateByUrl(routeFor(tapped));\n * }\n * ```\n *\n * The listeners are the part a hook used to be the only way to reach, and they are signals here.\n * *Received* is a notification arriving while the app is in front; the app usually decides whether\n * to show anything. *Responded* is the user having tapped one, which is a navigation instruction\n * and must not be missed - including the one that launched the app, which arrived before anything\n * was listening.\n *\n * The rest - permission, scheduling, what is on screen, the badge, Android's channels, the action\n * buttons of categories, the foreground handler, push tokens and topics, background tasks - is the\n * module's own functions under shorter names, with its own types. Without the module installed,\n * or in a test that provides nothing, every query answers empty and every change does nothing.\n * The push token methods ask for the permission first, since a token nobody can be shown anything\n * with fails silently: the push simply never arrives.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport type {\n DevicePushToken,\n Notification,\n NotificationResponse,\n SchedulableTriggerInputTypes,\n} from 'expo-notifications';\nimport { expoModule } from './native.ts';\nimport { Permission, type PermissionResponse } from './permissions.ts';\n\ntype Expo = typeof import('expo-notifications');\n\n/**\n * The module's functions this service calls. The real module is one; a test provides a fake that\n * delivers notifications nobody sent.\n */\nexport type NativeNotifications = Pick<\n Expo,\n | 'addNotificationReceivedListener'\n | 'addNotificationResponseReceivedListener'\n | 'addNotificationsDroppedListener'\n | 'addPushTokenListener'\n | 'getLastNotificationResponseAsync'\n | 'clearLastNotificationResponseAsync'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n | 'scheduleNotificationAsync'\n | 'cancelScheduledNotificationAsync'\n | 'cancelAllScheduledNotificationsAsync'\n | 'getAllScheduledNotificationsAsync'\n | 'getNextTriggerDateAsync'\n | 'getPresentedNotificationsAsync'\n | 'dismissNotificationAsync'\n | 'dismissAllNotificationsAsync'\n | 'getBadgeCountAsync'\n | 'setBadgeCountAsync'\n | 'getNotificationChannelsAsync'\n | 'getNotificationChannelAsync'\n | 'setNotificationChannelAsync'\n | 'deleteNotificationChannelAsync'\n | 'getNotificationChannelGroupsAsync'\n | 'getNotificationChannelGroupAsync'\n | 'setNotificationChannelGroupAsync'\n | 'deleteNotificationChannelGroupAsync'\n | 'getNotificationCategoriesAsync'\n | 'setNotificationCategoryAsync'\n | 'deleteNotificationCategoryAsync'\n | 'setNotificationHandler'\n | 'getExpoPushTokenAsync'\n | 'getDevicePushTokenAsync'\n | 'unregisterForNotificationsAsync'\n | 'setAutoServerRegistrationEnabledAsync'\n | 'subscribeToTopicAsync'\n | 'unsubscribeFromTopicAsync'\n | 'registerTaskAsync'\n | 'unregisterTaskAsync'\n>;\n\ntype Args<K extends keyof NativeNotifications> = Parameters<NativeNotifications[K]>;\n\n/**\n * The kinds of trigger a schedule takes, as `expo-notifications`' own enum. Importing the enum\n * from the module loads the module, which a test in Node cannot; these are the same strings,\n * typed as the enum, so `{ type: TriggerType.DAILY, hour: 9, minute: 0 }` is accepted either way.\n */\nexport const TriggerType = {\n CALENDAR: 'calendar',\n DAILY: 'daily',\n WEEKLY: 'weekly',\n MONTHLY: 'monthly',\n YEARLY: 'yearly',\n DATE: 'date',\n TIME_INTERVAL: 'timeInterval',\n} as unknown as typeof SchedulableTriggerInputTypes;\n\n/** The permission answer without the module: nothing granted, and nothing to ask. */\nconst UNAVAILABLE: PermissionResponse = { status: 'denied', granted: false, canAskAgain: false };\n\n@Service()\nexport class Notifications {\n /** Overridden in a test to deliver a notification nobody sent. */\n static readonly SOURCE = new InjectionToken<NativeNotifications | null>(\n 'angular-native.notificationsSource',\n {\n factory: () => expoModule('expo-notifications', () => require('expo-notifications') as Expo),\n },\n );\n\n private readonly native = inject(Notifications.SOURCE);\n private readonly received = signal<Notification | null>(null);\n private readonly responded = signal<NotificationResponse | null>(null);\n private readonly rolled = signal<DevicePushToken | null>(null);\n private readonly drops = signal(0);\n private readonly handled = new Set<string>();\n\n /**\n * The permission to show notifications. `ensure()` asks only if it has not been answered; for\n * iOS's finer options (provisional, critical alerts), use {@link requestPermission}.\n */\n readonly permission: Permission;\n\n constructor() {\n const native = this.native;\n this.permission = Permission.of(\n () => native?.getPermissionsAsync() ?? Promise.resolve(UNAVAILABLE),\n () => native?.requestPermissionsAsync() ?? Promise.resolve(UNAVAILABLE),\n );\n if (!native) return;\n const subscriptions = [\n native.addNotificationReceivedListener((notification) => this.received.set(notification)),\n native.addNotificationResponseReceivedListener((response) => this.responded.set(response)),\n // Android drops notifications when too many arrive at once while the app is not running;\n // this is the only word that any went missing.\n native.addNotificationsDroppedListener(() => this.drops.update((count) => count + 1)),\n // A device push token can be rolled by the platform while the app is running; the old one\n // then fails silently rather than delivering anything. This is the only way to find out.\n native.addPushTokenListener((token) => this.rolled.set(token)),\n ];\n inject(DestroyRef).onDestroy(() => subscriptions.forEach((one) => one.remove()));\n // The response that launched the app happened before anything was listening, so it has to be\n // asked for rather than waited for. Without this a cold start from a notification does\n // nothing, which is the single most common way this is got wrong. A platform that cannot say\n // is the same as a launch without a notification.\n void native\n .getLastNotificationResponseAsync()\n .then((response) => {\n if (response && this.responded() === null) this.responded.set(response);\n })\n .catch(() => {});\n }\n\n /** The most recent notification that arrived while the app was in front. */\n readonly latest: Signal<Notification | null> = this.received.asReadonly();\n\n /**\n * The most recent notification the user tapped, including the one that launched the app.\n *\n * A router usually wants `take()` instead: this stays set, so a screen that reacts to it will\n * react again every time it is created.\n */\n readonly response: Signal<NotificationResponse | null> = this.responded.asReadonly();\n\n /**\n * The response, once.\n *\n * Routing on a tap is a one-off, and the signal is not: an effect on it fires again whenever\n * its component is recreated, which sends the user back to a screen they navigated away from.\n */\n take(): NotificationResponse | null {\n const response = this.responded();\n if (!response) return null;\n\n const id = response.notification.request.identifier + response.actionIdentifier;\n if (this.handled.has(id)) return null;\n this.handled.add(id);\n return response;\n }\n\n /** Forgets the tap, here and in the module, so the next launch does not see it again. */\n async clearResponse(): Promise<void> {\n this.responded.set(null);\n await this.native?.clearLastNotificationResponseAsync();\n }\n\n /** How many times Android has reported notifications dropped. Zero on iOS. */\n readonly dropped: Signal<number> = this.drops.asReadonly();\n\n /** Asks with iOS's finer options. The answer also updates {@link permission}. */\n async requestPermission(...options: Args<'requestPermissionsAsync'>): Promise<boolean> {\n if (!this.native) return false;\n await this.native.requestPermissionsAsync(...options);\n return this.permission.check();\n }\n\n /** Schedules a local notification, or shows it now with a null trigger. Null without the module. */\n async schedule(...request: Args<'scheduleNotificationAsync'>): Promise<string | null> {\n return (await this.native?.scheduleNotificationAsync(...request)) ?? null;\n }\n\n async cancel(identifier: string): Promise<void> {\n await this.native?.cancelScheduledNotificationAsync(identifier);\n }\n\n async cancelAll(): Promise<void> {\n await this.native?.cancelAllScheduledNotificationsAsync();\n }\n\n /** Every local notification still waiting for its trigger. */\n async scheduled(): Promise<Awaited<ReturnType<Expo['getAllScheduledNotificationsAsync']>>> {\n return (await this.native?.getAllScheduledNotificationsAsync()) ?? [];\n }\n\n /** When a trigger would next fire, in milliseconds since the epoch; null if it never will. */\n async nextTriggerDate(...trigger: Args<'getNextTriggerDateAsync'>): Promise<number | null> {\n return (await this.native?.getNextTriggerDateAsync(...trigger)) ?? null;\n }\n\n /** The notifications in the notification centre that are the app's. */\n async presented(): Promise<Notification[]> {\n return (await this.native?.getPresentedNotificationsAsync()) ?? [];\n }\n\n async dismiss(identifier: string): Promise<void> {\n await this.native?.dismissNotificationAsync(identifier);\n }\n\n /** Clears every notification the app has on screen. A failure is ignored. */\n dismissAll(): void {\n void this.native?.dismissAllNotificationsAsync().catch(() => {});\n }\n\n async badge(): Promise<number> {\n return (await this.native?.getBadgeCountAsync()) ?? 0;\n }\n\n /**\n * iOS shows this on the icon; Android shows it where the launcher supports it. A failure is\n * ignored.\n */\n setBadge(count: number): void {\n void this.native?.setBadgeCountAsync(count).catch(() => {});\n }\n\n /** Android's notification channels. Empty on iOS, which has none. */\n async channels(): Promise<Awaited<ReturnType<Expo['getNotificationChannelsAsync']>>> {\n return (await this.native?.getNotificationChannelsAsync()) ?? [];\n }\n\n async channel(\n identifier: string,\n ): Promise<Awaited<ReturnType<Expo['getNotificationChannelAsync']>>> {\n return (await this.native?.getNotificationChannelAsync(identifier)) ?? null;\n }\n\n /**\n * Creates or updates an Android channel; a notification on Android 8 and later needs one to\n * show at all. Null on iOS.\n */\n async setChannel(\n ...channel: Args<'setNotificationChannelAsync'>\n ): Promise<Awaited<ReturnType<Expo['setNotificationChannelAsync']>>> {\n return (await this.native?.setNotificationChannelAsync(...channel)) ?? null;\n }\n\n async deleteChannel(identifier: string): Promise<void> {\n await this.native?.deleteNotificationChannelAsync(identifier);\n }\n\n async channelGroups(): Promise<Awaited<ReturnType<Expo['getNotificationChannelGroupsAsync']>>> {\n return (await this.native?.getNotificationChannelGroupsAsync()) ?? [];\n }\n\n async channelGroup(\n identifier: string,\n ): Promise<Awaited<ReturnType<Expo['getNotificationChannelGroupAsync']>>> {\n return (await this.native?.getNotificationChannelGroupAsync(identifier)) ?? null;\n }\n\n async setChannelGroup(\n ...group: Args<'setNotificationChannelGroupAsync'>\n ): Promise<Awaited<ReturnType<Expo['setNotificationChannelGroupAsync']>>> {\n return (await this.native?.setNotificationChannelGroupAsync(...group)) ?? null;\n }\n\n async deleteChannelGroup(identifier: string): Promise<void> {\n await this.native?.deleteNotificationChannelGroupAsync(identifier);\n }\n\n /** The categories set: each a set of action buttons a notification opts into. */\n async categories(): Promise<Awaited<ReturnType<Expo['getNotificationCategoriesAsync']>>> {\n return (await this.native?.getNotificationCategoriesAsync()) ?? [];\n }\n\n /**\n * Action buttons for notifications whose content names this category. A tap on one arrives as\n * a response with the button's identifier, and the typed text as `userText` for a text input.\n */\n async setCategory(\n ...category: Args<'setNotificationCategoryAsync'>\n ): Promise<Awaited<ReturnType<Expo['setNotificationCategoryAsync']>> | null> {\n return (await this.native?.setNotificationCategoryAsync(...category)) ?? null;\n }\n\n async deleteCategory(identifier: string): Promise<boolean> {\n return (await this.native?.deleteNotificationCategoryAsync(identifier)) ?? false;\n }\n\n /**\n * What a notification that arrives while the app is in front does: shown, silent, with a sound\n * or a badge. Without a handler, it is not shown. Null removes the handler.\n */\n setHandler(...handler: Args<'setNotificationHandler'>): void {\n this.native?.setNotificationHandler(...handler);\n }\n\n /**\n * The token to hand a backend so it can send a push through Expo's service.\n *\n * Asks for the notification permission first - a token nobody can be shown anything with is not\n * worth having. `projectId` defaults to the app's own EAS project; pass it explicitly for a bare\n * workflow app, one still without EAS Build configured, or a token fetched outside\n * `expo-notifications`' default resolution. Null without the permission or without the module.\n */\n async getExpoPushToken(projectId?: string): Promise<string | null> {\n if (!this.native || !(await this.permission.ensure())) return null;\n const token = await this.native.getExpoPushTokenAsync(projectId ? { projectId } : undefined);\n return token.data;\n }\n\n /**\n * The raw APNs (iOS) or FCM (Android) token, for a backend that talks to those services\n * directly rather than through Expo's push service. Same permission and module rules as\n * {@link getExpoPushToken}.\n */\n async getDevicePushToken(): Promise<DevicePushToken | null> {\n if (!this.native || !(await this.permission.ensure())) return null;\n return this.native.getDevicePushTokenAsync();\n }\n\n /**\n * The device push token, each time the platform rolls it while the app is running.\n *\n * A rolled token is not the one a backend has on file, and sending to the old one fails\n * silently - this is what tells an app to register the new one. Unset until a roll happens;\n * most of an app's life this stays null.\n */\n readonly devicePushToken: Signal<DevicePushToken | null> = this.rolled.asReadonly();\n\n /** Stops the device receiving pushes until a token is asked for again. */\n async unregister(): Promise<void> {\n await this.native?.unregisterForNotificationsAsync();\n }\n\n /** Whether Expo re-registers the device token with its push service by itself. */\n async setAutoServerRegistration(enabled: boolean): Promise<void> {\n await this.native?.setAutoServerRegistrationEnabledAsync(enabled);\n }\n\n /** Android: receive pushes sent to an FCM topic. */\n async subscribeToTopic(topic: string): Promise<void> {\n await this.native?.subscribeToTopicAsync(topic);\n }\n\n async unsubscribeFromTopic(topic: string): Promise<void> {\n await this.native?.unsubscribeFromTopicAsync(topic);\n }\n\n /**\n * Runs a task defined with `expo-task-manager` when a notification arrives in the background.\n * The task itself is defined at the top level of `main.ts`, since it runs without the app.\n */\n async registerTask(name: string): Promise<void> {\n await this.native?.registerTaskAsync(name);\n }\n\n async unregisterTask(name: string): Promise<void> {\n await this.native?.unregisterTaskAsync(name);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `Notifications`, bound to `expo-notifications`: everything the module does, as one injectable\n * service.\n *\n * ```ts\n * import { Notifications, TriggerType } from '@ng-native/expo/notifications';\n *\n * private readonly notifications = inject(Notifications);\n *\n * async remind(): Promise<void> {\n * if (!(await this.notifications.permission.ensure())) return;\n * await this.notifications.schedule({\n * content: { title: 'Time to stretch' },\n * trigger: { type: TriggerType.DAILY, hour: 9, minute: 0 },\n * });\n * }\n *\n * constructor() {\n * const tapped = this.notifications.take();\n * if (tapped) this.router.navigateByUrl(routeFor(tapped));\n * }\n * ```\n *\n * The listeners are the part a hook used to be the only way to reach, and they are signals here.\n * *Received* is a notification arriving while the app is in front; the app usually decides whether\n * to show anything. *Responded* is the user having tapped one, which is a navigation instruction\n * and must not be missed - including the one that launched the app, which arrived before anything\n * was listening.\n *\n * The rest - permission, scheduling, what is on screen, the badge, Android's channels, the action\n * buttons of categories, the foreground handler, push tokens and topics, background tasks - is the\n * module's own functions under shorter names, with its own types. Without the module installed,\n * or in a test that provides nothing, every query answers empty and every change does nothing.\n * The push token methods ask for the permission first, since a token nobody can be shown anything\n * with fails silently: the push simply never arrives.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport type {\n DevicePushToken,\n Notification,\n NotificationResponse,\n SchedulableTriggerInputTypes,\n} from 'expo-notifications';\nimport { expoModule } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-notifications');\n\n/**\n * The module's functions this service calls. The real module is one; a test provides a fake that\n * delivers notifications nobody sent.\n */\nexport type NativeNotifications = Pick<\n Expo,\n | 'addNotificationReceivedListener'\n | 'addNotificationResponseReceivedListener'\n | 'addNotificationsDroppedListener'\n | 'addPushTokenListener'\n | 'getLastNotificationResponseAsync'\n | 'clearLastNotificationResponseAsync'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n | 'scheduleNotificationAsync'\n | 'cancelScheduledNotificationAsync'\n | 'cancelAllScheduledNotificationsAsync'\n | 'getAllScheduledNotificationsAsync'\n | 'getNextTriggerDateAsync'\n | 'getPresentedNotificationsAsync'\n | 'dismissNotificationAsync'\n | 'dismissAllNotificationsAsync'\n | 'getBadgeCountAsync'\n | 'setBadgeCountAsync'\n | 'getNotificationChannelsAsync'\n | 'getNotificationChannelAsync'\n | 'setNotificationChannelAsync'\n | 'deleteNotificationChannelAsync'\n | 'getNotificationChannelGroupsAsync'\n | 'getNotificationChannelGroupAsync'\n | 'setNotificationChannelGroupAsync'\n | 'deleteNotificationChannelGroupAsync'\n | 'getNotificationCategoriesAsync'\n | 'setNotificationCategoryAsync'\n | 'deleteNotificationCategoryAsync'\n | 'setNotificationHandler'\n | 'getExpoPushTokenAsync'\n | 'getDevicePushTokenAsync'\n | 'unregisterForNotificationsAsync'\n | 'setAutoServerRegistrationEnabledAsync'\n | 'subscribeToTopicAsync'\n | 'unsubscribeFromTopicAsync'\n | 'registerTaskAsync'\n | 'unregisterTaskAsync'\n>;\n\ntype Args<K extends keyof NativeNotifications> = Parameters<NativeNotifications[K]>;\n\n/**\n * The kinds of trigger a schedule takes, as `expo-notifications`' own enum. Importing the enum\n * from the module loads the module, which a test in Node cannot; these are the same strings,\n * typed as the enum, so `{ type: TriggerType.DAILY, hour: 9, minute: 0 }` is accepted either way.\n */\nexport const TriggerType = {\n CALENDAR: 'calendar',\n DAILY: 'daily',\n WEEKLY: 'weekly',\n MONTHLY: 'monthly',\n YEARLY: 'yearly',\n DATE: 'date',\n TIME_INTERVAL: 'timeInterval',\n} as unknown as typeof SchedulableTriggerInputTypes;\n\n@Service()\nexport class Notifications {\n /** Overridden in a test to deliver a notification nobody sent. */\n static readonly SOURCE = new InjectionToken<NativeNotifications | null>(\n 'angular-native.notificationsSource',\n {\n factory: () => expoModule('expo-notifications', () => require('expo-notifications') as Expo),\n },\n );\n\n private readonly native = inject(Notifications.SOURCE);\n private readonly received = signal<Notification | null>(null);\n private readonly responded = signal<NotificationResponse | null>(null);\n private readonly rolled = signal<DevicePushToken | null>(null);\n private readonly drops = signal(0);\n private readonly handled = new Set<string>();\n\n /**\n * The permission to show notifications. `ensure()` asks only if it has not been answered; for\n * iOS's finer options (provisional, critical alerts), use {@link requestPermission}.\n */\n readonly permission: Permission;\n\n constructor() {\n const native = this.native;\n this.permission = Permission.of(\n () => native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n if (!native) return;\n const subscriptions = [\n native.addNotificationReceivedListener((notification) => this.received.set(notification)),\n native.addNotificationResponseReceivedListener((response) => this.responded.set(response)),\n // Android drops notifications when too many arrive at once while the app is not running;\n // this is the only word that any went missing.\n native.addNotificationsDroppedListener(() => this.drops.update((count) => count + 1)),\n // A device push token can be rolled by the platform while the app is running; the old one\n // then fails silently rather than delivering anything. This is the only way to find out.\n native.addPushTokenListener((token) => this.rolled.set(token)),\n ];\n inject(DestroyRef).onDestroy(() => subscriptions.forEach((one) => one.remove()));\n // The response that launched the app happened before anything was listening, so it has to be\n // asked for rather than waited for. Without this a cold start from a notification does\n // nothing, which is the single most common way this is got wrong. A platform that cannot say\n // is the same as a launch without a notification.\n void native\n .getLastNotificationResponseAsync()\n .then((response) => {\n if (response && this.responded() === null) this.responded.set(response);\n })\n .catch(() => {});\n }\n\n /** The most recent notification that arrived while the app was in front. */\n readonly latest: Signal<Notification | null> = this.received.asReadonly();\n\n /**\n * The most recent notification the user tapped, including the one that launched the app.\n *\n * A router usually wants `take()` instead: this stays set, so a screen that reacts to it will\n * react again every time it is created.\n */\n readonly response: Signal<NotificationResponse | null> = this.responded.asReadonly();\n\n /**\n * The response, once.\n *\n * Routing on a tap is a one-off, and the signal is not: an effect on it fires again whenever\n * its component is recreated, which sends the user back to a screen they navigated away from.\n */\n take(): NotificationResponse | null {\n const response = this.responded();\n if (!response) return null;\n\n const id = response.notification.request.identifier + response.actionIdentifier;\n if (this.handled.has(id)) return null;\n this.handled.add(id);\n return response;\n }\n\n /** Forgets the tap, here and in the module, so the next launch does not see it again. */\n async clearResponse(): Promise<void> {\n this.responded.set(null);\n await this.native?.clearLastNotificationResponseAsync();\n }\n\n /** How many times Android has reported notifications dropped. Zero on iOS. */\n readonly dropped: Signal<number> = this.drops.asReadonly();\n\n /** Asks with iOS's finer options. The answer also updates {@link permission}. */\n async requestPermission(...options: Args<'requestPermissionsAsync'>): Promise<boolean> {\n if (!this.native) return false;\n await this.native.requestPermissionsAsync(...options);\n return this.permission.check();\n }\n\n /** Schedules a local notification, or shows it now with a null trigger. Null without the module. */\n async schedule(...request: Args<'scheduleNotificationAsync'>): Promise<string | null> {\n return (await this.native?.scheduleNotificationAsync(...request)) ?? null;\n }\n\n async cancel(identifier: string): Promise<void> {\n await this.native?.cancelScheduledNotificationAsync(identifier);\n }\n\n async cancelAll(): Promise<void> {\n await this.native?.cancelAllScheduledNotificationsAsync();\n }\n\n /** Every local notification still waiting for its trigger. */\n async scheduled(): Promise<Awaited<ReturnType<Expo['getAllScheduledNotificationsAsync']>>> {\n return (await this.native?.getAllScheduledNotificationsAsync()) ?? [];\n }\n\n /** When a trigger would next fire, in milliseconds since the epoch; null if it never will. */\n async nextTriggerDate(...trigger: Args<'getNextTriggerDateAsync'>): Promise<number | null> {\n return (await this.native?.getNextTriggerDateAsync(...trigger)) ?? null;\n }\n\n /** The notifications in the notification centre that are the app's. */\n async presented(): Promise<Notification[]> {\n return (await this.native?.getPresentedNotificationsAsync()) ?? [];\n }\n\n async dismiss(identifier: string): Promise<void> {\n await this.native?.dismissNotificationAsync(identifier);\n }\n\n /** Clears every notification the app has on screen. A failure is ignored. */\n dismissAll(): void {\n void this.native?.dismissAllNotificationsAsync().catch(() => {});\n }\n\n async badge(): Promise<number> {\n return (await this.native?.getBadgeCountAsync()) ?? 0;\n }\n\n /**\n * iOS shows this on the icon; Android shows it where the launcher supports it. A failure is\n * ignored.\n */\n setBadge(count: number): void {\n void this.native?.setBadgeCountAsync(count).catch(() => {});\n }\n\n /** Android's notification channels. Empty on iOS, which has none. */\n async channels(): Promise<Awaited<ReturnType<Expo['getNotificationChannelsAsync']>>> {\n return (await this.native?.getNotificationChannelsAsync()) ?? [];\n }\n\n async channel(\n identifier: string,\n ): Promise<Awaited<ReturnType<Expo['getNotificationChannelAsync']>>> {\n return (await this.native?.getNotificationChannelAsync(identifier)) ?? null;\n }\n\n /**\n * Creates or updates an Android channel; a notification on Android 8 and later needs one to\n * show at all. Null on iOS.\n */\n async setChannel(\n ...channel: Args<'setNotificationChannelAsync'>\n ): Promise<Awaited<ReturnType<Expo['setNotificationChannelAsync']>>> {\n return (await this.native?.setNotificationChannelAsync(...channel)) ?? null;\n }\n\n async deleteChannel(identifier: string): Promise<void> {\n await this.native?.deleteNotificationChannelAsync(identifier);\n }\n\n async channelGroups(): Promise<Awaited<ReturnType<Expo['getNotificationChannelGroupsAsync']>>> {\n return (await this.native?.getNotificationChannelGroupsAsync()) ?? [];\n }\n\n async channelGroup(\n identifier: string,\n ): Promise<Awaited<ReturnType<Expo['getNotificationChannelGroupAsync']>>> {\n return (await this.native?.getNotificationChannelGroupAsync(identifier)) ?? null;\n }\n\n async setChannelGroup(\n ...group: Args<'setNotificationChannelGroupAsync'>\n ): Promise<Awaited<ReturnType<Expo['setNotificationChannelGroupAsync']>>> {\n return (await this.native?.setNotificationChannelGroupAsync(...group)) ?? null;\n }\n\n async deleteChannelGroup(identifier: string): Promise<void> {\n await this.native?.deleteNotificationChannelGroupAsync(identifier);\n }\n\n /** The categories set: each a set of action buttons a notification opts into. */\n async categories(): Promise<Awaited<ReturnType<Expo['getNotificationCategoriesAsync']>>> {\n return (await this.native?.getNotificationCategoriesAsync()) ?? [];\n }\n\n /**\n * Action buttons for notifications whose content names this category. A tap on one arrives as\n * a response with the button's identifier, and the typed text as `userText` for a text input.\n */\n async setCategory(\n ...category: Args<'setNotificationCategoryAsync'>\n ): Promise<Awaited<ReturnType<Expo['setNotificationCategoryAsync']>> | null> {\n return (await this.native?.setNotificationCategoryAsync(...category)) ?? null;\n }\n\n async deleteCategory(identifier: string): Promise<boolean> {\n return (await this.native?.deleteNotificationCategoryAsync(identifier)) ?? false;\n }\n\n /**\n * What a notification that arrives while the app is in front does: shown, silent, with a sound\n * or a badge. Without a handler, it is not shown. Null removes the handler.\n */\n setHandler(...handler: Args<'setNotificationHandler'>): void {\n this.native?.setNotificationHandler(...handler);\n }\n\n /**\n * The token to hand a backend so it can send a push through Expo's service.\n *\n * Asks for the notification permission first - a token nobody can be shown anything with is not\n * worth having. `projectId` defaults to the app's own EAS project; pass it explicitly for a bare\n * workflow app, one still without EAS Build configured, or a token fetched outside\n * `expo-notifications`' default resolution. Null without the permission or without the module.\n */\n async getExpoPushToken(projectId?: string): Promise<string | null> {\n if (!this.native || !(await this.permission.ensure())) return null;\n const token = await this.native.getExpoPushTokenAsync(projectId ? { projectId } : undefined);\n return token.data;\n }\n\n /**\n * The raw APNs (iOS) or FCM (Android) token, for a backend that talks to those services\n * directly rather than through Expo's push service. Same permission and module rules as\n * {@link getExpoPushToken}.\n */\n async getDevicePushToken(): Promise<DevicePushToken | null> {\n if (!this.native || !(await this.permission.ensure())) return null;\n return this.native.getDevicePushTokenAsync();\n }\n\n /**\n * The device push token, each time the platform rolls it while the app is running.\n *\n * A rolled token is not the one a backend has on file, and sending to the old one fails\n * silently - this is what tells an app to register the new one. Unset until a roll happens;\n * most of an app's life this stays null.\n */\n readonly devicePushToken: Signal<DevicePushToken | null> = this.rolled.asReadonly();\n\n /** Stops the device receiving pushes until a token is asked for again. */\n async unregister(): Promise<void> {\n await this.native?.unregisterForNotificationsAsync();\n }\n\n /** Whether Expo re-registers the device token with its push service by itself. */\n async setAutoServerRegistration(enabled: boolean): Promise<void> {\n await this.native?.setAutoServerRegistrationEnabledAsync(enabled);\n }\n\n /** Android: receive pushes sent to an FCM topic. */\n async subscribeToTopic(topic: string): Promise<void> {\n await this.native?.subscribeToTopicAsync(topic);\n }\n\n async unsubscribeFromTopic(topic: string): Promise<void> {\n await this.native?.unsubscribeFromTopicAsync(topic);\n }\n\n /**\n * Runs a task defined with `expo-task-manager` when a notification arrives in the background.\n * The task itself is defined at the top level of `main.ts`, since it runs without the app.\n */\n async registerTask(name: string): Promise<void> {\n await this.native?.registerTaskAsync(name);\n }\n\n async unregisterTask(name: string): Promise<void> {\n await this.native?.unregisterTaskAsync(name);\n }\n}\n"]}
|
package/dist/permissions.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface PermissionApi {
|
|
|
27
27
|
get(): Promise<PermissionResponse>;
|
|
28
28
|
request(): Promise<PermissionResponse>;
|
|
29
29
|
}
|
|
30
|
+
/** What a permission is without its module: refused, and never to be asked about again. */
|
|
31
|
+
export declare const UNAVAILABLE: PermissionResponse;
|
|
30
32
|
export declare class Permission {
|
|
31
33
|
private readonly api;
|
|
32
34
|
private readonly last;
|
package/dist/permissions.js
CHANGED
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
import { computed, signal } from '@angular/core';
|
|
18
|
+
/** What a permission is without its module: refused, and never to be asked about again. */
|
|
19
|
+
export const UNAVAILABLE = {
|
|
20
|
+
status: 'denied',
|
|
21
|
+
granted: false,
|
|
22
|
+
canAskAgain: false,
|
|
23
|
+
};
|
|
18
24
|
export class Permission {
|
|
19
25
|
api;
|
|
20
26
|
last = signal(null, /* @ts-ignore */
|
package/dist/permissions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAgB9D,MAAM,OAAO,UAAU;IACJ,GAAG,CAAgB;IACnB,IAAI,GAAG,MAAM,CAA4B,IAAI;6EAAC,CAAC;IAEhE,YAAY,GAAkB;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,iGAAiG;IACxF,MAAM,GAAqD,QAAQ,CAC1E,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,IAAI,SAAS;+EACvC,CAAC;IAEO,OAAO,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,IAAI,KAAK;gFAAC,CAAC;IAElF,qFAAqF;IAC5E,OAAO,GAAoB,QAAQ,CAAC,GAAG,EAAE;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;IACzE,CAAC;gFAAC,CAAC;IAEH,qFAAqF;IACrF,MAAM,CAAC,EAAE,CACP,GAAsC,EACtC,OAA0C;QAE1C,OAAO,IAAI,UAAU,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM;QACV,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QAClC,0FAA0F;QAC1F,kFAAkF;QAClF,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QAClD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEO,MAAM,CAAC,QAA4B;QACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;CACF","sourcesContent":["/**\n * A permission, as signals rather than a hook.\n *\n * Every Expo module that needs one exposes the same shape: `getXPermissionsAsync`,\n * `requestXPermissionsAsync`, and a `useXPermissions()` hook that is those two plus React state.\n * So this is not a facade per module - it is one class that takes the pair, and the module stays\n * the app's own dependency:\n *\n * ```ts\n * import * as Camera from 'expo-camera';\n * import { Permission } from '@ng-native/expo';\n *\n * readonly camera = Permission.of(Camera.Camera.getCameraPermissionsAsync,\n * Camera.Camera.requestCameraPermissionsAsync);\n * ```\n */\nimport { computed, signal, type Signal } from '@angular/core';\n\n/** What every Expo permission call answers with. */\nexport interface PermissionResponse {\n readonly status: 'granted' | 'denied' | 'undetermined';\n readonly granted: boolean;\n /** False once the platform will no longer show a dialog: the app must send them to Settings. */\n readonly canAskAgain: boolean;\n}\n\n/** The pair of functions a module exposes, which its hook is built out of. */\nexport interface PermissionApi {\n get(): Promise<PermissionResponse>;\n request(): Promise<PermissionResponse>;\n}\n\nexport class Permission {\n private readonly api: PermissionApi;\n private readonly last = signal<PermissionResponse | null>(null);\n\n constructor(api: PermissionApi) {\n this.api = api;\n }\n\n /** `unknown` until something has asked the platform, which is not the same as `undetermined`. */\n readonly status: Signal<PermissionResponse['status'] | 'unknown'> = computed(\n () => this.last()?.status ?? 'unknown',\n );\n\n readonly granted: Signal<boolean> = computed(() => this.last()?.granted ?? false);\n\n /** Refused, and the platform will not show a dialog again. Send them to Settings. */\n readonly blocked: Signal<boolean> = computed(() => {\n const response = this.last();\n return response !== null && !response.granted && !response.canAskAgain;\n });\n\n /** From a module's own two functions, which is how every one of them spells this. */\n static of(\n get: () => Promise<PermissionResponse>,\n request: () => Promise<PermissionResponse>,\n ): Permission {\n return new Permission({ get, request });\n }\n\n /** Ask the platform what it currently thinks, without showing anything to the user. */\n async check(): Promise<boolean> {\n return this.record(await this.api.get());\n }\n\n /** Show the dialog. Resolves to what the user said. */\n async request(): Promise<boolean> {\n return this.record(await this.api.request());\n }\n\n /**\n * Have the permission if it can be had, asking only if asking would do something.\n *\n * The method most call sites want. A permission already granted needs no dialog, and one the\n * platform has stopped asking about would return the same no while looking like the user had\n * been consulted.\n */\n async ensure(): Promise<boolean> {\n if (this.last() === null) await this.check();\n\n const current = this.last();\n if (current?.granted) return true;\n // Refused for good. Requesting again resolves to the same no without the platform showing\n // anything, which reads at the call site as though the user had been asked twice.\n if (current && !current.canAskAgain) return false;\n return this.request();\n }\n\n private record(response: PermissionResponse): boolean {\n this.last.set(response);\n return response.granted;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAgB9D,2FAA2F;AAC3F,MAAM,CAAC,MAAM,WAAW,GAAuB;IAC7C,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,KAAK;IACd,WAAW,EAAE,KAAK;CACnB,CAAC;AAEF,MAAM,OAAO,UAAU;IACJ,GAAG,CAAgB;IACnB,IAAI,GAAG,MAAM,CAA4B,IAAI;6EAAC,CAAC;IAEhE,YAAY,GAAkB;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,iGAAiG;IACxF,MAAM,GAAqD,QAAQ,CAC1E,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,IAAI,SAAS;+EACvC,CAAC;IAEO,OAAO,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,IAAI,KAAK;gFAAC,CAAC;IAElF,qFAAqF;IAC5E,OAAO,GAAoB,QAAQ,CAAC,GAAG,EAAE;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC;IACzE,CAAC;gFAAC,CAAC;IAEH,qFAAqF;IACrF,MAAM,CAAC,EAAE,CACP,GAAsC,EACtC,OAA0C;QAE1C,OAAO,IAAI,UAAU,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,MAAM;QACV,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QAE7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,EAAE,OAAO;YAAE,OAAO,IAAI,CAAC;QAClC,0FAA0F;QAC1F,kFAAkF;QAClF,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QAClD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEO,MAAM,CAAC,QAA4B;QACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;CACF","sourcesContent":["/**\n * A permission, as signals rather than a hook.\n *\n * Every Expo module that needs one exposes the same shape: `getXPermissionsAsync`,\n * `requestXPermissionsAsync`, and a `useXPermissions()` hook that is those two plus React state.\n * So this is not a facade per module - it is one class that takes the pair, and the module stays\n * the app's own dependency:\n *\n * ```ts\n * import * as Camera from 'expo-camera';\n * import { Permission } from '@ng-native/expo';\n *\n * readonly camera = Permission.of(Camera.Camera.getCameraPermissionsAsync,\n * Camera.Camera.requestCameraPermissionsAsync);\n * ```\n */\nimport { computed, signal, type Signal } from '@angular/core';\n\n/** What every Expo permission call answers with. */\nexport interface PermissionResponse {\n readonly status: 'granted' | 'denied' | 'undetermined';\n readonly granted: boolean;\n /** False once the platform will no longer show a dialog: the app must send them to Settings. */\n readonly canAskAgain: boolean;\n}\n\n/** The pair of functions a module exposes, which its hook is built out of. */\nexport interface PermissionApi {\n get(): Promise<PermissionResponse>;\n request(): Promise<PermissionResponse>;\n}\n\n/** What a permission is without its module: refused, and never to be asked about again. */\nexport const UNAVAILABLE: PermissionResponse = {\n status: 'denied',\n granted: false,\n canAskAgain: false,\n};\n\nexport class Permission {\n private readonly api: PermissionApi;\n private readonly last = signal<PermissionResponse | null>(null);\n\n constructor(api: PermissionApi) {\n this.api = api;\n }\n\n /** `unknown` until something has asked the platform, which is not the same as `undetermined`. */\n readonly status: Signal<PermissionResponse['status'] | 'unknown'> = computed(\n () => this.last()?.status ?? 'unknown',\n );\n\n readonly granted: Signal<boolean> = computed(() => this.last()?.granted ?? false);\n\n /** Refused, and the platform will not show a dialog again. Send them to Settings. */\n readonly blocked: Signal<boolean> = computed(() => {\n const response = this.last();\n return response !== null && !response.granted && !response.canAskAgain;\n });\n\n /** From a module's own two functions, which is how every one of them spells this. */\n static of(\n get: () => Promise<PermissionResponse>,\n request: () => Promise<PermissionResponse>,\n ): Permission {\n return new Permission({ get, request });\n }\n\n /** Ask the platform what it currently thinks, without showing anything to the user. */\n async check(): Promise<boolean> {\n return this.record(await this.api.get());\n }\n\n /** Show the dialog. Resolves to what the user said. */\n async request(): Promise<boolean> {\n return this.record(await this.api.request());\n }\n\n /**\n * Have the permission if it can be had, asking only if asking would do something.\n *\n * The method most call sites want. A permission already granted needs no dialog, and one the\n * platform has stopped asking about would return the same no while looking like the user had\n * been consulted.\n */\n async ensure(): Promise<boolean> {\n if (this.last() === null) await this.check();\n\n const current = this.last();\n if (current?.granted) return true;\n // Refused for good. Requesting again resolves to the same no without the platform showing\n // anything, which reads at the call site as though the user had been asked twice.\n if (current && !current.canAskAgain) return false;\n return this.request();\n }\n\n private record(response: PermissionResponse): boolean {\n this.last.set(response);\n return response.granted;\n }\n}\n"]}
|
package/dist/player.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* What `videoPlayer` (`@ng-native/expo/video`) and `audioPlayer` (`@ng-native/expo/audio`) share:
|
|
3
|
+
* the `Player` each answers, its state, and the watchers that keep that state.
|
|
3
4
|
*
|
|
4
5
|
* ```ts
|
|
5
6
|
* protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });
|
|
@@ -16,6 +17,9 @@
|
|
|
16
17
|
*
|
|
17
18
|
* The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`
|
|
18
19
|
* and the rest already on it, and wrapping those would be a second place for each to be wrong.
|
|
20
|
+
*
|
|
21
|
+
* Each player has an entry point of its own because each needs its own native module: Metro fails
|
|
22
|
+
* a build on a `require` it cannot resolve, so one entry point for both made an app install both.
|
|
19
23
|
*/
|
|
20
24
|
import { type Signal } from '@angular/core';
|
|
21
25
|
/** The player's own status, which is the same word in both modules. */
|
|
@@ -72,21 +76,30 @@ export declare function watchPlayer(player: NativePlayer | null, options?: {
|
|
|
72
76
|
state: Signal<PlayerState>;
|
|
73
77
|
stop: () => void;
|
|
74
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Watch an `expo-audio` player, and stop watching when the caller is done with it.
|
|
81
|
+
*
|
|
82
|
+
* `AudioPlayer` reports its whole state on `playbackStatusUpdate`, at the interval it was created
|
|
83
|
+
* with, rather than splitting it across the several events `expo-video` uses - so unlike
|
|
84
|
+
* `watchPlayer`, there is one listener, not several, and nothing to reset on a source change: the
|
|
85
|
+
* next status update carries the new source's own state.
|
|
86
|
+
*/
|
|
87
|
+
export declare function watchAudioPlayer(player: NativePlayer | null): {
|
|
88
|
+
state: Signal<PlayerState>;
|
|
89
|
+
stop: () => void;
|
|
90
|
+
};
|
|
75
91
|
export interface Player<T> {
|
|
76
92
|
/** The module's own player, for `play()`, `seekBy()`, and to hand to its view. */
|
|
77
93
|
readonly native: T;
|
|
78
94
|
readonly state: Signal<PlayerState>;
|
|
79
95
|
}
|
|
80
|
-
/** A video player owned by the current component, released when it is destroyed. */
|
|
81
|
-
export declare function videoPlayer(source: import('expo-video').VideoSource, options?: {
|
|
82
|
-
timeUpdate?: number;
|
|
83
|
-
}): Player<import('expo-video').VideoPlayer>;
|
|
84
96
|
/**
|
|
85
|
-
*
|
|
97
|
+
* Tie a player's life to the component that made it.
|
|
86
98
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
99
|
+
* Called in an injection context, which is the same constraint the hooks have and for the same
|
|
100
|
+
* reason: something has to know when the thing goes away.
|
|
89
101
|
*/
|
|
90
|
-
export declare function
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
export declare function ownPlayer<T extends NativePlayer>(native: T, watch: () => {
|
|
103
|
+
state: Signal<PlayerState>;
|
|
104
|
+
stop: () => void;
|
|
105
|
+
}): Player<T>;
|
package/dist/player.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* What `videoPlayer` (`@ng-native/expo/video`) and `audioPlayer` (`@ng-native/expo/audio`) share:
|
|
3
|
+
* the `Player` each answers, its state, and the watchers that keep that state.
|
|
3
4
|
*
|
|
4
5
|
* ```ts
|
|
5
6
|
* protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });
|
|
@@ -16,9 +17,11 @@
|
|
|
16
17
|
*
|
|
17
18
|
* The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`
|
|
18
19
|
* and the rest already on it, and wrapping those would be a second place for each to be wrong.
|
|
20
|
+
*
|
|
21
|
+
* Each player has an entry point of its own because each needs its own native module: Metro fails
|
|
22
|
+
* a build on a `require` it cannot resolve, so one entry point for both made an app install both.
|
|
19
23
|
*/
|
|
20
24
|
import { DestroyRef, inject, signal } from '@angular/core';
|
|
21
|
-
import { expoModule } from "./native.js";
|
|
22
25
|
const INITIAL = {
|
|
23
26
|
playing: false,
|
|
24
27
|
status: 'idle',
|
|
@@ -80,7 +83,7 @@ function listen(player, event, handler) {
|
|
|
80
83
|
* `watchPlayer`, there is one listener, not several, and nothing to reset on a source change: the
|
|
81
84
|
* next status update carries the new source's own state.
|
|
82
85
|
*/
|
|
83
|
-
function watchAudioPlayer(player) {
|
|
86
|
+
export function watchAudioPlayer(player) {
|
|
84
87
|
const state = signal(INITIAL, /* @ts-ignore */
|
|
85
88
|
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
86
89
|
if (!player)
|
|
@@ -105,35 +108,13 @@ function watchAudioPlayer(player) {
|
|
|
105
108
|
},
|
|
106
109
|
};
|
|
107
110
|
}
|
|
108
|
-
/** A video player owned by the current component, released when it is destroyed. */
|
|
109
|
-
export function videoPlayer(source, options = {}) {
|
|
110
|
-
const expo = expoModule('expo-video', () => require('expo-video'), ['ios', 'android', 'web']);
|
|
111
|
-
if (!expo)
|
|
112
|
-
throw new Error('[angular-native] expo-video is not installed');
|
|
113
|
-
const native = expo.createVideoPlayer(source);
|
|
114
|
-
return own(native, () => watchPlayer(native, options));
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* An audio player owned by the current component, released when it is destroyed.
|
|
118
|
-
*
|
|
119
|
-
* `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is
|
|
120
|
-
* an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.
|
|
121
|
-
*/
|
|
122
|
-
export function audioPlayer(source, options = {}) {
|
|
123
|
-
const expo = expoModule('expo-audio', () => require('expo-audio'), ['ios', 'android', 'web']);
|
|
124
|
-
if (!expo)
|
|
125
|
-
throw new Error('[angular-native] expo-audio is not installed');
|
|
126
|
-
const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;
|
|
127
|
-
const native = expo.createAudioPlayer(source, { updateInterval });
|
|
128
|
-
return own(native, () => watchAudioPlayer(native));
|
|
129
|
-
}
|
|
130
111
|
/**
|
|
131
112
|
* Tie a player's life to the component that made it.
|
|
132
113
|
*
|
|
133
114
|
* Called in an injection context, which is the same constraint the hooks have and for the same
|
|
134
115
|
* reason: something has to know when the thing goes away.
|
|
135
116
|
*/
|
|
136
|
-
function
|
|
117
|
+
export function ownPlayer(native, watch) {
|
|
137
118
|
const { state, stop } = watch();
|
|
138
119
|
inject(DestroyRef).onDestroy(stop);
|
|
139
120
|
return { native, state };
|
package/dist/player.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Media players, bound to `expo-video` and `expo-audio`.\n *\n * ```ts\n * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // <expo-video [player]=\"player.native\" />\n * // {{ player.state().currentTime }}\n * ```\n *\n * `useVideoPlayer` and `useAudioPlayer` are the documented way in, and the player they hand back\n * is not the React part - `createVideoPlayer` gives the same object. What the hooks add is the\n * two things a component actually needs and cannot get otherwise: the player is *released* when\n * the component goes, and its changing state is *readable*, because the player's own properties\n * are plain mutable fields that no template can watch.\n *\n * The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`\n * and the rest already on it, and wrapping those would be a second place for each to be wrong.\n */\nimport { DestroyRef, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\n\n/** The player's own status, which is the same word in both modules. */\nexport type PlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\n/** The slice of a player this reads. Both modules' players satisfy it structurally. */\nexport interface NativePlayer {\n addListener(event: string, listener: (payload: never) => void): { remove(): void };\n release?(): void;\n /** Seconds between `timeUpdate` events. Zero, its default, means the event never fires. `expo-video` only. */\n timeUpdateEventInterval?: number;\n /** Read directly rather than from an event: `expo-audio`'s status carries no volume field. */\n readonly volume?: number;\n}\n\n/**\n * `expo-audio`'s one status event, `playbackStatusUpdate`. Unlike `expo-video`, which splits its\n * state across several named events, `AudioPlayer` reports everything - including errors - on a\n * single event, at the interval `updateInterval` was created with.\n */\nexport interface NativeAudioStatus {\n readonly playing: boolean;\n readonly mute: boolean;\n readonly duration: number;\n readonly currentTime: number;\n readonly isLoaded: boolean;\n readonly didJustFinish: boolean;\n readonly error: string | null;\n}\n\nexport interface PlayerState {\n readonly playing: boolean;\n readonly status: PlayerStatus;\n /** Seconds. Only moves while `timeUpdateEventInterval` is non-zero. */\n readonly currentTime: number;\n readonly duration: number;\n readonly muted: boolean;\n readonly volume: number;\n /** Set once playback has run to the end, and cleared when it starts again. */\n readonly ended: boolean;\n}\n\nconst INITIAL: PlayerState = {\n playing: false,\n status: 'idle',\n currentTime: 0,\n duration: 0,\n muted: false,\n volume: 1,\n ended: false,\n};\n\n/**\n * Watch an `expo-video` player, and stop watching when the caller is done with it.\n *\n * `timeUpdate` defaults to off, so `currentTime` would never move. A caller that wants a progress\n * bar asks for one by passing an interval, and one that does not pays nothing: the event is the\n * most frequent thing the player emits.\n *\n * `expo-audio`'s player reports its state differently - one event, not several - so it is watched\n * by `watchAudioPlayer` below rather than this function.\n */\nexport function watchPlayer(\n player: NativePlayer | null,\n options: { timeUpdate?: number } = {},\n): { state: Signal<PlayerState>; stop: () => void } {\n const state = signal(INITIAL);\n if (!player) return { state: state.asReadonly(), stop: () => {} };\n\n const patch = (change: Partial<PlayerState>) => state.update((last) => ({ ...last, ...change }));\n const subscriptions = [\n listen(player, 'statusChange', ({ status }: { status: PlayerStatus }) => patch({ status })),\n listen(player, 'playingChange', ({ isPlaying }: { isPlaying: boolean }) =>\n patch({ playing: isPlaying, ...(isPlaying ? { ended: false } : {}) }),\n ),\n listen(player, 'mutedChange', ({ muted }: { muted: boolean }) => patch({ muted })),\n listen(player, 'volumeChange', ({ volume }: { volume: number }) => patch({ volume })),\n listen(player, 'playToEnd', () => patch({ ended: true, playing: false })),\n listen(player, 'timeUpdate', ({ currentTime }: { currentTime: number }) =>\n patch({ currentTime }),\n ),\n listen(player, 'sourceChange', () => patch({ ...INITIAL, status: 'loading' })),\n // The only event that says how long the video is: nothing else carries a duration.\n listen(player, 'sourceLoad', ({ duration }: { duration: number }) => patch({ duration })),\n ];\n\n if (options.timeUpdate !== undefined) player.timeUpdateEventInterval = options.timeUpdate;\n\n return {\n state: state.asReadonly(),\n stop: () => {\n for (const subscription of subscriptions) subscription();\n // Releasing is the half a hook does that nothing else will: a player left alive holds its\n // decoder, its audio session and, on iOS, the now-playing controls.\n player.release?.();\n },\n };\n}\n\nfunction listen<T>(player: NativePlayer, event: string, handler: (payload: T) => void): () => void {\n const subscription = player.addListener(event, handler as (payload: never) => void);\n return () => subscription.remove();\n}\n\n/**\n * Watch an `expo-audio` player, and stop watching when the caller is done with it.\n *\n * `AudioPlayer` reports its whole state on `playbackStatusUpdate`, at the interval it was created\n * with, rather than splitting it across the several events `expo-video` uses - so unlike\n * `watchPlayer`, there is one listener, not several, and nothing to reset on a source change: the\n * next status update carries the new source's own state.\n */\nfunction watchAudioPlayer(player: NativePlayer | null): {\n state: Signal<PlayerState>;\n stop: () => void;\n} {\n const state = signal(INITIAL);\n if (!player) return { state: state.asReadonly(), stop: () => {} };\n\n const subscription = listen(player, 'playbackStatusUpdate', (status: NativeAudioStatus) =>\n state.set({\n playing: status.playing,\n status: status.error !== null ? 'error' : status.isLoaded ? 'readyToPlay' : 'loading',\n currentTime: status.currentTime,\n duration: status.duration,\n muted: status.mute,\n // Not on the status event; the player's own property is the current value.\n volume: player.volume ?? INITIAL.volume,\n ended: status.didJustFinish,\n }),\n );\n\n return {\n state: state.asReadonly(),\n stop: () => {\n subscription();\n // Releasing is the half a hook does that nothing else will: a player left alive holds its\n // decoder, its audio session and, on iOS, the now-playing controls.\n player.release?.();\n },\n };\n}\n\nexport interface Player<T> {\n /** The module's own player, for `play()`, `seekBy()`, and to hand to its view. */\n readonly native: T;\n readonly state: Signal<PlayerState>;\n}\n\n/** A video player owned by the current component, released when it is destroyed. */\nexport function videoPlayer(\n source: import('expo-video').VideoSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-video').VideoPlayer> {\n const expo = expoModule(\n 'expo-video',\n () => require('expo-video') as typeof import('expo-video'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw new Error('[angular-native] expo-video is not installed');\n const native = expo.createVideoPlayer(source);\n return own(native, () => watchPlayer(native, options));\n}\n\n/**\n * An audio player owned by the current component, released when it is destroyed.\n *\n * `expo-audio` has no `timeUpdateEventInterval` property to set after the fact - the interval is\n * an option to `createAudioPlayer` itself, in milliseconds rather than `videoPlayer`'s seconds.\n */\nexport function audioPlayer(\n source: import('expo-audio').AudioSource,\n options: { timeUpdate?: number } = {},\n): Player<import('expo-audio').AudioPlayer> {\n const expo = expoModule(\n 'expo-audio',\n () => require('expo-audio') as typeof import('expo-audio'),\n ['ios', 'android', 'web'],\n );\n if (!expo) throw new Error('[angular-native] expo-audio is not installed');\n const updateInterval = options.timeUpdate === undefined ? undefined : options.timeUpdate * 1000;\n const native = expo.createAudioPlayer(source, { updateInterval });\n return own(native, () => watchAudioPlayer(native));\n}\n\n/**\n * Tie a player's life to the component that made it.\n *\n * Called in an injection context, which is the same constraint the hooks have and for the same\n * reason: something has to know when the thing goes away.\n */\nfunction own<T extends NativePlayer>(\n native: T,\n watch: () => { state: Signal<PlayerState>; stop: () => void },\n): Player<T> {\n const { state, stop } = watch();\n inject(DestroyRef).onDestroy(stop);\n return { native, state };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"player.js","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `videoPlayer` (`@ng-native/expo/video`) and `audioPlayer` (`@ng-native/expo/audio`) share:\n * the `Player` each answers, its state, and the watchers that keep that state.\n *\n * ```ts\n * protected readonly player = videoPlayer(this.source, { timeUpdate: 0.5 });\n *\n * // <expo-video [player]=\"player.native\" />\n * // {{ player.state().currentTime }}\n * ```\n *\n * `useVideoPlayer` and `useAudioPlayer` are the documented way in, and the player they hand back\n * is not the React part - `createVideoPlayer` gives the same object. What the hooks add is the\n * two things a component actually needs and cannot get otherwise: the player is *released* when\n * the component goes, and its changing state is *readable*, because the player's own properties\n * are plain mutable fields that no template can watch.\n *\n * The player itself is passed straight through: it is a shared object with `play()`, `seekBy()`\n * and the rest already on it, and wrapping those would be a second place for each to be wrong.\n *\n * Each player has an entry point of its own because each needs its own native module: Metro fails\n * a build on a `require` it cannot resolve, so one entry point for both made an app install both.\n */\nimport { DestroyRef, inject, signal, type Signal } from '@angular/core';\n\n/** The player's own status, which is the same word in both modules. */\nexport type PlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\n/** The slice of a player this reads. Both modules' players satisfy it structurally. */\nexport interface NativePlayer {\n addListener(event: string, listener: (payload: never) => void): { remove(): void };\n release?(): void;\n /** Seconds between `timeUpdate` events. Zero, its default, means the event never fires. `expo-video` only. */\n timeUpdateEventInterval?: number;\n /** Read directly rather than from an event: `expo-audio`'s status carries no volume field. */\n readonly volume?: number;\n}\n\n/**\n * `expo-audio`'s one status event, `playbackStatusUpdate`. Unlike `expo-video`, which splits its\n * state across several named events, `AudioPlayer` reports everything - including errors - on a\n * single event, at the interval `updateInterval` was created with.\n */\nexport interface NativeAudioStatus {\n readonly playing: boolean;\n readonly mute: boolean;\n readonly duration: number;\n readonly currentTime: number;\n readonly isLoaded: boolean;\n readonly didJustFinish: boolean;\n readonly error: string | null;\n}\n\nexport interface PlayerState {\n readonly playing: boolean;\n readonly status: PlayerStatus;\n /** Seconds. Only moves while `timeUpdateEventInterval` is non-zero. */\n readonly currentTime: number;\n readonly duration: number;\n readonly muted: boolean;\n readonly volume: number;\n /** Set once playback has run to the end, and cleared when it starts again. */\n readonly ended: boolean;\n}\n\nconst INITIAL: PlayerState = {\n playing: false,\n status: 'idle',\n currentTime: 0,\n duration: 0,\n muted: false,\n volume: 1,\n ended: false,\n};\n\n/**\n * Watch an `expo-video` player, and stop watching when the caller is done with it.\n *\n * `timeUpdate` defaults to off, so `currentTime` would never move. A caller that wants a progress\n * bar asks for one by passing an interval, and one that does not pays nothing: the event is the\n * most frequent thing the player emits.\n *\n * `expo-audio`'s player reports its state differently - one event, not several - so it is watched\n * by `watchAudioPlayer` below rather than this function.\n */\nexport function watchPlayer(\n player: NativePlayer | null,\n options: { timeUpdate?: number } = {},\n): { state: Signal<PlayerState>; stop: () => void } {\n const state = signal(INITIAL);\n if (!player) return { state: state.asReadonly(), stop: () => {} };\n\n const patch = (change: Partial<PlayerState>) => state.update((last) => ({ ...last, ...change }));\n const subscriptions = [\n listen(player, 'statusChange', ({ status }: { status: PlayerStatus }) => patch({ status })),\n listen(player, 'playingChange', ({ isPlaying }: { isPlaying: boolean }) =>\n patch({ playing: isPlaying, ...(isPlaying ? { ended: false } : {}) }),\n ),\n listen(player, 'mutedChange', ({ muted }: { muted: boolean }) => patch({ muted })),\n listen(player, 'volumeChange', ({ volume }: { volume: number }) => patch({ volume })),\n listen(player, 'playToEnd', () => patch({ ended: true, playing: false })),\n listen(player, 'timeUpdate', ({ currentTime }: { currentTime: number }) =>\n patch({ currentTime }),\n ),\n listen(player, 'sourceChange', () => patch({ ...INITIAL, status: 'loading' })),\n // The only event that says how long the video is: nothing else carries a duration.\n listen(player, 'sourceLoad', ({ duration }: { duration: number }) => patch({ duration })),\n ];\n\n if (options.timeUpdate !== undefined) player.timeUpdateEventInterval = options.timeUpdate;\n\n return {\n state: state.asReadonly(),\n stop: () => {\n for (const subscription of subscriptions) subscription();\n // Releasing is the half a hook does that nothing else will: a player left alive holds its\n // decoder, its audio session and, on iOS, the now-playing controls.\n player.release?.();\n },\n };\n}\n\nfunction listen<T>(player: NativePlayer, event: string, handler: (payload: T) => void): () => void {\n const subscription = player.addListener(event, handler as (payload: never) => void);\n return () => subscription.remove();\n}\n\n/**\n * Watch an `expo-audio` player, and stop watching when the caller is done with it.\n *\n * `AudioPlayer` reports its whole state on `playbackStatusUpdate`, at the interval it was created\n * with, rather than splitting it across the several events `expo-video` uses - so unlike\n * `watchPlayer`, there is one listener, not several, and nothing to reset on a source change: the\n * next status update carries the new source's own state.\n */\nexport function watchAudioPlayer(player: NativePlayer | null): {\n state: Signal<PlayerState>;\n stop: () => void;\n} {\n const state = signal(INITIAL);\n if (!player) return { state: state.asReadonly(), stop: () => {} };\n\n const subscription = listen(player, 'playbackStatusUpdate', (status: NativeAudioStatus) =>\n state.set({\n playing: status.playing,\n status: status.error !== null ? 'error' : status.isLoaded ? 'readyToPlay' : 'loading',\n currentTime: status.currentTime,\n duration: status.duration,\n muted: status.mute,\n // Not on the status event; the player's own property is the current value.\n volume: player.volume ?? INITIAL.volume,\n ended: status.didJustFinish,\n }),\n );\n\n return {\n state: state.asReadonly(),\n stop: () => {\n subscription();\n // Releasing is the half a hook does that nothing else will: a player left alive holds its\n // decoder, its audio session and, on iOS, the now-playing controls.\n player.release?.();\n },\n };\n}\n\nexport interface Player<T> {\n /** The module's own player, for `play()`, `seekBy()`, and to hand to its view. */\n readonly native: T;\n readonly state: Signal<PlayerState>;\n}\n\n/**\n * Tie a player's life to the component that made it.\n *\n * Called in an injection context, which is the same constraint the hooks have and for the same\n * reason: something has to know when the thing goes away.\n */\nexport function ownPlayer<T extends NativePlayer>(\n native: T,\n watch: () => { state: Signal<PlayerState>; stop: () => void },\n): Player<T> {\n const { state, stop } = watch();\n inject(DestroyRef).onDestroy(stop);\n return { native, state };\n}\n"]}
|
|
@@ -20,6 +20,11 @@ export interface ExpoViewOptions {
|
|
|
20
20
|
* source to an array and a `contentFit` to a string native understands.
|
|
21
21
|
*/
|
|
22
22
|
readonly defaultProps?: Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* The prop the view reads its text from, where it takes no child views, as `@expo/ui`'s `Text`
|
|
25
|
+
* reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.
|
|
26
|
+
*/
|
|
27
|
+
readonly textContent?: string;
|
|
23
28
|
}
|
|
24
29
|
/**
|
|
25
30
|
* Teach the engine an element name for an Expo module's view, e.g.
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* registers are ordinary elements in a template.
|
|
16
16
|
*/
|
|
17
17
|
import { registerViewName } from '@ng-native/fabric';
|
|
18
|
+
import { optional } from "./native.js";
|
|
18
19
|
/**
|
|
19
20
|
* The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes
|
|
20
21
|
* it in `expo-modules-core`.
|
|
@@ -41,7 +42,10 @@ export function expoViewName(moduleName, viewName) {
|
|
|
41
42
|
* module installed so the native side registers the component; this is the JavaScript half only.
|
|
42
43
|
*/
|
|
43
44
|
export function registerExpoView(elementName, moduleName, options) {
|
|
44
|
-
registerViewName(elementName, expoViewName(moduleName, options?.viewName), options?.defaultProps
|
|
45
|
+
registerViewName(elementName, expoViewName(moduleName, options?.viewName), options?.defaultProps, {
|
|
46
|
+
textContent: options?.textContent,
|
|
47
|
+
});
|
|
48
|
+
optional(() => require('expo').requireNativeView(moduleName, options?.viewName));
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* The views worth knowing the names of, so an app does not have to look each one up.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-expo-view.js","sourceRoot":"","sources":["../src/register-expo-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"register-expo-view.js","sourceRoot":"","sources":["../src/register-expo-view.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Expo's native views, driven directly.\n *\n * An Expo module that renders something registers a Fabric component whose name it derives\n * rather than declares, and `requireNativeViewManager` is the only place that derivation is\n * written down. Everything else that function does is React: it caches a host component, builds\n * a view config for React's renderer, and wraps the result in a class whose whole job is to hold\n * a ref. None of that is reachable without React, and none of it is needed - the engine writes\n * props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,\n * so there is no codegen'd prop list to satisfy.\n *\n * What is left is the name. That is what this package is.\n *\n * No decorators and no Angular here on purpose: this is a registration, and the components it\n * registers are ordinary elements in a template.\n */\nimport { registerViewName } from '@ng-native/fabric';\nimport { optional } from './native.ts';\n\n/**\n * What Expo installs on the global when the app starts. Only the app identifier matters here;\n * `getViewConfig` is for React's renderer, which never runs.\n */\ninterface ExpoGlobal {\n readonly __expo_app_identifier__?: string;\n}\n\n/**\n * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes\n * it in `expo-modules-core`.\n *\n * The app identifier is the part that cannot be hardcoded. Expo Go runs many projects in one\n * binary, so it namespaces every view name with a per-app suffix; a standalone build has no\n * identifier and no suffix. Get it wrong and the view commits as `UnimplementedNativeView`\n * with no error, which is the same failure mode as a misspelled name.\n *\n * A module with more than one view names the extra ones (`viewName`); the default view is the\n * module itself.\n */\nexport function expoViewName(moduleName: string, viewName?: string): string {\n const identifier = (globalThis as { expo?: ExpoGlobal }).expo?.__expo_app_identifier__ ?? '';\n const suffix = identifier ? `_${identifier}` : '';\n const module = viewName ? `${moduleName}_${viewName}` : moduleName;\n return `ViewManagerAdapter_${module}${suffix}`;\n}\n\nexport interface ExpoViewOptions {\n /** A named view on a module that has several. Omit for the module's default view. */\n readonly viewName?: string;\n /**\n * Props the module's React component would have applied before native saw them. Expo's\n * wrappers do less of this than React Native's, but they do some: `expo-image` resolves a\n * source to an array and a `contentFit` to a string native understands.\n */\n readonly defaultProps?: Record<string, unknown>;\n /**\n * The prop the view reads its text from, where it takes no child views, as `@expo/ui`'s `Text`\n * reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.\n */\n readonly textContent?: string;\n}\n\n/**\n * Teach the engine an element name for an Expo module's view, e.g.\n * `registerExpoView('expo-image', 'ExpoImage')` makes `<expo-image>` commit as one.\n *\n * Call it at startup, before the first commit that uses the element. The app must have the\n * module installed so the native side registers the component; this is the JavaScript half only.\n */\nexport function registerExpoView(\n elementName: string,\n moduleName: string,\n options?: ExpoViewOptions,\n): void {\n registerViewName(\n elementName,\n expoViewName(moduleName, options?.viewName),\n options?.defaultProps,\n {\n textContent: options?.textContent,\n },\n );\n optional(() =>\n (\n require('expo') as { requireNativeView(module: string, view?: string): unknown }\n ).requireNativeView(moduleName, options?.viewName),\n );\n}\n\n/**\n * The views worth knowing the names of, so an app does not have to look each one up.\n *\n * Deliberately names and defaults, not components. An Angular component per Expo view, prop for\n * prop, is a maintenance burden with no end and a second place for every prop to be wrong; the\n * element plus the module's own documentation covers the common case, and an app that wants a\n * nicer surface writes the one component it actually needs.\n *\n * A module still has to be installed for its element to commit as anything: this is the\n * JavaScript half. An element whose native side is missing commits as `UnimplementedNativeView`.\n */\nexport const EXPO_VIEWS: Readonly<Record<string, [string, ExpoViewOptions?]>> = {\n /** `expo-image`. Takes `source` as a list, so a single `{uri}` is wrapped by the caller. */\n 'expo-image': ['ExpoImage', { defaultProps: { contentFit: 'cover' } }],\n /** `expo-blur`. The one thing CSS cannot express: a blur of what is *behind* a view. */\n 'expo-blur': ['ExpoBlurView', { defaultProps: { intensity: 50, tint: 'default' } }],\n /** `expo-video`. The player itself is a shared object the module hands out; this is its view. */\n 'expo-video': ['ExpoVideo', { viewName: 'VideoView' }],\n /**\n * `expo-camera`. Its module's first view, so its default: `ExpoCamera_CameraView` exists only on\n * iOS, after the Swift class, and commits as nothing on Android. `Camera` takes its pictures.\n */\n 'expo-camera': ['ExpoCamera'],\n /** `expo-apple-authentication`: Apple's Sign in with Apple button. iOS only. */\n 'apple-sign-in-button': ['ExpoAppleAuthentication'],\n /** `expo-symbols`: SF Symbols, iOS only, and the reason `icons` is not needed for them. */\n 'expo-symbol': ['SymbolModule', { defaultProps: { type: 'monochrome' } }],\n /** `expo-gl`, whose context is reached through an event rather than a prop. */\n 'expo-gl': ['ExpoGL'],\n /** `expo-glass-effect`: the iOS 26 material. Renders as a plain view where it is unavailable. */\n 'expo-glass': ['ExpoGlassEffect', { viewName: 'GlassView' }],\n /** Glass views that merge into one another when they come within `spacing` points. */\n 'expo-glass-container': ['ExpoGlassEffect', { viewName: 'GlassContainer' }],\n /** `expo-mesh-gradient`: the one gradient CSS cannot express, because it has no CSS spelling. */\n 'expo-mesh-gradient': ['ExpoMeshGradient', { viewName: 'MeshGradientView' }],\n /** `expo-live-photo`, iOS only. */\n 'expo-live-photo': ['ExpoLivePhoto', { viewName: 'LivePhotoView' }],\n /**\n * `expo-maps`, Google's on Android and Apple's on iOS: two modules, each with one view, so each\n * its module's default. `ExpoMaps` itself is the permissions module and has no view.\n * `MapView` (`<expo-map>`) types both as one element.\n */\n 'expo-maps-google': ['ExpoGoogleMaps'],\n 'expo-maps-apple': ['ExpoAppleMaps'],\n};\n\n/**\n * Register several of the known views by element name.\n *\n * ```ts\n * registerExpoViews('expo-image', 'expo-blur');\n * ```\n *\n * Named one at a time rather than all at once on purpose: registering a view for a module the app\n * has not installed produces an element that commits as nothing, and finding out at startup which\n * modules are present is not something JavaScript can do without importing them all.\n */\nexport function registerExpoViews(...elements: readonly (keyof typeof EXPO_VIEWS)[]): void {\n for (const element of elements) {\n const known = EXPO_VIEWS[element];\n if (!known) throw new Error(`[angular-native] no Expo view is known as '${element}'`);\n registerExpoView(element, known[0], known[1]);\n }\n}\n"]}
|
package/dist/screen-capture.js
CHANGED
|
@@ -19,10 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { DestroyRef, InjectionToken, Service, inject, signal } from '@angular/core';
|
|
21
21
|
import { expoModule } from "./native.js";
|
|
22
|
-
import { Permission } from "./permissions.js";
|
|
22
|
+
import { Permission, UNAVAILABLE } from "./permissions.js";
|
|
23
23
|
import * as i0 from "@angular/core";
|
|
24
|
-
/** The permission answer without the module: nothing granted, and nothing to ask. */
|
|
25
|
-
const UNAVAILABLE = { status: 'denied', granted: false, canAskAgain: false };
|
|
26
24
|
export class ScreenCapture {
|
|
27
25
|
/** Overridden in a test to take screenshots nobody took. */
|
|
28
26
|
static SOURCE = new InjectionToken('angular-native.screenCaptureSource', {
|
|
@@ -35,7 +33,7 @@ export class ScreenCapture {
|
|
|
35
33
|
* The permission screenshot detection needs on Android 13 and earlier, where it reads the
|
|
36
34
|
* photo library. Later Android needs none, and iOS always answers granted.
|
|
37
35
|
*/
|
|
38
|
-
permission = Permission.of(() => this.native?.getPermissionsAsync() ?? Promise.resolve(UNAVAILABLE), () => this.native?.requestPermissionsAsync() ?? Promise.resolve(UNAVAILABLE));
|
|
36
|
+
permission = Permission.of(() => this.native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE), () => this.native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE));
|
|
39
37
|
/** How many screenshots the user has taken while the app was in front. */
|
|
40
38
|
screenshots = this.count.asReadonly();
|
|
41
39
|
constructor() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;;AAkB3D,MAAM,OAAO,aAAa;IACxB,4DAA4D;IAC5D,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,oCAAoC,EACpC;QACE,OAAO,EAAE,GAAG,EAAE,CACZ,UAAU,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAS,CAAC;KAClF,CACF,CAAC;IAEe,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,GAAG,MAAM,CAAC,CAAC;8EAAC,CAAC;IAEnC;;;OAGG;IACM,UAAU,GAAe,UAAU,CAAC,EAAE,CAC7C,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,mBAAmB,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAC1E,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,uBAAuB,EAAE,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAC/E,CAAC;IAEF,0EAA0E;IACjE,WAAW,GAAmB,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;IAE/D;QACE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,qBAAqB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,QAAQ;YAAE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,gDAAgD;IAChD,KAAK,CAAC,SAAS;QACb,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,IAAI,KAAK,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,GAAY;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,KAAK,CAAC,GAAY;QACtB,MAAM,IAAI,CAAC,MAAM,EAAE,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CAAC,aAAsB;QAC7C,MAAM,IAAI,CAAC,MAAM,EAAE,gCAAgC,CAAC,aAAa,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,oBAAoB;QACxB,MAAM,IAAI,CAAC,MAAM,EAAE,iCAAiC,EAAE,CAAC;IACzD,CAAC;uGA3DU,aAAa;wGAAb,aAAa;;2FAAb,aAAa;kBADzB,OAAO","sourcesContent":["/**\n * `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and\n * recordings, and knowing when one was taken.\n *\n * ```ts\n * private readonly capture = inject(ScreenCapture);\n *\n * constructor() {\n * void this.capture.prevent('card-details');\n * inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));\n * }\n * ```\n *\n * `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:\n * two screens that each prevent it do not undo each other. `screenshots` counts the screenshots\n * the user has taken, and the listener behind it lives as long as the app does and is removed\n * when the app is destroyed. Without the module installed, nothing is prevented and the count\n * stays at zero.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { expoModule } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-screen-capture');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeScreenCapture = Pick<\n Expo,\n | 'isAvailableAsync'\n | 'preventScreenCaptureAsync'\n | 'allowScreenCaptureAsync'\n | 'enableAppSwitcherProtectionAsync'\n | 'disableAppSwitcherProtectionAsync'\n | 'addScreenshotListener'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n>;\n\n@Service()\nexport class ScreenCapture {\n /** Overridden in a test to take screenshots nobody took. */\n static readonly SOURCE = new InjectionToken<NativeScreenCapture | null>(\n 'angular-native.screenCaptureSource',\n {\n factory: () =>\n expoModule('expo-screen-capture', () => require('expo-screen-capture') as Expo),\n },\n );\n\n private readonly native = inject(ScreenCapture.SOURCE);\n private readonly count = signal(0);\n\n /**\n * The permission screenshot detection needs on Android 13 and earlier, where it reads the\n * photo library. Later Android needs none, and iOS always answers granted.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** How many screenshots the user has taken while the app was in front. */\n readonly screenshots: Signal<number> = this.count.asReadonly();\n\n constructor() {\n const listener = this.native?.addScreenshotListener(() => this.count.update((n) => n + 1));\n if (listener) inject(DestroyRef).onDestroy(() => listener.remove());\n }\n\n /** Whether the platform can prevent capture. */\n async available(): Promise<boolean> {\n return (await this.native?.isAvailableAsync()) ?? false;\n }\n\n /**\n * Keeps the app out of screenshots and screen recordings until `allow()` is called with the same\n * key, or the app restarts. Omitting the key uses one shared default key.\n */\n async prevent(key?: string): Promise<void> {\n await this.native?.preventScreenCaptureAsync(key);\n }\n\n /** Releases the key; capture is allowed again once no key is held. */\n async allow(key?: string): Promise<void> {\n await this.native?.allowScreenCaptureAsync(key);\n }\n\n /**\n * iOS: blurs the app while it is not in focus - in the app switcher, in the background, and\n * during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides\n * it in the app switcher while capture is prevented.\n */\n async protectAppSwitcher(blurIntensity?: number): Promise<void> {\n await this.native?.enableAppSwitcherProtectionAsync(blurIntensity);\n }\n\n async unprotectAppSwitcher(): Promise<void> {\n await this.native?.disableAppSwitcherProtectionAsync();\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SecureStorage`, a `Store` over `expo-secure-store`: the keychain and the Android keystore.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* private readonly store = inject(SecureStorage);
|
|
6
|
+
* protected readonly token = this.store.signal<string | null>('token', null);
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* See `Store` in `@ng-native/expo/store` for what the signal does.
|
|
10
|
+
*/
|
|
11
|
+
import { InjectionToken } from '@angular/core';
|
|
12
|
+
import { Store } from './store.ts';
|
|
13
|
+
/** The keychain and the Android keystore. Small values, and worth the size limit. */
|
|
14
|
+
export declare const SecureStorage: InjectionToken<Store>;
|
|
15
|
+
export type SecureStorage = Store;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SecureStorage`, a `Store` over `expo-secure-store`: the keychain and the Android keystore.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* private readonly store = inject(SecureStorage);
|
|
6
|
+
* protected readonly token = this.store.signal<string | null>('token', null);
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* See `Store` in `@ng-native/expo/store` for what the signal does.
|
|
10
|
+
*/
|
|
11
|
+
import { InjectionToken } from '@angular/core';
|
|
12
|
+
import { expoModule } from "./native.js";
|
|
13
|
+
import { Store } from "./store.js";
|
|
14
|
+
const secure = () => {
|
|
15
|
+
const expo = expoModule('expo-secure-store', () => require('expo-secure-store'));
|
|
16
|
+
if (!expo)
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
get: (key) => expo.getItemAsync(key),
|
|
20
|
+
set: (key, value) => expo.setItemAsync(key, value),
|
|
21
|
+
remove: (key) => expo.deleteItemAsync(key),
|
|
22
|
+
// The keychain can answer without waiting, so a token never flashes its default.
|
|
23
|
+
getSync: (key) => expo.getItem(key),
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** The keychain and the Android keystore. Small values, and worth the size limit. */
|
|
27
|
+
export const SecureStorage = new InjectionToken('angular-native.secureStorage', {
|
|
28
|
+
factory: () => new Store(secure()),
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=secure-store.js.map
|