@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.
Files changed (623) 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 +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -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 +44 -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 +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -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 +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -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/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +15 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
@@ -0,0 +1,215 @@
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
+ import { jest } from '@jest/globals';
19
+
20
+ // The real URI helpers drive the challenge strings, so the header assertions
21
+ // below exercise the actual BETTER_AUTH_URL derivation - only createMcpServer
22
+ // is stubbed out.
23
+ const uriHelpers = await import('@lowdefy/api/routes/mcp/getMcpUri.js');
24
+
25
+ const mockCreateMcpServer = jest.fn();
26
+ jest.unstable_mockModule('@lowdefy/api', () => ({
27
+ ...uriHelpers,
28
+ createMcpServer: mockCreateMcpServer,
29
+ }));
30
+
31
+ jest.unstable_mockModule('@hono/mcp', () => ({
32
+ StreamableHTTPTransport: class {
33
+ handleRequest() {
34
+ return new Response(JSON.stringify({ served: true }), {
35
+ status: 200,
36
+ headers: { 'content-type': 'application/json' },
37
+ });
38
+ }
39
+ },
40
+ }));
41
+
42
+ const mockAuthJson = { configured: true, oauthProvider: { consentPage: '/oauth/consent' } };
43
+ jest.unstable_mockModule('../../lib/build/auth.js', () => ({
44
+ default: mockAuthJson,
45
+ }));
46
+
47
+ const { default: mcpHandler } = await import('./mcp.js');
48
+
49
+ const originalBetterAuthUrl = process.env.BETTER_AUTH_URL;
50
+
51
+ const mockReadConfigFile = jest.fn();
52
+
53
+ let contextOverrides;
54
+
55
+ function createApp() {
56
+ const app = new Hono();
57
+ app.use('*', async (c, next) => {
58
+ c.set('lowdefyContext', {
59
+ config: { basePath: '' },
60
+ logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
61
+ readConfigFile: mockReadConfigFile,
62
+ ...contextOverrides,
63
+ });
64
+ await next();
65
+ });
66
+ app.all('/api/mcp', mcpHandler);
67
+ return app;
68
+ }
69
+
70
+ beforeEach(() => {
71
+ process.env.BETTER_AUTH_URL = 'https://app.test.com';
72
+ mockAuthJson.oauthProvider = { consentPage: '/oauth/consent' };
73
+ contextOverrides = {};
74
+ mockReadConfigFile.mockReset();
75
+ mockCreateMcpServer.mockReset();
76
+ mockCreateMcpServer.mockResolvedValue({ connect: jest.fn() });
77
+ mockReadConfigFile.mockResolvedValue({ configured: true, hasPublicTool: true, endpoints: [] });
78
+ });
79
+
80
+ afterAll(() => {
81
+ if (originalBetterAuthUrl === undefined) {
82
+ delete process.env.BETTER_AUTH_URL;
83
+ } else {
84
+ process.env.BETTER_AUTH_URL = originalBetterAuthUrl;
85
+ }
86
+ });
87
+
88
+ test('mcpHandler returns 404 when mcp is not configured', async () => {
89
+ mockReadConfigFile.mockResolvedValue({ configured: false });
90
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
91
+ expect(res.status).toEqual(404);
92
+ expect(await res.json()).toEqual({ error: 'MCP is not configured.' });
93
+ expect(mockCreateMcpServer).not.toHaveBeenCalled();
94
+ });
95
+
96
+ test('mcpHandler challenges an invalid token with the resource metadata pointer', async () => {
97
+ contextOverrides.mcpAuth = { tokenStatus: 'invalid', parseableJwt: true };
98
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
99
+ expect(res.status).toEqual(401);
100
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
101
+ 'Bearer resource_metadata="https://app.test.com/.well-known/oauth-protected-resource/api/mcp"'
102
+ );
103
+ expect(mockCreateMcpServer).not.toHaveBeenCalled();
104
+ });
105
+
106
+ test('mcpHandler extends the challenge with invalid_token when the bearer is not a JWT', async () => {
107
+ contextOverrides.mcpAuth = { tokenStatus: 'invalid', parseableJwt: false };
108
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
109
+ expect(res.status).toEqual(401);
110
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
111
+ 'Bearer error="invalid_token", error_description="The access token is not a JWT. Connect with a client that sends the RFC 8707 resource parameter.", resource_metadata="https://app.test.com/.well-known/oauth-protected-resource/api/mcp"'
112
+ );
113
+ });
114
+
115
+ test('mcpHandler tells a revoked grant to reconnect and choose an organization', async () => {
116
+ contextOverrides.mcpAuth = {
117
+ clientId: 'client_1',
118
+ organizationId: 'org_1',
119
+ tokenStatus: 'invalid',
120
+ parseableJwt: true,
121
+ revoked: true,
122
+ };
123
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
124
+ expect(res.status).toEqual(401);
125
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
126
+ 'Bearer error="invalid_token", error_description="This connection was disconnected. Reconnect to choose the organization to work in.", resource_metadata="https://app.test.com/.well-known/oauth-protected-resource/api/mcp"'
127
+ );
128
+ expect(mockCreateMcpServer).not.toHaveBeenCalled();
129
+ });
130
+
131
+ test('mcpHandler gives a token without membership the same challenge as an invalid token', async () => {
132
+ contextOverrides.mcpAuth = {
133
+ clientId: 'client_1',
134
+ organizationId: 'org_1',
135
+ tokenStatus: 'invalid',
136
+ parseableJwt: true,
137
+ noMembership: true,
138
+ };
139
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
140
+ expect(res.status).toEqual(401);
141
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
142
+ 'Bearer resource_metadata="https://app.test.com/.well-known/oauth-protected-resource/api/mcp"'
143
+ );
144
+ expect(mockCreateMcpServer).not.toHaveBeenCalled();
145
+ });
146
+
147
+ test('mcpHandler challenges an anonymous request when the surface has no public tool', async () => {
148
+ mockReadConfigFile.mockResolvedValue({ configured: true, hasPublicTool: false, endpoints: [] });
149
+ contextOverrides.mcpAuth = { tokenStatus: 'none', parseableJwt: true };
150
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
151
+ expect(res.status).toEqual(401);
152
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
153
+ 'Bearer resource_metadata="https://app.test.com/.well-known/oauth-protected-resource/api/mcp"'
154
+ );
155
+ });
156
+
157
+ test('mcpHandler serves an anonymous request when the surface has a public tool', async () => {
158
+ contextOverrides.mcpAuth = { tokenStatus: 'none', parseableJwt: true };
159
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
160
+ expect(res.status).toEqual(200);
161
+ expect(await res.json()).toEqual({ served: true });
162
+ });
163
+
164
+ test('mcpHandler serves a valid-token request and never challenges past the boundary', async () => {
165
+ mockReadConfigFile.mockResolvedValue({ configured: true, hasPublicTool: false, endpoints: [] });
166
+ contextOverrides.mcpAuth = {
167
+ tokenStatus: 'valid',
168
+ parseableJwt: true,
169
+ grantedScopes: ['mcp:read'],
170
+ };
171
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
172
+ expect(res.status).toEqual(200);
173
+ expect(await res.json()).toEqual({ served: true });
174
+ });
175
+
176
+ test('mcpHandler refuses a foreign Origin with a 403', async () => {
177
+ contextOverrides.mcpAuth = { tokenStatus: 'none', parseableJwt: true };
178
+ const res = await createApp().request('/api/mcp', {
179
+ method: 'POST',
180
+ headers: { Origin: 'https://evil.test' },
181
+ });
182
+ expect(res.status).toEqual(403);
183
+ expect(await res.json()).toEqual({ error: 'Origin not allowed.' });
184
+ expect(mockCreateMcpServer).not.toHaveBeenCalled();
185
+ });
186
+
187
+ test('mcpHandler accepts the pinned canonical origin', async () => {
188
+ contextOverrides.mcpAuth = { tokenStatus: 'none', parseableJwt: true };
189
+ const res = await createApp().request('/api/mcp', {
190
+ method: 'POST',
191
+ headers: { Origin: 'https://app.test.com' },
192
+ });
193
+ expect(res.status).toEqual(200);
194
+ });
195
+
196
+ test('mcpHandler accepts an origin registered on BetterAuth trustedOrigins', async () => {
197
+ contextOverrides.mcpAuth = { tokenStatus: 'none', parseableJwt: true };
198
+ contextOverrides.auth = { options: { trustedOrigins: ['https://studio.test.com'] } };
199
+ const res = await createApp().request('/api/mcp', {
200
+ method: 'POST',
201
+ headers: { Origin: 'https://studio.test.com' },
202
+ });
203
+ expect(res.status).toEqual(200);
204
+ });
205
+
206
+ test('mcpHandler serves openly with no envelope when oauthProvider is not configured', async () => {
207
+ delete mockAuthJson.oauthProvider;
208
+ contextOverrides.mcpAuth = { tokenStatus: 'invalid', parseableJwt: false };
209
+ const res = await createApp().request('/api/mcp', {
210
+ method: 'POST',
211
+ headers: { Origin: 'https://evil.test' },
212
+ });
213
+ expect(res.status).toEqual(200);
214
+ expect(await res.json()).toEqual({ served: true });
215
+ });
@@ -0,0 +1,125 @@
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
+ import { jest } from '@jest/globals';
19
+
20
+ // The real URI helpers drive the challenge strings, so the header assertions
21
+ // below exercise the actual BETTER_AUTH_URL derivation - only createMcpServer
22
+ // is stubbed out.
23
+ const uriHelpers = await import('@lowdefy/api/routes/mcp/getMcpUri.js');
24
+
25
+ const mockCreateMcpServer = jest.fn();
26
+ jest.unstable_mockModule('@lowdefy/api', () => ({
27
+ ...uriHelpers,
28
+ createMcpServer: mockCreateMcpServer,
29
+ }));
30
+
31
+ jest.unstable_mockModule('@hono/mcp', () => ({
32
+ StreamableHTTPTransport: class {
33
+ handleRequest() {
34
+ return new Response(JSON.stringify({ served: true }), {
35
+ status: 200,
36
+ headers: { 'content-type': 'application/json' },
37
+ });
38
+ }
39
+ },
40
+ }));
41
+
42
+ const mockAuthJson = { configured: true, oauthProvider: { consentPage: '/oauth/consent' } };
43
+ jest.unstable_mockModule('../../lib/build/auth.js', () => ({
44
+ default: mockAuthJson,
45
+ }));
46
+
47
+ const { default: mcpHandler } = await import('./mcp.js');
48
+
49
+ const originalBetterAuthUrl = process.env.BETTER_AUTH_URL;
50
+
51
+ const mockReadConfigFile = jest.fn();
52
+
53
+ let contextOverrides;
54
+
55
+ function createApp() {
56
+ const app = new Hono();
57
+ app.use('*', async (c, next) => {
58
+ c.set('lowdefyContext', {
59
+ config: { basePath: '' },
60
+ logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
61
+ readConfigFile: mockReadConfigFile,
62
+ ...contextOverrides,
63
+ });
64
+ await next();
65
+ });
66
+ app.all('/api/mcp', mcpHandler);
67
+ return app;
68
+ }
69
+
70
+ beforeEach(() => {
71
+ process.env.BETTER_AUTH_URL = 'https://app.test.com';
72
+ mockAuthJson.oauthProvider = { consentPage: '/oauth/consent' };
73
+ contextOverrides = {};
74
+ mockReadConfigFile.mockReset();
75
+ mockCreateMcpServer.mockReset();
76
+ mockCreateMcpServer.mockResolvedValue({ connect: jest.fn() });
77
+ mockReadConfigFile.mockResolvedValue({ configured: true, hasPublicTool: true, endpoints: [] });
78
+ });
79
+
80
+ afterAll(() => {
81
+ if (originalBetterAuthUrl === undefined) {
82
+ delete process.env.BETTER_AUTH_URL;
83
+ } else {
84
+ process.env.BETTER_AUTH_URL = originalBetterAuthUrl;
85
+ }
86
+ });
87
+
88
+ test('mcpHandler answers a refused member token with its reason and no resource metadata', async () => {
89
+ contextOverrides.mcpAuth = {
90
+ tokenStatus: 'invalid',
91
+ memberToken: true,
92
+ description: 'This token has expired.',
93
+ };
94
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
95
+ expect(res.status).toEqual(401);
96
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
97
+ 'Bearer error="invalid_token", error_description="This token has expired."'
98
+ );
99
+ expect(mockCreateMcpServer).not.toHaveBeenCalled();
100
+ });
101
+
102
+ test('mcpHandler answers a member token whose member is gone with that reason', async () => {
103
+ contextOverrides.mcpAuth = {
104
+ tokenStatus: 'invalid',
105
+ memberToken: true,
106
+ description: "This token's member can no longer use it.",
107
+ };
108
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
109
+ expect(res.status).toEqual(401);
110
+ expect(res.headers.get('WWW-Authenticate')).toEqual(
111
+ 'Bearer error="invalid_token", error_description="This token\'s member can no longer use it."'
112
+ );
113
+ });
114
+
115
+ test('mcpHandler serves a valid member token caller', async () => {
116
+ contextOverrides.mcpAuth = {
117
+ tokenStatus: 'valid',
118
+ tokenId: 'token_1',
119
+ organizationId: 'org_1',
120
+ grantedScopes: ['mcp:read', 'mcp:write'],
121
+ };
122
+ const res = await createApp().request('/api/mcp', { method: 'POST' });
123
+ expect(res.status).toEqual(200);
124
+ expect(mockCreateMcpServer).toHaveBeenCalledTimes(1);
125
+ });
@@ -0,0 +1,34 @@
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 { getAsIssuer, getMcpResourceUri, MCP_OAUTH_SCOPES } from '@lowdefy/api';
18
+
19
+ import lowdefyConfig from '../../lib/build/config.js';
20
+
21
+ // RFC 9728 protected-resource metadata for the MCP endpoint. Constant per
22
+ // deployment: the URIs derive from the pinned BETTER_AUTH_URL, never a Host
23
+ // header. The organization a token acts in is not part of the resource - it is
24
+ // chosen at authorization and carried as a token claim.
25
+ function mcpProtectedResourceMetadataHandler(c) {
26
+ return c.json({
27
+ resource: getMcpResourceUri({ config: lowdefyConfig }),
28
+ authorization_servers: [getAsIssuer({ config: lowdefyConfig })],
29
+ scopes_supported: MCP_OAUTH_SCOPES,
30
+ bearer_methods_supported: ['header'],
31
+ });
32
+ }
33
+
34
+ export default mcpProtectedResourceMetadataHandler;
@@ -0,0 +1,65 @@
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
+ import { jest } from '@jest/globals';
19
+
20
+ jest.unstable_mockModule('../../lib/build/config.js', () => ({
21
+ default: { basePath: '' },
22
+ }));
23
+
24
+ const { default: mcpProtectedResourceMetadataHandler } = await import(
25
+ './mcpProtectedResourceMetadata.js'
26
+ );
27
+
28
+ const originalBetterAuthUrl = process.env.BETTER_AUTH_URL;
29
+
30
+ function createApp() {
31
+ const app = new Hono();
32
+ app.get('/.well-known/oauth-protected-resource/api/mcp', mcpProtectedResourceMetadataHandler);
33
+ return app;
34
+ }
35
+
36
+ beforeEach(() => {
37
+ process.env.BETTER_AUTH_URL = 'https://app.test.com';
38
+ });
39
+
40
+ afterAll(() => {
41
+ if (originalBetterAuthUrl === undefined) {
42
+ delete process.env.BETTER_AUTH_URL;
43
+ } else {
44
+ process.env.BETTER_AUTH_URL = originalBetterAuthUrl;
45
+ }
46
+ });
47
+
48
+ test('metadata handler serves the single MCP resource document', async () => {
49
+ const res = await createApp().request('/.well-known/oauth-protected-resource/api/mcp');
50
+ expect(res.status).toEqual(200);
51
+ expect(await res.json()).toEqual({
52
+ resource: 'https://app.test.com/api/mcp',
53
+ authorization_servers: ['https://app.test.com/api/auth'],
54
+ scopes_supported: ['mcp:read', 'mcp:write', 'offline_access'],
55
+ bearer_methods_supported: ['header'],
56
+ });
57
+ });
58
+
59
+ test('metadata handler derives the resource from BETTER_AUTH_URL, never the request host', async () => {
60
+ const res = await createApp().request(
61
+ 'http://evil.example.com/.well-known/oauth-protected-resource/api/mcp'
62
+ );
63
+ expect(res.status).toEqual(200);
64
+ expect((await res.json()).resource).toEqual('https://app.test.com/api/mcp');
65
+ });
@@ -0,0 +1,25 @@
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
+ // Every page path renders the same dev shell; the client reads the page from
18
+ // the URL. `.*`, not `.+`: under a basePath, `<basePath>/` arrives with an
19
+ // empty rest, and is the app root like `<basePath>` itself.
20
+ function mountDevPageRoutes({ app, renderPage }) {
21
+ app.get('/', renderPage);
22
+ app.get('/:rest{.*}', renderPage);
23
+ }
24
+
25
+ export default mountDevPageRoutes;
@@ -0,0 +1,30 @@
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 mountDevPageRoutes from './mountDevPageRoutes.js';
20
+
21
+ test.each([
22
+ ['/app', '/app'],
23
+ ['/app', '/app/'],
24
+ ['/app', '/app/invoices'],
25
+ ])('with basePath "%s", %s renders the dev shell', async (basePath, path) => {
26
+ const app = basePath === '' ? new Hono() : new Hono().basePath(basePath);
27
+ mountDevPageRoutes({ app, renderPage: (c) => c.text('shell') });
28
+ const res = await app.request(path);
29
+ expect(await res.text()).toEqual('shell');
30
+ });
@@ -0,0 +1,44 @@
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 { createAsMetadataHandler } from '@lowdefy/api';
18
+ import { type } from '@lowdefy/helpers';
19
+
20
+ import authJson from '../../lib/build/auth.js';
21
+ import mcpProtectedResourceMetadataHandler from './mcpProtectedResourceMetadata.js';
22
+
23
+ // OAuth discovery for the MCP resource. Both documents are public,
24
+ // unauthenticated and constant per deployment, so they mount outside the api
25
+ // context. Nothing mounts when the app is not an authorization server - the
26
+ // build then guaranteed an all-public tool surface with nothing to discover.
27
+ function mountOauthDiscovery({ app, auth }) {
28
+ if (type.isNone(authJson.oauthProvider)) {
29
+ return;
30
+ }
31
+ app.get('/.well-known/oauth-protected-resource/api/mcp', mcpProtectedResourceMetadataHandler);
32
+ // The auth instance is absent only in dev mock mode, which runs no auth
33
+ // engine - the static resource document above still serves there.
34
+ if (type.isNone(auth)) {
35
+ return;
36
+ }
37
+ // The oauth-provider serves its AS metadata relative to the auth mount, so
38
+ // the RFC 8414 path-inserted location a client derives from the issuer must
39
+ // be mounted here, re-exposing the same live document.
40
+ const asMetadataHandler = createAsMetadataHandler({ auth });
41
+ app.get('/.well-known/oauth-authorization-server/api/auth', (c) => asMetadataHandler(c.req.raw));
42
+ }
43
+
44
+ export default mountOauthDiscovery;
@@ -0,0 +1,79 @@
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
+ import { jest } from '@jest/globals';
19
+
20
+ const mockCreateAsMetadataHandler = jest.fn(
21
+ () => async () =>
22
+ new Response(JSON.stringify({ issuer: 'https://app.test.com/api/auth' }), {
23
+ status: 200,
24
+ headers: { 'content-type': 'application/json' },
25
+ })
26
+ );
27
+ jest.unstable_mockModule('@lowdefy/api', () => ({
28
+ createAsMetadataHandler: mockCreateAsMetadataHandler,
29
+ }));
30
+
31
+ jest.unstable_mockModule('./mcpProtectedResourceMetadata.js', () => ({
32
+ default: (c) => c.json({ resource: 'mcp' }),
33
+ }));
34
+
35
+ const mockAuthJson = { configured: true };
36
+ jest.unstable_mockModule('../../lib/build/auth.js', () => ({
37
+ default: mockAuthJson,
38
+ }));
39
+
40
+ const { default: mountOauthDiscovery } = await import('./mountOauthDiscovery.js');
41
+
42
+ beforeEach(() => {
43
+ delete mockAuthJson.oauthProvider;
44
+ });
45
+
46
+ test('mountOauthDiscovery mounts nothing when oauthProvider is not configured', async () => {
47
+ const app = new Hono();
48
+ mountOauthDiscovery({ app, auth: { api: {} } });
49
+ const resourceRes = await app.request('/.well-known/oauth-protected-resource/api/mcp');
50
+ expect(resourceRes.status).toEqual(404);
51
+ const asRes = await app.request('/.well-known/oauth-authorization-server/api/auth');
52
+ expect(asRes.status).toEqual(404);
53
+ expect(mockCreateAsMetadataHandler).not.toHaveBeenCalled();
54
+ });
55
+
56
+ test('mountOauthDiscovery mounts both discovery documents when oauthProvider is configured', async () => {
57
+ mockAuthJson.oauthProvider = { consentPage: '/oauth/consent' };
58
+ const app = new Hono();
59
+ const auth = { api: {} };
60
+ mountOauthDiscovery({ app, auth });
61
+ const resourceRes = await app.request('/.well-known/oauth-protected-resource/api/mcp');
62
+ expect(resourceRes.status).toEqual(200);
63
+ expect(await resourceRes.json()).toEqual({ resource: 'mcp' });
64
+ const asRes = await app.request('/.well-known/oauth-authorization-server/api/auth');
65
+ expect(asRes.status).toEqual(200);
66
+ expect(await asRes.json()).toEqual({ issuer: 'https://app.test.com/api/auth' });
67
+ expect(mockCreateAsMetadataHandler).toHaveBeenCalledWith({ auth });
68
+ });
69
+
70
+ test('mountOauthDiscovery serves only the static resource document without an auth instance', async () => {
71
+ mockAuthJson.oauthProvider = { consentPage: '/oauth/consent' };
72
+ const app = new Hono();
73
+ mountOauthDiscovery({ app, auth: null });
74
+ const resourceRes = await app.request('/.well-known/oauth-protected-resource/api/mcp');
75
+ expect(resourceRes.status).toEqual(200);
76
+ const asRes = await app.request('/.well-known/oauth-authorization-server/api/auth');
77
+ expect(asRes.status).toEqual(404);
78
+ expect(mockCreateAsMetadataHandler).not.toHaveBeenCalled();
79
+ });
@@ -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
+ // Claude Code merges the protocol version it negotiated (mcp-protocol-version)
18
+ // with a static MCP-Protocol-Version it sets on every request, and the Fetch
19
+ // Headers constructor folds the two spellings of one header into a single
20
+ // comma-joined value: "2025-11-25, 2025-11-25". @hono/mcp compares that raw
21
+ // string against its supported list, so a client that agrees with the server
22
+ // twice over is answered 404 "Unsupported protocol version" - which Claude Code
23
+ // shows as "MCP endpoint not found". Collapsing a repeated value to the one
24
+ // version it names is lossless; a header naming two DIFFERENT versions is left
25
+ // for the transport to refuse. Runs before the transport reads the header: the
26
+ // transport reads c.req.header(), which reads the raw request headers live.
27
+ function normalizeMcpProtocolVersionHeader({ request }) {
28
+ const value = request.headers.get('mcp-protocol-version');
29
+ if (value === null || !value.includes(',')) {
30
+ return;
31
+ }
32
+ const versions = [
33
+ ...new Set(
34
+ value
35
+ .split(',')
36
+ .map((version) => version.trim())
37
+ .filter(Boolean)
38
+ ),
39
+ ];
40
+ if (versions.length !== 1) {
41
+ return;
42
+ }
43
+ request.headers.set('mcp-protocol-version', versions[0]);
44
+ }
45
+
46
+ export default normalizeMcpProtocolVersionHeader;