@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.7

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 (407) hide show
  1. package/README.md +69 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
  5. package/dist/agentSessions/BridgeHostInstance.js +14 -4
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
  7. package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
  8. package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
  9. package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
  10. package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
  11. package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +31 -8
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +85 -41
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +14 -4
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +28 -4
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
  22. package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
  23. package/dist/agentSessions/conversationBrowsers.js +118 -0
  24. package/dist/agentSessions/conversationBrowsers.js.map +1 -0
  25. package/dist/agentSessions/sessionDeadline.d.ts +43 -0
  26. package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
  27. package/dist/agentSessions/sessionDeadline.js +68 -0
  28. package/dist/agentSessions/sessionDeadline.js.map +1 -0
  29. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  30. package/dist/auth/APIKeyScopeAuth.js +5 -4
  31. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  32. package/dist/auth/loginEmailResolution.d.ts +41 -0
  33. package/dist/auth/loginEmailResolution.d.ts.map +1 -0
  34. package/dist/auth/loginEmailResolution.js +47 -0
  35. package/dist/auth/loginEmailResolution.js.map +1 -0
  36. package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
  37. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
  39. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  40. package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
  41. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  42. package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
  43. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  44. package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
  45. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  46. package/dist/auth/magicLink/MagicLinkService.js +221 -9
  47. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  48. package/dist/auth/magicLink/index.d.ts +1 -1
  49. package/dist/auth/magicLink/index.d.ts.map +1 -1
  50. package/dist/auth/magicLink/index.js +1 -1
  51. package/dist/auth/magicLink/index.js.map +1 -1
  52. package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
  53. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  54. package/dist/auth/magicLink/redeemLanding.js +7 -4
  55. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  56. package/dist/auth/magicLink/types.d.ts +27 -3
  57. package/dist/auth/magicLink/types.d.ts.map +1 -1
  58. package/dist/auth/magicLink/types.js +0 -4
  59. package/dist/auth/magicLink/types.js.map +1 -1
  60. package/dist/auth/newUsers.d.ts.map +1 -1
  61. package/dist/auth/newUsers.js +1 -0
  62. package/dist/auth/newUsers.js.map +1 -1
  63. package/dist/backgroundSweepConfigUnits.d.ts +231 -0
  64. package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
  65. package/dist/backgroundSweepConfigUnits.js +180 -0
  66. package/dist/backgroundSweepConfigUnits.js.map +1 -0
  67. package/dist/config.d.ts +906 -8
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +59 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +2 -0
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +35 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/coreRoutes.d.ts +3 -1
  76. package/dist/coreRoutes.d.ts.map +1 -1
  77. package/dist/coreRoutes.js +6 -4
  78. package/dist/coreRoutes.js.map +1 -1
  79. package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
  80. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  81. package/dist/generated/graphql-schemas/__mj.js +1163 -6
  82. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  83. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  85. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  87. package/dist/generic/ResolverBase.d.ts +50 -5
  88. package/dist/generic/ResolverBase.d.ts.map +1 -1
  89. package/dist/generic/ResolverBase.js +321 -132
  90. package/dist/generic/ResolverBase.js.map +1 -1
  91. package/dist/generic/RunViewResolver.d.ts +4 -0
  92. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  93. package/dist/generic/RunViewResolver.js +47 -7
  94. package/dist/generic/RunViewResolver.js.map +1 -1
  95. package/dist/index.d.ts +1 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +157 -35
  98. package/dist/index.js.map +1 -1
  99. package/dist/integration/ConnectionPause.d.ts +121 -0
  100. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  101. package/dist/integration/ConnectionPause.js +183 -0
  102. package/dist/integration/ConnectionPause.js.map +1 -0
  103. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  104. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  105. package/dist/integration/CustomColumnPromoter.js +87 -46
  106. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  107. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  108. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  109. package/dist/integration/EntityMapLifecycle.js +199 -5
  110. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  111. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  112. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  113. package/dist/integration/InactiveRowWarnings.js +29 -0
  114. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  115. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  116. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  117. package/dist/integration/ObjectNamesByID.js +87 -0
  118. package/dist/integration/ObjectNamesByID.js.map +1 -0
  119. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  120. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  121. package/dist/integration/PostRestartIntrospect.js +27 -0
  122. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  123. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  124. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  125. package/dist/integration/RSUPostRestartProgress.js +323 -0
  126. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  127. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  128. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  129. package/dist/integration/RSUProgressBridge.js +84 -1
  130. package/dist/integration/RSUProgressBridge.js.map +1 -1
  131. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  132. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  133. package/dist/integration/RunArtifactAuthorization.js +45 -0
  134. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  135. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  136. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  137. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  138. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  139. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  140. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  141. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  142. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  143. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  144. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  145. package/dist/integration/TargetConfigGuard.js +40 -0
  146. package/dist/integration/TargetConfigGuard.js.map +1 -0
  147. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  148. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  149. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  150. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  151. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  152. package/dist/realtimeSessions/sessionAccess.js +22 -0
  153. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  154. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  155. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  156. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  157. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  158. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  159. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  160. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  161. package/dist/realtimeWidget/widgetCore.js +4 -7
  162. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  164. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  165. package/dist/requestOrigin.d.ts +22 -0
  166. package/dist/requestOrigin.d.ts.map +1 -0
  167. package/dist/requestOrigin.js +60 -0
  168. package/dist/requestOrigin.js.map +1 -0
  169. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  170. package/dist/resolvers/AgentSessionResolver.js +6 -2
  171. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  172. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  173. package/dist/resolvers/AutotagPipelineResolver.js +2 -1
  174. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  175. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  176. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  178. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  179. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  180. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  181. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  182. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  183. package/dist/resolvers/GetDataResolver.js +34 -13
  184. package/dist/resolvers/GetDataResolver.js.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  186. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  187. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  188. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  189. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  190. package/dist/resolvers/MCPResolver.js +13 -2
  191. package/dist/resolvers/MCPResolver.js.map +1 -1
  192. package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
  193. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  194. package/dist/resolvers/QuerySystemUserResolver.js +174 -87
  195. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  196. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  197. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  198. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  199. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  200. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  201. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  202. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  204. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  205. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  206. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  207. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  208. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  210. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  211. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  212. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  213. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  214. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  215. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.js +3 -1
  217. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  218. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  219. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  220. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  221. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  222. package/dist/rest/MediaAccessKeys.js +6 -2
  223. package/dist/rest/MediaAccessKeys.js.map +1 -1
  224. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.js +11 -5
  226. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  227. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  228. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  229. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  230. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  231. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  232. package/dist/rest/ViewOperationsHandler.js +2 -1
  233. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  234. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  235. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  236. package/dist/rest/setupRESTEndpoints.js +11 -2
  237. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  238. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  239. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  240. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  241. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  242. package/dist/services/TaskGraphActionRunner.js +2 -26
  243. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  244. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  245. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  246. package/dist/services/TaskGraphAgentRunner.js +38 -0
  247. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  248. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  249. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  250. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  251. package/dist/services/WorkQueueHostService.d.ts +2 -1
  252. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  253. package/dist/services/WorkQueueHostService.js +19 -4
  254. package/dist/services/WorkQueueHostService.js.map +1 -1
  255. package/dist/services/workQueueConfig.d.ts +2 -2
  256. package/dist/services/workQueueConfig.js +5 -5
  257. package/dist/services/workQueueConfig.js.map +1 -1
  258. package/dist/sharedCache.js +1 -1
  259. package/dist/sharedCache.js.map +1 -1
  260. package/dist/types.d.ts +17 -0
  261. package/dist/types.d.ts.map +1 -1
  262. package/dist/types.js.map +1 -1
  263. package/package.json +98 -98
  264. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  265. package/src/__tests__/ActiveOperations.test.ts +68 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  267. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  268. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  269. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  270. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  271. package/src/__tests__/ConnectionPause.test.ts +192 -0
  272. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  273. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  274. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  275. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  276. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  277. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  278. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  279. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  280. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  281. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  282. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  283. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  284. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  285. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  286. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  287. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  288. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  289. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  290. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  291. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  292. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  293. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  294. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  295. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  296. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  297. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  298. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  299. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  300. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  301. package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
  302. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  303. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  304. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  305. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  306. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  307. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  308. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  309. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  310. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  311. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  312. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  313. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  314. package/src/__tests__/SessionJanitor.test.ts +195 -4
  315. package/src/__tests__/SessionManager.test.ts +69 -0
  316. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  317. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  318. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  319. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  320. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  321. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  322. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  323. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  324. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  325. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  326. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  327. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  328. package/src/__tests__/requestOrigin.test.ts +104 -0
  329. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  330. package/src/__tests__/sessionDeadline.test.ts +51 -0
  331. package/src/__tests__/sharedCache.test.ts +11 -0
  332. package/src/__tests__/unifiedAuth.test.ts +73 -1
  333. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  334. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  335. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  336. package/src/agentSessions/SessionJanitor.ts +92 -42
  337. package/src/agentSessions/SessionManager.ts +38 -8
  338. package/src/agentSessions/conversationBrowsers.ts +143 -0
  339. package/src/agentSessions/sessionDeadline.ts +81 -0
  340. package/src/auth/APIKeyScopeAuth.ts +5 -4
  341. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  342. package/src/auth/loginEmailResolution.ts +67 -0
  343. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  344. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  345. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  346. package/src/auth/magicLink/index.ts +1 -1
  347. package/src/auth/magicLink/redeemLanding.ts +7 -4
  348. package/src/auth/magicLink/types.ts +28 -3
  349. package/src/auth/newUsers.ts +1 -0
  350. package/src/backgroundSweepConfigUnits.ts +213 -0
  351. package/src/config.ts +69 -0
  352. package/src/context.ts +39 -6
  353. package/src/coreRoutes.ts +14 -7
  354. package/src/generated/graphql-schemas/__mj.ts +822 -7
  355. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  356. package/src/generic/ResolverBase.ts +414 -143
  357. package/src/generic/RunViewResolver.ts +96 -49
  358. package/src/index.ts +201 -35
  359. package/src/integration/ConnectionPause.ts +222 -0
  360. package/src/integration/CustomColumnPromoter.ts +123 -53
  361. package/src/integration/EntityMapLifecycle.ts +229 -6
  362. package/src/integration/InactiveRowWarnings.ts +50 -0
  363. package/src/integration/ObjectNamesByID.ts +97 -0
  364. package/src/integration/PostRestartIntrospect.ts +49 -0
  365. package/src/integration/RSUPostRestartProgress.ts +359 -0
  366. package/src/integration/RSUProgressBridge.ts +85 -2
  367. package/src/integration/RunArtifactAuthorization.ts +65 -0
  368. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  369. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  370. package/src/integration/TargetConfigGuard.ts +71 -0
  371. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  372. package/src/realtimeSessions/sessionAccess.ts +30 -0
  373. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  374. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  375. package/src/realtimeWidget/widgetCore.ts +4 -8
  376. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  377. package/src/requestOrigin.ts +64 -0
  378. package/src/resolvers/AgentSessionResolver.ts +6 -2
  379. package/src/resolvers/AutotagPipelineResolver.ts +2 -1
  380. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  381. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  382. package/src/resolvers/GetDataResolver.ts +35 -13
  383. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  384. package/src/resolvers/MCPResolver.ts +15 -2
  385. package/src/resolvers/QuerySystemUserResolver.ts +197 -98
  386. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  387. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  388. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  389. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  390. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  391. package/src/resolvers/RunTemplateResolver.ts +3 -1
  392. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  393. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  394. package/src/rest/MediaAccessKeys.ts +6 -2
  395. package/src/rest/OAuthCallbackHandler.ts +13 -5
  396. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  397. package/src/rest/ViewOperationsHandler.ts +2 -1
  398. package/src/rest/setupRESTEndpoints.ts +11 -2
  399. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  400. package/src/services/TaskGraphActionRunner.ts +2 -23
  401. package/src/services/TaskGraphAgentRunner.ts +43 -0
  402. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  403. package/src/services/WorkQueueHostService.ts +26 -4
  404. package/src/services/workQueueConfig.ts +5 -5
  405. package/src/sharedCache.ts +1 -1
  406. package/src/types.ts +18 -0
  407. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -37,9 +37,12 @@ import { configInfo, type MagicLinkConfig } from '../../config.js';
37
37
  import { CreateIsolatedProvider } from '../../isolatedProvider.js';
38
38
  import { MagicLinkKeyManager } from './MagicLinkKeys.js';
39
39
  import { GenerateRawToken, GenerateSessionId, HashToken, EvaluateInvite, BuildSessionClaims, BuildConsumeInvitePostgresSQL, CanIssueInvites, IsRoleGrantable, MAGIC_LINK_TOKEN_PREFIX } from './magicLinkCore.js';
40
+ import type { IServerExtensionMagicLinkService } from '@memberjunction/server-extensions-core';
40
41
  import type {
41
42
  CreateMagicLinkInviteParams,
42
43
  CreateMagicLinkInviteResult,
44
+ ManageMagicLinkInviteResult,
45
+ MagicLinkJWTClaims,
43
46
  RedeemMagicLinkResult,
44
47
  RedeemAuditContext,
45
48
  } from './types.js';
@@ -75,11 +78,53 @@ interface ProvisionResult {
75
78
  error?: string;
76
79
  }
77
80
 
81
+ /**
82
+ * Validates that a candidate `redirectUrl` is a well-formed `http:` or `https:` URL whose
83
+ * origin matches at least one concrete normalized entry in `allowedOrigins`.
84
+ * Wildcard `'*'` entries are explicitly ignored/rejected so session tokens can never be
85
+ * redirected to unvalidated origins.
86
+ */
87
+ export function IsMagicLinkRedirectUrlAllowed(redirectUrl: string, allowedOrigins: readonly string[]): boolean {
88
+ const trimmed = redirectUrl.trim();
89
+ if (!trimmed) {
90
+ return false;
91
+ }
92
+ let parsed: URL;
93
+ try {
94
+ parsed = new URL(trimmed);
95
+ } catch {
96
+ return false;
97
+ }
98
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
99
+ return false;
100
+ }
101
+ const normalizedTargetOrigin = parsed.origin.toLowerCase();
102
+ for (const rawAllowed of allowedOrigins) {
103
+ const candidate = (rawAllowed ?? '').trim();
104
+ if (!candidate || candidate === '*') {
105
+ continue;
106
+ }
107
+ try {
108
+ const allowedUrl = new URL(candidate);
109
+ if (allowedUrl.origin.toLowerCase() === normalizedTargetOrigin) {
110
+ return true;
111
+ }
112
+ } catch {
113
+ if (candidate.replace(/\/$/, '').toLowerCase() === normalizedTargetOrigin) {
114
+ return true;
115
+ }
116
+ }
117
+ }
118
+ return false;
119
+ }
120
+
78
121
  /**
79
122
  * Magic-link service. One instance per server, constructed with the resolved
80
- * public URL and the validated config.
123
+ * public URL and the validated config. Implements {@link IServerExtensionMagicLinkService}
124
+ * so `BaseServerExtension` implementations can mint, revoke, extend, and verify invites
125
+ * via `context.services.GetMagicLinkService?.()`.
81
126
  */
82
- export class MagicLinkService {
127
+ export class MagicLinkService implements IServerExtensionMagicLinkService {
83
128
  constructor(
84
129
  private readonly publicUrl: string,
85
130
  private readonly config: MagicLinkConfig,
@@ -87,7 +132,7 @@ export class MagicLinkService {
87
132
 
88
133
  /**
89
134
  * Creates a single-use, app-scoped invite, persists its hashed token, and
90
- * (when a communication provider is configured) emails the link.
135
+ * (when a communication provider is configured and `params.SendEmail !== false`) emails the link.
91
136
  *
92
137
  * @param params invite parameters
93
138
  * @param creatingUser the authenticated internal user issuing the invite
@@ -110,6 +155,15 @@ export class MagicLinkService {
110
155
  return { success: false, errorCode: 'forbidden', error: 'Not authorized to issue magic-link invites.' };
111
156
  }
112
157
 
158
+ const validatedRedirectUrl = params.RedirectUrl?.trim() ? params.RedirectUrl.trim() : undefined;
159
+ if (validatedRedirectUrl && !IsMagicLinkRedirectUrlAllowed(validatedRedirectUrl, this.allowedRedirectOrigins())) {
160
+ return {
161
+ success: false,
162
+ errorCode: 'invalid_redirect_url',
163
+ error: `RedirectUrl '${validatedRedirectUrl}' is not permitted by the server's allowed frontend origins.`,
164
+ };
165
+ }
166
+
113
167
  const roleId = params.RoleId ?? this.resolveRestrictedRoleId(md);
114
168
  if (!roleId) {
115
169
  return { success: false, error: `Restricted role '${this.config.restrictedRoleName}' not found and no roleId supplied.` };
@@ -136,12 +190,13 @@ export class MagicLinkService {
136
190
  }
137
191
 
138
192
  const rawToken = GenerateRawToken();
193
+ const tokenHash = HashToken(rawToken);
139
194
  const expiresInHours = params.ExpiresInHours ?? this.config.defaultExpiresInHours;
140
195
  const expiresAt = new Date(Date.now() + expiresInHours * 3600 * 1000);
141
196
 
142
197
  const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, creatingUser);
143
198
  invite.NewRecord();
144
- invite.TokenHash = HashToken(rawToken);
199
+ invite.TokenHash = tokenHash;
145
200
  invite.Email = params.email;
146
201
  invite.ApplicationID = params.ApplicationId;
147
202
  invite.RoleID = roleId;
@@ -161,10 +216,22 @@ export class MagicLinkService {
161
216
  // multi-scope reads. Best-effort so a child-row hiccup never fails issuance.
162
217
  await this.writeInviteScopeChildRows(invite.ID, params.ApplicationId, roleId, creatingUser, md);
163
218
 
164
- const redemptionUrl = this.buildRedemptionUrl(rawToken);
219
+ let redirectBindingToken: string | undefined;
220
+ if (validatedRedirectUrl) {
221
+ if (!MagicLinkKeyManager.Instance.IsInitialized) {
222
+ MagicLinkKeyManager.Instance.Initialize(this.config.rsaPrivateKey);
223
+ }
224
+ redirectBindingToken = MagicLinkKeyManager.Instance.SignRedirectBinding(
225
+ tokenHash,
226
+ validatedRedirectUrl,
227
+ Math.floor(expiresAt.getTime() / 1000),
228
+ );
229
+ }
230
+
231
+ const redemptionUrl = this.buildRedemptionUrl(rawToken, redirectBindingToken);
165
232
 
166
233
  let emailSent = false;
167
- if (this.config.communicationProvider) {
234
+ if (params.SendEmail !== false && this.config.communicationProvider) {
168
235
  emailSent = await this.sendInviteEmail(params.email, redemptionUrl, app.Name, creatingUser);
169
236
  }
170
237
 
@@ -172,7 +239,7 @@ export class MagicLinkService {
172
239
  success: true,
173
240
  inviteId: invite.ID,
174
241
  redemptionUrl,
175
- // Surface the raw token only when we couldn't deliver it ourselves.
242
+ // Surface the raw token only when we couldn't (or were asked not to) deliver it ourselves.
176
243
  rawToken: emailSent ? undefined : rawToken,
177
244
  emailSent,
178
245
  expiresAt: expiresAt.toISOString(),
@@ -183,6 +250,158 @@ export class MagicLinkService {
183
250
  }
184
251
  }
185
252
 
253
+ /**
254
+ * Revokes an existing invite so subsequent redemptions fail with `'revoked'`.
255
+ * Requires the acting user to hold invite-issuance authority (`CanIssueInvites`).
256
+ */
257
+ public async RevokeInvite(
258
+ inviteId: string,
259
+ actingUser: UserInfo,
260
+ provider?: IMetadataProvider,
261
+ ): Promise<ManageMagicLinkInviteResult> {
262
+ try {
263
+ const callerRoleNames = (actingUser.UserRoles ?? []).map((r) => r.Role).filter((n): n is string => !!n);
264
+ if (!CanIssueInvites(actingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
265
+ return { success: false, errorCode: 'forbidden', error: 'Not authorized to revoke magic-link invites.' };
266
+ }
267
+ if (!inviteId?.trim()) {
268
+ return { success: false, errorCode: 'not_found', error: 'Invite ID is required.' };
269
+ }
270
+
271
+ const md = provider ?? Metadata.Provider;
272
+ const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, actingUser);
273
+ const loaded = await invite.Load(inviteId.trim());
274
+ if (!loaded) {
275
+ return { success: false, errorCode: 'not_found', error: `Invite '${inviteId}' not found.` };
276
+ }
277
+
278
+ if (invite.Status === 'Revoked') {
279
+ return {
280
+ success: true,
281
+ inviteId: invite.ID,
282
+ status: invite.Status,
283
+ expiresAt: invite.ExpiresAt.toISOString(),
284
+ };
285
+ }
286
+
287
+ invite.Status = 'Revoked';
288
+ if (!(await invite.Save())) {
289
+ return {
290
+ success: false,
291
+ errorCode: 'server_error',
292
+ error: `Failed to revoke invite: ${invite.LatestResult?.CompleteMessage ?? 'unknown error'}`,
293
+ };
294
+ }
295
+
296
+ return {
297
+ success: true,
298
+ inviteId: invite.ID,
299
+ status: invite.Status,
300
+ expiresAt: invite.ExpiresAt.toISOString(),
301
+ };
302
+ } catch (e) {
303
+ LogError(e);
304
+ return { success: false, errorCode: 'server_error', error: e instanceof Error ? e.message : String(e) };
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Updates the expiration timestamp of an existing non-revoked, non-consumed invite.
310
+ * Requires the acting user to hold invite-issuance authority (`CanIssueInvites`).
311
+ */
312
+ public async UpdateInviteExpiry(
313
+ inviteId: string,
314
+ expiresAt: Date,
315
+ actingUser: UserInfo,
316
+ provider?: IMetadataProvider,
317
+ ): Promise<ManageMagicLinkInviteResult> {
318
+ try {
319
+ const callerRoleNames = (actingUser.UserRoles ?? []).map((r) => r.Role).filter((n): n is string => !!n);
320
+ if (!CanIssueInvites(actingUser.Type, callerRoleNames, this.config.inviteIssuerRoleNames)) {
321
+ return { success: false, errorCode: 'forbidden', error: 'Not authorized to update magic-link invites.' };
322
+ }
323
+ if (!(expiresAt instanceof Date) || Number.isNaN(expiresAt.getTime())) {
324
+ return { success: false, errorCode: 'invalid_expiry', error: 'expiresAt must be a valid Date.' };
325
+ }
326
+ if (!inviteId?.trim()) {
327
+ return { success: false, errorCode: 'not_found', error: 'Invite ID is required.' };
328
+ }
329
+
330
+ const md = provider ?? Metadata.Provider;
331
+ const invite = await md.GetEntityObject<MJMagicLinkInviteEntity>(INVITE_ENTITY, actingUser);
332
+ const loaded = await invite.Load(inviteId.trim());
333
+ if (!loaded) {
334
+ return { success: false, errorCode: 'not_found', error: `Invite '${inviteId}' not found.` };
335
+ }
336
+
337
+ if (invite.Status === 'Revoked' || invite.Status === 'Consumed') {
338
+ return {
339
+ success: false,
340
+ errorCode: 'invalid_state',
341
+ error: `Cannot update expiry on invite '${invite.ID}' because its status is '${invite.Status}'.`,
342
+ };
343
+ }
344
+
345
+ const nowMs = Date.now();
346
+ invite.ExpiresAt = expiresAt;
347
+ if (invite.Status === 'Expired' && expiresAt.getTime() > nowMs && invite.UseCount < invite.MaxUses) {
348
+ invite.Status = 'Active';
349
+ } else if (invite.Status === 'Active' && expiresAt.getTime() <= nowMs) {
350
+ invite.Status = 'Expired';
351
+ }
352
+
353
+ if (!(await invite.Save())) {
354
+ return {
355
+ success: false,
356
+ errorCode: 'server_error',
357
+ error: `Failed to update invite expiry: ${invite.LatestResult?.CompleteMessage ?? 'unknown error'}`,
358
+ };
359
+ }
360
+
361
+ return {
362
+ success: true,
363
+ inviteId: invite.ID,
364
+ status: invite.Status,
365
+ expiresAt: invite.ExpiresAt.toISOString(),
366
+ };
367
+ } catch (e) {
368
+ LogError(e);
369
+ return { success: false, errorCode: 'server_error', error: e instanceof Error ? e.message : String(e) };
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Verifies a magic-link session JWT against this service's configured issuer and audience.
375
+ */
376
+ public VerifySessionToken(token: string): MagicLinkJWTClaims | null {
377
+ if (!token || !MagicLinkKeyManager.Instance.IsInitialized) {
378
+ return null;
379
+ }
380
+ return MagicLinkKeyManager.Instance.VerifySessionToken(token, {
381
+ issuer: this.publicUrl,
382
+ audience: this.config.audience,
383
+ });
384
+ }
385
+
386
+ /**
387
+ * Verifies a signed redirect binding (`rt`) against the raw invite token's hash AND the host's
388
+ * allowed frontend origins. Returns the verified redirect URL, or `undefined` if absent/invalid.
389
+ */
390
+ public ResolveVerifiedRedirectUrl(rawToken: string, redirectBindingToken: string | undefined): string | undefined {
391
+ if (!rawToken || !redirectBindingToken || !MagicLinkKeyManager.Instance.IsInitialized) {
392
+ return undefined;
393
+ }
394
+ const tokenHash = HashToken(rawToken);
395
+ const boundUrl = MagicLinkKeyManager.Instance.VerifyRedirectBinding(redirectBindingToken, tokenHash);
396
+ if (!boundUrl) {
397
+ return undefined;
398
+ }
399
+ if (!IsMagicLinkRedirectUrlAllowed(boundUrl, this.allowedRedirectOrigins())) {
400
+ return undefined;
401
+ }
402
+ return boundUrl;
403
+ }
404
+
186
405
  /**
187
406
  * Redeems a raw token: validates the invite, atomically consumes one use,
188
407
  * provisions/links the user with the restricted role + app, and mints a
@@ -420,10 +639,31 @@ export class MagicLinkService {
420
639
  return role?.ID;
421
640
  }
422
641
 
423
- /** Builds the shareable redemption URL. */
424
- private buildRedemptionUrl(rawToken: string): string {
642
+ /** Builds the shareable redemption URL (including `&rt=...` when a per-invite RedirectUrl was signed). */
643
+ private buildRedemptionUrl(rawToken: string, redirectBindingToken?: string): string {
425
644
  const base = this.publicUrl.replace(/\/$/, '');
426
- return `${base}/magic-link/redeem?token=${encodeURIComponent(rawToken)}`;
645
+ const rtParam = redirectBindingToken ? `&rt=${encodeURIComponent(redirectBindingToken)}` : '';
646
+ return `${base}/magic-link/redeem?token=${encodeURIComponent(rawToken)}${rtParam}`;
647
+ }
648
+
649
+ /**
650
+ * Collects concrete frontend origins a caller-supplied per-invite `RedirectUrl` is permitted
651
+ * to target (`explorerUrl`, `magicLink.allowedRedirectOrigins`, and non-wildcard `cors.allowedOrigins`).
652
+ */
653
+ private allowedRedirectOrigins(): string[] {
654
+ const origins: string[] = [];
655
+ const candidates: readonly (string | undefined)[] = [
656
+ this.config.explorerUrl ?? configInfo.magicLink?.explorerUrl,
657
+ ...(this.config.allowedRedirectOrigins ?? configInfo.magicLink?.allowedRedirectOrigins ?? []),
658
+ ...(configInfo.cors?.allowedOrigins ?? []),
659
+ ];
660
+ for (const raw of candidates) {
661
+ const trimmed = raw?.trim();
662
+ if (trimmed && trimmed !== '*') {
663
+ origins.push(trimmed);
664
+ }
665
+ }
666
+ return origins;
427
667
  }
428
668
 
429
669
  /**
@@ -8,7 +8,7 @@ export {
8
8
  type InviteEvaluationInput,
9
9
  } from './magicLinkCore.js';
10
10
  export { MagicLinkKeyManager } from './MagicLinkKeys.js';
11
- export { MagicLinkService } from './MagicLinkService.js';
11
+ export { MagicLinkService, IsMagicLinkRedirectUrlAllowed } from './MagicLinkService.js';
12
12
  export {
13
13
  CreateMagicLinkHandler, createMagicLinkHandler,
14
14
  CreateMagicLinkJwksRouter, createMagicLinkJwksRouter,
@@ -32,9 +32,12 @@ export function escapeHtml(s: string): string {
32
32
  * @param token the raw magic-link token (embedded in a hidden field)
33
33
  * @param actionPath the same-origin path the form POSTs to (e.g. `/magic-link/redeem`)
34
34
  */
35
- export function BuildRedeemLandingHtml(token: string, actionPath: string): string {
35
+ export function BuildRedeemLandingHtml(token: string, actionPath: string, redirectBindingToken?: string): string {
36
36
  const t = EscapeHtml(token);
37
37
  const action = EscapeHtml(actionPath);
38
+ const rtInput = redirectBindingToken
39
+ ? `\n <input type="hidden" name="rt" value="${EscapeHtml(redirectBindingToken)}">`
40
+ : '';
38
41
  // Hardcoded colors are intentional here — this is a standalone server-rendered
39
42
  // HTML response, not Angular component CSS, so the design-token rule doesn't apply.
40
43
  return `<!doctype html>
@@ -58,7 +61,7 @@ export function BuildRedeemLandingHtml(token: string, actionPath: string): strin
58
61
  <h1>Sign in to continue</h1>
59
62
  <p>Click below to securely sign in with your invitation link.</p>
60
63
  <form method="POST" action="${action}">
61
- <input type="hidden" name="token" value="${t}">
64
+ <input type="hidden" name="token" value="${t}">${rtInput}
62
65
  <button type="submit">Continue to sign in</button>
63
66
  </form>
64
67
  </div>
@@ -67,6 +70,6 @@ export function BuildRedeemLandingHtml(token: string, actionPath: string): strin
67
70
  }
68
71
 
69
72
  /** @deprecated Use {@link BuildRedeemLandingHtml}. */
70
- export function buildRedeemLandingHtml(token: string, actionPath: string): string {
71
- return BuildRedeemLandingHtml(token, actionPath);
73
+ export function buildRedeemLandingHtml(token: string, actionPath: string, redirectBindingToken?: string): string {
74
+ return BuildRedeemLandingHtml(token, actionPath, redirectBindingToken);
72
75
  }
@@ -2,6 +2,7 @@
2
2
  * @fileoverview Types for the MemberJunction magic-link feature.
3
3
  * @module @memberjunction/server/auth/magicLink
4
4
  */
5
+ import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
5
6
 
6
7
  /**
7
8
  * One unit of scope a magic-link session holds. The union of these across all links
@@ -113,6 +114,19 @@ export interface CreateMagicLinkInviteParams {
113
114
  /** Optional given/family name to seed the provisioned user. */
114
115
  firstName?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
115
116
  lastName?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
117
+ /**
118
+ * Optional per-invite post-redemption redirect URL. Must match an explicit allowed frontend
119
+ * origin (`magicLink.explorerUrl`, `magicLink.allowedRedirectOrigins`, or non-wildcard
120
+ * `cors.allowedOrigins`; wildcard `'*'` is always rejected). Cryptographically bound to the
121
+ * invite's token hash via RS256 so it cannot be tampered with or swapped across invites.
122
+ */
123
+ RedirectUrl?: string;
124
+ /**
125
+ * When `false`, skips MJServer's default invite email delivery and returns `rawToken` +
126
+ * `redemptionUrl` so the caller (e.g. a `BaseServerExtension`) can send its own branded message.
127
+ * Defaults to `true` when a communication provider is configured.
128
+ */
129
+ SendEmail?: boolean;
116
130
  }
117
131
 
118
132
  /** Result of creating an invite. */
@@ -133,11 +147,22 @@ export interface CreateMagicLinkInviteResult {
133
147
  expiresAt?: string; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
134
148
  error?: string; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
135
149
  /**
136
- * Set when the request was rejected for authorization reasons:
150
+ * Set when the request was rejected for authorization or validation reasons:
137
151
  * `forbidden` — caller may not issue invites; `invalid_role` — the requested
138
- * role is not grantable via magic-link. Drives the HTTP status in the router.
152
+ * role is not grantable via magic-link; `invalid_redirect_url` — `RedirectUrl`
153
+ * failed HTTP(S) or origin allowlist validation. Drives the HTTP status in the router.
139
154
  */
140
- errorCode?: 'forbidden' | 'invalid_role'; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
155
+ errorCode?: 'forbidden' | 'invalid_role' | 'invalid_redirect_url'; // case-violation-ok-legacy-back-compat: the type crosses a serialization boundary (JSON / HTTP body), so this member name is part of a wire or on-disk shape
156
+ }
157
+
158
+ /** Result of revoking or updating expiry on an existing invite. */
159
+ export interface ManageMagicLinkInviteResult {
160
+ success: boolean; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
161
+ inviteId?: string; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
162
+ status?: MJMagicLinkInviteEntity['Status']; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
163
+ expiresAt?: string; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
164
+ error?: string; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
165
+ errorCode?: 'forbidden' | 'not_found' | 'invalid_state' | 'invalid_expiry' | 'server_error'; // case-violation-ok-legacy-back-compat: matches MagicLink result wire conventions
141
166
  }
142
167
 
143
168
  /** Why a redemption failed. */
@@ -195,6 +195,7 @@ export class NewUserBase {
195
195
  Fields: ['ID', 'IsTemplate'],
196
196
  ResultType: 'simple',
197
197
  MaxRows: 1,
198
+ SkipTotalRowCount: true,
198
199
  },
199
200
  contextUser
200
201
  );
@@ -0,0 +1,213 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Settings for the in-process background sweeps that keep an idle MJAPI talking to its database:
5
+ * the AI Agent Session janitor, the realtime bridge orphan reconciler, the human-handoff offer
6
+ * expiry sweep, the orphaned conversation detail reconciler and the agent-run watchdog. Each used to start unconditionally on a fixed
7
+ * interval, so a serverless database (Azure SQL serverless, for one) could never auto-pause. Every
8
+ * switch is described, with what turning it off costs, in the Serverless Database Auto-Pause guide
9
+ * (`guides/SERVERLESS_DATABASE_AUTO_PAUSE_GUIDE.md`).
10
+ *
11
+ * Like the telemetry and realtime units, the schemas and env-var readers live here rather than in
12
+ * config.ts so they are unit-testable without config.ts's full dependency graph. The env readers
13
+ * feed `DEFAULT_SERVER_CONFIG` (the base of the merge), so `mj.config.cjs` still wins over them.
14
+ */
15
+
16
+ /** Shipped janitor defaults (the values MJAPI has always used). */
17
+ export const DEFAULT_SESSION_JANITOR_SETTINGS = {
18
+ enabled: true,
19
+ sweepIntervalMs: 60_000,
20
+ closeThresholdMinutes: 15,
21
+ } as const;
22
+
23
+ /** Shipped realtime bridge orphan reconciler defaults (the values MJAPI has always used). */
24
+ export const DEFAULT_BRIDGE_RECONCILER_SETTINGS = {
25
+ enabled: true,
26
+ sweepIntervalMs: 600_000,
27
+ } as const;
28
+
29
+ /** Shipped human-handoff offer expiry sweep defaults (the values MJAPI has always used). */
30
+ export const DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS = {
31
+ enabled: true,
32
+ sweepIntervalMs: 15_000,
33
+ } as const;
34
+
35
+ /** Shipped orphaned conversation detail reconciler defaults (the values MJAPI has always used). */
36
+ export const DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS = {
37
+ enabled: true,
38
+ sweepIntervalMs: 300_000,
39
+ } as const;
40
+
41
+ /** Shipped watchdog defaults (the values MJAPI has always used). */
42
+ export const DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS = {
43
+ sweepIntervalMs: 300_000,
44
+ heartbeatIntervalMs: 30_000,
45
+ staleThresholdMinutes: 5,
46
+ } as const;
47
+
48
+ /**
49
+ * `agentSessions.janitor` (env `MJ_SESSION_JANITOR_ENABLED`, `MJ_SESSION_JANITOR_SWEEP_INTERVAL_MS`).
50
+ * `enabled: false` turns off the periodic tick (stale-session and max-duration close sweeps, then
51
+ * realtime recording recovery); the one-time startup recovery and the graceful-shutdown drain still
52
+ * run, so this instance's own orphaned sessions are closed on restart. See the Serverless Database
53
+ * Auto-Pause guide.
54
+ */
55
+ export const SessionJanitorSchema = z.object({
56
+ enabled: z.boolean().optional().default(DEFAULT_SESSION_JANITOR_SETTINGS.enabled),
57
+ sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_SESSION_JANITOR_SETTINGS.sweepIntervalMs),
58
+ closeThresholdMinutes: z.coerce.number().positive().optional().default(DEFAULT_SESSION_JANITOR_SETTINGS.closeThresholdMinutes),
59
+ });
60
+
61
+ /**
62
+ * `agentSessions.bridgeReconciler` (env `MJ_BRIDGE_RECONCILER_ENABLED`,
63
+ * `MJ_BRIDGE_RECONCILER_SWEEP_INTERVAL_MS`): closes the realtime bridges an earlier boot of this
64
+ * instance left open. `enabled: false` keeps the one pass at startup and schedules no repeat. See
65
+ * the Serverless Database Auto-Pause guide.
66
+ */
67
+ export const BridgeReconcilerSchema = z.object({
68
+ enabled: z.boolean().optional().default(DEFAULT_BRIDGE_RECONCILER_SETTINGS.enabled),
69
+ sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_BRIDGE_RECONCILER_SETTINGS.sweepIntervalMs),
70
+ });
71
+
72
+ /**
73
+ * `agentSessions.handoffOfferExpiry` (env `MJ_HANDOFF_OFFER_EXPIRY_ENABLED`,
74
+ * `MJ_HANDOFF_OFFER_EXPIRY_SWEEP_INTERVAL_MS`): marks agent-to-person handoff offers (`MJ: Interaction
75
+ * Offers`) `Expired` once past their deadline and tells the person's browser. `enabled: false` stops
76
+ * the periodic read; overdue offers still read as expired everywhere, but their rows stay `Pending`
77
+ * and no expiry notice is pushed. See the Serverless Database Auto-Pause guide.
78
+ */
79
+ export const HandoffOfferSweepSchema = z.object({
80
+ enabled: z.boolean().optional().default(DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS.enabled),
81
+ sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS.sweepIntervalMs),
82
+ });
83
+
84
+ /** `agentSessions`: settings for AI Agent Sessions (realtime/voice and other long-lived agent sessions). */
85
+ export const AgentSessionsSchema = z.object({
86
+ janitor: SessionJanitorSchema.optional().default({}),
87
+ bridgeReconciler: BridgeReconcilerSchema.optional().default({}),
88
+ handoffOfferExpiry: HandoffOfferSweepSchema.optional().default({}),
89
+ });
90
+
91
+ /**
92
+ * `conversationDetailReconciler` (env `MJ_CONVERSATION_DETAIL_RECONCILER_ENABLED`,
93
+ * `MJ_CONVERSATION_DETAIL_RECONCILER_SWEEP_INTERVAL_MS`): closes conversation details a run that
94
+ * already ended left `In-Progress`. `enabled: false` keeps the one pass at startup and schedules no
95
+ * repeat. See the Serverless Database Auto-Pause guide.
96
+ */
97
+ export const ConversationDetailReconcilerSchema = z.object({
98
+ enabled: z.boolean().optional().default(DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS.enabled),
99
+ sweepIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS.sweepIntervalMs),
100
+ });
101
+
102
+ /**
103
+ * `agentRunWatchdog` (env `MJ_AGENT_RUN_WATCHDOG_SWEEP_INTERVAL_MS`). The heartbeat runs only while
104
+ * this process has an agent run in flight; once any run has been tracked, the stale-run sweep keeps
105
+ * running every `sweepIntervalMs` so it also closes runs other, crashed instances left `Running`.
106
+ * `sweepIntervalMs: 0` turns the periodic sweep off; the startup sweep still runs. See the
107
+ * Serverless Database Auto-Pause guide.
108
+ */
109
+ export const AgentRunWatchdogSchema = z.object({
110
+ sweepIntervalMs: z.coerce.number().nonnegative().optional().default(DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.sweepIntervalMs),
111
+ heartbeatIntervalMs: z.coerce.number().positive().optional().default(DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.heartbeatIntervalMs),
112
+ staleThresholdMinutes: z.coerce.number().positive().optional().default(DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.staleThresholdMinutes),
113
+ });
114
+
115
+ export type SessionJanitorSettings = z.infer<typeof SessionJanitorSchema>;
116
+ export type BridgeReconcilerSettings = z.infer<typeof BridgeReconcilerSchema>;
117
+ export type HandoffOfferSweepSettings = z.infer<typeof HandoffOfferSweepSchema>;
118
+ export type AgentSessionsSettings = z.infer<typeof AgentSessionsSchema>;
119
+ export type ConversationDetailReconcilerSettings = z.infer<typeof ConversationDetailReconcilerSchema>;
120
+ export type AgentRunWatchdogSettings = z.infer<typeof AgentRunWatchdogSchema>;
121
+
122
+ const TRUE_VALUES: ReadonlySet<string> = new Set(['true', '1', 'yes', 'y', 'on', 't']);
123
+ const FALSE_VALUES: ReadonlySet<string> = new Set(['false', '0', 'no', 'n', 'off', 'f']);
124
+
125
+ /**
126
+ * Reads an on/off switch from an env var, returning `fallback` when it is unset or blank (so an
127
+ * empty variable exported by a deployment script cannot silently flip it). Accepts
128
+ * `true/false`, `1/0`, `yes/no`, `y/n`, `on/off`, `t/f` in any case; throws, naming the variable,
129
+ * on anything else, so a typo fails at startup instead of silently turning a sweep off.
130
+ *
131
+ * @param name The variable's name, for the error message.
132
+ * @param rawEnvValue The variable's raw value.
133
+ * @param fallback The value when the variable is unset or blank.
134
+ */
135
+ export function EnabledFromEnv(name: string, rawEnvValue: string | undefined | null, fallback: boolean): boolean {
136
+ const normalized = rawEnvValue?.trim().toLowerCase();
137
+ if (!normalized) {
138
+ return fallback;
139
+ }
140
+ if (TRUE_VALUES.has(normalized)) {
141
+ return true;
142
+ }
143
+ if (FALSE_VALUES.has(normalized)) {
144
+ return false;
145
+ }
146
+ throw new Error(`Invalid ${name} "${rawEnvValue}". Expected true or false.`);
147
+ }
148
+
149
+ /**
150
+ * Reads a millisecond interval from an env var, returning `fallback` when it is unset or blank.
151
+ * Throws, naming the variable, on anything that is not a finite number in range, so a deployment
152
+ * typo fails at startup instead of silently running the default (or a zero-delay timer).
153
+ *
154
+ * @param name The variable's name, for the error message.
155
+ * @param rawEnvValue The variable's raw value.
156
+ * @param fallback The value when the variable is unset or blank.
157
+ * @param allowZero Whether `0` is accepted (it means "off" for the settings that allow it).
158
+ */
159
+ export function IntervalMsFromEnv(name: string, rawEnvValue: string | undefined | null, fallback: number, allowZero: boolean): number {
160
+ const trimmed = rawEnvValue?.trim();
161
+ if (!trimmed) {
162
+ return fallback;
163
+ }
164
+ const value = Number(trimmed);
165
+ const inRange = allowZero ? value >= 0 : value > 0;
166
+ if (!Number.isFinite(value) || !inRange) {
167
+ const rule = allowZero ? 'a number of milliseconds, 0 or greater' : 'a number of milliseconds greater than 0';
168
+ throw new Error(`Invalid ${name} "${rawEnvValue}". Expected ${rule}.`);
169
+ }
170
+ return value;
171
+ }
172
+
173
+ /** The env vars each background sweep reads, keyed by what they set. */
174
+ type SweepEnv = Readonly<Record<string, string | undefined>>;
175
+
176
+ /** `enabled` + `sweepIntervalMs` for one sweep, read from `<prefix>_ENABLED` and `<prefix>_SWEEP_INTERVAL_MS`. */
177
+ function enabledSweepFromEnv(env: SweepEnv, prefix: string, defaults: { enabled: boolean; sweepIntervalMs: number }): { enabled: boolean; sweepIntervalMs: number } {
178
+ return {
179
+ enabled: EnabledFromEnv(`${prefix}_ENABLED`, env[`${prefix}_ENABLED`], defaults.enabled),
180
+ sweepIntervalMs: IntervalMsFromEnv(`${prefix}_SWEEP_INTERVAL_MS`, env[`${prefix}_SWEEP_INTERVAL_MS`], defaults.sweepIntervalMs, false),
181
+ };
182
+ }
183
+
184
+ /** The `agentSessions` block of `DEFAULT_SERVER_CONFIG`, from the environment and the shipped defaults. */
185
+ export function AgentSessionsDefaultsFromEnv(env: SweepEnv): AgentSessionsSettings {
186
+ return {
187
+ janitor: {
188
+ ...enabledSweepFromEnv(env, 'MJ_SESSION_JANITOR', DEFAULT_SESSION_JANITOR_SETTINGS),
189
+ closeThresholdMinutes: DEFAULT_SESSION_JANITOR_SETTINGS.closeThresholdMinutes,
190
+ },
191
+ bridgeReconciler: enabledSweepFromEnv(env, 'MJ_BRIDGE_RECONCILER', DEFAULT_BRIDGE_RECONCILER_SETTINGS),
192
+ handoffOfferExpiry: enabledSweepFromEnv(env, 'MJ_HANDOFF_OFFER_EXPIRY', DEFAULT_HANDOFF_OFFER_SWEEP_SETTINGS),
193
+ };
194
+ }
195
+
196
+ /** The `conversationDetailReconciler` block of `DEFAULT_SERVER_CONFIG`. */
197
+ export function ConversationDetailReconcilerDefaultsFromEnv(env: SweepEnv): ConversationDetailReconcilerSettings {
198
+ return enabledSweepFromEnv(env, 'MJ_CONVERSATION_DETAIL_RECONCILER', DEFAULT_CONVERSATION_DETAIL_RECONCILER_SETTINGS);
199
+ }
200
+
201
+ /** The `agentRunWatchdog` block of `DEFAULT_SERVER_CONFIG`. */
202
+ export function AgentRunWatchdogDefaultsFromEnv(env: SweepEnv): AgentRunWatchdogSettings {
203
+ return {
204
+ sweepIntervalMs: IntervalMsFromEnv(
205
+ 'MJ_AGENT_RUN_WATCHDOG_SWEEP_INTERVAL_MS',
206
+ env.MJ_AGENT_RUN_WATCHDOG_SWEEP_INTERVAL_MS,
207
+ DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.sweepIntervalMs,
208
+ true
209
+ ),
210
+ heartbeatIntervalMs: DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.heartbeatIntervalMs,
211
+ staleThresholdMinutes: DEFAULT_AGENT_RUN_WATCHDOG_SETTINGS.staleThresholdMinutes,
212
+ };
213
+ }