@lowdefy/shell 3.23.3 → 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.f01501a75909f38ccc32.js +0 -1
  356. package/dist/shell/runtime.e50f6c3a5f7c74a0a717.js +0 -1
@@ -0,0 +1,36 @@
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 { useEffect } from 'react';
18
+ import { router } from 'expo-router';
19
+
20
+ /**
21
+ * Shows a tab's own page on its tab. A route that lands a tab's page on another tab's stack (a
22
+ * link the app is opened with, which the router matches in the first tab group) is closed, and
23
+ * the page's tab is selected, so the tab bar shows the tab of the page on screen.
24
+ *
25
+ * @param {Object} props
26
+ * @param {string} props.href The tab's router href (getTabHref).
27
+ */
28
+ function SwitchToTab({ href }) {
29
+ useEffect(() => {
30
+ if (router.canDismiss()) router.dismiss();
31
+ router.navigate(href);
32
+ }, [href]);
33
+ return null;
34
+ }
35
+
36
+ export default SwitchToTab;
@@ -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 React from 'react';
18
+ import { jest } from '@jest/globals';
19
+ import { render } from '@testing-library/react-native';
20
+
21
+ import { createRoot } from '../test/fixtures.js';
22
+ import PathRoute from '../../app/(t0,t1,t2,t3,t4)/[...path].js';
23
+
24
+ const mockRouter = { canDismiss: jest.fn(() => true), dismiss: jest.fn(), navigate: jest.fn() };
25
+ const mockParams = {};
26
+
27
+ jest.mock('expo-router', () => ({
28
+ get router() {
29
+ return mockRouter;
30
+ },
31
+ useLocalSearchParams: () => mockParams,
32
+ }));
33
+
34
+ const mockState = { root: null };
35
+ jest.mock('../ShellContext.js', () => ({
36
+ useSessionState: () => mockState,
37
+ }));
38
+
39
+ jest.mock('./PageScreen.js', () => {
40
+ const { createElement } = jest.requireActual('react');
41
+ return (props) => createElement('PageScreen', props);
42
+ });
43
+
44
+ beforeEach(() => {
45
+ mockState.root = createRoot();
46
+ Object.keys(mockParams).forEach((key) => delete mockParams[key]);
47
+ });
48
+
49
+ // A link the app is opened with, such as /jobs, is matched in the first tab group's stack.
50
+ test('a tab page opened on another tab stack is shown on its own tab', async () => {
51
+ Object.assign(mockParams, { path: ['jobs'], status: 'open' });
52
+ const view = await render(<PathRoute />);
53
+ expect(view.toJSON()).toBeNull();
54
+ expect(mockRouter.dismiss).toHaveBeenCalledTimes(1);
55
+ expect(mockRouter.navigate.mock.calls).toEqual([['/(t1)?status=open']]);
56
+ });
57
+
58
+ test('a page no tab shows is stacked on the tab it was opened on', async () => {
59
+ Object.assign(mockParams, { path: ['jobs', '12'] });
60
+ const view = await render(<PathRoute />);
61
+ expect(view.toJSON().props).toEqual({ path: 'jobs/12', query: '' });
62
+ expect(mockRouter.navigate).not.toHaveBeenCalled();
63
+ });
@@ -0,0 +1,72 @@
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 React, { useRef } from 'react';
18
+ import { PixelRatio, View } from 'react-native';
19
+ import { Icon } from '@lowdefy/client-native';
20
+
21
+ // A tab bar icon is 24 points; drawn at the screen's scale so it stays sharp.
22
+ const SIZE = 24;
23
+
24
+ const styles = {
25
+ hidden: { left: -1000, opacity: 0, position: 'absolute', top: -1000 },
26
+ };
27
+
28
+ function TabIconImage({ data, onImage }) {
29
+ const ref = useRef(null);
30
+ const drawnRef = useRef(false);
31
+ const scale = PixelRatio.get();
32
+ function draw() {
33
+ if (drawnRef.current) return;
34
+ drawnRef.current = true;
35
+ ref.current.toDataURL(
36
+ (base64) =>
37
+ onImage({ height: SIZE, scale, uri: `data:image/png;base64,${base64}`, width: SIZE }),
38
+ { height: SIZE * scale, width: SIZE * scale }
39
+ );
40
+ }
41
+ return (
42
+ <View onLayout={draw}>
43
+ <Icon color="#000000" data={data} ref={ref} size={SIZE} />
44
+ </View>
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Draws tab icons that have no SF Symbol (every tab off iOS) as images: a native tab bar takes an
50
+ * image source, not a view, so each Lucide icon is drawn once, out of sight, and read back as a
51
+ * PNG with react-native-svg's toDataURL. The image is black on transparent, and the tab bar tints
52
+ * it (selected and unselected).
53
+ *
54
+ * @param {Object} props
55
+ * @param {Object[]} props.items The tabs to draw: `{ pageId, iconData }`.
56
+ * @param {(pageId: string, source: Object) => void} props.onImage Called with each image source.
57
+ */
58
+ function TabIconImages({ items, onImage }) {
59
+ return (
60
+ <View pointerEvents="none" style={styles.hidden}>
61
+ {items.map((item) => (
62
+ <TabIconImage
63
+ data={item.iconData}
64
+ key={item.pageId}
65
+ onImage={(source) => onImage(item.pageId, source)}
66
+ />
67
+ ))}
68
+ </View>
69
+ );
70
+ }
71
+
72
+ export default TabIconImages;
@@ -0,0 +1,40 @@
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 React from 'react';
18
+ import { Linking, Platform } from 'react-native';
19
+
20
+ import MessageScreen from './MessageScreen.js';
21
+
22
+ // Shown for a page this shell cannot render (it lacks a type the page needs), and for every page
23
+ // when the app requires a newer shell (minShellVersion). The store link comes from shell.json.
24
+ function UpdateScreen({ shell }) {
25
+ const storeUrl = shell.store?.[Platform.OS];
26
+ return (
27
+ <MessageScreen
28
+ action={
29
+ storeUrl
30
+ ? { title: 'Update', onPress: () => Linking.openURL(storeUrl).catch(() => {}) }
31
+ : null
32
+ }
33
+ body="This page needs a newer version of the app."
34
+ testID="shell-update"
35
+ title="Update the app to open this page"
36
+ />
37
+ );
38
+ }
39
+
40
+ export default UpdateScreen;
@@ -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
+ /**
18
+ * The React Navigation theme the shell's navigators draw with, from the app's theme tokens: the
19
+ * header and back button take the tint, screens the grouped background, and titles the label
20
+ * colour. On iOS 26 the header and tab bar are the system's glass bars, which take their mode from
21
+ * the window (ThemeProvider sets it) and their tint from here.
22
+ *
23
+ * @param {Object} params
24
+ * @param {Object} params.base React Navigation's DefaultTheme or DarkTheme, for its fonts.
25
+ * @param {boolean} params.isDark
26
+ * @param {Object} params.tokens The theme tokens of the mode showing.
27
+ * @returns {{ dark: boolean, colors: Object, fonts: Object }}
28
+ */
29
+ function getNavigationTheme({ base, isDark, tokens }) {
30
+ return {
31
+ ...base,
32
+ dark: isDark,
33
+ colors: {
34
+ primary: tokens.colorTint,
35
+ background: tokens.colorGroupedBackground,
36
+ card: tokens.colorGroupedBackground,
37
+ text: tokens.colorLabel,
38
+ border: tokens.colorSeparator,
39
+ notification: tokens.colorError,
40
+ },
41
+ };
42
+ }
43
+
44
+ export default getNavigationTheme;
@@ -0,0 +1,41 @@
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 getNavigationTheme from './getNavigationTheme.js';
18
+
19
+ const tokens = {
20
+ colorError: '#ff4d4f',
21
+ colorGroupedBackground: '#F2F2F7',
22
+ colorLabel: '#000000',
23
+ colorSeparator: 'rgba(60,60,67,0.29)',
24
+ colorTint: '#6366f1',
25
+ };
26
+
27
+ test('getNavigationTheme draws the navigators in the app tint, surfaces and label colour', () => {
28
+ const base = { dark: false, colors: {}, fonts: { regular: { fontFamily: 'System' } } };
29
+ expect(getNavigationTheme({ base, isDark: true, tokens })).toEqual({
30
+ dark: true,
31
+ colors: {
32
+ primary: '#6366f1',
33
+ background: '#F2F2F7',
34
+ card: '#F2F2F7',
35
+ text: '#000000',
36
+ border: 'rgba(60,60,67,0.29)',
37
+ notification: '#ff4d4f',
38
+ },
39
+ fonts: base.fonts,
40
+ });
41
+ });
@@ -0,0 +1,50 @@
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 { StyleSheet } from 'react-native';
18
+
19
+ const styles = StyleSheet.create({
20
+ centered: {
21
+ flex: 1,
22
+ alignItems: 'center',
23
+ justifyContent: 'center',
24
+ padding: 24,
25
+ gap: 16,
26
+ },
27
+ title: {
28
+ fontSize: 22,
29
+ fontWeight: '700',
30
+ lineHeight: 28,
31
+ textAlign: 'center',
32
+ },
33
+ body: {
34
+ fontSize: 17,
35
+ lineHeight: 22,
36
+ textAlign: 'center',
37
+ },
38
+ button: {
39
+ paddingHorizontal: 20,
40
+ paddingVertical: 12,
41
+ borderRadius: 12,
42
+ },
43
+ buttonText: {
44
+ color: '#FFFFFF',
45
+ fontSize: 17,
46
+ fontWeight: '600',
47
+ },
48
+ });
49
+
50
+ export default styles;
@@ -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 { useContext } from 'react';
18
+ import { useColorScheme } from 'react-native';
19
+ import { iosColors, ThemeContext } from '@lowdefy/client-native';
20
+
21
+ // The iOS default tint, for the screens shown before the app's theme is known.
22
+ const SYSTEM_TINT = '#007AFF';
23
+
24
+ /**
25
+ * The colours the shell's own screens (loading, messages, sign in, lock) draw with: the app's
26
+ * theme tokens inside its ThemeProvider. A shell opens, signs in and locks before the app's root
27
+ * config, and so its theme, has arrived; those screens take the device's mode in the iOS
28
+ * semantic colours and tint.
29
+ *
30
+ * @returns {Object} Tokens with colorGroupedBackground, colorCell, colorLabel,
31
+ * colorSecondaryLabel, colorSeparator, colorTint and colorError.
32
+ */
33
+ function useSurfaceTokens() {
34
+ const theme = useContext(ThemeContext);
35
+ const systemScheme = useColorScheme();
36
+ if (theme !== null) {
37
+ return theme.tokens;
38
+ }
39
+ const isDark = systemScheme === 'dark';
40
+ return {
41
+ ...iosColors[isDark ? 'dark' : 'light'],
42
+ colorError: isDark ? '#FF453A' : '#FF3B30',
43
+ colorTint: SYSTEM_TINT,
44
+ };
45
+ }
46
+
47
+ export default useSurfaceTokens;
@@ -0,0 +1,34 @@
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 { LowdefyInternalError } from '@lowdefy/errors';
18
+
19
+ // The app server's URL for the shell's environment (shell.json `environment`), without a trailing
20
+ // slash. Each app's urls come from its environments; Lowdefy Dev reads lowdefy dev's. checkShellConfig
21
+ // refuses a shell.json with no url for its environment, so a missing one is a Lowdefy wiring bug.
22
+ function getAppApiBase({ app, environment }) {
23
+ const url = app.urls?.[environment];
24
+ if (typeof url !== 'string' || url.length === 0) {
25
+ throw new LowdefyInternalError(
26
+ `Shell app "${app.id}" has no url for environment "${environment}". Received ${JSON.stringify(
27
+ app.urls
28
+ )}.`
29
+ );
30
+ }
31
+ return url.replace(/\/+$/, '');
32
+ }
33
+
34
+ export default getAppApiBase;
@@ -0,0 +1,37 @@
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 Constants from 'expo-constants';
18
+
19
+ import typeManifest from '../plugins/typeManifest.js';
20
+ import shellVersion from '../version/shellVersion.js';
21
+
22
+ /**
23
+ * The shell's config: shell.json as app.config.ts built it into the app (`id`, `name`,
24
+ * `profile`, `scheme`, `environment`, `store` and the one app it opens,
25
+ * `app: { id, name, urls: { <env>: url } }`, checked by checkShellConfig.js), with the `version`
26
+ * and `typeManifest` of the JavaScript that is running, and `easProjectId`. An embedded launch reads the Expo config
27
+ * the native build embedded, so both come from the bundle instead: a type added to the plugin maps
28
+ * reaches the shell from Metro or by an over-the-air update, and a shell whose new JavaScript
29
+ * meets an app's `minShellVersion` opens its pages.
30
+ */
31
+ function getShellConfig() {
32
+ const { eas, shell } = Constants.expoConfig.extra;
33
+ // The EAS project, for an Expo push token; null for a shell without one (Lowdefy Dev).
34
+ return { ...shell, easProjectId: eas?.projectId ?? null, typeManifest, version: shellVersion };
35
+ }
36
+
37
+ export default getShellConfig;
@@ -0,0 +1,80 @@
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 getPageView from '../session/getPageView.js';
18
+ import types from '../plugins/types.js';
19
+ import typeManifest from '../plugins/typeManifest.js';
20
+ import shellVersion from '../version/shellVersion.js';
21
+ import getShellConfig from './getShellConfig.js';
22
+
23
+ // The Expo config a native build embedded: its shell version is older than the JavaScript's, and its
24
+ // type manifest predates the native Markdown block.
25
+ jest.mock('expo-constants', () => ({
26
+ expoConfig: {
27
+ extra: {
28
+ shell: {
29
+ scheme: 'lowdefy-dev',
30
+ version: '0.1.0',
31
+ typeManifest: {
32
+ actions: {},
33
+ blocks: { Screen: { package: '@lowdefy/blocks-native', version: 6 } },
34
+ operators: {},
35
+ },
36
+ },
37
+ },
38
+ },
39
+ }));
40
+
41
+ test('getShellConfig reads the type manifest from the bundle, not the embedded Expo config', () => {
42
+ const shell = getShellConfig();
43
+ expect(shell.typeManifest).toBe(typeManifest);
44
+ expect(shell.scheme).toBe('lowdefy-dev');
45
+ });
46
+
47
+ test('getShellConfig reads the shell version from the bundle, not the embedded Expo config', () => {
48
+ expect(getShellConfig().version).toBe(shellVersion);
49
+ });
50
+
51
+ test('an app whose minShellVersion the bundled JavaScript meets opens on a shell built before it', () => {
52
+ const root = {
53
+ minShellVersion: shellVersion,
54
+ pageIndex: [{ path: 'home', pageId: 'home', hash: 'h-home-1', typesKey: 'basic' }],
55
+ typeSets: {
56
+ basic: { blocks: [{ type: 'Screen', ...typeManifest.blocks.Screen }] },
57
+ },
58
+ };
59
+ const view = getPageView({ path: 'home', shell: getShellConfig(), state: { pages: {}, root } });
60
+ expect(view.view).toBe('loading');
61
+ });
62
+
63
+ test('a block type in the plugin maps opens on a shell whose embedded Expo config lacks it', () => {
64
+ expect(types.blocks.Markdown).not.toBeUndefined();
65
+ const { version } = typeManifest.blocks.Markdown;
66
+ const root = {
67
+ minShellVersion: null,
68
+ pageIndex: [{ path: 'notes', pageId: 'notes', hash: 'h-notes-1', typesKey: 'markdown' }],
69
+ typeSets: {
70
+ markdown: {
71
+ blocks: [
72
+ { type: 'Screen', package: '@lowdefy/blocks-native', version },
73
+ { type: 'Markdown', package: '@lowdefy/blocks-native', version },
74
+ ],
75
+ },
76
+ },
77
+ };
78
+ const view = getPageView({ path: 'notes', shell: getShellConfig(), state: { pages: {}, root } });
79
+ expect(view.view).toBe('loading');
80
+ });
@@ -0,0 +1,126 @@
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 { fetch as expoFetch } from 'expo/fetch';
18
+ import * as Crypto from 'expo-crypto';
19
+ import { openDatabaseSync } from 'expo-sqlite';
20
+
21
+ import createAppAuth from './auth/createAppAuth.js';
22
+ import createAppEventTrigger from './native/createAppEventTrigger.js';
23
+ import createAppSession from './session/createAppSession.js';
24
+ import createDeviceStore from './host/createDeviceStore.js';
25
+ import createRouterAdapter from './host/createRouterAdapter.js';
26
+ import createShellFetch from './host/createShellFetch.js';
27
+ import createShellHost from './host/createShellHost.js';
28
+ import createRequestQueue from './queue/createRequestQueue.js';
29
+ import createShellLocale from './locale/createShellLocale.js';
30
+ import createSqliteCacheStore from './cache/createSqliteCacheStore.js';
31
+ import createSqliteQueueStore from './queue/createSqliteQueueStore.js';
32
+ import getAppApiBase from './config/getAppApiBase.js';
33
+ import getDeviceLocales from './locale/getDeviceLocales.js';
34
+ import networkStatus from './host/networkStatus.js';
35
+ import withNetworkErrors from './host/withNetworkErrors.js';
36
+ import withoutCookies from './auth/withoutCookies.js';
37
+ import withShellHeader from './host/withShellHeader.js';
38
+
39
+ let db = null;
40
+
41
+ function getDatabase() {
42
+ if (db === null) {
43
+ db = openDatabaseSync('lowdefy-shell.db');
44
+ }
45
+ return db;
46
+ }
47
+
48
+ // Everything the shell's one app (shell.app) needs: its auth, session, core host, locale, request
49
+ // queue and the trigger of its app events.
50
+ // auth.start reads the session token and starts the session; onSignedOut is called once a sign-out
51
+ // removed the person's data, to open the app afresh.
52
+ async function createShellApp({ onSignedOut, shell }) {
53
+ const { app } = shell;
54
+ const apiBase = getAppApiBase({ app, environment: shell.environment });
55
+ const cacheStore = createSqliteCacheStore({ db: getDatabase() });
56
+ const queueStore = createSqliteQueueStore({ db: getDatabase() });
57
+ const kv = createDeviceStore();
58
+ // Lowdefy Dev sends a paired dev server's token on every call (src/dev, shell.transport).
59
+ const transport = shell.transport ?? {
60
+ fetch: globalThis.fetch,
61
+ WebSocket: globalThis.WebSocket,
62
+ wrapFetch: (baseFetch) => baseFetch,
63
+ };
64
+ const fetch = withShellHeader({
65
+ fetch: withoutCookies(withNetworkErrors(transport.fetch)),
66
+ shellVersion: shell.version,
67
+ });
68
+ // React Native's fetch buffers a response body; a block that reads its answer as a stream (the
69
+ // AgentChat block's chat stream) calls through expo/fetch, sent the same way.
70
+ const streamFetch = withShellHeader({
71
+ fetch: withoutCookies(withNetworkErrors(transport.wrapFetch(expoFetch))),
72
+ shellVersion: shell.version,
73
+ });
74
+
75
+ // Sign-in, unlock and sign-out (src/auth): the token store, the fetches that watch it and the
76
+ // stores a signed-out session can no longer write to.
77
+ const auth = createAppAuth({
78
+ apiBase,
79
+ app,
80
+ cacheStore,
81
+ fetch,
82
+ kv,
83
+ onSignedOut,
84
+ queueStore,
85
+ shell,
86
+ streamFetch,
87
+ });
88
+
89
+ const session = createAppSession({
90
+ apiBase,
91
+ app,
92
+ cacheStore: auth.sessionStores.cacheStore,
93
+ fetch: auth.fetch,
94
+ kv: auth.sessionStores.kv,
95
+ tokenStore: auth.tokenStore,
96
+ });
97
+ const shellFetch = createShellFetch({ fetch: auth.fetch, session });
98
+ const triggerAppEvent = createAppEventTrigger({ session });
99
+ const requestQueue = createRequestQueue({
100
+ appId: app.id,
101
+ fetch: shellFetch,
102
+ networkStatus,
103
+ newId: Crypto.randomUUID,
104
+ queueStore,
105
+ tokenStore: auth.tokenStore,
106
+ triggerAppEvent,
107
+ });
108
+ const host = createShellHost({
109
+ fetch: shellFetch,
110
+ requestQueue,
111
+ router: createRouterAdapter({ session }),
112
+ session,
113
+ streamFetch: auth.streamFetch,
114
+ tokenStore: auth.tokenStore,
115
+ WebSocket: transport.WebSocket,
116
+ });
117
+ const locale = createShellLocale({
118
+ getDeviceLocales,
119
+ globals: host.globals,
120
+ session,
121
+ storage: host.storage,
122
+ });
123
+ return { apiBase, auth, host, locale, requestQueue, session, triggerAppEvent };
124
+ }
125
+
126
+ export default createShellApp;