@ng-native/expo 0.12.0 → 0.14.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.
Files changed (88) hide show
  1. package/android/build.gradle +2 -0
  2. package/android/src/main/java/dev/ngnative/expo/SearchFieldModule.kt +111 -0
  3. package/dist/app-info.d.ts +18 -15
  4. package/dist/app-info.js +17 -5
  5. package/dist/app-info.js.map +1 -1
  6. package/dist/apple-sign-in.js +5 -4
  7. package/dist/apple-sign-in.js.map +1 -1
  8. package/dist/assets.js +2 -2
  9. package/dist/assets.js.map +1 -1
  10. package/dist/async-storage.js +4 -1
  11. package/dist/async-storage.js.map +1 -1
  12. package/dist/audio.js +2 -2
  13. package/dist/audio.js.map +1 -1
  14. package/dist/battery.js +4 -1
  15. package/dist/battery.js.map +1 -1
  16. package/dist/biometrics.js +5 -4
  17. package/dist/biometrics.js.map +1 -1
  18. package/dist/brightness.js +7 -2
  19. package/dist/brightness.js.map +1 -1
  20. package/dist/camera.js +4 -1
  21. package/dist/camera.js.map +1 -1
  22. package/dist/clipboard.js +5 -2
  23. package/dist/clipboard.js.map +1 -1
  24. package/dist/database.js +2 -2
  25. package/dist/database.js.map +1 -1
  26. package/dist/expo-ui-components.d.ts +101 -9
  27. package/dist/expo-ui-components.js +305 -43
  28. package/dist/expo-ui-components.js.map +1 -1
  29. package/dist/expo-ui.js +7 -1
  30. package/dist/expo-ui.js.map +1 -1
  31. package/dist/foldable.d.ts +2 -0
  32. package/dist/foldable.js +15 -5
  33. package/dist/foldable.js.map +1 -1
  34. package/dist/fonts.d.ts +2 -0
  35. package/dist/fonts.js +12 -14
  36. package/dist/fonts.js.map +1 -1
  37. package/dist/haptics.js +2 -2
  38. package/dist/haptics.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/language-model.js +7 -3
  43. package/dist/language-model.js.map +1 -1
  44. package/dist/native.d.ts +17 -2
  45. package/dist/native.js +61 -3
  46. package/dist/native.js.map +1 -1
  47. package/dist/network.js +4 -1
  48. package/dist/network.js.map +1 -1
  49. package/dist/notifications.js +35 -8
  50. package/dist/notifications.js.map +1 -1
  51. package/dist/observed.js +5 -1
  52. package/dist/observed.js.map +1 -1
  53. package/dist/orientation.js +4 -1
  54. package/dist/orientation.js.map +1 -1
  55. package/dist/register-expo-view.d.ts +19 -0
  56. package/dist/register-expo-view.js +1 -0
  57. package/dist/register-expo-view.js.map +1 -1
  58. package/dist/screen-capture.js +2 -2
  59. package/dist/screen-capture.js.map +1 -1
  60. package/dist/search-field.d.ts +83 -0
  61. package/dist/search-field.js +147 -0
  62. package/dist/search-field.js.map +1 -0
  63. package/dist/secure-store.js +4 -1
  64. package/dist/secure-store.js.map +1 -1
  65. package/dist/sensors.js +6 -3
  66. package/dist/sensors.js.map +1 -1
  67. package/dist/splash-screen.js +2 -2
  68. package/dist/splash-screen.js.map +1 -1
  69. package/dist/store.js +4 -0
  70. package/dist/store.js.map +1 -1
  71. package/dist/tracking.js +2 -2
  72. package/dist/tracking.js.map +1 -1
  73. package/dist/ui-font-names.d.ts +16 -0
  74. package/dist/ui-font-names.js +71 -0
  75. package/dist/ui-font-names.js.map +1 -0
  76. package/dist/updates.js +4 -1
  77. package/dist/updates.js.map +1 -1
  78. package/dist/video.js +2 -2
  79. package/dist/video.js.map +1 -1
  80. package/dist/watch.js +6 -5
  81. package/dist/watch.js.map +1 -1
  82. package/expo-module.config.json +5 -2
  83. package/ios/ExpoUIModifiersModule.swift +25 -0
  84. package/ios/InlineNavigationTitle.swift +12 -0
  85. package/ios/NgNativeExpo.podspec +3 -0
  86. package/ios/SearchField.swift +130 -0
  87. package/ios/SegmentedFont.swift +113 -0
  88. package/package.json +6 -2
package/dist/native.js CHANGED
@@ -131,8 +131,9 @@ function nativeHalfMissing(module, platform) {
131
131
  * The Expo module a service needs.
132
132
  *
133
133
  * Where the module exists for the platform the app is on, a module that is missing is a mistake
134
- * the developer can fix, so this throws a `MissingModuleError` that says how - rather than leave a
135
- * feature that silently does nothing. Anywhere it cannot exist - an iOS-only module on Android, the
134
+ * the developer can fix, so using what this answers throws a `MissingModuleError` that says how -
135
+ * rather than leave a feature that silently does nothing. Using it, not asking for it: see
136
+ * `missing`. Anywhere it cannot exist - an iOS-only module on Android, the
136
137
  * web, and Node, where a test runs with no platform at all - this answers null, and the service
137
138
  * goes inert, as code shared across platforms needs it to.
138
139
  *
@@ -153,6 +154,63 @@ export function expoModule(module, load, platforms = ['ios', 'android']) {
153
154
  }
154
155
  if (platform === null || !platforms.includes(platform))
155
156
  return null;
156
- throw new MissingModuleError(module, platform, failure);
157
+ return missing(new MissingModuleError(module, platform, failure));
158
+ }
159
+ /** What Angular asks of any injected value, and `await` of any value: never the module's. */
160
+ const ASKED_OF_ANY = new Set(['then', 'ngOnDestroy', 'name']);
161
+ /**
162
+ * A module that is not there, which throws `error` when it is used rather than when it is asked
163
+ * for. Thrown from a token's factory or a service's constructor, the error would be the one the
164
+ * first injection sees: Angular leaves the token marked as being made, and every injection after
165
+ * it fails with NG0200, "Circular dependency", which names nothing to install.
166
+ *
167
+ * Not thenable, and quiet to a symbol, so awaiting it or logging it does not throw, nor Angular
168
+ * asking an injected value whether it has an `ngOnDestroy` or is its own not-found marker, by
169
+ * `name`.
170
+ */
171
+ function missing(error) {
172
+ const fail = () => {
173
+ throw error;
174
+ };
175
+ // A function, so calling it, or a member read off it, is a use that says so too.
176
+ const standIn = new Proxy(function standIn() { }, {
177
+ get: (_, key) => (typeof key === 'symbol' || ASKED_OF_ANY.has(key) ? undefined : fail()),
178
+ set: fail,
179
+ has: fail,
180
+ apply: fail,
181
+ construct: fail,
182
+ ownKeys: fail,
183
+ deleteProperty: fail,
184
+ defineProperty: fail,
185
+ getOwnPropertyDescriptor: fail,
186
+ });
187
+ missingErrors.set(standIn, error);
188
+ return standIn;
189
+ }
190
+ /** The error each stand-in for a missing module throws when it is used. */
191
+ const missingErrors = new WeakMap();
192
+ /** Whether `value` is the stand-in for a module missing from the build. */
193
+ const isMissing = (value) => typeof value === 'function' && missingErrors.has(value);
194
+ /**
195
+ * The module, or null where it is missing from the build: for what a service does as it is made,
196
+ * a listener or a first read, which is skipped then rather than thrown, so the service is made and
197
+ * what the app calls on it is what says the module is missing. See `missing`. A factory that
198
+ * builds over the module hands the stand-in on where it is missing (`if (!present(expo)) return
199
+ * expo as never`), for the same reason.
200
+ */
201
+ export function present(value) {
202
+ return value == null || isMissing(value) ? null : value;
203
+ }
204
+ /**
205
+ * As `expoModule`, throwing at once where the module is missing: for a function an app calls
206
+ * itself, a player, a database or the splash screen, where no token is left marked and a failure
207
+ * after the call would be caught and lost.
208
+ */
209
+ export function expoModuleNow(module, load, platforms) {
210
+ const found = expoModule(module, load, platforms);
211
+ const error = found && missingErrors.get(found);
212
+ if (error)
213
+ throw error;
214
+ return found;
157
215
  }
158
216
  //# sourceMappingURL=native.js.map
@@ -1 +1 @@
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 */\n/**\n * Calls a listener the app gave. What it throws, or rejects with when it is asynchronous, goes to\n * `report`, so one failing listener does not stop what calls it.\n */\nexport function isolated(call: () => unknown, report: (failure: unknown) => void): void {\n try {\n const done = call();\n if (done instanceof Promise) done.catch(report);\n } catch (failure) {\n report(failure);\n }\n}\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, message?: string) {\n super(\n message ??\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/**\n * The `MissingModuleError` to throw where `expoModule` answered null: the module is installed or not, but there is\n * nothing to load it on. In Node, where a test runs, that is every native module, and \"install it\"\n * would be advice to do what is already done; `inTest` says what a test does instead. On a platform\n * the module does not support, it says that.\n */\nexport function unavailable(module: string, inTest: string): MissingModuleError {\n const platform = currentPlatform();\n const message =\n platform === null\n ? `[angular-native] ${module} has no native module to load in Node, where a test runs. ${inTest}`\n : `[angular-native] ${module} is not available on ${platform}.`;\n // The platform is only read for the default message, which this replaces.\n return new MissingModuleError(module, platform ?? 'web', undefined, message);\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"]}
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 */\n/**\n * Calls a listener the app gave. What it throws, or rejects with when it is asynchronous, goes to\n * `report`, so one failing listener does not stop what calls it.\n */\nexport function isolated(call: () => unknown, report: (failure: unknown) => void): void {\n try {\n const done = call();\n if (done instanceof Promise) done.catch(report);\n } catch (failure) {\n report(failure);\n }\n}\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, message?: string) {\n super(\n message ??\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/**\n * The `MissingModuleError` to throw where `expoModule` answered null: the module is installed or not, but there is\n * nothing to load it on. In Node, where a test runs, that is every native module, and \"install it\"\n * would be advice to do what is already done; `inTest` says what a test does instead. On a platform\n * the module does not support, it says that.\n */\nexport function unavailable(module: string, inTest: string): MissingModuleError {\n const platform = currentPlatform();\n const message =\n platform === null\n ? `[angular-native] ${module} has no native module to load in Node, where a test runs. ${inTest}`\n : `[angular-native] ${module} is not available on ${platform}.`;\n // The platform is only read for the default message, which this replaces.\n return new MissingModuleError(module, platform ?? 'web', undefined, message);\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 using what this answers throws a `MissingModuleError` that says how -\n * rather than leave a feature that silently does nothing. Using it, not asking for it: see\n * `missing`. 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 return missing(new MissingModuleError(module, platform, failure));\n}\n\n/** What Angular asks of any injected value, and `await` of any value: never the module's. */\nconst ASKED_OF_ANY = new Set(['then', 'ngOnDestroy', 'name']);\n\n/**\n * A module that is not there, which throws `error` when it is used rather than when it is asked\n * for. Thrown from a token's factory or a service's constructor, the error would be the one the\n * first injection sees: Angular leaves the token marked as being made, and every injection after\n * it fails with NG0200, \"Circular dependency\", which names nothing to install.\n *\n * Not thenable, and quiet to a symbol, so awaiting it or logging it does not throw, nor Angular\n * asking an injected value whether it has an `ngOnDestroy` or is its own not-found marker, by\n * `name`.\n */\nfunction missing<T>(error: MissingModuleError): T {\n const fail = (): never => {\n throw error;\n };\n // A function, so calling it, or a member read off it, is a use that says so too.\n const standIn = new Proxy(function standIn() {}, {\n get: (_, key) => (typeof key === 'symbol' || ASKED_OF_ANY.has(key) ? undefined : fail()),\n set: fail,\n has: fail,\n apply: fail,\n construct: fail,\n ownKeys: fail,\n deleteProperty: fail,\n defineProperty: fail,\n getOwnPropertyDescriptor: fail,\n });\n missingErrors.set(standIn, error);\n return standIn as T;\n}\n\n/** The error each stand-in for a missing module throws when it is used. */\nconst missingErrors = new WeakMap<object, MissingModuleError>();\n\n/** Whether `value` is the stand-in for a module missing from the build. */\nconst isMissing = (value: unknown): boolean =>\n typeof value === 'function' && missingErrors.has(value);\n\n/**\n * The module, or null where it is missing from the build: for what a service does as it is made,\n * a listener or a first read, which is skipped then rather than thrown, so the service is made and\n * what the app calls on it is what says the module is missing. See `missing`. A factory that\n * builds over the module hands the stand-in on where it is missing (`if (!present(expo)) return\n * expo as never`), for the same reason.\n */\nexport function present<T>(value: T | null | undefined): T | null {\n return value == null || isMissing(value) ? null : value;\n}\n\n/**\n * As `expoModule`, throwing at once where the module is missing: for a function an app calls\n * itself, a player, a database or the splash screen, where no token is left marked and a failure\n * after the call would be caught and lost.\n */\nexport function expoModuleNow<T>(\n module: string,\n load: () => T | null | undefined,\n platforms?: readonly ModulePlatform[],\n): T | null {\n const found = expoModule(module, load, platforms);\n const error = found && missingErrors.get(found as object);\n if (error) throw error;\n return found;\n}\n"]}
package/dist/network.js CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { InjectionToken, Service, computed, inject } from '@angular/core';
10
10
  import { observed, observedFrom } from "./observed.js";
11
- import { expoModule } from "./native.js";
11
+ import { expoModule, present } from "./native.js";
12
12
  import * as i0 from "@angular/core";
13
13
  export const OFFLINE = { connected: false, type: 'unknown', reachable: null };
14
14
  const TYPES = {
@@ -28,6 +28,9 @@ export class Network {
28
28
  const expo = expoModule('expo-network', () => require('expo-network'));
29
29
  if (!expo)
30
30
  return null;
31
+ // Missing from the build: the stand-in, to say so when it is used.
32
+ if (!present(expo))
33
+ return expo;
31
34
  const read = (state) => ({
32
35
  connected: state.isConnected ?? false,
33
36
  type: TYPES[state.type ?? 'UNKNOWN'] ?? 'unknown',
@@ -1 +1 @@
1
- {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAiB,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;;AAYzC,MAAM,CAAC,MAAM,OAAO,GAAkB,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAE7F,MAAM,KAAK,GAAmC;IAC5C,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,UAAU;IACpB,GAAG,EAAE,KAAK;IACV,KAAK,EAAE,OAAO;CACf,CAAC;AAGF,MAAM,OAAO,OAAO;IAClB,4DAA4D;IAC5D,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,8BAA8B,EAC9B;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,cAAc,EACd,GAAG,EAAE,CAAC,OAAO,CAAC,cAAc,CAAkC,CAC/D,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YAEvB,MAAM,IAAI,GAAG,CACX,KAAqF,EACtE,EAAE,CAAC,CAAC;gBACnB,SAAS,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK;gBACrC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,SAAS;gBACjD,0FAA0F;gBAC1F,oDAAoD;gBACpD,SAAS,EAAE,KAAK,CAAC,mBAAmB,IAAI,IAAI;aAC7C,CAAC,CAAC;YAEH,OAAO,YAAY,CACjB,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC,EACnD,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAC7E,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEO,MAAM,GAA0B,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAEnF,8FAA8F;IACrF,SAAS,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS;kFAAC,CAAC;IAE9E,8EAA8E;IACrE,IAAI,GAA2B,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI;6EAAC,CAAC;IAE3E;;;OAGG;IACM,SAAS,GAA2B,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS;kFAAC,CAAC;uGA1C1E,OAAO;wGAAP,OAAO;;2FAAP,OAAO;kBADnB,OAAO","sourcesContent":["/**\n * `Network`, bound to `expo-network`.\n *\n * Whether the app can reach anything, which is not the same as whether it has a connection. A\n * phone on a captive-portal wifi is connected and reachable by nothing; a phone in a lift has a\n * connection type and no signal. `reachable` is the one worth branching on, and it is the one the\n * platform is least sure about - it stays `null` until something has actually been reached.\n */\nimport { InjectionToken, Service, computed, inject, type Signal } from '@angular/core';\nimport { observed, observedFrom, type Observed } from './observed.ts';\nimport { expoModule } from './native.ts';\n\nexport type ConnectionType =\n 'wifi' | 'cellular' | 'ethernet' | 'bluetooth' | 'vpn' | 'other' | 'none' | 'unknown';\n\nexport interface NetworkStatus {\n readonly connected: boolean;\n readonly type: ConnectionType;\n /** Null while the platform has not established it, which is not the same as false. */\n readonly reachable: boolean | null;\n}\n\nexport const OFFLINE: NetworkStatus = { connected: false, type: 'unknown', reachable: null };\n\nconst TYPES: Record<string, ConnectionType> = {\n NONE: 'none',\n UNKNOWN: 'unknown',\n WIFI: 'wifi',\n CELLULAR: 'cellular',\n BLUETOOTH: 'bluetooth',\n ETHERNET: 'ethernet',\n VPN: 'vpn',\n OTHER: 'other',\n};\n\n@Service()\nexport class Network {\n /** Overridden in a test to go offline without a network. */\n static readonly SOURCE = new InjectionToken<Observed<NetworkStatus> | null>(\n 'angular-native.networkSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-network',\n () => require('expo-network') as typeof import('expo-network'),\n );\n if (!expo) return null;\n\n const read = (\n state: import('expo-network').NetworkState | import('expo-network').NetworkStateEvent,\n ): NetworkStatus => ({\n connected: state.isConnected ?? false,\n type: TYPES[state.type ?? 'UNKNOWN'] ?? 'unknown',\n // `undefined` means the platform has not established it; `null` is how that is said here,\n // so a consumer cannot confuse \"not yet\" with \"no\".\n reachable: state.isInternetReachable ?? null,\n });\n\n return observedFrom(\n async () => read(await expo.getNetworkStateAsync()),\n (listener) => expo.addNetworkStateListener((event) => listener(read(event))),\n );\n },\n },\n );\n\n readonly status: Signal<NetworkStatus> = observed(inject(Network.SOURCE), OFFLINE);\n\n /** Whether there is a connection of any kind. Say what you mean: this is not reachability. */\n readonly connected: Signal<boolean> = computed(() => this.status().connected);\n\n /** What the connection is over, for a screen that offers to wait for wifi. */\n readonly type: Signal<ConnectionType> = computed(() => this.status().type);\n\n /**\n * Whether anything has actually been reached. Null until the platform knows, so a screen that\n * shows an offline banner should treat null as \"not yet\" rather than as offline.\n */\n readonly reachable: Signal<boolean | null> = computed(() => this.status().reachable);\n}\n"]}
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAiB,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;;AAYlD,MAAM,CAAC,MAAM,OAAO,GAAkB,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAE7F,MAAM,KAAK,GAAmC;IAC5C,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,QAAQ,EAAE,UAAU;IACpB,GAAG,EAAE,KAAK;IACV,KAAK,EAAE,OAAO;CACf,CAAC;AAGF,MAAM,OAAO,OAAO;IAClB,4DAA4D;IAC5D,MAAM,CAAU,MAAM,GAAG,IAAI,cAAc,CACzC,8BAA8B,EAC9B;QACE,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,UAAU,CACrB,cAAc,EACd,GAAG,EAAE,CAAC,OAAO,CAAC,cAAc,CAAkC,CAC/D,CAAC;YACF,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,mEAAmE;YACnE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAa,CAAC;YAEzC,MAAM,IAAI,GAAG,CACX,KAAqF,EACtE,EAAE,CAAC,CAAC;gBACnB,SAAS,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK;gBACrC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,SAAS,CAAC,IAAI,SAAS;gBACjD,0FAA0F;gBAC1F,oDAAoD;gBACpD,SAAS,EAAE,KAAK,CAAC,mBAAmB,IAAI,IAAI;aAC7C,CAAC,CAAC;YAEH,OAAO,YAAY,CACjB,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC,EACnD,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAC7E,CAAC;QACJ,CAAC;KACF,CACF,CAAC;IAEO,MAAM,GAA0B,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IAEnF,8FAA8F;IACrF,SAAS,GAAoB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS;kFAAC,CAAC;IAE9E,8EAA8E;IACrE,IAAI,GAA2B,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI;6EAAC,CAAC;IAE3E;;;OAGG;IACM,SAAS,GAA2B,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS;kFAAC,CAAC;uGA5C1E,OAAO;wGAAP,OAAO;;2FAAP,OAAO;kBADnB,OAAO","sourcesContent":["/**\n * `Network`, bound to `expo-network`.\n *\n * Whether the app can reach anything, which is not the same as whether it has a connection. A\n * phone on a captive-portal wifi is connected and reachable by nothing; a phone in a lift has a\n * connection type and no signal. `reachable` is the one worth branching on, and it is the one the\n * platform is least sure about - it stays `null` until something has actually been reached.\n */\nimport { InjectionToken, Service, computed, inject, type Signal } from '@angular/core';\nimport { observed, observedFrom, type Observed } from './observed.ts';\nimport { expoModule, present } from './native.ts';\n\nexport type ConnectionType =\n 'wifi' | 'cellular' | 'ethernet' | 'bluetooth' | 'vpn' | 'other' | 'none' | 'unknown';\n\nexport interface NetworkStatus {\n readonly connected: boolean;\n readonly type: ConnectionType;\n /** Null while the platform has not established it, which is not the same as false. */\n readonly reachable: boolean | null;\n}\n\nexport const OFFLINE: NetworkStatus = { connected: false, type: 'unknown', reachable: null };\n\nconst TYPES: Record<string, ConnectionType> = {\n NONE: 'none',\n UNKNOWN: 'unknown',\n WIFI: 'wifi',\n CELLULAR: 'cellular',\n BLUETOOTH: 'bluetooth',\n ETHERNET: 'ethernet',\n VPN: 'vpn',\n OTHER: 'other',\n};\n\n@Service()\nexport class Network {\n /** Overridden in a test to go offline without a network. */\n static readonly SOURCE = new InjectionToken<Observed<NetworkStatus> | null>(\n 'angular-native.networkSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-network',\n () => require('expo-network') as typeof import('expo-network'),\n );\n if (!expo) return null;\n // Missing from the build: the stand-in, to say so when it is used.\n if (!present(expo)) return expo as never;\n\n const read = (\n state: import('expo-network').NetworkState | import('expo-network').NetworkStateEvent,\n ): NetworkStatus => ({\n connected: state.isConnected ?? false,\n type: TYPES[state.type ?? 'UNKNOWN'] ?? 'unknown',\n // `undefined` means the platform has not established it; `null` is how that is said here,\n // so a consumer cannot confuse \"not yet\" with \"no\".\n reachable: state.isInternetReachable ?? null,\n });\n\n return observedFrom(\n async () => read(await expo.getNetworkStateAsync()),\n (listener) => expo.addNetworkStateListener((event) => listener(read(event))),\n );\n },\n },\n );\n\n readonly status: Signal<NetworkStatus> = observed(inject(Network.SOURCE), OFFLINE);\n\n /** Whether there is a connection of any kind. Say what you mean: this is not reachability. */\n readonly connected: Signal<boolean> = computed(() => this.status().connected);\n\n /** What the connection is over, for a screen that offers to wait for wifi. */\n readonly type: Signal<ConnectionType> = computed(() => this.status().type);\n\n /**\n * Whether anything has actually been reached. Null until the platform knows, so a screen that\n * shows an offline banner should treat null as \"not yet\" rather than as offline.\n */\n readonly reachable: Signal<boolean | null> = computed(() => this.status().reachable);\n}\n"]}
@@ -35,7 +35,7 @@
35
35
  * with fails silently: the push simply never arrives.
36
36
  */
37
37
  import { DestroyRef, InjectionToken, Service, inject, signal } from '@angular/core';
38
- import { expoModule } from "./native.js";
38
+ import { currentPlatform, expoModule, optional, present } from "./native.js";
39
39
  import { Permission, UNAVAILABLE } from "./permissions.js";
40
40
  import * as i0 from "@angular/core";
41
41
  /**
@@ -55,7 +55,13 @@ export const TriggerType = {
55
55
  export class Notifications {
56
56
  /** Overridden in a test to deliver a notification nobody sent. */
57
57
  static SOURCE = new InjectionToken('angular-native.notificationsSource', {
58
- factory: () => expoModule('expo-notifications', () => require('expo-notifications')),
58
+ factory: () => {
59
+ if (refusedByExpoGo()) {
60
+ console.warn(REFUSED_BY_EXPO_GO);
61
+ return null;
62
+ }
63
+ return expoModule('expo-notifications', () => require('expo-notifications'));
64
+ },
59
65
  });
60
66
  native = inject(Notifications.SOURCE);
61
67
  received = signal(null, /* @ts-ignore */
@@ -75,24 +81,26 @@ export class Notifications {
75
81
  constructor() {
76
82
  const native = this.native;
77
83
  this.permission = Permission.of(() => native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE), () => native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE));
78
- if (!native)
84
+ // Its listeners only where the module is there; missing, what the app calls says so.
85
+ const live = present(native);
86
+ if (!live)
79
87
  return;
80
88
  const subscriptions = [
81
- native.addNotificationReceivedListener((notification) => this.received.set(notification)),
82
- native.addNotificationResponseReceivedListener((response) => this.responded.set(response)),
89
+ live.addNotificationReceivedListener((notification) => this.received.set(notification)),
90
+ live.addNotificationResponseReceivedListener((response) => this.responded.set(response)),
83
91
  // Android drops notifications when too many arrive at once while the app is not running;
84
92
  // this is the only word that any went missing.
85
- native.addNotificationsDroppedListener(() => this.drops.update((count) => count + 1)),
93
+ live.addNotificationsDroppedListener(() => this.drops.update((count) => count + 1)),
86
94
  // A device push token can be rolled by the platform while the app is running; the old one
87
95
  // then fails silently rather than delivering anything. This is the only way to find out.
88
- native.addPushTokenListener((token) => this.rolled.set(token)),
96
+ live.addPushTokenListener((token) => this.rolled.set(token)),
89
97
  ];
90
98
  inject(DestroyRef).onDestroy(() => subscriptions.forEach((one) => one.remove()));
91
99
  // The response that launched the app happened before anything was listening, so it has to be
92
100
  // asked for rather than waited for. Without this a cold start from a notification does
93
101
  // nothing, which is the single most common way this is got wrong. A platform that cannot say
94
102
  // is the same as a launch without a notification.
95
- void native
103
+ void live
96
104
  .getLastNotificationResponseAsync()
97
105
  .then((response) => {
98
106
  if (response && this.responded() === null)
@@ -291,4 +299,23 @@ export class Notifications {
291
299
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Notifications, decorators: [{
292
300
  type: Service
293
301
  }], ctorParameters: () => [] });
302
+ /**
303
+ * Whether this is Expo Go on Android, where `expo-notifications` cannot be imported.
304
+ *
305
+ * The package subscribes to push tokens as it is imported, and in Expo Go on Android, which has
306
+ * had no push since SDK 53, that subscription throws. So the import fails there, local
307
+ * notifications included, and no rebuild puts it right. Asked before the import and not caught
308
+ * after it: Metro reports a module that throws as it loads as a fatal error itself, so the red
309
+ * screen is up before anything here could catch it. With no module the service is what it is on
310
+ * the web, and the app gets past the first screen that injects it.
311
+ */
312
+ function refusedByExpoGo() {
313
+ if (currentPlatform() !== 'android')
314
+ return false;
315
+ const expo = optional(() => require('expo'));
316
+ return expo?.isRunningInExpoGo?.() === true;
317
+ }
318
+ const REFUSED_BY_EXPO_GO = '[angular-native] Notifications does nothing here: expo-notifications cannot be loaded in Expo ' +
319
+ 'Go on Android, which has had no push since SDK 53, so nothing is scheduled or received and ' +
320
+ 'the permission is never granted. Use a development build.';
294
321
  //# sourceMappingURL=notifications.js.map
@@ -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, 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"]}
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 { currentPlatform, expoModule, optional, present } 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: () => {\n if (refusedByExpoGo()) {\n console.warn(REFUSED_BY_EXPO_GO);\n return null;\n }\n return expoModule('expo-notifications', () => require('expo-notifications') as Expo);\n },\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 // Its listeners only where the module is there; missing, what the app calls says so.\n const live = present(native);\n if (!live) return;\n const subscriptions = [\n live.addNotificationReceivedListener((notification) => this.received.set(notification)),\n live.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 live.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 live.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 live\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\n/**\n * Whether this is Expo Go on Android, where `expo-notifications` cannot be imported.\n *\n * The package subscribes to push tokens as it is imported, and in Expo Go on Android, which has\n * had no push since SDK 53, that subscription throws. So the import fails there, local\n * notifications included, and no rebuild puts it right. Asked before the import and not caught\n * after it: Metro reports a module that throws as it loads as a fatal error itself, so the red\n * screen is up before anything here could catch it. With no module the service is what it is on\n * the web, and the app gets past the first screen that injects it.\n */\nfunction refusedByExpoGo(): boolean {\n if (currentPlatform() !== 'android') return false;\n const expo = optional(() => require('expo') as { isRunningInExpoGo?(): boolean });\n return expo?.isRunningInExpoGo?.() === true;\n}\n\nconst REFUSED_BY_EXPO_GO =\n '[angular-native] Notifications does nothing here: expo-notifications cannot be loaded in Expo ' +\n 'Go on Android, which has had no push since SDK 53, so nothing is scheduled or received and ' +\n 'the permission is never granted. Use a development build.';\n"]}
package/dist/observed.js CHANGED
@@ -8,7 +8,8 @@
8
8
  * So this is that hook, as a signal: a value that starts at a stated default, is fetched once,
9
9
  * and follows the listener from then on.
10
10
  */
11
- import { DestroyRef, inject, signal } from '@angular/core';
11
+ import { computed, DestroyRef, inject, signal } from '@angular/core';
12
+ import { present } from "./native.js";
12
13
  /**
13
14
  * A signal fed by an `Observed`, subscribed until the injector it was made in is destroyed.
14
15
  *
@@ -25,6 +26,9 @@ export function observed(source, initial) {
25
26
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
26
27
  if (!source)
27
28
  return value.asReadonly();
29
+ // A module missing from the build says so when the value is read, not as it is subscribed.
30
+ if (!present(source))
31
+ return computed(() => source.current);
28
32
  // Failures are swallowed rather than surfaced: a device with no battery API is a device where
29
33
  // the default is the honest answer, and nothing on screen should break over it. The first
30
34
  // answer was asked for before anything was heard, so it is older than anything the listener
@@ -1 +1 @@
1
- {"version":3,"file":"observed.js","sourceRoot":"","sources":["../src/observed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAQxE;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAI,MAA0B,EAAE,OAAU;IAChE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO;8EAAC,CAAC;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC;IAEvC,8FAA8F;IAC9F,0FAA0F;IAC1F,4FAA4F;IAC5F,wDAAwD;IACxD,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,CACxB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EACpC,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;IACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAC1B,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,KAAK,GAAG,IAAI,CAAC;QACb,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CACH,CAAC;IACF,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAyB,EACzB,SAA+D;IAE/D,OAAO;QACL,OAAO;QACP,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;YACtB,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACzC,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The shape nearly every device-state module has, and the one part of it that is React-only.\n *\n * `getNetworkStateAsync` + `addNetworkStateListener` + `useNetworkState`. `getBatteryLevelAsync` +\n * `addBatteryLevelListener` + `useBatteryLevel`. The two functions are plain; the hook is React\n * state around them, and a hook is the only surface some of these offer for *reading over time*.\n *\n * So this is that hook, as a signal: a value that starts at a stated default, is fetched once,\n * and follows the listener from then on.\n */\nimport { DestroyRef, inject, signal, type Signal } from '@angular/core';\n\n/** A value the platform knows now and will say more about later. */\nexport interface Observed<T> {\n current(): Promise<T>;\n subscribe(listener: (value: T) => void): () => void;\n}\n\n/**\n * A signal fed by an `Observed`, subscribed until the injector it was made in is destroyed.\n *\n * Called in an injection context, as `inject()` is - a field initialiser or a constructor - so\n * that for a root service the subscription ends with the app. An app cares about the battery for\n * as long as it is running, and not after: one that is mounted and unmounted would otherwise leave\n * a listener behind each time.\n *\n * `initial` is what the value is before the platform has answered, which is always at least one\n * turn: every one of these getters is asynchronous even when the answer is already in memory.\n */\nexport function observed<T>(source: Observed<T> | null, initial: T): Signal<T> {\n const value = signal(initial);\n if (!source) return value.asReadonly();\n\n // Failures are swallowed rather than surfaced: a device with no battery API is a device where\n // the default is the honest answer, and nothing on screen should break over it. The first\n // answer was asked for before anything was heard, so it is older than anything the listener\n // has said by the time it arrives, and is dropped then.\n let heard = false;\n void source.current().then(\n (first) => heard || value.set(first),\n () => {},\n );\n inject(DestroyRef).onDestroy(\n source.subscribe((next) => {\n heard = true;\n value.set(next);\n }),\n );\n return value.asReadonly();\n}\n\n/**\n * An `Observed` from the pair of functions these modules always expose.\n *\n * Named for what it makes rather than `from`, which is what it was: the package index re-exports\n * everything, and a bare `from` in an app's import list says nothing about what it is from.\n */\nexport function observedFrom<T>(\n current: () => Promise<T>,\n subscribe: (listener: (value: T) => void) => { remove(): void },\n): Observed<T> {\n return {\n current,\n subscribe: (listener) => {\n const subscription = subscribe(listener);\n return () => subscription.remove();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"observed.js","sourceRoot":"","sources":["../src/observed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAQtC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAI,MAA0B,EAAE,OAAU;IAChE,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO;8EAAC,CAAC;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC;IACvC,2FAA2F;IAC3F,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAgB,CAAC,CAAC;IAErE,8FAA8F;IAC9F,0FAA0F;IAC1F,4FAA4F;IAC5F,wDAAwD;IACxD,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,CACxB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EACpC,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;IACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAC1B,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,KAAK,GAAG,IAAI,CAAC;QACb,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC,CAAC,CACH,CAAC;IACF,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAyB,EACzB,SAA+D;IAE/D,OAAO;QACL,OAAO;QACP,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;YACtB,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACzC,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The shape nearly every device-state module has, and the one part of it that is React-only.\n *\n * `getNetworkStateAsync` + `addNetworkStateListener` + `useNetworkState`. `getBatteryLevelAsync` +\n * `addBatteryLevelListener` + `useBatteryLevel`. The two functions are plain; the hook is React\n * state around them, and a hook is the only surface some of these offer for *reading over time*.\n *\n * So this is that hook, as a signal: a value that starts at a stated default, is fetched once,\n * and follows the listener from then on.\n */\nimport { computed, DestroyRef, inject, signal, type Signal } from '@angular/core';\nimport { present } from './native.ts';\n\n/** A value the platform knows now and will say more about later. */\nexport interface Observed<T> {\n current(): Promise<T>;\n subscribe(listener: (value: T) => void): () => void;\n}\n\n/**\n * A signal fed by an `Observed`, subscribed until the injector it was made in is destroyed.\n *\n * Called in an injection context, as `inject()` is - a field initialiser or a constructor - so\n * that for a root service the subscription ends with the app. An app cares about the battery for\n * as long as it is running, and not after: one that is mounted and unmounted would otherwise leave\n * a listener behind each time.\n *\n * `initial` is what the value is before the platform has answered, which is always at least one\n * turn: every one of these getters is asynchronous even when the answer is already in memory.\n */\nexport function observed<T>(source: Observed<T> | null, initial: T): Signal<T> {\n const value = signal(initial);\n if (!source) return value.asReadonly();\n // A module missing from the build says so when the value is read, not as it is subscribed.\n if (!present(source)) return computed(() => source.current as never);\n\n // Failures are swallowed rather than surfaced: a device with no battery API is a device where\n // the default is the honest answer, and nothing on screen should break over it. The first\n // answer was asked for before anything was heard, so it is older than anything the listener\n // has said by the time it arrives, and is dropped then.\n let heard = false;\n void source.current().then(\n (first) => heard || value.set(first),\n () => {},\n );\n inject(DestroyRef).onDestroy(\n source.subscribe((next) => {\n heard = true;\n value.set(next);\n }),\n );\n return value.asReadonly();\n}\n\n/**\n * An `Observed` from the pair of functions these modules always expose.\n *\n * Named for what it makes rather than `from`, which is what it was: the package index re-exports\n * everything, and a bare `from` in an app's import list says nothing about what it is from.\n */\nexport function observedFrom<T>(\n current: () => Promise<T>,\n subscribe: (listener: (value: T) => void) => { remove(): void },\n): Observed<T> {\n return {\n current,\n subscribe: (listener) => {\n const subscription = subscribe(listener);\n return () => subscription.remove();\n },\n };\n}\n"]}
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { InjectionToken, Service, computed, inject } from '@angular/core';
10
10
  import { observed, observedFrom } from "./observed.js";
11
- import { expoModule } from "./native.js";
11
+ import { expoModule, present } from "./native.js";
12
12
  import * as i0 from "@angular/core";
13
13
  const NOTHING = {
14
14
  reported: null,
@@ -22,6 +22,9 @@ export class DeviceOrientation {
22
22
  const expo = expoModule('expo-screen-orientation', () => require('expo-screen-orientation'));
23
23
  if (!expo)
24
24
  return NOTHING;
25
+ // Missing from the build: stand-ins, to say so when they are used.
26
+ if (!present(expo))
27
+ return { reported: expo, lock: expo, unlock: expo };
25
28
  const ORIENTATIONS = {
26
29
  [expo.Orientation.UNKNOWN]: 'unknown',
27
30
  [expo.Orientation.PORTRAIT_UP]: 'portrait',
@@ -1 +1 @@
1
- {"version":3,"file":"orientation.js","sourceRoot":"","sources":["../src/orientation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `DeviceOrientation`, bound to `expo-screen-orientation`.\n *\n * Named for the device rather than the screen because that is what it reports: a layout asking\n * which way *it* is laid out wants the `orientation` media feature, which needs none of this.\n * This is what a camera preview or a video player needs - a screen locked to portrait still has a\n * phone that is sideways.\n */\nimport { InjectionToken, Service, computed, inject, type Signal } from '@angular/core';\nimport { observed, observedFrom, type Observed } from './observed.ts';\nimport { expoModule } from './native.ts';\n\nexport type Orientation =\n 'unknown' | 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';\n\n/** What a screen may rotate to. `default` is portrait plus, on a phone, nothing else. */\nexport type OrientationLock = 'default' | 'all' | 'portrait' | 'landscape';\n\nexport interface OrientationSource {\n readonly reported: Observed<Orientation> | null;\n lock(lock: OrientationLock): Promise<void>;\n unlock(): Promise<void>;\n}\n\nconst NOTHING: OrientationSource = {\n reported: null,\n lock: () => Promise.resolve(),\n unlock: () => Promise.resolve(),\n};\n\n@Service()\nexport class DeviceOrientation {\n /** Overridden in a test to rotate a device that is not there. */\n static readonly SOURCE = new InjectionToken<OrientationSource>(\n 'angular-native.orientationSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-screen-orientation',\n () => require('expo-screen-orientation') as typeof import('expo-screen-orientation'),\n );\n if (!expo) return NOTHING;\n\n const ORIENTATIONS: Record<number, Orientation> = {\n [expo.Orientation.UNKNOWN]: 'unknown',\n [expo.Orientation.PORTRAIT_UP]: 'portrait',\n [expo.Orientation.PORTRAIT_DOWN]: 'portrait-upside-down',\n [expo.Orientation.LANDSCAPE_LEFT]: 'landscape-left',\n [expo.Orientation.LANDSCAPE_RIGHT]: 'landscape-right',\n };\n const LOCKS: Record<OrientationLock, import('expo-screen-orientation').OrientationLock> = {\n default: expo.OrientationLock.DEFAULT,\n all: expo.OrientationLock.ALL,\n portrait: expo.OrientationLock.PORTRAIT,\n landscape: expo.OrientationLock.LANDSCAPE,\n };\n\n return {\n reported: observedFrom(\n async () => ORIENTATIONS[await expo.getOrientationAsync()] ?? 'unknown',\n (listener) =>\n expo.addOrientationChangeListener(({ orientationInfo }) =>\n listener(ORIENTATIONS[orientationInfo.orientation] ?? 'unknown'),\n ),\n ),\n lock: (lock) => expo.lockAsync(LOCKS[lock]),\n unlock: () => expo.unlockAsync(),\n };\n },\n },\n );\n\n private readonly source = inject(DeviceOrientation.SOURCE);\n\n readonly orientation: Signal<Orientation> = observed(this.source.reported, 'unknown');\n\n readonly landscape: Signal<boolean> = computed(() => this.orientation().startsWith('landscape'));\n\n /**\n * Pin the screen. Returns the function that unpins it, so a screen that wants landscape while\n * it is up can hand it to `DestroyRef.onDestroy` and not think about it again.\n */\n lock(lock: OrientationLock): () => void {\n // A stack, as the status bar's is: a screen pushed over one with a lock of its own releases\n // only its own, and the lock beneath it comes back rather than the whole app unlocking.\n const entry = { lock };\n this.locks.push(entry);\n void this.source.lock(lock);\n return () => {\n const at = this.locks.indexOf(entry);\n if (at === -1) return;\n this.locks.splice(at, 1);\n if (at < this.locks.length) return;\n const below = this.locks.at(-1);\n void (below ? this.source.lock(below.lock) : this.source.unlock());\n };\n }\n\n /** Every lock not yet released, the one in force last. */\n private readonly locks: { readonly lock: OrientationLock }[] = [];\n}\n"]}
1
+ {"version":3,"file":"orientation.js","sourceRoot":"","sources":["../src/orientation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `DeviceOrientation`, bound to `expo-screen-orientation`.\n *\n * Named for the device rather than the screen because that is what it reports: a layout asking\n * which way *it* is laid out wants the `orientation` media feature, which needs none of this.\n * This is what a camera preview or a video player needs - a screen locked to portrait still has a\n * phone that is sideways.\n */\nimport { InjectionToken, Service, computed, inject, type Signal } from '@angular/core';\nimport { observed, observedFrom, type Observed } from './observed.ts';\nimport { expoModule, present } from './native.ts';\n\nexport type Orientation =\n 'unknown' | 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';\n\n/** What a screen may rotate to. `default` is portrait plus, on a phone, nothing else. */\nexport type OrientationLock = 'default' | 'all' | 'portrait' | 'landscape';\n\nexport interface OrientationSource {\n readonly reported: Observed<Orientation> | null;\n lock(lock: OrientationLock): Promise<void>;\n unlock(): Promise<void>;\n}\n\nconst NOTHING: OrientationSource = {\n reported: null,\n lock: () => Promise.resolve(),\n unlock: () => Promise.resolve(),\n};\n\n@Service()\nexport class DeviceOrientation {\n /** Overridden in a test to rotate a device that is not there. */\n static readonly SOURCE = new InjectionToken<OrientationSource>(\n 'angular-native.orientationSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-screen-orientation',\n () => require('expo-screen-orientation') as typeof import('expo-screen-orientation'),\n );\n if (!expo) return NOTHING;\n // Missing from the build: stand-ins, to say so when they are used.\n if (!present(expo)) return { reported: expo, lock: expo, unlock: expo } as never;\n\n const ORIENTATIONS: Record<number, Orientation> = {\n [expo.Orientation.UNKNOWN]: 'unknown',\n [expo.Orientation.PORTRAIT_UP]: 'portrait',\n [expo.Orientation.PORTRAIT_DOWN]: 'portrait-upside-down',\n [expo.Orientation.LANDSCAPE_LEFT]: 'landscape-left',\n [expo.Orientation.LANDSCAPE_RIGHT]: 'landscape-right',\n };\n const LOCKS: Record<OrientationLock, import('expo-screen-orientation').OrientationLock> = {\n default: expo.OrientationLock.DEFAULT,\n all: expo.OrientationLock.ALL,\n portrait: expo.OrientationLock.PORTRAIT,\n landscape: expo.OrientationLock.LANDSCAPE,\n };\n\n return {\n reported: observedFrom(\n async () => ORIENTATIONS[await expo.getOrientationAsync()] ?? 'unknown',\n (listener) =>\n expo.addOrientationChangeListener(({ orientationInfo }) =>\n listener(ORIENTATIONS[orientationInfo.orientation] ?? 'unknown'),\n ),\n ),\n lock: (lock) => expo.lockAsync(LOCKS[lock]),\n unlock: () => expo.unlockAsync(),\n };\n },\n },\n );\n\n private readonly source = inject(DeviceOrientation.SOURCE);\n\n readonly orientation: Signal<Orientation> = observed(this.source.reported, 'unknown');\n\n readonly landscape: Signal<boolean> = computed(() => this.orientation().startsWith('landscape'));\n\n /**\n * Pin the screen. Returns the function that unpins it, so a screen that wants landscape while\n * it is up can hand it to `DestroyRef.onDestroy` and not think about it again.\n */\n lock(lock: OrientationLock): () => void {\n // A stack, as the status bar's is: a screen pushed over one with a lock of its own releases\n // only its own, and the lock beneath it comes back rather than the whole app unlocking.\n const entry = { lock };\n this.locks.push(entry);\n void this.source.lock(lock);\n return () => {\n const at = this.locks.indexOf(entry);\n if (at === -1) return;\n this.locks.splice(at, 1);\n if (at < this.locks.length) return;\n const below = this.locks.at(-1);\n void (below ? this.source.lock(below.lock) : this.source.unlock());\n };\n }\n\n /** Every lock not yet released, the one in force last. */\n private readonly locks: { readonly lock: OrientationLock }[] = [];\n}\n"]}
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Expo's native views, driven directly.
3
+ *
4
+ * An Expo module that renders something registers a Fabric component whose name it derives
5
+ * rather than declares, and `requireNativeViewManager` is the only place that derivation is
6
+ * written down. Everything else that function does is React: it caches a host component, builds
7
+ * a view config for React's renderer, and wraps the result in a class whose whole job is to hold
8
+ * a ref. None of that is reachable without React, and none of it is needed - the engine writes
9
+ * props onto the shadow node itself, and Expo's Fabric views take their props as an untyped map,
10
+ * so there is no codegen'd prop list to satisfy.
11
+ *
12
+ * What is left is the name. That is what this package is.
13
+ *
14
+ * No decorators and no Angular here on purpose: this is a registration, and the components it
15
+ * registers are ordinary elements in a template.
16
+ */
17
+ import { type ViewNameOptions } from '@ng-native/fabric';
1
18
  /**
2
19
  * The Fabric component name for a module's view, exactly as `requireNativeViewManager` computes
3
20
  * it in `expo-modules-core`.
@@ -27,6 +44,8 @@ export interface ExpoViewOptions {
27
44
  * reads `text`. The text written inside the element becomes that prop. See `ViewNameOptions`.
28
45
  */
29
46
  readonly textContent?: string;
47
+ /** Rewrites the fonts the view's own props name. See `ViewNameOptions`. */
48
+ readonly fontNames?: ViewNameOptions['fontNames'];
30
49
  }
31
50
  /**
32
51
  * Teach the engine an element name for an Expo module's view, e.g.
@@ -45,6 +45,7 @@ export function registerExpoView(elementName, moduleName, options) {
45
45
  registerViewName(elementName, expoViewName(moduleName, options?.viewName), options?.defaultProps, {
46
46
  textContent: options?.textContent,
47
47
  yieldsToComponents: options?.yieldsToComponents,
48
+ fontNames: options?.fontNames,
48
49
  });
49
50
  optional(() => require('expo').requireNativeView(moduleName, options?.viewName));
50
51
  }
@@ -1 +1 @@
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 /** Whether a component of the app's own with this selector is a plain view. */\n readonly yieldsToComponents?: boolean;\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 yieldsToComponents: options?.yieldsToComponents,\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"]}
1
+ {"version":3,"file":"register-expo-view.js","sourceRoot":"","sources":["../src/register-expo-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,gBAAgB,EAAwB,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAUvC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,QAAiB;IAChE,MAAM,UAAU,GAAI,UAAoC,CAAC,IAAI,EAAE,uBAAuB,IAAI,EAAE,CAAC;IAC7F,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IACnE,OAAO,sBAAsB,MAAM,GAAG,MAAM,EAAE,CAAC;AACjD,CAAC;AAsBD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAAmB,EACnB,UAAkB,EAClB,OAAyB;IAEzB,gBAAgB,CACd,WAAW,EACX,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,EAC3C,OAAO,EAAE,YAAY,EACrB;QACE,WAAW,EAAE,OAAO,EAAE,WAAW;QACjC,kBAAkB,EAAE,OAAO,EAAE,kBAAkB;QAC/C,SAAS,EAAE,OAAO,EAAE,SAAS;KAC9B,CACF,CAAC;IACF,QAAQ,CAAC,GAAG,EAAE,CAEV,OAAO,CAAC,MAAM,CACf,CAAC,iBAAiB,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,CACnD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,UAAU,GAAyD;IAC9E,4FAA4F;IAC5F,YAAY,EAAE,CAAC,WAAW,EAAE,EAAE,YAAY,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;IACtE,wFAAwF;IACxF,WAAW,EAAE,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IACnF,iGAAiG;IACjG,YAAY,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACtD;;;OAGG;IACH,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,gFAAgF;IAChF,sBAAsB,EAAE,CAAC,yBAAyB,CAAC;IACnD,2FAA2F;IAC3F,aAAa,EAAE,CAAC,cAAc,EAAE,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC;IACzE,+EAA+E;IAC/E,SAAS,EAAE,CAAC,QAAQ,CAAC;IACrB,iGAAiG;IACjG,YAAY,EAAE,CAAC,iBAAiB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;IAC5D,sFAAsF;IACtF,sBAAsB,EAAE,CAAC,iBAAiB,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC3E,iGAAiG;IACjG,oBAAoB,EAAE,CAAC,kBAAkB,EAAE,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC5E,mCAAmC;IACnC,iBAAiB,EAAE,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;IACnE;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,gBAAgB,CAAC;IACtC,iBAAiB,EAAE,CAAC,eAAe,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,QAA8C;IACjF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,OAAO,GAAG,CAAC,CAAC;QACtF,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC","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, type ViewNameOptions } 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 /** Whether a component of the app's own with this selector is a plain view. */\n readonly yieldsToComponents?: boolean;\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 /** Rewrites the fonts the view's own props name. See `ViewNameOptions`. */\n readonly fontNames?: ViewNameOptions['fontNames'];\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 yieldsToComponents: options?.yieldsToComponents,\n fontNames: options?.fontNames,\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"]}
@@ -18,7 +18,7 @@
18
18
  * stays at zero.
19
19
  */
20
20
  import { DestroyRef, InjectionToken, Service, inject, signal } from '@angular/core';
21
- import { expoModule } from "./native.js";
21
+ import { expoModule, present } from "./native.js";
22
22
  import { Permission, UNAVAILABLE } from "./permissions.js";
23
23
  import * as i0 from "@angular/core";
24
24
  export class ScreenCapture {
@@ -37,7 +37,7 @@ export class ScreenCapture {
37
37
  /** How many screenshots the user has taken while the app was in front. */
38
38
  screenshots = this.count.asReadonly();
39
39
  constructor() {
40
- const listener = this.native?.addScreenshotListener(() => this.count.update((n) => n + 1));
40
+ const listener = present(this.native)?.addScreenshotListener(() => this.count.update((n) => n + 1));
41
41
  if (listener)
42
42
  inject(DestroyRef).onDestroy(() => listener.remove());
43
43
  }