@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
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
// Links that are not page paths:
|
|
18
|
+
// - The sign-in sheet's Android app link (/auth/mobile/callback) carries a one-time token that
|
|
19
|
+
// signInWithSheet reads from the sheet's result; the router leaves it alone.
|
|
20
|
+
// - A Lowdefy Dev link (lowdefy-dev://connect, lowdefy-dev://menu): Lowdefy Dev handles it
|
|
21
|
+
// (src/dev). A release shell has no developer features and routes every other link.
|
|
22
|
+
import { redirectDevLink } from '../src/dev/index.js';
|
|
23
|
+
import isSheetReturnLink from '../src/auth/isSheetReturnLink.js';
|
|
24
|
+
|
|
25
|
+
export function redirectSystemPath({ initial, path }) {
|
|
26
|
+
if (isSheetReturnLink(path)) {
|
|
27
|
+
return initial ? '/' : null;
|
|
28
|
+
}
|
|
29
|
+
return redirectDevLink ? redirectDevLink({ initial, path }) : path;
|
|
30
|
+
}
|
package/app/_layout.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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 * as SplashScreen from 'expo-splash-screen';
|
|
19
|
+
|
|
20
|
+
import installGetRandomValues from '../src/polyfills/installGetRandomValues.js';
|
|
21
|
+
import ShellRoot from '../src/components/ShellRoot.js';
|
|
22
|
+
// Lowdefy Dev's developer features; undefined in a release shell, whose bundle leaves them out.
|
|
23
|
+
import { DevShell } from '../src/dev/index.js';
|
|
24
|
+
|
|
25
|
+
installGetRandomValues();
|
|
26
|
+
|
|
27
|
+
// The launch screen stays until the first screen is ready: the app's home page from the cache (or
|
|
28
|
+
// from the server on a first launch).
|
|
29
|
+
SplashScreen.preventAutoHideAsync();
|
|
30
|
+
|
|
31
|
+
export default function RootLayout() {
|
|
32
|
+
return DevShell ? <DevShell /> : <ShellRoot />;
|
|
33
|
+
}
|
package/app.config.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
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 applyShellPlugins from './applyShellPlugins.js';
|
|
26
|
+
import { getAuthCallbackIntentFilters } from './authCallbackIntentFilters.js';
|
|
27
|
+
import checkShellPlugins from './checkShellPlugins.js';
|
|
28
|
+
import getPrivacyManifest from './getPrivacyManifest.js';
|
|
29
|
+
import { pluginName } from './profiles.js';
|
|
30
|
+
import readShellConfig from './readShellConfig.js';
|
|
31
|
+
import resolveShellPlugins from './resolveShellPlugins.js';
|
|
32
|
+
import getRuntimeVersion from './runtimeVersion.js';
|
|
33
|
+
import { getExclusions, getShellProfile, getShellVariant } from './shellVariant.js';
|
|
34
|
+
import withExcludedNativeModules from './withExcludedNativeModules.js';
|
|
35
|
+
|
|
36
|
+
// The shell's config is its shell.json (readShellConfig.js). Its capability profile is added here,
|
|
37
|
+
// at build, so the app reads one object (src/config/getShellConfig.js). The shell's version and
|
|
38
|
+
// type manifest are not: they travel in the JavaScript bundle (src/version/shellVersion.js,
|
|
39
|
+
// src/plugins/typeManifest.js), since an embedded launch reads the Expo config of the native build,
|
|
40
|
+
// not that of the JavaScript it runs.
|
|
41
|
+
//
|
|
42
|
+
// Three versions: `version` is the store version (shell.json `version`, set per build by
|
|
43
|
+
// `lowdefy mobile`), kept out of `extra.shell`; the shell version is the @lowdefy/shell package
|
|
44
|
+
// version, which the runtime sends as `x-lowdefy-shell`; `runtimeVersion` is
|
|
45
|
+
// `<nativeVersion>-<profile>[-<plugins hash>]` (runtimeVersion.js), which decides which
|
|
46
|
+
// over-the-air updates a binary takes.
|
|
47
|
+
const shellRoot = __dirname;
|
|
48
|
+
|
|
49
|
+
function readJson(filePath: string) {
|
|
50
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The shell's icon (shell.json `icon`, a 1024×1024 PNG, relative to shell.json) and its Android
|
|
54
|
+
// adaptive-icon foreground: the icon padded into the launcher's safe zone, written beside shell.json
|
|
55
|
+
// once per icon (scripts/createShellAssets.mjs) and reused while the icon is unchanged.
|
|
56
|
+
function createShellIcons({ configPath, shell }) {
|
|
57
|
+
const directory = path.dirname(configPath);
|
|
58
|
+
const icon = path.resolve(directory, shell.icon);
|
|
59
|
+
const iconHash = crypto
|
|
60
|
+
.createHash('sha256')
|
|
61
|
+
.update(fs.readFileSync(icon))
|
|
62
|
+
.digest('hex')
|
|
63
|
+
.slice(0, 12);
|
|
64
|
+
const adaptiveForeground = path.join(directory, `adaptive-icon-${iconHash}.png`);
|
|
65
|
+
if (!fs.existsSync(adaptiveForeground)) {
|
|
66
|
+
execFileSync(process.execPath, [
|
|
67
|
+
path.join(shellRoot, 'scripts/createShellAssets.mjs'),
|
|
68
|
+
icon,
|
|
69
|
+
adaptiveForeground,
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
return { adaptiveForeground, icon };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The splash shows the shell's icon on its colour.
|
|
76
|
+
function withSplash({ color, icon, plugins }) {
|
|
77
|
+
return plugins.map((plugin) =>
|
|
78
|
+
pluginName(plugin) === 'expo-splash-screen'
|
|
79
|
+
? ['expo-splash-screen', { image: icon, imageWidth: 200, backgroundColor: color }]
|
|
80
|
+
: plugin
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default function appConfig({ config }: ConfigContext): ExpoConfig {
|
|
85
|
+
const { configPath, shell } = readShellConfig();
|
|
86
|
+
const { version: storeVersion, ...shellExtra } = shell;
|
|
87
|
+
const variant = getShellVariant();
|
|
88
|
+
const { nativeVersion } = readJson(path.join(shellRoot, 'package.json'));
|
|
89
|
+
// The shell carries only its capability profile's native modules (profiles.js). The rest, and
|
|
90
|
+
// Lowdefy Dev's developer features in a release shell, are left out: not linked, their config
|
|
91
|
+
// plugins and permissions not applied (scripts/checkReleaseBundle.mjs checks it).
|
|
92
|
+
// Its shell plugins (shell.json `plugins`, installed in the shell project by `lowdefy mobile
|
|
93
|
+
// config`) add their own modules, config plugins, permissions, privacy reasons and data on top.
|
|
94
|
+
// Their modules are never excluded: the exclusions name only modules of the shell's own profiles.
|
|
95
|
+
const shellPlugins = resolveShellPlugins({
|
|
96
|
+
shell,
|
|
97
|
+
require: createRequire(path.join(shellRoot, 'package.json')),
|
|
98
|
+
});
|
|
99
|
+
checkShellPlugins({ shell, variant, plugins: shellPlugins });
|
|
100
|
+
const profile = applyShellPlugins({
|
|
101
|
+
profile: getShellProfile({ shell, variant }),
|
|
102
|
+
plugins: shellPlugins,
|
|
103
|
+
});
|
|
104
|
+
const excluded = getExclusions({ shell, variant });
|
|
105
|
+
// Lowdefy Dev has no icon of its own and keeps Expo's.
|
|
106
|
+
const icons = shell.icon === undefined ? null : createShellIcons({ configPath, shell });
|
|
107
|
+
const color = shell.color ?? '#FFFFFF';
|
|
108
|
+
const appConfigResult: ExpoConfig = {
|
|
109
|
+
...config,
|
|
110
|
+
name: shell.name,
|
|
111
|
+
slug: shell.id,
|
|
112
|
+
// Lowdefy Dev has no store version of its own here.
|
|
113
|
+
version: storeVersion ?? '0.0.0',
|
|
114
|
+
runtimeVersion: getRuntimeVersion({
|
|
115
|
+
nativeVersion,
|
|
116
|
+
profile: profile.name,
|
|
117
|
+
plugins: shell.plugins,
|
|
118
|
+
}),
|
|
119
|
+
platforms: ['ios', 'android'],
|
|
120
|
+
scheme: shell.scheme,
|
|
121
|
+
orientation: 'default',
|
|
122
|
+
userInterfaceStyle: 'automatic',
|
|
123
|
+
...(icons ? { icon: icons.icon } : {}),
|
|
124
|
+
ios: {
|
|
125
|
+
bundleIdentifier: shell.ios?.bundleIdentifier,
|
|
126
|
+
supportsTablet: true,
|
|
127
|
+
// The shell uses only the encryption the OS provides (HTTPS, the Keychain), so App Store
|
|
128
|
+
// Connect asks no export compliance question on upload.
|
|
129
|
+
config: { usesNonExemptEncryption: false },
|
|
130
|
+
// expo-dev-menu's floating tools button sits over the header's trailing actions in a
|
|
131
|
+
// development build; the dev menu stays a shake away (DevMenuPreferences.swift reads this).
|
|
132
|
+
infoPlist: { EXDevMenuShowFloatingActionButton: false },
|
|
133
|
+
privacyManifests: getPrivacyManifest({ privacyReasons: profile.privacyReasons }),
|
|
134
|
+
},
|
|
135
|
+
android: {
|
|
136
|
+
package: shell.android?.package,
|
|
137
|
+
...(icons
|
|
138
|
+
? { adaptiveIcon: { foregroundImage: icons.adaptiveForeground, backgroundColor: color } }
|
|
139
|
+
: {}),
|
|
140
|
+
// The sign-in sheet returns by a verified app link on Android (src/auth/signInWithSheet.js).
|
|
141
|
+
intentFilters: getAuthCallbackIntentFilters({ shell }),
|
|
142
|
+
blockedPermissions: excluded.permissions.filter((permission) =>
|
|
143
|
+
permission.startsWith('android.permission.')
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
plugins: icons
|
|
147
|
+
? withSplash({ color, icon: icons.icon, plugins: profile.plugins })
|
|
148
|
+
: profile.plugins,
|
|
149
|
+
// A shell built for the stores is its own EAS project, which serves its over-the-air updates.
|
|
150
|
+
// Lowdefy Dev's shell.dev.json has none.
|
|
151
|
+
...(shell.eas
|
|
152
|
+
? {
|
|
153
|
+
owner: shell.eas.owner,
|
|
154
|
+
updates: { url: `https://u.expo.dev/${shell.eas.projectId}` },
|
|
155
|
+
}
|
|
156
|
+
: {}),
|
|
157
|
+
extra: {
|
|
158
|
+
...(shell.eas ? { eas: { projectId: shell.eas.projectId } } : {}),
|
|
159
|
+
shell: {
|
|
160
|
+
...shellExtra,
|
|
161
|
+
profile: profile.name,
|
|
162
|
+
capabilities: shell.capabilities ?? profile.capabilities,
|
|
163
|
+
variant,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
return withExcludedNativeModules(appConfigResult, Object.keys(excluded.modules));
|
|
168
|
+
}
|
|
@@ -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,145 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function isObject(value) {
|
|
18
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A shell opens exactly one Lowdefy app: every app's `_js` runs in the shell's one JavaScript realm,
|
|
22
|
+
// so two apps in one shell would share its session tokens, storage and cache. app.config.ts (and
|
|
23
|
+
// so `expo prebuild` and `expo export`) refuses a shell.json that lists `apps`, or that does not
|
|
24
|
+
// name one app with a url for the shell's environment.
|
|
25
|
+
function checkShellConfig({ configPath, shell }) {
|
|
26
|
+
const where = `Shell config "${configPath}"`;
|
|
27
|
+
if (!isObject(shell)) {
|
|
28
|
+
throw new Error(`${where} should be an object.`);
|
|
29
|
+
}
|
|
30
|
+
if (Object.prototype.hasOwnProperty.call(shell, 'apps')) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`${where} lists "apps". A shell opens exactly one app: replace "apps" with "app": { id, name, urls: { <environment>: url } }.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(shell.app)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`${where} names ${shell.app.length} apps in "app". A shell opens exactly one app: "app" should be one { id, name, urls } object.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (!isObject(shell.app)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`${where} should name the app it opens as "app": { id, name, urls: { <environment>: url } }. Received ${JSON.stringify(
|
|
43
|
+
shell.app
|
|
44
|
+
)}.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (typeof shell.app.id !== 'string' || shell.app.id.length === 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`${where} "app.id" should be a string. Received ${JSON.stringify(shell.app.id)}.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const url = isObject(shell.app.urls) ? shell.app.urls[shell.environment] : undefined;
|
|
53
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`${where} "app.urls" should have a url for the shell's environment "${
|
|
56
|
+
shell.environment
|
|
57
|
+
}". Received ${JSON.stringify(shell.app.urls)}.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (shell.profile !== undefined && typeof shell.profile !== 'string') {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`${where} "profile" should be the name of a capability profile. Received ${JSON.stringify(
|
|
63
|
+
shell.profile
|
|
64
|
+
)}.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
// The store version, major.minor.patch; `lowdefy mobile build` sets the patch.
|
|
68
|
+
if (shell.version !== undefined && typeof shell.version !== 'string') {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${where} "version" should be a string. Received ${JSON.stringify(shell.version)}.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
// The icon is a path relative to shell.json; `lowdefy mobile config` copies it there.
|
|
74
|
+
if (shell.icon !== undefined && (typeof shell.icon !== 'string' || shell.icon.length === 0)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`${where} "icon" should be the path of a PNG, relative to the shell config. Received ${JSON.stringify(
|
|
77
|
+
shell.icon
|
|
78
|
+
)}.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (shell.color !== undefined && !/^#[0-9a-fA-F]{6}$/.test(shell.color)) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`${where} "color" should be a colour like "#0B5FFF". Received ${JSON.stringify(shell.color)}.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (shell.eas !== undefined) {
|
|
87
|
+
if (!isObject(shell.eas) || typeof shell.eas.projectId !== 'string') {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`${where} "eas" should be { owner, projectId }, the shell's Expo account and project. Received ${JSON.stringify(
|
|
90
|
+
shell.eas
|
|
91
|
+
)}.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (shell.eas.owner !== undefined && typeof shell.eas.owner !== 'string') {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`${where} "eas.owner" should be a string. Received ${JSON.stringify(shell.eas.owner)}.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// The shell plugins `lowdefy mobile config` installs into the shell project
|
|
101
|
+
// (resolveShellPlugins.js).
|
|
102
|
+
if (shell.plugins !== undefined) {
|
|
103
|
+
checkPlugins({ plugins: shell.plugins, where });
|
|
104
|
+
}
|
|
105
|
+
return shell;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function checkPlugins({ plugins, where }) {
|
|
109
|
+
if (!Array.isArray(plugins)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${where} "plugins" should be an array of { name, version, typePrefix?, options? }. Received ${JSON.stringify(
|
|
112
|
+
plugins
|
|
113
|
+
)}.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
plugins.forEach((plugin, index) => {
|
|
117
|
+
const at = `${where} "plugins[${index}]"`;
|
|
118
|
+
if (!isObject(plugin)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`${at} should be { name, version, typePrefix?, options? }. Received ${JSON.stringify(
|
|
121
|
+
plugin
|
|
122
|
+
)}.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
['name', 'version'].forEach((key) => {
|
|
126
|
+
if (typeof plugin[key] !== 'string' || plugin[key].length === 0) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`${at} "${key}" should be a string. Received ${JSON.stringify(plugin[key])}.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
if (plugin.typePrefix !== undefined && typeof plugin.typePrefix !== 'string') {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`${at} "typePrefix" should be a string. Received ${JSON.stringify(plugin.typePrefix)}.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (plugin.options !== undefined && !isObject(plugin.options)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`${at} "options" should be an object. Received ${JSON.stringify(plugin.options)}.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
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;
|