@lowdefy/shell 0.0.0-experimental-20261019052246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +308 -0
- package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
- package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
- package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
- package/app/+native-intent.js +30 -0
- package/app/_layout.js +33 -0
- package/app.config.ts +168 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +145 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native-fingerprint.json +12 -0
- package/package.json +130 -0
- package/profiles.js +274 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +105 -0
- package/scripts/checkReleaseBundle.mjs +358 -0
- package/scripts/createDayjsLocales.mjs +67 -0
- package/scripts/createShellAssets.mjs +54 -0
- package/scripts/createShellVersion.mjs +83 -0
- package/scripts/createTypeManifest.mjs +205 -0
- package/shell.dev.json +11 -0
- package/shellVariant.js +111 -0
- package/src/ShellContext.js +32 -0
- package/src/actions/CopyToClipboard.js +29 -0
- package/src/actions/CopyToClipboard.test.js +33 -0
- package/src/actions/GeolocationCurrentPosition.js +55 -0
- package/src/actions/GeolocationCurrentPosition.test.js +66 -0
- package/src/actions/GetLocalStorage.js +36 -0
- package/src/actions/RemoveLocalStorage.js +31 -0
- package/src/actions/ScrollTo.js +66 -0
- package/src/actions/ScrollTo.test.js +83 -0
- package/src/actions/SetFocus.js +33 -0
- package/src/actions/SetFocus.test.js +49 -0
- package/src/actions/SetLocalStorage.js +34 -0
- package/src/actions/localStorage.test.js +68 -0
- package/src/actions/measureInScrollView.js +36 -0
- package/src/actions/nativeActions.js +41 -0
- package/src/auth/AuthButton.js +44 -0
- package/src/auth/AuthGate.js +57 -0
- package/src/auth/AuthLink.js +43 -0
- package/src/auth/LockScreen.js +67 -0
- package/src/auth/LockScreen.test.js +37 -0
- package/src/auth/SignInScreen.js +256 -0
- package/src/auth/SignInScreen.test.js +182 -0
- package/src/auth/createAppAuth.js +306 -0
- package/src/auth/createAppAuth.test.js +414 -0
- package/src/auth/createAuthApi.js +99 -0
- package/src/auth/createAuthApi.test.js +135 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createSessionStores.js +45 -0
- package/src/auth/createTokenStore.js +103 -0
- package/src/auth/createTokenStore.test.js +116 -0
- package/src/auth/getAuthSettings.js +24 -0
- package/src/auth/getAuthView.js +39 -0
- package/src/auth/getAuthView.test.js +52 -0
- package/src/auth/getSheetReturnUrl.js +35 -0
- package/src/auth/getTokenStoreOptions.js +46 -0
- package/src/auth/isBackgroundShell.js +26 -0
- package/src/auth/isSheetReturnLink.js +35 -0
- package/src/auth/parseQuery.js +39 -0
- package/src/auth/purgeAppData.js +45 -0
- package/src/auth/shouldLock.js +26 -0
- package/src/auth/signInWithSheet.js +97 -0
- package/src/auth/signInWithSheet.test.js +205 -0
- package/src/auth/styles.js +78 -0
- package/src/auth/unregisterPushToken.js +21 -0
- package/src/auth/usesBiometricUnlock.js +23 -0
- package/src/auth/watchSession.js +47 -0
- package/src/auth/withoutCookies.js +26 -0
- package/src/cache/createSqliteCacheStore.js +113 -0
- package/src/cache/createSqliteCacheStore.test.js +93 -0
- package/src/cache/getCacheKey.js +25 -0
- package/src/cache/getLastCacheKeyName.js +22 -0
- package/src/components/AppProvider.js +90 -0
- package/src/components/Frame.js +116 -0
- package/src/components/Frame.test.js +101 -0
- package/src/components/LoadingScreen.js +35 -0
- package/src/components/MenuButton.js +108 -0
- package/src/components/MenuButton.test.js +51 -0
- package/src/components/MessageScreen.js +51 -0
- package/src/components/NavigationTheme.js +33 -0
- package/src/components/NoConnectionBanner.js +51 -0
- package/src/components/NoConnectionBanner.test.js +73 -0
- package/src/components/OtherPageScreen.js +56 -0
- package/src/components/PageScreen.js +244 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/ShellRoot.js +34 -0
- package/src/components/ShellRoot.test.js +62 -0
- package/src/components/ShellThemeProvider.js +45 -0
- package/src/components/SwitchToTab.js +36 -0
- package/src/components/SwitchToTab.test.js +63 -0
- package/src/components/TabIconImages.js +72 -0
- package/src/components/UpdateScreen.js +36 -0
- package/src/components/getNavigationTheme.js +44 -0
- package/src/components/getNavigationTheme.test.js +41 -0
- package/src/components/styles.js +50 -0
- package/src/components/useSurfaceTokens.js +47 -0
- package/src/config/getAppApiBase.js +31 -0
- package/src/config/getShellConfig.js +35 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +105 -0
- package/src/dev/DevShell.js +228 -0
- package/src/dev/DevShell.test.js +233 -0
- package/src/dev/connect/ConfirmSheet.js +71 -0
- package/src/dev/connect/ConnectScreen.js +117 -0
- package/src/dev/connect/QrScanner.js +89 -0
- package/src/dev/connection/createDevShellConfig.js +47 -0
- package/src/dev/connection/createDevTransport.js +51 -0
- package/src/dev/connection/createDevTransport.test.js +87 -0
- package/src/dev/connection/createServerList.js +48 -0
- package/src/dev/connection/isLoopbackHost.js +25 -0
- package/src/dev/connection/pairedShellApp.test.js +104 -0
- package/src/dev/connection/parseDevLink.js +66 -0
- package/src/dev/connection/parseDevLink.test.js +97 -0
- package/src/dev/connection/parseQuery.js +33 -0
- package/src/dev/connection/parseServerInput.js +40 -0
- package/src/dev/connection/parseServerUrl.js +45 -0
- package/src/dev/devStyles.js +97 -0
- package/src/dev/index.js +21 -0
- package/src/dev/inject/createDeviceInjections.js +185 -0
- package/src/dev/inject/createDeviceInjections.test.js +208 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/menu/BlockGallery.js +33 -0
- package/src/dev/menu/DevMenu.js +121 -0
- package/src/dev/menu/createLogBuffer.js +83 -0
- package/src/dev/menu/watchShake.js +52 -0
- package/src/dev/menu/watchShake.test.js +61 -0
- package/src/dev/recorder/createNativeRecorder.js +204 -0
- package/src/dev/recorder/createNativeRecorder.test.js +307 -0
- package/src/dev/redirectDevLink.js +30 -0
- package/src/dev/redirectDevLink.test.js +49 -0
- package/src/dev/steps/createStepRunner.js +184 -0
- package/src/dev/steps/createStepRunner.test.js +262 -0
- package/src/dev/steps/findBlock.js +78 -0
- package/src/dev/steps/isContextSettled.js +37 -0
- package/src/dev/steps/waitForSettle.js +39 -0
- package/src/dev/steps/waitUntil.js +40 -0
- package/src/dev/tab/DevTabClient.js +97 -0
- package/src/dev/tab/answerEval.js +39 -0
- package/src/dev/tab/answerInspect.js +45 -0
- package/src/dev/tab/answerLoadState.js +50 -0
- package/src/dev/tab/createDevTabClient.js +277 -0
- package/src/dev/tab/createDevTabClient.test.js +314 -0
- package/src/dev/tab/devClientMarker.js +22 -0
- package/src/dev/tab/devClientMarker.test.js +23 -0
- package/src/dev/tab/findPageContext.js +43 -0
- package/src/dev/tab/getShownPage.js +42 -0
- package/src/dev/tab/readEventStream.js +68 -0
- package/src/dev/tab/reloadApp.js +42 -0
- package/src/dev/tab/reloadApp.test.js +115 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/host/createDeviceStore.js +32 -0
- package/src/host/createMemoryStore.js +32 -0
- package/src/host/createNetworkStatus.js +94 -0
- package/src/host/createNetworkStatus.test.js +94 -0
- package/src/host/createRouterAdapter.js +33 -0
- package/src/host/createShellFetch.js +60 -0
- package/src/host/createShellFetch.test.js +150 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +73 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +26 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/withShellHeader.js +25 -0
- package/src/host/withShellHeader.test.js +28 -0
- package/src/js/evaluateJsEntries.js +43 -0
- package/src/js/evaluateJsEntries.test.js +48 -0
- package/src/locale/createShellLocale.js +112 -0
- package/src/locale/createShellLocale.test.js +74 -0
- package/src/locale/dayjsLocales.js +165 -0
- package/src/locale/getDeviceLocales.js +23 -0
- package/src/locale/loadDayjsLocale.js +32 -0
- package/src/locale/resolveActiveLocale.js +46 -0
- package/src/locale/resolveActiveLocale.test.js +40 -0
- package/src/navigation/SlotContext.js +24 -0
- package/src/navigation/frameSlots.js +27 -0
- package/src/navigation/getFrame.js +30 -0
- package/src/navigation/getInitialScreenOptions.js +63 -0
- package/src/navigation/getInitialScreenOptions.test.js +66 -0
- package/src/navigation/getPresentation.js +28 -0
- package/src/navigation/getSlotRootPath.js +27 -0
- package/src/navigation/getStackOptions.js +185 -0
- package/src/navigation/getTabHref.js +45 -0
- package/src/navigation/getTabIcons.js +43 -0
- package/src/navigation/isPageUnderHeader.js +46 -0
- package/src/navigation/navigation.test.js +342 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +14 -0
- package/src/plugins/typeManifest.js +649 -0
- package/src/plugins/types.js +61 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/session/createAppSession.js +318 -0
- package/src/session/createAppSession.test.js +359 -0
- package/src/session/getPageView.js +59 -0
- package/src/session/getPageView.test.js +137 -0
- package/src/sync/encodePagePath.js +27 -0
- package/src/sync/fetchPage.js +49 -0
- package/src/sync/fetchRoot.js +38 -0
- package/src/sync/fetchUser.js +28 -0
- package/src/sync/runLimited.js +36 -0
- package/src/sync/toRootConfig.js +35 -0
- package/src/test/createFakeNetwork.js +40 -0
- package/src/test/createFakeSecureStore.js +40 -0
- package/src/test/createFakeServer.js +98 -0
- package/src/test/createMemoryCacheStore.js +59 -0
- package/src/test/expoImage.js +38 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoUi.js +55 -0
- package/src/test/expoUiBottomSheet.js +64 -0
- package/src/test/expoUiDateTimePicker.js +26 -0
- package/src/test/expoUiMenu.js +46 -0
- package/src/test/expoUiSegmentedControl.js +47 -0
- package/src/test/expoUiSwiftUi.js +62 -0
- package/src/test/expoUiSwiftUiModifiers.js +23 -0
- package/src/test/expoWebBrowser.js +23 -0
- package/src/test/fixtures.js +98 -0
- package/src/test/renderPageScreen.js +109 -0
- package/src/types/checkTypeSet.js +39 -0
- package/src/types/collectPageTypes.js +49 -0
- package/src/types/collectPageTypes.test.js +90 -0
- package/src/types/findMissingPageTypes.js +40 -0
- package/src/version/compareVersions.js +38 -0
- package/src/version/isShellTooOld.js +30 -0
- package/src/version/shellVersion.js +21 -0
- package/src/version/shellVersion.test.js +32 -0
- package/withExcludedNativeModules.js +93 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# @lowdefy/shell
|
|
2
|
+
|
|
3
|
+
The Expo app every Lowdefy phone app is built from (Expo SDK 58, React Native 0.88, Hermes, New
|
|
4
|
+
Architecture). It carries code only: the Lowdefy engine, `@lowdefy/client-native` and the shell's
|
|
5
|
+
plugin maps. Pages, menus and theme come from the app's Lowdefy server as data. No `ios/` or
|
|
6
|
+
`android/` folder is committed: Expo writes them at build time.
|
|
7
|
+
|
|
8
|
+
## Config
|
|
9
|
+
|
|
10
|
+
`app.config.ts` reads `shell.json` from the path in `LOWDEFY_SHELL_CONFIG`, and falls back to
|
|
11
|
+
`shell.dev.json` (Lowdefy Dev):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"id": "field",
|
|
16
|
+
"name": "Field",
|
|
17
|
+
"profile": "field",
|
|
18
|
+
"version": "1.4.2",
|
|
19
|
+
"icon": "./icon.png",
|
|
20
|
+
"color": "#0B5FFF",
|
|
21
|
+
"scheme": "field",
|
|
22
|
+
"environment": "production",
|
|
23
|
+
"eas": { "owner": "example", "projectId": "2b6f…" },
|
|
24
|
+
"ios": { "bundleIdentifier": "com.example.field" },
|
|
25
|
+
"android": { "package": "com.example.field" },
|
|
26
|
+
"store": { "ios": "https://apps.apple.com/...", "android": "https://play.google.com/..." },
|
|
27
|
+
"app": { "id": "field", "name": "Field", "urls": { "production": "https://field.example.com" } }
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A shell opens exactly one app, named by `app`. Every app's `_js` runs in the shell's one JavaScript
|
|
32
|
+
realm, which cannot be sandboxed, so a second app in the same shell could reach the first one's
|
|
33
|
+
session token, storage and cache. `checkShellConfig.js` refuses a `shell.json` that lists `apps`,
|
|
34
|
+
names more than one app, or has no url for the shell's `environment`; `app.config.ts` runs it, so
|
|
35
|
+
`expo prebuild` and `expo export` fail on such a config.
|
|
36
|
+
|
|
37
|
+
`profile` names the shell's capability profile, one row of the table in `profiles.js`: `basic`,
|
|
38
|
+
`field`, `field-nfc`, `field-tracking`, and `dev` and `development` for Lowdefy Dev and development
|
|
39
|
+
builds. Each lists the native modules, config plugins, permissions, JavaScript entry modules,
|
|
40
|
+
required-reason APIs and collected data it carries, and may extend another (`field` is `basic` and
|
|
41
|
+
the camera). `basic`, and so every profile, carries on-device dictation (`expo-speech-recognition`)
|
|
42
|
+
with its microphone and speech purpose texts; `expo-camera` is given the same microphone text, so
|
|
43
|
+
plugin order cannot change it. `resolveProfile(name)` flattens one, and throws on a name the table
|
|
44
|
+
lacks. A shell
|
|
45
|
+
config without `profile` is `basic`, or `dev` for Lowdefy Dev. `app.config.ts` applies only the
|
|
46
|
+
profile's config plugins, and sets `capabilities` from it when `shell.json` has none.
|
|
47
|
+
|
|
48
|
+
The shell's version and type manifest are not in the Expo config: they are
|
|
49
|
+
`src/version/shellVersion.js` and `src/plugins/typeManifest.js`, part of the JavaScript bundle, so
|
|
50
|
+
they match the code that runs. An embedded launch reads the Expo config of the native build, so a
|
|
51
|
+
version there would keep a debug shell on Metro behind an app's `minShellVersion`, and a manifest
|
|
52
|
+
there would hide a type added since. The manifest lists every block, action and operator type the
|
|
53
|
+
shell registers (`src/plugins/types.js`, listed in
|
|
54
|
+
`src/plugins/shellPlugins.json`), with its plugin package and that package's major version.
|
|
55
|
+
`pnpm --filter=@lowdefy/shell type-manifest` rewrites it (`scripts/createTypeManifest.mjs`) after
|
|
56
|
+
a change to the plugin maps, `src/plugins/types.test.js` fails when it is stale, and
|
|
57
|
+
`pnpm release:version` rewrites it after the version bump.
|
|
58
|
+
|
|
59
|
+
## Shell plugins
|
|
60
|
+
|
|
61
|
+
`plugins` in `shell.json` lists npm packages that add native code on top of the shell's profile:
|
|
62
|
+
`[{ name, version, typePrefix?, options? }]`, written by `lowdefy mobile config` from
|
|
63
|
+
`mobile.shells[].plugins`, which also installs them in the shell project. Each is a Lowdefy shell
|
|
64
|
+
plugin; `resolveShellPlugins.js` describes the package contract:
|
|
65
|
+
|
|
66
|
+
- `./shell` (CommonJS): a row shaped like one of `profiles.js` (`capabilities`, `modules`,
|
|
67
|
+
`plugins`, `permissions`, `privacyReasons`, `dataCollected`). `plugins` may be a function of the
|
|
68
|
+
plugin's `options`. `app.config.ts` loads it (`resolveShellPlugins.js`) and folds it into the
|
|
69
|
+
resolved profile (`applyShellPlugins.js`), so its config plugins, permissions, privacy reasons and
|
|
70
|
+
data join the profile's. Its modules are never excluded.
|
|
71
|
+
- `./native` (optional): named exports `blocks` and `actions`, `{ [typeName]: implementation }`.
|
|
72
|
+
The shell ships `src/plugins/appTypes.js` empty; `config` rewrites it in the copy to import each
|
|
73
|
+
plugin's `./native` with its `typePrefix` (`src/plugins/prefixTypes.js`), and `types.js`
|
|
74
|
+
registers them after the built-in types.
|
|
75
|
+
- `./types` (optional): `native: { blocks, actions, blockMetas, icons }`.
|
|
76
|
+
`scripts/createTypeManifest.mjs` adds `native.blocks` and `native.actions`, prefixed, to the type
|
|
77
|
+
manifest with the plugin's major version, and fails when two packages give one type name.
|
|
78
|
+
`config` runs it with `--write` in the copy, so the bundled `src/plugins/typeManifest.js` lists
|
|
79
|
+
the plugins' types.
|
|
80
|
+
|
|
81
|
+
`checkShellPlugins.js` refuses a plugin that links a module `@lowdefy/shell` carries itself
|
|
82
|
+
(`expo-camera`) or asks for a permission some profile carries and the shell's leaves out (the camera
|
|
83
|
+
on `basic`), naming the profile to pick. `config` runs it from the copied shell; `app.config.ts`
|
|
84
|
+
runs it again at prebuild. `fixtures/printer-plugin` is the test plugin.
|
|
85
|
+
|
|
86
|
+
## Store uploads
|
|
87
|
+
|
|
88
|
+
`app.config.ts` sets what App Store Connect and Google Play would otherwise ask a person on upload:
|
|
89
|
+
|
|
90
|
+
- `ios.config.usesNonExemptEncryption: false`: the shell uses only the OS's encryption.
|
|
91
|
+
- `ios.privacyManifests`: the required-reason APIs of the profile's modules (`privacyReasons` in
|
|
92
|
+
`profiles.js`, where `basic` carries React Native's and expo-sqlite's), each listed once
|
|
93
|
+
(`getPrivacyManifest.js`).
|
|
94
|
+
- From `shell.json` `icon` (a 1024×1024 PNG, relative to `shell.json`) and `color`: the app icon,
|
|
95
|
+
the `expo-splash-screen` image on `color`, and the Android adaptive icon on `color`. Its
|
|
96
|
+
foreground, the icon scaled into the centre 66% of a transparent canvas, is written beside
|
|
97
|
+
`shell.json` as `adaptive-icon-<hash>.png` (`scripts/createShellAssets.mjs`) once per icon.
|
|
98
|
+
Without `icon` (Lowdefy Dev) Expo's defaults stay.
|
|
99
|
+
|
|
100
|
+
## Versions
|
|
101
|
+
|
|
102
|
+
- `version` in `shell.json` is the store version (`major.minor.patch`), the app config's `version`.
|
|
103
|
+
Lowdefy Dev has none and builds as `0.0.0`.
|
|
104
|
+
- The shell version is the `@lowdefy/shell` package version, which the runtime sends as
|
|
105
|
+
`x-lowdefy-shell` and compares with `mobile.minShellVersion`. It is `src/version/shellVersion.js`,
|
|
106
|
+
which `pnpm --filter=@lowdefy/shell shell-version` rewrites (`scripts/createShellVersion.mjs`) and
|
|
107
|
+
`pnpm release:version` rewrites after the version bump; `src/version/shellVersion.test.js` fails
|
|
108
|
+
when it is stale.
|
|
109
|
+
- `runtimeVersion` is `<nativeVersion>-<profile>`, with `-<hash>` of the shell's `plugins` when it
|
|
110
|
+
lists any (`runtimeVersion.js`, a pure function `lowdefy mobile` reuses). Each plugin is keyed on
|
|
111
|
+
`name@version` and its `options` as JSON with keys sorted, so a new plugin version or option is a
|
|
112
|
+
new runtime version and a native build. An over-the-air update reaches only binaries with the same runtime version. Shell
|
|
113
|
+
identity is not part of it, so every shell on one `nativeVersion` and profile shares it.
|
|
114
|
+
- `nativeVersion` in `package.json` is bumped only when the shell's native dependencies or config
|
|
115
|
+
plugins change. `pnpm check:native-version` (run by the package's tests) computes Expo's
|
|
116
|
+
fingerprint of the shell's native inputs for every profile and fails when it differs from the one
|
|
117
|
+
`native-fingerprint.json` records for the current `nativeVersion`. After a bump,
|
|
118
|
+
`pnpm check:native-version --write` records the new one.
|
|
119
|
+
- With `eas`, the app config sets `owner`, `updates.url` (`https://u.expo.dev/<projectId>`) and
|
|
120
|
+
`extra.eas.projectId`.
|
|
121
|
+
|
|
122
|
+
## Boot, cache and sync
|
|
123
|
+
|
|
124
|
+
- The shell opens its one app at once. There is no app picker.
|
|
125
|
+
- The last root config and pages the app showed are read from SQLite and rendered at once. The
|
|
126
|
+
cache is keyed by app, caller (person and organisation) and roles, from the server's
|
|
127
|
+
`x-lowdefy-caller` stamp.
|
|
128
|
+
- In the background the shell fetches `GET /api/mobile/root` with `If-None-Match`, then every page
|
|
129
|
+
whose index hash changed, 4 at a time. Dynamic pages, and dev pages (no hash), are fetched on
|
|
130
|
+
each visit.
|
|
131
|
+
- Every call carries `x-lowdefy-shell` (the shell's version) and the session token as a bearer
|
|
132
|
+
token, kept in the Keychain or Keystore.
|
|
133
|
+
|
|
134
|
+
## Sign-in, unlock and sign-out
|
|
135
|
+
|
|
136
|
+
`src/auth`. The mobile root carries the app's `mobile.auth` (null for an app without auth, which
|
|
137
|
+
opens with no sign-in screen).
|
|
138
|
+
|
|
139
|
+
- With nobody signed in, the native email-code screen asks for an email, then the 6-digit code
|
|
140
|
+
(iOS offers it from Mail). It always says "If this email can sign in, a code is on its way".
|
|
141
|
+
"Other ways to sign in", `emailCode: false`, a server without email codes and a second factor
|
|
142
|
+
open the app's sign-in page in an ephemeral system sheet (`/auth/mobile`, with a state, a PKCE
|
|
143
|
+
S256 challenge and the platform). Once signed in, the page asks "Sign in to <app> on this
|
|
144
|
+
device?"; Continue creates a new session for the phone and returns a one-time token for it. iOS
|
|
145
|
+
returns to the shell's scheme; Android returns by the verified app link
|
|
146
|
+
`<app url>/auth/mobile/callback`, which `app.config.ts` declares for each app url
|
|
147
|
+
(`authCallbackIntentFilters.js`) and the app's `assetlinks.json` names the shell for. The shell
|
|
148
|
+
checks the state and exchanges the one-time token with the code verifier at
|
|
149
|
+
`/api/auth/one-time-token/verify`.
|
|
150
|
+
- The session token is kept in `expo-secure-store` on this device only: readable while unlocked,
|
|
151
|
+
behind biometrics with `unlock: biometric`, or readable after first unlock for the
|
|
152
|
+
`field-tracking` profile. It is read once per unlock and held in memory. Calls send no cookies.
|
|
153
|
+
- `unlock: biometric` shows a lock screen on launch and after `unlockAfter` seconds in the
|
|
154
|
+
background, with "Sign in again" as the way out.
|
|
155
|
+
- Sign-out, the token cleared by the core (a Logout action, a 401), and a call with the token
|
|
156
|
+
answered as the signed-out caller remove the token, every cached root and page of the app and
|
|
157
|
+
the person's storage keys, then open the app afresh. A 403 drops that caller's cache.
|
|
158
|
+
`sharedDevice: true` signs out whenever the app goes to the background.
|
|
159
|
+
|
|
160
|
+
## Version skew
|
|
161
|
+
|
|
162
|
+
- A page whose type set names a type the shell lacks, or carries at another major version, shows
|
|
163
|
+
"Update the app to open this page" with the store link. Other pages still open.
|
|
164
|
+
- `mobile.minShellVersion` above the shell's version shows the same screen for every page.
|
|
165
|
+
- A page request refused with a 409 for another build syncs the root and changed pages, then is
|
|
166
|
+
retried once when its own page did not change; otherwise the screen is rebuilt on the new page.
|
|
167
|
+
- A response answered as another caller drops the screens, syncs under the new caller's key and
|
|
168
|
+
opens home.
|
|
169
|
+
|
|
170
|
+
## Navigation
|
|
171
|
+
|
|
172
|
+
Expo Router, with one route for every page, `app/(t0,t1,t2,t3,t4)/[...path].js`, in five route
|
|
173
|
+
groups: with `mobile.menus.tabs` each tab is a group with its own native stack, under native tabs.
|
|
174
|
+
A `drawer` frame is one stack with a menu button; a `stack` frame is one stack. `Link` actions go
|
|
175
|
+
through the router; a link to a tab's page switches tab (with its query), and links to other sites
|
|
176
|
+
open in the system browser.
|
|
177
|
+
|
|
178
|
+
A `Screen` page reports its header and `src/navigation/getStackOptions.js` maps it to native stack
|
|
179
|
+
options: the title, or an iOS large title (`largeTitle`) that collapses as the page scrolls; the
|
|
180
|
+
presentation; a native search bar (`headerSearch`, focused on each show with `autoFocus`); and
|
|
181
|
+
the `headerLeft`/`headerRight` slots and `headerActions` at their natural size. On iOS each action
|
|
182
|
+
is a native bar button drawing its SF Symbol in the bar's glass, and an icon with no symbol (or
|
|
183
|
+
the slot) a custom item; elsewhere a side is a row of icon buttons. A side with nothing keeps the
|
|
184
|
+
stack's own (a drawer's menu button). Pull-to-refresh (`onRefresh`) and `onFocus` come from the
|
|
185
|
+
page's screen: `PageScreen` passes `useIsFocused()`.
|
|
186
|
+
|
|
187
|
+
Before a page is first drawn, `PageScreen` sets the bar its config gives
|
|
188
|
+
(`navigation/getInitialScreenOptions.js`: a literal `largeTitle`, `headerSearch`, `title`,
|
|
189
|
+
`presentation` and `backgroundTint`) and waits a frame: iOS keeps a large title collapsed when
|
|
190
|
+
the page's scroll view was first laid out under a standard bar, and the Screen reports its options
|
|
191
|
+
only once it renders inside that scroll view.
|
|
192
|
+
|
|
193
|
+
A Screen's `headerBottom` slot pins its blocks under the bar. `PageScreen` passes `headerInset`,
|
|
194
|
+
where the bar's bottom edge is in the page's scroll view: the native stack's
|
|
195
|
+
`useAnimatedHeaderHeight()` (from `expo-router/native-stack`), which follows a large title as it
|
|
196
|
+
collapses, when the page scrolls under the bar (`navigation/isPageUnderHeader.js`: iOS 26 and
|
|
197
|
+
later, or a large title or search bar before that), and 0 when the page sits below it (Android).
|
|
198
|
+
A page with a headerBottom hides the bar's own hairline; the strip draws one under itself.
|
|
199
|
+
A Screen with a `backgroundTint` gets a clear bar on iOS (`headerTransparent`; before iOS 26 also
|
|
200
|
+
clear at the scroll edge, with the chrome material once content scrolls under it), so the tint the
|
|
201
|
+
page draws at the screen's top shows behind the bar, and the page counts as under the bar.
|
|
202
|
+
|
|
203
|
+
Each screen keeps its page state per route instance (`useRoute().key`), so task B pushed on task A
|
|
204
|
+
has its own, and reads `_url_query` from its own route (`routePath`). The focused screen sets the
|
|
205
|
+
session's current path and instance (`setCurrentPath`), which the host's `location` and Lowdefy
|
|
206
|
+
Dev's shown page (`getShownPage`, `shownScreen` in the shell's globals) read.
|
|
207
|
+
|
|
208
|
+
Tabs (`mobile.menus.tabs`) draw their icons in the native tab bar, the selected tab in the theme's
|
|
209
|
+
`colorPrimary`. On iOS a tab draws an SF Symbol: its `sfSymbol`, or the symbol client-native's
|
|
210
|
+
`getSfSymbol` maps from its icon (`Inbox` to `tray`, `FileText` to `doc.text`, `edit` to `pencil`).
|
|
211
|
+
Elsewhere, and for an icon with no symbol, `TabIconImages` draws the Lucide icon from the item's
|
|
212
|
+
icon data once, out of sight, and reads it back as a PNG for the tab bar to tint. A tab with
|
|
213
|
+
`role: search` is iOS 26's search tab, apart at the right of the bar with a magnifier; on Android
|
|
214
|
+
and older iOS it is a tab with the search icon. Its page is a Screen with
|
|
215
|
+
`headerSearch: { autoFocus: true }`.
|
|
216
|
+
|
|
217
|
+
## Plugins, `_js`, locale and theme
|
|
218
|
+
|
|
219
|
+
- Pages run every actions-core action and the client operators of every default operator package
|
|
220
|
+
(`src/plugins/types.js`). The actions whose web bodies reach the DOM run native bodies under the
|
|
221
|
+
same names (`src/actions`): `SetFocus` focuses the control a block registered, `ScrollTo`
|
|
222
|
+
scrolls the page's ScrollView to a block's measured place, `CopyToClipboard` uses
|
|
223
|
+
`expo-clipboard`, and the local storage actions keep the app's keys in the device store, from
|
|
224
|
+
which sign-out clears the user keys. `SetDarkMode` and `SetLocale` keep their actions-core
|
|
225
|
+
bodies: they call the window members the theme provider and the shell's locale set.
|
|
226
|
+
- `_js` reaches the shell as module text: a page's `_jsEntries`, and the root's for the app
|
|
227
|
+
events. `src/js/evaluateJsEntries.js` is the one place it becomes functions, with
|
|
228
|
+
`new Function`.
|
|
229
|
+
- The app's locale is chosen as on the web: the `SetLocale` choice the device keeps, the device's
|
|
230
|
+
locale the app supports, the app's default. Every dayjs locale is bundled
|
|
231
|
+
(`src/locale/dayjsLocales.js`, written by `scripts/createDayjsLocales.mjs`) and loaded when
|
|
232
|
+
used.
|
|
233
|
+
- One theme provider holds the theme mode for every screen, from the root's `theme` and the
|
|
234
|
+
`SetDarkMode` choice; the root's `classMap`, with a dev page's own `_classMap`, styles classes.
|
|
235
|
+
- Block icons are drawn from the icon data each page carries (`_icons`), which `PageScreen` adds
|
|
236
|
+
to the icon map client-native's `components.Icon` reads (`types.icons`).
|
|
237
|
+
- Menu icons are drawn from the icon data the root carries: with client-native's `Icon` in the
|
|
238
|
+
drawer menu, and as SF Symbols or images in the tab bar (see Navigation).
|
|
239
|
+
- Hermes has no `crypto.getRandomValues`, which `_uuid` needs; `expo-crypto` fills it.
|
|
240
|
+
|
|
241
|
+
## Lowdefy Dev
|
|
242
|
+
|
|
243
|
+
`src/dev`. Lowdefy Dev is this shell built as the `dev` variant: `LOWDEFY_SHELL_VARIANT=dev | release`
|
|
244
|
+
(`shellVariant.js`), `dev` by default for Lowdefy Dev (no `LOWDEFY_SHELL_CONFIG`) and `release` for
|
|
245
|
+
every other shell. A release bundle resolves `src/dev/index.js` to an empty module
|
|
246
|
+
(`metro.config.js`), so a customer shell carries none of it. Every shell
|
|
247
|
+
leaves out the native modules its profile does not carry, and a release shell also leaves out
|
|
248
|
+
Lowdefy Dev's own (`expo-sensors`, `react-native-view-shot`, `expo-dev-client`) whatever its profile
|
|
249
|
+
(`getExclusions` in `shellVariant.js`): a `basic` shell links no `expo-camera`. `app.config.ts`
|
|
250
|
+
leaves out their config plugins and blocks their Android permissions, `withExcludedNativeModules.js`
|
|
251
|
+
passes them as exclusions to Expo autolinking in the generated Podfile and settings.gradle, and
|
|
252
|
+
`metro.config.js` resolves the left-out profiles' JavaScript entries to empty modules.
|
|
253
|
+
|
|
254
|
+
`pnpm check:release-bundle` checks a release shell (Lowdefy Dev's, or the one
|
|
255
|
+
`LOWDEFY_SHELL_CONFIG` names) on each release profile (`--profile` names one) and both platforms:
|
|
256
|
+
its bundle holds neither the dev-tab client's marker string nor a left-out module's native name,
|
|
257
|
+
and an `expo prebuild` of it links none of the left-out modules and asks for none of their
|
|
258
|
+
permissions. `--control` first checks that the `dev` variant holds and links them.
|
|
259
|
+
|
|
260
|
+
- **Connect.** `lowdefy-dev://connect?url=<server>&pair=<token>` (the QR code `lowdefy dev --lan`
|
|
261
|
+
prints, or the link `--ios`/`--android` open), a scanned code (`expo-camera`), a typed address, or
|
|
262
|
+
one of the last 5 servers. A server not on this device is confirmed first ("Connect to
|
|
263
|
+
`<host>`?"); a simulator's or USB phone's localhost connects at once. Lowdefy Dev opens one dev
|
|
264
|
+
server at a time; switching server closes the open app. Each server is its own app, with its own
|
|
265
|
+
cache and session, and every call to it (fetch and websocket) carries its pairing
|
|
266
|
+
token as `x-lowdefy-pair`.
|
|
267
|
+
- **Shake menu** (or `lowdefy-dev://menu`): reload, switch server, clear data, show logs, the block
|
|
268
|
+
gallery, and the shell version.
|
|
269
|
+
- **Dev-tab client.** The open app joins the dev server's tab channel as a browser tab does: it
|
|
270
|
+
registers on `/api/reload` (an event stream read from `expo/fetch`, or polls of
|
|
271
|
+
`/api/reload?transport=poll` every second when the stream cannot be read), posts the page it shows
|
|
272
|
+
to `/api/dev-inspect/page`, and answers on `/api/dev-inspect`:
|
|
273
|
+
- `reload`: refetches the root and the page on screen and rebuilds the screens with empty page
|
|
274
|
+
state, staying on the page.
|
|
275
|
+
- `inspect-request`, `eval-request`: the browser tab's payloads (shared fixtures with the dev
|
|
276
|
+
server's tests).
|
|
277
|
+
- `step-request` `{ step }`: a journey step by block id (`click`, `fill`, `select`,
|
|
278
|
+
`press: 'Enter'`, `wait`, `screenshot`), run through the engine; it waits for the action chain
|
|
279
|
+
to settle. A screenshot is the root view as a base64 PNG (`react-native-view-shot`).
|
|
280
|
+
- `inject` `{ inject: { kind, value, name } }`: fakes the next device result
|
|
281
|
+
(`src/dev/inject`): `scan` `{ data, type }`, `photo` `{ base64, ... }` and `nfc` for the next
|
|
282
|
+
read; `transcript` `{ partials?, final }` or `{ error }` for the next dictation session, which
|
|
283
|
+
plays the partials and the final a moment apart, while a session with nothing injected uses
|
|
284
|
+
the microphone (blocks-native's `installDictationSource`, which Lowdefy Dev fills with
|
|
285
|
+
`injected`; `permission` `microphone` answers its prompt);
|
|
286
|
+
`location` `{ latitude, longitude, ... }`, or a route as a list, read one at a time with
|
|
287
|
+
the last one staying; `permission` with `name` and `'granted' | 'denied'`, standing until
|
|
288
|
+
changed; `network` `'offline' | 'online'`, which fails the app's every fetch as offline (the tab
|
|
289
|
+
client keeps its own) and takes the shell's online state offline (`src/host/networkStatus.js`,
|
|
290
|
+
which expo-network also feeds), so the NoConnection banner shows and the app's `offline` events
|
|
291
|
+
fire; `clear`. `push` is refused until the shell carries expo-notifications.
|
|
292
|
+
Device actions read these with `injected(name)` (`src/device/injected.js`), which returns
|
|
293
|
+
nothing in a release shell; `GeolocationCurrentPosition` answers with an injected location.
|
|
294
|
+
- **Recorder** (`src/dev/recorder`). What the developer does is recorded as the web recorder records
|
|
295
|
+
it: the engine trace's events paired with the presses and changes client-native reports by block
|
|
296
|
+
id, a pageview when the page on screen changes, password inputs redacted, each record labelled
|
|
297
|
+
`device: ios | android` and `shell_version`, posted to `/api/dev-recording`. `lowdefy journeys
|
|
298
|
+
session` lists and prints phone sessions as it does browser ones.
|
|
299
|
+
|
|
300
|
+
## Checks
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
pnpm --filter=@lowdefy/shell test # Jest, and the hello fixture build
|
|
304
|
+
pnpm --filter=@lowdefy/shell export:ios # Metro and Hermes bundle, no simulator needed
|
|
305
|
+
pnpm --filter=@lowdefy/shell export:android
|
|
306
|
+
pnpm --filter=@lowdefy/shell check:release-bundle # each release profile links only its modules
|
|
307
|
+
pnpm --filter=@lowdefy/shell check:native-version # native code changed only with a nativeVersion bump
|
|
308
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
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 { useLocalSearchParams } from 'expo-router';
|
|
19
|
+
import { serializeQuery } from '@lowdefy/client-core';
|
|
20
|
+
|
|
21
|
+
import getTabHref from '../../src/navigation/getTabHref.js';
|
|
22
|
+
import PageScreen from '../../src/components/PageScreen.js';
|
|
23
|
+
import SwitchToTab from '../../src/components/SwitchToTab.js';
|
|
24
|
+
import { useSessionState } from '../../src/ShellContext.js';
|
|
25
|
+
|
|
26
|
+
// Every Lowdefy page, by path. The page and its path params come from the page index; the route's
|
|
27
|
+
// other params are the page's query. A tab's own page is shown on its tab, not stacked here.
|
|
28
|
+
export default function PathRoute() {
|
|
29
|
+
const { path, ...query } = useLocalSearchParams();
|
|
30
|
+
const { root } = useSessionState();
|
|
31
|
+
const segments = Array.isArray(path) ? path : [path];
|
|
32
|
+
const pagePath = segments.join('/');
|
|
33
|
+
const tabHref = getTabHref({ path: pagePath, query: serializeQuery(query), root });
|
|
34
|
+
if (tabHref !== null) {
|
|
35
|
+
return <SwitchToTab href={tabHref} />;
|
|
36
|
+
}
|
|
37
|
+
return <PageScreen path={pagePath} query={serializeQuery(query)} />;
|
|
38
|
+
}
|
|
@@ -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 React from 'react';
|
|
18
|
+
import { Stack } from 'expo-router';
|
|
19
|
+
|
|
20
|
+
import getFrame from '../../src/navigation/getFrame.js';
|
|
21
|
+
import { getSlotIndex } from '../../src/navigation/frameSlots.js';
|
|
22
|
+
import MenuButton from '../../src/components/MenuButton.js';
|
|
23
|
+
import SlotContext from '../../src/navigation/SlotContext.js';
|
|
24
|
+
import { useSessionState } from '../../src/ShellContext.js';
|
|
25
|
+
|
|
26
|
+
// A deep link opens on top of the group's first screen, so back leads to it.
|
|
27
|
+
export const unstable_settings = { initialRouteName: 'index' };
|
|
28
|
+
|
|
29
|
+
// The stack of one frame group (a tab, or the app's one stack). A drawer frame puts its menu
|
|
30
|
+
// button in the header of the group's first screen. Expo Router passes the group's own segment,
|
|
31
|
+
// such as `(t1)`, to a layout shared by several groups; its screens read the group from SlotContext.
|
|
32
|
+
export default function GroupLayout({ segment }) {
|
|
33
|
+
const { root } = useSessionState();
|
|
34
|
+
const frame = getFrame(root.menus);
|
|
35
|
+
return (
|
|
36
|
+
<SlotContext.Provider value={getSlotIndex(segment)}>
|
|
37
|
+
<Stack screenOptions={{ title: '' }}>
|
|
38
|
+
<Stack.Screen
|
|
39
|
+
name="index"
|
|
40
|
+
options={
|
|
41
|
+
frame.kind === 'drawer' ? { headerLeft: () => <MenuButton items={frame.items} /> } : {}
|
|
42
|
+
}
|
|
43
|
+
/>
|
|
44
|
+
<Stack.Screen name="[...path]" />
|
|
45
|
+
</Stack>
|
|
46
|
+
</SlotContext.Provider>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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, { useContext } from 'react';
|
|
18
|
+
import { useLocalSearchParams } from 'expo-router';
|
|
19
|
+
import { serializeQuery } from '@lowdefy/client-core';
|
|
20
|
+
|
|
21
|
+
import getSlotRootPath from '../../src/navigation/getSlotRootPath.js';
|
|
22
|
+
import MessageScreen from '../../src/components/MessageScreen.js';
|
|
23
|
+
import PageScreen from '../../src/components/PageScreen.js';
|
|
24
|
+
import SlotContext from '../../src/navigation/SlotContext.js';
|
|
25
|
+
import { useSessionState } from '../../src/ShellContext.js';
|
|
26
|
+
|
|
27
|
+
// The first screen of a frame group: its tab's page, or the app's home page. A link to a tab's
|
|
28
|
+
// page switches to the tab (toRouterHref) with its query as the route's params.
|
|
29
|
+
export default function GroupIndex() {
|
|
30
|
+
const slotIndex = useContext(SlotContext);
|
|
31
|
+
const query = useLocalSearchParams();
|
|
32
|
+
const { root } = useSessionState();
|
|
33
|
+
const path = getSlotRootPath({ root, slotIndex });
|
|
34
|
+
if (path === null) {
|
|
35
|
+
return <MessageScreen testID="shell-no-home" title="This app has no page to open" />;
|
|
36
|
+
}
|
|
37
|
+
return <PageScreen path={path} query={serializeQuery(query)} />;
|
|
38
|
+
}
|