@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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useEffect } from 'react';
|
|
18
|
+
import { Platform } from 'react-native';
|
|
19
|
+
import { fetch as expoFetch } from 'expo/fetch';
|
|
20
|
+
import { captureRef } from 'react-native-view-shot';
|
|
21
|
+
|
|
22
|
+
import createDevTabClient from './createDevTabClient.js';
|
|
23
|
+
import createNativeRecorder from '../recorder/createNativeRecorder.js';
|
|
24
|
+
import createStepRunner from '../steps/createStepRunner.js';
|
|
25
|
+
import reloadApp from './reloadApp.js';
|
|
26
|
+
import withShellHeader from '../../host/withShellHeader.js';
|
|
27
|
+
import { useShell } from '../../ShellContext.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Joins the dev server's tab channel for the app that is open (createDevTabClient), records what
|
|
31
|
+
* the developer does in it (createNativeRecorder), and tells Lowdefy Dev which app that is through
|
|
32
|
+
* onApp, so the shake menu can reload it or clear its data. Rendered inside the app's provider;
|
|
33
|
+
* draws nothing.
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} props
|
|
36
|
+
* @param {Object} props.injections The device injections (createDeviceInjections).
|
|
37
|
+
* @param {Object} props.kv The device store.
|
|
38
|
+
* @param {Object} props.logs The log buffer (createLogBuffer).
|
|
39
|
+
* @param {(app: Object|null) => void} props.onApp
|
|
40
|
+
* @param {{ current: Object }} props.rootRef The view the whole app is drawn in, for screenshots.
|
|
41
|
+
*/
|
|
42
|
+
function DevTabClient({ injections, kv, logs, onApp, rootRef }) {
|
|
43
|
+
const app = useShell();
|
|
44
|
+
const { session, shell } = app;
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
onApp(app);
|
|
48
|
+
return () => onApp(null);
|
|
49
|
+
}, [app, onApp]);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const getLowdefy = () => session.getState().lowdefy;
|
|
53
|
+
const withHeaders = (fetch) =>
|
|
54
|
+
withShellHeader({ fetch: shell.transport.wrapFetch(fetch), shellVersion: shell.version });
|
|
55
|
+
const runner = createStepRunner({
|
|
56
|
+
capture: () => captureRef(rootRef, { format: 'png', result: 'base64' }),
|
|
57
|
+
getLowdefy,
|
|
58
|
+
});
|
|
59
|
+
const fetch = withHeaders(globalThis.fetch);
|
|
60
|
+
const recorder = createNativeRecorder({
|
|
61
|
+
apiBase: session.apiBase,
|
|
62
|
+
device: Platform.OS,
|
|
63
|
+
fetch,
|
|
64
|
+
getBuild: () => session.getState().buildId ?? null,
|
|
65
|
+
getLowdefy,
|
|
66
|
+
kv,
|
|
67
|
+
shellVersion: shell.version,
|
|
68
|
+
});
|
|
69
|
+
const client = createDevTabClient({
|
|
70
|
+
apiBase: session.apiBase,
|
|
71
|
+
device: {
|
|
72
|
+
platform: Platform.OS,
|
|
73
|
+
app: Platform.OS === 'ios' ? shell.ios?.bundleIdentifier : shell.android?.package,
|
|
74
|
+
},
|
|
75
|
+
fetch,
|
|
76
|
+
getLowdefy,
|
|
77
|
+
log: (message) => logs.add('info', message),
|
|
78
|
+
onInject: ({ injection }) => injections.inject(injection ?? {}),
|
|
79
|
+
onReload: async () => {
|
|
80
|
+
await recorder.flush();
|
|
81
|
+
await reloadApp({ session });
|
|
82
|
+
},
|
|
83
|
+
onStep: ({ step }) => runner.run({ step }),
|
|
84
|
+
streamFetch: withHeaders(expoFetch),
|
|
85
|
+
});
|
|
86
|
+
recorder.start();
|
|
87
|
+
client.start();
|
|
88
|
+
return () => {
|
|
89
|
+
client.stop();
|
|
90
|
+
recorder.stop();
|
|
91
|
+
};
|
|
92
|
+
}, [injections, kv, logs, rootRef, session, shell]);
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default DevTabClient;
|
|
@@ -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
|
+
import { serializer, type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import findPageContext from './findPageContext.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The answer to an eval-request: the operator expression evaluated with the page instance's own
|
|
23
|
+
* parser, `{ value, errors }`, as a browser tab answers it (server-dev client/Inspector.jsx). The
|
|
24
|
+
* expression may arrive as JSON text, as an MCP tool argument passes it.
|
|
25
|
+
*/
|
|
26
|
+
function answerEval({ expression, lowdefy, page }) {
|
|
27
|
+
const { context, error } = findPageContext({ lowdefy, page });
|
|
28
|
+
if (error) {
|
|
29
|
+
return { error };
|
|
30
|
+
}
|
|
31
|
+
const input = type.isString(expression) ? JSON.parse(expression) : expression;
|
|
32
|
+
const { output, errors } = context._internal.parser.parse({ input, location: 'agent_eval' });
|
|
33
|
+
return {
|
|
34
|
+
value: serializer.serializeToString(output),
|
|
35
|
+
errors: errors.map((parseError) => parseError.message),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default answerEval;
|
|
@@ -0,0 +1,45 @@
|
|
|
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 { serializer } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import findPageContext from './findPageContext.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The answer to an inspect-request: the page instance's live state, as a browser tab answers it
|
|
23
|
+
* (server-dev client/Inspector.jsx), a serialized string the dev server deserializes
|
|
24
|
+
* (lib/docs/inspectStateFromTab.js). The URL query is the shown route's.
|
|
25
|
+
*/
|
|
26
|
+
function answerInspect({ lowdefy, page }) {
|
|
27
|
+
const { context, error } = findPageContext({ lowdefy, page });
|
|
28
|
+
if (error) {
|
|
29
|
+
return { error };
|
|
30
|
+
}
|
|
31
|
+
return serializer.serializeToString({
|
|
32
|
+
pageId: page.pageId,
|
|
33
|
+
pathParams: context.pathParams,
|
|
34
|
+
instanceKey: page.instanceKey,
|
|
35
|
+
state: context.state,
|
|
36
|
+
requests: context.requests,
|
|
37
|
+
eventLog: (context.eventLog ?? []).slice(-50),
|
|
38
|
+
global: lowdefy.lowdefyGlobal,
|
|
39
|
+
user: lowdefy.user,
|
|
40
|
+
input: lowdefy.inputs?.[page.instanceKey],
|
|
41
|
+
urlQuery: lowdefy._internal.globals.window.location.search,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default answerInspect;
|
|
@@ -0,0 +1,272 @@
|
|
|
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 { type } from '@lowdefy/helpers';
|
|
18
|
+
import devPassiveHeader from '@lowdefy/node-utils/devPassiveHeader.js';
|
|
19
|
+
|
|
20
|
+
import answerEval from './answerEval.js';
|
|
21
|
+
import answerInspect from './answerInspect.js';
|
|
22
|
+
import DEV_CLIENT_MARKER from './devClientMarker.js';
|
|
23
|
+
import describeTabError from './describeTabError.js';
|
|
24
|
+
import getShownPage from './getShownPage.js';
|
|
25
|
+
import readEventStream from './readEventStream.js';
|
|
26
|
+
|
|
27
|
+
const FIRST_EVENT_MS = 5000;
|
|
28
|
+
const RECONNECT_MS = 1000;
|
|
29
|
+
const PAGE_CHECK_MS = 500;
|
|
30
|
+
|
|
31
|
+
function sleep(ms) {
|
|
32
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The phone's platform and app id ride on every registration, so the dev server's device tools
|
|
36
|
+
// know a simulator from an Android device (server-dev src/routes/reload.js).
|
|
37
|
+
function deviceQuery(device) {
|
|
38
|
+
const params = new URLSearchParams({ client: DEV_CLIENT_MARKER });
|
|
39
|
+
if (type.isString(device?.platform)) {
|
|
40
|
+
params.set('platform', device.platform);
|
|
41
|
+
}
|
|
42
|
+
if (type.isString(device?.app)) {
|
|
43
|
+
params.set('app', device.app);
|
|
44
|
+
}
|
|
45
|
+
return params.toString();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The instance a request names (the dev server picks it), else the one on screen.
|
|
49
|
+
function requestedPage({ request, shownPage }) {
|
|
50
|
+
if (type.isString(request.instanceKey)) {
|
|
51
|
+
return {
|
|
52
|
+
pageId: request.pageId,
|
|
53
|
+
pathParams: request.pathParams,
|
|
54
|
+
instanceKey: request.instanceKey,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return shownPage;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Lowdefy Dev's dev-tab client: the shell joins the dev server's tab channel as a browser tab does
|
|
62
|
+
* (server-dev src/routes/reload.js, lib/docs/tabChannel.js, src/routes/devInspect.js), so agents
|
|
63
|
+
* reach it with the tools they use on a browser tab.
|
|
64
|
+
*
|
|
65
|
+
* - It registers on /api/reload, reading the event stream from a streaming fetch body. When the
|
|
66
|
+
* stream cannot be read (no `tab` event within 5 s), it polls /api/reload?transport=poll every
|
|
67
|
+
* second instead.
|
|
68
|
+
* - It posts the page instance on screen to /api/dev-inspect/page whenever it changes.
|
|
69
|
+
* - It names the phone's platform and app id (`device`) when it registers, for the device tools.
|
|
70
|
+
* - `reload` runs onReload. `inspect-request` and `eval-request` are answered on /api/dev-inspect
|
|
71
|
+
* with the payloads a browser tab posts; `step-request` runs a journey step (onStep), and
|
|
72
|
+
* `inject` fakes the next device result (onInject: `{ requestId, inject: { kind, value, name } }`).
|
|
73
|
+
*
|
|
74
|
+
* @param {Object} params
|
|
75
|
+
* @param {string} params.apiBase The dev server's URL.
|
|
76
|
+
* @param {{ platform: string, app: string }} [params.device] The phone's platform ('ios' or
|
|
77
|
+
* 'android') and app id (bundle id or package).
|
|
78
|
+
* @param {Function} params.fetch The Fetch API for posts and polls (pairing and shell headers).
|
|
79
|
+
* @param {Function} params.streamFetch A Fetch API whose response body streams (expo/fetch), with
|
|
80
|
+
* the same headers.
|
|
81
|
+
* @param {() => Object|null} params.getLowdefy The open app's lowdefy object.
|
|
82
|
+
* @param {() => Promise} params.onReload
|
|
83
|
+
* @param {({ injection }) => Object} params.onInject
|
|
84
|
+
* @param {({ step, page }) => Promise<Object>} params.onStep
|
|
85
|
+
* @param {(message: string) => void} [params.log]
|
|
86
|
+
* @param {number} [params.pollInterval] Ms between polls.
|
|
87
|
+
*/
|
|
88
|
+
function createDevTabClient({
|
|
89
|
+
apiBase,
|
|
90
|
+
device,
|
|
91
|
+
fetch,
|
|
92
|
+
getLowdefy,
|
|
93
|
+
log = () => undefined,
|
|
94
|
+
onInject,
|
|
95
|
+
onReload,
|
|
96
|
+
onStep,
|
|
97
|
+
pollInterval = 1000,
|
|
98
|
+
streamFetch,
|
|
99
|
+
}) {
|
|
100
|
+
let stopped = false;
|
|
101
|
+
let tabId = null;
|
|
102
|
+
let postedPage = null;
|
|
103
|
+
let streamAbort = null;
|
|
104
|
+
let pageTimer = null;
|
|
105
|
+
|
|
106
|
+
async function post(path, body) {
|
|
107
|
+
try {
|
|
108
|
+
await fetch(`${apiBase}/api/dev-inspect${path}`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'Content-Type': 'application/json' },
|
|
111
|
+
body: JSON.stringify(body),
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
// A failed answer leaves the agent's request to time out on the server.
|
|
115
|
+
log(`Dev tab: could not reach the dev server (${error.message}).`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function postShownPage() {
|
|
120
|
+
const page = getShownPage(getLowdefy());
|
|
121
|
+
if (tabId === null || page === null) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const key = `${tabId}|${page.instanceKey}`;
|
|
125
|
+
if (key === postedPage) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
postedPage = key;
|
|
129
|
+
post('/page', { tabId, ...page });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function respond({ event, request }) {
|
|
133
|
+
const lowdefy = getLowdefy();
|
|
134
|
+
const page = requestedPage({ request, shownPage: getShownPage(lowdefy) });
|
|
135
|
+
if (event === 'inspect-request') {
|
|
136
|
+
return answerInspect({ lowdefy, page });
|
|
137
|
+
}
|
|
138
|
+
if (event === 'eval-request') {
|
|
139
|
+
return answerEval({ expression: request.expression, lowdefy, page });
|
|
140
|
+
}
|
|
141
|
+
if (event === 'inject') {
|
|
142
|
+
return onInject({ injection: request.inject });
|
|
143
|
+
}
|
|
144
|
+
return onStep({ step: request.step, page });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function answer({ event, data }) {
|
|
148
|
+
let requestId;
|
|
149
|
+
try {
|
|
150
|
+
const request = JSON.parse(data);
|
|
151
|
+
requestId = request.requestId;
|
|
152
|
+
const result = await respond({ event, request });
|
|
153
|
+
await post('', { requestId, result });
|
|
154
|
+
} catch (error) {
|
|
155
|
+
await post('', { requestId, result: { error: describeTabError(error) } });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function handleEvent({ event, data }) {
|
|
160
|
+
switch (event) {
|
|
161
|
+
case 'tab':
|
|
162
|
+
tabId = JSON.parse(data).tabId;
|
|
163
|
+
postedPage = null;
|
|
164
|
+
log(`Dev tab: joined the dev server as tab ${tabId}.`);
|
|
165
|
+
postShownPage();
|
|
166
|
+
return;
|
|
167
|
+
case 'reload':
|
|
168
|
+
log('Dev tab: the dev server rebuilt the app; reloading.');
|
|
169
|
+
await onReload();
|
|
170
|
+
return;
|
|
171
|
+
case 'inspect-request':
|
|
172
|
+
case 'eval-request':
|
|
173
|
+
case 'step-request':
|
|
174
|
+
case 'inject':
|
|
175
|
+
await answer({ event, data });
|
|
176
|
+
return;
|
|
177
|
+
default:
|
|
178
|
+
// ping
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function dispatch(message) {
|
|
184
|
+
handleEvent(message).catch((error) =>
|
|
185
|
+
log(`Dev tab: ${message.event} failed: ${error.message}`)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Resolves 'ended' when an open stream ends (the dev server restarted), 'unreachable' when the
|
|
190
|
+
// server did not answer, and 'unsupported' when no event arrived over a stream that opened.
|
|
191
|
+
async function runStream() {
|
|
192
|
+
let received = false;
|
|
193
|
+
const controller = new AbortController();
|
|
194
|
+
streamAbort = controller;
|
|
195
|
+
const firstEventTimer = setTimeout(() => {
|
|
196
|
+
if (!received) controller.abort();
|
|
197
|
+
}, FIRST_EVENT_MS);
|
|
198
|
+
try {
|
|
199
|
+
const res = await streamFetch(`${apiBase}/api/reload?${deviceQuery(device)}`, {
|
|
200
|
+
headers: { Accept: 'text/event-stream' },
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
return 'unreachable';
|
|
205
|
+
}
|
|
206
|
+
if (type.isNone(res.body?.getReader)) {
|
|
207
|
+
return 'unsupported';
|
|
208
|
+
}
|
|
209
|
+
await readEventStream({
|
|
210
|
+
body: res.body,
|
|
211
|
+
onEvent: (message) => {
|
|
212
|
+
received = true;
|
|
213
|
+
dispatch(message);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
return 'ended';
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (received) return 'ended';
|
|
219
|
+
return controller.signal.aborted ? 'unsupported' : 'unreachable';
|
|
220
|
+
} finally {
|
|
221
|
+
clearTimeout(firstEventTimer);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function runPolls() {
|
|
226
|
+
while (!stopped) {
|
|
227
|
+
const tabQuery = tabId === null ? '' : `&tabId=${encodeURIComponent(tabId)}`;
|
|
228
|
+
try {
|
|
229
|
+
const res = await fetch(
|
|
230
|
+
`${apiBase}/api/reload?transport=poll&${deviceQuery(device)}${tabQuery}`,
|
|
231
|
+
{ headers: { [devPassiveHeader]: '1' } }
|
|
232
|
+
);
|
|
233
|
+
if (res.ok) {
|
|
234
|
+
const { events } = await res.json();
|
|
235
|
+
events.forEach(dispatch);
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// The dev server is restarting: poll again.
|
|
239
|
+
}
|
|
240
|
+
await sleep(pollInterval);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function run() {
|
|
245
|
+
while (!stopped) {
|
|
246
|
+
const outcome = await runStream();
|
|
247
|
+
if (stopped) return;
|
|
248
|
+
if (outcome === 'unsupported') {
|
|
249
|
+
log('Dev tab: the event stream could not be read; polling every second.');
|
|
250
|
+
tabId = null;
|
|
251
|
+
await runPolls();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
await sleep(RECONNECT_MS);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function start() {
|
|
259
|
+
pageTimer = setInterval(postShownPage, PAGE_CHECK_MS);
|
|
260
|
+
run();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function stop() {
|
|
264
|
+
stopped = true;
|
|
265
|
+
streamAbort?.abort();
|
|
266
|
+
clearInterval(pageTimer);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { getTabId: () => tabId, start, stop };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export default createDevTabClient;
|