@lowdefy/server-dev 6.1.0 → 7.1.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/client/App.jsx +52 -24
- package/client/Inspector.jsx +73 -30
- package/client/JourneyObserver.jsx +47 -0
- package/client/Page.jsx +54 -22
- package/client/Recorder.jsx +73 -0
- package/client/Routing.jsx +37 -13
- package/client/clientBuildImports.test.mjs +2 -0
- package/client/devStreamConnections.test.mjs +40 -0
- package/client/feedback/FeedbackMount.jsx +2 -2
- package/client/feedback/FeedbackOverlay.jsx +7 -4
- package/client/feedback/feedbackStyles.js +3 -0
- package/client/journeyObserver/startJourneyObserver.js +101 -0
- package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
- package/client/openInEditor/OpenInEditorListener.jsx +2 -0
- package/client/recorder/buildEngineRecord.js +37 -0
- package/client/recorder/buildInteractionRecord.js +64 -0
- package/client/recorder/buildTraceEvent.js +57 -0
- package/client/recorder/buildTraceEvent.test.mjs +81 -0
- package/client/recorder/chunkRecords.js +69 -0
- package/client/recorder/chunkRecords.test.mjs +59 -0
- package/client/recorder/collectEventActions.js +30 -0
- package/client/recorder/collectEventEndpoints.js +40 -0
- package/client/recorder/collectEventRequests.js +39 -0
- package/client/recorder/createPairingBuffer.js +187 -0
- package/client/recorder/createPairingBuffer.test.mjs +405 -0
- package/client/recorder/createPasswordRedactor.js +84 -0
- package/client/recorder/createRecordBatcher.js +54 -0
- package/client/recorder/createRecordBatcher.test.mjs +76 -0
- package/client/recorder/createRecorder.js +216 -0
- package/client/recorder/createRecorder.test.mjs +318 -0
- package/client/recorder/createRecordingSession.js +61 -0
- package/client/recorder/createRecordingSession.test.mjs +72 -0
- package/client/recorder/diffStateWrites.js +77 -0
- package/client/recorder/diffStateWrites.test.mjs +66 -0
- package/client/recorder/isEventCall.js +30 -0
- package/client/recorder/jsonType.js +39 -0
- package/client/recorder/keyChord.js +47 -0
- package/client/recorder/keyChord.test.mjs +50 -0
- package/client/recorder/sendRecords.js +40 -0
- package/client/recorder/toJsonSafe.js +29 -0
- package/lib/client/BuildingPage.jsx +3 -1
- package/lib/client/ErrorBar.jsx +17 -13
- package/lib/client/RedirectingPage.jsx +56 -0
- package/lib/client/auth/Auth.jsx +11 -6
- package/lib/client/auth/AuthConfigured.jsx +222 -36
- package/lib/client/auth/AuthNotConfigured.js +45 -4
- package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
- package/lib/client/auth/authTemplateSync.test.mjs +40 -0
- package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
- package/lib/client/auth/createFetchCheckingBuild.js +45 -0
- package/lib/client/bundleBuildId.js +25 -0
- package/lib/client/collectVisibleBlockIds.js +51 -0
- package/lib/client/getCspNonce.js +25 -0
- package/lib/client/getPagePath.js +36 -0
- package/lib/client/getPagePath.test.mjs +51 -0
- package/lib/client/utils/formatErrorsForCopy.js +30 -0
- package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
- package/lib/client/utils/getErrorBarColor.js +25 -0
- package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
- package/lib/client/utils/request.js +8 -1
- package/lib/client/utils/usePageConfig.js +89 -25
- package/lib/client/utils/usePageConfig.test.mjs +198 -13
- package/lib/configTree/buildConfigTree.mjs +58 -0
- package/lib/configTree/buildConfigTree.test.mjs +77 -0
- package/lib/configTree/collectConfigText.mjs +101 -0
- package/lib/configTree/collectConfigText.test.mjs +116 -0
- package/lib/configTree/prepareScratchServer.mjs +46 -0
- package/lib/configTree/runConfigTreeBuild.mjs +88 -0
- package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
- package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
- package/lib/docs/JourneyStepError.js +30 -0
- package/lib/docs/acquireBrowserSlot.js +30 -0
- package/lib/docs/addNestedEndpoints.js +54 -0
- package/lib/docs/addNestedEndpoints.test.mjs +185 -0
- package/lib/docs/addWriteFlags.js +77 -0
- package/lib/docs/buildEditedPages.js +50 -0
- package/lib/docs/buildWaitHeader.js +21 -0
- package/lib/docs/captureAnnotatedScreenshot.js +23 -12
- package/lib/docs/checkAgentFiles.js +148 -0
- package/lib/docs/checkAgentFiles.test.mjs +225 -0
- package/lib/docs/checkStepReferences.js +239 -0
- package/lib/docs/checkStepReferences.test.mjs +173 -0
- package/lib/docs/checkpointStore.js +3 -0
- package/lib/docs/clientErrorStore.js +18 -1
- package/lib/docs/clientErrorStore.test.mjs +46 -0
- package/lib/docs/collectExercised.js +52 -0
- package/lib/docs/createBrowserLifecycle.js +102 -0
- package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
- package/lib/docs/createDocsMcpServer.js +345 -388
- package/lib/docs/createDocsMcpServer.test.mjs +644 -4
- package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
- package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
- package/lib/docs/createJourneyActors.js +213 -0
- package/lib/docs/createJourneyActors.test.mjs +79 -0
- package/lib/docs/createJourneyObservations.js +80 -0
- package/lib/docs/createLeftOriginError.js +26 -0
- package/lib/docs/createNetworkCounter.js +101 -0
- package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/describeTargetKey.js +29 -0
- package/lib/docs/dataSets/deserializeFixture.js +34 -0
- package/lib/docs/dataSets/drainSessionWork.js +37 -0
- package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
- package/lib/docs/dataSets/duplicateIndexName.js +24 -0
- package/lib/docs/dataSets/getDataStore.js +106 -0
- package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
- package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
- package/lib/docs/dataSets/insertFixture.js +57 -0
- package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
- package/lib/docs/dataSets/isDataStoreGone.js +29 -0
- package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
- package/lib/docs/dataSets/openDataSession.js +133 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
- package/lib/docs/dataSets/pickDataStorePort.js +58 -0
- package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
- package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
- package/lib/docs/dataSets/readDataSession.js +41 -0
- package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
- package/lib/docs/dataSets/readDataSet.js +83 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
- package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
- package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
- package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
- package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
- package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
- package/lib/docs/devEventBus.js +136 -0
- package/lib/docs/devEventBus.test.mjs +269 -0
- package/lib/docs/devToolDefinitions.js +529 -0
- package/lib/docs/docSources.js +21 -0
- package/lib/docs/docs.test.mjs +171 -15
- package/lib/docs/docsIndex.test.mjs +377 -0
- package/lib/docs/evalOperator.js +5 -3
- package/lib/docs/evalOperatorHeadless.js +84 -12
- package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
- package/lib/docs/evalOperatorInTab.js +2 -1
- package/lib/docs/findConfig.js +95 -49
- package/lib/docs/fitResponse.js +81 -0
- package/lib/docs/fitResponse.test.mjs +128 -0
- package/lib/docs/formatFeedback.js +26 -5
- package/lib/docs/formatFeedback.test.mjs +48 -0
- package/lib/docs/getAppMap.js +7 -1
- package/lib/docs/getBrowser.js +150 -80
- package/lib/docs/getBrowser.test.mjs +407 -16
- package/lib/docs/getBuildId.js +61 -0
- package/lib/docs/getBuildId.test.mjs +84 -0
- package/lib/docs/getBuildStatus.js +81 -7
- package/lib/docs/getBuildStatus.test.mjs +229 -0
- package/lib/docs/getBuildStatusAfterEdits.js +35 -0
- package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
- package/lib/docs/getCoreDoc.js +9 -1
- package/lib/docs/getDoc.js +100 -0
- package/lib/docs/getDocsIndex.js +152 -0
- package/lib/docs/getHazards.js +115 -0
- package/lib/docs/getHazards.test.mjs +175 -0
- package/lib/docs/getMailOutboxDirectory.js +26 -0
- package/lib/docs/getOverview.js +79 -13
- package/lib/docs/getOverviewBasePath.test.mjs +37 -0
- package/lib/docs/getPageBuildStatus.js +46 -0
- package/lib/docs/getPluginDoc.js +21 -23
- package/lib/docs/getSchema.js +33 -1
- package/lib/docs/getStaleStatus.js +36 -0
- package/lib/docs/getStaleStatus.test.mjs +74 -0
- package/lib/docs/guardJourneyOrigin.js +39 -0
- package/lib/docs/inspectState.js +5 -3
- package/lib/docs/inspectStateFromTab.js +2 -2
- package/lib/docs/inspectStateHeadless.js +89 -27
- package/lib/docs/inspectStateHeadless.test.mjs +20 -1
- package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
- package/lib/docs/installHeadlessShell.js +123 -0
- package/lib/docs/installHeadlessShell.test.mjs +70 -0
- package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
- package/lib/docs/isHeadlessShellIncomplete.js +34 -0
- package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
- package/lib/docs/isPageReady.js +77 -0
- package/lib/docs/isPageReady.test.mjs +235 -0
- package/lib/docs/isRunErrorEntry.js +25 -0
- package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
- package/lib/docs/journeyRunRecording.js +35 -0
- package/lib/docs/journeyRunRecording.test.mjs +39 -0
- package/lib/docs/keepEventStreamAlive.js +68 -0
- package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
- package/lib/docs/launchBrowser.js +83 -0
- package/lib/docs/launchBrowser.test.mjs +175 -0
- package/lib/docs/listModuleDocEntries.js +69 -0
- package/lib/docs/listPackageDocFiles.js +67 -0
- package/lib/docs/listPluginDocEntries.js +111 -0
- package/lib/docs/listPlugins.js +9 -58
- package/lib/docs/listTypes.js +14 -6
- package/lib/docs/loadFrameworkHazards.js +40 -0
- package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
- package/lib/docs/loadState.js +103 -35
- package/lib/docs/makeModuleManifestDoc.js +174 -0
- package/lib/docs/matchExercisedPages.js +35 -0
- package/lib/docs/matchJourneyRoute.js +69 -0
- package/lib/docs/matchJourneyRoute.test.mjs +178 -0
- package/lib/docs/mergeNetworkSnapshots.js +49 -0
- package/lib/docs/nextJourneySequence.js +30 -0
- package/lib/docs/noBrowserError.js +22 -0
- package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
- package/lib/docs/observe/claimExpectedErrors.js +67 -0
- package/lib/docs/observe/createJourneyAppErrors.js +236 -0
- package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
- package/lib/docs/observe/evaluateInvariants.js +183 -0
- package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
- package/lib/docs/observe/findingKey.js +29 -0
- package/lib/docs/observe/hasNoEffect.js +31 -0
- package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
- package/lib/docs/observe/installStepObserver.js +72 -0
- package/lib/docs/observe/isAppApiUrl.js +33 -0
- package/lib/docs/observe/parseAppApiUrl.js +37 -0
- package/lib/docs/observe/readStepObserver.js +42 -0
- package/lib/docs/observe/relativeSource.js +29 -0
- package/lib/docs/observe/resolveConfigKeySource.js +33 -0
- package/lib/docs/observe/takeJourneyEvents.js +36 -0
- package/lib/docs/observe/toAppErrorFailure.js +61 -0
- package/lib/docs/observe/usesSearchStage.js +45 -0
- package/lib/docs/observe/waitForClientErrorReports.js +31 -0
- package/lib/docs/observe/watchJourneyContext.js +75 -0
- package/lib/docs/observe/withAppErrors.js +31 -0
- package/lib/docs/observe/withAppErrors.test.mjs +101 -0
- package/lib/docs/openJourney.js +94 -0
- package/lib/docs/openJourneyEmail.js +41 -0
- package/lib/docs/openJourneyEmail.test.mjs +187 -0
- package/lib/docs/pageBlocks.js +47 -0
- package/lib/docs/pageBuildStatus.test.mjs +103 -0
- package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
- package/lib/docs/rails/authEngineActions.js +53 -0
- package/lib/docs/rails/authEngineActions.test.mjs +69 -0
- package/lib/docs/rails/authEngineMethods.js +52 -0
- package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
- package/lib/docs/rails/checkAuthActionsRan.js +66 -0
- package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
- package/lib/docs/rails/checkClickRails.js +56 -0
- package/lib/docs/rails/createDataSetRails.js +82 -0
- package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
- package/lib/docs/rails/findExternalBlocks.js +147 -0
- package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
- package/lib/docs/rails/installAuthActionRecorder.js +56 -0
- package/lib/docs/rails/readAuthActionRecorder.js +32 -0
- package/lib/docs/rails/readBuiltArtifact.js +30 -0
- package/lib/docs/readConnectionPlugins.js +26 -0
- package/lib/docs/readDevAuthMode.js +32 -0
- package/lib/docs/readDocEntry.js +40 -0
- package/lib/docs/readDocTitle.js +26 -0
- package/lib/docs/readJourneyEmailMatch.js +57 -0
- package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
- package/lib/docs/readJourneySession.js +56 -0
- package/lib/docs/readMailOutbox.js +37 -0
- package/lib/docs/readPagePath.js +27 -0
- package/lib/docs/readPluginPackages.js +99 -0
- package/lib/docs/readProxyBuildWait.js +38 -0
- package/lib/docs/readRoutineSteps.js +45 -0
- package/lib/docs/registerShownTextEngine.js +118 -0
- package/lib/docs/requestRestart.js +30 -0
- package/lib/docs/requestRestart.test.mjs +49 -0
- package/lib/docs/resolvePageInstance.js +46 -0
- package/lib/docs/resolvePageInstance.test.mjs +81 -0
- package/lib/docs/resolveSource.js +11 -7
- package/lib/docs/resolveSource.test.mjs +1 -1
- package/lib/docs/resolveToolCaller.js +62 -0
- package/lib/docs/reviewPage.js +51 -0
- package/lib/docs/reviewPageBuilds.js +56 -0
- package/lib/docs/reviewPageBuilds.test.mjs +207 -0
- package/lib/docs/runCheck.js +75 -0
- package/lib/docs/runCheck.test.mjs +91 -0
- package/lib/docs/runCheckChild.js +49 -0
- package/lib/docs/runEndpoint.js +185 -0
- package/lib/docs/runEndpoint.test.mjs +509 -0
- package/lib/docs/runErrorBuffers.js +99 -0
- package/lib/docs/runErrorBuffers.test.mjs +95 -0
- package/lib/docs/runJourney.js +347 -0
- package/lib/docs/runJourney.test.mjs +2383 -0
- package/lib/docs/runJourneyAsUsers.js +44 -0
- package/lib/docs/runJourneyDataSet.test.mjs +274 -0
- package/lib/docs/runJourneySteps.js +1320 -0
- package/lib/docs/runRequest.js +100 -59
- package/lib/docs/runRequest.test.mjs +190 -0
- package/lib/docs/saveAnnotatedScreenshot.js +1 -1
- package/lib/docs/scaffoldPage.js +3 -1
- package/lib/docs/screenshotPage.js +163 -11
- package/lib/docs/screenshotPage.test.mjs +263 -1
- package/lib/docs/screenshotPageClip.test.mjs +4 -0
- package/lib/docs/searchDocs.js +82 -30
- package/lib/docs/searchDocs.test.mjs +100 -0
- package/lib/docs/searchIcons.js +90 -0
- package/lib/docs/searchIcons.test.mjs +127 -0
- package/lib/docs/selectExercised.js +39 -0
- package/lib/docs/selectFinalState.js +66 -0
- package/lib/docs/selectFinalState.test.mjs +51 -0
- package/lib/docs/serverErrorStore.js +54 -0
- package/lib/docs/serverErrorStore.test.mjs +50 -0
- package/lib/docs/setupTestFixtures.mjs +133 -39
- package/lib/docs/shownTextSelector.js +28 -0
- package/lib/docs/snapshotState.js +13 -5
- package/lib/docs/splitSearchTerms.js +62 -0
- package/lib/docs/splitSearchTerms.test.mjs +50 -0
- package/lib/docs/stateCheckpoints.test.mjs +77 -0
- package/lib/docs/tabAvailable.js +6 -5
- package/lib/docs/tabChannel.js +127 -21
- package/lib/docs/tabChannel.test.mjs +187 -29
- package/lib/docs/unsettledPageNote.js +24 -0
- package/lib/docs/validateJourneyMail.js +36 -0
- package/lib/docs/validateJourneyTimeout.js +38 -0
- package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
- package/lib/docs/validateRecordingParam.js +42 -0
- package/lib/docs/validateStateSelection.js +33 -0
- package/lib/docs/validateStateSelection.test.mjs +33 -0
- package/lib/docs/validateViewport.js +51 -0
- package/lib/docs/validateViewport.test.mjs +54 -0
- package/lib/docs/waitForBuild.js +50 -0
- package/lib/docs/waitForBuild.test.mjs +75 -0
- package/lib/docs/waitForJourneyEmail.js +77 -0
- package/lib/docs/withBrowserSlot.js +45 -0
- package/lib/docs/withBrowserSlot.test.mjs +75 -0
- package/lib/docs/withDataSession.js +53 -0
- package/lib/server/applyDataSetRedirect.js +92 -0
- package/lib/server/applyDataSetRedirect.test.mjs +446 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
- package/lib/server/auth/getHeadlessUser.js +9 -8
- package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
- package/lib/server/auth/getMockUser.js +18 -14
- package/lib/server/auth/{session.js → getStrategies.js} +17 -16
- package/lib/server/auth/headlessUser.js +3 -2
- package/lib/server/auth/journeyActor.js +35 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
- package/lib/server/checkPageRecord.js +44 -0
- package/lib/server/contextMapBudget.js +24 -0
- package/lib/server/contextMapBudget.test.mjs +98 -0
- package/lib/server/continueJitKeys.js +28 -0
- package/lib/server/continueJitKeys.test.mjs +43 -0
- package/lib/server/createDataSessionEndedError.js +33 -0
- package/lib/server/createDataSetRefusal.js +26 -0
- package/lib/server/createLowdefyContext.js +168 -41
- package/lib/server/createLowdefyContext.test.mjs +331 -8
- package/lib/server/getClientAddress.js +51 -0
- package/lib/server/getClientAddress.test.mjs +56 -0
- package/lib/server/getPageJitEnrichment.test.mjs +29 -11
- package/lib/server/hashConfigContent.js +26 -0
- package/lib/server/importAppCode.test.mjs +57 -0
- package/lib/server/isRoutineStep.js +27 -0
- package/lib/server/jitPageBuilder.js +309 -126
- package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
- package/lib/server/jitPageBuilder.test.mjs +218 -0
- package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
- package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
- package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
- package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
- package/lib/server/journeyCookies.js +22 -0
- package/lib/server/journeyCookies.test.mjs +145 -0
- package/lib/server/loadDynamicJsMap.js +47 -0
- package/lib/server/log/createHandleError.js +38 -2
- package/lib/server/log/createHandleError.test.mjs +125 -2
- package/lib/server/log/createLogger.js +3 -0
- package/lib/server/log/createLogger.test.mjs +15 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
- package/lib/server/mutants/applyMutant.js +39 -0
- package/lib/server/mutants/artifactPatterns.js +25 -0
- package/lib/server/mutants/buildAnchor.js +45 -0
- package/lib/server/mutants/collectSharedIds.js +41 -0
- package/lib/server/mutants/configPathSegments.js +38 -0
- package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
- package/lib/server/mutants/describeEvent.js +22 -0
- package/lib/server/mutants/enumerateArtifact.js +56 -0
- package/lib/server/mutants/findNodeByKey.js +45 -0
- package/lib/server/mutants/findNodeByPointer.js +49 -0
- package/lib/server/mutants/getArtifactKind.js +32 -0
- package/lib/server/mutants/groupMutantCopies.js +56 -0
- package/lib/server/mutants/hashArtifact.js +25 -0
- package/lib/server/mutants/hashMutantTarget.js +34 -0
- package/lib/server/mutants/listMutants.js +122 -0
- package/lib/server/mutants/listMutants.test.mjs +502 -0
- package/lib/server/mutants/mutantId.js +30 -0
- package/lib/server/mutants/mutantRuns.js +57 -0
- package/lib/server/mutants/mutantRuns.test.mjs +136 -0
- package/lib/server/mutants/operators/dropAction.js +47 -0
- package/lib/server/mutants/operators/dropBlock.js +44 -0
- package/lib/server/mutants/operators/dropPayload.js +52 -0
- package/lib/server/mutants/operators/dropStep.js +44 -0
- package/lib/server/mutants/operators/flipVisible.js +53 -0
- package/lib/server/mutants/operators/index.js +37 -0
- package/lib/server/mutants/operators/retargetLink.js +72 -0
- package/lib/server/mutants/operators/skipValidate.js +77 -0
- package/lib/server/mutants/operators/swapIf.js +112 -0
- package/lib/server/mutants/operators.test.mjs +396 -0
- package/lib/server/mutants/positionFreePath.js +38 -0
- package/lib/server/mutants/removeFromParent.js +28 -0
- package/lib/server/mutants/test/fixtures.mjs +173 -0
- package/lib/server/mutants/test/keyArtifact.mjs +63 -0
- package/lib/server/mutants/validateMutantParam.js +69 -0
- package/lib/server/mutants/validateMutantsBody.js +88 -0
- package/lib/server/mutants/walkArtifact.js +179 -0
- package/lib/server/pageBuildRecords.js +131 -0
- package/lib/server/pageBuildRecords.test.mjs +258 -0
- package/lib/server/pageCache.mjs +22 -7
- package/lib/server/pageCache.test.mjs +22 -7
- package/lib/server/pageRebuilds.test.mjs +543 -0
- package/lib/server/pruneJitMaps.js +36 -0
- package/lib/server/pruneJitMaps.test.mjs +55 -0
- package/lib/server/readInstanceRecord.js +33 -0
- package/lib/server/readInstanceRecord.test.mjs +99 -0
- package/lib/server/readMergedMaps.js +86 -0
- package/lib/server/readMergedMaps.test.mjs +79 -0
- package/lib/server/recording/appendRecords.js +33 -0
- package/lib/server/recording/errorRecordingStamp.js +34 -0
- package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
- package/lib/server/recording/isRecordingEnabled.js +31 -0
- package/lib/server/recording/readRecordingCookie.js +71 -0
- package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
- package/lib/server/recording/recordingCookiePayload.js +33 -0
- package/lib/server/recording/servedBuilds.js +46 -0
- package/lib/server/recording/servedBuilds.test.mjs +42 -0
- package/lib/server/sinkExternalConnections.js +95 -0
- package/lib/server/sinkExternalConnections.test.mjs +135 -0
- package/lib/server/skipStaleMapWrites.js +62 -0
- package/lib/server/skipStaleMapWrites.test.mjs +113 -0
- package/lib/server/startIdleGc.js +67 -0
- package/lib/server/startIdleGc.test.mjs +138 -0
- package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
- package/lib/server/trackSessionWork.js +30 -0
- package/lib/vite/createDevServerExclude.js +40 -0
- package/lib/vite/createDevServerExclude.test.mjs +79 -0
- package/manager/getContext.mjs +25 -3
- package/manager/processes/initialBuild.mjs +17 -1
- package/manager/processes/initialBuild.test.mjs +104 -0
- package/manager/processes/lowdefyBuild.mjs +31 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
- package/manager/processes/lowdefyBuild.test.mjs +136 -1
- package/manager/processes/optimizeDependencies.mjs +59 -0
- package/manager/processes/restartServer.mjs +34 -6
- package/manager/processes/restartServer.test.mjs +162 -0
- package/manager/processes/shutdownServer.mjs +11 -0
- package/manager/processes/shutdownServer.test.mjs +45 -0
- package/manager/processes/startFirstServer.mjs +46 -0
- package/manager/processes/startFirstServer.test.mjs +326 -0
- package/manager/processes/startMailSink.mjs +135 -0
- package/manager/processes/startMailSink.test.mjs +146 -0
- package/manager/processes/startProxy.mjs +129 -12
- package/manager/processes/startProxy.test.mjs +366 -0
- package/manager/processes/startServer.mjs +47 -6
- package/manager/processes/startServer.test.mjs +155 -0
- package/manager/processes/startWatchers.mjs +6 -4
- package/manager/processes/syncServer.mjs +69 -0
- package/manager/processes/syncServer.test.mjs +141 -0
- package/manager/processes/waitForDevServer.mjs +48 -0
- package/manager/processes/waitForDevServer.test.mjs +112 -0
- package/manager/run.mjs +128 -43
- package/manager/utils/BatchChanges.mjs +21 -5
- package/manager/utils/BatchChanges.test.mjs +77 -0
- package/manager/utils/acquireDevInstance.mjs +99 -0
- package/manager/utils/acquireDevInstance.test.mjs +160 -0
- package/manager/utils/createBuildActivity.mjs +67 -0
- package/manager/utils/createBuildActivity.test.mjs +58 -0
- package/manager/utils/createChangeSignal.mjs +39 -0
- package/manager/utils/createChangeSignal.test.mjs +62 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
- package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
- package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
- package/manager/utils/createDotPathIgnore.mjs +44 -0
- package/manager/utils/createDotPathIgnore.test.mjs +65 -0
- package/manager/utils/createIdleGc.mjs +51 -0
- package/manager/utils/createIdleGc.test.mjs +77 -0
- package/manager/utils/createRequestActivity.mjs +100 -0
- package/manager/utils/createRequestActivity.test.mjs +121 -0
- package/manager/utils/createServerArtifactTracker.mjs +83 -0
- package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
- package/manager/utils/createServerEnv.mjs +39 -0
- package/manager/utils/filesHaveSameBytes.mjs +41 -0
- package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
- package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
- package/manager/utils/importFresh.mjs +46 -0
- package/manager/utils/importFresh.test.mjs +72 -0
- package/manager/utils/importFreshWorker.mjs +20 -0
- package/manager/utils/killTaggedBrowser.mjs +35 -0
- package/manager/utils/pagePathChanged.mjs +56 -0
- package/manager/utils/pruneRecordings.mjs +85 -0
- package/manager/utils/pruneRecordings.test.mjs +117 -0
- package/manager/utils/publishBuildDirectory.mjs +121 -0
- package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
- package/manager/utils/publishBuildDirectory.test.mjs +262 -0
- package/manager/utils/readBasePath.mjs +27 -0
- package/manager/utils/readBasePath.test.mjs +43 -0
- package/manager/utils/readBuildStatusWait.mjs +67 -0
- package/manager/utils/readManagerVersion.mjs +30 -0
- package/manager/utils/readManagerVersion.test.mjs +35 -0
- package/manager/utils/readPluginDefinitions.mjs +36 -0
- package/manager/utils/resolveDevAuthUrl.mjs +58 -0
- package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
- package/manager/utils/resolvePorts.mjs +49 -0
- package/manager/utils/setupWatcher.mjs +19 -11
- package/manager/utils/startRecordingPruner.mjs +45 -0
- package/manager/utils/startRecordingPruner.test.mjs +66 -0
- package/manager/utils/waitForChildExit.mjs +53 -0
- package/manager/utils/waitForChildExit.test.mjs +70 -0
- package/manager/utils/waitForServer.mjs +44 -0
- package/manager/utils/writeBuildStatus.mjs +1 -1
- package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
- package/manager/watchers/envWatcher.mjs +9 -2
- package/manager/watchers/envWatcher.test.mjs +87 -0
- package/manager/watchers/listServerSidePackages.mjs +47 -0
- package/manager/watchers/listServerSidePackages.test.mjs +38 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
- package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
- package/manager/watchers/pluginSourceWatcher.mjs +79 -0
- package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
- package/manager/watchers/restartRequestWatcher.mjs +71 -0
- package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
- package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
- package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
- package/manager/watchers/serverPackageWatcher.mjs +38 -0
- package/package.json +60 -47
- package/package.original.json +60 -47
- package/src/app.js +131 -20
- package/src/html/renderDevPage.js +63 -11
- package/src/html/renderDevPage.test.mjs +138 -0
- package/src/lib/getRequestPath.js +29 -0
- package/src/middleware/createSameOriginGuard.js +37 -0
- package/src/middleware/createSameOriginGuard.test.mjs +102 -0
- package/src/middleware/errorHandler.js +51 -1
- package/src/middleware/errorHandler.test.mjs +173 -3
- package/src/middleware/isRebindingSafeHost.js +44 -0
- package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
- package/src/middleware/localDevToolsOnly.js +38 -0
- package/src/middleware/localDevToolsOnly.test.mjs +61 -0
- package/src/middleware/noStore.js +27 -0
- package/src/middleware/noStore.test.mjs +48 -0
- package/src/middleware/requestTimeout.js +38 -0
- package/src/middleware/staleFlag.js +76 -0
- package/src/middleware/staleFlag.test.mjs +127 -0
- package/src/middleware/stampCaller.js +38 -0
- package/src/middleware/stampCaller.test.mjs +64 -0
- package/src/routes/agent.js +15 -3
- package/src/routes/auth.js +29 -20
- package/src/routes/auth.test.mjs +30 -56
- package/src/routes/clientError.js +13 -13
- package/src/routes/cron.js +3 -1
- package/src/routes/cronForward.js +3 -1
- package/src/routes/detached.js +22 -9
- package/src/routes/devInspect.js +17 -8
- package/src/routes/devInspect.test.mjs +44 -7
- package/src/routes/devRecording.js +113 -0
- package/src/routes/devRecording.test.mjs +253 -0
- package/src/routes/docs/buildStatus.js +10 -2
- package/src/routes/docs/buildStatus.test.mjs +46 -0
- package/src/routes/docs/content.js +5 -3
- package/src/routes/docs/evalOperator.js +15 -1
- package/src/routes/docs/events.js +50 -0
- package/src/routes/docs/events.test.mjs +117 -0
- package/src/routes/docs/find.js +9 -1
- package/src/routes/docs/icons.js +30 -0
- package/src/routes/docs/inspectState.js +16 -1
- package/src/routes/docs/journey.js +194 -0
- package/src/routes/docs/journey.test.mjs +401 -0
- package/src/routes/docs/journeyRefusals.test.mjs +71 -0
- package/src/routes/docs/loadState.js +4 -2
- package/src/routes/docs/mcp.js +34 -3
- package/src/routes/docs/mcp.test.mjs +130 -0
- package/src/routes/docs/mutants.js +78 -0
- package/src/routes/docs/mutants.test.mjs +180 -0
- package/src/routes/docs/parsePathParamsParam.js +41 -0
- package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
- package/src/routes/docs/parseUserParam.js +27 -21
- package/src/routes/docs/parseUserParam.test.mjs +17 -5
- package/src/routes/docs/restart.js +35 -0
- package/src/routes/docs/restart.test.mjs +64 -0
- package/src/routes/docs/runEndpoint.js +54 -0
- package/src/routes/docs/runEndpoint.test.mjs +165 -0
- package/src/routes/docs/runRequest.js +25 -8
- package/src/routes/docs/runRequest.test.mjs +92 -0
- package/src/routes/docs/screenshot.js +50 -2
- package/src/routes/docs/screenshot.test.mjs +117 -0
- package/src/routes/docs/search.js +15 -2
- package/src/routes/docs/snapshotState.js +17 -3
- package/src/routes/endpoints.js +27 -26
- package/src/routes/endpointsTextAnswer.test.mjs +77 -0
- package/src/routes/feedback.js +10 -13
- package/src/routes/jitPage.js +99 -35
- package/src/routes/jitPage.test.mjs +248 -0
- package/src/routes/mcp.js +82 -5
- package/src/routes/mcp.test.mjs +215 -0
- package/src/routes/mcpMemberToken.test.mjs +125 -0
- package/src/routes/mcpProtectedResourceMetadata.js +34 -0
- package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
- package/src/routes/mountDevPageRoutes.js +25 -0
- package/src/routes/mountDevPageRoutes.test.mjs +30 -0
- package/src/routes/mountOauthDiscovery.js +44 -0
- package/src/routes/mountOauthDiscovery.test.mjs +79 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
- package/src/routes/reload.js +16 -0
- package/src/routes/request.js +6 -4
- package/src/routes/request.test.mjs +9 -5
- package/src/routes/requestBody.test.mjs +159 -0
- package/src/routes/usage.js +6 -2
- package/src/routes/user.js +27 -0
- package/src/routes/websocket.js +29 -8
- package/src/routes/websocket.test.mjs +65 -0
- package/src/routes/wellKnownFallback.js +30 -0
- package/src/routes/wellKnownFallback.test.mjs +32 -0
- package/src/websocket/devWebSocket.js +41 -26
- package/src/websocket/devWebSocket.test.mjs +157 -0
- package/vite.config.js +53 -23
- package/lib/server/auth/getDevSession.js +0 -70
- package/lib/server/auth/getDevSession.test.mjs +0 -138
- package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
- package/manager/watchers/moduleBuildWatcher.mjs +0 -79
- package/manager/watchers/serverArtifactWatcher.mjs +0 -120
- package/src/routes/apiPage.js +0 -37
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
|
|
20
|
+
import { addNonceToElements, createContentSecurityPolicy } from '@lowdefy/api';
|
|
21
|
+
import { appStorageKey, serializer, type } from '@lowdefy/helpers';
|
|
22
|
+
|
|
23
|
+
import isRecordingEnabled from '../../lib/server/recording/isRecordingEnabled.js';
|
|
20
24
|
import safeScriptJson from '../lib/safeScriptJson.js';
|
|
21
25
|
|
|
22
26
|
// Dev HTML shell — config-free: the client fetches root config and page
|
|
@@ -26,7 +30,9 @@ import safeScriptJson from '../lib/safeScriptJson.js';
|
|
|
26
30
|
//
|
|
27
31
|
// @hono/vite-dev-server injects /@vite/client into this response; the
|
|
28
32
|
// react-refresh preamble must be inlined here for @vitejs/plugin-react.
|
|
29
|
-
|
|
33
|
+
// The layer order style takes its script's nonce, which a Content-Security-Policy
|
|
34
|
+
// needs to apply it.
|
|
35
|
+
const layerOrderScript = `(function(){var s=document.createElement("style");s.id="__lf-layer-order";var n=document.currentScript&&document.currentScript.nonce;if(n)s.nonce=n;s.textContent="@layer theme, base, antd, components, utilities;";document.head.prepend(s);new MutationObserver(function(){if(document.head.firstChild!==s)document.head.prepend(s)}).observe(document.head,{childList:true})})();`;
|
|
30
36
|
|
|
31
37
|
function readBuildJson(name) {
|
|
32
38
|
try {
|
|
@@ -36,9 +42,47 @@ function readBuildJson(name) {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
// Vite's dev server needs no source beyond the page's own origin: /@vite/client
|
|
46
|
+
// and the modules load from 'self', Vite's style tags take the nonce from the
|
|
47
|
+
// csp-nonce meta tag, and the hot reload socket connects back to the page's
|
|
48
|
+
// host, which allowWebSocket names in connect-src for every dev page.
|
|
49
|
+
function getContentSecurityPolicy(c) {
|
|
50
|
+
const security = serializer.deserialize(readBuildJson('security'));
|
|
51
|
+
if (type.isNone(security.contentSecurityPolicy)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return createContentSecurityPolicy({
|
|
55
|
+
policy: security.contentSecurityPolicy,
|
|
56
|
+
requestUrl: c.req.url,
|
|
57
|
+
allowWebSocket: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
function renderDevPage(c, { basePath = '' }) {
|
|
62
|
+
// The resolved caller, injected like the production server's template.js —
|
|
63
|
+
// the client's Auth provider seeds _user from it (roles, organization_id).
|
|
64
|
+
const user = c.get('lowdefyContext')?.user ?? null;
|
|
40
65
|
const themeConfig = readBuildJson('theme');
|
|
41
66
|
const appJson = readBuildJson('app');
|
|
67
|
+
// The client reads the slug before root config arrives, to key the dark mode and locale
|
|
68
|
+
// preferences per app.
|
|
69
|
+
const slug = readBuildJson('appMeta').slug ?? null;
|
|
70
|
+
const recording = { enabled: isRecordingEnabled(c.req.header('cookie')) };
|
|
71
|
+
|
|
72
|
+
// Off unless config.security.contentSecurityPolicy is set. Each render gets its own nonce.
|
|
73
|
+
let nonceAttribute = '';
|
|
74
|
+
let nonceMeta = '';
|
|
75
|
+
let appendHead = appJson.html?.appendHead ?? '';
|
|
76
|
+
let appendBody = appJson.html?.appendBody ?? '';
|
|
77
|
+
const policy = getContentSecurityPolicy(c);
|
|
78
|
+
if (!type.isNull(policy)) {
|
|
79
|
+
const { nonce } = policy;
|
|
80
|
+
c.header(policy.name, policy.value);
|
|
81
|
+
nonceAttribute = ` nonce="${nonce}"`;
|
|
82
|
+
nonceMeta = `<meta property="csp-nonce" nonce="${nonce}" />`;
|
|
83
|
+
appendHead = addNonceToElements({ html: appendHead, nonce });
|
|
84
|
+
appendBody = addNonceToElements({ html: appendBody, nonce });
|
|
85
|
+
}
|
|
42
86
|
|
|
43
87
|
const VALID_COLOR_MODES = ['system', 'light', 'dark'];
|
|
44
88
|
const configColorMode = VALID_COLOR_MODES.includes(themeConfig.darkMode)
|
|
@@ -47,36 +91,44 @@ function renderDevPage(c, { basePath = '' }) {
|
|
|
47
91
|
const darkBg = themeConfig?.antd?.darkToken?.colorBgLayout ?? '#000';
|
|
48
92
|
const lightBg = themeConfig?.antd?.lightToken?.colorBgLayout ?? '';
|
|
49
93
|
|
|
94
|
+
const darkModeStorageKey = appStorageKey({ basePath, name: 'lowdefy_darkMode', slug });
|
|
95
|
+
|
|
50
96
|
const darkModeScript = `(function(){var c=${safeScriptJson(
|
|
51
97
|
configColorMode
|
|
52
|
-
)};var db=${safeScriptJson(darkBg)};var lb=${safeScriptJson(
|
|
53
|
-
|
|
54
|
-
)};var d;if(c==="dark")d=true;else if(c==="light")d=false;else{try{var p=localStorage.getItem("lowdefy_darkMode");if(p==="dark")d=true;else if(p==="light")d=false;else d=window.matchMedia("(prefers-color-scheme:dark)").matches}catch(e){d=window.matchMedia("(prefers-color-scheme:dark)").matches}}var bg=d?db:lb;document.documentElement.style.colorScheme=d?"dark":"light";if(bg)document.documentElement.style.backgroundColor=bg})();`;
|
|
98
|
+
)};var db=${safeScriptJson(darkBg)};var lb=${safeScriptJson(lightBg)};var k=${safeScriptJson(
|
|
99
|
+
darkModeStorageKey
|
|
100
|
+
)};var d;if(c==="dark")d=true;else if(c==="light")d=false;else{try{var p=localStorage.getItem(k);if(p===null)p=localStorage.getItem("lowdefy_darkMode");if(p==="dark")d=true;else if(p==="light")d=false;else d=window.matchMedia("(prefers-color-scheme:dark)").matches}catch(e){d=window.matchMedia("(prefers-color-scheme:dark)").matches}}var bg=d?db:lb;document.documentElement.style.colorScheme=d?"dark":"light";if(bg)document.documentElement.style.backgroundColor=bg})();`;
|
|
55
101
|
|
|
56
102
|
const html = `<!DOCTYPE html>
|
|
57
103
|
<html class="lowdefy">
|
|
58
104
|
<head>
|
|
59
105
|
<meta charset="utf-8" />
|
|
60
|
-
|
|
61
|
-
<script>${
|
|
106
|
+
${nonceMeta}
|
|
107
|
+
<script${nonceAttribute}>${layerOrderScript}</script>
|
|
108
|
+
<script${nonceAttribute}>${darkModeScript}</script>
|
|
62
109
|
<link rel="manifest" href="${basePath}/manifest.webmanifest" />
|
|
63
110
|
<link rel="icon" type="image/svg+xml" href="${basePath}/icon.svg" />
|
|
64
111
|
<link rel="apple-touch-icon" href="${basePath}/apple-touch-icon.png" />
|
|
65
112
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
66
|
-
<script type="module">
|
|
113
|
+
<script type="module"${nonceAttribute}>
|
|
67
114
|
import RefreshRuntime from '${basePath}/@react-refresh';
|
|
68
115
|
RefreshRuntime.injectIntoGlobalHook(window);
|
|
69
116
|
window.$RefreshReg$ = () => {};
|
|
70
117
|
window.$RefreshSig$ = () => (type) => type;
|
|
71
118
|
window.__vite_plugin_react_preamble_installed__ = true;
|
|
72
119
|
</script>
|
|
73
|
-
${
|
|
120
|
+
${appendHead}
|
|
74
121
|
</head>
|
|
75
122
|
<body>
|
|
76
123
|
<div id="root"></div>
|
|
77
|
-
<script id="__LOWDEFY_CONFIG__" type="application/json">${safeScriptJson({
|
|
78
|
-
|
|
79
|
-
|
|
124
|
+
<script id="__LOWDEFY_CONFIG__" type="application/json"${nonceAttribute}>${safeScriptJson({
|
|
125
|
+
basePath,
|
|
126
|
+
recording,
|
|
127
|
+
slug,
|
|
128
|
+
user,
|
|
129
|
+
})}</script>
|
|
130
|
+
${appendBody}
|
|
131
|
+
<script type="module" src="${basePath}/client/main.jsx"${nonceAttribute}></script>
|
|
80
132
|
</body>
|
|
81
133
|
</html>`;
|
|
82
134
|
|
|
@@ -0,0 +1,138 @@
|
|
|
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 fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
|
|
21
|
+
const { default: renderDevPage } = await import('./renderDevPage.js');
|
|
22
|
+
const { JOURNEY_COOKIES, writeJourneyCookie } = await import('../../lib/server/journeyCookies.js');
|
|
23
|
+
|
|
24
|
+
function render({ cookie } = {}) {
|
|
25
|
+
let html;
|
|
26
|
+
const c = {
|
|
27
|
+
get: () => undefined,
|
|
28
|
+
req: { header: (name) => (name === 'cookie' ? cookie : undefined) },
|
|
29
|
+
html: (value) => {
|
|
30
|
+
html = value;
|
|
31
|
+
return value;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
renderDevPage(c, { basePath: '' });
|
|
35
|
+
const match = /<script id="__LOWDEFY_CONFIG__" type="application\/json">(.*?)<\/script>/.exec(
|
|
36
|
+
html
|
|
37
|
+
);
|
|
38
|
+
return JSON.parse(match[1]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
delete process.env.LOWDEFY_DEV_RECORD;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('renderDevPage enables recording in a developer tab by default', () => {
|
|
46
|
+
expect(render().recording).toEqual({ enabled: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('renderDevPage disables recording for a context whose recording cookie is off', () => {
|
|
50
|
+
const written = writeJourneyCookie({
|
|
51
|
+
name: JOURNEY_COOKIES.recording.name,
|
|
52
|
+
payload: 'off',
|
|
53
|
+
origin: 'http://localhost:3001',
|
|
54
|
+
});
|
|
55
|
+
expect(render({ cookie: `${written.name}=${written.value}` }).recording).toEqual({
|
|
56
|
+
enabled: false,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('renderDevPage disables recording when LOWDEFY_DEV_RECORD is false', () => {
|
|
61
|
+
process.env.LOWDEFY_DEV_RECORD = 'false';
|
|
62
|
+
expect(render().recording).toEqual({ enabled: false });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function renderWithHeaders() {
|
|
66
|
+
const headers = {};
|
|
67
|
+
let html;
|
|
68
|
+
const c = {
|
|
69
|
+
get: () => undefined,
|
|
70
|
+
header: (name, value) => {
|
|
71
|
+
headers[name] = value;
|
|
72
|
+
},
|
|
73
|
+
req: { header: () => undefined, url: 'http://localhost:3122/home' },
|
|
74
|
+
html: (value) => {
|
|
75
|
+
html = value;
|
|
76
|
+
return value;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
renderDevPage(c, { basePath: '' });
|
|
80
|
+
return { headers, html };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('renderDevPage with a Content-Security-Policy', () => {
|
|
84
|
+
const originalCwd = process.cwd();
|
|
85
|
+
let serverDirectory;
|
|
86
|
+
|
|
87
|
+
beforeAll(() => {
|
|
88
|
+
serverDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-dev-csp-'));
|
|
89
|
+
fs.mkdirSync(path.join(serverDirectory, 'build'));
|
|
90
|
+
process.chdir(serverDirectory);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterAll(() => {
|
|
94
|
+
process.chdir(originalCwd);
|
|
95
|
+
fs.rmSync(serverDirectory, { recursive: true, force: true });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
function writeSecurity(security) {
|
|
99
|
+
fs.writeFileSync(path.join(serverDirectory, 'build/security.json'), JSON.stringify(security));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
test('renderDevPage sends no header and writes no nonce when the policy is off', () => {
|
|
103
|
+
writeSecurity({ contentSecurityPolicy: null });
|
|
104
|
+
const { headers, html } = renderWithHeaders();
|
|
105
|
+
expect(headers).toEqual({});
|
|
106
|
+
expect(html).not.toContain(' nonce="');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('renderDevPage puts the header nonce on every script and allows the hot reload socket', () => {
|
|
110
|
+
writeSecurity({
|
|
111
|
+
contentSecurityPolicy: {
|
|
112
|
+
reportOnly: false,
|
|
113
|
+
directives: {
|
|
114
|
+
'script-src': ["'self'"],
|
|
115
|
+
'style-src': ["'self'"],
|
|
116
|
+
'connect-src': ["'self'"],
|
|
117
|
+
'~k': '12',
|
|
118
|
+
},
|
|
119
|
+
nonceDirectives: ['script-src', 'style-src'],
|
|
120
|
+
websockets: false,
|
|
121
|
+
'~k': '11',
|
|
122
|
+
},
|
|
123
|
+
'~k': '10',
|
|
124
|
+
});
|
|
125
|
+
const first = renderWithHeaders();
|
|
126
|
+
const second = renderWithHeaders();
|
|
127
|
+
const header = first.headers['Content-Security-Policy'];
|
|
128
|
+
const nonce = header.match(/'nonce-([^']+)'/)[1];
|
|
129
|
+
expect(header).toEqual(
|
|
130
|
+
`script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'; connect-src 'self' ws://localhost:3122 wss://localhost:3122`
|
|
131
|
+
);
|
|
132
|
+
const scripts = first.html.match(/<script\b[^>]*>/g);
|
|
133
|
+
expect(scripts.length).toEqual(5);
|
|
134
|
+
scripts.forEach((tag) => expect(tag).toContain(` nonce="${nonce}"`));
|
|
135
|
+
expect(first.html).toContain(`<meta property="csp-nonce" nonce="${nonce}" />`);
|
|
136
|
+
expect(second.headers['Content-Security-Policy']).not.toEqual(header);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
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 rest of the request path after basePath and prefix, still encoded: the
|
|
18
|
+
// page matcher decodes each segment once, and Hono's c.req.path is partly
|
|
19
|
+
// decoded, so it is taken from the request URL. path goes to the matcher as it
|
|
20
|
+
// arrived; matchedPath is path with one trailing "/" removed, the form the
|
|
21
|
+
// client compares its navigations with.
|
|
22
|
+
function getRequestPath({ c, basePath, prefix }) {
|
|
23
|
+
const pathname = new URL(c.req.url).pathname.slice(basePath.length);
|
|
24
|
+
const path = pathname.startsWith(prefix) ? pathname.slice(prefix.length) : '';
|
|
25
|
+
const matchedPath = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
26
|
+
return { path, matchedPath };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default getRequestPath;
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { isSameOriginRequest } from '@lowdefy/api';
|
|
18
|
+
|
|
19
|
+
// The cross-site defence for the routes only this app's own pages call
|
|
20
|
+
// (/api/client-error, /api/feedback) and for the dev tools. The rule itself -
|
|
21
|
+
// Sec-Fetch-Site, then Origin against Host - is isSameOriginRequest in
|
|
22
|
+
// @lowdefy/api, which every server shares. Unlike a websocket handshake, a
|
|
23
|
+
// fetch can carry X-Forwarded-Host where a CORS preflight allows it (Vite's
|
|
24
|
+
// dev CORS allows localhost origins), so HTTP routes compare with Host only.
|
|
25
|
+
function createSameOriginGuard({ allowNoOrigin = false } = {}) {
|
|
26
|
+
// Returns the 403 response to answer with, or null when the request may
|
|
27
|
+
// proceed. A guard, not Hono middleware, so a route keeps one entry point
|
|
28
|
+
// and the refusal is visible at the top of the handler that owns it.
|
|
29
|
+
return function guardSameOrigin(c) {
|
|
30
|
+
if (isSameOriginRequest({ getHeader: (name) => c.req.header(name), allowNoOrigin })) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default createSameOriginGuard;
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { Hono } from 'hono';
|
|
18
|
+
|
|
19
|
+
import createSameOriginGuard from './createSameOriginGuard.js';
|
|
20
|
+
|
|
21
|
+
function createApp({ allowNoOrigin } = {}) {
|
|
22
|
+
const guardSameOrigin = createSameOriginGuard({ allowNoOrigin });
|
|
23
|
+
const app = new Hono();
|
|
24
|
+
app.all('/post', (c) => {
|
|
25
|
+
const forbidden = guardSameOrigin(c);
|
|
26
|
+
if (forbidden) {
|
|
27
|
+
return forbidden;
|
|
28
|
+
}
|
|
29
|
+
return c.json({ allowed: true });
|
|
30
|
+
});
|
|
31
|
+
return app;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function post({ allowNoOrigin, headers }) {
|
|
35
|
+
return createApp({ allowNoOrigin }).request('/post', { headers, method: 'POST' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test.each([
|
|
39
|
+
['origin matches the host', { host: 'app.test', origin: 'https://app.test' }],
|
|
40
|
+
[
|
|
41
|
+
'sec-fetch-site says same-origin',
|
|
42
|
+
{ host: 'app.test', origin: 'https://app.test', 'sec-fetch-site': 'same-origin' },
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
'sec-fetch-site says none',
|
|
46
|
+
{ host: 'app.test', origin: 'https://app.test', 'sec-fetch-site': 'none' },
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
'the origin carries a port matching the host',
|
|
50
|
+
{ host: 'app.test:3000', origin: 'http://app.test:3000' },
|
|
51
|
+
],
|
|
52
|
+
])('the guard allows a post when %s', async (_, headers) => {
|
|
53
|
+
const res = await post({ headers });
|
|
54
|
+
|
|
55
|
+
expect(res.status).toEqual(200);
|
|
56
|
+
expect(await res.json()).toEqual({ allowed: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test.each([
|
|
60
|
+
['the origin is another site', { host: 'app.test', origin: 'https://evil.test' }],
|
|
61
|
+
['the origin is a sibling subdomain', { host: 'app.test', origin: 'https://other.app.test' }],
|
|
62
|
+
['the origin is not a url', { host: 'app.test', origin: 'not a url' }],
|
|
63
|
+
[
|
|
64
|
+
'sec-fetch-site says cross-site',
|
|
65
|
+
{ host: 'app.test', origin: 'https://app.test', 'sec-fetch-site': 'cross-site' },
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
'sec-fetch-site says same-site',
|
|
69
|
+
{ host: 'app.test', origin: 'https://app.test', 'sec-fetch-site': 'same-site' },
|
|
70
|
+
],
|
|
71
|
+
['there is no origin', { host: 'app.test' }],
|
|
72
|
+
])('the guard answers 403 when %s', async (_, headers) => {
|
|
73
|
+
const res = await post({ headers });
|
|
74
|
+
|
|
75
|
+
expect(res.status).toEqual(403);
|
|
76
|
+
expect(await res.json()).toEqual({ error: 'Forbidden' });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('the guard lets a caller that sends no origin through when the route allows it', async () => {
|
|
80
|
+
const res = await post({ allowNoOrigin: true, headers: { host: 'app.test' } });
|
|
81
|
+
|
|
82
|
+
expect(res.status).toEqual(200);
|
|
83
|
+
expect(await res.json()).toEqual({ allowed: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('allowNoOrigin does not excuse a browser posting cross-site', async () => {
|
|
87
|
+
const res = await post({
|
|
88
|
+
allowNoOrigin: true,
|
|
89
|
+
headers: { host: 'app.test', origin: 'https://evil.test' },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(res.status).toEqual(403);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('allowNoOrigin does not excuse a cross-site sec-fetch-site with no origin', async () => {
|
|
96
|
+
const res = await post({
|
|
97
|
+
allowNoOrigin: true,
|
|
98
|
+
headers: { host: 'app.test', 'sec-fetch-site': 'cross-site' },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(res.status).toEqual(403);
|
|
102
|
+
});
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { redactErrorResponse } from '@lowdefy/api';
|
|
18
|
+
import { HTTPException } from 'hono/http-exception';
|
|
18
19
|
|
|
19
20
|
// Hono routes every handler error to the app-level error handler — upstream
|
|
20
21
|
// middleware try/catch never sees them. API routes get the serialized error
|
|
@@ -24,7 +25,8 @@ function createErrorHandler({ basePath = '', logger }) {
|
|
|
24
25
|
const path = basePath ? c.req.path.replace(basePath, '') : c.req.path;
|
|
25
26
|
// Unauthenticated requests to protected endpoints are expected traffic -
|
|
26
27
|
// one warning line and a 401, skipping the structured error log so
|
|
27
|
-
// probing cannot flood it. Keys strictly on the error name
|
|
28
|
+
// probing cannot flood it. Keys strictly on the error name; the 500 path
|
|
29
|
+
// below is untouched.
|
|
28
30
|
if (error.name === 'AuthenticationError') {
|
|
29
31
|
logger.warn(`Unauthenticated request: ${c.req.method} ${c.req.path}`);
|
|
30
32
|
if (path.startsWith('/api/')) {
|
|
@@ -32,6 +34,54 @@ function createErrorHandler({ basePath = '', logger }) {
|
|
|
32
34
|
}
|
|
33
35
|
return c.text('Unauthorized', 401);
|
|
34
36
|
}
|
|
37
|
+
// An authenticated caller whose roles do not permit the resource. Expected
|
|
38
|
+
// traffic, not a fault: one warning line, no structured error log and no
|
|
39
|
+
// Sentry capture.
|
|
40
|
+
if (error.name === 'AuthorizationError') {
|
|
41
|
+
logger.warn(`Forbidden: ${c.req.method} ${c.req.path}`);
|
|
42
|
+
if (path.startsWith('/api/')) {
|
|
43
|
+
return c.json({ name: error.name, message: error.message }, 403);
|
|
44
|
+
}
|
|
45
|
+
return c.text('Forbidden', 403);
|
|
46
|
+
}
|
|
47
|
+
// An authorized caller who has not enrolled a second factor under
|
|
48
|
+
// auth.twoFactor.required. 403, not 401 - a 401 reads to the client as a dead
|
|
49
|
+
// session and bounces the user to sign-in, which is the loop the gate exists to
|
|
50
|
+
// avoid. Expected traffic under required: true, so one warning line and no
|
|
51
|
+
// Sentry capture, exactly as for AuthenticationError.
|
|
52
|
+
if (error.name === 'TwoFactorEnrolmentRequiredError') {
|
|
53
|
+
logger.warn(`Two-factor enrolment required: ${c.req.method} ${c.req.path}`);
|
|
54
|
+
if (path.startsWith('/api/')) {
|
|
55
|
+
return c.json({ name: error.name, message: error.message }, 403);
|
|
56
|
+
}
|
|
57
|
+
return c.text('Two-factor enrolment required', 403);
|
|
58
|
+
}
|
|
59
|
+
// An API client whose payload does not match the endpoint's declared
|
|
60
|
+
// payloadSchema (or any other UserError raised before a routine starts). A
|
|
61
|
+
// page's CallAPI payload the schema refuses is a ConfigError instead: the
|
|
62
|
+
// page's own config built it (validatePayload). The caller's own
|
|
63
|
+
// request is wrong, not the config or the server: a 400 with the message the
|
|
64
|
+
// caller needs, one warning line, no structured error log and no Sentry
|
|
65
|
+
// capture. A UserError raised inside a routine never gets here - it returns
|
|
66
|
+
// 200 with success: false through buildEndpointResult.
|
|
67
|
+
if (error.name === 'UserError') {
|
|
68
|
+
logger.warn(`Bad request: ${c.req.method} ${c.req.path} - ${error.message}`);
|
|
69
|
+
if (path.startsWith('/api/')) {
|
|
70
|
+
return c.json({ name: error.name, message: error.message }, 400);
|
|
71
|
+
}
|
|
72
|
+
return c.text('Bad Request', 400);
|
|
73
|
+
}
|
|
74
|
+
// A route or transport that has already decided its own HTTP answer - hono's
|
|
75
|
+
// HTTPException carries the response to send. The MCP transport refuses an
|
|
76
|
+
// unsupported protocol-version header this way (a 404 with a JSON-RPC body
|
|
77
|
+
// that the client SDK falls back on to negotiate a version both sides
|
|
78
|
+
// know). Not a fault: send its response and log one warning line, no
|
|
79
|
+
// structured error log and no Sentry capture. Turning it into a 500 would
|
|
80
|
+
// hide the status the client acts on.
|
|
81
|
+
if (error instanceof HTTPException) {
|
|
82
|
+
logger.warn(`${error.status} answered by the route: ${c.req.method} ${c.req.path}`);
|
|
83
|
+
return error.getResponse();
|
|
84
|
+
}
|
|
35
85
|
const context = c.get('lowdefyContext');
|
|
36
86
|
if (context) {
|
|
37
87
|
await context.handleError(error);
|