@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.
- package/LICENSE +183 -183
- package/README.md +364 -2
- 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 +39 -0
- package/app/_layout.js +36 -0
- package/app.config.ts +183 -0
- package/appLinks.js +65 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +172 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native/LowdefyAppShortcuts.swift.template +84 -0
- package/native/withAppShortcuts.js +60 -0
- package/native-fingerprint.json +16 -0
- package/package.json +120 -40
- package/profiles.js +607 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +112 -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 +126 -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 +61 -0
- package/src/actions/GeolocationCurrentPosition.test.js +79 -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 +268 -0
- package/src/auth/SignInScreen.test.js +187 -0
- package/src/auth/createAppAuth.js +325 -0
- package/src/auth/createAppAuth.test.js +424 -0
- package/src/auth/createAuthApi.js +115 -0
- package/src/auth/createAuthApi.test.js +138 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createDeviceKey.js +62 -0
- package/src/auth/createDeviceKey.test.js +54 -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 +80 -0
- package/src/auth/purgeAppData.test.js +104 -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 +62 -0
- package/src/auth/unregisterPushToken.test.js +67 -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 +118 -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 +121 -0
- package/src/components/MenuButton.test.js +74 -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 +95 -0
- package/src/components/OtherPageScreen.test.js +74 -0
- package/src/components/PageScreen.js +254 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/RouteErrorScreen.js +47 -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 +40 -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 +34 -0
- package/src/config/getShellConfig.js +37 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +126 -0
- package/src/dev/DevShell.js +239 -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 +188 -0
- package/src/dev/inject/createDeviceInjections.test.js +224 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/inject/injectPush.js +60 -0
- package/src/dev/inject/injectShare.js +55 -0
- package/src/dev/inject/withInjectedDevice.js +98 -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/createDevTabClient.js +272 -0
- package/src/dev/tab/createDevTabClient.test.js +317 -0
- package/src/dev/tab/describeTabError.js +35 -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/DeviceHosts.js +32 -0
- package/src/device/basic/createFilesFeature.js +41 -0
- package/src/device/basic/createHapticsFeature.js +46 -0
- package/src/device/basic/createKeepAwakeFeature.js +37 -0
- package/src/device/basic/createPhotosFeature.js +40 -0
- package/src/device/basic/createRequestQueueFeature.js +37 -0
- package/src/device/basic/createShareFeature.js +63 -0
- package/src/device/basic/createShareFeature.test.js +47 -0
- package/src/device/basic/createSpeechFeature.js +43 -0
- package/src/device/basic/createUploadFeature.js +41 -0
- package/src/device/basic/toImagePickerOptions.js +41 -0
- package/src/device/basic/toPickedFile.js +54 -0
- package/src/device/calendar/index.js +68 -0
- package/src/device/contacts/index.js +67 -0
- package/src/device/createDevice.js +65 -0
- package/src/device/device.js +27 -0
- package/src/device/field/MapView.js +93 -0
- package/src/device/field/ScanCodeSheet.js +86 -0
- package/src/device/field/ScannerView.js +52 -0
- package/src/device/field/createAudioFeature.js +70 -0
- package/src/device/field/createBrightnessFeature.js +40 -0
- package/src/device/field/createCameraFeature.js +56 -0
- package/src/device/field/createDocumentScannerFeature.js +51 -0
- package/src/device/field/createLocationFeature.js +39 -0
- package/src/device/field/createScanRequests.js +55 -0
- package/src/device/field/index.js +39 -0
- package/src/device/field/scanRequests.js +22 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/device/nfc/index.js +89 -0
- package/src/device/nfc/toNfcRecord.js +44 -0
- package/src/device/nfc/toNfcRecord.test.js +48 -0
- package/src/device/tracking/createBacklog.js +63 -0
- package/src/device/tracking/createBacklog.test.js +47 -0
- package/src/device/tracking/index.js +211 -0
- package/src/device/tracking/index.test.js +110 -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 +62 -0
- package/src/host/createShellFetch.test.js +165 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +83 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +31 -0
- package/src/host/isNetworkError.js +24 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/readServerError.js +45 -0
- package/src/host/readServerError.test.js +85 -0
- package/src/host/reportShellError.js +47 -0
- package/src/host/reportShellError.test.js +79 -0
- package/src/host/withNetworkErrors.js +38 -0
- package/src/host/withNetworkErrors.test.js +61 -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/native/backgroundFetch.js +97 -0
- package/src/native/callEndpoint.js +63 -0
- package/src/native/createAppEventTrigger.js +70 -0
- package/src/native/currentApp.js +49 -0
- package/src/native/getPagePath.js +32 -0
- package/src/native/openPath.js +55 -0
- package/src/native/openPath.test.js +39 -0
- package/src/native/parseShellLink.js +38 -0
- package/src/native/parseShellLink.test.js +29 -0
- package/src/native/startAppServices.js +94 -0
- package/src/native/startAppServices.test.js +97 -0
- package/src/native/startShareIn.js +59 -0
- package/src/native/startShortcuts.js +88 -0
- package/src/native/startShortcuts.test.js +59 -0
- package/src/native/toSharePayload.js +47 -0
- package/src/native/toSharePayload.test.js +47 -0
- package/src/native/whenAppReady.js +48 -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/toAppLinkPath.js +43 -0
- package/src/navigation/toAppLinkPath.test.js +53 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/notifications/createNotificationsFeature.js +61 -0
- package/src/notifications/getCategoryAction.js +31 -0
- package/src/notifications/handleNotificationResponse.js +82 -0
- package/src/notifications/registerPushToken.js +71 -0
- package/src/notifications/startNotifications.js +114 -0
- package/src/notifications/toCategoryActions.js +42 -0
- package/src/notifications/toNotificationEvent.js +36 -0
- package/src/notifications/toScheduleRequest.js +62 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +15 -0
- package/src/plugins/typeManifest.js +789 -0
- package/src/plugins/types.js +64 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/queue/createRequestQueue.js +198 -0
- package/src/queue/createRequestQueue.test.js +215 -0
- package/src/queue/createSqliteQueueStore.js +121 -0
- package/src/queue/summarizeQueue.js +39 -0
- package/src/session/createAppSession.js +323 -0
- package/src/session/createAppSession.test.js +391 -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 +72 -0
- package/src/sync/fetchPage.test.js +84 -0
- package/src/sync/fetchRoot.js +43 -0
- package/src/sync/fetchUser.js +33 -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/createNetworkError.js +24 -0
- package/src/test/expoImage.js +42 -0
- package/src/test/expoNotifications.js +40 -0
- package/src/test/expoQuickActions.js +22 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoTaskManager.js +25 -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/nativeModule.js +33 -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/src/widgets/fetchWidgetData.js +59 -0
- package/src/widgets/index.js +156 -0
- package/src/widgets/refreshWidgets.js +31 -0
- package/withExcludedNativeModules.js +93 -0
- package/dist/location.js +0 -33
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/icon-32.png +0 -0
- package/dist/public/icon-512.png +0 -0
- package/dist/public/icon.svg +0 -17
- package/dist/public/logo-dark-theme.png +0 -0
- package/dist/public/logo-light-theme.png +0 -0
- package/dist/public/logo-square-dark-theme.png +0 -0
- package/dist/public/logo-square-light-theme.png +0 -0
- package/dist/public/manifest.webmanifest +0 -16
- package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
- package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
- package/dist/shell/454.b597ca21366afda000af.js +0 -2
- package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
- package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
- package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
- package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
- package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
- package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
- package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
- package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
- package/dist/shell/index.html +0 -57
- package/dist/shell/main.f01501a75909f38ccc32.js +0 -1
- package/dist/shell/runtime.e50f6c3a5f7c74a0a717.js +0 -1
package/app.config.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
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 { execFileSync } from 'node:child_process';
|
|
18
|
+
import crypto from 'node:crypto';
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
|
|
23
|
+
import type { ConfigContext, ExpoConfig } from 'expo/config';
|
|
24
|
+
|
|
25
|
+
import { getAppLinkIntentFilters, getAssociatedDomains } from './appLinks.js';
|
|
26
|
+
import applyShellPlugins from './applyShellPlugins.js';
|
|
27
|
+
import { getAuthCallbackIntentFilters } from './authCallbackIntentFilters.js';
|
|
28
|
+
import checkShellPlugins from './checkShellPlugins.js';
|
|
29
|
+
import getPrivacyManifest from './getPrivacyManifest.js';
|
|
30
|
+
import { pluginName } from './profiles.js';
|
|
31
|
+
import readShellConfig from './readShellConfig.js';
|
|
32
|
+
import resolveShellPlugins from './resolveShellPlugins.js';
|
|
33
|
+
import getRuntimeVersion from './runtimeVersion.js';
|
|
34
|
+
import { getExclusions, getShellProfile, getShellVariant } from './shellVariant.js';
|
|
35
|
+
import withExcludedNativeModules from './withExcludedNativeModules.js';
|
|
36
|
+
|
|
37
|
+
// The shell's config is its shell.json (readShellConfig.js). Its capability profile is added here,
|
|
38
|
+
// at build, so the app reads one object (src/config/getShellConfig.js). The shell's version and
|
|
39
|
+
// type manifest are not: they travel in the JavaScript bundle (src/version/shellVersion.js,
|
|
40
|
+
// src/plugins/typeManifest.js), since an embedded launch reads the Expo config of the native build,
|
|
41
|
+
// not that of the JavaScript it runs.
|
|
42
|
+
//
|
|
43
|
+
// Three versions: `version` is the store version (shell.json `version`, set per build by
|
|
44
|
+
// `lowdefy mobile`), kept out of `extra.shell`; the shell version is the @lowdefy/shell package
|
|
45
|
+
// version, which the runtime sends as `x-lowdefy-shell`; `runtimeVersion` is
|
|
46
|
+
// `<nativeVersion>-<profile>[-<plugins hash>]` (runtimeVersion.js), which decides which
|
|
47
|
+
// over-the-air updates a binary takes.
|
|
48
|
+
const shellRoot = __dirname;
|
|
49
|
+
|
|
50
|
+
function readJson(filePath: string) {
|
|
51
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The shell's icon (shell.json `icon`, a 1024×1024 PNG, relative to shell.json) and its Android
|
|
55
|
+
// adaptive-icon foreground: the icon padded into the launcher's safe zone, written beside shell.json
|
|
56
|
+
// once per icon (scripts/createShellAssets.mjs) and reused while the icon is unchanged.
|
|
57
|
+
function createShellIcons({ configPath, shell }) {
|
|
58
|
+
const directory = path.dirname(configPath);
|
|
59
|
+
const icon = path.resolve(directory, shell.icon);
|
|
60
|
+
const iconHash = crypto
|
|
61
|
+
.createHash('sha256')
|
|
62
|
+
.update(fs.readFileSync(icon))
|
|
63
|
+
.digest('hex')
|
|
64
|
+
.slice(0, 12);
|
|
65
|
+
const adaptiveForeground = path.join(directory, `adaptive-icon-${iconHash}.png`);
|
|
66
|
+
if (!fs.existsSync(adaptiveForeground)) {
|
|
67
|
+
execFileSync(process.execPath, [
|
|
68
|
+
path.join(shellRoot, 'scripts/createShellAssets.mjs'),
|
|
69
|
+
icon,
|
|
70
|
+
adaptiveForeground,
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
return { adaptiveForeground, icon };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The splash shows the shell's icon on its colour.
|
|
77
|
+
function withSplash({ color, icon, plugins }) {
|
|
78
|
+
return plugins.map((plugin) =>
|
|
79
|
+
pluginName(plugin) === 'expo-splash-screen'
|
|
80
|
+
? ['expo-splash-screen', { image: icon, imageWidth: 200, backgroundColor: color }]
|
|
81
|
+
: plugin
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default function appConfig({ config }: ConfigContext): ExpoConfig {
|
|
86
|
+
const { configPath, shell } = readShellConfig();
|
|
87
|
+
const { version: storeVersion, ...shellExtra } = shell;
|
|
88
|
+
const variant = getShellVariant();
|
|
89
|
+
const { nativeVersion } = readJson(path.join(shellRoot, 'package.json'));
|
|
90
|
+
// The shell carries only its capability profile's native modules (profiles.js). The rest, and
|
|
91
|
+
// Lowdefy Dev's developer features in a release shell, are left out: not linked, their config
|
|
92
|
+
// plugins and permissions not applied (scripts/checkReleaseBundle.mjs checks it).
|
|
93
|
+
// Its shell plugins (shell.json `plugins`, installed in the shell project by `lowdefy mobile
|
|
94
|
+
// config`) add their own modules, config plugins, permissions, privacy reasons and data on top.
|
|
95
|
+
// Their modules are never excluded: the exclusions name only modules of the shell's own profiles.
|
|
96
|
+
const shellPlugins = resolveShellPlugins({
|
|
97
|
+
shell,
|
|
98
|
+
require: createRequire(path.join(shellRoot, 'package.json')),
|
|
99
|
+
});
|
|
100
|
+
checkShellPlugins({ shell, variant, plugins: shellPlugins });
|
|
101
|
+
const profile = applyShellPlugins({
|
|
102
|
+
profile: getShellProfile({ shell, variant }),
|
|
103
|
+
plugins: shellPlugins,
|
|
104
|
+
});
|
|
105
|
+
const excluded = getExclusions({ shell, variant });
|
|
106
|
+
// Lowdefy Dev has no icon of its own and keeps Expo's.
|
|
107
|
+
const icons = shell.icon === undefined ? null : createShellIcons({ configPath, shell });
|
|
108
|
+
const color = shell.color ?? '#FFFFFF';
|
|
109
|
+
const appConfigResult: ExpoConfig = {
|
|
110
|
+
...config,
|
|
111
|
+
name: shell.name,
|
|
112
|
+
slug: shell.id,
|
|
113
|
+
// Lowdefy Dev has no store version of its own here.
|
|
114
|
+
version: storeVersion ?? '0.0.0',
|
|
115
|
+
runtimeVersion: getRuntimeVersion({
|
|
116
|
+
nativeVersion,
|
|
117
|
+
profile: profile.name,
|
|
118
|
+
plugins: shell.plugins,
|
|
119
|
+
extensions: shell.extensions,
|
|
120
|
+
}),
|
|
121
|
+
platforms: ['ios', 'android'],
|
|
122
|
+
scheme: shell.scheme,
|
|
123
|
+
orientation: 'default',
|
|
124
|
+
userInterfaceStyle: 'automatic',
|
|
125
|
+
...(icons ? { icon: icons.icon } : {}),
|
|
126
|
+
ios: {
|
|
127
|
+
bundleIdentifier: shell.ios?.bundleIdentifier,
|
|
128
|
+
supportsTablet: true,
|
|
129
|
+
// The shell uses only the encryption the OS provides (HTTPS, the Keychain), so App Store
|
|
130
|
+
// Connect asks no export compliance question on upload.
|
|
131
|
+
config: { usesNonExemptEncryption: false },
|
|
132
|
+
// The profile's purpose texts no config plugin writes (profiles.js `infoPlist`).
|
|
133
|
+
// expo-dev-menu's floating tools button sits over the header's trailing actions in a
|
|
134
|
+
// development build; the dev menu stays a shake away (DevMenuPreferences.swift reads this).
|
|
135
|
+
infoPlist: { ...profile.infoPlist, EXDevMenuShowFloatingActionButton: false },
|
|
136
|
+
// Links to the app's pages open in the shell (appLinks.js).
|
|
137
|
+
associatedDomains: getAssociatedDomains({ shell }),
|
|
138
|
+
privacyManifests: getPrivacyManifest({ privacyReasons: profile.privacyReasons }),
|
|
139
|
+
},
|
|
140
|
+
android: {
|
|
141
|
+
package: shell.android?.package,
|
|
142
|
+
...(icons
|
|
143
|
+
? { adaptiveIcon: { foregroundImage: icons.adaptiveForeground, backgroundColor: color } }
|
|
144
|
+
: {}),
|
|
145
|
+
// The sign-in sheet returns by a verified app link on Android (src/auth/signInWithSheet.js).
|
|
146
|
+
// Links to the app's pages open in the shell too (appLinks.js).
|
|
147
|
+
intentFilters: [
|
|
148
|
+
...getAuthCallbackIntentFilters({ shell }),
|
|
149
|
+
...getAppLinkIntentFilters({ shell }),
|
|
150
|
+
],
|
|
151
|
+
// Google Maps draws the Map block on Android. Its key is a build secret, never in
|
|
152
|
+
// lowdefy.yaml or the bundle.
|
|
153
|
+
...(profile.capabilities.includes('maps') && process.env.LOWDEFY_GOOGLE_MAPS_API_KEY
|
|
154
|
+
? { config: { googleMaps: { apiKey: process.env.LOWDEFY_GOOGLE_MAPS_API_KEY } } }
|
|
155
|
+
: {}),
|
|
156
|
+
blockedPermissions: excluded.permissions.filter((permission) =>
|
|
157
|
+
permission.startsWith('android.permission.')
|
|
158
|
+
),
|
|
159
|
+
},
|
|
160
|
+
plugins: icons
|
|
161
|
+
? withSplash({ color, icon: icons.icon, plugins: profile.plugins })
|
|
162
|
+
: profile.plugins,
|
|
163
|
+
// A shell built for the stores is its own EAS project, which serves its over-the-air updates.
|
|
164
|
+
// Lowdefy Dev's shell.dev.json has none.
|
|
165
|
+
...(shell.eas
|
|
166
|
+
? {
|
|
167
|
+
owner: shell.eas.owner,
|
|
168
|
+
updates: { url: `https://u.expo.dev/${shell.eas.projectId}` },
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
extra: {
|
|
172
|
+
...(shell.eas ? { eas: { projectId: shell.eas.projectId } } : {}),
|
|
173
|
+
shell: {
|
|
174
|
+
...shellExtra,
|
|
175
|
+
profile: profile.name,
|
|
176
|
+
// The profile's and its extensions' capabilities, with any shell.json lists beside them.
|
|
177
|
+
capabilities: [...new Set([...(shell.capabilities ?? []), ...profile.capabilities])],
|
|
178
|
+
variant,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
return withExcludedNativeModules(appConfigResult, Object.keys(excluded.modules));
|
|
183
|
+
}
|
package/appLinks.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The app's links that open in the shell: https links to any page of the app (universal links on
|
|
18
|
+
// iOS, verified app links on Android). The server's apple-app-site-association and assetlinks.json
|
|
19
|
+
// name the shell for every page path under basePath and leave /api to the server; the shell
|
|
20
|
+
// declares the domain (iOS) and the host and path prefix (Android) of its app's url for its
|
|
21
|
+
// environment. Both platforms verify https links only, so an http url (a local dev server) gets
|
|
22
|
+
// none.
|
|
23
|
+
function getAppUrl({ shell }) {
|
|
24
|
+
const url = shell.app.urls[shell.environment];
|
|
25
|
+
if (!url.startsWith('https://')) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return new URL(url);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// iOS: the Associated Domains entitlement, `applinks:<host>` (with its port when it has one).
|
|
32
|
+
function getAssociatedDomains({ shell }) {
|
|
33
|
+
const url = getAppUrl({ shell });
|
|
34
|
+
if (url === null) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return [`applinks:${url.host}`];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Android: one autoVerify filter for the app's host under its basePath. The sign-in callback has its
|
|
41
|
+
// own filter (authCallbackIntentFilters.js); the router hands that path to the sign-in sheet.
|
|
42
|
+
function getAppLinkIntentFilters({ shell }) {
|
|
43
|
+
const url = getAppUrl({ shell });
|
|
44
|
+
if (url === null) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const basePath = url.pathname.replace(/\/$/, '');
|
|
48
|
+
const data = { scheme: 'https', host: url.hostname };
|
|
49
|
+
if (basePath !== '') {
|
|
50
|
+
data.pathPrefix = basePath;
|
|
51
|
+
}
|
|
52
|
+
if (url.port !== '') {
|
|
53
|
+
data.port = url.port;
|
|
54
|
+
}
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
action: 'VIEW',
|
|
58
|
+
autoVerify: true,
|
|
59
|
+
data: [data],
|
|
60
|
+
category: ['BROWSABLE', 'DEFAULT'],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { getAppLinkIntentFilters, getAssociatedDomains };
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
const { mergeProfiles } = require('./profiles.js');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A shell's resolved profile with its plugins' `./shell` entries folded in (resolveShellPlugins.js):
|
|
21
|
+
* their capabilities, modules, config plugins, permissions, privacy reasons and data join the
|
|
22
|
+
* profile's. The profile keeps its name, and its JavaScript entries are its own.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} params
|
|
25
|
+
* @param {Object} params.profile The shell's resolved profile (getShellProfile in shellVariant.js).
|
|
26
|
+
* @param {{ entry: Object }[]} params.plugins The resolved shell plugins.
|
|
27
|
+
*/
|
|
28
|
+
function applyShellPlugins({ profile, plugins }) {
|
|
29
|
+
return plugins.reduce(
|
|
30
|
+
(merged, { entry }) =>
|
|
31
|
+
mergeProfiles(merged, {
|
|
32
|
+
capabilities: entry.capabilities,
|
|
33
|
+
modules: entry.modules,
|
|
34
|
+
plugins: entry.plugins,
|
|
35
|
+
permissions: entry.permissions,
|
|
36
|
+
privacyReasons: entry.privacyReasons,
|
|
37
|
+
dataCollected: entry.dataCollected,
|
|
38
|
+
}),
|
|
39
|
+
profile
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = applyShellPlugins;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The Android app link the sign-in sheet returns by: the app server sends the one-time token to
|
|
18
|
+
// https://<app domain><basePath>/auth/mobile/callback, and Android opens that link only in the
|
|
19
|
+
// shell the app's assetlinks.json names (autoVerify), never in another app. A shell opens one app
|
|
20
|
+
// (checkShellConfig), so there is one filter, at the app's url for the shell's environment.
|
|
21
|
+
// Android verifies https links only, so an http url (a local dev server) gets none.
|
|
22
|
+
function getAuthCallbackIntentFilters({ shell }) {
|
|
23
|
+
const url = shell.app.urls[shell.environment];
|
|
24
|
+
if (!url.startsWith('https://')) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const { hostname, pathname, port } = new URL(url);
|
|
28
|
+
const data = {
|
|
29
|
+
scheme: 'https',
|
|
30
|
+
host: hostname,
|
|
31
|
+
pathPrefix: `${pathname.replace(/\/$/, '')}/auth/mobile/callback`,
|
|
32
|
+
};
|
|
33
|
+
if (port !== '') {
|
|
34
|
+
data.port = port;
|
|
35
|
+
}
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
action: 'VIEW',
|
|
39
|
+
autoVerify: true,
|
|
40
|
+
data: [data],
|
|
41
|
+
category: ['BROWSABLE', 'DEFAULT'],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { getAuthCallbackIntentFilters };
|
package/babel.config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
// intl-messageformat (through @lowdefy/helpers translate) ships static class blocks, which
|
|
18
|
+
// babel-preset-expo leaves alone; Metro and Jest transform node_modules with this config too.
|
|
19
|
+
module.exports = function babelConfig(api) {
|
|
20
|
+
api.cache(true);
|
|
21
|
+
return {
|
|
22
|
+
presets: ['babel-preset-expo'],
|
|
23
|
+
plugins: ['@babel/plugin-transform-class-static-block'],
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
const { EXTENSIONS } = require('./profiles.js');
|
|
18
|
+
|
|
19
|
+
function isObject(value) {
|
|
20
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A shell opens exactly one Lowdefy app: every app's `_js` runs in the shell's one JavaScript realm,
|
|
24
|
+
// so two apps in one shell would share its session tokens, storage and cache. app.config.ts (and
|
|
25
|
+
// so `expo prebuild` and `expo export`) refuses a shell.json that lists `apps`, or that does not
|
|
26
|
+
// name one app with a url for the shell's environment.
|
|
27
|
+
function checkShellConfig({ configPath, shell }) {
|
|
28
|
+
const where = `Shell config "${configPath}"`;
|
|
29
|
+
if (!isObject(shell)) {
|
|
30
|
+
throw new Error(`${where} should be an object.`);
|
|
31
|
+
}
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(shell, 'apps')) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`${where} lists "apps". A shell opens exactly one app: replace "apps" with "app": { id, name, urls: { <environment>: url } }.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(shell.app)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`${where} names ${shell.app.length} apps in "app". A shell opens exactly one app: "app" should be one { id, name, urls } object.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (!isObject(shell.app)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`${where} should name the app it opens as "app": { id, name, urls: { <environment>: url } }. Received ${JSON.stringify(
|
|
45
|
+
shell.app
|
|
46
|
+
)}.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (typeof shell.app.id !== 'string' || shell.app.id.length === 0) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`${where} "app.id" should be a string. Received ${JSON.stringify(shell.app.id)}.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const url = isObject(shell.app.urls) ? shell.app.urls[shell.environment] : undefined;
|
|
55
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`${where} "app.urls" should have a url for the shell's environment "${
|
|
58
|
+
shell.environment
|
|
59
|
+
}". Received ${JSON.stringify(shell.app.urls)}.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
// The shell reads the server's origin from this url (src/host/getOrigin.js).
|
|
63
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\/[^/?#]+/i.test(url)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`${where} "app.urls.${
|
|
66
|
+
shell.environment
|
|
67
|
+
}" should be an absolute URL, like "https://app.example.com". Received ${JSON.stringify(
|
|
68
|
+
url
|
|
69
|
+
)}.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (shell.profile !== undefined && typeof shell.profile !== 'string') {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`${where} "profile" should be the name of a capability profile. Received ${JSON.stringify(
|
|
75
|
+
shell.profile
|
|
76
|
+
)}.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
// The store version, major.minor.patch; `lowdefy mobile build` sets the patch.
|
|
80
|
+
if (shell.version !== undefined && typeof shell.version !== 'string') {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`${where} "version" should be a string. Received ${JSON.stringify(shell.version)}.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
// The icon is a path relative to shell.json; `lowdefy mobile config` copies it there.
|
|
86
|
+
if (shell.icon !== undefined && (typeof shell.icon !== 'string' || shell.icon.length === 0)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`${where} "icon" should be the path of a PNG, relative to the shell config. Received ${JSON.stringify(
|
|
89
|
+
shell.icon
|
|
90
|
+
)}.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (shell.color !== undefined && !/^#[0-9a-fA-F]{6}$/.test(shell.color)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`${where} "color" should be a colour like "#0B5FFF". Received ${JSON.stringify(shell.color)}.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (shell.eas !== undefined) {
|
|
99
|
+
if (!isObject(shell.eas) || typeof shell.eas.projectId !== 'string') {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`${where} "eas" should be { owner, projectId }, the shell's Expo account and project. Received ${JSON.stringify(
|
|
102
|
+
shell.eas
|
|
103
|
+
)}.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (shell.eas.owner !== undefined && typeof shell.eas.owner !== 'string') {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`${where} "eas.owner" should be a string. Received ${JSON.stringify(shell.eas.owner)}.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// The opt-in extensions (EXTENSIONS in profiles.js), each named once.
|
|
113
|
+
if (shell.extensions !== undefined) {
|
|
114
|
+
const known = Object.keys(EXTENSIONS);
|
|
115
|
+
if (
|
|
116
|
+
!Array.isArray(shell.extensions) ||
|
|
117
|
+
!shell.extensions.every((name) => known.includes(name)) ||
|
|
118
|
+
new Set(shell.extensions).size !== shell.extensions.length
|
|
119
|
+
) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`${where} "extensions" should list each of ${known.join(
|
|
122
|
+
', '
|
|
123
|
+
)} at most once. Received ${JSON.stringify(shell.extensions)}.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// The shell plugins `lowdefy mobile config` installs into the shell project
|
|
128
|
+
// (resolveShellPlugins.js).
|
|
129
|
+
if (shell.plugins !== undefined) {
|
|
130
|
+
checkPlugins({ plugins: shell.plugins, where });
|
|
131
|
+
}
|
|
132
|
+
return shell;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function checkPlugins({ plugins, where }) {
|
|
136
|
+
if (!Array.isArray(plugins)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`${where} "plugins" should be an array of { name, version, typePrefix?, options? }. Received ${JSON.stringify(
|
|
139
|
+
plugins
|
|
140
|
+
)}.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
plugins.forEach((plugin, index) => {
|
|
144
|
+
const at = `${where} "plugins[${index}]"`;
|
|
145
|
+
if (!isObject(plugin)) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`${at} should be { name, version, typePrefix?, options? }. Received ${JSON.stringify(
|
|
148
|
+
plugin
|
|
149
|
+
)}.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
['name', 'version'].forEach((key) => {
|
|
153
|
+
if (typeof plugin[key] !== 'string' || plugin[key].length === 0) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`${at} "${key}" should be a string. Received ${JSON.stringify(plugin[key])}.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (plugin.typePrefix !== undefined && typeof plugin.typePrefix !== 'string') {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`${at} "typePrefix" should be a string. Received ${JSON.stringify(plugin.typePrefix)}.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (plugin.options !== undefined && !isObject(plugin.options)) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`${at} "options" should be an object. Received ${JSON.stringify(plugin.options)}.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = checkShellConfig;
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
const { PROFILES, RELEASE_PROFILES, resolveProfile } = require('./profiles.js');
|
|
18
|
+
const { getProfileName, getShellProfile } = require('./shellVariant.js');
|
|
19
|
+
|
|
20
|
+
// Release profiles first, lowest first, so an error names the smallest profile a shell can pick.
|
|
21
|
+
const PROFILE_ORDER = [
|
|
22
|
+
...RELEASE_PROFILES,
|
|
23
|
+
...Object.keys(PROFILES).filter((name) => !RELEASE_PROFILES.includes(name)),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function findProfile(carries) {
|
|
27
|
+
return PROFILE_ORDER.find((name) => carries(resolveProfile(name)));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Refuses shell plugins that would carry native code around the capability profiles, so plugins
|
|
32
|
+
* never become a way past profile review. Throws, naming the plugin and the profile to pick, when
|
|
33
|
+
* a plugin's `./shell` entry:
|
|
34
|
+
* - links a module @lowdefy/shell depends on itself (one of any profile's, such as expo-camera); or
|
|
35
|
+
* - asks for a permission some profile carries and this shell's profile leaves out (the camera on
|
|
36
|
+
* `basic`).
|
|
37
|
+
*
|
|
38
|
+
* `lowdefy mobile config` runs it from the copied shell, and app.config.ts runs it again, so a
|
|
39
|
+
* hand-edited shell.json is refused at prebuild.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} params
|
|
42
|
+
* @param {Object} params.shell The shell config (shell.json).
|
|
43
|
+
* @param {string} params.variant The shell variant, dev or release.
|
|
44
|
+
* @param {{ name: string, entry: Object }[]} params.plugins The resolved shell plugins
|
|
45
|
+
* (resolveShellPlugins.js).
|
|
46
|
+
*/
|
|
47
|
+
function checkShellPlugins({ shell, variant, plugins }) {
|
|
48
|
+
const profileName = getProfileName({ shell, variant });
|
|
49
|
+
const profile = getShellProfile({ shell, variant });
|
|
50
|
+
plugins.forEach(({ name, entry }) => {
|
|
51
|
+
Object.keys(entry.modules ?? {}).forEach((module) => {
|
|
52
|
+
const carrier = findProfile((candidate) => module in candidate.modules);
|
|
53
|
+
if (carrier !== undefined) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Shell plugin "${name}" links "${module}", which @lowdefy/shell carries in the "${carrier}" profile. Pick the profile that carries it instead of adding it with a plugin.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
(entry.permissions ?? []).forEach((permission) => {
|
|
60
|
+
if (profile.permissions.includes(permission)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const carrier = findProfile((candidate) => candidate.permissions.includes(permission));
|
|
64
|
+
if (carrier !== undefined) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Shell plugin "${name}" asks for "${permission}", which the "${profileName}" profile leaves out. Pick the "${carrier}" profile, which carries it.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return plugins;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = checkShellPlugins;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The iOS privacy manifest (`ios.privacyManifests`) of a shell on a capability profile: the
|
|
19
|
+
* required-reason APIs its modules use (profiles.js `privacyReasons`, where `basic` holds React
|
|
20
|
+
* Native's and expo-sqlite's, so every profile carries them), each category listed once. App Store
|
|
21
|
+
* Connect refuses an upload whose binary uses one of these APIs without a declared reason.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} params
|
|
24
|
+
* @param {{ category: string, reasons: string[] }[]} params.privacyReasons The resolved profile's.
|
|
25
|
+
*/
|
|
26
|
+
function getPrivacyManifest({ privacyReasons }) {
|
|
27
|
+
const byCategory = new Map();
|
|
28
|
+
privacyReasons.forEach(({ category, reasons }) => {
|
|
29
|
+
byCategory.set(category, [...new Set([...(byCategory.get(category) ?? []), ...reasons])]);
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
NSPrivacyAccessedAPITypes: [...byCategory.entries()].map(([category, reasons]) => ({
|
|
33
|
+
NSPrivacyAccessedAPIType: category,
|
|
34
|
+
NSPrivacyAccessedAPITypeReasons: reasons,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = getPrivacyManifest;
|