@lowdefy/server-dev 6.1.0 → 7.0.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 +67 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -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/findCollectionCollisions.js +52 -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 +44 -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 +119 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -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 +68 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +220 -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/sweepOrphanDatabases.js +33 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -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 +54 -6
- package/lib/docs/getBuildStatus.test.mjs +133 -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 +83 -0
- package/lib/server/applyDataSetRedirect.test.mjs +340 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -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 +328 -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 +112 -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 +24 -2
- package/manager/processes/initialBuild.mjs +14 -0
- package/manager/processes/initialBuild.test.mjs +101 -0
- package/manager/processes/lowdefyBuild.mjs +30 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -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,7 +17,7 @@
|
|
|
17
17
|
import { Hono } from 'hono';
|
|
18
18
|
import { jest } from '@jest/globals';
|
|
19
19
|
|
|
20
|
-
const { listTabs, registerTab, requestFromTab, unregisterTab } = await import(
|
|
20
|
+
const { listTabs, registerTab, requestFromTab, unregisterTab, updateTabPage } = await import(
|
|
21
21
|
'../../lib/docs/tabChannel.js'
|
|
22
22
|
);
|
|
23
23
|
const { default: devInspectHandler } = await import('./devInspect.js');
|
|
@@ -41,8 +41,13 @@ afterEach(() => {
|
|
|
41
41
|
listTabs().forEach((tab) => unregisterTab({ id: tab.id }));
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
function connectTab({ id, pageId, send = jest.fn() }) {
|
|
45
|
+
registerTab({ id, send });
|
|
46
|
+
updateTabPage({ id, pageId, pathParams: {}, instanceKey: `page:${pageId}` });
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
test('GET /api/dev-inspect lists connected tabs', async () => {
|
|
45
|
-
|
|
50
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
46
51
|
const res = await createApp().request('/api/dev-inspect');
|
|
47
52
|
expect(res.status).toBe(200);
|
|
48
53
|
const body = await res.json();
|
|
@@ -51,7 +56,7 @@ test('GET /api/dev-inspect lists connected tabs', async () => {
|
|
|
51
56
|
});
|
|
52
57
|
|
|
53
58
|
test('POST /api/dev-inspect/page returns 403 when the Origin header is missing', async () => {
|
|
54
|
-
|
|
59
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
55
60
|
const res = await createApp().request('/api/dev-inspect/page', {
|
|
56
61
|
method: 'POST',
|
|
57
62
|
headers: { host: 'localhost:3001', 'content-type': 'application/json' },
|
|
@@ -62,17 +67,49 @@ test('POST /api/dev-inspect/page returns 403 when the Origin header is missing',
|
|
|
62
67
|
});
|
|
63
68
|
|
|
64
69
|
test('POST /api/dev-inspect/page moves a connected tab to the posted page', async () => {
|
|
65
|
-
|
|
70
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
66
71
|
const res = await createApp().request('/api/dev-inspect/page', {
|
|
67
72
|
method: 'POST',
|
|
68
73
|
headers: sameOriginHeaders,
|
|
69
|
-
body: JSON.stringify({ tabId: 'tab-1', pageId: 'about' }),
|
|
74
|
+
body: JSON.stringify({ tabId: 'tab-1', pageId: 'about', instanceKey: 'page:about' }),
|
|
70
75
|
});
|
|
71
76
|
expect(res.status).toBe(200);
|
|
72
77
|
expect(await res.json()).toEqual({ ok: true });
|
|
73
78
|
expect(listTabs()[0].pageId).toBe('about');
|
|
74
79
|
});
|
|
75
80
|
|
|
81
|
+
test('POST /api/dev-inspect/page records the page instance a tab shows', async () => {
|
|
82
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
83
|
+
const res = await createApp().request('/api/dev-inspect/page', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: sameOriginHeaders,
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
tabId: 'tab-1',
|
|
88
|
+
pageId: 'ticket',
|
|
89
|
+
pathParams: { space: 's', ticket_id: '2' },
|
|
90
|
+
instanceKey: 'page:ticket#tickets/s/2',
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
expect(res.status).toBe(200);
|
|
94
|
+
expect(listTabs()[0]).toMatchObject({
|
|
95
|
+
pageId: 'ticket',
|
|
96
|
+
pathParams: { space: 's', ticket_id: '2' },
|
|
97
|
+
instanceKey: 'page:ticket#tickets/s/2',
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('POST /api/dev-inspect/page returns 400 when the instance key is missing', async () => {
|
|
102
|
+
connectTab({ id: 'tab-1', pageId: 'home' });
|
|
103
|
+
const res = await createApp().request('/api/dev-inspect/page', {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: sameOriginHeaders,
|
|
106
|
+
body: JSON.stringify({ tabId: 'tab-1', pageId: 'about' }),
|
|
107
|
+
});
|
|
108
|
+
expect(res.status).toBe(400);
|
|
109
|
+
expect(await res.json()).toEqual({ error: 'Missing "pageId" or "instanceKey".' });
|
|
110
|
+
expect(listTabs()[0].pageId).toBe('home');
|
|
111
|
+
});
|
|
112
|
+
|
|
76
113
|
test('POST /api/dev-inspect/page returns 400 when tabId is missing', async () => {
|
|
77
114
|
const res = await createApp().request('/api/dev-inspect/page', {
|
|
78
115
|
method: 'POST',
|
|
@@ -87,7 +124,7 @@ test('POST /api/dev-inspect/page for a tab that already disconnected returns ok'
|
|
|
87
124
|
const res = await createApp().request('/api/dev-inspect/page', {
|
|
88
125
|
method: 'POST',
|
|
89
126
|
headers: sameOriginHeaders,
|
|
90
|
-
body: JSON.stringify({ tabId: 'gone', pageId: 'about' }),
|
|
127
|
+
body: JSON.stringify({ tabId: 'gone', pageId: 'about', instanceKey: 'page:about' }),
|
|
91
128
|
});
|
|
92
129
|
expect(res.status).toBe(200);
|
|
93
130
|
expect(listTabs()).toHaveLength(0);
|
|
@@ -95,7 +132,7 @@ test('POST /api/dev-inspect/page for a tab that already disconnected returns ok'
|
|
|
95
132
|
|
|
96
133
|
test('POST /api/dev-inspect resolves a pending tab request with the posted result', async () => {
|
|
97
134
|
const send = jest.fn();
|
|
98
|
-
|
|
135
|
+
connectTab({ id: 'tab-1', pageId: 'home', send });
|
|
99
136
|
const pending = requestFromTab({ pageId: 'home', event: 'inspect-request' });
|
|
100
137
|
const [, { requestId }] = send.mock.calls[0];
|
|
101
138
|
|
|
@@ -0,0 +1,113 @@
|
|
|
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 { isTraceId, omit, type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import appendRecords from '../../lib/server/recording/appendRecords.js';
|
|
20
|
+
import getBuildId from '../../lib/docs/getBuildId.js';
|
|
21
|
+
import readRecordingCookie from '../../lib/server/recording/readRecordingCookie.js';
|
|
22
|
+
import servedBuilds from '../../lib/server/recording/servedBuilds.js';
|
|
23
|
+
import { getConfigDirectory } from '../../lib/docs/checkpointPaths.js';
|
|
24
|
+
|
|
25
|
+
const MAX_RECORDS = 500;
|
|
26
|
+
|
|
27
|
+
// This route writes to disk, so unlike localDevToolsOnly it does not let a
|
|
28
|
+
// request with no Origin through: only the recorder in a same-origin dev tab
|
|
29
|
+
// posts here. Agents and curl have no reason to.
|
|
30
|
+
function isSameOrigin(c) {
|
|
31
|
+
const origin = c.req.header('origin');
|
|
32
|
+
if (!origin) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return new URL(origin).host === c.req.header('host');
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readBody(c) {
|
|
43
|
+
try {
|
|
44
|
+
return await c.req.json();
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isValidBody(body) {
|
|
51
|
+
return (
|
|
52
|
+
type.isObject(body) &&
|
|
53
|
+
isTraceId(body.session) &&
|
|
54
|
+
type.isArray(body.records) &&
|
|
55
|
+
body.records.length <= MAX_RECORDS &&
|
|
56
|
+
body.records.every((record) => type.isObject(record))
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The page names the build of the config it rendered when each interaction
|
|
61
|
+
// happened (jitPage.js serves it as `_buildId`). It is kept when this process
|
|
62
|
+
// served it, else (missing, unknown, from before a restart) replaced with the
|
|
63
|
+
// build served now. The cookie decides source and run, never the page: a
|
|
64
|
+
// headless journey run is marked by the dev server's own browser,
|
|
65
|
+
// and everything else records as dev.
|
|
66
|
+
function stampRecord({ record, currentBuild, cookie }) {
|
|
67
|
+
const rest = omit({ ...record }, ['source', 'run']);
|
|
68
|
+
const build = servedBuilds.has(record.build) ? record.build : currentBuild;
|
|
69
|
+
if (cookie === null) {
|
|
70
|
+
return { ...rest, build, source: 'dev' };
|
|
71
|
+
}
|
|
72
|
+
return { ...rest, build, source: cookie.source, run: cookie.run };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// POST /api/dev-recording: the dev recorder (client/Recorder.jsx) posts
|
|
76
|
+
// batches of interaction records here. Each keeps the build its page ran on
|
|
77
|
+
// (or the build served now) and is appended to the trace file of its tab
|
|
78
|
+
// session (dev) or run (journey).
|
|
79
|
+
async function devRecordingHandler(c) {
|
|
80
|
+
if (!isSameOrigin(c)) {
|
|
81
|
+
return c.json({ error: 'Forbidden' }, 403);
|
|
82
|
+
}
|
|
83
|
+
if (process.env.LOWDEFY_DEV_RECORD === 'false') {
|
|
84
|
+
return c.body(null, 204);
|
|
85
|
+
}
|
|
86
|
+
const cookie = readRecordingCookie(c.req.header('cookie'));
|
|
87
|
+
if (cookie === 'off' || cookie?.record === false) {
|
|
88
|
+
return c.body(null, 204);
|
|
89
|
+
}
|
|
90
|
+
const body = await readBody(c);
|
|
91
|
+
if (!isValidBody(body)) {
|
|
92
|
+
return c.json(
|
|
93
|
+
{
|
|
94
|
+
error: `Expected { session, records } with a trace id session and at most ${MAX_RECORDS} record objects.`,
|
|
95
|
+
},
|
|
96
|
+
400
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (body.records.length === 0) {
|
|
100
|
+
return c.body(null, 204);
|
|
101
|
+
}
|
|
102
|
+
const currentBuild = getBuildId();
|
|
103
|
+
const records = body.records.map((record) => stampRecord({ record, currentBuild, cookie }));
|
|
104
|
+
appendRecords({
|
|
105
|
+
configDirectory: getConfigDirectory(),
|
|
106
|
+
source: cookie === null ? 'dev' : cookie.source,
|
|
107
|
+
id: cookie === null ? body.session : cookie.run.id,
|
|
108
|
+
records,
|
|
109
|
+
});
|
|
110
|
+
return c.body(null, 204);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default devRecordingHandler;
|
|
@@ -0,0 +1,253 @@
|
|
|
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 'fs';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import { Hono } from 'hono';
|
|
21
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
22
|
+
import { jest } from '@jest/globals';
|
|
23
|
+
|
|
24
|
+
jest.unstable_mockModule('../../lib/docs/getBuildId.js', () => ({
|
|
25
|
+
default: () => '2026-10-03T14:02:00.000Z',
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const { default: devRecordingHandler } = await import('./devRecording.js');
|
|
29
|
+
const { default: servedBuilds } = await import('../../lib/server/recording/servedBuilds.js');
|
|
30
|
+
const { default: localDevToolsOnly } = await import('../middleware/localDevToolsOnly.js');
|
|
31
|
+
const { JOURNEY_COOKIES, writeJourneyCookie } = await import('../../lib/server/journeyCookies.js');
|
|
32
|
+
const { default: recordingCookiePayload } = await import(
|
|
33
|
+
'../../lib/server/recording/recordingCookiePayload.js'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const SESSION = '20261003T140311Z-k3x9qa';
|
|
37
|
+
const RUN_ID = '20261003T151200Z-p0d4rm';
|
|
38
|
+
const origin = 'http://localhost:3001';
|
|
39
|
+
let configDirectory;
|
|
40
|
+
|
|
41
|
+
function createApp() {
|
|
42
|
+
const app = new Hono();
|
|
43
|
+
app.use('/api/dev-recording', localDevToolsOnly());
|
|
44
|
+
app.post('/api/dev-recording', bodyLimit({ maxSize: 1024 * 1024 }), devRecordingHandler);
|
|
45
|
+
return app;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function record(extra = {}) {
|
|
49
|
+
return {
|
|
50
|
+
v: 1,
|
|
51
|
+
session: SESSION,
|
|
52
|
+
t: '2026-10-03T14:03:12.000Z',
|
|
53
|
+
kind: 'click',
|
|
54
|
+
scope: 'page',
|
|
55
|
+
page_id: 'tickets',
|
|
56
|
+
event: null,
|
|
57
|
+
...extra,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function cookieFor(payload) {
|
|
62
|
+
const cookie = writeJourneyCookie({ name: JOURNEY_COOKIES.recording.name, payload, origin });
|
|
63
|
+
return `${cookie.name}=${cookie.value}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function post({ body, headers = {} }) {
|
|
67
|
+
return createApp().request('/api/dev-recording', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
host: 'localhost:3001',
|
|
71
|
+
origin,
|
|
72
|
+
'content-type': 'application/json',
|
|
73
|
+
...headers,
|
|
74
|
+
},
|
|
75
|
+
body: typeof body === 'string' ? body : JSON.stringify(body),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readLines(...segments) {
|
|
80
|
+
const filePath = path.join(configDirectory, '.lowdefy', 'traces', ...segments);
|
|
81
|
+
return fs
|
|
82
|
+
.readFileSync(filePath, 'utf8')
|
|
83
|
+
.split('\n')
|
|
84
|
+
.filter((line) => line !== '')
|
|
85
|
+
.map((line) => JSON.parse(line));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function tracesExist() {
|
|
89
|
+
return fs.existsSync(path.join(configDirectory, '.lowdefy', 'traces'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-dev-recording-'));
|
|
94
|
+
process.env.LOWDEFY_DIRECTORY_CONFIG = configDirectory;
|
|
95
|
+
delete process.env.LOWDEFY_DEV_RECORD;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
fs.rmSync(configDirectory, { recursive: true, force: true });
|
|
100
|
+
delete process.env.LOWDEFY_DIRECTORY_CONFIG;
|
|
101
|
+
delete process.env.LOWDEFY_DEV_RECORD;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('POST /api/dev-recording refuses a cross-site request', async () => {
|
|
105
|
+
const res = await post({
|
|
106
|
+
body: { session: SESSION, records: [record()] },
|
|
107
|
+
headers: { origin: 'https://evil.example' },
|
|
108
|
+
});
|
|
109
|
+
expect(res.status).toBe(403);
|
|
110
|
+
expect(tracesExist()).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('POST /api/dev-recording refuses a request with no Origin', async () => {
|
|
114
|
+
const res = await createApp().request('/api/dev-recording', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { host: 'localhost:3001', 'content-type': 'application/json' },
|
|
117
|
+
body: JSON.stringify({ session: SESSION, records: [record()] }),
|
|
118
|
+
});
|
|
119
|
+
expect(res.status).toBe(403);
|
|
120
|
+
expect(tracesExist()).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('POST /api/dev-recording answers 400 for a session id that is not a trace id', async () => {
|
|
124
|
+
for (const session of ['../x', '', `${SESSION}/../../x`]) {
|
|
125
|
+
const res = await post({ body: { session, records: [record()] } });
|
|
126
|
+
expect(res.status).toBe(400);
|
|
127
|
+
}
|
|
128
|
+
expect(tracesExist()).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('POST /api/dev-recording answers 400 for more than 500 records or non-object records', async () => {
|
|
132
|
+
const many = Array.from({ length: 501 }, () => record());
|
|
133
|
+
expect((await post({ body: { session: SESSION, records: many } })).status).toBe(400);
|
|
134
|
+
expect((await post({ body: { session: SESSION, records: ['x'] } })).status).toBe(400);
|
|
135
|
+
expect((await post({ body: 'not json' })).status).toBe(400);
|
|
136
|
+
expect(tracesExist()).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('POST /api/dev-recording refuses a body over 1 MiB', async () => {
|
|
140
|
+
const big = 'x'.repeat(1024 * 1024 + 1);
|
|
141
|
+
const res = await post({ body: { session: SESSION, records: [record({ big })] } });
|
|
142
|
+
expect(res.status).toBe(413);
|
|
143
|
+
expect(tracesExist()).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('POST /api/dev-recording writes nothing when LOWDEFY_DEV_RECORD is false', async () => {
|
|
147
|
+
process.env.LOWDEFY_DEV_RECORD = 'false';
|
|
148
|
+
const res = await post({ body: { session: SESSION, records: [record()] } });
|
|
149
|
+
expect(res.status).toBe(204);
|
|
150
|
+
expect(tracesExist()).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('POST /api/dev-recording writes nothing when the recording cookie is off', async () => {
|
|
154
|
+
const res = await post({
|
|
155
|
+
body: { session: SESSION, records: [record()] },
|
|
156
|
+
headers: { cookie: cookieFor('off') },
|
|
157
|
+
});
|
|
158
|
+
expect(res.status).toBe(204);
|
|
159
|
+
expect(tracesExist()).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('POST /api/dev-recording writes nothing for a journey run that records nothing', async () => {
|
|
163
|
+
const payload = recordingCookiePayload({
|
|
164
|
+
recording: {
|
|
165
|
+
source: 'journey',
|
|
166
|
+
run: { id: '20261003T160000Z-77abcd', by: 'agent', journey: 'save-ticket' },
|
|
167
|
+
record: false,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const res = await post({
|
|
171
|
+
body: { session: SESSION, records: [record()] },
|
|
172
|
+
headers: { cookie: cookieFor(payload) },
|
|
173
|
+
});
|
|
174
|
+
expect(res.status).toBe(204);
|
|
175
|
+
expect(tracesExist()).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('POST /api/dev-recording appends lines to the dev session file, stamped with build and source', async () => {
|
|
179
|
+
await post({ body: { session: SESSION, records: [record(), record({ kind: 'change' })] } });
|
|
180
|
+
const res = await post({
|
|
181
|
+
body: { session: SESSION, records: [record({ source: 'journey', run: { id: RUN_ID } })] },
|
|
182
|
+
});
|
|
183
|
+
expect(res.status).toBe(204);
|
|
184
|
+
const lines = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
|
|
185
|
+
expect(lines).toHaveLength(3);
|
|
186
|
+
lines.forEach((line) => {
|
|
187
|
+
expect(line.build).toBe('2026-10-03T14:02:00.000Z');
|
|
188
|
+
expect(line.source).toBe('dev');
|
|
189
|
+
expect(line).not.toHaveProperty('run');
|
|
190
|
+
});
|
|
191
|
+
expect(lines[1].kind).toBe('change');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('POST /api/dev-recording writes a journey cookie run to its run file, overriding a client source', async () => {
|
|
195
|
+
const run = {
|
|
196
|
+
id: RUN_ID,
|
|
197
|
+
by: 'test',
|
|
198
|
+
journey: 'tests/journeys/tickets.yaml#Assign a ticket',
|
|
199
|
+
actor: 'main',
|
|
200
|
+
};
|
|
201
|
+
const res = await post({
|
|
202
|
+
body: { session: SESSION, records: [record({ source: 'dev' })] },
|
|
203
|
+
headers: {
|
|
204
|
+
cookie: cookieFor(recordingCookiePayload({ recording: { source: 'journey', run } })),
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
expect(res.status).toBe(204);
|
|
208
|
+
const [line] = readLines('journey', '2026-10-03', `${RUN_ID}.jsonl`);
|
|
209
|
+
expect(line).toMatchObject({ source: 'journey', run, session: SESSION });
|
|
210
|
+
expect(fs.existsSync(path.join(configDirectory, '.lowdefy', 'traces', 'dev'))).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('POST /api/dev-recording records a forged recording cookie as dev', async () => {
|
|
214
|
+
const payload = recordingCookiePayload({
|
|
215
|
+
recording: { source: 'journey', run: { id: RUN_ID, by: 'test' } },
|
|
216
|
+
});
|
|
217
|
+
const res = await post({
|
|
218
|
+
body: { session: SESSION, records: [record()] },
|
|
219
|
+
headers: { cookie: `lowdefy_recording=forged-token.${payload}` },
|
|
220
|
+
});
|
|
221
|
+
expect(res.status).toBe(204);
|
|
222
|
+
const [line] = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
|
|
223
|
+
expect(line.source).toBe('dev');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('POST /api/dev-recording keeps a build the process served a page config under', async () => {
|
|
227
|
+
servedBuilds.add('2026-10-03T13:58:00.000Z');
|
|
228
|
+
const res = await post({
|
|
229
|
+
body: { session: SESSION, records: [record({ build: '2026-10-03T13:58:00.000Z' })] },
|
|
230
|
+
});
|
|
231
|
+
expect(res.status).toBe(204);
|
|
232
|
+
const [line] = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
|
|
233
|
+
expect(line.build).toBe('2026-10-03T13:58:00.000Z');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('POST /api/dev-recording replaces a missing, unknown or non-string build with the build served now', async () => {
|
|
237
|
+
servedBuilds.add('2026-10-03T13:58:00.000Z');
|
|
238
|
+
const res = await post({
|
|
239
|
+
body: {
|
|
240
|
+
session: SESSION,
|
|
241
|
+
records: [
|
|
242
|
+
record(),
|
|
243
|
+
record({ build: null }),
|
|
244
|
+
record({ build: '2026-10-03T09:00:00.000Z' }),
|
|
245
|
+
record({ build: 42 }),
|
|
246
|
+
record({ build: { id: '2026-10-03T13:58:00.000Z' } }),
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
expect(res.status).toBe(204);
|
|
251
|
+
const lines = readLines('dev', '2026-10-03', `${SESSION}.jsonl`);
|
|
252
|
+
expect(lines.map((line) => line.build)).toEqual(Array(5).fill('2026-10-03T14:02:00.000Z'));
|
|
253
|
+
});
|
|
@@ -15,9 +15,17 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import getBuildStatus from '../../../lib/docs/getBuildStatus.js';
|
|
18
|
+
import getBuildStatusAfterEdits from '../../../lib/docs/getBuildStatusAfterEdits.js';
|
|
19
|
+
import readProxyBuildWait from '../../../lib/docs/readProxyBuildWait.js';
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
// ?wait=true answers once the dev server has processed the latest edits and
|
|
22
|
+
// built the pages they touched.
|
|
23
|
+
async function docsBuildStatusHandler(c) {
|
|
24
|
+
if (c.req.query('wait') === 'true') {
|
|
25
|
+
const proxyWait = readProxyBuildWait({ getHeader: (name) => c.req.header(name) });
|
|
26
|
+
return c.json(await getBuildStatusAfterEdits({ proxyWait }));
|
|
27
|
+
}
|
|
28
|
+
return c.json(await getBuildStatus());
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export default docsBuildStatusHandler;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
import { Hono } from 'hono';
|
|
19
|
+
|
|
20
|
+
jest.unstable_mockModule('../../../lib/docs/clientErrorStore.js', () => ({
|
|
21
|
+
default: { list: () => [] },
|
|
22
|
+
}));
|
|
23
|
+
jest.unstable_mockModule('../../../lib/docs/serverErrorStore.js', () => ({
|
|
24
|
+
default: { list: () => [] },
|
|
25
|
+
}));
|
|
26
|
+
jest.unstable_mockModule('../../../lib/docs/getBuildId.js', () => ({
|
|
27
|
+
default: () => '2026-10-03T08:00:00.000Z',
|
|
28
|
+
}));
|
|
29
|
+
jest.unstable_mockModule('../../../lib/docs/getPageBuildStatus.js', () => ({
|
|
30
|
+
default: () => ({ unbuilt: 0 }),
|
|
31
|
+
}));
|
|
32
|
+
jest.unstable_mockModule('../../../lib/docs/readBuildArtifact.js', () => ({
|
|
33
|
+
default: () => ({ status: 'ok', errors: [], warnings: [] }),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const { default: docsBuildStatusHandler } = await import('./buildStatus.js');
|
|
37
|
+
|
|
38
|
+
test('GET /lowdefy-docs/build-status includes the buildId of the build being served', async () => {
|
|
39
|
+
const app = new Hono();
|
|
40
|
+
app.get('/lowdefy-docs/build-status', docsBuildStatusHandler);
|
|
41
|
+
|
|
42
|
+
const res = await app.request('/lowdefy-docs/build-status');
|
|
43
|
+
|
|
44
|
+
expect(res.status).toBe(200);
|
|
45
|
+
expect((await res.json()).buildId).toBe('2026-10-03T08:00:00.000Z');
|
|
46
|
+
});
|
|
@@ -14,14 +14,16 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import
|
|
17
|
+
import getDoc from '../../../lib/docs/getDoc.js';
|
|
18
18
|
|
|
19
19
|
function docsContentHandler(c) {
|
|
20
20
|
const slug = c.req.param('slug');
|
|
21
|
-
const doc =
|
|
21
|
+
const doc = getDoc({ slug });
|
|
22
22
|
if (doc === null) {
|
|
23
23
|
return c.json(
|
|
24
|
-
{
|
|
24
|
+
{
|
|
25
|
+
error: `No doc found for slug "${slug}". Use GET /lowdefy-docs/search?q=... to find the right slug.`,
|
|
26
|
+
},
|
|
25
27
|
404
|
|
26
28
|
);
|
|
27
29
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import evalOperator from '../../../lib/docs/evalOperator.js';
|
|
18
|
+
import parsePathParamsParam from './parsePathParamsParam.js';
|
|
18
19
|
import parseUserParam from './parseUserParam.js';
|
|
19
20
|
|
|
20
21
|
async function docsEvalOperatorHandler(c) {
|
|
@@ -42,7 +43,20 @@ async function docsEvalOperatorHandler(c) {
|
|
|
42
43
|
return c.json({ error: userError }, 400);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
const
|
|
46
|
+
const { pathParams, error: pathParamsError } = parsePathParamsParam({ value: body.pathParams });
|
|
47
|
+
if (pathParamsError) {
|
|
48
|
+
return c.json({ error: pathParamsError }, 400);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = await evalOperator({
|
|
52
|
+
origin,
|
|
53
|
+
pageId,
|
|
54
|
+
pathParams,
|
|
55
|
+
expression,
|
|
56
|
+
source,
|
|
57
|
+
user,
|
|
58
|
+
data: body.data,
|
|
59
|
+
});
|
|
46
60
|
if (result.error) {
|
|
47
61
|
// A contradictory call (`user` with `source: 'tab'`) is the caller's
|
|
48
62
|
// mistake, not a failed render — 502 would read as "the renderer broke" and
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { streamSSE } from 'hono/streaming';
|
|
18
|
+
|
|
19
|
+
import { bootedAt, subscribe } from '../../../lib/docs/devEventBus.js';
|
|
20
|
+
|
|
21
|
+
// SSE push channel for agents without MCP support: the same dev events the MCP
|
|
22
|
+
// endpoint delivers as notifications/message, one SSE frame each, named by
|
|
23
|
+
// event type. The first frame is always the restart event so a reconnecting
|
|
24
|
+
// client can tell a server restart from a dropped connection.
|
|
25
|
+
async function docsEventsHandler(c) {
|
|
26
|
+
return streamSSE(c, async (stream) => {
|
|
27
|
+
let open = true;
|
|
28
|
+
const writeEvent = (event) =>
|
|
29
|
+
stream.writeSSE({ event: event.type, data: JSON.stringify(event) });
|
|
30
|
+
|
|
31
|
+
// Subscribe and arm the abort hook before the first await: onAbort does not
|
|
32
|
+
// fire for a listener added after the client has already gone.
|
|
33
|
+
const unsubscribe = subscribe(writeEvent);
|
|
34
|
+
stream.onAbort(() => {
|
|
35
|
+
open = false;
|
|
36
|
+
unsubscribe();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await writeEvent({ type: 'restart', timestamp: new Date().toISOString(), bootedAt });
|
|
40
|
+
|
|
41
|
+
while (open) {
|
|
42
|
+
await stream.sleep(15000);
|
|
43
|
+
if (open) {
|
|
44
|
+
await stream.writeSSE({ event: 'ping', data: '' });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default docsEventsHandler;
|