@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.
Files changed (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +60 -47
  527. package/package.original.json +60 -47
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +173 -3
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +3 -1
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,127 @@
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
+
21
+ import { Hono } from 'hono';
22
+
23
+ // The middleware reads build/buildStatus.json from process.cwd() via
24
+ // readBuildArtifact — point it at a throwaway server directory before import.
25
+ const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-stale-mw-test-'));
26
+ fs.mkdirSync(path.join(fixtureDir, 'build'));
27
+ process.chdir(fixtureDir);
28
+
29
+ const statusPath = path.join(fixtureDir, 'build', 'buildStatus.json');
30
+
31
+ function setBuildFailed() {
32
+ fs.writeFileSync(
33
+ statusPath,
34
+ JSON.stringify({ status: 'error', timestamp: '2026-02-03T04:05:06.000Z', errors: [] })
35
+ );
36
+ }
37
+
38
+ function setBuildOk() {
39
+ fs.writeFileSync(
40
+ statusPath,
41
+ JSON.stringify({ status: 'ok', timestamp: '2026-02-03T04:05:06.000Z', errors: [] })
42
+ );
43
+ }
44
+
45
+ const { default: staleFlag } = await import('./staleFlag.js');
46
+
47
+ function createApp() {
48
+ const app = new Hono();
49
+ // Mirrors src/app.js: the MCP transport is registered BEFORE the middleware
50
+ // so its JSON-RPC envelopes are never given extra members.
51
+ app.post('/lowdefy-docs/mcp', (c) => c.json({ jsonrpc: '2.0', id: 1, result: {} }));
52
+ app.use('/lowdefy-docs/*', staleFlag());
53
+ app.get('/lowdefy-docs/object', (c) => c.json({ pageId: 'home', blocks: [] }));
54
+ app.get('/lowdefy-docs/own-stale', (c) => c.json({ stale: false, staleSince: 'handler' }));
55
+ app.get('/lowdefy-docs/array', (c) => c.json([{ type: 'Button' }]));
56
+ app.get('/lowdefy-docs/markdown', (c) =>
57
+ c.text('# Overview\n', 200, { 'Content-Type': 'text/markdown; charset=UTF-8' })
58
+ );
59
+ app.get('/lowdefy-docs/png', (c) =>
60
+ c.body(new Uint8Array([1, 2, 3]), 200, { 'Content-Type': 'image/png' })
61
+ );
62
+ return app;
63
+ }
64
+
65
+ test('staleFlag merges the stale fields into a JSON object body when the build failed', async () => {
66
+ setBuildFailed();
67
+ const res = await createApp().request('/lowdefy-docs/object');
68
+ const body = await res.json();
69
+ expect(body.pageId).toEqual('home');
70
+ expect(body.stale).toBe(true);
71
+ expect(body.staleSince).toEqual('2026-02-03T04:05:06.000Z');
72
+ expect(body.staleReason).toContain('The last build failed.');
73
+ expect(res.headers.get('X-Lowdefy-Stale')).toEqual('true');
74
+ expect(res.headers.get('X-Lowdefy-Stale-Since')).toEqual('2026-02-03T04:05:06.000Z');
75
+ });
76
+
77
+ test('staleFlag never overwrites a stale key the handler already set', async () => {
78
+ setBuildFailed();
79
+ const res = await createApp().request('/lowdefy-docs/own-stale');
80
+ const body = await res.json();
81
+ expect(body.stale).toBe(false);
82
+ expect(body.staleSince).toEqual('handler');
83
+ expect(body.staleReason).toContain('The last build failed.');
84
+ });
85
+
86
+ test('staleFlag leaves a JSON array body unchanged but still sets the headers', async () => {
87
+ setBuildFailed();
88
+ const res = await createApp().request('/lowdefy-docs/array');
89
+ expect(await res.json()).toEqual([{ type: 'Button' }]);
90
+ expect(res.headers.get('X-Lowdefy-Stale')).toEqual('true');
91
+ expect(res.headers.get('X-Lowdefy-Stale-Since')).toEqual('2026-02-03T04:05:06.000Z');
92
+ });
93
+
94
+ test('staleFlag prepends a banner to a markdown body when the build failed', async () => {
95
+ setBuildFailed();
96
+ const res = await createApp().request('/lowdefy-docs/markdown');
97
+ const body = await res.text();
98
+ expect(body.startsWith('> STALE: The last build failed.')).toBe(true);
99
+ expect(body).toContain('# Overview');
100
+ expect(res.headers.get('X-Lowdefy-Stale')).toEqual('true');
101
+ });
102
+
103
+ test('staleFlag leaves other content types untouched but still sets the headers', async () => {
104
+ setBuildFailed();
105
+ const res = await createApp().request('/lowdefy-docs/png');
106
+ expect(new Uint8Array(await res.arrayBuffer())).toEqual(new Uint8Array([1, 2, 3]));
107
+ expect(res.headers.get('X-Lowdefy-Stale')).toEqual('true');
108
+ });
109
+
110
+ test('staleFlag changes nothing when the last build succeeded', async () => {
111
+ setBuildOk();
112
+ const app = createApp();
113
+ const jsonRes = await app.request('/lowdefy-docs/object');
114
+ expect(await jsonRes.json()).toEqual({ pageId: 'home', blocks: [] });
115
+ expect(jsonRes.headers.get('X-Lowdefy-Stale')).toBe(null);
116
+ const markdownRes = await app.request('/lowdefy-docs/markdown');
117
+ expect(await markdownRes.text()).toEqual('# Overview\n');
118
+ });
119
+
120
+ // MCP results carry their own STALE notice from createDocsMcpServer; a
121
+ // JSON-RPC envelope must not gain unknown top-level members.
122
+ test('staleFlag does not touch a route registered before it, such as the MCP transport', async () => {
123
+ setBuildFailed();
124
+ const res = await createApp().request('/lowdefy-docs/mcp', { method: 'POST' });
125
+ expect(await res.json()).toEqual({ jsonrpc: '2.0', id: 1, result: {} });
126
+ expect(res.headers.get('X-Lowdefy-Stale')).toBe(null);
127
+ });
@@ -0,0 +1,38 @@
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 { callerStamp } from '@lowdefy/helpers';
18
+
19
+ // Stamps page, request and endpoint responses with the caller the server
20
+ // answered as, in full (see callerStamp): user and organization, session, roles
21
+ // there and every organization the person belongs to. The client compares it
22
+ // with the caller it renders and reloads when the person, organization or
23
+ // roles differ, so a tab never shows one caller's data or access after the
24
+ // session moved on. Registered after apiContext, so the resolved caller is
25
+ // always on the context here; set after next() so error responses carry it
26
+ // too.
27
+ function stampCaller() {
28
+ return async function stampCallerMiddleware(c, next) {
29
+ await next();
30
+ const context = c.get('lowdefyContext');
31
+ c.header(
32
+ 'x-lowdefy-caller',
33
+ callerStamp({ user: context.user, authSession: context.authSession })
34
+ );
35
+ };
36
+ }
37
+
38
+ export default stampCaller;
@@ -0,0 +1,64 @@
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 stampCaller from './stampCaller.js';
20
+
21
+ function createApp({ user, authSession }) {
22
+ const app = new Hono();
23
+ app.use('/api/*', async (c, next) => {
24
+ c.set('lowdefyContext', { user, authSession });
25
+ return next();
26
+ });
27
+ app.use('/api/request/*', stampCaller());
28
+ app.post('/api/request/page/ok', (c) => c.json({ ok: true }));
29
+ app.post('/api/request/page/throws', () => {
30
+ throw new Error('Request failed.');
31
+ });
32
+ app.onError((error, c) => c.json({ message: error.message }, 500));
33
+ return app;
34
+ }
35
+
36
+ test.each([
37
+ ['/api/request/page/ok', 200],
38
+ ['/api/request/page/throws', 500],
39
+ ])('stampCaller sets x-lowdefy-caller on %s (%i)', async (path, status) => {
40
+ const app = createApp({
41
+ user: { id: 'user_1', organization_id: 'org_1', roles: ['editor', 'admin'] },
42
+ authSession: { id: 'session_1', memberOrganizationIds: ['org_2', 'org_1'] },
43
+ });
44
+ const res = await app.request(path, { method: 'POST' });
45
+ expect(res.status).toBe(status);
46
+ // The fixed vector callerStamp's own tests pin, so server and client agree.
47
+ expect(res.headers.get('x-lowdefy-caller')).toBe(
48
+ '82e92667feb5824d; session=3a1d7b6848ba4816; roles=228d50a09b1b1e2b; memberships=476954726056d64d'
49
+ );
50
+ });
51
+
52
+ test('stampCaller names the caller and roles alone for a caller with no session', async () => {
53
+ const app = createApp({
54
+ user: { id: 'user_1', organization_id: 'org_1', roles: ['editor', 'admin'] },
55
+ });
56
+ const res = await app.request('/api/request/page/ok', { method: 'POST' });
57
+ expect(res.headers.get('x-lowdefy-caller')).toBe('82e92667feb5824d; roles=228d50a09b1b1e2b');
58
+ });
59
+
60
+ test('stampCaller sends an empty x-lowdefy-caller for an anonymous caller', async () => {
61
+ const app = createApp({ user: null });
62
+ const res = await app.request('/api/request/page/ok', { method: 'POST' });
63
+ expect(res.headers.get('x-lowdefy-caller')).toBe('');
64
+ });
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { callAgent } from '@lowdefy/api';
17
+ import { callAgent, parseRequestBody } from '@lowdefy/api';
18
18
  import { translate, type } from '@lowdefy/helpers';
19
19
 
20
20
  import getPathSegments from '../lib/getPathSegments.js';
@@ -23,7 +23,9 @@ async function agentHandler(c) {
23
23
  const context = c.get('lowdefyContext');
24
24
  const t = (key, values) => translate({ key, values, i18n: context.i18n });
25
25
  if (c.req.method !== 'POST') {
26
- throw new Error(t('agent.runtime.methodNotAllowed'));
26
+ // A wrong-method request is client-caused: answer 405 rather than raising a
27
+ // fault that would be logged at error level and answered with a 500.
28
+ return c.json({ error: t('agent.runtime.methodNotAllowed') }, 405);
27
29
  }
28
30
  const segments = getPathSegments(c, '/api/agent/');
29
31
  if (segments.length < 2) {
@@ -33,22 +35,32 @@ async function agentHandler(c) {
33
35
  const pageId = segments.slice(0, -1).join('/');
34
36
  context.logger.info({ event: 'call_agent', agentId, pageId });
35
37
  const { conversationId } = c.req.query();
36
- const { messages, urlQuery, sharedState } = await c.req.json();
38
+ const { messages, pathParams, urlQuery, sharedState, sharedStateReadOnly } = parseRequestBody({
39
+ text: await c.req.text(),
40
+ });
37
41
  if (!Array.isArray(messages)) {
38
42
  return c.json({ error: t('agent.runtime.messagesMustBeArray') }, 400);
39
43
  }
40
44
  if (urlQuery != null && (typeof urlQuery !== 'object' || Array.isArray(urlQuery))) {
41
45
  return c.json({ error: t('agent.runtime.urlQueryMustBeObject') }, 400);
42
46
  }
47
+ if (pathParams != null && !type.isObject(pathParams)) {
48
+ return c.json({ error: t('agent.runtime.pathParamsMustBeObject') }, 400);
49
+ }
43
50
  if (sharedState != null && !type.isObject(sharedState)) {
44
51
  return c.json({ error: t('agent.runtime.sharedStateMustBeObject') }, 400);
45
52
  }
53
+ if (sharedStateReadOnly != null && !type.isBoolean(sharedStateReadOnly)) {
54
+ return c.json({ error: t('agent.runtime.sharedStateReadOnlyMustBeBoolean') }, 400);
55
+ }
46
56
  const { response: webResponse } = await callAgent(context, {
47
57
  agentId,
48
58
  pageId,
49
59
  messages,
50
60
  conversationId: conversationId ?? undefined,
51
61
  sharedState: sharedState ?? undefined,
62
+ sharedStateReadOnly: sharedStateReadOnly ?? undefined,
63
+ pathParams: pathParams ?? undefined,
52
64
  urlQuery: urlQuery ?? undefined,
53
65
  });
54
66
 
@@ -14,35 +14,44 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { authHandler } from '@hono/auth-js';
17
+ import { handleAuthRequest } from '@lowdefy/api';
18
+ import { type } from '@lowdefy/helpers';
18
19
 
19
20
  import authJson from '../../lib/build/auth.js';
20
- import getDevSession from '../../lib/server/auth/getDevSession.js';
21
+ import getAuth from '../../lib/server/auth/getAuth.js';
22
+ import getClientAddress from '../../lib/server/getClientAddress.js';
23
+ import getMockUser from '../../lib/server/auth/getMockUser.js';
21
24
 
22
- // Replaces pages/api/auth/[...nextauth].js. Hono routes HEAD requests through
23
- // GET handlers, so the corporate-email pre-check branch must live inside the
24
- // middleware, before delegating to the Auth.js handler. See:
25
- // https://next-auth.js.org/tutorials/avoid-corporate-link-checking-email-provider
26
- function authMiddleware() {
27
- const handler = authJson.configured === true ? authHandler() : null;
28
- return async function auth(c, next) {
25
+ // Mounts BetterAuth's Web Standard handler on /api/auth/*. Hono routes HEAD
26
+ // requests through GET handlers, so HEAD short-circuits before the handler -
27
+ // corporate email link-checkers pre-fetch magic-link and verification URLs
28
+ // with HEAD, and letting those reach the handler would consume the one-time
29
+ // token before the user clicks. BetterAuth rate-limits by the client address
30
+ // getClientAddress resolves.
31
+ function authMiddleware({ logger }) {
32
+ return async function auth(c) {
29
33
  if (authJson.configured !== true) {
30
34
  return c.json({ message: 'Auth not configured' }, 404);
31
35
  }
36
+ if (getMockUser()) {
37
+ // Mock user active - no auth engine runs; the get-session stub in
38
+ // app.js is the only auth endpoint.
39
+ return c.json({ message: 'Auth engine disabled while dev.mockUser is active' }, 404);
40
+ }
41
+ if (!type.isNone(c.get('lowdefyContext')?.dataSet)) {
42
+ // A journey on a data set: the auth engine is bound to the app's real auth database, so its
43
+ // browser contexts never reach it. Data set users are injected callers; the get-session stub
44
+ // in app.js answers for them.
45
+ return c.json({ message: 'Auth engine disabled for journeys on a data set' }, 404);
46
+ }
32
47
  if (c.req.method === 'HEAD') {
33
48
  return c.body(null, 200);
34
49
  }
35
- // Dev sessions (mock user, headless renderer cookie) exist outside
36
- // Auth.js. getDevSession is the same function the server request
37
- // context uses (lib/server/auth/session.js), so the session the browser
38
- // client sees here is identical to the one requests authenticate with.
39
- if (c.req.method === 'GET' && c.req.path.endsWith('/api/auth/session')) {
40
- const devSession = await getDevSession(c);
41
- if (devSession) {
42
- return c.json(devSession);
43
- }
44
- }
45
- return handler(c, next);
50
+ return handleAuthRequest({
51
+ auth: getAuth({ logger }),
52
+ request: c.req.raw,
53
+ clientAddress: getClientAddress(c),
54
+ });
46
55
  };
47
56
  }
48
57
 
@@ -14,71 +14,45 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { Hono } from 'hono';
18
17
  import { jest } from '@jest/globals';
19
18
 
20
- // Only the route wiring is under test here — getDevSession's own behaviour
21
- // (mock/headless resolution, session-callback parity) is covered in
22
- // lib/server/auth/getDevSession.test.mjs, and the real Auth.js engine is
23
- // stubbed with a sentinel handler so "delegated to Auth.js" is observable.
24
- const mockAuthHandler = jest.fn(() => (c) => c.json({ delegated: true }));
25
- jest.unstable_mockModule('@hono/auth-js', () => ({
26
- authHandler: mockAuthHandler,
27
- }));
28
- jest.unstable_mockModule('../../lib/build/auth.js', () => ({
29
- default: { configured: true },
19
+ const handleAuthRequest = jest.fn(async () => new Response('engine'));
20
+ const getAuth = jest.fn(() => ({}));
21
+
22
+ jest.unstable_mockModule('@lowdefy/api', () => ({ handleAuthRequest }));
23
+ jest.unstable_mockModule('../../lib/build/auth.js', () => ({ default: { configured: true } }));
24
+ jest.unstable_mockModule('../../lib/server/auth/getAuth.js', () => ({ default: getAuth }));
25
+ jest.unstable_mockModule('../../lib/server/auth/getMockUser.js', () => ({
26
+ default: jest.fn(() => undefined),
30
27
  }));
31
- const mockGetDevSession = jest.fn(async () => undefined);
32
- jest.unstable_mockModule('../../lib/server/auth/getDevSession.js', () => ({
33
- default: mockGetDevSession,
28
+ jest.unstable_mockModule('../../lib/server/getClientAddress.js', () => ({
29
+ default: jest.fn(() => '127.0.0.1'),
34
30
  }));
35
31
 
36
32
  const { default: authMiddleware } = await import('./auth.js');
37
33
 
38
- function createApp() {
39
- const app = new Hono();
40
- app.use('/api/auth/*', authMiddleware());
41
- return app;
42
- }
43
-
44
- afterEach(() => {
45
- mockGetDevSession.mockReset();
46
- mockGetDevSession.mockResolvedValue(undefined);
47
- });
48
-
49
- test('GET /api/auth/session returns the dev session so the client matches the server', async () => {
50
- const devSession = {
51
- user: {
52
- id: 'dev-mock-user',
53
- roles: ['admin'],
54
- global_attributes: { company_ids: ['C-1'] },
55
- },
56
- hashed_id: 'abc',
57
- expires: '2100-01-01T00:00:00.000Z',
34
+ function createHonoContext({ context }) {
35
+ return {
36
+ get: (key) => (key === 'lowdefyContext' ? context : undefined),
37
+ json: (body, status) => ({ body, status }),
38
+ body: (body, status) => ({ body, status }),
39
+ req: { method: 'POST', raw: new Request('http://localhost/api/auth/sign-out') },
58
40
  };
59
- mockGetDevSession.mockResolvedValue(devSession);
60
-
61
- const res = await createApp().request('/api/auth/session');
62
- expect(res.status).toEqual(200);
63
- expect(await res.json()).toEqual(devSession);
64
- });
65
-
66
- test('GET /api/auth/session delegates to Auth.js when no dev session exists', async () => {
67
- const res = await createApp().request('/api/auth/session');
68
- expect(res.status).toEqual(200);
69
- expect(await res.json()).toEqual({ delegated: true });
70
- });
71
-
72
- test('other auth routes delegate to Auth.js even when a dev session exists', async () => {
73
- mockGetDevSession.mockResolvedValue({ user: { id: 'mock' } });
41
+ }
74
42
 
75
- const res = await createApp().request('/api/auth/csrf');
76
- expect(await res.json()).toEqual({ delegated: true });
43
+ test('authMiddleware refuses a data-session request without reaching the auth engine', async () => {
44
+ const handler = authMiddleware({ logger: {} });
45
+ const result = await handler(createHonoContext({ context: { dataSet: 'staging-sample' } }));
46
+ expect(result).toEqual({
47
+ body: { message: 'Auth engine disabled for journeys on a data set' },
48
+ status: 404,
49
+ });
50
+ expect(getAuth).not.toHaveBeenCalled();
51
+ expect(handleAuthRequest).not.toHaveBeenCalled();
77
52
  });
78
53
 
79
- test('POST /api/auth/session delegates to Auth.js even when a dev session exists', async () => {
80
- mockGetDevSession.mockResolvedValue({ user: { id: 'mock' } });
81
-
82
- const res = await createApp().request('/api/auth/session', { method: 'POST' });
83
- expect(await res.json()).toEqual({ delegated: true });
54
+ test('authMiddleware hands a request with no data session to the auth engine', async () => {
55
+ const handler = authMiddleware({ logger: {} });
56
+ await handler(createHonoContext({ context: {} }));
57
+ expect(handleAuthRequest).toHaveBeenCalledTimes(1);
84
58
  });
@@ -14,30 +14,29 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { logClientError } from '@lowdefy/api';
17
+ import { logClientError, parseRequestBody } from '@lowdefy/api';
18
18
 
19
19
  import clientErrorStore from '../../lib/docs/clientErrorStore.js';
20
+ import errorRecordingStamp from '../../lib/server/recording/errorRecordingStamp.js';
21
+ import createSameOriginGuard from '../middleware/createSameOriginGuard.js';
22
+
23
+ const guardSameOrigin = createSameOriginGuard();
20
24
 
21
25
  async function clientErrorHandler(c) {
22
26
  if (c.req.method !== 'POST') {
23
- throw new Error('Only POST requests are supported.');
27
+ // A wrong-method request is client-caused: answer 405 rather than raising a
28
+ // fault that would be logged at error level and answered with a 500.
29
+ return c.json({ error: 'Method not allowed.' }, 405);
24
30
  }
25
31
  const context = c.get('lowdefyContext');
26
32
 
27
- const origin = c.req.header('origin');
28
- if (!origin) {
29
- return c.json({ error: 'Forbidden' }, 403);
30
- }
31
- try {
32
- if (new URL(origin).host !== c.req.header('host')) {
33
- return c.json({ error: 'Forbidden' }, 403);
34
- }
35
- } catch {
36
- return c.json({ error: 'Forbidden' }, 403);
33
+ const forbidden = guardSameOrigin(c);
34
+ if (forbidden) {
35
+ return forbidden;
37
36
  }
38
37
 
39
38
  // Dev keeps `received` in the payload — it powers error tracing in dev tools.
40
- const body = await c.req.json();
39
+ const body = parseRequestBody({ text: await c.req.text() });
41
40
  const { error, ...response } = await logClientError(context, body);
42
41
 
43
42
  // Feed the agent-facing feedback loop (GET /lowdefy-docs/build-status) with a
@@ -49,6 +48,7 @@ async function clientErrorHandler(c) {
49
48
  message: error?.message ?? null,
50
49
  source: response.source ?? null,
51
50
  config: response.config ?? null,
51
+ recording: errorRecordingStamp(context.recording),
52
52
  });
53
53
 
54
54
  return c.json(response);
@@ -26,7 +26,9 @@ import getPathSegments from '../lib/getPathSegments.js';
26
26
  // the environment whose schedules apply in `x-lowdefy-cron-environment`.
27
27
  async function cronHandler(c) {
28
28
  if (c.req.method !== 'GET') {
29
- throw new Error('Only GET requests are supported.');
29
+ // A wrong-method request is client-caused: answer 405 rather than raising a
30
+ // fault that would be logged at error level and answered with a 500.
31
+ return c.json({ error: 'Method not allowed.' }, 405);
30
32
  }
31
33
  const context = c.get('lowdefyContext');
32
34
 
@@ -26,7 +26,9 @@ import getPathSegments from '../lib/getPathSegments.js';
26
26
  // fails closed).
27
27
  async function cronForwardHandler(c) {
28
28
  if (c.req.method !== 'GET') {
29
- throw new Error('Only GET requests are supported.');
29
+ // A wrong-method request is client-caused: answer 405 rather than raising a
30
+ // fault that would be logged at error level and answered with a 500.
31
+ return c.json({ error: 'Method not allowed.' }, 405);
30
32
  }
31
33
  const context = c.get('lowdefyContext');
32
34
 
@@ -14,23 +14,34 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { runDetachedEndpoint } from '@lowdefy/api';
17
+ import { acceptDetachedEndpoint, parseRequestBody } from '@lowdefy/api';
18
18
 
19
19
  import getPathSegments from '../lib/getPathSegments.js';
20
20
 
21
21
  // Target of a CallApi step with `detached: true`: the calling invocation
22
22
  // fire-and-forgets a POST here, so the endpoint runs in its OWN function
23
23
  // invocation with a fresh duration budget (chainable bounded work without a
24
- // queue). Same transport auth as cron (CRON_SECRET, fails closed) and the same
25
- // system execution context — InternalApi endpoints are callable, `_user` is
26
- // undefined. Delivery is at-most-once with no retry: targets must be
27
- // idempotent.
24
+ // queue). Transport auth is CRON_SECRET (fails closed), which proves the
25
+ // request originated from the deployment. The run carries the DISPATCHER'S
26
+ // identity (Decision 4): the request body carries a server-built principal
27
+ // snapshot that runDetachedEndpoint rehydrates, so nested calls authorize
28
+ // against the dispatcher, not a forced system context. Delivery is at-most-once
29
+ // with no retry: targets must be idempotent.
30
+ //
31
+ // The route answers 202 as soon as the call is authorized and parsed; the
32
+ // endpoint runs after the response under this invocation's waitUntil
33
+ // (acceptDetachedEndpoint). The dispatcher is therefore released at once
34
+ // instead of staying alive - and billed - until the target's routine ends.
28
35
  async function detachedHandler(c) {
29
36
  if (c.req.method !== 'POST') {
30
- throw new Error('Only POST requests are supported.');
37
+ // A wrong-method request is client-caused: answer 405 rather than raising a
38
+ // fault that would be logged at error level and answered with a 500.
39
+ return c.json({ error: 'Method not allowed.' }, 405);
31
40
  }
32
41
  const context = c.get('lowdefyContext');
33
42
 
43
+ // The secret gate stays first: an external POST without the secret is
44
+ // rejected before the body (and its principal assertion) is ever read.
34
45
  const secret = process.env.CRON_SECRET;
35
46
  if (!secret || c.req.header('authorization') !== `Bearer ${secret}`) {
36
47
  context.logger.warn({ event: 'detached_unauthorized' });
@@ -38,10 +49,12 @@ async function detachedHandler(c) {
38
49
  }
39
50
 
40
51
  const endpointId = getPathSegments(c, '/api/detached/').join('/');
41
- const { payload } = await c.req.json();
52
+ const { credentials, payload, principal } = parseRequestBody({ text: await c.req.text() });
42
53
  context.logger.info({ event: 'call_detached_endpoint', endpointId });
43
- const response = await runDetachedEndpoint(context, { endpointId, payload });
44
- return c.json(response);
54
+ return c.json(
55
+ acceptDetachedEndpoint(context, { credentials, endpointId, payload, principal }),
56
+ 202
57
+ );
45
58
  }
46
59
 
47
60
  export default detachedHandler;
@@ -14,7 +14,8 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { serializer } from '@lowdefy/helpers';
17
+ import { parseRequestBody } from '@lowdefy/api';
18
+ import { serializer, type } from '@lowdefy/helpers';
18
19
 
19
20
  import { readCheckpoint } from '../../lib/docs/checkpointStore.js';
20
21
  import { loadMocks } from '../../lib/docs/devMockRegistry.js';
@@ -62,15 +63,21 @@ function handleCheckpointBootstrap(c, { name }) {
62
63
  );
63
64
  }
64
65
 
65
- // Inspector.jsx posts { tabId, pageId } here whenever its tab navigates, so
66
- // the tab registry (lib/docs/tabChannel.js) knows which page each connected
67
- // tab is on without the tab reconnecting its event stream.
66
+ // Inspector.jsx posts { tabId, pageId, pathParams, instanceKey } here whenever
67
+ // the page instance on screen in its tab changes, so the tab registry
68
+ // (lib/docs/tabChannel.js) knows which instance each connected tab shows, and
69
+ // which it has rendered, without the tab reconnecting its event stream.
68
70
  async function handleTabPage(c) {
69
- const { tabId, pageId } = await c.req.json();
71
+ const { tabId, pageId, pathParams, instanceKey } = parseRequestBody({
72
+ text: await c.req.text(),
73
+ });
70
74
  if (!tabId) {
71
75
  return c.json({ error: 'Missing "tabId".' }, 400);
72
76
  }
73
- updateTabPage({ id: tabId, pageId });
77
+ if (!type.isString(pageId) || !type.isString(instanceKey)) {
78
+ return c.json({ error: 'Missing "pageId" or "instanceKey".' }, 400);
79
+ }
80
+ updateTabPage({ id: tabId, pageId, pathParams, instanceKey });
74
81
  return c.json({ ok: true });
75
82
  }
76
83
 
@@ -89,7 +96,9 @@ async function devInspectHandler(c) {
89
96
  }
90
97
 
91
98
  if (c.req.method !== 'POST') {
92
- throw new Error('Only GET and POST requests are supported.');
99
+ // A wrong-method request is client-caused: answer 405 rather than raising a
100
+ // fault that would be logged at error level and answered with a 500.
101
+ return c.json({ error: 'Method not allowed.' }, 405);
93
102
  }
94
103
 
95
104
  if (!checkOrigin(c)) {
@@ -100,7 +109,7 @@ async function devInspectHandler(c) {
100
109
  return handleTabPage(c);
101
110
  }
102
111
 
103
- const { requestId, result } = await c.req.json();
112
+ const { requestId, result } = parseRequestBody({ text: await c.req.text() });
104
113
  if (!requestId) {
105
114
  return c.json({ error: 'Missing "requestId".' }, 400);
106
115
  }