@lowdefy/shell 3.23.2 → 7.2.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 (356) hide show
  1. package/LICENSE +183 -183
  2. package/README.md +364 -2
  3. package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
  4. package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
  5. package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
  6. package/app/+native-intent.js +39 -0
  7. package/app/_layout.js +36 -0
  8. package/app.config.ts +183 -0
  9. package/appLinks.js +65 -0
  10. package/applyShellPlugins.js +43 -0
  11. package/authCallbackIntentFilters.js +46 -0
  12. package/babel.config.js +25 -0
  13. package/checkShellConfig.js +172 -0
  14. package/checkShellPlugins.js +74 -0
  15. package/getPrivacyManifest.js +39 -0
  16. package/metro.config.js +73 -0
  17. package/native/LowdefyAppShortcuts.swift.template +84 -0
  18. package/native/withAppShortcuts.js +60 -0
  19. package/native-fingerprint.json +16 -0
  20. package/package.json +120 -40
  21. package/profiles.js +607 -0
  22. package/readShellConfig.js +36 -0
  23. package/resolveShellPlugins.js +109 -0
  24. package/runtimeVersion.js +112 -0
  25. package/scripts/checkReleaseBundle.mjs +358 -0
  26. package/scripts/createDayjsLocales.mjs +67 -0
  27. package/scripts/createShellAssets.mjs +54 -0
  28. package/scripts/createShellVersion.mjs +83 -0
  29. package/scripts/createTypeManifest.mjs +205 -0
  30. package/shell.dev.json +11 -0
  31. package/shellVariant.js +126 -0
  32. package/src/ShellContext.js +32 -0
  33. package/src/actions/CopyToClipboard.js +29 -0
  34. package/src/actions/CopyToClipboard.test.js +33 -0
  35. package/src/actions/GeolocationCurrentPosition.js +61 -0
  36. package/src/actions/GeolocationCurrentPosition.test.js +79 -0
  37. package/src/actions/GetLocalStorage.js +36 -0
  38. package/src/actions/RemoveLocalStorage.js +31 -0
  39. package/src/actions/ScrollTo.js +66 -0
  40. package/src/actions/ScrollTo.test.js +83 -0
  41. package/src/actions/SetFocus.js +33 -0
  42. package/src/actions/SetFocus.test.js +49 -0
  43. package/src/actions/SetLocalStorage.js +34 -0
  44. package/src/actions/localStorage.test.js +68 -0
  45. package/src/actions/measureInScrollView.js +36 -0
  46. package/src/actions/nativeActions.js +41 -0
  47. package/src/auth/AuthButton.js +44 -0
  48. package/src/auth/AuthGate.js +57 -0
  49. package/src/auth/AuthLink.js +43 -0
  50. package/src/auth/LockScreen.js +67 -0
  51. package/src/auth/LockScreen.test.js +37 -0
  52. package/src/auth/SignInScreen.js +268 -0
  53. package/src/auth/SignInScreen.test.js +187 -0
  54. package/src/auth/createAppAuth.js +325 -0
  55. package/src/auth/createAppAuth.test.js +424 -0
  56. package/src/auth/createAuthApi.js +115 -0
  57. package/src/auth/createAuthApi.test.js +138 -0
  58. package/src/auth/createCoreAuth.js +96 -0
  59. package/src/auth/createCoreAuth.test.js +188 -0
  60. package/src/auth/createDeviceKey.js +62 -0
  61. package/src/auth/createDeviceKey.test.js +54 -0
  62. package/src/auth/createSessionStores.js +45 -0
  63. package/src/auth/createTokenStore.js +103 -0
  64. package/src/auth/createTokenStore.test.js +116 -0
  65. package/src/auth/getAuthSettings.js +24 -0
  66. package/src/auth/getAuthView.js +39 -0
  67. package/src/auth/getAuthView.test.js +52 -0
  68. package/src/auth/getSheetReturnUrl.js +35 -0
  69. package/src/auth/getTokenStoreOptions.js +46 -0
  70. package/src/auth/isBackgroundShell.js +26 -0
  71. package/src/auth/isSheetReturnLink.js +35 -0
  72. package/src/auth/parseQuery.js +39 -0
  73. package/src/auth/purgeAppData.js +80 -0
  74. package/src/auth/purgeAppData.test.js +104 -0
  75. package/src/auth/shouldLock.js +26 -0
  76. package/src/auth/signInWithSheet.js +97 -0
  77. package/src/auth/signInWithSheet.test.js +205 -0
  78. package/src/auth/styles.js +78 -0
  79. package/src/auth/unregisterPushToken.js +62 -0
  80. package/src/auth/unregisterPushToken.test.js +67 -0
  81. package/src/auth/usesBiometricUnlock.js +23 -0
  82. package/src/auth/watchSession.js +47 -0
  83. package/src/auth/withoutCookies.js +26 -0
  84. package/src/cache/createSqliteCacheStore.js +113 -0
  85. package/src/cache/createSqliteCacheStore.test.js +93 -0
  86. package/src/cache/getCacheKey.js +25 -0
  87. package/src/cache/getLastCacheKeyName.js +22 -0
  88. package/src/components/AppProvider.js +118 -0
  89. package/src/components/Frame.js +116 -0
  90. package/src/components/Frame.test.js +101 -0
  91. package/src/components/LoadingScreen.js +35 -0
  92. package/src/components/MenuButton.js +121 -0
  93. package/src/components/MenuButton.test.js +74 -0
  94. package/src/components/MessageScreen.js +51 -0
  95. package/src/components/NavigationTheme.js +33 -0
  96. package/src/components/NoConnectionBanner.js +51 -0
  97. package/src/components/NoConnectionBanner.test.js +73 -0
  98. package/src/components/OtherPageScreen.js +95 -0
  99. package/src/components/OtherPageScreen.test.js +74 -0
  100. package/src/components/PageScreen.js +254 -0
  101. package/src/components/PageScreen.test.js +492 -0
  102. package/src/components/RouteErrorScreen.js +47 -0
  103. package/src/components/ShellRoot.js +34 -0
  104. package/src/components/ShellRoot.test.js +62 -0
  105. package/src/components/ShellThemeProvider.js +45 -0
  106. package/src/components/SwitchToTab.js +36 -0
  107. package/src/components/SwitchToTab.test.js +63 -0
  108. package/src/components/TabIconImages.js +72 -0
  109. package/src/components/UpdateScreen.js +40 -0
  110. package/src/components/getNavigationTheme.js +44 -0
  111. package/src/components/getNavigationTheme.test.js +41 -0
  112. package/src/components/styles.js +50 -0
  113. package/src/components/useSurfaceTokens.js +47 -0
  114. package/src/config/getAppApiBase.js +34 -0
  115. package/src/config/getShellConfig.js +37 -0
  116. package/src/config/getShellConfig.test.js +80 -0
  117. package/src/createShellApp.js +126 -0
  118. package/src/dev/DevShell.js +239 -0
  119. package/src/dev/DevShell.test.js +233 -0
  120. package/src/dev/connect/ConfirmSheet.js +71 -0
  121. package/src/dev/connect/ConnectScreen.js +117 -0
  122. package/src/dev/connect/QrScanner.js +89 -0
  123. package/src/dev/connection/createDevShellConfig.js +47 -0
  124. package/src/dev/connection/createDevTransport.js +51 -0
  125. package/src/dev/connection/createDevTransport.test.js +87 -0
  126. package/src/dev/connection/createServerList.js +48 -0
  127. package/src/dev/connection/isLoopbackHost.js +25 -0
  128. package/src/dev/connection/pairedShellApp.test.js +104 -0
  129. package/src/dev/connection/parseDevLink.js +66 -0
  130. package/src/dev/connection/parseDevLink.test.js +97 -0
  131. package/src/dev/connection/parseQuery.js +33 -0
  132. package/src/dev/connection/parseServerInput.js +40 -0
  133. package/src/dev/connection/parseServerUrl.js +45 -0
  134. package/src/dev/devStyles.js +97 -0
  135. package/src/dev/index.js +21 -0
  136. package/src/dev/inject/createDeviceInjections.js +188 -0
  137. package/src/dev/inject/createDeviceInjections.test.js +224 -0
  138. package/src/dev/inject/createOfflineFetch.js +35 -0
  139. package/src/dev/inject/createOfflineFetch.test.js +68 -0
  140. package/src/dev/inject/injectPush.js +60 -0
  141. package/src/dev/inject/injectShare.js +55 -0
  142. package/src/dev/inject/withInjectedDevice.js +98 -0
  143. package/src/dev/menu/BlockGallery.js +33 -0
  144. package/src/dev/menu/DevMenu.js +121 -0
  145. package/src/dev/menu/createLogBuffer.js +83 -0
  146. package/src/dev/menu/watchShake.js +52 -0
  147. package/src/dev/menu/watchShake.test.js +61 -0
  148. package/src/dev/recorder/createNativeRecorder.js +204 -0
  149. package/src/dev/recorder/createNativeRecorder.test.js +307 -0
  150. package/src/dev/redirectDevLink.js +30 -0
  151. package/src/dev/redirectDevLink.test.js +49 -0
  152. package/src/dev/steps/createStepRunner.js +184 -0
  153. package/src/dev/steps/createStepRunner.test.js +262 -0
  154. package/src/dev/steps/findBlock.js +78 -0
  155. package/src/dev/steps/isContextSettled.js +37 -0
  156. package/src/dev/steps/waitForSettle.js +39 -0
  157. package/src/dev/steps/waitUntil.js +40 -0
  158. package/src/dev/tab/DevTabClient.js +97 -0
  159. package/src/dev/tab/answerEval.js +39 -0
  160. package/src/dev/tab/answerInspect.js +45 -0
  161. package/src/dev/tab/createDevTabClient.js +272 -0
  162. package/src/dev/tab/createDevTabClient.test.js +317 -0
  163. package/src/dev/tab/describeTabError.js +35 -0
  164. package/src/dev/tab/devClientMarker.js +22 -0
  165. package/src/dev/tab/devClientMarker.test.js +23 -0
  166. package/src/dev/tab/findPageContext.js +43 -0
  167. package/src/dev/tab/getShownPage.js +42 -0
  168. package/src/dev/tab/readEventStream.js +68 -0
  169. package/src/dev/tab/reloadApp.js +42 -0
  170. package/src/dev/tab/reloadApp.test.js +115 -0
  171. package/src/device/DeviceHosts.js +32 -0
  172. package/src/device/basic/createFilesFeature.js +41 -0
  173. package/src/device/basic/createHapticsFeature.js +46 -0
  174. package/src/device/basic/createKeepAwakeFeature.js +37 -0
  175. package/src/device/basic/createPhotosFeature.js +40 -0
  176. package/src/device/basic/createRequestQueueFeature.js +37 -0
  177. package/src/device/basic/createShareFeature.js +63 -0
  178. package/src/device/basic/createShareFeature.test.js +47 -0
  179. package/src/device/basic/createSpeechFeature.js +43 -0
  180. package/src/device/basic/createUploadFeature.js +41 -0
  181. package/src/device/basic/toImagePickerOptions.js +41 -0
  182. package/src/device/basic/toPickedFile.js +54 -0
  183. package/src/device/calendar/index.js +68 -0
  184. package/src/device/contacts/index.js +67 -0
  185. package/src/device/createDevice.js +65 -0
  186. package/src/device/device.js +27 -0
  187. package/src/device/field/MapView.js +93 -0
  188. package/src/device/field/ScanCodeSheet.js +86 -0
  189. package/src/device/field/ScannerView.js +52 -0
  190. package/src/device/field/createAudioFeature.js +70 -0
  191. package/src/device/field/createBrightnessFeature.js +40 -0
  192. package/src/device/field/createCameraFeature.js +56 -0
  193. package/src/device/field/createDocumentScannerFeature.js +51 -0
  194. package/src/device/field/createLocationFeature.js +39 -0
  195. package/src/device/field/createScanRequests.js +55 -0
  196. package/src/device/field/index.js +39 -0
  197. package/src/device/field/scanRequests.js +22 -0
  198. package/src/device/injected.js +60 -0
  199. package/src/device/injected.test.js +39 -0
  200. package/src/device/nfc/index.js +89 -0
  201. package/src/device/nfc/toNfcRecord.js +44 -0
  202. package/src/device/nfc/toNfcRecord.test.js +48 -0
  203. package/src/device/tracking/createBacklog.js +63 -0
  204. package/src/device/tracking/createBacklog.test.js +47 -0
  205. package/src/device/tracking/index.js +211 -0
  206. package/src/device/tracking/index.test.js +110 -0
  207. package/src/host/createDeviceStore.js +32 -0
  208. package/src/host/createMemoryStore.js +32 -0
  209. package/src/host/createNetworkStatus.js +94 -0
  210. package/src/host/createNetworkStatus.test.js +94 -0
  211. package/src/host/createRouterAdapter.js +33 -0
  212. package/src/host/createShellFetch.js +62 -0
  213. package/src/host/createShellFetch.test.js +165 -0
  214. package/src/host/createShellGlobals.js +62 -0
  215. package/src/host/createShellHost.js +83 -0
  216. package/src/host/createShellHost.test.js +36 -0
  217. package/src/host/getOrigin.js +31 -0
  218. package/src/host/isNetworkError.js +24 -0
  219. package/src/host/isOnline.js +22 -0
  220. package/src/host/networkStatus.js +24 -0
  221. package/src/host/readServerError.js +45 -0
  222. package/src/host/readServerError.test.js +85 -0
  223. package/src/host/reportShellError.js +47 -0
  224. package/src/host/reportShellError.test.js +79 -0
  225. package/src/host/withNetworkErrors.js +38 -0
  226. package/src/host/withNetworkErrors.test.js +61 -0
  227. package/src/host/withShellHeader.js +25 -0
  228. package/src/host/withShellHeader.test.js +28 -0
  229. package/src/js/evaluateJsEntries.js +43 -0
  230. package/src/js/evaluateJsEntries.test.js +48 -0
  231. package/src/locale/createShellLocale.js +112 -0
  232. package/src/locale/createShellLocale.test.js +74 -0
  233. package/src/locale/dayjsLocales.js +165 -0
  234. package/src/locale/getDeviceLocales.js +23 -0
  235. package/src/locale/loadDayjsLocale.js +32 -0
  236. package/src/locale/resolveActiveLocale.js +46 -0
  237. package/src/locale/resolveActiveLocale.test.js +40 -0
  238. package/src/native/backgroundFetch.js +97 -0
  239. package/src/native/callEndpoint.js +63 -0
  240. package/src/native/createAppEventTrigger.js +70 -0
  241. package/src/native/currentApp.js +49 -0
  242. package/src/native/getPagePath.js +32 -0
  243. package/src/native/openPath.js +55 -0
  244. package/src/native/openPath.test.js +39 -0
  245. package/src/native/parseShellLink.js +38 -0
  246. package/src/native/parseShellLink.test.js +29 -0
  247. package/src/native/startAppServices.js +94 -0
  248. package/src/native/startAppServices.test.js +97 -0
  249. package/src/native/startShareIn.js +59 -0
  250. package/src/native/startShortcuts.js +88 -0
  251. package/src/native/startShortcuts.test.js +59 -0
  252. package/src/native/toSharePayload.js +47 -0
  253. package/src/native/toSharePayload.test.js +47 -0
  254. package/src/native/whenAppReady.js +48 -0
  255. package/src/navigation/SlotContext.js +24 -0
  256. package/src/navigation/frameSlots.js +27 -0
  257. package/src/navigation/getFrame.js +30 -0
  258. package/src/navigation/getInitialScreenOptions.js +63 -0
  259. package/src/navigation/getInitialScreenOptions.test.js +66 -0
  260. package/src/navigation/getPresentation.js +28 -0
  261. package/src/navigation/getSlotRootPath.js +27 -0
  262. package/src/navigation/getStackOptions.js +185 -0
  263. package/src/navigation/getTabHref.js +45 -0
  264. package/src/navigation/getTabIcons.js +43 -0
  265. package/src/navigation/isPageUnderHeader.js +46 -0
  266. package/src/navigation/navigation.test.js +342 -0
  267. package/src/navigation/toAppLinkPath.js +43 -0
  268. package/src/navigation/toAppLinkPath.test.js +53 -0
  269. package/src/navigation/toRouterHref.js +29 -0
  270. package/src/notifications/createNotificationsFeature.js +61 -0
  271. package/src/notifications/getCategoryAction.js +31 -0
  272. package/src/notifications/handleNotificationResponse.js +82 -0
  273. package/src/notifications/registerPushToken.js +71 -0
  274. package/src/notifications/startNotifications.js +114 -0
  275. package/src/notifications/toCategoryActions.js +42 -0
  276. package/src/notifications/toNotificationEvent.js +36 -0
  277. package/src/notifications/toScheduleRequest.js +62 -0
  278. package/src/plugins/appTypes.js +28 -0
  279. package/src/plugins/prefixTypes.js +28 -0
  280. package/src/plugins/shellPlugins.json +15 -0
  281. package/src/plugins/typeManifest.js +789 -0
  282. package/src/plugins/types.js +64 -0
  283. package/src/plugins/types.test.js +173 -0
  284. package/src/polyfills/installGetRandomValues.js +28 -0
  285. package/src/queue/createRequestQueue.js +198 -0
  286. package/src/queue/createRequestQueue.test.js +215 -0
  287. package/src/queue/createSqliteQueueStore.js +121 -0
  288. package/src/queue/summarizeQueue.js +39 -0
  289. package/src/session/createAppSession.js +323 -0
  290. package/src/session/createAppSession.test.js +391 -0
  291. package/src/session/getPageView.js +59 -0
  292. package/src/session/getPageView.test.js +137 -0
  293. package/src/sync/encodePagePath.js +27 -0
  294. package/src/sync/fetchPage.js +72 -0
  295. package/src/sync/fetchPage.test.js +84 -0
  296. package/src/sync/fetchRoot.js +43 -0
  297. package/src/sync/fetchUser.js +33 -0
  298. package/src/sync/runLimited.js +36 -0
  299. package/src/sync/toRootConfig.js +35 -0
  300. package/src/test/createFakeNetwork.js +40 -0
  301. package/src/test/createFakeSecureStore.js +40 -0
  302. package/src/test/createFakeServer.js +98 -0
  303. package/src/test/createMemoryCacheStore.js +59 -0
  304. package/src/test/createNetworkError.js +24 -0
  305. package/src/test/expoImage.js +42 -0
  306. package/src/test/expoNotifications.js +40 -0
  307. package/src/test/expoQuickActions.js +22 -0
  308. package/src/test/expoSpeechRecognition.js +31 -0
  309. package/src/test/expoTaskManager.js +25 -0
  310. package/src/test/expoUi.js +55 -0
  311. package/src/test/expoUiBottomSheet.js +64 -0
  312. package/src/test/expoUiDateTimePicker.js +26 -0
  313. package/src/test/expoUiMenu.js +46 -0
  314. package/src/test/expoUiSegmentedControl.js +47 -0
  315. package/src/test/expoUiSwiftUi.js +62 -0
  316. package/src/test/expoUiSwiftUiModifiers.js +23 -0
  317. package/src/test/expoWebBrowser.js +23 -0
  318. package/src/test/fixtures.js +98 -0
  319. package/src/test/nativeModule.js +33 -0
  320. package/src/test/renderPageScreen.js +109 -0
  321. package/src/types/checkTypeSet.js +39 -0
  322. package/src/types/collectPageTypes.js +49 -0
  323. package/src/types/collectPageTypes.test.js +90 -0
  324. package/src/types/findMissingPageTypes.js +40 -0
  325. package/src/version/compareVersions.js +38 -0
  326. package/src/version/isShellTooOld.js +30 -0
  327. package/src/version/shellVersion.js +21 -0
  328. package/src/version/shellVersion.test.js +32 -0
  329. package/src/widgets/fetchWidgetData.js +59 -0
  330. package/src/widgets/index.js +156 -0
  331. package/src/widgets/refreshWidgets.js +31 -0
  332. package/withExcludedNativeModules.js +93 -0
  333. package/dist/location.js +0 -33
  334. package/dist/public/apple-touch-icon.png +0 -0
  335. package/dist/public/icon-32.png +0 -0
  336. package/dist/public/icon-512.png +0 -0
  337. package/dist/public/icon.svg +0 -17
  338. package/dist/public/logo-dark-theme.png +0 -0
  339. package/dist/public/logo-light-theme.png +0 -0
  340. package/dist/public/logo-square-dark-theme.png +0 -0
  341. package/dist/public/logo-square-light-theme.png +0 -0
  342. package/dist/public/manifest.webmanifest +0 -16
  343. package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
  344. package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
  345. package/dist/shell/454.b597ca21366afda000af.js +0 -2
  346. package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
  347. package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
  348. package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
  349. package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
  350. package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
  351. package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
  352. package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
  353. package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
  354. package/dist/shell/index.html +0 -57
  355. package/dist/shell/main.bde87d1433f0625faed6.js +0 -1
  356. package/dist/shell/runtime.1ead93159ba56c05b969.js +0 -1
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import * as Location from 'expo-location';
18
+
19
+ /**
20
+ * The location device feature (profile field): GeolocationCurrentPosition reads the phone's
21
+ * position, asking for location while in use the first time. Answers as the web's geolocation
22
+ * does: { coords, timestamp }, or the PERMISSION_DENIED shape when refused.
23
+ */
24
+ function createLocationFeature() {
25
+ return {
26
+ async current({ enableHighAccuracy = false } = {}) {
27
+ const { granted } = await Location.requestForegroundPermissionsAsync();
28
+ if (!granted) {
29
+ return { code: 1, error: 'PERMISSION_DENIED', message: 'Location permission was denied.' };
30
+ }
31
+ const position = await Location.getCurrentPositionAsync({
32
+ accuracy: enableHighAccuracy ? Location.Accuracy.High : Location.Accuracy.Balanced,
33
+ });
34
+ return { coords: position.coords, timestamp: position.timestamp };
35
+ },
36
+ };
37
+ }
38
+
39
+ export default createLocationFeature;
@@ -0,0 +1,55 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * The ScanCode requests the scan sheet answers: one at a time. `open` resolves with the first code
19
+ * read, or null when the sheet closes without one; a request made while one is open replaces it
20
+ * (the first resolves null).
21
+ */
22
+ function createScanRequests() {
23
+ let current = null;
24
+ const listeners = new Set();
25
+
26
+ function notify() {
27
+ listeners.forEach((listener) => listener(current));
28
+ }
29
+
30
+ function finish(result) {
31
+ if (current === null) return;
32
+ const { resolve } = current;
33
+ current = null;
34
+ notify();
35
+ resolve(result);
36
+ }
37
+
38
+ return {
39
+ open(params) {
40
+ finish(null);
41
+ return new Promise((resolve) => {
42
+ current = { params, resolve };
43
+ notify();
44
+ });
45
+ },
46
+ finish,
47
+ get: () => current,
48
+ subscribe(listener) {
49
+ listeners.add(listener);
50
+ return () => listeners.delete(listener);
51
+ },
52
+ };
53
+ }
54
+
55
+ export default createScanRequests;
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import createAudioFeature from './createAudioFeature.js';
18
+ import createBrightnessFeature from './createBrightnessFeature.js';
19
+ import createCameraFeature from './createCameraFeature.js';
20
+ import createDocumentScannerFeature from './createDocumentScannerFeature.js';
21
+ import createLocationFeature from './createLocationFeature.js';
22
+ import MapView from './MapView.js';
23
+ import ScanCodeSheet from './ScanCodeSheet.js';
24
+
25
+ // The `field` profile's device features (profiles.js FIELD_ENTRY). A shell on another profile
26
+ // resolves this module to an empty one (metro.config.js), so it links none of their modules.
27
+ const features = {
28
+ audio: createAudioFeature(),
29
+ brightness: createBrightnessFeature(),
30
+ camera: createCameraFeature(),
31
+ documentScanner: createDocumentScannerFeature(),
32
+ location: createLocationFeature(),
33
+ maps: { MapView },
34
+ };
35
+
36
+ // Views the features open at the root (DeviceHosts).
37
+ const hosts = [ScanCodeSheet];
38
+
39
+ export { features, hosts };
@@ -0,0 +1,22 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import createScanRequests from './createScanRequests.js';
18
+
19
+ // The shell's one set of ScanCode requests, shared by the action and the sheet.
20
+ const scanRequests = createScanRequests();
21
+
22
+ export default scanRequests;
@@ -0,0 +1,60 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // The injector Lowdefy Dev installs (src/dev/inject). A release shell never installs one.
18
+ let injector = null;
19
+
20
+ /**
21
+ * The next device result Lowdefy Dev was told to fake, or undefined when there is none: in a
22
+ * release shell always. A device action reads it first, in one line, and uses the device only when
23
+ * nothing was injected:
24
+ *
25
+ * const scan = injected('scan');
26
+ * if (scan !== undefined) return scan;
27
+ *
28
+ * Names:
29
+ * - `scan`: the next ScanCode or QRScanner read, `{ data, type }`.
30
+ * - `photo`: the next TakePhoto or PickImage result, `{ base64, mimeType, width, height }`.
31
+ * - `nfc`: the next ReadNfc read.
32
+ * - `transcript`: what the next dictation session hears, `{ partials?, final }` or `{ error }`.
33
+ * Dictation (blocks-native) reads it through the source Lowdefy Dev installs with
34
+ * installDictationSource.
35
+ * - `location`: the location GeolocationCurrentPosition and geofences read,
36
+ * `{ latitude, longitude, accuracy, ... }`.
37
+ * - `permission:<name>`: the answer to the next request for a permission, 'granted' or 'denied'
38
+ * (`permission:microphone` for dictation).
39
+ *
40
+ * @param {string} name
41
+ */
42
+ function injected(name) {
43
+ if (injector === null) {
44
+ return undefined;
45
+ }
46
+ return injector.take(name);
47
+ }
48
+
49
+ // Installs the injector `injected` reads (`{ take(name) }`); returns the function that removes it.
50
+ function installInjector(next) {
51
+ injector = next;
52
+ return () => {
53
+ if (injector === next) {
54
+ injector = null;
55
+ }
56
+ };
57
+ }
58
+
59
+ export { installInjector };
60
+ export default injected;
@@ -0,0 +1,39 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import injected, { installInjector } from './injected.js';
18
+
19
+ test('with no injector installed, as in a release shell, nothing is injected', () => {
20
+ expect(injected('scan')).toBeUndefined();
21
+ expect(injected('permission:camera')).toBeUndefined();
22
+ });
23
+
24
+ test('injected reads the installed injector until it is removed', () => {
25
+ const take = (name) => `next ${name}`;
26
+ const remove = installInjector({ take });
27
+ expect(injected('scan')).toBe('next scan');
28
+ remove();
29
+ expect(injected('scan')).toBeUndefined();
30
+ });
31
+
32
+ test('removing an injector that was replaced leaves the newer one installed', () => {
33
+ const removeFirst = installInjector({ take: () => 'first' });
34
+ const removeSecond = installInjector({ take: () => 'second' });
35
+ removeFirst();
36
+ expect(injected('scan')).toBe('second');
37
+ removeSecond();
38
+ expect(injected('scan')).toBeUndefined();
39
+ });
@@ -0,0 +1,89 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { Platform } from 'react-native';
18
+ import NfcManager, { Ndef, NfcError, NfcTech } from 'react-native-nfc-manager';
19
+
20
+ import toNfcRecord from './toNfcRecord.js';
21
+
22
+ let started = null;
23
+
24
+ function start() {
25
+ if (started === null) {
26
+ started = NfcManager.start();
27
+ }
28
+ return started;
29
+ }
30
+
31
+ // Runs one NFC session: waits for a tag, works with it, and always ends the session. A cancelled
32
+ // sheet answers null.
33
+ async function withTag(prompt, work) {
34
+ await start();
35
+ try {
36
+ await NfcManager.requestTechnology(NfcTech.Ndef, { alertMessage: prompt });
37
+ return await work();
38
+ } catch (error) {
39
+ if (error instanceof NfcError.UserCancel) {
40
+ return null;
41
+ }
42
+ throw error;
43
+ } finally {
44
+ NfcManager.cancelTechnologyRequest().catch(() => {});
45
+ }
46
+ }
47
+
48
+ function toRecordBytes(records) {
49
+ return Ndef.encodeMessage(
50
+ records.map((record) =>
51
+ record.type === 'uri'
52
+ ? Ndef.uriRecord(record.value)
53
+ : Ndef.textRecord(record.value, record.language ?? 'en')
54
+ )
55
+ );
56
+ }
57
+
58
+ /**
59
+ * The nfc device feature (profile field-nfc): ReadNfc and WriteNfc on NDEF tags
60
+ * (react-native-nfc-manager). iOS shows its scan sheet with the prompt.
61
+ */
62
+ const nfc = {
63
+ read({ prompt = 'Hold the phone near the tag.' } = {}) {
64
+ return withTag(prompt, async () => {
65
+ const tag = await NfcManager.getTag();
66
+ return {
67
+ id: tag?.id ?? null,
68
+ type: tag?.type ?? tag?.tech ?? null,
69
+ records: (tag?.ndefMessage ?? []).map((record) => toNfcRecord({ Ndef, record })),
70
+ };
71
+ });
72
+ },
73
+ write({ prompt = 'Hold the phone near the tag to write it.', records }) {
74
+ return withTag(prompt, async () => {
75
+ await NfcManager.ndefHandler.writeNdefMessage(toRecordBytes(records));
76
+ if (Platform.OS === 'ios') {
77
+ await NfcManager.setAlertMessage('Done');
78
+ }
79
+ const tag = await NfcManager.getTag();
80
+ return { id: tag?.id ?? null };
81
+ });
82
+ },
83
+ };
84
+
85
+ // The `field-nfc` profile's device features (profiles.js NFC_ENTRY).
86
+ const features = { nfc };
87
+ const hosts = [];
88
+
89
+ export { features, hosts };
@@ -0,0 +1,44 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ function typeText(recordType) {
18
+ if (typeof recordType === 'string') {
19
+ return recordType;
20
+ }
21
+ return String.fromCharCode(...recordType);
22
+ }
23
+
24
+ /**
25
+ * One NDEF record as ReadNfc returns it: { type, text?, uri?, payload }. Text and URI records are
26
+ * decoded; any other record keeps its raw payload bytes.
27
+ *
28
+ * @param {Object} params
29
+ * @param {Object} params.record A react-native-nfc-manager NdefRecord.
30
+ * @param {Object} params.Ndef react-native-nfc-manager's Ndef helpers.
31
+ */
32
+ function toNfcRecord({ Ndef, record }) {
33
+ const recordType = typeText(record.type);
34
+ const payload = Array.from(record.payload ?? []);
35
+ if (record.tnf === Ndef.TNF_WELL_KNOWN && recordType === Ndef.RTD_TEXT) {
36
+ return { type: 'text', text: Ndef.text.decodePayload(Uint8Array.from(payload)), payload };
37
+ }
38
+ if (record.tnf === Ndef.TNF_WELL_KNOWN && recordType === Ndef.RTD_URI) {
39
+ return { type: 'uri', uri: Ndef.uri.decodePayload(Uint8Array.from(payload)), payload };
40
+ }
41
+ return { type: recordType, payload };
42
+ }
43
+
44
+ export default toNfcRecord;
@@ -0,0 +1,48 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import toNfcRecord from './toNfcRecord.js';
18
+
19
+ const Ndef = {
20
+ TNF_WELL_KNOWN: 0x01,
21
+ RTD_TEXT: 'T',
22
+ RTD_URI: 'U',
23
+ text: { decodePayload: () => 'ASSET-0042' },
24
+ uri: { decodePayload: () => 'https://example.com/a/42' },
25
+ };
26
+
27
+ test('toNfcRecord decodes a text record from iOS type bytes', () => {
28
+ expect(toNfcRecord({ Ndef, record: { tnf: 1, type: [0x54], payload: [2, 101, 110] } })).toEqual({
29
+ type: 'text',
30
+ text: 'ASSET-0042',
31
+ payload: [2, 101, 110],
32
+ });
33
+ });
34
+
35
+ test('toNfcRecord decodes a URI record from an Android type string', () => {
36
+ expect(toNfcRecord({ Ndef, record: { tnf: 1, type: 'U', payload: [4] } })).toEqual({
37
+ type: 'uri',
38
+ uri: 'https://example.com/a/42',
39
+ payload: [4],
40
+ });
41
+ });
42
+
43
+ test('toNfcRecord keeps the raw payload of any other record', () => {
44
+ expect(toNfcRecord({ Ndef, record: { tnf: 2, type: 'text/plain', payload: [1, 2] } })).toEqual({
45
+ type: 'text/plain',
46
+ payload: [1, 2],
47
+ });
48
+ });
@@ -0,0 +1,63 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import reportShellError from '../../host/reportShellError.js';
18
+
19
+ // The most posts the backlog keeps; older ones are dropped first, so a phone left offline for days
20
+ // does not fill its store.
21
+ const MAX_ENTRIES = 500;
22
+
23
+ /**
24
+ * Background posts that could not be sent (no signal, no session), kept in the device store and
25
+ * sent on the next wake, oldest first, as { endpointId, payload }.
26
+ *
27
+ * @param {Object} params
28
+ * @param {string} params.key The device store key.
29
+ * @param {{ get, set }} params.kv The device store.
30
+ */
31
+ function createBacklog({ key, kv }) {
32
+ function read() {
33
+ return JSON.parse(kv.get(key) ?? '[]');
34
+ }
35
+ function write(entries) {
36
+ kv.set(key, JSON.stringify(entries.slice(-MAX_ENTRIES)));
37
+ }
38
+ return {
39
+ add(entry) {
40
+ write([...read(), entry]);
41
+ },
42
+ // Sends every kept post with `send`, stopping at the first failure (reported); the rest stay.
43
+ async flush(send) {
44
+ const entries = read();
45
+ let sent = 0;
46
+ for (const entry of entries) {
47
+ try {
48
+ await send(entry);
49
+ } catch (error) {
50
+ reportShellError({ error });
51
+ break;
52
+ }
53
+ sent += 1;
54
+ }
55
+ // Read again: a post added while these were sending stays behind them.
56
+ write(read().slice(sent));
57
+ return sent;
58
+ },
59
+ size: () => read().length,
60
+ };
61
+ }
62
+
63
+ export default createBacklog;
@@ -0,0 +1,47 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import createBacklog from './createBacklog.js';
18
+ import createNetworkError from '../../test/createNetworkError.js';
19
+
20
+ function memoryKv() {
21
+ const values = new Map();
22
+ return { get: (key) => values.get(key) ?? null, set: (key, value) => values.set(key, value) };
23
+ }
24
+
25
+ test('a backlog sends its posts oldest first and keeps the rest after a failure', async () => {
26
+ const backlog = createBacklog({ key: 'k', kv: memoryKv() });
27
+ backlog.add({ endpointId: 'a', payload: 1 });
28
+ backlog.add({ endpointId: 'a', payload: 2 });
29
+ backlog.add({ endpointId: 'a', payload: 3 });
30
+ const sent = [];
31
+ const count = await backlog.flush(async (entry) => {
32
+ if (entry.payload === 2) throw createNetworkError();
33
+ sent.push(entry.payload);
34
+ });
35
+ expect(count).toBe(1);
36
+ expect(sent).toEqual([1]);
37
+ expect(backlog.size()).toBe(2);
38
+ });
39
+
40
+ test('a post added while the backlog flushes is kept', async () => {
41
+ const backlog = createBacklog({ key: 'k', kv: memoryKv() });
42
+ backlog.add({ endpointId: 'a', payload: 1 });
43
+ await backlog.flush(async () => {
44
+ backlog.add({ endpointId: 'a', payload: 2 });
45
+ });
46
+ expect(backlog.size()).toBe(1);
47
+ });