@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
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+
3
+ const endSession = vi.fn(async (_id: string) => true);
4
+ vi.mock('@memberjunction/remote-browser-server', () => ({
5
+ RemoteBrowserEngine: { Instance: { EndSessionForAgentSession: (id: string) => endSession(id) } },
6
+ }));
7
+
8
+ import { GetGlobalObjectStore, ShutdownRegistry } from '@memberjunction/global';
9
+ import { ConversationBrowserLedger, RemoteBrowserRefusedError } from '../agentSessions/conversationBrowsers.js';
10
+
11
+ const USER = { Key: 'user:a1a1a1a1-0000-4000-8000-000000000001', Anonymous: false };
12
+ const LIMITS = { maxPerUser: 2, maxAnonymousTotal: 4, idleMinutes: 15 };
13
+ const guest = (scope: string) => ({ Key: `scope:${scope}`, Anonymous: true });
14
+ const ledger = ConversationBrowserLedger.Instance;
15
+
16
+ afterEach(() => {
17
+ for (const id of ['conv-idle', 'conv-used', 'conv-third', 'g1-a', 'g1-b', 'g2-a']) {
18
+ ledger.Release(id);
19
+ }
20
+ vi.useRealTimers();
21
+ });
22
+
23
+ describe('ConversationBrowserLedger', () => {
24
+ it('is one process-wide instance, held in the global object store so every copy of the module shares it', () => {
25
+ expect(ConversationBrowserLedger.Instance).toBe(ledger);
26
+ expect(GetGlobalObjectStore()?.[ledger.GlobalKey]).toBe(ledger);
27
+ });
28
+
29
+ it('ends a browser nobody used for its idle limit, keeps a used one, and frees the owner\'s slot', async () => {
30
+ vi.useFakeTimers({ toFake: ['Date'] });
31
+ vi.setSystemTime(0);
32
+ ledger.Claim('conv-idle', USER, LIMITS);
33
+ ledger.Claim('conv-used', USER, LIMITS);
34
+ expect(() => ledger.Claim('conv-third', USER, LIMITS)).toThrow(RemoteBrowserRefusedError);
35
+
36
+ vi.setSystemTime(10 * 60_000);
37
+ ledger.Touch('conv-used');
38
+ vi.setSystemTime(16 * 60_000);
39
+ expect(await ledger.ReapIdle()).toBe(1);
40
+ expect(endSession).toHaveBeenCalledWith('conv-idle');
41
+ expect(endSession).not.toHaveBeenCalledWith('conv-used');
42
+ expect(() => ledger.Claim('conv-third', USER, LIMITS)).not.toThrow();
43
+ });
44
+
45
+ it('joins the graceful shutdown with its first browser, and ends every browser it holds there', async () => {
46
+ endSession.mockClear();
47
+ ledger.Claim('conv-idle', USER, LIMITS);
48
+ ledger.Claim('conv-used', USER, LIMITS);
49
+ expect(ShutdownRegistry.Instance.List()).toContain(ledger);
50
+
51
+ await ledger.Shutdown();
52
+ expect(endSession.mock.calls.map((c) => c[0]).sort()).toEqual(['conv-idle', 'conv-used']);
53
+ expect(await ledger.ReapIdle()).toBe(0);
54
+ });
55
+
56
+ it('counts each owner key on its own, so one anonymous guest cannot use up another\'s cap', () => {
57
+ const one = { ...LIMITS, maxPerUser: 1 };
58
+ ledger.Claim('g1-a', guest('visitor-1'), one);
59
+ expect(() => ledger.Claim('g1-b', guest('visitor-1'), one)).toThrow(RemoteBrowserRefusedError);
60
+ expect(() => ledger.Claim('g2-a', guest('visitor-2'), one)).not.toThrow();
61
+ });
62
+ });
@@ -0,0 +1,413 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ const { mockCorsConfig } = vi.hoisted(() => ({
4
+ mockCorsConfig: {
5
+ allowedOrigins: ['https://portal.example.com', 'https://forms.example.com'],
6
+ },
7
+ }));
8
+
9
+ vi.mock('@memberjunction/generic-database-provider', () => ({
10
+ UserCache: { Instance: { Users: [] } },
11
+ }));
12
+ vi.mock('@memberjunction/communication-engine', () => ({
13
+ CommunicationEngine: {
14
+ Instance: {
15
+ Config: vi.fn(async () => undefined),
16
+ SendSingleMessage: vi.fn(async () => ({ Success: true })),
17
+ },
18
+ },
19
+ }));
20
+ vi.mock('@memberjunction/communication-types', () => ({
21
+ Message: class {
22
+ From = '';
23
+ To = '';
24
+ Subject = '';
25
+ Body = '';
26
+ HTMLBody = '';
27
+ },
28
+ }));
29
+ vi.mock('../config.js', () => ({
30
+ configInfo: {
31
+ cors: mockCorsConfig,
32
+ },
33
+ }));
34
+ vi.mock('../isolatedProvider.js', () => ({
35
+ CreateIsolatedProvider: async () => {
36
+ throw new Error('provisioning is not exercised in this suite');
37
+ },
38
+ }));
39
+
40
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
41
+ import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
42
+ import { CommunicationEngine } from '@memberjunction/communication-engine';
43
+ import type { MagicLinkConfig } from '../config.js';
44
+ import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
45
+ import { MagicLinkService, IsMagicLinkRedirectUrlAllowed } from '../auth/magicLink/MagicLinkService.js';
46
+ import { BuildSessionClaims, HashToken } from '../auth/magicLink/magicLinkCore.js';
47
+
48
+ interface StoredInviteState {
49
+ ID: string;
50
+ TokenHash: string;
51
+ Email: string | null;
52
+ ApplicationID: string;
53
+ RoleID: string;
54
+ ExpiresAt: Date;
55
+ MaxUses: number;
56
+ UseCount: number;
57
+ CreatedByUserID: string;
58
+ Status: MJMagicLinkInviteEntity['Status'];
59
+ }
60
+
61
+ function makeConfig(overrides: Partial<MagicLinkConfig> = {}): MagicLinkConfig {
62
+ return {
63
+ enabled: true,
64
+ audience: 'mj-magic-link',
65
+ restrictedRoleName: 'External Guest',
66
+ grantableRoleNames: ['External Guest'],
67
+ inviteIssuerRoleNames: ['Invite Issuer'],
68
+ defaultExpiresInHours: 24,
69
+ sessionTokenTtlHours: 8,
70
+ explorerUrl: 'https://explorer.example.com',
71
+ communicationProvider: 'SendGrid',
72
+ fromAddress: 'noreply@example.com',
73
+ provisioningGuard: 'block',
74
+ rateLimitWindowMs: 60_000,
75
+ redeemRateLimitMax: 20,
76
+ createRateLimitMax: 20,
77
+ ...overrides,
78
+ };
79
+ }
80
+
81
+ function makeFakeProvider(storedInvites: Map<string, StoredInviteState>): IMetadataProvider {
82
+ let seq = 1;
83
+ const provider = {
84
+ Roles: [{ ID: 'role-guest', Name: 'External Guest' }],
85
+ Applications: [{ ID: 'app-forms', Name: 'Public Forms', Path: 'public-forms' }],
86
+ GetEntityObject: async (entityName: string) => {
87
+ if (entityName === 'MJ: Magic Link Invites') {
88
+ const record: StoredInviteState & {
89
+ NewRecord: () => void;
90
+ Load: (id: string) => Promise<boolean>;
91
+ Save: () => Promise<boolean>;
92
+ LatestResult?: { CompleteMessage: string };
93
+ } = {
94
+ ID: '',
95
+ TokenHash: '',
96
+ Email: null,
97
+ ApplicationID: '',
98
+ RoleID: '',
99
+ ExpiresAt: new Date(),
100
+ MaxUses: 1,
101
+ UseCount: 0,
102
+ CreatedByUserID: '',
103
+ Status: 'Active',
104
+ NewRecord: () => {
105
+ record.ID = `invite-${seq++}`;
106
+ },
107
+ Load: async (id: string) => {
108
+ const existing = storedInvites.get(id);
109
+ if (!existing) {
110
+ return false;
111
+ }
112
+ Object.assign(record, existing);
113
+ return true;
114
+ },
115
+ Save: async () => {
116
+ storedInvites.set(record.ID, {
117
+ ID: record.ID,
118
+ TokenHash: record.TokenHash,
119
+ Email: record.Email,
120
+ ApplicationID: record.ApplicationID,
121
+ RoleID: record.RoleID,
122
+ ExpiresAt: record.ExpiresAt,
123
+ MaxUses: record.MaxUses,
124
+ UseCount: record.UseCount,
125
+ CreatedByUserID: record.CreatedByUserID,
126
+ Status: record.Status,
127
+ });
128
+ return true;
129
+ },
130
+ };
131
+ return record;
132
+ }
133
+ return {
134
+ NewRecord: () => undefined,
135
+ Save: async () => true,
136
+ };
137
+ },
138
+ };
139
+ return provider as object as IMetadataProvider;
140
+ }
141
+
142
+ describe('MagicLinkService programmatic extension capabilities (Item 3.6)', () => {
143
+ const ownerUser = {
144
+ ID: 'user-owner',
145
+ Email: 'owner@example.com',
146
+ Type: 'Owner',
147
+ UserRoles: [],
148
+ } as object as UserInfo;
149
+
150
+ const regularUser = {
151
+ ID: 'user-guest',
152
+ Email: 'guest@example.com',
153
+ Type: 'User',
154
+ UserRoles: [{ Role: 'External Guest' }],
155
+ } as object as UserInfo;
156
+
157
+ beforeEach(() => {
158
+ mockCorsConfig.allowedOrigins = ['https://portal.example.com', 'https://forms.example.com'];
159
+ MagicLinkKeyManager.Instance.Initialize();
160
+ vi.clearAllMocks();
161
+ });
162
+
163
+ describe('IsMagicLinkRedirectUrlAllowed', () => {
164
+ it('allows matching http(s) origins and rejects non-http schemes, unlisted origins, and wildcard *', () => {
165
+ const allowed = ['https://explorer.example.com', 'https://forms.example.com:8443'];
166
+ expect(IsMagicLinkRedirectUrlAllowed('https://forms.example.com:8443/respond/abc?step=2', allowed)).toBe(true);
167
+ expect(IsMagicLinkRedirectUrlAllowed('https://evil.example.com/phish', allowed)).toBe(false);
168
+ expect(IsMagicLinkRedirectUrlAllowed('javascript:alert(1)', allowed)).toBe(false);
169
+ expect(IsMagicLinkRedirectUrlAllowed('https://anywhere.example/cb', ['*'])).toBe(false);
170
+ expect(IsMagicLinkRedirectUrlAllowed('https://anywhere.example.org/ok', ['*', ''])).toBe(false);
171
+ });
172
+ });
173
+
174
+ describe('CreateInvite RedirectUrl & SendEmail controls', () => {
175
+ it('binds a permitted RedirectUrl via RS256 and skips default email when SendEmail=false', async () => {
176
+ const invites = new Map<string, StoredInviteState>();
177
+ const provider = makeFakeProvider(invites);
178
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
179
+
180
+ const res = await service.CreateInvite(
181
+ {
182
+ email: 'respondent@example.com',
183
+ ApplicationId: 'app-forms',
184
+ RedirectUrl: 'https://forms.example.com/survey/42',
185
+ SendEmail: false,
186
+ },
187
+ ownerUser,
188
+ provider,
189
+ );
190
+
191
+ expect(res.success).toBe(true);
192
+ expect(res.emailSent).toBe(false);
193
+ expect(res.rawToken).toBeDefined();
194
+ expect(CommunicationEngine.Instance.SendSingleMessage).not.toHaveBeenCalled();
195
+
196
+ const url = new URL(res.redemptionUrl!);
197
+ const rt = url.searchParams.get('rt') ?? undefined;
198
+ expect(rt).toBeTruthy();
199
+
200
+ // Validated against matching rawToken
201
+ expect(service.ResolveVerifiedRedirectUrl(res.rawToken!, rt)).toBe('https://forms.example.com/survey/42');
202
+ // Fails closed if an attacker swaps `rt` onto a different invite's token
203
+ expect(service.ResolveVerifiedRedirectUrl('mj_ml_0000000000000000000000000000000000000000000000000000000000000000', rt)).toBeUndefined();
204
+ });
205
+
206
+ it('rejects disallowed RedirectUrl origins with invalid_redirect_url', async () => {
207
+ const invites = new Map<string, StoredInviteState>();
208
+ const provider = makeFakeProvider(invites);
209
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
210
+
211
+ const res = await service.CreateInvite(
212
+ {
213
+ email: 'respondent@example.com',
214
+ ApplicationId: 'app-forms',
215
+ RedirectUrl: 'https://attacker.invalid/steal',
216
+ },
217
+ ownerUser,
218
+ provider,
219
+ );
220
+
221
+ expect(res.success).toBe(false);
222
+ expect(res.errorCode).toBe('invalid_redirect_url');
223
+ expect(invites.size).toBe(0);
224
+ });
225
+
226
+ it('ignores wildcard * in cors.allowedOrigins while honoring explicit magicLink.allowedRedirectOrigins', async () => {
227
+ mockCorsConfig.allowedOrigins = ['*'];
228
+ const invites = new Map<string, StoredInviteState>();
229
+ const provider = makeFakeProvider(invites);
230
+ const service = new MagicLinkService(
231
+ 'https://api.example.com',
232
+ makeConfig({
233
+ explorerUrl: 'https://explorer.example.com',
234
+ allowedRedirectOrigins: ['https://surveys.example.com'],
235
+ }),
236
+ );
237
+
238
+ // Wildcard CORS does NOT open up arbitrary redirect origins
239
+ const rejected = await service.CreateInvite(
240
+ {
241
+ email: 'respondent@example.com',
242
+ ApplicationId: 'app-forms',
243
+ RedirectUrl: 'https://anywhere.example/cb',
244
+ SendEmail: false,
245
+ },
246
+ ownerUser,
247
+ provider,
248
+ );
249
+ expect(rejected.success).toBe(false);
250
+ expect(rejected.errorCode).toBe('invalid_redirect_url');
251
+
252
+ // Explicit magicLink.allowedRedirectOrigins works cleanly under wildcard CORS
253
+ const permitted = await service.CreateInvite(
254
+ {
255
+ email: 'respondent@example.com',
256
+ ApplicationId: 'app-forms',
257
+ RedirectUrl: 'https://surveys.example.com/respond/99',
258
+ SendEmail: false,
259
+ },
260
+ ownerUser,
261
+ provider,
262
+ );
263
+ expect(permitted.success).toBe(true);
264
+ const url = new URL(permitted.redemptionUrl!);
265
+ const rt = url.searchParams.get('rt') ?? undefined;
266
+ expect(service.ResolveVerifiedRedirectUrl(permitted.rawToken!, rt)).toBe('https://surveys.example.com/respond/99');
267
+ });
268
+ });
269
+
270
+ describe('RevokeInvite & UpdateInviteExpiry', () => {
271
+ it('revokes an active invite and enforces issuer authorization', async () => {
272
+ const invites = new Map<string, StoredInviteState>();
273
+ const provider = makeFakeProvider(invites);
274
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
275
+
276
+ const created = await service.CreateInvite(
277
+ { email: 'person@example.com', ApplicationId: 'app-forms', SendEmail: false },
278
+ ownerUser,
279
+ provider,
280
+ );
281
+ expect(created.success).toBe(true);
282
+
283
+ const denied = await service.RevokeInvite(created.inviteId!, regularUser, provider);
284
+ expect(denied.success).toBe(false);
285
+ expect(denied.errorCode).toBe('forbidden');
286
+
287
+ const revoked = await service.RevokeInvite(created.inviteId!, ownerUser, provider);
288
+ expect(revoked.success).toBe(true);
289
+ expect(revoked.status).toBe('Revoked');
290
+ expect(invites.get(created.inviteId!)?.Status).toBe('Revoked');
291
+
292
+ // Cannot extend a revoked invite
293
+ const extendRevoked = await service.UpdateInviteExpiry(
294
+ created.inviteId!,
295
+ new Date(Date.now() + 72 * 3600 * 1000),
296
+ ownerUser,
297
+ provider,
298
+ );
299
+ expect(extendRevoked.success).toBe(false);
300
+ expect(extendRevoked.errorCode).toBe('invalid_state');
301
+ });
302
+
303
+ it('updates expiry on an active or expired invite and re-activates expired status when extended', async () => {
304
+ const invites = new Map<string, StoredInviteState>();
305
+ invites.set('inv-expired', {
306
+ ID: 'inv-expired',
307
+ TokenHash: HashToken('mj_ml_sample'),
308
+ Email: 'late@example.com',
309
+ ApplicationID: 'app-forms',
310
+ RoleID: 'role-guest',
311
+ ExpiresAt: new Date(Date.now() - 3600_000),
312
+ MaxUses: 1,
313
+ UseCount: 0,
314
+ CreatedByUserID: ownerUser.ID,
315
+ Status: 'Expired',
316
+ });
317
+ const provider = makeFakeProvider(invites);
318
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
319
+
320
+ const newExpiry = new Date(Date.now() + 48 * 3600 * 1000);
321
+ const updated = await service.UpdateInviteExpiry('inv-expired', newExpiry, ownerUser, provider);
322
+ expect(updated.success).toBe(true);
323
+ expect(updated.status).toBe('Active');
324
+ expect(updated.expiresAt).toBe(newExpiry.toISOString());
325
+ });
326
+
327
+ it('keeps &rt= redirect binding valid after UpdateInviteExpiry extends an invite past its initial expiry, and guards UseCount/past-expiry transitions', async () => {
328
+ const invites = new Map<string, StoredInviteState>();
329
+ const provider = makeFakeProvider(invites);
330
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
331
+
332
+ const rawToken = 'mj_ml_1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff';
333
+ const tokenHash = HashToken(rawToken);
334
+ const pastExpiry = new Date(Date.now() - 3600_000);
335
+ // Mint redirect binding with a past ExpiresAt epoch
336
+ const rt = MagicLinkKeyManager.Instance.SignRedirectBinding(
337
+ tokenHash,
338
+ 'https://forms.example.com/survey/extended',
339
+ Math.floor(pastExpiry.getTime() / 1000),
340
+ );
341
+
342
+ invites.set('inv-extend-rt', {
343
+ ID: 'inv-extend-rt',
344
+ TokenHash: tokenHash,
345
+ Email: 'respondent@example.com',
346
+ ApplicationID: 'app-forms',
347
+ RoleID: 'role-guest',
348
+ ExpiresAt: pastExpiry,
349
+ MaxUses: 1,
350
+ UseCount: 0,
351
+ CreatedByUserID: ownerUser.ID,
352
+ Status: 'Expired',
353
+ });
354
+
355
+ // Extend invite into the future without re-issuing the URL
356
+ const extendedExpiry = new Date(Date.now() + 24 * 3600_000);
357
+ const extended = await service.UpdateInviteExpiry('inv-extend-rt', extendedExpiry, ownerUser, provider);
358
+ expect(extended.success).toBe(true);
359
+ expect(extended.status).toBe('Active');
360
+
361
+ // Original &rt= binding still resolves the allowlisted RedirectUrl
362
+ expect(service.ResolveVerifiedRedirectUrl(rawToken, rt)).toBe('https://forms.example.com/survey/extended');
363
+
364
+ // Shortening an Active invite to <= now transitions Status to Expired
365
+ const shortened = await service.UpdateInviteExpiry('inv-extend-rt', new Date(Date.now() - 1000), ownerUser, provider);
366
+ expect(shortened.success).toBe(true);
367
+ expect(shortened.status).toBe('Expired');
368
+
369
+ // An Expired invite whose UseCount >= MaxUses is NOT reactivated to Active
370
+ invites.set('inv-exhausted-expired', {
371
+ ID: 'inv-exhausted-expired',
372
+ TokenHash: HashToken('mj_ml_exhausted'),
373
+ Email: 'used@example.com',
374
+ ApplicationID: 'app-forms',
375
+ RoleID: 'role-guest',
376
+ ExpiresAt: pastExpiry,
377
+ MaxUses: 1,
378
+ UseCount: 1,
379
+ CreatedByUserID: ownerUser.ID,
380
+ Status: 'Expired',
381
+ });
382
+ const notReactivated = await service.UpdateInviteExpiry('inv-exhausted-expired', extendedExpiry, ownerUser, provider);
383
+ expect(notReactivated.success).toBe(true);
384
+ expect(notReactivated.status).toBe('Expired');
385
+ });
386
+ });
387
+
388
+ describe('VerifySessionToken', () => {
389
+ it('verifies a signed magic-link session JWT and rejects wrong audience or issuer', () => {
390
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
391
+ const nowSeconds = Math.floor(Date.now() / 1000);
392
+ const claims = BuildSessionClaims({
393
+ issuer: 'https://api.example.com',
394
+ audience: 'mj-magic-link',
395
+ inviteId: 'inv-123',
396
+ email: 'person@example.com',
397
+ applicationId: 'app-forms',
398
+ roleName: 'External Guest',
399
+ nowSeconds,
400
+ ttlSeconds: 3600,
401
+ });
402
+ const jwtToken = MagicLinkKeyManager.Instance.Sign(claims);
403
+
404
+ const verified = service.VerifySessionToken(jwtToken);
405
+ expect(verified).not.toBeNull();
406
+ expect(verified?.email).toBe('person@example.com');
407
+ expect(verified?.mj_app_id).toBe('app-forms');
408
+
409
+ const wrongIssuerService = new MagicLinkService('https://other-api.example.com', makeConfig());
410
+ expect(wrongIssuerService.VerifySessionToken(jwtToken)).toBeNull();
411
+ });
412
+ });
413
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `conversationDetailReconciler` settings: the reconciler always runs its startup pass, and repeats
3
+ * on the configured interval only while enabled. Disabled, an idle server sends it no periodic query.
4
+ */
5
+ import 'reflect-metadata';
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import type { UserInfo } from '@memberjunction/core';
8
+
9
+ const { mockRunView } = vi.hoisted(() => ({ mockRunView: vi.fn() }));
10
+
11
+ vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
12
+ UserCache: { Instance: { Users: [] } },
13
+ }));
14
+
15
+ vi.mock('@memberjunction/core', () => {
16
+ class MockRunView {
17
+ public static FromMetadataProvider(): MockRunView {
18
+ return new MockRunView();
19
+ }
20
+ public async RunView(...args: unknown[]): Promise<unknown> {
21
+ return mockRunView(...args);
22
+ }
23
+ }
24
+ return { LogError: vi.fn(), LogStatus: vi.fn(), RunView: MockRunView };
25
+ });
26
+
27
+ import { StartOrphanedConversationDetailReconciler } from '../generic/OrphanedConversationDetailReconciler.js';
28
+
29
+ const USER = { ID: 'U1' } as unknown as UserInfo;
30
+ const PROVIDER = {
31
+ EntityByName: (name: string) =>
32
+ name === 'MJ: AI Agent Runs' ? { SchemaName: '__mj', BaseView: 'vwAIAgentRuns' } : undefined,
33
+ QuoteIdentifier: (name: string) => `[${name}]`,
34
+ QuoteSchemaAndView: (schema: string, view: string) => `[${schema}].[${view}]`,
35
+ } as never;
36
+ const HOUR = 60 * 60_000;
37
+
38
+ /** Passes run so far: each pass starts with one read of in-progress conversation details. */
39
+ const passes = (): number => mockRunView.mock.calls.length;
40
+
41
+ describe('StartOrphanedConversationDetailReconciler', () => {
42
+ beforeEach(() => {
43
+ vi.useFakeTimers();
44
+ mockRunView.mockReset();
45
+ mockRunView.mockResolvedValue({ Success: true, Results: [] });
46
+ });
47
+ afterEach(() => {
48
+ vi.clearAllTimers();
49
+ vi.useRealTimers();
50
+ });
51
+
52
+ it('enabled: runs a pass now, then one every interval', async () => {
53
+ const timer = StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: true, sweepIntervalMs: 60_000 });
54
+ await vi.advanceTimersByTimeAsync(5 * 60_000);
55
+
56
+ expect(timer).not.toBeNull();
57
+ expect(passes()).toBe(6);
58
+ });
59
+
60
+ it('disabled: runs the startup pass only, and schedules nothing', async () => {
61
+ const timer = StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: false, sweepIntervalMs: 60_000 });
62
+ await vi.advanceTimersByTimeAsync(HOUR);
63
+
64
+ expect(passes()).toBe(1);
65
+ expect(timer).toBeNull();
66
+ });
67
+
68
+ it('with no settings keeps the shipped 5-minute repeat', async () => {
69
+ StartOrphanedConversationDetailReconciler(PROVIDER, USER);
70
+ await vi.advanceTimersByTimeAsync(HOUR);
71
+
72
+ expect(passes()).toBe(13);
73
+ });
74
+
75
+ it('a failing pass is logged, not thrown, and the next pass still runs', async () => {
76
+ mockRunView.mockRejectedValueOnce(new Error('db down'));
77
+ StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: true, sweepIntervalMs: 60_000 });
78
+ await vi.advanceTimersByTimeAsync(60_000);
79
+
80
+ expect(passes()).toBe(2);
81
+ });
82
+ });
@@ -36,4 +36,18 @@ describe('LoadOwnedRealtimeSession', () => {
36
36
  await expect(LoadOwnedRealtimeSession(SESSION, STRANGER, providerWith({ UserID: OWNER.ID })))
37
37
  .rejects.toThrow('Not authorized: you do not own this realtime session');
38
38
  });
39
+
40
+ it('skips loading MJ: Conversations when the caller is a standard named user without a scope identity', async () => {
41
+ const getEntityObject = vi.fn(async (name: string) => ({
42
+ UserID: OWNER.ID,
43
+ ConversationID: '22222222-2222-4222-8222-222222222222',
44
+ Load: vi.fn(async () => name === 'MJ: AI Agent Sessions'),
45
+ }));
46
+ const prov = { GetEntityObject: getEntityObject } as unknown as IMetadataProvider;
47
+
48
+ await LoadOwnedRealtimeSession(SESSION, OWNER, prov);
49
+
50
+ expect(getEntityObject).toHaveBeenCalledTimes(1);
51
+ expect(getEntityObject).toHaveBeenCalledWith('MJ: AI Agent Sessions', OWNER);
52
+ });
39
53
  });
@@ -25,6 +25,7 @@ import {
25
25
  CreateRealtimeRecordingRouter, FINAL_TERMINAL_RECHECK_MS, REALTIME_RECORDING_MOUNT_PATH, type RealtimeRecordingRouterDeps,
26
26
  } from '../rest/RealtimeRecordingHandler.js';
27
27
  import { RealtimeSessionNotFoundError, RealtimeSessionOwnershipError } from '../realtimeSessions/ownedRealtimeSession.js';
28
+ import { AuthorizeSessionAccess } from '../realtimeSessions/sessionAccess.js';
28
29
  import { RecordingAssemblyScheduler, type IRecordingAssemblyScheduler } from '../realtimeSessions/RecordingAssemblyScheduler.js';
29
30
 
30
31
  const SESSION = '11111111-1111-4111-8111-111111111111';
@@ -274,9 +275,13 @@ describe('realtime recording REST routes', () => {
274
275
  };
275
276
  return id ? { ContextUser: user as unknown as UserInfo, Provider: {} as unknown as DatabaseProviderBase } : null;
276
277
  },
277
- LoadConversationScope: async session => {
278
+ // The real scope rule over this test's conversation rows (no database behind them).
279
+ AuthorizeSession: async (session, contextUser) => {
278
280
  const row = ledger.Sessions.get(session.ID);
279
- return { ExternalID: row?.ConversationExternalID ?? null, Readable: row?.ConversationExternalID !== undefined };
281
+ return AuthorizeSessionAccess(
282
+ { SessionUserID: session.UserID, ConversationExternalID: row?.ConversationExternalID ?? null, ConversationReadable: row?.ConversationExternalID !== undefined },
283
+ contextUser,
284
+ );
280
285
  },
281
286
  LoadOwnedSession: async (agentSessionId, contextUser) => {
282
287
  const row = ledger.Sessions.get(agentSessionId);