@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4

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 (435) hide show
  1. package/README.md +30 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  5. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  7. package/dist/agentSessions/HostInstance.d.ts +75 -11
  8. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  9. package/dist/agentSessions/HostInstance.js +99 -28
  10. package/dist/agentSessions/HostInstance.js.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  12. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  13. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  14. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +452 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +43 -2
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +99 -8
  21. package/dist/agentSessions/SessionManager.js.map +1 -1
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  25. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  26. package/dist/agentSessions/index.d.ts +2 -0
  27. package/dist/agentSessions/index.d.ts.map +1 -1
  28. package/dist/agentSessions/index.js +10 -3
  29. package/dist/agentSessions/index.js.map +1 -1
  30. package/dist/auth/actionAuthorization.d.ts +40 -0
  31. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  32. package/dist/auth/actionAuthorization.js +94 -0
  33. package/dist/auth/actionAuthorization.js.map +1 -0
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +3 -10
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  39. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  40. package/dist/auth/newUsers.d.ts +10 -0
  41. package/dist/auth/newUsers.d.ts.map +1 -1
  42. package/dist/auth/newUsers.js +80 -11
  43. package/dist/auth/newUsers.js.map +1 -1
  44. package/dist/config.d.ts +4567 -71
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +246 -3
  47. package/dist/config.js.map +1 -1
  48. package/dist/context.d.ts +8 -0
  49. package/dist/context.d.ts.map +1 -1
  50. package/dist/context.js +25 -34
  51. package/dist/context.js.map +1 -1
  52. package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
  53. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  54. package/dist/generated/graphql-schemas/__mj.js +9188 -3649
  55. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  56. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  57. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  58. package/dist/generic/ResolverBase.d.ts +62 -6
  59. package/dist/generic/ResolverBase.d.ts.map +1 -1
  60. package/dist/generic/ResolverBase.js +179 -35
  61. package/dist/generic/ResolverBase.js.map +1 -1
  62. package/dist/generic/RunViewResolver.d.ts +4 -0
  63. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  64. package/dist/generic/RunViewResolver.js +37 -1
  65. package/dist/generic/RunViewResolver.js.map +1 -1
  66. package/dist/index.d.ts +7 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +214 -159
  69. package/dist/index.js.map +1 -1
  70. package/dist/isolatedProvider.d.ts +20 -0
  71. package/dist/isolatedProvider.d.ts.map +1 -0
  72. package/dist/isolatedProvider.js +22 -0
  73. package/dist/isolatedProvider.js.map +1 -0
  74. package/dist/postgresqlCompat.d.ts +8 -0
  75. package/dist/postgresqlCompat.d.ts.map +1 -0
  76. package/dist/postgresqlCompat.js +19 -0
  77. package/dist/postgresqlCompat.js.map +1 -0
  78. package/dist/postgresqlPoolSettings.d.ts +108 -0
  79. package/dist/postgresqlPoolSettings.d.ts.map +1 -0
  80. package/dist/postgresqlPoolSettings.js +134 -0
  81. package/dist/postgresqlPoolSettings.js.map +1 -0
  82. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  83. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  84. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  85. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  86. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  87. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  88. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  89. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  90. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  91. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  92. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  93. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  94. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  95. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  96. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  97. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  98. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  99. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  100. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  101. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  102. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  103. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  104. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  105. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  106. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  107. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  108. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  109. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  110. package/dist/realtimeSessions/index.d.ts +17 -0
  111. package/dist/realtimeSessions/index.d.ts.map +1 -0
  112. package/dist/realtimeSessions/index.js +17 -0
  113. package/dist/realtimeSessions/index.js.map +1 -0
  114. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  115. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  116. package/dist/realtimeSessions/sessionAccess.js +90 -0
  117. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  118. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  119. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  120. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  121. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  122. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  123. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  124. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  125. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  126. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  127. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  128. package/dist/realtimeSessions/verificationCore.js +356 -0
  129. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  130. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  131. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  132. package/dist/realtimeSessions/verificationPages.js +110 -0
  133. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  134. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  135. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  136. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  137. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  138. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  139. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  140. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  141. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  142. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  143. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  144. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  145. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  146. package/dist/resolvers/ActionResolver.d.ts +31 -5
  147. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  148. package/dist/resolvers/ActionResolver.js +75 -14
  149. package/dist/resolvers/ActionResolver.js.map +1 -1
  150. package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
  151. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  152. package/dist/resolvers/AdhocQueryResolver.js +92 -166
  153. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  154. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  155. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  156. package/dist/resolvers/CacheStatsResolver.js +113 -2
  157. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  158. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  159. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  160. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  161. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  162. package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
  163. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  164. package/dist/resolvers/EntityRecordNameResolver.js +9 -47
  165. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  166. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  167. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  168. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  169. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  170. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  171. package/dist/resolvers/GetDataResolver.js +9 -0
  172. package/dist/resolvers/GetDataResolver.js.map +1 -1
  173. package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
  174. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
  175. package/dist/resolvers/HumanHandoffResolver.js +330 -0
  176. package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
  177. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  178. package/dist/resolvers/MCPResolver.js +9 -4
  179. package/dist/resolvers/MCPResolver.js.map +1 -1
  180. package/dist/resolvers/MeetingResolver.d.ts +69 -0
  181. package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
  182. package/dist/resolvers/MeetingResolver.js +381 -0
  183. package/dist/resolvers/MeetingResolver.js.map +1 -0
  184. package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
  185. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  186. package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
  187. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  188. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
  189. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  190. package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
  191. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  192. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  193. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  194. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  195. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  196. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  197. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  198. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  199. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  200. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  201. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  202. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  204. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  205. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  206. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  207. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  208. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunTemplateResolver.js +12 -11
  210. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  211. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  212. package/dist/resolvers/SyncDataResolver.js +6 -2
  213. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  214. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  215. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  216. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  217. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  218. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  219. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  220. package/dist/resolvers/UserAvatarResolver.js +124 -0
  221. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  222. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  223. package/dist/resolvers/UserResolver.js +2 -17
  224. package/dist/resolvers/UserResolver.js.map +1 -1
  225. package/dist/resolvers/adhocTimeout.d.ts +7 -0
  226. package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
  227. package/dist/resolvers/adhocTimeout.js +12 -0
  228. package/dist/resolvers/adhocTimeout.js.map +1 -0
  229. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  230. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  231. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  232. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  233. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  234. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  235. package/dist/resolvers/avatarInputValidation.js +97 -0
  236. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  237. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  238. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  239. package/dist/resolvers/currentUserRoles.js +30 -0
  240. package/dist/resolvers/currentUserRoles.js.map +1 -1
  241. package/dist/resolvers/meetings/index.d.ts +5 -0
  242. package/dist/resolvers/meetings/index.d.ts.map +1 -0
  243. package/dist/resolvers/meetings/index.js +5 -0
  244. package/dist/resolvers/meetings/index.js.map +1 -0
  245. package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
  246. package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
  247. package/dist/resolvers/meetings/meetingHelpers.js +212 -0
  248. package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
  249. package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
  250. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
  251. package/dist/resolvers/meetings/meetingMutations.js +439 -0
  252. package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
  253. package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
  254. package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
  255. package/dist/resolvers/meetings/meetingQueries.js +113 -0
  256. package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
  257. package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
  258. package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
  259. package/dist/resolvers/meetings/meetingTypes.js +345 -0
  260. package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
  261. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  262. package/dist/rest/ViewOperationsHandler.js +8 -5
  263. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  264. package/dist/services/TaskGraphActionRunner.d.ts +8 -0
  265. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  266. package/dist/services/TaskGraphActionRunner.js +27 -1
  267. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  268. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  269. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  270. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  271. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  272. package/dist/services/WorkQueueHostService.d.ts +36 -0
  273. package/dist/services/WorkQueueHostService.d.ts.map +1 -0
  274. package/dist/services/WorkQueueHostService.js +97 -0
  275. package/dist/services/WorkQueueHostService.js.map +1 -0
  276. package/dist/services/workQueueConfig.d.ts +103 -0
  277. package/dist/services/workQueueConfig.d.ts.map +1 -0
  278. package/dist/services/workQueueConfig.js +48 -0
  279. package/dist/services/workQueueConfig.js.map +1 -0
  280. package/dist/sharedCache.d.ts +105 -0
  281. package/dist/sharedCache.d.ts.map +1 -0
  282. package/dist/sharedCache.js +276 -0
  283. package/dist/sharedCache.js.map +1 -0
  284. package/dist/types.d.ts +11 -0
  285. package/dist/types.d.ts.map +1 -1
  286. package/dist/types.js.map +1 -1
  287. package/package.json +97 -94
  288. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  289. package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
  290. package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
  291. package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
  292. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  293. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  294. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  295. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
  296. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  297. package/src/__tests__/HostInstance.test.ts +84 -0
  298. package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
  299. package/src/__tests__/MeetingResolver.test.ts +448 -0
  300. package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
  301. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  302. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  303. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  304. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  305. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  306. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
  307. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  308. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  309. package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
  310. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
  311. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  312. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  313. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  314. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  315. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  316. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  317. package/src/__tests__/SessionJanitor.test.ts +845 -18
  318. package/src/__tests__/SessionManager.test.ts +213 -17
  319. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  320. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  321. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
  322. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  323. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  324. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  325. package/src/__tests__/WorkQueueHostService.test.ts +133 -0
  326. package/src/__tests__/actionAuthorization.test.ts +130 -0
  327. package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
  328. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  329. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  330. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  331. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  332. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  333. package/src/__tests__/newUsers.template.test.ts +151 -0
  334. package/src/__tests__/newUsers.test.ts +74 -5
  335. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  336. package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
  337. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  338. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  339. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  340. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  341. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  342. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  343. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  344. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  345. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  346. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  347. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  348. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  349. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  350. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  351. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  352. package/src/__tests__/sharedCache.test.ts +310 -0
  353. package/src/__tests__/translateBracketsToPG.test.ts +20 -0
  354. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  355. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  356. package/src/agentSessions/HostInstance.ts +124 -28
  357. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  358. package/src/agentSessions/SessionJanitor.ts +543 -57
  359. package/src/agentSessions/SessionManager.ts +125 -25
  360. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  361. package/src/agentSessions/index.ts +10 -3
  362. package/src/auth/actionAuthorization.ts +116 -0
  363. package/src/auth/index.ts +3 -8
  364. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  365. package/src/auth/newUsers.ts +99 -11
  366. package/src/config.ts +255 -3
  367. package/src/context.ts +26 -34
  368. package/src/generated/graphql-schemas/__mj.ts +22701 -18838
  369. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  370. package/src/generic/ResolverBase.ts +210 -36
  371. package/src/generic/RunViewResolver.ts +37 -2
  372. package/src/index.ts +236 -170
  373. package/src/isolatedProvider.ts +22 -0
  374. package/src/postgresqlCompat.ts +21 -0
  375. package/src/postgresqlPoolSettings.ts +213 -0
  376. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  377. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  378. package/src/realtimeSessions/README.md +179 -0
  379. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  380. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  381. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  382. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  383. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  384. package/src/realtimeSessions/index.ts +16 -0
  385. package/src/realtimeSessions/sessionAccess.ts +141 -0
  386. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  387. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  388. package/src/realtimeSessions/verificationCore.ts +502 -0
  389. package/src/realtimeSessions/verificationPages.ts +149 -0
  390. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  391. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  392. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  393. package/src/resolvers/ActionResolver.ts +84 -21
  394. package/src/resolvers/AdhocQueryResolver.ts +105 -187
  395. package/src/resolvers/CacheStatsResolver.ts +89 -2
  396. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  397. package/src/resolvers/EntityRecordNameResolver.ts +9 -51
  398. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  399. package/src/resolvers/GetDataResolver.ts +10 -0
  400. package/src/resolvers/HumanHandoffResolver.ts +312 -0
  401. package/src/resolvers/MCPResolver.ts +8 -5
  402. package/src/resolvers/MeetingResolver.ts +438 -0
  403. package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
  404. package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
  405. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  406. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  407. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  408. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  409. package/src/resolvers/RunTemplateResolver.ts +13 -12
  410. package/src/resolvers/SyncDataResolver.ts +6 -2
  411. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  412. package/src/resolvers/UserAvatarResolver.ts +111 -0
  413. package/src/resolvers/UserResolver.ts +2 -16
  414. package/src/resolvers/adhocTimeout.ts +12 -0
  415. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  416. package/src/resolvers/avatarInputValidation.ts +123 -0
  417. package/src/resolvers/currentUserRoles.ts +32 -1
  418. package/src/resolvers/meetings/index.ts +4 -0
  419. package/src/resolvers/meetings/meetingHelpers.ts +307 -0
  420. package/src/resolvers/meetings/meetingMutations.ts +556 -0
  421. package/src/resolvers/meetings/meetingQueries.ts +172 -0
  422. package/src/resolvers/meetings/meetingTypes.ts +238 -0
  423. package/src/rest/ViewOperationsHandler.ts +8 -5
  424. package/src/services/TaskGraphActionRunner.ts +29 -1
  425. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  426. package/src/services/WorkQueueHostService.ts +129 -0
  427. package/src/services/workQueueConfig.ts +52 -0
  428. package/src/sharedCache.ts +302 -0
  429. package/src/types.ts +12 -0
  430. package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
  431. package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
  432. package/dist/resolvers/adhoc-query-helpers.js +0 -50
  433. package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
  434. package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
  435. package/src/resolvers/adhoc-query-helpers.ts +0 -67
@@ -0,0 +1,22 @@
1
+ import { DatabaseProviderBase, Metadata } from '@memberjunction/core';
2
+
3
+ /**
4
+ * A fresh Read-Write provider with its OWN transaction state, sharing the server's connection pool.
5
+ *
6
+ * A provider's ambient transaction (`BeginTransaction` / `CommitTransaction` / `RollbackTransaction`)
7
+ * is a field on the provider INSTANCE. Two units of work that open transactions on one instance nest
8
+ * into each other, and every write anyone else makes through that instance while one is open lands
9
+ * inside it (MJ#2140; `entityTransactionScope.ts`). The process-global `Metadata.Provider` is shared
10
+ * by every request, scheduled job and background service, so transactional work must never run on
11
+ * it: one transaction left open there silently swallows every later global-provider write until the
12
+ * process restarts.
13
+ *
14
+ * GraphQL resolvers already get a per-request provider from `AppContext.providers`. Code that runs
15
+ * outside one, such as the auth flow before the context exists, calls this.
16
+ *
17
+ * Cheap: it is the global provider's `CreateIndependentInstance()` (SQL Server and PostgreSQL), which
18
+ * reuses the already-loaded metadata and the shared pool.
19
+ */
20
+ export async function CreateIsolatedProvider(): Promise<DatabaseProviderBase> {
21
+ return (Metadata.Provider as DatabaseProviderBase).CreateIndependentInstance(); // global-provider-ok: borrows only its pool and metadata, never its transaction state
22
+ }
@@ -0,0 +1,21 @@
1
+ import { LexSQL } from '@memberjunction/sql-parser';
2
+ import { SQLServerDialect } from '@memberjunction/sql-dialect';
3
+
4
+ const sqlServer = new SQLServerDialect();
5
+
6
+ /**
7
+ * Translates SQL Server bracket-quoted identifiers (`[Name]`) to PostgreSQL double-quoted ones
8
+ * (`"Name"`), for SQL written in SQL Server syntax that runs on PostgreSQL. Only identifiers
9
+ * change: brackets inside string literals and comments are left as written. A `]]` in a name
10
+ * becomes `]`, and a `"` in a name is doubled.
11
+ */
12
+ export function TranslateBracketsToPG(sql: string): string {
13
+ return LexSQL(sql, sqlServer)
14
+ .map(t => t.Kind === 'identifier' && t.Text.startsWith('[') ? toDoubleQuoted(t.Text) : t.Text)
15
+ .join('');
16
+ }
17
+
18
+ function toDoubleQuoted(bracketed: string): string {
19
+ const name = bracketed.slice(1, bracketed.endsWith(']') ? -1 : undefined).replace(/]]/g, ']');
20
+ return `"${name.replace(/"/g, '""')}"`;
21
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Connection settings for MJAPI's PostgreSQL pools, derived from the same `databaseSettings`
3
+ * values SQL Server reads, with the same defaults.
4
+ *
5
+ * | Setting | SQL Server | PostgreSQL |
6
+ * |--------------------------------------|----------------------------------|----------------------------------------------|
7
+ * | `requestTimeout` | driver request timeout | `statement_timeout` on every backend |
8
+ * | `connectionPool.max` / `min` | pool size | pool size |
9
+ * | `connectionPool.idleTimeoutMillis` | idle connection eviction | idle connection eviction |
10
+ * | `connectionPool.acquireTimeoutMillis`| wait for a pooled connection | `connectionTimeoutMillis` (see below) |
11
+ * | `connectionTimeout` | time to open a connection | not used |
12
+ *
13
+ * Where the two cannot match exactly:
14
+ * - The `pg` pool has one timeout covering both waiting for a free slot and opening a connection.
15
+ * It is fed from `acquireTimeoutMillis`; `connectionTimeout` is not used on PostgreSQL.
16
+ * - SQL Server's `requestTimeout` is a client-side timer over the whole request, including reading
17
+ * rows. PostgreSQL's `statement_timeout` is server-side execution time per statement, so a query
18
+ * that executes quickly and returns its rows slowly is not cut off on PostgreSQL.
19
+ * - `idle_in_transaction_session_timeout` has no SQL Server counterpart. It is derived as twice the
20
+ * statement timeout, so a transaction left open by an abandoned request is ended and its locks
21
+ * released.
22
+ * - MJAPI opens a raw compatibility pool and a provider pool, each up to `connectionPool.max`, plus
23
+ * a CodeGen pool of 10 and, when a read-only login is configured, a read-only pool of at most 10,
24
+ * where SQL Server opens one pool (and a read-only one). PostgreSQL's default `max_connections`
25
+ * is 100, so the read-only and CodeGen pools are kept small.
26
+ */
27
+ import type { PGConnectionConfig } from '@memberjunction/postgresql-dataprovider';
28
+ import type { Pool, PoolConfig } from 'pg';
29
+ import { BaseSingleton } from '@memberjunction/global';
30
+ import type { DatabaseSettingsInfo } from './config.js';
31
+
32
+ /** Statement timeout for the CodeGen/DDL pool, matching the SQL Server CodeGen pool. */
33
+ export const CODEGEN_STATEMENT_TIMEOUT_MS = 600000;
34
+
35
+ /** Pool size for the CodeGen/DDL pool. */
36
+ const CODEGEN_MAX_CONNECTIONS = 10;
37
+
38
+ /** Largest pool size for the read-only pool, which serves only caller-supplied SQL. */
39
+ const READ_ONLY_MAX_CONNECTIONS = 10;
40
+
41
+ /**
42
+ * Which pool the settings are for: API traffic, the read-only pool for caller-supplied SQL, or
43
+ * CodeGen and DDL work.
44
+ */
45
+ export type PostgreSQLPoolPurpose = 'api' | 'read-only' | 'codegen';
46
+
47
+ /** Where a PostgreSQL pool connects and as whom. */
48
+ export interface PostgreSQLEndpoint {
49
+ Host: string;
50
+ Port: number;
51
+ Database: string;
52
+ User: string;
53
+ Password: string;
54
+ }
55
+
56
+ /** Reads the PostgreSQL endpoint from `PG_*` environment variables, then `DB_*` ones. */
57
+ export function ResolvePostgreSQLEndpoint(env: NodeJS.ProcessEnv = process.env): PostgreSQLEndpoint {
58
+ return {
59
+ Host: env.PG_HOST || env.DB_HOST || 'localhost',
60
+ Port: parseInt(env.PG_PORT || env.DB_PORT || '5432', 10),
61
+ Database: env.PG_DATABASE || env.DB_DATABASE || '',
62
+ User: env.PG_USERNAME || env.DB_USERNAME || 'postgres',
63
+ Password: env.PG_PASSWORD || env.DB_PASSWORD || ''
64
+ };
65
+ }
66
+
67
+ /** The idle-in-transaction limit for a given statement timeout; 0 means none. */
68
+ export function IdleInTransactionTimeoutFor(statementTimeoutMs: number): number {
69
+ return statementTimeoutMs > 0 ? statementTimeoutMs * 2 : 0;
70
+ }
71
+
72
+ /** Builds the provider connection config for one of MJAPI's PostgreSQL pools. */
73
+ export function BuildPostgreSQLConnectionConfig(
74
+ endpoint: PostgreSQLEndpoint,
75
+ settings: DatabaseSettingsInfo,
76
+ purpose: PostgreSQLPoolPurpose
77
+ ): PGConnectionConfig {
78
+ const pool = settings.connectionPool;
79
+ if (purpose === 'codegen') {
80
+ return {
81
+ ...endpoint,
82
+ MaxConnections: CODEGEN_MAX_CONNECTIONS,
83
+ StatementTimeoutMs: CODEGEN_STATEMENT_TIMEOUT_MS,
84
+ IdleInTransactionSessionTimeoutMs: 0
85
+ };
86
+ }
87
+ const statementTimeoutMs = Math.max(0, settings.requestTimeout);
88
+ const apiMax = pool?.max ?? 50;
89
+ const apiMin = pool?.min ?? 5;
90
+ const readOnly = purpose === 'read-only';
91
+ return {
92
+ ...endpoint,
93
+ MaxConnections: readOnly ? Math.min(READ_ONLY_MAX_CONNECTIONS, apiMax) : apiMax,
94
+ MinConnections: readOnly ? 0 : apiMin,
95
+ IdleTimeoutMillis: pool?.idleTimeoutMillis ?? 30000,
96
+ ConnectionTimeoutMillis: pool?.acquireTimeoutMillis ?? 30000,
97
+ StatementTimeoutMs: statementTimeoutMs,
98
+ IdleInTransactionSessionTimeoutMs: IdleInTransactionTimeoutFor(statementTimeoutMs)
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Maps a provider connection config onto `pg.Pool` options, for the pools MJAPI creates directly
104
+ * rather than through the provider. Timeouts that are unset or 0 are left out.
105
+ */
106
+ export function ToPGPoolConfig(config: PGConnectionConfig): PoolConfig {
107
+ const pool: PoolConfig = {
108
+ host: config.Host,
109
+ port: config.Port,
110
+ database: config.Database,
111
+ user: config.User,
112
+ password: config.Password,
113
+ max: config.MaxConnections
114
+ };
115
+ if (config.MinConnections !== undefined) pool.min = config.MinConnections;
116
+ if (config.IdleTimeoutMillis !== undefined) pool.idleTimeoutMillis = config.IdleTimeoutMillis;
117
+ if (config.ConnectionTimeoutMillis !== undefined) pool.connectionTimeoutMillis = config.ConnectionTimeoutMillis;
118
+ if (config.StatementTimeoutMs) pool.statement_timeout = config.StatementTimeoutMs;
119
+ if (config.IdleInTransactionSessionTimeoutMs) pool.idle_in_transaction_session_timeout = config.IdleInTransactionSessionTimeoutMs;
120
+ return pool;
121
+ }
122
+
123
+ /** Credentials for a read-only database login. */
124
+ export interface ReadOnlyCredentials {
125
+ User: string;
126
+ Password: string;
127
+ }
128
+
129
+ /**
130
+ * The read-only login for PostgreSQL: the `dbReadOnlyUsername` / `dbReadOnlyPassword` settings
131
+ * SQL Server uses (which default from `DB_READ_ONLY_USERNAME` / `DB_READ_ONLY_PASSWORD`), then
132
+ * `PG_READ_ONLY_USERNAME` / `PG_READ_ONLY_PASSWORD`. `null` when neither pair is complete.
133
+ */
134
+ export function ResolvePostgreSQLReadOnlyCredentials(
135
+ configured: { dbReadOnlyUsername?: string; dbReadOnlyPassword?: string },
136
+ env: NodeJS.ProcessEnv = process.env
137
+ ): ReadOnlyCredentials | null {
138
+ if (configured.dbReadOnlyUsername && configured.dbReadOnlyPassword) {
139
+ return { User: configured.dbReadOnlyUsername, Password: configured.dbReadOnlyPassword };
140
+ }
141
+ if (env.PG_READ_ONLY_USERNAME && env.PG_READ_ONLY_PASSWORD) {
142
+ return { User: env.PG_READ_ONLY_USERNAME, Password: env.PG_READ_ONLY_PASSWORD };
143
+ }
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Holds MJAPI's PostgreSQL read-only pool, opened once at startup with the read-only login.
149
+ * Per-request read-only providers share it, so they connect as the read-only user rather than
150
+ * through the read-write pool.
151
+ */
152
+ export class PostgreSQLReadOnlyPool extends BaseSingleton<PostgreSQLReadOnlyPool> {
153
+ private _pool: Pool | null = null;
154
+
155
+ /** Use {@link PostgreSQLReadOnlyPool.Instance}. */
156
+ public constructor() {
157
+ super();
158
+ }
159
+
160
+ public static get Instance(): PostgreSQLReadOnlyPool {
161
+ return PostgreSQLReadOnlyPool.getInstance<PostgreSQLReadOnlyPool>();
162
+ }
163
+
164
+ /** The read-only pool, or `null` when no read-only login is configured. */
165
+ public get Pool(): Pool | null {
166
+ return this._pool;
167
+ }
168
+
169
+ public set Pool(pool: Pool | null) {
170
+ this._pool = pool;
171
+ }
172
+ }
173
+
174
+ /** What {@link DescribeReadOnlyLoginOverreach} reads about the read-only login. */
175
+ export interface ReadOnlyLoginGrants {
176
+ ReadsServerFiles: boolean;
177
+ ReadableBaseTables: number;
178
+ }
179
+
180
+ /** The one method {@link DescribeReadOnlyLoginOverreach} needs from a connection; a `pg` client has it. */
181
+ export interface ReadOnlyLoginCheckClient {
182
+ query(sql: string, params: unknown[]): Promise<{ rows: ReadOnlyLoginGrants[] }>; // case-violation-ok-legacy-back-compat: mirrors the pg client's query method, which a pg client must satisfy as-is
183
+ }
184
+
185
+ /**
186
+ * What the PostgreSQL read-only login can do beyond reading entity base views, as warnings to log at
187
+ * startup. Ad-hoc SQL and `TestQuerySQL` run as this login, and SQL can reach data through functions
188
+ * that no check of its table references sees (`query_to_xml`, file readers), so the login's own
189
+ * grants are the only complete limit. It should hold `SELECT` on the entity base views and nothing
190
+ * else: not on the base tables, and not membership of `pg_read_server_files`. Empty when the login
191
+ * is that narrow.
192
+ *
193
+ * @param client - A connection opened as the read-only login.
194
+ * @param coreSchema - The MJ core schema, whose base tables the login should not read.
195
+ */
196
+ export async function DescribeReadOnlyLoginOverreach(client: ReadOnlyLoginCheckClient, coreSchema: string): Promise<string[]> {
197
+ const result = await client.query(
198
+ `SELECT pg_has_role(current_user, 'pg_read_server_files', 'MEMBER') AS "ReadsServerFiles",
199
+ (SELECT count(*)::int FROM pg_catalog.pg_tables t
200
+ WHERE t.schemaname = $1
201
+ AND has_table_privilege(current_user, quote_ident(t.schemaname) || '.' || quote_ident(t.tablename), 'SELECT')) AS "ReadableBaseTables"`,
202
+ [coreSchema],
203
+ );
204
+ const row = result.rows[0];
205
+ const warnings: string[] = [];
206
+ if (row?.ReadableBaseTables > 0) {
207
+ warnings.push(`The PostgreSQL read-only login can read ${row.ReadableBaseTables} base table(s) in schema "${coreSchema}". Ad-hoc SQL and TestQuerySQL run as this login; grant it SELECT on the entity base views only.`);
208
+ }
209
+ if (row?.ReadsServerFiles) {
210
+ warnings.push('The PostgreSQL read-only login is a member of pg_read_server_files, so SQL run as it can read files on the database server. Revoke that membership.');
211
+ }
212
+ return warnings;
213
+ }
@@ -2,7 +2,27 @@ import type { IncomingMessage } from 'node:http';
2
2
  import type { Duplex } from 'node:stream';
3
3
  import { WebSocket as WsClient, WebSocketServer, type RawData } from 'ws';
4
4
  import { BaseSingleton, ShutdownRegistry, type IShutdownable } from '@memberjunction/global';
5
- import { RealtimeProxyRegistry, REALTIME_PROXY_PATH, type RealtimeProxyTicketEntry } from '@memberjunction/ai';
5
+ import {
6
+ RealtimeProxyRegistry,
7
+ REALTIME_PROXY_PATH,
8
+ REALTIME_RELAY_PATH,
9
+ type IRealtimeRelayPolicy,
10
+ type RealtimeProxyTicketEntry,
11
+ } from '@memberjunction/ai';
12
+ import {
13
+ RealtimeRelayTunnel,
14
+ RelayTicketPrefix,
15
+ REALTIME_RELAY_LIMITS,
16
+ REALTIME_RELAY_MAX_CLIENT_FRAME_BYTES,
17
+ type RealtimeRelayLimits,
18
+ type RealtimeRelayTunnelHost,
19
+ } from './RealtimeRelayTunnel.js';
20
+
21
+ /** The optional Origin allowlist both paths apply: comma-separated origins. */
22
+ const ALLOWED_ORIGINS_ENV = 'MJ_REALTIME_PROXY_ALLOWED_ORIGINS';
23
+
24
+ /** The longest part of a refused Origin header a log line quotes. */
25
+ const MAX_LOGGED_ORIGIN_CHARS = 200;
6
26
 
7
27
  /**
8
28
  * MJAPI's realtime websocket **proxy** — the transport half of the self-hosted realtime provider story.
@@ -18,6 +38,11 @@ import { RealtimeProxyRegistry, REALTIME_PROXY_PATH, type RealtimeProxyTicketEnt
18
38
  * wire vocabulary) and knows nothing about any specific provider — it is a pure authenticated byte tunnel,
19
39
  * reusable by any future self-hosted realtime provider that mints a ticket.
20
40
  *
41
+ * It also serves the realtime **relay** at `{REALTIME_RELAY_PATH}/<ticket>/…` (any suffix: a provider's
42
+ * browser SDK appends its own path): one ticket per realtime session, a frame policy from the provider driver
43
+ * between the legs (see {@link RealtimeRelayTunnel}), one live upstream per session (a resume replaces the
44
+ * older connection). The Origin allowlist applies to both paths.
45
+ *
21
46
  * A {@link BaseSingleton} + {@link IShutdownable}: it tracks live tunnels and closes them on graceful
22
47
  * shutdown (drained by MJServer's `ShutdownRegistry` before `httpServer.close()`).
23
48
  */
@@ -27,9 +52,23 @@ export class RealtimeProxyServer extends BaseSingleton<RealtimeProxyServer> impl
27
52
  /** `noServer` so THIS server never binds its own upgrade listener — MJServer routes upgrades to it by path. */
28
53
  private readonly wss = new WebSocketServer({ noServer: true });
29
54
 
55
+ /** The relay's upgrade server: the same routing, plus the client frame cap, enforced while a frame is read. */
56
+ private readonly relayWss = new WebSocketServer({ noServer: true, maxPayload: REALTIME_RELAY_MAX_CLIENT_FRAME_BYTES });
57
+
30
58
  /** Live browser↔upstream tunnels, tracked for shutdown teardown. */
31
59
  private readonly tunnels = new Set<RealtimeProxyTunnel>();
32
60
 
61
+ /** Live relay connections, tracked for shutdown teardown. */
62
+ private readonly relays = new Set<RealtimeRelayTunnel>();
63
+
64
+ /** The granted connection each relay session uses now: one live upstream per session. */
65
+ private readonly liveRelays = new Map<string, RealtimeRelayTunnel>();
66
+
67
+ private readonly relayHost: RealtimeRelayTunnelHost = {
68
+ OnGranted: (tunnel) => this.relayGranted(tunnel),
69
+ OnClosed: (tunnel) => this.relayClosed(tunnel),
70
+ };
71
+
33
72
  private registered = false;
34
73
 
35
74
  protected constructor() {
@@ -41,32 +80,107 @@ export class RealtimeProxyServer extends BaseSingleton<RealtimeProxyServer> impl
41
80
  return super.getInstance<RealtimeProxyServer>();
42
81
  }
43
82
 
83
+ /** The relay's deadlines, ping interval and pre-open buffer ({@link REALTIME_RELAY_LIMITS}); a subclass may tune them. */
84
+ protected get RelayLimits(): RealtimeRelayLimits {
85
+ return REALTIME_RELAY_LIMITS;
86
+ }
87
+
44
88
  /**
45
- * Routes an HTTP `upgrade` for {@link REALTIME_PROXY_PATH} to the proxy. Returns `true` when it OWNS
46
- * (handled/rejected) the request, `false` when the path is not the proxy's — so the caller leaves the
47
- * socket for the GraphQL websocket server's own upgrade listener. NEVER destroys a socket it doesn't own.
89
+ * Routes an HTTP `upgrade` for {@link REALTIME_PROXY_PATH} (ticket in the query) or {@link REALTIME_RELAY_PATH}
90
+ * (ticket in the path, any suffix). Returns `true` when it OWNS (handled/rejected) the request, `false` when
91
+ * the path is neither — so the caller leaves the socket for the GraphQL websocket server's own upgrade
92
+ * listener. NEVER destroys a socket it doesn't own.
48
93
  */
49
94
  public TryHandleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): boolean {
50
95
  const url = RealtimeProxyServer.parseUrl(request.url);
51
- if (!url || url.pathname !== REALTIME_PROXY_PATH) {
52
- return false; // not ours — leave it for the GraphQL WS server
96
+ if (url?.pathname === REALTIME_PROXY_PATH) {
97
+ this.handleProxyUpgrade(request, socket, head, url);
98
+ return true;
53
99
  }
100
+ if (url && RealtimeProxyServer.isRelayPath(url.pathname)) {
101
+ this.handleRelayUpgrade(request, socket, head, url.pathname);
102
+ return true;
103
+ }
104
+ return false; // not ours — leave it for the GraphQL WS server
105
+ }
106
+
107
+ /** `/realtime-proxy?ticket=<id>`: consumes the single-use ticket and opens a byte tunnel. */
108
+ private handleProxyUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer, url: URL): void {
54
109
  this.ensureRegistered();
55
110
  // Optional Origin allowlist (MJ_REALTIME_PROXY_ALLOWED_ORIGINS: comma-separated origins).
56
111
  // Unset ⇒ prior behavior (the single-use short-TTL ticket remains the primary guard);
57
112
  // set ⇒ a browser page on a foreign origin cannot ride a leaked ticket id.
58
113
  if (!RealtimeProxyServer.originAllowed(request.headers?.origin)) {
59
- RealtimeProxyServer.rejectUpgrade(socket, 403, 'Forbidden');
60
- return true;
114
+ RealtimeProxyServer.refuseOrigin(socket, '[RealtimeProxy]', request.headers?.origin);
115
+ return;
61
116
  }
62
117
  const ticketId = url.searchParams.get('ticket') ?? '';
63
118
  const entry = RealtimeProxyRegistry.Instance.Consume(ticketId);
64
119
  if (!entry) {
65
120
  RealtimeProxyServer.rejectUpgrade(socket, 401, 'Unauthorized');
66
- return true;
121
+ return;
67
122
  }
68
123
  this.wss.handleUpgrade(request, socket, head, (browserWs) => this.openTunnel(browserWs, entry));
69
- return true;
124
+ }
125
+
126
+ /**
127
+ * `/realtime/relay/<ticket>/…`: the same Origin allowlist (403, logged with the origin), then 401 unless the relay
128
+ * session can still take a connection. Whether it opens fresh or resumes is decided on the connection's first frame.
129
+ * A query string is ignored: the ticket is read from the path only.
130
+ */
131
+ private handleRelayUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer, pathname: string): void {
132
+ this.ensureRegistered();
133
+ if (!RealtimeProxyServer.originAllowed(request.headers?.origin)) {
134
+ RealtimeProxyServer.refuseOrigin(socket, '[RealtimeRelay]', request.headers?.origin);
135
+ return;
136
+ }
137
+ const sessionId = RealtimeProxyServer.relayTicketFrom(pathname);
138
+ const session = sessionId ? RealtimeProxyRegistry.Instance.FindRelaySession(sessionId) : null;
139
+ if (!session) {
140
+ console.warn(`[RealtimeRelay] relay ${RelayTicketPrefix(sessionId)}: upgrade refused (unknown, ended or used up)`);
141
+ RealtimeProxyServer.rejectUpgrade(socket, 401, 'Unauthorized');
142
+ return;
143
+ }
144
+ this.relayWss.handleUpgrade(request, socket, head, (browserWs) => this.openRelay(browserWs, sessionId, session.Policy));
145
+ }
146
+
147
+ /** Whether a path is the relay's: `/realtime/relay` itself or anything below it. */
148
+ private static isRelayPath(pathname: string): boolean {
149
+ return pathname === REALTIME_RELAY_PATH || pathname.startsWith(`${REALTIME_RELAY_PATH}/`);
150
+ }
151
+
152
+ /** The ticket id in a relay path: the segment right after `/realtime/relay/`; whatever follows is the SDK's. */
153
+ private static relayTicketFrom(pathname: string): string {
154
+ const segment = pathname.slice(REALTIME_RELAY_PATH.length + 1).split('/')[0] ?? '';
155
+ try {
156
+ return decodeURIComponent(segment);
157
+ } catch {
158
+ return ''; // malformed escape — no ticket
159
+ }
160
+ }
161
+
162
+ /** Starts one relay connection; the session's policy reads its first frame. */
163
+ private openRelay(browserWs: WsClient, sessionId: string, policy: IRealtimeRelayPolicy): void {
164
+ const tunnel = new RealtimeRelayTunnel(browserWs, sessionId, policy, this.RelayLimits, this.relayHost);
165
+ this.relays.add(tunnel);
166
+ tunnel.Start();
167
+ }
168
+
169
+ /** A relay connection was granted: it becomes the session's live one, and the older one closes. */
170
+ private relayGranted(tunnel: RealtimeRelayTunnel): void {
171
+ const previous = this.liveRelays.get(tunnel.SessionID);
172
+ this.liveRelays.set(tunnel.SessionID, tunnel);
173
+ if (previous && previous !== tunnel) {
174
+ previous.Close(1000, 'Replaced by a resumed connection');
175
+ }
176
+ }
177
+
178
+ /** A relay connection closed: forget it, and the session's live slot when it held it. */
179
+ private relayClosed(tunnel: RealtimeRelayTunnel): void {
180
+ this.relays.delete(tunnel);
181
+ if (this.liveRelays.get(tunnel.SessionID) === tunnel) {
182
+ this.liveRelays.delete(tunnel.SessionID);
183
+ }
70
184
  }
71
185
 
72
186
  /**
@@ -79,7 +193,7 @@ export class RealtimeProxyServer extends BaseSingleton<RealtimeProxyServer> impl
79
193
  * @returns True when allowed (or no allowlist is configured).
80
194
  */
81
195
  private static originAllowed(origin: string | undefined): boolean {
82
- const raw = process.env['MJ_REALTIME_PROXY_ALLOWED_ORIGINS'];
196
+ const raw = process.env[ALLOWED_ORIGINS_ENV];
83
197
  if (!raw || raw.trim().length === 0) {
84
198
  return true; // no allowlist configured — prior behavior
85
199
  }
@@ -106,16 +220,23 @@ export class RealtimeProxyServer extends BaseSingleton<RealtimeProxyServer> impl
106
220
  }
107
221
  }
108
222
 
109
- /** Closes every live tunnel and the proxy server. Idempotent; never throws. */
223
+ /** Closes every live tunnel and relay connection (1001), and both upgrade servers. Idempotent; never throws. */
110
224
  public Shutdown(): void {
111
225
  for (const tunnel of [...this.tunnels]) {
112
226
  tunnel.Close();
113
227
  }
114
228
  this.tunnels.clear();
115
- try {
116
- this.wss.close();
117
- } catch {
118
- /* already closing */
229
+ for (const relay of [...this.relays]) {
230
+ relay.Close(1001, 'Server shutting down');
231
+ }
232
+ this.relays.clear();
233
+ this.liveRelays.clear();
234
+ for (const server of [this.wss, this.relayWss]) {
235
+ try {
236
+ server.close();
237
+ } catch {
238
+ /* already closing */
239
+ }
119
240
  }
120
241
  }
121
242
 
@@ -131,6 +252,17 @@ export class RealtimeProxyServer extends BaseSingleton<RealtimeProxyServer> impl
131
252
  }
132
253
  }
133
254
 
255
+ /**
256
+ * Refuses an upgrade whose Origin is not on the allowlist: 403, and one log line naming the origin (printable
257
+ * characters only, shortened), so a browser page on an origin the allowlist lacks shows in MJAPI's log. The line
258
+ * never quotes the request's path or query: they carry the ticket.
259
+ */
260
+ private static refuseOrigin(socket: Duplex, logPrefix: string, origin: string | undefined): void {
261
+ const shown = (origin ?? '').replace(/[^\x20-\x7E]/g, '?').slice(0, MAX_LOGGED_ORIGIN_CHARS);
262
+ console.warn(`${logPrefix} upgrade refused (403): origin "${shown}" is not in ${ALLOWED_ORIGINS_ENV}`);
263
+ RealtimeProxyServer.rejectUpgrade(socket, 403, 'Forbidden');
264
+ }
265
+
134
266
  /** Writes a minimal HTTP error response and destroys the socket (used for a rejected upgrade). */
135
267
  private static rejectUpgrade(socket: Duplex, code: number, reason: string): void {
136
268
  try {