@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
- package/dist/agentSessions/BridgeHostInstance.js +14 -4
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
- package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
- package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
- package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
- package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +31 -8
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +85 -41
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +14 -4
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +28 -4
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
- package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
- package/dist/agentSessions/conversationBrowsers.js +118 -0
- package/dist/agentSessions/conversationBrowsers.js.map +1 -0
- package/dist/agentSessions/sessionDeadline.d.ts +43 -0
- package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
- package/dist/agentSessions/sessionDeadline.js +68 -0
- package/dist/agentSessions/sessionDeadline.js.map +1 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +5 -4
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/loginEmailResolution.d.ts +41 -0
- package/dist/auth/loginEmailResolution.d.ts.map +1 -0
- package/dist/auth/loginEmailResolution.js +47 -0
- package/dist/auth/loginEmailResolution.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +221 -9
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +7 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +27 -3
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/magicLink/types.js +0 -4
- package/dist/auth/magicLink/types.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +1 -0
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/backgroundSweepConfigUnits.d.ts +231 -0
- package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
- package/dist/backgroundSweepConfigUnits.js +180 -0
- package/dist/backgroundSweepConfigUnits.js.map +1 -0
- package/dist/config.d.ts +906 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +35 -4
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +3 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +6 -4
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1163 -6
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +50 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +321 -132
- 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 +47 -7
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +157 -35
- package/dist/index.js.map +1 -1
- package/dist/integration/ConnectionPause.d.ts +121 -0
- package/dist/integration/ConnectionPause.d.ts.map +1 -0
- package/dist/integration/ConnectionPause.js +183 -0
- package/dist/integration/ConnectionPause.js.map +1 -0
- package/dist/integration/CustomColumnPromoter.d.ts +72 -6
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +87 -46
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +78 -4
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +199 -5
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/integration/InactiveRowWarnings.d.ts +31 -0
- package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
- package/dist/integration/InactiveRowWarnings.js +29 -0
- package/dist/integration/InactiveRowWarnings.js.map +1 -1
- package/dist/integration/ObjectNamesByID.d.ts +28 -0
- package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
- package/dist/integration/ObjectNamesByID.js +87 -0
- package/dist/integration/ObjectNamesByID.js.map +1 -0
- package/dist/integration/PostRestartIntrospect.d.ts +26 -0
- package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
- package/dist/integration/PostRestartIntrospect.js +27 -0
- package/dist/integration/PostRestartIntrospect.js.map +1 -0
- package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
- package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
- package/dist/integration/RSUPostRestartProgress.js +323 -0
- package/dist/integration/RSUPostRestartProgress.js.map +1 -0
- package/dist/integration/RSUProgressBridge.d.ts +41 -2
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
- package/dist/integration/RSUProgressBridge.js +84 -1
- package/dist/integration/RSUProgressBridge.js.map +1 -1
- package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
- package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
- package/dist/integration/RunArtifactAuthorization.js +45 -0
- package/dist/integration/RunArtifactAuthorization.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
- package/dist/integration/SchemaRefreshLaunch.js +3 -2
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
- package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
- package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
- package/dist/integration/ScopedFieldsForBuild.js +91 -0
- package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
- package/dist/integration/TargetConfigGuard.d.ts +40 -0
- package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
- package/dist/integration/TargetConfigGuard.js +40 -0
- package/dist/integration/TargetConfigGuard.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +22 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +1 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +4 -7
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/requestOrigin.d.ts +22 -0
- package/dist/requestOrigin.d.ts.map +1 -0
- package/dist/requestOrigin.js +60 -0
- package/dist/requestOrigin.js.map +1 -0
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +6 -2
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +2 -1
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +34 -13
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -2
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +174 -87
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +185 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +19 -18
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +3 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +2 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +6 -2
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +11 -5
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +2 -4
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +2 -1
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +3 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +11 -2
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +2 -26
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +38 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +2 -1
- package/dist/services/WorkQueueHostService.d.ts.map +1 -1
- package/dist/services/WorkQueueHostService.js +19 -4
- package/dist/services/WorkQueueHostService.js.map +1 -1
- package/dist/services/workQueueConfig.d.ts +2 -2
- package/dist/services/workQueueConfig.js +5 -5
- package/dist/services/workQueueConfig.js.map +1 -1
- package/dist/sharedCache.js +1 -1
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
- package/src/__tests__/ActiveOperations.test.ts +68 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
- package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
- package/src/__tests__/BatchApplyParity.test.ts +109 -0
- package/src/__tests__/BridgeHostInstance.test.ts +42 -0
- package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
- package/src/__tests__/ConnectionPause.test.ts +192 -0
- package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
- package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
- package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
- package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
- package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
- package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
- package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
- package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
- package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
- package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
- package/src/__tests__/MagicLinkKeys.test.ts +102 -0
- package/src/__tests__/MediaAccessKeys.test.ts +57 -10
- package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
- package/src/__tests__/ObjectNamesByID.test.ts +176 -0
- package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
- package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
- package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
- package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
- package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
- package/src/__tests__/RSUProgressBridge.test.ts +188 -5
- package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
- package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
- package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
- package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
- package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
- package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
- package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
- package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
- package/src/__tests__/SessionJanitor.test.ts +195 -4
- package/src/__tests__/SessionManager.test.ts +69 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
- package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
- package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
- package/src/__tests__/WorkQueueHostService.test.ts +25 -7
- package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
- package/src/__tests__/contextRequestOrigin.test.ts +105 -0
- package/src/__tests__/conversationBrowsers.test.ts +62 -0
- package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
- package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
- package/src/__tests__/requestOrigin.test.ts +104 -0
- package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
- package/src/__tests__/sessionDeadline.test.ts +51 -0
- package/src/__tests__/sharedCache.test.ts +11 -0
- package/src/__tests__/unifiedAuth.test.ts +73 -1
- package/src/agentSessions/BridgeHostInstance.ts +20 -5
- package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
- package/src/agentSessions/SessionJanitor.ts +92 -42
- package/src/agentSessions/SessionManager.ts +38 -8
- package/src/agentSessions/conversationBrowsers.ts +143 -0
- package/src/agentSessions/sessionDeadline.ts +81 -0
- package/src/auth/APIKeyScopeAuth.ts +5 -4
- package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
- package/src/auth/loginEmailResolution.ts +67 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
- package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
- package/src/auth/magicLink/MagicLinkService.ts +250 -10
- package/src/auth/magicLink/index.ts +1 -1
- package/src/auth/magicLink/redeemLanding.ts +7 -4
- package/src/auth/magicLink/types.ts +28 -3
- package/src/auth/newUsers.ts +1 -0
- package/src/backgroundSweepConfigUnits.ts +213 -0
- package/src/config.ts +69 -0
- package/src/context.ts +39 -6
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +822 -7
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +414 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/index.ts +201 -35
- package/src/integration/ConnectionPause.ts +222 -0
- package/src/integration/CustomColumnPromoter.ts +123 -53
- package/src/integration/EntityMapLifecycle.ts +229 -6
- package/src/integration/InactiveRowWarnings.ts +50 -0
- package/src/integration/ObjectNamesByID.ts +97 -0
- package/src/integration/PostRestartIntrospect.ts +49 -0
- package/src/integration/RSUPostRestartProgress.ts +359 -0
- package/src/integration/RSUProgressBridge.ts +85 -2
- package/src/integration/RunArtifactAuthorization.ts +65 -0
- package/src/integration/SchemaRefreshLaunch.ts +12 -2
- package/src/integration/ScopedFieldsForBuild.ts +113 -0
- package/src/integration/TargetConfigGuard.ts +71 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
- package/src/realtimeSessions/sessionAccess.ts +30 -0
- package/src/realtimeWidget/WidgetSessionService.ts +2 -0
- package/src/realtimeWidget/visitorIdentity.ts +1 -0
- package/src/realtimeWidget/widgetCore.ts +4 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
- package/src/requestOrigin.ts +64 -0
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +2 -1
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +197 -98
- package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
- package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
- package/src/resolvers/RunAIAgentResolver.ts +265 -86
- package/src/resolvers/RunAIPromptResolver.ts +18 -18
- package/src/resolvers/RunTemplateResolver.ts +3 -1
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/rest/MediaAccessKeys.ts +6 -2
- package/src/rest/OAuthCallbackHandler.ts +13 -5
- package/src/rest/RealtimeRecordingHandler.ts +5 -9
- package/src/rest/ViewOperationsHandler.ts +2 -1
- package/src/rest/setupRESTEndpoints.ts +11 -2
- package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
- package/src/services/TaskGraphActionRunner.ts +2 -23
- package/src/services/TaskGraphAgentRunner.ts +43 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
- package/src/services/WorkQueueHostService.ts +26 -4
- package/src/services/workQueueConfig.ts +5 -5
- package/src/sharedCache.ts +1 -1
- package/src/types.ts +18 -0
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FIELD rows a schema build materializes, for a connection with its own catalog.
|
|
3
|
+
*
|
|
4
|
+
* A schema build (ApplySchema, ApplyAll, ApplyAllBatch, the connector build, schema evolution)
|
|
5
|
+
* reconstructs the source schema from persisted catalog rows and hands it to SchemaBuilder. It used
|
|
6
|
+
* to read each object's fields from the engine's in-memory catalog. Once a connection's field rows
|
|
7
|
+
* are no longer held resident — they are warmed per object on demand, and the only site that warms
|
|
8
|
+
* them is the sync loop — that read answers `[]` for every object of the connection, and an object
|
|
9
|
+
* with no fields materializes nothing. Observed on a per-connection catalog (364 objects, 7,230
|
|
10
|
+
* active fields): the apply logged "Reusing 364 persisted IOs", then ran a migration with an empty
|
|
11
|
+
* table list in 4 ms — no CREATE, no ALTER, no error.
|
|
12
|
+
*
|
|
13
|
+
* So the build reads the connection's field rows itself: one read per {@link SCOPED_FIELDS_CHUNK}
|
|
14
|
+
* objects, plain rows, bypassing the query cache (an answer cached before discovery's persist is the
|
|
15
|
+
* empty set), and without the row cap (one object can carry thousands of fields). A failed read
|
|
16
|
+
* THROWS: an empty answer here is exactly what produced the zero-table apply.
|
|
17
|
+
*
|
|
18
|
+
* Outside a per-connection scope — or for a connection without its own catalog — this returns
|
|
19
|
+
* `null`, and the build reads the shared catalog from memory exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
import { RunView, type UserInfo } from '@memberjunction/core';
|
|
22
|
+
import type { MJIntegrationObjectFieldEntity } from '@memberjunction/core-entities';
|
|
23
|
+
import {
|
|
24
|
+
IntegrationEngineBase,
|
|
25
|
+
ENTITY_COMPANY_INTEGRATION_OBJECT_FIELDS,
|
|
26
|
+
type CompanyIntegrationObjectFieldRow,
|
|
27
|
+
} from '@memberjunction/integration-engine-base';
|
|
28
|
+
import { CurrentCatalogCI } from '@memberjunction/integration-engine';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What a schema build — and a refresh's re-key check — reads off a field row. The shared entity and
|
|
32
|
+
* the per-connection row both satisfy it.
|
|
33
|
+
*/
|
|
34
|
+
export type BuildFieldRow = Pick<
|
|
35
|
+
MJIntegrationObjectFieldEntity,
|
|
36
|
+
'Name' | 'DisplayName' | 'Description' | 'Type' | 'IsRequired' | 'AllowsNull' | 'Length' | 'Precision'
|
|
37
|
+
| 'Scale' | 'DefaultValue' | 'IsPrimaryKey' | 'IsUniqueKey' | 'IsReadOnly' | 'RelatedIntegrationObjectID' | 'Status'
|
|
38
|
+
| 'Sequence'
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
/** A connection's field rows, keyed by lower-cased per-connection object id. */
|
|
42
|
+
export type ScopedBuildFields = Map<string, CompanyIntegrationObjectFieldRow[]>;
|
|
43
|
+
|
|
44
|
+
/** Objects per read. Each read is one `IN (…)` list, so this bounds the filter's length. */
|
|
45
|
+
export const SCOPED_FIELDS_CHUNK = 200;
|
|
46
|
+
|
|
47
|
+
/** A per-connection field row as the database returns it — before the legacy read names are derived. */
|
|
48
|
+
type StoredFieldRow = Omit<CompanyIntegrationObjectFieldRow, 'IntegrationObjectID' | 'RelatedIntegrationObjectID'>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Loads the field rows of the connection in catalog scope for a schema build, or returns `null`
|
|
52
|
+
* when the build should read the shared catalog (no scope, or no per-connection catalog).
|
|
53
|
+
*
|
|
54
|
+
* @throws PER_CONNECTION_FIELDS_UNREADABLE when a read fails — never an empty answer.
|
|
55
|
+
*/
|
|
56
|
+
export async function LoadScopedFieldsForBuild(integrationID: string, user: UserInfo): Promise<ScopedBuildFields | null> {
|
|
57
|
+
const ciID = CurrentCatalogCI();
|
|
58
|
+
if (!ciID) return null;
|
|
59
|
+
const engine = IntegrationEngineBase.Instance;
|
|
60
|
+
if (!engine.HasCompanyIntegrationCatalog(ciID)) return null;
|
|
61
|
+
|
|
62
|
+
const objects = engine.GetActiveIntegrationObjects(integrationID);
|
|
63
|
+
const byObject: ScopedBuildFields = new Map();
|
|
64
|
+
const rv = new RunView();
|
|
65
|
+
for (let i = 0; i < objects.length; i += SCOPED_FIELDS_CHUNK) {
|
|
66
|
+
const chunk = objects.slice(i, i + SCOPED_FIELDS_CHUNK);
|
|
67
|
+
const ids = chunk.map(o => `'${String(o.ID).replace(/'/g, "''")}'`).join(',');
|
|
68
|
+
const res = await rv.RunView<StoredFieldRow>({
|
|
69
|
+
EntityName: ENTITY_COMPANY_INTEGRATION_OBJECT_FIELDS,
|
|
70
|
+
ExtraFilter: `CompanyIntegrationObjectID IN (${ids})`,
|
|
71
|
+
ResultType: 'simple',
|
|
72
|
+
BypassCache: true,
|
|
73
|
+
IgnoreMaxRows: true,
|
|
74
|
+
}, user);
|
|
75
|
+
if (!res?.Success) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`PER_CONNECTION_FIELDS_UNREADABLE: could not read the field rows of connection ${ciID} ` +
|
|
78
|
+
`(objects ${i + 1}-${i + chunk.length} of ${objects.length}): ${res?.ErrorMessage ?? 'unknown error'}. ` +
|
|
79
|
+
`Refusing to build a schema from an empty field list.`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
for (const row of res.Results ?? []) addRow(byObject, row);
|
|
83
|
+
}
|
|
84
|
+
const rowCount = [...byObject.values()].reduce((n, rows) => n + rows.length, 0);
|
|
85
|
+
console.log(`[LoadScopedFieldsForBuild] connection ${ciID}: ${rowCount} field rows for ${byObject.size} of ${objects.length} active objects`);
|
|
86
|
+
return byObject;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The fields a build reads for one object: the connection's rows when they were loaded, the
|
|
91
|
+
* engine's catalog otherwise. With loaded rows the engine is not consulted at all, so a cold
|
|
92
|
+
* per-connection field cache cannot empty the build.
|
|
93
|
+
*/
|
|
94
|
+
export function FieldsForBuild(objectID: string, scoped: ScopedBuildFields | null | undefined, engine: IntegrationEngineBase): BuildFieldRow[] {
|
|
95
|
+
if (scoped) return scoped.get(String(objectID).trim().toLowerCase()) ?? [];
|
|
96
|
+
return engine.GetIntegrationObjectFields(objectID);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Files one stored row under its object, deriving the two legacy names the build reads
|
|
101
|
+
* (`IntegrationObjectID`, `RelatedIntegrationObjectID`) exactly as the engine's projection does.
|
|
102
|
+
*/
|
|
103
|
+
function addRow(byObject: ScopedBuildFields, row: StoredFieldRow): void {
|
|
104
|
+
const key = String(row.CompanyIntegrationObjectID).trim().toLowerCase();
|
|
105
|
+
const projected: CompanyIntegrationObjectFieldRow = {
|
|
106
|
+
...row,
|
|
107
|
+
IntegrationObjectID: row.CompanyIntegrationObjectID,
|
|
108
|
+
RelatedIntegrationObjectID: row.RelatedCompanyIntegrationObjectID ?? null,
|
|
109
|
+
};
|
|
110
|
+
const bucket = byObject.get(key);
|
|
111
|
+
if (bucket) bucket.push(projected);
|
|
112
|
+
else byObject.set(key, [projected]);
|
|
113
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuses a schema build whose selected objects produced no target table.
|
|
3
|
+
*
|
|
4
|
+
* `buildTargetConfigs` drops a selected object that is not in the source schema, has no fields or
|
|
5
|
+
* has no primary key, and logs each drop — but every execution caller then built and ran a migration
|
|
6
|
+
* anyway. With nothing accepted that migration is a header-only file: RSU wrote it, opened and merged
|
|
7
|
+
* a PR, ran CodeGen, redeployed Explorer, and the sync "completed" over zero entity maps, every signal
|
|
8
|
+
* green. Seen 2026-10-29 on a PropFuel connection: three selections requested, three dropped as
|
|
9
|
+
* notInSchema, three empty migrations committed.
|
|
10
|
+
*
|
|
11
|
+
* A caller that asked for tables and gets none has failed, and should hear so with the names it asked
|
|
12
|
+
* for and why each was dropped — in the error itself, because an API or UI caller cannot read the
|
|
13
|
+
* server log line that used to be the only place the reasons appeared. Preview is deliberately not
|
|
14
|
+
* gated: an empty preview is an honest answer.
|
|
15
|
+
*/
|
|
16
|
+
import type { TargetTableConfig } from '@memberjunction/integration-schema-builder';
|
|
17
|
+
|
|
18
|
+
/** Why `buildTargetConfigs` left each selected object out. */
|
|
19
|
+
export interface TargetConfigDrops {
|
|
20
|
+
NotInSchema: string[];
|
|
21
|
+
NoFields: string[];
|
|
22
|
+
NoPrimaryKey: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** How many names one list in the error shows before summarising the rest — the summary log's cap. */
|
|
26
|
+
export const TARGET_CONFIG_NAME_SAMPLE = 5;
|
|
27
|
+
|
|
28
|
+
/** An empty set of drop lists, for `buildTargetConfigs` to fill. */
|
|
29
|
+
export function NewTargetConfigDrops(): TargetConfigDrops {
|
|
30
|
+
return { NotInSchema: [], NoFields: [], NoPrimaryKey: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** `a, b, c, d, e and 163 more` — so a large selection cannot make a multi-KB error. */
|
|
34
|
+
export function SampleNames(names: ReadonlyArray<string>): string {
|
|
35
|
+
const head = names.slice(0, TARGET_CONFIG_NAME_SAMPLE).join(', ');
|
|
36
|
+
const rest = names.length - TARGET_CONFIG_NAME_SAMPLE;
|
|
37
|
+
return rest > 0 ? `${head} and ${rest} more` : head;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Throws `NO_TARGET_TABLES` when objects were selected and none produced a target table.
|
|
42
|
+
*
|
|
43
|
+
* @param drops why each object was dropped; without it the error lists the three possible reasons.
|
|
44
|
+
* @param alreadyCommitted what the caller had already written before the build, for a caller that
|
|
45
|
+
* cannot honestly say "Nothing was applied" (schema evolution persists the catalog and disables the
|
|
46
|
+
* removed objects' maps first).
|
|
47
|
+
*/
|
|
48
|
+
export function AssertTargetConfigs(
|
|
49
|
+
objects: ReadonlyArray<{ SourceObjectName: string }>,
|
|
50
|
+
targetConfigs: ReadonlyArray<TargetTableConfig>,
|
|
51
|
+
sourceType: string,
|
|
52
|
+
drops?: TargetConfigDrops,
|
|
53
|
+
alreadyCommitted?: string,
|
|
54
|
+
): void {
|
|
55
|
+
if (objects.length === 0 || targetConfigs.length > 0) return;
|
|
56
|
+
const reasons = drops ? describeDrops(drops) : '';
|
|
57
|
+
throw new Error(
|
|
58
|
+
`NO_TARGET_TABLES: none of the ${objects.length} selected object(s) for ${sourceType} produced a target table ` +
|
|
59
|
+
`(${SampleNames(objects.map(o => o.SourceObjectName))}). ` +
|
|
60
|
+
(reasons || 'Each was dropped as not in the source schema, without fields, or without a primary key.') +
|
|
61
|
+
` ${alreadyCommitted ?? 'Nothing was applied.'} Re-run discovery for this connection and apply again.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function describeDrops(drops: TargetConfigDrops): string {
|
|
66
|
+
const parts: string[] = [];
|
|
67
|
+
if (drops.NotInSchema.length > 0) parts.push(`not in the source schema: ${SampleNames(drops.NotInSchema)}`);
|
|
68
|
+
if (drops.NoFields.length > 0) parts.push(`no fields: ${SampleNames(drops.NoFields)}`);
|
|
69
|
+
if (drops.NoPrimaryKey.length > 0) parts.push(`no primary key: ${SampleNames(drops.NoPrimaryKey)}`);
|
|
70
|
+
return parts.length > 0 ? `Dropped as ${parts.join('; ')}.` : '';
|
|
71
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { IMetadataProvider, LogError, UserInfo } from '@memberjunction/core';
|
|
9
9
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
10
10
|
import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
|
|
11
|
-
import {
|
|
11
|
+
import { AuthorizeLoadedSession } from './sessionAccess.js';
|
|
12
12
|
|
|
13
13
|
const SESSION_ENTITY = 'MJ: AI Agent Sessions';
|
|
14
14
|
|
|
@@ -54,11 +54,7 @@ export async function LoadOwnedRealtimeSession(
|
|
|
54
54
|
);
|
|
55
55
|
throw new RealtimeSessionOwnershipError(agentSessionId);
|
|
56
56
|
}
|
|
57
|
-
const
|
|
58
|
-
const access = AuthorizeSessionAccess(
|
|
59
|
-
{ SessionUserID: session.UserID, ConversationExternalID: conversation.ExternalID, ConversationReadable: conversation.Readable },
|
|
60
|
-
contextUser,
|
|
61
|
-
);
|
|
57
|
+
const access = await AuthorizeLoadedSession(session, contextUser, provider);
|
|
62
58
|
if (!access.Allowed) {
|
|
63
59
|
LogError(
|
|
64
60
|
`LoadOwnedRealtimeSession: session access refused (${access.Reason}) — user ${contextUser.ID} attempted to operate on session ${agentSessionId}`,
|
|
@@ -199,6 +199,36 @@ export async function LoadSessionConversationScope(
|
|
|
199
199
|
return { ExternalID, Readable };
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Decides whether `contextUser` may address an already-loaded `session`: the {@link AuthorizeSessionAccess}
|
|
204
|
+
* decision, reading the session's conversation only when that decision needs it.
|
|
205
|
+
*
|
|
206
|
+
* {@link AuthorizeSessionAccess} decides a principal without a scope identity — a named user with no magic-link
|
|
207
|
+
* scope, the usual signed-in caller — from the owner check alone, and refuses a non-owner before it looks at
|
|
208
|
+
* anything else, so neither needs the conversation read. This function and that one encode the same rule on
|
|
209
|
+
* purpose and live together for that reason: change the rule there, change the read here. The
|
|
210
|
+
* `AuthorizeLoadedSession` table test in `sessionAccess.test.ts` fails if the two ever disagree.
|
|
211
|
+
*
|
|
212
|
+
* @param session - the session, loaded through the caller's own gate
|
|
213
|
+
* @param contextUser - the authenticated caller; the conversation, when read, is read as this user (RLS applies)
|
|
214
|
+
* @param provider - the request-scoped provider
|
|
215
|
+
*/
|
|
216
|
+
export async function AuthorizeLoadedSession(
|
|
217
|
+
session: MJAIAgentSessionEntity,
|
|
218
|
+
contextUser: UserInfo,
|
|
219
|
+
provider: IMetadataProvider,
|
|
220
|
+
): Promise<SessionAccessDecision> {
|
|
221
|
+
const ownsSession = !!contextUser?.ID && !!session.UserID && UUIDsEqual(session.UserID, contextUser.ID);
|
|
222
|
+
if (!ownsSession || !ResolveSessionScopeIdentity(contextUser)) {
|
|
223
|
+
return AuthorizeSessionAccess({ SessionUserID: session.UserID, ConversationExternalID: null, ConversationReadable: false }, contextUser);
|
|
224
|
+
}
|
|
225
|
+
const conversation = await LoadSessionConversationScope(session, contextUser, provider);
|
|
226
|
+
return AuthorizeSessionAccess(
|
|
227
|
+
{ SessionUserID: session.UserID, ConversationExternalID: conversation.ExternalID, ConversationReadable: conversation.Readable },
|
|
228
|
+
contextUser,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
202
232
|
/**
|
|
203
233
|
* Reads a conversation's owner and scope id under `contextUser` (so its row-level security applies), for a
|
|
204
234
|
* caller that addresses the conversation directly rather than through a session — a text agent turn, for
|
|
@@ -861,6 +861,7 @@ export class WidgetSessionService {
|
|
|
861
861
|
ExtraFilter: `VisitorKey = '${visitorKey}' AND ApplicationID = '${applicationId.replace(/'/g, "''")}'`,
|
|
862
862
|
OrderBy: '__mj_CreatedAt DESC',
|
|
863
863
|
MaxRows: 1,
|
|
864
|
+
SkipTotalRowCount: true,
|
|
864
865
|
Fields: ['ID'],
|
|
865
866
|
ResultType: 'simple',
|
|
866
867
|
},
|
|
@@ -885,6 +886,7 @@ export class WidgetSessionService {
|
|
|
885
886
|
EntityName: WIDGET_ENTITY,
|
|
886
887
|
ExtraFilter: `PublicKey = '${key.replace(/'/g, "''")}'`,
|
|
887
888
|
MaxRows: 1,
|
|
889
|
+
SkipTotalRowCount: true,
|
|
888
890
|
ResultType: 'entity_object',
|
|
889
891
|
},
|
|
890
892
|
contextUser,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { BuildSessionClaims, IsRoleGrantable } from '../auth/magicLink/magicLinkCore.js';
|
|
16
16
|
import type { MagicLinkJWTClaims } from '../auth/magicLink/types.js';
|
|
17
|
+
import { NormalizeOrigin } from '../requestOrigin.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* The magic-link resource-scope `resourceType` used for a widget guest session. Pairs with the
|
|
@@ -77,12 +78,12 @@ export function ParseAllowedOrigins(allowedOrigins: string | null | undefined):
|
|
|
77
78
|
list = JSON.parse(raw);
|
|
78
79
|
} catch {
|
|
79
80
|
// Not JSON — tolerate a CSV form.
|
|
80
|
-
return raw.split(',').map((s) =>
|
|
81
|
+
return raw.split(',').map((s) => NormalizeOrigin(s)).filter((s): s is string => !!s);
|
|
81
82
|
}
|
|
82
83
|
if (!Array.isArray(list)) {
|
|
83
84
|
return [];
|
|
84
85
|
}
|
|
85
|
-
return list.filter((s): s is string => typeof s === 'string').map(
|
|
86
|
+
return list.filter((s): s is string => typeof s === 'string').map(NormalizeOrigin).filter((s): s is string => !!s);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
/** @deprecated Use {@link ParseAllowedOrigins}. */
|
|
@@ -90,11 +91,6 @@ export function parseAllowedOrigins(allowedOrigins: string | null | undefined):
|
|
|
90
91
|
return ParseAllowedOrigins(allowedOrigins);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
/** Lowercases + trims an origin and strips a trailing slash for stable comparison. */
|
|
94
|
-
function normalizeOrigin(origin: string): string {
|
|
95
|
-
return origin.trim().toLowerCase().replace(/\/+$/, '');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
94
|
/**
|
|
99
95
|
* Parses the widget's `EnabledChannels` column into a clean list of channel names (Phase 2). Accepts a
|
|
100
96
|
* JSON array (preferred, e.g. `["Whiteboard"]`) or a comma-separated string (tolerated). Returns an
|
|
@@ -129,7 +125,7 @@ export function IsOriginAllowed(requestOrigin: string | null | undefined, allowe
|
|
|
129
125
|
if (!requestOrigin || allowedOrigins.length === 0) {
|
|
130
126
|
return false;
|
|
131
127
|
}
|
|
132
|
-
const candidate =
|
|
128
|
+
const candidate = NormalizeOrigin(requestOrigin);
|
|
133
129
|
return allowedOrigins.some((o) => o === candidate);
|
|
134
130
|
}
|
|
135
131
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-origin helpers shared by the GraphQL context (`AppContext.RequestOrigin`) and the realtime
|
|
3
|
+
* widget origin allowlist, so an origin read from a request and an origin read from an allowlist are
|
|
4
|
+
* normalized the same way and compare with plain string equality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shape of a serialized http(s) origin: scheme, host and optional port, with no path, query,
|
|
9
|
+
* fragment or credentials. Applied after {@link NormalizeOrigin}, so it only needs lowercase.
|
|
10
|
+
*/
|
|
11
|
+
const HTTP_ORIGIN_PATTERN = /^https?:\/\/[^/?#@\s]+$/;
|
|
12
|
+
|
|
13
|
+
/** Character code of `/`. */
|
|
14
|
+
const SLASH_CHAR_CODE = 47;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes an origin for stable comparison: trims whitespace, lowercases, and strips trailing
|
|
18
|
+
* slashes. It does not parse the value or rewrite ports, so `https://a.com:443` stays as written.
|
|
19
|
+
* @param origin an origin as written in a header or an allowlist entry
|
|
20
|
+
*/
|
|
21
|
+
export function NormalizeOrigin(origin: string): string {
|
|
22
|
+
return StripTrailingSlashes(origin.trim().toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Removes trailing `/` characters with a linear scan. A `/\/+$/` regex is quadratic on input with
|
|
27
|
+
* many `/` not at the end, and this runs on a request header the client controls.
|
|
28
|
+
*/
|
|
29
|
+
function StripTrailingSlashes(value: string): string {
|
|
30
|
+
let end = value.length;
|
|
31
|
+
while (end > 0 && value.charCodeAt(end - 1) === SLASH_CHAR_CODE) {
|
|
32
|
+
end--;
|
|
33
|
+
}
|
|
34
|
+
return value.slice(0, end);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reads a request's `Origin` header into a normalized origin (`scheme://host[:port]`).
|
|
39
|
+
*
|
|
40
|
+
* Returns undefined when the header is absent, repeated, blank, opaque (`null`, which sandboxed
|
|
41
|
+
* frames and privacy-sensitive redirects send), or anything other than a parseable http(s) origin.
|
|
42
|
+
* Ports are kept as sent: browsers omit default ports from `Origin`, and allowlist entries pass
|
|
43
|
+
* through the same {@link NormalizeOrigin}, so both sides agree without rewriting.
|
|
44
|
+
* @param originHeader the raw `Origin` header value from the request
|
|
45
|
+
*/
|
|
46
|
+
export function ReadRequestOrigin(originHeader: string | string[] | undefined): string | undefined {
|
|
47
|
+
if (typeof originHeader !== 'string') {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const normalized = NormalizeOrigin(originHeader);
|
|
51
|
+
return IsHttpOrigin(normalized) ? normalized : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** True when a normalized value is a parseable http(s) origin with no path, query, fragment or credentials. */
|
|
55
|
+
function IsHttpOrigin(normalized: string): boolean {
|
|
56
|
+
if (!HTTP_ORIGIN_PATTERN.test(normalized)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return new URL(normalized).host.length > 0;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -23,8 +23,12 @@ import {
|
|
|
23
23
|
RealtimeSessionOwnershipError,
|
|
24
24
|
} from '../realtimeSessions/ownedRealtimeSession.js';
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The `CloseReason` values a client may stamp on its own session; `Idle`, `Janitor` and `Shutdown` are the server's.
|
|
28
|
+
* `TimeLimit` is a claim: the browser's deadline alarm fired, and {@link SessionManager.CloseSession} verifies it
|
|
29
|
+
* against the session's stored deadline, recording `Explicit` instead when the deadline is not near.
|
|
30
|
+
*/
|
|
31
|
+
const CLIENT_CLOSE_REASONS: readonly SessionCloseReason[] = ['Explicit', 'Error', 'TimeLimit'];
|
|
28
32
|
|
|
29
33
|
/** Reads the mutation's optional `closeReason`: absent means `Explicit`; a server-only or unknown value is refused. */
|
|
30
34
|
function ParseClientCloseReason(closeReason: string | null | undefined): SessionCloseReason {
|
|
@@ -170,7 +170,8 @@ export class AutotagPipelineResolver extends ResolverBase {
|
|
|
170
170
|
EntityName: 'MJ: Content Sources',
|
|
171
171
|
Fields: ['ID'],
|
|
172
172
|
ResultType: 'simple',
|
|
173
|
-
MaxRows: 1
|
|
173
|
+
MaxRows: 1,
|
|
174
|
+
SkipTotalRowCount: true
|
|
174
175
|
}, currentUser);
|
|
175
176
|
if (result.Success && result.Results.length > 0) {
|
|
176
177
|
sourceID = result.Results[0].ID;
|
|
@@ -235,6 +235,7 @@ export class ConversationRunEventsResolver {
|
|
|
235
235
|
EntityName: 'MJ: Conversation Details',
|
|
236
236
|
ExtraFilter: `ID='${this.escape(conversationDetailID)}'`,
|
|
237
237
|
MaxRows: 1,
|
|
238
|
+
SkipTotalRowCount: true,
|
|
238
239
|
ResultType: 'entity_object',
|
|
239
240
|
BypassCache: true,
|
|
240
241
|
},
|
|
@@ -259,6 +260,7 @@ export class ConversationRunEventsResolver {
|
|
|
259
260
|
ExtraFilter: `ConversationDetailID='${this.escape(conversationDetailID)}'`,
|
|
260
261
|
OrderBy: '__mj_CreatedAt DESC',
|
|
261
262
|
MaxRows: 1,
|
|
263
|
+
SkipTotalRowCount: true,
|
|
262
264
|
Fields: RUN_FIELDS,
|
|
263
265
|
ResultType: 'simple',
|
|
264
266
|
BypassCache: true,
|
|
@@ -12,13 +12,13 @@ const REMOTE_OP_PROGRESS_TOPIC = 'RemoteOperationProgress';
|
|
|
12
12
|
/** Input for the generic Remote Operation transport mutation. */
|
|
13
13
|
@InputType()
|
|
14
14
|
export class ExecuteRemoteOperationInput {
|
|
15
|
-
@Field()
|
|
15
|
+
@Field(() => String)
|
|
16
16
|
operationKey: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
|
|
17
17
|
|
|
18
|
-
@Field()
|
|
18
|
+
@Field(() => String)
|
|
19
19
|
inputJSON: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
|
|
20
20
|
|
|
21
|
-
@Field()
|
|
21
|
+
@Field(() => String)
|
|
22
22
|
invokeMode: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -28,16 +28,24 @@ export class ExecuteRemoteOperationInput {
|
|
|
28
28
|
*/
|
|
29
29
|
@Field(() => String, { nullable: true })
|
|
30
30
|
progressChannelId?: string; // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Optional structural contract fingerprint emitted by CodeGen (or supplied by the caller).
|
|
34
|
+
* When provided, the server rejects requests whose fingerprint diverges from the server's
|
|
35
|
+
* active operation contract with `ResultCode = 'CONTRACT_MISMATCH'`.
|
|
36
|
+
*/
|
|
37
|
+
@Field(() => String, { nullable: true })
|
|
38
|
+
contractFingerprint?: string; // case-violation-ok-legacy-back-compat: matches camelCase GraphQL input field convention on ExecuteRemoteOperationInput
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
/** A single streamed progress envelope delivered over the `RemoteOperationProgress` subscription. */
|
|
34
42
|
@ObjectType()
|
|
35
43
|
export class RemoteOperationProgressNotification {
|
|
36
|
-
@Field()
|
|
44
|
+
@Field(() => String)
|
|
37
45
|
ChannelId: string;
|
|
38
46
|
|
|
39
47
|
/** JSON-serialized {@link RemoteOpProgress}. */
|
|
40
|
-
@Field()
|
|
48
|
+
@Field(() => String)
|
|
41
49
|
ProgressJSON: string;
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -71,7 +79,7 @@ export class ExecuteRemoteOperationResultType {
|
|
|
71
79
|
export class ExecuteRemoteOperationResolver extends ResolverBase {
|
|
72
80
|
@Mutation(() => ExecuteRemoteOperationResultType)
|
|
73
81
|
async ExecuteRemoteOperation(
|
|
74
|
-
@Arg('input') input: ExecuteRemoteOperationInput,
|
|
82
|
+
@Arg('input', () => ExecuteRemoteOperationInput) input: ExecuteRemoteOperationInput,
|
|
75
83
|
@Ctx() ctx: AppContext,
|
|
76
84
|
@PubSub() pubSub: PubSubEngine,
|
|
77
85
|
): Promise<ExecuteRemoteOperationResultType> {
|
|
@@ -111,9 +119,24 @@ export class ExecuteRemoteOperationResolver extends ResolverBase {
|
|
|
111
119
|
return fail(invokability.ResultCode ?? 'OPERATION_UNAVAILABLE', invokability.Reason ?? `Operation '${key}' is unavailable`);
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
|
|
122
|
+
// (5) Contract fingerprint gate: when the client sends a contractFingerprint, verify it against
|
|
123
|
+
// the active server operation's ContractFingerprint (or metadata-derived fingerprint) so stale
|
|
124
|
+
// client bundles fail loudly with CONTRACT_MISMATCH rather than mis-deserializing.
|
|
125
|
+
const clientFingerprint = input.contractFingerprint?.trim();
|
|
126
|
+
if (clientFingerprint) {
|
|
127
|
+
const expectedFingerprint =
|
|
128
|
+
opDecl?.ContractFingerprint ?? RemoteOperationEngineBase.Instance.GetContractFingerprint(key);
|
|
129
|
+
if (expectedFingerprint && clientFingerprint !== expectedFingerprint) {
|
|
130
|
+
return fail(
|
|
131
|
+
'CONTRACT_MISMATCH',
|
|
132
|
+
`Contract fingerprint mismatch for operation '${key}' (client='${clientFingerprint}', server='${expectedFingerprint}')`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let parsedInput: object | string | number | boolean | null;
|
|
115
138
|
try {
|
|
116
|
-
parsedInput = input.inputJSON ? JSON.parse(input.inputJSON) : {};
|
|
139
|
+
parsedInput = input.inputJSON ? (JSON.parse(input.inputJSON) as object | string | number | boolean | null) : {};
|
|
117
140
|
} catch (e) {
|
|
118
141
|
return fail('INVALID_INPUT_JSON', `Invalid input JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
119
142
|
}
|
|
@@ -127,7 +150,16 @@ export class ExecuteRemoteOperationResolver extends ResolverBase {
|
|
|
127
150
|
: undefined;
|
|
128
151
|
|
|
129
152
|
// Route through the per-request provider (server in-process path; runs the op's Authorize + InternalExecute).
|
|
130
|
-
const result = await provider.RouteOperation(key, parsedInput, {
|
|
153
|
+
const result = await provider.RouteOperation(key, parsedInput, {
|
|
154
|
+
user,
|
|
155
|
+
mode: input.invokeMode as RemoteOpInvokeMode,
|
|
156
|
+
onProgress,
|
|
157
|
+
contractFingerprint: clientFingerprint,
|
|
158
|
+
requestOrigin: ctx.RequestOrigin,
|
|
159
|
+
clientIp: ctx.ClientIp,
|
|
160
|
+
sessionId: ctx.userPayload?.sessionId,
|
|
161
|
+
apiKeyHash: ctx.userPayload?.apiKeyHash,
|
|
162
|
+
});
|
|
131
163
|
|
|
132
164
|
return {
|
|
133
165
|
success: result.Success,
|
|
@@ -6,6 +6,7 @@ import { QueryCompositionEngine } from '@memberjunction/generic-database-provide
|
|
|
6
6
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
7
7
|
import { NoLog } from '../logging/NoLog.js';
|
|
8
8
|
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
9
10
|
import { GetReadOnlyDataSource, GetReadOnlyProvider } from '../util.js';
|
|
10
11
|
import { GetDbType } from '../index.js';
|
|
11
12
|
import { GetSystemUser } from '../auth/index.js';
|
|
@@ -125,9 +126,9 @@ export class GetDataResolver {
|
|
|
125
126
|
});
|
|
126
127
|
LogStatus(`GetData invoked: ${input.Queries.length} queries`);
|
|
127
128
|
|
|
128
|
-
// validate the token
|
|
129
|
+
// validate the token (never echo the raw token into error messages or logs — Token is @NoLog)
|
|
129
130
|
if (!IsTokenValid(input.Token)) {
|
|
130
|
-
throw new Error(
|
|
131
|
+
throw new Error('Access token is not valid or has expired');
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
// Use the read-only connection for executing queries
|
|
@@ -296,6 +297,26 @@ export class GetDataAccessToken {
|
|
|
296
297
|
const __accessTokens: GetDataAccessToken[] = [];
|
|
297
298
|
const __defaultTokenLifeSpan = 1000 * 60 * 5; // 5 minutes
|
|
298
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Non-short-circuiting constant-time scan over active access tokens: hashes the presented token
|
|
302
|
+
* once and compares against every stored token digest with `timingSafeEqual` without breaking early,
|
|
303
|
+
* so neither prefix similarity, token length, nor array index leaks through timing.
|
|
304
|
+
*/
|
|
305
|
+
function findMatchingAccessTokenIndex(token: string): number {
|
|
306
|
+
if (typeof token !== 'string' || token.length === 0) {
|
|
307
|
+
return -1;
|
|
308
|
+
}
|
|
309
|
+
const targetDigest = createHash('sha256').update(token).digest();
|
|
310
|
+
let matchedIndex = -1;
|
|
311
|
+
for (let i = 0; i < __accessTokens.length; i++) {
|
|
312
|
+
const candidateDigest = createHash('sha256').update(__accessTokens[i].Token).digest();
|
|
313
|
+
if (timingSafeEqual(candidateDigest, targetDigest)) {
|
|
314
|
+
matchedIndex = i;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return matchedIndex;
|
|
318
|
+
}
|
|
319
|
+
|
|
299
320
|
/**
|
|
300
321
|
* Removes every token whose `ExpiresAt` has already passed (and its `TokenUses`
|
|
301
322
|
* log along with it). Without this sweep `__accessTokens` grows forever — nothing
|
|
@@ -339,7 +360,7 @@ export function RegisterAccessToken(token?: string, lifeSpan: number = __default
|
|
|
339
360
|
|
|
340
361
|
if (TokenExists(tokenToUse)) {
|
|
341
362
|
// should never happen if we used the uuidv4() function but could happen if someone tries to use a custom token
|
|
342
|
-
throw new Error(
|
|
363
|
+
throw new Error('Access token already exists');
|
|
343
364
|
}
|
|
344
365
|
|
|
345
366
|
const newToken = new GetDataAccessToken();
|
|
@@ -356,12 +377,12 @@ export function registerAccessToken(token?: string, lifeSpan: number = __default
|
|
|
356
377
|
return RegisterAccessToken(token, lifeSpan, requestorPayload);
|
|
357
378
|
}
|
|
358
379
|
export function DeleteAccessToken(token: string) {
|
|
359
|
-
const index =
|
|
380
|
+
const index = findMatchingAccessTokenIndex(token);
|
|
360
381
|
if (index >= 0) {
|
|
361
382
|
__accessTokens.splice(index, 1);
|
|
362
383
|
}
|
|
363
384
|
else {
|
|
364
|
-
throw new Error(
|
|
385
|
+
throw new Error('Access token does not exist');
|
|
365
386
|
}
|
|
366
387
|
}
|
|
367
388
|
|
|
@@ -370,7 +391,7 @@ export function deleteAccessToken(token: string) {
|
|
|
370
391
|
return DeleteAccessToken(token);
|
|
371
392
|
}
|
|
372
393
|
export function TokenExists(token: string) {
|
|
373
|
-
return
|
|
394
|
+
return findMatchingAccessTokenIndex(token) >= 0;
|
|
374
395
|
}
|
|
375
396
|
|
|
376
397
|
/** @deprecated Use {@link TokenExists}. */
|
|
@@ -378,9 +399,9 @@ export function tokenExists(token: string) {
|
|
|
378
399
|
return TokenExists(token);
|
|
379
400
|
}
|
|
380
401
|
export function IsTokenValid(token: string) {
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
383
|
-
return
|
|
402
|
+
const index = findMatchingAccessTokenIndex(token);
|
|
403
|
+
if (index >= 0) {
|
|
404
|
+
return __accessTokens[index].ExpiresAt > new Date();
|
|
384
405
|
}
|
|
385
406
|
return false;
|
|
386
407
|
}
|
|
@@ -390,15 +411,16 @@ export function isTokenValid(token: string) {
|
|
|
390
411
|
return IsTokenValid(token);
|
|
391
412
|
}
|
|
392
413
|
export function RecordTokenUse(token: string, usePayload: any) {
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
414
|
+
const index = findMatchingAccessTokenIndex(token);
|
|
415
|
+
if (index >= 0) {
|
|
416
|
+
const t = __accessTokens[index];
|
|
395
417
|
if (!t.TokenUses) {
|
|
396
418
|
t.TokenUses = [];
|
|
397
419
|
}
|
|
398
|
-
t.TokenUses.push({ Token:
|
|
420
|
+
t.TokenUses.push({ Token: t.Token, UsedAt: new Date(), UsePayload: usePayload });
|
|
399
421
|
}
|
|
400
422
|
else {
|
|
401
|
-
throw new Error(
|
|
423
|
+
throw new Error('Access token does not exist');
|
|
402
424
|
}
|
|
403
425
|
}
|
|
404
426
|
|