@lowdefy/shell 3.23.2 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.bde87d1433f0625faed6.js +0 -1
- package/dist/shell/runtime.1ead93159ba56c05b969.js +0 -1
|
@@ -0,0 +1,121 @@
|
|
|
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 offline request queue in SQLite: per app, the `offline: queue` requests the shell kept while
|
|
18
|
+
// it had no signal, in the order they were made, each with the body it will send unchanged.
|
|
19
|
+
const SCHEMA = `
|
|
20
|
+
CREATE TABLE IF NOT EXISTS request_queue (
|
|
21
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
22
|
+
app_id TEXT NOT NULL,
|
|
23
|
+
seq INTEGER NOT NULL,
|
|
24
|
+
url TEXT NOT NULL,
|
|
25
|
+
body TEXT NOT NULL,
|
|
26
|
+
build_id TEXT,
|
|
27
|
+
page_id TEXT NOT NULL,
|
|
28
|
+
request_id TEXT NOT NULL,
|
|
29
|
+
status TEXT NOT NULL,
|
|
30
|
+
error TEXT,
|
|
31
|
+
attempts INTEGER NOT NULL,
|
|
32
|
+
queued_at TEXT NOT NULL
|
|
33
|
+
);
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
function toItem(row) {
|
|
37
|
+
return {
|
|
38
|
+
id: row.id,
|
|
39
|
+
url: row.url,
|
|
40
|
+
body: JSON.parse(row.body),
|
|
41
|
+
buildId: row.build_id,
|
|
42
|
+
pageId: row.page_id,
|
|
43
|
+
requestId: row.request_id,
|
|
44
|
+
status: row.status,
|
|
45
|
+
error: row.error,
|
|
46
|
+
attempts: row.attempts,
|
|
47
|
+
queuedAt: row.queued_at,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {Object} params
|
|
53
|
+
* @param {Object} params.db An expo-sqlite database (openDatabaseSync).
|
|
54
|
+
*/
|
|
55
|
+
function createSqliteQueueStore({ db }) {
|
|
56
|
+
let ready = null;
|
|
57
|
+
function init() {
|
|
58
|
+
if (ready === null) {
|
|
59
|
+
ready = db.execAsync(SCHEMA);
|
|
60
|
+
}
|
|
61
|
+
return ready;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
async add({ appId, item }) {
|
|
66
|
+
await init();
|
|
67
|
+
const last = await db.getFirstAsync(
|
|
68
|
+
'SELECT MAX(seq) AS seq FROM request_queue WHERE app_id = ?',
|
|
69
|
+
[appId]
|
|
70
|
+
);
|
|
71
|
+
await db.runAsync(
|
|
72
|
+
'INSERT INTO request_queue (id, app_id, seq, url, body, build_id, page_id, request_id, status, error, attempts, queued_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
73
|
+
[
|
|
74
|
+
item.id,
|
|
75
|
+
appId,
|
|
76
|
+
(last?.seq ?? 0) + 1,
|
|
77
|
+
item.url,
|
|
78
|
+
JSON.stringify(item.body),
|
|
79
|
+
item.buildId ?? null,
|
|
80
|
+
item.pageId,
|
|
81
|
+
item.requestId,
|
|
82
|
+
'pending',
|
|
83
|
+
null,
|
|
84
|
+
0,
|
|
85
|
+
item.queuedAt,
|
|
86
|
+
]
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
async list({ appId }) {
|
|
90
|
+
await init();
|
|
91
|
+
const rows = await db.getAllAsync(
|
|
92
|
+
'SELECT * FROM request_queue WHERE app_id = ? ORDER BY seq',
|
|
93
|
+
[appId]
|
|
94
|
+
);
|
|
95
|
+
return rows.map(toItem);
|
|
96
|
+
},
|
|
97
|
+
async update({ id, status, error = null, attempts }) {
|
|
98
|
+
await init();
|
|
99
|
+
await db.runAsync(
|
|
100
|
+
'UPDATE request_queue SET status = ?, error = ?, attempts = ? WHERE id = ?',
|
|
101
|
+
[status, error, attempts, id]
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
async remove({ id }) {
|
|
105
|
+
await init();
|
|
106
|
+
await db.runAsync('DELETE FROM request_queue WHERE id = ?', [id]);
|
|
107
|
+
},
|
|
108
|
+
async removeFailed({ appId }) {
|
|
109
|
+
await init();
|
|
110
|
+
await db.runAsync("DELETE FROM request_queue WHERE app_id = ? AND status = 'failed'", [
|
|
111
|
+
appId,
|
|
112
|
+
]);
|
|
113
|
+
},
|
|
114
|
+
async removeApp({ appId }) {
|
|
115
|
+
await init();
|
|
116
|
+
await db.runAsync('DELETE FROM request_queue WHERE app_id = ?', [appId]);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default createSqliteQueueStore;
|
|
@@ -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 queue as pages see it (GetRequestQueue, onRequestQueue): counts and the items without their
|
|
19
|
+
* bodies.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object[]} items The stored items, in order.
|
|
22
|
+
*/
|
|
23
|
+
function summarizeQueue(items) {
|
|
24
|
+
return {
|
|
25
|
+
pending: items.filter((item) => item.status === 'pending').length,
|
|
26
|
+
failed: items.filter((item) => item.status === 'failed').length,
|
|
27
|
+
items: items.map(({ attempts, error, id, pageId, queuedAt, requestId, status }) => ({
|
|
28
|
+
attempts,
|
|
29
|
+
error,
|
|
30
|
+
id,
|
|
31
|
+
pageId,
|
|
32
|
+
queuedAt,
|
|
33
|
+
requestId,
|
|
34
|
+
status,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default summarizeQueue;
|
|
@@ -0,0 +1,323 @@
|
|
|
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 { getAuthorizationHeaders } from '@lowdefy/client-core';
|
|
18
|
+
import { type } from '@lowdefy/helpers';
|
|
19
|
+
|
|
20
|
+
import fetchPage from '../sync/fetchPage.js';
|
|
21
|
+
import fetchRoot from '../sync/fetchRoot.js';
|
|
22
|
+
import fetchUser from '../sync/fetchUser.js';
|
|
23
|
+
import getCacheKey from '../cache/getCacheKey.js';
|
|
24
|
+
import getLastCacheKeyName from '../cache/getLastCacheKeyName.js';
|
|
25
|
+
import reportShellError from '../host/reportShellError.js';
|
|
26
|
+
import runLimited from '../sync/runLimited.js';
|
|
27
|
+
import toRootConfig from '../sync/toRootConfig.js';
|
|
28
|
+
|
|
29
|
+
const PAGE_FETCH_LIMIT = 4;
|
|
30
|
+
|
|
31
|
+
// Pages kept for the new root: those still in its page index. A page whose hash changed stays, and
|
|
32
|
+
// is shown, until its new version arrives.
|
|
33
|
+
function keepIndexedPages({ pages, root }) {
|
|
34
|
+
const indexed = new Set(root.pageIndex.map((entry) => entry.pageId));
|
|
35
|
+
return Object.fromEntries(Object.entries(pages).filter(([pageId]) => indexed.has(pageId)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Hashed index entries whose page the shell does not hold at that hash. A dev root has no hashes:
|
|
39
|
+
// its pages are fetched when they are opened.
|
|
40
|
+
function getChangedEntries({ pages, root }) {
|
|
41
|
+
return root.pageIndex.filter(
|
|
42
|
+
(entry) => !type.isNone(entry.hash) && pages[entry.pageId]?.hash !== entry.hash
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The state of one app in the shell, and how it follows the app's server: boot from the cache,
|
|
48
|
+
* sync the mobile root and pages, and move onto a new build or a new caller.
|
|
49
|
+
*
|
|
50
|
+
* State, read with getState and followed with subscribe:
|
|
51
|
+
* - `status`: 'loading' until a root is held (from the cache or the server), then 'ready';
|
|
52
|
+
* 'error' when there is none and the server cannot be reached.
|
|
53
|
+
* - `cacheKey`: app × caller × roles (getCacheKey) of the cache in use.
|
|
54
|
+
* - `root`, `rootEtag`, `rootConfig`, `user`, `pages` ({ [pageId]: { hash, etag, buildId,
|
|
55
|
+
* pageConfig } }): what the screens render.
|
|
56
|
+
* - `buildId`: the server build the root came from; page requests carry it.
|
|
57
|
+
* - `lowdefy`: the app-level object the pages share, new each generation.
|
|
58
|
+
* - `generation`: bumped when every screen must be rebuilt (a new caller or build).
|
|
59
|
+
* - `homeRequests`: bumped when the shell must go back to the home page (a new caller).
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} params
|
|
62
|
+
* @param {string} params.apiBase The app server's URL.
|
|
63
|
+
* @param {{ id: string }} params.app The app, from shell.json.
|
|
64
|
+
* @param {Object} params.cacheStore The page cache (createSqliteCacheStore).
|
|
65
|
+
* @param {Function} params.fetch The Fetch API, sending the shell header (withShellHeader).
|
|
66
|
+
* @param {{ get, set }} params.kv A synchronous key-value store on the device.
|
|
67
|
+
* @param {{ get }} params.tokenStore The session token store.
|
|
68
|
+
*/
|
|
69
|
+
function createAppSession({ apiBase, app, cacheStore, fetch, kv, tokenStore }) {
|
|
70
|
+
let state = {
|
|
71
|
+
status: 'loading',
|
|
72
|
+
error: null,
|
|
73
|
+
cacheKey: null,
|
|
74
|
+
root: null,
|
|
75
|
+
rootEtag: null,
|
|
76
|
+
rootConfig: null,
|
|
77
|
+
user: null,
|
|
78
|
+
pages: {},
|
|
79
|
+
buildId: null,
|
|
80
|
+
lowdefy: null,
|
|
81
|
+
generation: 0,
|
|
82
|
+
homeRequests: 0,
|
|
83
|
+
};
|
|
84
|
+
const listeners = new Set();
|
|
85
|
+
let currentPath = '/';
|
|
86
|
+
let currentInstanceId = null;
|
|
87
|
+
let homeRequestsTaken = 0;
|
|
88
|
+
let syncing = null;
|
|
89
|
+
const pageLoads = new Map();
|
|
90
|
+
|
|
91
|
+
function getState() {
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function setState(update) {
|
|
96
|
+
state = { ...state, ...update };
|
|
97
|
+
listeners.forEach((listener) => listener());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function subscribe(listener) {
|
|
101
|
+
listeners.add(listener);
|
|
102
|
+
return () => listeners.delete(listener);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function fetchWithAuth(url, init) {
|
|
106
|
+
const authorizationHeaders = getAuthorizationHeaders({
|
|
107
|
+
host: { authTransport: 'bearer', tokenStore },
|
|
108
|
+
});
|
|
109
|
+
return fetch(url, { ...init, headers: { ...init.headers, ...authorizationHeaders } });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function newGeneration(buildId) {
|
|
113
|
+
return { generation: state.generation + 1, lowdefy: { buildId } };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function setPage({ cacheKey, pageId, page }) {
|
|
117
|
+
// A caller change while the page was in flight: it belongs to the previous cache.
|
|
118
|
+
if (state.cacheKey !== cacheKey) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
setState({ pages: { ...state.pages, [pageId]: page } });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function fetchChangedPages({ cacheKey, entries }) {
|
|
125
|
+
// A page that failed keeps its held version, and is fetched again on the next sync or visit.
|
|
126
|
+
const failures = await runLimited({
|
|
127
|
+
items: entries,
|
|
128
|
+
limit: PAGE_FETCH_LIMIT,
|
|
129
|
+
fn: async (entry) => {
|
|
130
|
+
const held = state.pages[entry.pageId];
|
|
131
|
+
const result = await fetchPage({
|
|
132
|
+
apiBase,
|
|
133
|
+
etag: held?.etag,
|
|
134
|
+
fetchWithAuth,
|
|
135
|
+
path: entry.path,
|
|
136
|
+
});
|
|
137
|
+
if (result.notFound) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const page = result.notModified
|
|
141
|
+
? { ...held, hash: entry.hash }
|
|
142
|
+
: { ...result.page, hash: entry.hash };
|
|
143
|
+
await cacheStore.writePage({ cacheKey, pageId: entry.pageId, page });
|
|
144
|
+
setPage({ cacheKey, pageId: entry.pageId, page });
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
failures.forEach(({ error }) => reportShellError({ error, session: { getState } }));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runSync() {
|
|
151
|
+
const [rootResult, user] = await Promise.all([
|
|
152
|
+
fetchRoot({ apiBase, etag: state.rootEtag, fetchWithAuth }),
|
|
153
|
+
fetchUser({ apiBase, fetchWithAuth }),
|
|
154
|
+
]);
|
|
155
|
+
const cacheKey = getCacheKey({ appId: app.id, stamp: rootResult.stamp });
|
|
156
|
+
const callerChanged = state.cacheKey !== null && cacheKey !== state.cacheKey;
|
|
157
|
+
// Another caller's pages: those cached when this caller last opened the app, if any. A 304 still
|
|
158
|
+
// applies: the root is the same for both callers.
|
|
159
|
+
const heldPages =
|
|
160
|
+
cacheKey === state.cacheKey ? state.pages : (await cacheStore.read(cacheKey))?.pages ?? {};
|
|
161
|
+
const root = rootResult.notModified ? state.root : rootResult.root;
|
|
162
|
+
const pages = keepIndexedPages({ pages: heldPages, root });
|
|
163
|
+
const removed = Object.keys(heldPages).filter((pageId) => pages[pageId] === undefined);
|
|
164
|
+
await cacheStore.writeRoot({ cacheKey, entry: { root, rootEtag: rootResult.etag, user } });
|
|
165
|
+
await cacheStore.removePages({ cacheKey, pageIds: removed });
|
|
166
|
+
kv.set(getLastCacheKeyName(app.id), cacheKey);
|
|
167
|
+
const buildId = root.lowdefyApp?.buildId ?? null;
|
|
168
|
+
setState({
|
|
169
|
+
status: 'ready',
|
|
170
|
+
error: null,
|
|
171
|
+
cacheKey,
|
|
172
|
+
root,
|
|
173
|
+
rootEtag: rootResult.etag,
|
|
174
|
+
rootConfig: rootResult.notModified && !callerChanged ? state.rootConfig : toRootConfig(root),
|
|
175
|
+
user,
|
|
176
|
+
pages,
|
|
177
|
+
buildId,
|
|
178
|
+
...(callerChanged || state.lowdefy === null ? newGeneration(buildId) : {}),
|
|
179
|
+
...(callerChanged ? { homeRequests: state.homeRequests + 1 } : {}),
|
|
180
|
+
});
|
|
181
|
+
await fetchChangedPages({ cacheKey, entries: getChangedEntries({ pages, root }) });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// One sync at a time: a build change met by several requests at once syncs once.
|
|
185
|
+
function sync() {
|
|
186
|
+
if (syncing === null) {
|
|
187
|
+
syncing = runSync().finally(() => {
|
|
188
|
+
syncing = null;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return syncing;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Renders the last pages this app showed at once, then syncs with the server in the background.
|
|
195
|
+
// With nothing cached (first launch, a new caller) the shell waits for the sync.
|
|
196
|
+
async function start() {
|
|
197
|
+
const lastCacheKey = kv.get(getLastCacheKeyName(app.id));
|
|
198
|
+
const entry = type.isNone(lastCacheKey) ? null : await cacheStore.read(lastCacheKey);
|
|
199
|
+
if (entry !== null) {
|
|
200
|
+
const buildId = entry.root.lowdefyApp?.buildId ?? null;
|
|
201
|
+
setState({
|
|
202
|
+
status: 'ready',
|
|
203
|
+
cacheKey: lastCacheKey,
|
|
204
|
+
root: entry.root,
|
|
205
|
+
rootEtag: entry.rootEtag,
|
|
206
|
+
rootConfig: toRootConfig(entry.root),
|
|
207
|
+
user: entry.user,
|
|
208
|
+
pages: entry.pages,
|
|
209
|
+
buildId,
|
|
210
|
+
...newGeneration(buildId),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
await sync();
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// With a cached root the app keeps working from the cache (no signal, a server down).
|
|
217
|
+
reportShellError({ error, session: { getState } });
|
|
218
|
+
if (state.status !== 'ready') {
|
|
219
|
+
setState({ status: 'error', error });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// A page that is opened: fetched when the shell does not hold it, or holds a version that may be
|
|
225
|
+
// old (a dynamic page, built per request, or a dev page, built on demand).
|
|
226
|
+
function loadPage({ entry, path }) {
|
|
227
|
+
const held = state.pages[entry.pageId];
|
|
228
|
+
const perVisit = type.isNone(entry.hash) || held?.pageConfig?.dynamic === true;
|
|
229
|
+
if (held !== undefined && !perVisit) {
|
|
230
|
+
return Promise.resolve();
|
|
231
|
+
}
|
|
232
|
+
const fetchPath = perVisit ? path : entry.path;
|
|
233
|
+
const loadKey = `${state.cacheKey}|${entry.pageId}|${fetchPath}`;
|
|
234
|
+
if (!pageLoads.has(loadKey)) {
|
|
235
|
+
const { cacheKey } = state;
|
|
236
|
+
const load = fetchPage({ apiBase, etag: held?.etag, fetchWithAuth, path: fetchPath })
|
|
237
|
+
.then(async (result) => {
|
|
238
|
+
if (result.notFound || result.notModified) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const page = { ...result.page, hash: entry.hash ?? null };
|
|
242
|
+
await cacheStore.writePage({ cacheKey, pageId: entry.pageId, page });
|
|
243
|
+
setPage({ cacheKey, pageId: entry.pageId, page });
|
|
244
|
+
})
|
|
245
|
+
.finally(() => pageLoads.delete(loadKey));
|
|
246
|
+
pageLoads.set(loadKey, load);
|
|
247
|
+
}
|
|
248
|
+
return pageLoads.get(loadKey);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// A path no mobile page has: { surface: 'web' } when the server has a web page there.
|
|
252
|
+
async function fetchOtherPage(path) {
|
|
253
|
+
return fetchPage({ apiBase, fetchWithAuth, path });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// The core's onBuildChanged: a page request was refused for another build and could not be
|
|
257
|
+
// retried (its page changed with the build). Like the web's reload: sync, then rebuild every
|
|
258
|
+
// screen on the current route.
|
|
259
|
+
async function handleBuildChanged() {
|
|
260
|
+
await sync();
|
|
261
|
+
setState(newGeneration(state.buildId));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// The core's onCallerChanged: the server answered as another caller (sign-in, sign-out, an
|
|
265
|
+
// organisation or role change). Drops the screens, syncs under the new caller's key and goes home.
|
|
266
|
+
async function handleCallerChanged() {
|
|
267
|
+
setState({
|
|
268
|
+
status: 'loading',
|
|
269
|
+
...newGeneration(state.buildId),
|
|
270
|
+
homeRequests: state.homeRequests + 1,
|
|
271
|
+
});
|
|
272
|
+
try {
|
|
273
|
+
await sync();
|
|
274
|
+
} catch (error) {
|
|
275
|
+
reportShellError({ error, session: { getState } });
|
|
276
|
+
setState({ status: 'error', error });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// The entry the page a request is for, read from a /api/request/<pageId>/<requestId> URL.
|
|
281
|
+
function getRequestPageEntry(url) {
|
|
282
|
+
const match = /\/api\/request\/([^/]+)\//.exec(url);
|
|
283
|
+
if (match === null || state.root === null) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const pageId = decodeURIComponent(match[1]);
|
|
287
|
+
return state.root.pageIndex.find((entry) => entry.pageId === pageId) ?? null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// True once for each home request: the frame that is mounted when it comes goes home.
|
|
291
|
+
function takeHomeRequest() {
|
|
292
|
+
if (state.homeRequests <= homeRequestsTaken) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
homeRequestsTaken = state.homeRequests;
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
apiBase,
|
|
301
|
+
app,
|
|
302
|
+
fetchOtherPage,
|
|
303
|
+
getCurrentInstanceId: () => currentInstanceId,
|
|
304
|
+
getCurrentPath: () => currentPath,
|
|
305
|
+
getRequestPageEntry,
|
|
306
|
+
getState,
|
|
307
|
+
handleBuildChanged,
|
|
308
|
+
handleCallerChanged,
|
|
309
|
+
loadPage,
|
|
310
|
+
// The focused screen: its route (path and query) and its route instance, which keys its page
|
|
311
|
+
// context.
|
|
312
|
+
setCurrentPath: (path, instanceId = null) => {
|
|
313
|
+
currentPath = path;
|
|
314
|
+
currentInstanceId = instanceId;
|
|
315
|
+
},
|
|
316
|
+
start,
|
|
317
|
+
subscribe,
|
|
318
|
+
sync,
|
|
319
|
+
takeHomeRequest,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export default createAppSession;
|