@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.
- package/README.md +30 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
- package/dist/agentSessions/BridgeHostInstance.js +30 -0
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
- package/dist/agentSessions/HostInstance.d.ts +75 -11
- package/dist/agentSessions/HostInstance.d.ts.map +1 -1
- package/dist/agentSessions/HostInstance.js +99 -28
- package/dist/agentSessions/HostInstance.js.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
- package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +173 -25
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +452 -54
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +43 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +99 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +2 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +10 -3
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +6 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/newUsers.d.ts +10 -0
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +80 -11
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/config.d.ts +4567 -71
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +246 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +25 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +9188 -3649
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +62 -6
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +179 -35
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +37 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -159
- package/dist/index.js.map +1 -1
- package/dist/isolatedProvider.d.ts +20 -0
- package/dist/isolatedProvider.d.ts.map +1 -0
- package/dist/isolatedProvider.js +22 -0
- package/dist/isolatedProvider.js.map +1 -0
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
- package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
- package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
- package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
- package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
- package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
- package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/realtimeSessions/index.d.ts +17 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -0
- package/dist/realtimeSessions/index.js +17 -0
- package/dist/realtimeSessions/index.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionAccess.js +90 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
- package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
- package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
- package/dist/realtimeSessions/verificationCore.d.ts +243 -0
- package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationCore.js +356 -0
- package/dist/realtimeSessions/verificationCore.js.map +1 -0
- package/dist/realtimeSessions/verificationPages.d.ts +66 -0
- package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPages.js +110 -0
- package/dist/realtimeSessions/verificationPages.js.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
- package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationPolicy.js +249 -0
- package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
- package/dist/realtimeSessions/verificationWorkflow.js +435 -0
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +92 -166
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
- package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
- package/dist/resolvers/CacheStatsResolver.js +113 -2
- package/dist/resolvers/CacheStatsResolver.js.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
- package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
- package/dist/resolvers/ClientToolRequestResolver.js +28 -10
- package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +9 -47
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +9 -0
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
- package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
- package/dist/resolvers/HumanHandoffResolver.js +330 -0
- package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +9 -4
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/MeetingResolver.d.ts +69 -0
- package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
- package/dist/resolvers/MeetingResolver.js +381 -0
- package/dist/resolvers/MeetingResolver.js.map +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
- package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
- package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +68 -11
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +6 -2
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
- package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
- package/dist/resolvers/UserAvatarResolver.js +124 -0
- package/dist/resolvers/UserAvatarResolver.js.map +1 -0
- package/dist/resolvers/UserResolver.d.ts.map +1 -1
- package/dist/resolvers/UserResolver.js +2 -17
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
- package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
- package/dist/resolvers/agentVisionConsentAudit.js +63 -0
- package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
- package/dist/resolvers/avatarInputValidation.d.ts +40 -0
- package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
- package/dist/resolvers/avatarInputValidation.js +97 -0
- package/dist/resolvers/avatarInputValidation.js.map +1 -0
- package/dist/resolvers/currentUserRoles.d.ts +29 -0
- package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
- package/dist/resolvers/currentUserRoles.js +30 -0
- package/dist/resolvers/currentUserRoles.js.map +1 -1
- package/dist/resolvers/meetings/index.d.ts +5 -0
- package/dist/resolvers/meetings/index.d.ts.map +1 -0
- package/dist/resolvers/meetings/index.js +5 -0
- package/dist/resolvers/meetings/index.js.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
- package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingHelpers.js +212 -0
- package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
- package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingMutations.js +439 -0
- package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
- package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingQueries.js +113 -0
- package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
- package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
- package/dist/resolvers/meetings/meetingTypes.js +345 -0
- package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +8 -5
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +8 -0
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -1
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +36 -0
- package/dist/services/WorkQueueHostService.d.ts.map +1 -0
- package/dist/services/WorkQueueHostService.js +97 -0
- package/dist/services/WorkQueueHostService.js.map +1 -0
- package/dist/services/workQueueConfig.d.ts +103 -0
- package/dist/services/workQueueConfig.d.ts.map +1 -0
- package/dist/services/workQueueConfig.js +48 -0
- package/dist/services/workQueueConfig.js.map +1 -0
- package/dist/sharedCache.d.ts +105 -0
- package/dist/sharedCache.d.ts.map +1 -0
- package/dist/sharedCache.js +276 -0
- package/dist/sharedCache.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +97 -94
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/BridgeHostInstance.test.ts +232 -0
- package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
- package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
- package/src/__tests__/HostInstance.test.ts +84 -0
- package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
- package/src/__tests__/MeetingResolver.test.ts +448 -0
- package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
- package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
- package/src/__tests__/RealtimeRelay.test.ts +876 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
- package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
- package/src/__tests__/SessionJanitor.test.ts +845 -18
- package/src/__tests__/SessionManager.test.ts +213 -17
- package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
- package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
- package/src/__tests__/UserAvatarResolver.test.ts +255 -0
- package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
- package/src/__tests__/WorkQueueHostService.test.ts +133 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
- package/src/__tests__/avatarInputValidation.test.ts +160 -0
- package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
- package/src/__tests__/magicLinkService.consume.test.ts +6 -0
- package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
- package/src/__tests__/newUsers.template.test.ts +151 -0
- package/src/__tests__/newUsers.test.ts +74 -5
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
- package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
- package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
- package/src/__tests__/realtimeSessions/helpers.ts +4 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
- package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
- package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
- package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
- package/src/__tests__/resolverBase.fls.test.ts +87 -4
- package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
- package/src/__tests__/sharedCache.test.ts +310 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/__tests__/widgetGuestElevation.test.ts +44 -0
- package/src/agentSessions/BridgeHostInstance.ts +32 -0
- package/src/agentSessions/HostInstance.ts +124 -28
- package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
- package/src/agentSessions/SessionJanitor.ts +543 -57
- package/src/agentSessions/SessionManager.ts +125 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
- package/src/agentSessions/index.ts +10 -3
- package/src/auth/actionAuthorization.ts +116 -0
- package/src/auth/index.ts +3 -8
- package/src/auth/magicLink/MagicLinkService.ts +6 -3
- package/src/auth/newUsers.ts +99 -11
- package/src/config.ts +255 -3
- package/src/context.ts +26 -34
- package/src/generated/graphql-schemas/__mj.ts +22701 -18838
- package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
- package/src/generic/ResolverBase.ts +210 -36
- package/src/generic/RunViewResolver.ts +37 -2
- package/src/index.ts +236 -170
- package/src/isolatedProvider.ts +22 -0
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
- package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
- package/src/realtimeSessions/README.md +179 -0
- package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
- package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
- package/src/realtimeSessions/index.ts +16 -0
- package/src/realtimeSessions/sessionAccess.ts +141 -0
- package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
- package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
- package/src/realtimeSessions/verificationCore.ts +502 -0
- package/src/realtimeSessions/verificationPages.ts +149 -0
- package/src/realtimeSessions/verificationPolicy.ts +321 -0
- package/src/realtimeSessions/verificationWorkflow.ts +702 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
- package/src/resolvers/ActionResolver.ts +84 -21
- package/src/resolvers/AdhocQueryResolver.ts +105 -187
- package/src/resolvers/CacheStatsResolver.ts +89 -2
- package/src/resolvers/ClientToolRequestResolver.ts +25 -7
- package/src/resolvers/EntityRecordNameResolver.ts +9 -51
- package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
- package/src/resolvers/GetDataResolver.ts +10 -0
- package/src/resolvers/HumanHandoffResolver.ts +312 -0
- package/src/resolvers/MCPResolver.ts +8 -5
- package/src/resolvers/MeetingResolver.ts +438 -0
- package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
- package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
- package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
- package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
- package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
- package/src/resolvers/RunAIAgentResolver.ts +78 -11
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/SyncDataResolver.ts +6 -2
- package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
- package/src/resolvers/UserAvatarResolver.ts +111 -0
- package/src/resolvers/UserResolver.ts +2 -16
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/resolvers/agentVisionConsentAudit.ts +82 -0
- package/src/resolvers/avatarInputValidation.ts +123 -0
- package/src/resolvers/currentUserRoles.ts +32 -1
- package/src/resolvers/meetings/index.ts +4 -0
- package/src/resolvers/meetings/meetingHelpers.ts +307 -0
- package/src/resolvers/meetings/meetingMutations.ts +556 -0
- package/src/resolvers/meetings/meetingQueries.ts +172 -0
- package/src/resolvers/meetings/meetingTypes.ts +238 -0
- package/src/rest/ViewOperationsHandler.ts +8 -5
- package/src/services/TaskGraphActionRunner.ts +29 -1
- package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
- package/src/services/WorkQueueHostService.ts +129 -0
- package/src/services/workQueueConfig.ts +52 -0
- package/src/sharedCache.ts +302 -0
- package/src/types.ts +12 -0
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -50
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -67
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { hostname } from 'os';
|
|
3
|
+
import type { IMetadataProvider, RunViewParams, UserInfo } from '@memberjunction/core';
|
|
4
|
+
import type { IRealtimeSession, RealtimeInputFrame } from '@memberjunction/ai';
|
|
5
|
+
import type { MJAIAgentSessionBridgeEntity, MJAIBridgeProviderEntity } from '@memberjunction/core-entities';
|
|
6
|
+
import { AIBridgeEngine, LOOPBACK_BRIDGE_DRIVER_CLASS } from '@memberjunction/ai-bridge-server';
|
|
7
|
+
import { BindBridgeEngineHostInstance, StartBridgeOrphanReconciliation } from '../agentSessions/BridgeHostInstance.js';
|
|
8
|
+
import { GetBootID, GetCurrentHostInstance, HostInstanceIdentity, SetHostInstancePort } from '../agentSessions/HostInstance.js';
|
|
9
|
+
|
|
10
|
+
/** The port this test process "serves" on, set the way `Serve` sets it, before anything reads the identity. */
|
|
11
|
+
const OUR_PORT = 4000;
|
|
12
|
+
/** The port of a second MJAPI on the same host. */
|
|
13
|
+
const OTHER_PORT = 4100;
|
|
14
|
+
SetHostInstancePort(OUR_PORT);
|
|
15
|
+
|
|
16
|
+
const user = { ID: 'system-user', Email: 'system@example.com' } as unknown as UserInfo;
|
|
17
|
+
|
|
18
|
+
/** A bridge row as the engine reads and writes it. */
|
|
19
|
+
interface BridgeRow {
|
|
20
|
+
ID: string;
|
|
21
|
+
Status: MJAIAgentSessionBridgeEntity['Status'];
|
|
22
|
+
HostInstanceID: string | null;
|
|
23
|
+
AgentSessionID: string;
|
|
24
|
+
CloseReason?: MJAIAgentSessionBridgeEntity['CloseReason'];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A `LIKE` pattern as a regular expression: `%` any run of characters, `_` any one character. */
|
|
28
|
+
function likePattern(pattern: string): RegExp {
|
|
29
|
+
const body = [...pattern]
|
|
30
|
+
.map((c) => (c === '%' ? '.*' : c === '_' ? '.' : c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
|
|
31
|
+
.join('');
|
|
32
|
+
return new RegExp(`^${body}$`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A provider over a bridge table, answering the engine the way the database would: the orphan reconcile's read
|
|
37
|
+
* returns the open rows whose `HostInstanceID` matches the filter's `LIKE` pattern and isn't the id it excludes, and a
|
|
38
|
+
* row the engine loads and saves is written back. `row(id)` reads a row's current state.
|
|
39
|
+
*/
|
|
40
|
+
function bridgeTable(rows: BridgeRow[]): {
|
|
41
|
+
provider: IMetadataProvider;
|
|
42
|
+
row: (id: string) => BridgeRow;
|
|
43
|
+
runView: ReturnType<typeof vi.fn>;
|
|
44
|
+
} {
|
|
45
|
+
const table = new Map(rows.map((r) => [r.ID, { ...r }]));
|
|
46
|
+
const runView = vi.fn(async (params: RunViewParams) => {
|
|
47
|
+
if (params.EntityName !== 'MJ: AI Agent Session Bridges') {
|
|
48
|
+
return { Success: true, Results: [] };
|
|
49
|
+
}
|
|
50
|
+
const filter = typeof params.ExtraFilter === 'string' ? params.ExtraFilter : '';
|
|
51
|
+
const like = likePattern(/HostInstanceID LIKE '(.*?)'/.exec(filter)?.[1] ?? '');
|
|
52
|
+
const excluded = /HostInstanceID <> '(.*?)'/.exec(filter)?.[1];
|
|
53
|
+
const results = [...table.values()]
|
|
54
|
+
.filter((r) => r.Status === 'Connecting' || r.Status === 'Connected')
|
|
55
|
+
.filter((r) => r.HostInstanceID != null && like.test(r.HostInstanceID) && r.HostInstanceID !== excluded)
|
|
56
|
+
.map((r) => ({ ...r }));
|
|
57
|
+
return { Success: true, Results: results };
|
|
58
|
+
});
|
|
59
|
+
const provider = {
|
|
60
|
+
RunView: runView,
|
|
61
|
+
GetEntityObject: vi.fn(async () => {
|
|
62
|
+
const entity: BridgeRow & { LatestResult: { CompleteMessage: string }; Load: (id: string) => Promise<boolean>; Save: () => Promise<boolean> } = {
|
|
63
|
+
ID: '',
|
|
64
|
+
Status: 'Pending',
|
|
65
|
+
HostInstanceID: null,
|
|
66
|
+
AgentSessionID: '',
|
|
67
|
+
CloseReason: null,
|
|
68
|
+
LatestResult: { CompleteMessage: '' },
|
|
69
|
+
Load: async (id: string) => {
|
|
70
|
+
const stored = table.get(id);
|
|
71
|
+
if (!stored) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
Object.assign(entity, stored);
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
Save: async () => {
|
|
78
|
+
const stored = table.get(entity.ID);
|
|
79
|
+
if (stored) {
|
|
80
|
+
stored.Status = entity.Status;
|
|
81
|
+
stored.CloseReason = entity.CloseReason;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
return entity;
|
|
87
|
+
}),
|
|
88
|
+
} as unknown as IMetadataProvider;
|
|
89
|
+
return { provider, row: (id: string) => table.get(id) as BridgeRow, runView };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A realtime model session that says nothing: a bridge only needs one to connect. */
|
|
93
|
+
class SilentSession implements IRealtimeSession {
|
|
94
|
+
public SendInput(_frame: RealtimeInputFrame): void {}
|
|
95
|
+
public async RegisterTools(): Promise<void> {}
|
|
96
|
+
public OnOutput(): void {}
|
|
97
|
+
public OnTranscript(): void {}
|
|
98
|
+
public OnToolCall(): void {}
|
|
99
|
+
public async SendToolResult(): Promise<void> {}
|
|
100
|
+
public OnInterruption(): void {}
|
|
101
|
+
public OnError(): void {}
|
|
102
|
+
public OnUsage(): void {}
|
|
103
|
+
public async Close(): Promise<void> {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The loopback bridge provider: a bridge with no platform behind it. */
|
|
107
|
+
const loopbackProvider = {
|
|
108
|
+
ID: 'provider-loopback',
|
|
109
|
+
Name: 'Loopback',
|
|
110
|
+
DriverClass: LOOPBACK_BRIDGE_DRIVER_CLASS,
|
|
111
|
+
SupportedFeaturesObject: { AudioIn: true, AudioOut: true },
|
|
112
|
+
} as unknown as MJAIBridgeProviderEntity;
|
|
113
|
+
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
AIBridgeEngine.Instance.StopOrphanReconciliation();
|
|
116
|
+
BindBridgeEngineHostInstance();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('BindBridgeEngineHostInstance', () => {
|
|
120
|
+
it("makes the engine stamp this MJAPI instance's identity (hostname:port:pid:bootId) on the bridges it creates", async () => {
|
|
121
|
+
BindBridgeEngineHostInstance();
|
|
122
|
+
const row = {
|
|
123
|
+
ID: 'bridge-new',
|
|
124
|
+
Status: 'Pending',
|
|
125
|
+
HostInstanceID: null as string | null,
|
|
126
|
+
NewRecord: vi.fn(),
|
|
127
|
+
Save: vi.fn(async () => true),
|
|
128
|
+
Load: vi.fn(async () => true),
|
|
129
|
+
LatestResult: { CompleteMessage: '' },
|
|
130
|
+
};
|
|
131
|
+
const provider = {
|
|
132
|
+
GetEntityObject: vi.fn(async () => row),
|
|
133
|
+
RunView: vi.fn(async () => ({ Success: true, Results: [] })),
|
|
134
|
+
} as unknown as IMetadataProvider;
|
|
135
|
+
|
|
136
|
+
const active = await AIBridgeEngine.Instance.StartBridgeSession({
|
|
137
|
+
AgentSessionID: 'session-1',
|
|
138
|
+
Provider: loopbackProvider,
|
|
139
|
+
RealtimeSession: new SilentSession(),
|
|
140
|
+
Address: 'loopback://room',
|
|
141
|
+
ContextUser: user,
|
|
142
|
+
MetadataProvider: provider,
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
expect(row.HostInstanceID).toBe(GetCurrentHostInstance().GetHostInstanceID());
|
|
146
|
+
expect(row.HostInstanceID).toBe(`${hostname()}:${OUR_PORT}:${process.pid}:${GetBootID()}`);
|
|
147
|
+
} finally {
|
|
148
|
+
await AIBridgeEngine.Instance.StopBridgeSession(active.SessionBridgeID, 'Explicit', user, provider);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("the bridge engine's orphan reconcile with this MJAPI's identity (#5310)", () => {
|
|
154
|
+
const ours = GetCurrentHostInstance();
|
|
155
|
+
const theirs = new HostInstanceIdentity(ours.HostName, String(OTHER_PORT), 40404, 'boot-theirs');
|
|
156
|
+
|
|
157
|
+
/** Both MJAPIs' live bridges, and one each left by an earlier boot. */
|
|
158
|
+
function sharedTable(): BridgeRow[] {
|
|
159
|
+
return [
|
|
160
|
+
{ ID: 'ours-live', Status: 'Connected', HostInstanceID: ours.GetHostInstanceID(), AgentSessionID: 'session-ours-live' },
|
|
161
|
+
{ ID: 'ours-stale', Status: 'Connected', HostInstanceID: `${ours.GetInstancePrefix()}31337:boot-ours-before`, AgentSessionID: 'session-ours-stale' },
|
|
162
|
+
{ ID: 'theirs-live', Status: 'Connected', HostInstanceID: theirs.GetHostInstanceID(), AgentSessionID: 'session-theirs-live' },
|
|
163
|
+
{ ID: 'theirs-stale', Status: 'Connecting', HostInstanceID: `${theirs.GetInstancePrefix()}27182:boot-theirs-before`, AgentSessionID: 'session-theirs-stale' },
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
it('closes the bridges an earlier boot of this instance left, and nothing else (one MJAPI, as before)', async () => {
|
|
168
|
+
BindBridgeEngineHostInstance();
|
|
169
|
+
const { provider, row } = bridgeTable([
|
|
170
|
+
{ ID: 'before-restart', Status: 'Connected', HostInstanceID: `${ours.GetInstancePrefix()}31337:boot-before`, AgentSessionID: 'session-1' },
|
|
171
|
+
// The OS gave the restarted process the same pid; the boot id still differs.
|
|
172
|
+
{ ID: 'before-restart-same-pid', Status: 'Connecting', HostInstanceID: `${ours.GetInstancePrefix()}${ours.ProcessID}:boot-before`, AgentSessionID: 'session-2' },
|
|
173
|
+
{ ID: 'this-boot', Status: 'Connected', HostInstanceID: ours.GetHostInstanceID(), AgentSessionID: 'session-3' },
|
|
174
|
+
{ ID: 'other-host', Status: 'Connected', HostInstanceID: `other-host:${OUR_PORT}:31337:boot-other`, AgentSessionID: 'session-4' },
|
|
175
|
+
// Stamped before MJServer gave the engine its identity; no instance owns it.
|
|
176
|
+
{ ID: 'fallback-stamped', Status: 'Connected', HostInstanceID: `unknown-host:31337:boot-old`, AgentSessionID: 'session-5' },
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
const closed = await AIBridgeEngine.Instance.ReconcileOrphans(user, provider);
|
|
180
|
+
|
|
181
|
+
expect(closed).toBe(2);
|
|
182
|
+
expect(row('before-restart')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
|
|
183
|
+
expect(row('before-restart-same-pid')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
|
|
184
|
+
expect(row('this-boot').Status).toBe('Connected');
|
|
185
|
+
expect(row('other-host').Status).toBe('Connected');
|
|
186
|
+
expect(row('fallback-stamped').Status).toBe('Connected');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("closes its own stale bridges and leaves the other MJAPI's live bridges open", async () => {
|
|
190
|
+
BindBridgeEngineHostInstance();
|
|
191
|
+
const { provider, row } = bridgeTable(sharedTable());
|
|
192
|
+
|
|
193
|
+
const closed = await AIBridgeEngine.Instance.ReconcileOrphans(user, provider);
|
|
194
|
+
|
|
195
|
+
expect(closed).toBe(1);
|
|
196
|
+
expect(row('ours-stale')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
|
|
197
|
+
expect(row('ours-live').Status).toBe('Connected');
|
|
198
|
+
expect(row('theirs-live').Status).toBe('Connected');
|
|
199
|
+
expect(row('theirs-stale').Status).toBe('Connecting');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("the other MJAPI's reconcile closes its stale bridges and leaves this one's live bridges open", async () => {
|
|
203
|
+
// What the other MJAPI's own BindBridgeEngineHostInstance gives its engine.
|
|
204
|
+
AIBridgeEngine.Instance.SetHostInstanceIdentity(theirs);
|
|
205
|
+
const { provider, row } = bridgeTable(sharedTable());
|
|
206
|
+
|
|
207
|
+
const closed = await AIBridgeEngine.Instance.ReconcileOrphans(user, provider);
|
|
208
|
+
|
|
209
|
+
expect(closed).toBe(1);
|
|
210
|
+
expect(row('theirs-stale')).toMatchObject({ Status: 'Disconnected', CloseReason: 'Janitor' });
|
|
211
|
+
expect(row('theirs-live').Status).toBe('Connected');
|
|
212
|
+
expect(row('ours-live').Status).toBe('Connected');
|
|
213
|
+
expect(row('ours-stale').Status).toBe('Connected');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('StartBridgeOrphanReconciliation', () => {
|
|
218
|
+
it("reconciles at once, with this instance's identity, as the given user", async () => {
|
|
219
|
+
BindBridgeEngineHostInstance();
|
|
220
|
+
const ours = GetCurrentHostInstance();
|
|
221
|
+
const { provider, row, runView } = bridgeTable([
|
|
222
|
+
{ ID: 'ours-stale', Status: 'Connected', HostInstanceID: `${ours.GetInstancePrefix()}31337:boot-before`, AgentSessionID: 'session-1' },
|
|
223
|
+
{ ID: 'theirs-live', Status: 'Connected', HostInstanceID: `${ours.HostName}:${OTHER_PORT}:40404:boot-theirs`, AgentSessionID: 'session-2' },
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
StartBridgeOrphanReconciliation(provider, user);
|
|
227
|
+
|
|
228
|
+
await vi.waitFor(() => expect(row('ours-stale').Status).toBe('Disconnected'));
|
|
229
|
+
expect(row('theirs-live').Status).toBe('Connected');
|
|
230
|
+
expect(runView.mock.calls[0][1]).toBe(user);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { ClientToolResponse } from '@memberjunction/ai-core-plus';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* RespondToClientToolRequest is the seam between a browser's tool answer and the waiting agent run:
|
|
6
|
+
* the resolver checks the media with the real ParseClientToolMedia and hands the answer to
|
|
7
|
+
* ClientToolRequestManager.ReceiveResponse, which this suite replaces to see what the run receives.
|
|
8
|
+
*/
|
|
9
|
+
const h = vi.hoisted(() => ({
|
|
10
|
+
receiveResponse: vi.fn((_response: ClientToolResponse): boolean => true),
|
|
11
|
+
logError: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// The resolver's GraphQL decorators read TypeScript type metadata that the test transform does not
|
|
15
|
+
// emit, so they become no-ops; the suite calls the resolver method directly.
|
|
16
|
+
vi.mock('type-graphql', () => {
|
|
17
|
+
const noopDecorator = () => () => undefined;
|
|
18
|
+
const decorators = [
|
|
19
|
+
'Resolver', 'Query', 'Mutation', 'Subscription', 'Arg', 'Args', 'ArgsType', 'Ctx', 'Root', 'Info', 'Field',
|
|
20
|
+
'FieldResolver', 'ObjectType', 'InputType', 'InterfaceType', 'Authorized', 'UseMiddleware', 'Extensions',
|
|
21
|
+
'Directive', 'PubSub',
|
|
22
|
+
];
|
|
23
|
+
return {
|
|
24
|
+
...Object.fromEntries(decorators.map((name) => [name, noopDecorator])),
|
|
25
|
+
Float: class {},
|
|
26
|
+
Int: class {},
|
|
27
|
+
ID: class {},
|
|
28
|
+
PubSubEngine: class {},
|
|
29
|
+
AuthorizationError: class AuthorizationError extends Error {},
|
|
30
|
+
registerEnumType: () => undefined,
|
|
31
|
+
createUnionType: () => class {},
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Replace the agent package so the resolver test does not load the @memberjunction/ai-agents graph.
|
|
36
|
+
vi.mock('@memberjunction/ai-agents', () => ({
|
|
37
|
+
ClientToolRequestManager: { Instance: { ReceiveResponse: h.receiveResponse } },
|
|
38
|
+
CLIENT_TOOL_REQUEST_TOPIC: 'CLIENT_TOOL_REQUEST',
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// Keep the resolver's error log out of the test output, and observable.
|
|
42
|
+
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
43
|
+
const actual = await importOriginal<Record<string, unknown>>();
|
|
44
|
+
return { ...actual, LogError: h.logError, LogStatus: vi.fn() };
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
import { ClientToolRequestResolver } from '../resolvers/ClientToolRequestResolver.js';
|
|
48
|
+
|
|
49
|
+
/** Base64 content of 3 bytes ("ABC"). */
|
|
50
|
+
const SMALL_BASE64 = 'QUJD';
|
|
51
|
+
/** Base64 content that decodes to 2,000,004 bytes, over the 2,000,000-byte limit. */
|
|
52
|
+
const OVERSIZED_BASE64 = 'A'.repeat(2_666_672);
|
|
53
|
+
|
|
54
|
+
/** Sends one answer through the mutation, as the browser does. */
|
|
55
|
+
function respond(media: string | undefined, result: string | undefined = '{"ok":true}'): Promise<boolean> {
|
|
56
|
+
return new ClientToolRequestResolver().RespondToClientToolRequest('req-1', true, result, undefined, media);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The response the waiting run received. */
|
|
60
|
+
function received(): ClientToolResponse {
|
|
61
|
+
expect(h.receiveResponse).toHaveBeenCalledTimes(1);
|
|
62
|
+
return h.receiveResponse.mock.calls[0][0];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
h.receiveResponse.mockReset();
|
|
67
|
+
h.receiveResponse.mockReturnValue(true);
|
|
68
|
+
h.logError.mockReset();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('ClientToolRequestResolver.RespondToClientToolRequest', () => {
|
|
72
|
+
it('hands valid media to the waiting run with the result', async () => {
|
|
73
|
+
const media = JSON.stringify([{ MimeType: 'image/jpeg', Base64: SMALL_BASE64, Width: 1280, Height: 720 }]);
|
|
74
|
+
|
|
75
|
+
await expect(respond(media)).resolves.toBe(true);
|
|
76
|
+
|
|
77
|
+
expect(received()).toEqual({
|
|
78
|
+
RequestID: 'req-1',
|
|
79
|
+
Success: true,
|
|
80
|
+
Result: { ok: true },
|
|
81
|
+
ErrorMessage: undefined,
|
|
82
|
+
Media: [{ MimeType: 'image/jpeg', Base64: SMALL_BASE64, Width: 1280, Height: 720 }],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('hands no Media to the run when the answer carries none', async () => {
|
|
87
|
+
await respond(undefined);
|
|
88
|
+
|
|
89
|
+
expect(received()).toEqual({ RequestID: 'req-1', Success: true, Result: { ok: true }, ErrorMessage: undefined, Media: undefined });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it.each([
|
|
93
|
+
['a MIME type that is not allowed', JSON.stringify([{ MimeType: 'image/gif', Base64: SMALL_BASE64 }]), 'media MIME type "image/gif" is not allowed'],
|
|
94
|
+
['an item over the size limit', JSON.stringify([{ MimeType: 'image/png', Base64: OVERSIZED_BASE64 }]), 'media item is 2000004 bytes; the limit is 2000000'],
|
|
95
|
+
[
|
|
96
|
+
'more than 4 items',
|
|
97
|
+
JSON.stringify(Array.from({ length: 5 }, () => ({ MimeType: 'image/jpeg', Base64: SMALL_BASE64 }))),
|
|
98
|
+
'media holds 5 items; the limit is 4',
|
|
99
|
+
],
|
|
100
|
+
])('gives the run a failed result for %s, without the result or the media', async (_case, media, reason) => {
|
|
101
|
+
await respond(media);
|
|
102
|
+
|
|
103
|
+
expect(received()).toEqual({ RequestID: 'req-1', Success: false, ErrorMessage: `Client tool media rejected: ${reason}` });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('gives the run a failed result and returns false when the result is not valid JSON', async () => {
|
|
107
|
+
await expect(respond(undefined, '{not json')).resolves.toBe(false);
|
|
108
|
+
|
|
109
|
+
expect(received()).toEqual({
|
|
110
|
+
RequestID: 'req-1',
|
|
111
|
+
Success: false,
|
|
112
|
+
ErrorMessage: expect.stringMatching(/^Client tool response rejected: /),
|
|
113
|
+
});
|
|
114
|
+
expect(h.logError).toHaveBeenCalledWith(expect.stringContaining('RespondToClientToolRequest error:'));
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -7,43 +7,22 @@ import type { DatabaseProviderBase, EntityInfo, UserInfo } from '@memberjunction
|
|
|
7
7
|
import { EntityRecordNameResolver } from '../resolvers/EntityRecordNameResolver.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* `GetEntityRecordName`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* open side channel: the name never travelled through `MapFieldNamesToCodeNames`, the boundary
|
|
14
|
-
* that strips denied fields, so a caller denied READ on an entity's name field could still obtain
|
|
15
|
-
* that name from here.
|
|
16
|
-
*
|
|
17
|
-
* FLS turned the latent gap into a live bypass, and the shape of it is worth remembering: the
|
|
18
|
-
* form-field FK control falls through to this query PRECISELY WHEN the joined display column is
|
|
19
|
-
* denied. The fallback that exists to handle a denial was the thing that defeated it. Found in
|
|
20
|
-
* manual Phase 5 testing — a Contract form showed its Client's name while the grid, correctly,
|
|
21
|
-
* did not.
|
|
10
|
+
* `GetEntityRecordName` hands the acting user to the provider, which applies field- and row-level
|
|
11
|
+
* security for them, and reports a withheld name exactly as it reports a missing record. The
|
|
12
|
+
* field-level rule itself is covered by MJCore's DatabaseProviderBase record-name tests.
|
|
22
13
|
*/
|
|
23
14
|
|
|
24
|
-
const
|
|
15
|
+
const ACTING_USER = { ID: 'u-1', Email: 'restricted@test' } as unknown as UserInfo;
|
|
25
16
|
|
|
26
|
-
|
|
27
|
-
function entityInfo(opts: { fls: boolean; deniedNames?: string[]; nameField?: string }): EntityInfo {
|
|
28
|
-
const nameFieldName = opts.nameField ?? 'Name';
|
|
29
|
-
return {
|
|
30
|
-
Name: 'Clients',
|
|
31
|
-
EnableFieldLevelSecurity: opts.fls,
|
|
32
|
-
Fields: [
|
|
33
|
-
{ Name: 'ID', IsNameField: false },
|
|
34
|
-
{ Name: nameFieldName, IsNameField: true },
|
|
35
|
-
{ Name: 'Industry', IsNameField: false },
|
|
36
|
-
],
|
|
37
|
-
GetDeniedReadFields: () => new Set(opts.deniedNames ?? []),
|
|
38
|
-
} as unknown as EntityInfo;
|
|
39
|
-
}
|
|
17
|
+
const ENTITY = { Name: 'Clients' } as unknown as EntityInfo;
|
|
40
18
|
|
|
41
|
-
/** A provider
|
|
42
|
-
function
|
|
19
|
+
/** A provider whose lookup answers with `name` ('' when it withholds or finds nothing). */
|
|
20
|
+
function providerAnswering(name: string) {
|
|
21
|
+
const getName = vi.fn().mockResolvedValue(name);
|
|
43
22
|
return {
|
|
44
|
-
Entities: [
|
|
45
|
-
|
|
46
|
-
}
|
|
23
|
+
md: { Entities: [ENTITY], GetEntityRecordName: getName } as unknown as DatabaseProviderBase,
|
|
24
|
+
getName,
|
|
25
|
+
};
|
|
47
26
|
}
|
|
48
27
|
|
|
49
28
|
/** Exposes the inner method so the test does not need a GraphQL context. */
|
|
@@ -57,62 +36,38 @@ class Probe extends EntityRecordNameResolver {
|
|
|
57
36
|
|
|
58
37
|
const KEY = new CompositeKey([{ FieldName: 'ID', Value: 'c-1' }]);
|
|
59
38
|
|
|
60
|
-
describe('GetEntityRecordName —
|
|
61
|
-
it('
|
|
62
|
-
const getName =
|
|
63
|
-
const md = providerWith(entityInfo({ fls: true, deniedNames: ['name'] }), getName);
|
|
39
|
+
describe('GetEntityRecordName — security is applied for the acting user', () => {
|
|
40
|
+
it('passes the acting user to the provider lookup', async () => {
|
|
41
|
+
const { md, getName } = providerAnswering('Acme Manufacturing');
|
|
64
42
|
|
|
65
|
-
|
|
43
|
+
await new Probe().Inner(md, 'Clients', KEY, ACTING_USER);
|
|
66
44
|
|
|
67
|
-
expect(
|
|
68
|
-
expect(result.RecordName).toBeUndefined();
|
|
69
|
-
// Not merely filtered after the fact — the lookup must not run at all. The provider's
|
|
70
|
-
// record-name cache is keyed by entity + primary key and NOT by user, so a name fetched
|
|
71
|
-
// here would be visible to any later caller.
|
|
72
|
-
expect(getName).not.toHaveBeenCalled();
|
|
45
|
+
expect(getName).toHaveBeenCalledWith('Clients', expect.anything(), ACTING_USER);
|
|
73
46
|
});
|
|
74
47
|
|
|
75
|
-
it('returns the name
|
|
76
|
-
const md =
|
|
48
|
+
it('returns the name the provider allows', async () => {
|
|
49
|
+
const { md } = providerAnswering('Acme Manufacturing');
|
|
77
50
|
|
|
78
|
-
const result = await new Probe().Inner(md, 'Clients', KEY,
|
|
51
|
+
const result = await new Probe().Inner(md, 'Clients', KEY, ACTING_USER);
|
|
79
52
|
|
|
80
53
|
expect(result.Success).toBe(true);
|
|
81
54
|
expect(result.RecordName).toBe('Acme Manufacturing');
|
|
82
55
|
});
|
|
83
56
|
|
|
84
|
-
it('
|
|
85
|
-
|
|
86
|
-
const md = providerWith(entityInfo({ fls: false, deniedNames: ['name'] }));
|
|
57
|
+
it('reports a withheld name as unsuccessful, with no name', async () => {
|
|
58
|
+
const { md } = providerAnswering('');
|
|
87
59
|
|
|
88
|
-
const result = await new Probe().Inner(md, 'Clients', KEY,
|
|
89
|
-
|
|
90
|
-
expect(result.Success).toBe(true);
|
|
91
|
-
expect(result.RecordName).toBe('Acme Manufacturing');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('honours a designated name field that is not literally called "Name"', async () => {
|
|
95
|
-
const md = providerWith(entityInfo({ fls: true, nameField: 'Title', deniedNames: ['title'] }));
|
|
96
|
-
|
|
97
|
-
const result = await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
|
|
60
|
+
const result = await new Probe().Inner(md, 'Clients', KEY, ACTING_USER);
|
|
98
61
|
|
|
99
62
|
expect(result.Success).toBe(false);
|
|
63
|
+
expect(result.RecordName).toBeUndefined();
|
|
100
64
|
});
|
|
101
65
|
|
|
102
|
-
it('
|
|
103
|
-
const
|
|
104
|
-
const md = providerWith(entityInfo({ fls: true }), getName);
|
|
105
|
-
|
|
106
|
-
await new Probe().Inner(md, 'Clients', KEY, DENIED_USER);
|
|
66
|
+
it('does not disclose which of "missing" or "restricted" applies', async () => {
|
|
67
|
+
const { md } = providerAnswering('');
|
|
107
68
|
|
|
108
|
-
|
|
109
|
-
});
|
|
69
|
+
const result = await new Probe().Inner(md, 'Clients', KEY, ACTING_USER);
|
|
110
70
|
|
|
111
|
-
|
|
112
|
-
// The denial status must read the same as a genuine not-found, so the query cannot be
|
|
113
|
-
// used to probe for which entities carry restricted name fields.
|
|
114
|
-
const denied = await new Probe().Inner(
|
|
115
|
-
providerWith(entityInfo({ fls: true, deniedNames: ['name'] })), 'Clients', KEY, DENIED_USER);
|
|
116
|
-
expect(denied.Status).not.toMatch(/denied|permission|restricted|field.security/i);
|
|
71
|
+
expect(result.Status).not.toMatch(/denied|permission|restricted|field.security|RLS/i);
|
|
117
72
|
});
|
|
118
73
|
});
|