@brightchain/brightchain-api-lib 0.23.25 → 0.24.0
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/package.json +5 -5
- package/src/lib/__tests__/fixtures/mock-backend-brightchain-member.d.ts.map +1 -1
- package/src/lib/__tests__/fixtures/mock-backend-brightchain-member.js +11 -0
- package/src/lib/__tests__/fixtures/mock-backend-brightchain-member.js.map +1 -1
- package/src/lib/__tests__/fixtures/mocked-model.js +63 -60
- package/src/lib/__tests__/fixtures/mocked-model.js.map +1 -1
- package/src/lib/application.d.ts +1 -1
- package/src/lib/application.d.ts.map +1 -1
- package/src/lib/application.js +103 -34
- package/src/lib/application.js.map +1 -1
- package/src/lib/auth/aclEnforcedAvailability.js +4 -0
- package/src/lib/auth/aclEnforcedAvailability.js.map +1 -1
- package/src/lib/auth/aclEnforcedBlockStore.js +7 -0
- package/src/lib/auth/aclEnforcedBlockStore.js.map +1 -1
- package/src/lib/auth/ecdsaNodeAuthenticator.js +1 -0
- package/src/lib/auth/ecdsaNodeAuthenticator.js.map +1 -1
- package/src/lib/auth/poolAclBootstrap.js +2 -0
- package/src/lib/auth/poolAclBootstrap.js.map +1 -1
- package/src/lib/auth/poolAclStore.js +2 -1
- package/src/lib/auth/poolAclStore.js.map +1 -1
- package/src/lib/auth/poolAclUpdater.js +4 -0
- package/src/lib/auth/poolAclUpdater.js.map +1 -1
- package/src/lib/availability/availabilityMetrics.js +43 -45
- package/src/lib/availability/availabilityMetrics.js.map +1 -1
- package/src/lib/availability/availabilityService.js +26 -20
- package/src/lib/availability/availabilityService.js.map +1 -1
- package/src/lib/availability/blockRegistry.js +46 -25
- package/src/lib/availability/blockRegistry.js.map +1 -1
- package/src/lib/availability/discoveryProtocol.js +10 -8
- package/src/lib/availability/discoveryProtocol.js.map +1 -1
- package/src/lib/availability/gossipService.js +56 -45
- package/src/lib/availability/gossipService.js.map +1 -1
- package/src/lib/availability/heartbeatMonitor.js +22 -20
- package/src/lib/availability/heartbeatMonitor.js.map +1 -1
- package/src/lib/availability/poolDiscoveryService.js +7 -1
- package/src/lib/availability/poolDiscoveryService.js.map +1 -1
- package/src/lib/availability/quorumGossipHandler.js +13 -6
- package/src/lib/availability/quorumGossipHandler.js.map +1 -1
- package/src/lib/availability/reconciliationService.js +18 -12
- package/src/lib/availability/reconciliationService.js.map +1 -1
- package/src/lib/blockFetch/blockFetcher.js +14 -8
- package/src/lib/blockFetch/blockFetcher.js.map +1 -1
- package/src/lib/blockFetch/fetchQueue.js +9 -7
- package/src/lib/blockFetch/fetchQueue.js.map +1 -1
- package/src/lib/blockFetch/httpBlockFetchTransport.js +2 -0
- package/src/lib/blockFetch/httpBlockFetchTransport.js.map +1 -1
- package/src/lib/browserKeyring.js +5 -2
- package/src/lib/browserKeyring.js.map +1 -1
- package/src/lib/controllers/api/blocks.d.ts.map +1 -1
- package/src/lib/controllers/api/blocks.js +9 -3
- package/src/lib/controllers/api/blocks.js.map +1 -1
- package/src/lib/controllers/api/brighthub/connectionController.d.ts +80 -0
- package/src/lib/controllers/api/brighthub/connectionController.d.ts.map +1 -0
- package/src/lib/controllers/api/brighthub/connectionController.js +890 -0
- package/src/lib/controllers/api/brighthub/connectionController.js.map +1 -0
- package/src/lib/controllers/api/brighthub/index.d.ts +9 -0
- package/src/lib/controllers/api/brighthub/index.d.ts.map +1 -0
- package/src/lib/controllers/api/brighthub/index.js +12 -0
- package/src/lib/controllers/api/brighthub/index.js.map +1 -0
- package/src/lib/controllers/api/brighthub/messagingController.d.ts +84 -0
- package/src/lib/controllers/api/brighthub/messagingController.d.ts.map +1 -0
- package/src/lib/controllers/api/brighthub/messagingController.js +1077 -0
- package/src/lib/controllers/api/brighthub/messagingController.js.map +1 -0
- package/src/lib/controllers/api/brighthub/notificationController.d.ts +89 -0
- package/src/lib/controllers/api/brighthub/notificationController.d.ts.map +1 -0
- package/src/lib/controllers/api/brighthub/notificationController.js +444 -0
- package/src/lib/controllers/api/brighthub/notificationController.js.map +1 -0
- package/src/lib/controllers/api/brighthub/postController.d.ts +75 -0
- package/src/lib/controllers/api/brighthub/postController.d.ts.map +1 -0
- package/src/lib/controllers/api/brighthub/postController.js +388 -0
- package/src/lib/controllers/api/brighthub/postController.js.map +1 -0
- package/src/lib/controllers/api/brighthub/timelineController.d.ts +74 -0
- package/src/lib/controllers/api/brighthub/timelineController.d.ts.map +1 -0
- package/src/lib/controllers/api/brighthub/timelineController.js +418 -0
- package/src/lib/controllers/api/brighthub/timelineController.js.map +1 -0
- package/src/lib/controllers/api/brightpass.d.ts.map +1 -1
- package/src/lib/controllers/api/brightpass.js +46 -4
- package/src/lib/controllers/api/brightpass.js.map +1 -1
- package/src/lib/controllers/api/cbl.js +1 -0
- package/src/lib/controllers/api/cbl.js.map +1 -1
- package/src/lib/controllers/api/channels.js +2 -2
- package/src/lib/controllers/api/channels.js.map +1 -1
- package/src/lib/controllers/api/conversations.js +1 -1
- package/src/lib/controllers/api/conversations.js.map +1 -1
- package/src/lib/controllers/api/docs.js +2 -1
- package/src/lib/controllers/api/docs.js.map +1 -1
- package/src/lib/controllers/api/emails.d.ts.map +1 -1
- package/src/lib/controllers/api/emails.js +16 -2
- package/src/lib/controllers/api/emails.js.map +1 -1
- package/src/lib/controllers/api/explodingMessages.js +2 -4
- package/src/lib/controllers/api/explodingMessages.js.map +1 -1
- package/src/lib/controllers/api/groups.js +2 -2
- package/src/lib/controllers/api/groups.js.map +1 -1
- package/src/lib/controllers/api/health.d.ts +9 -2
- package/src/lib/controllers/api/health.d.ts.map +1 -1
- package/src/lib/controllers/api/health.js +48 -6
- package/src/lib/controllers/api/health.js.map +1 -1
- package/src/lib/controllers/api/index.d.ts +1 -0
- package/src/lib/controllers/api/index.d.ts.map +1 -1
- package/src/lib/controllers/api/index.js +1 -0
- package/src/lib/controllers/api/index.js.map +1 -1
- package/src/lib/controllers/api/introspection.js +2 -0
- package/src/lib/controllers/api/introspection.js.map +1 -1
- package/src/lib/controllers/api/messages.js +1 -1
- package/src/lib/controllers/api/messages.js.map +1 -1
- package/src/lib/controllers/api/nodes.js +6 -6
- package/src/lib/controllers/api/nodes.js.map +1 -1
- package/src/lib/controllers/api/quorum.d.ts +1 -1
- package/src/lib/controllers/api/quorum.d.ts.map +1 -1
- package/src/lib/controllers/api/quorum.js +3 -2
- package/src/lib/controllers/api/quorum.js.map +1 -1
- package/src/lib/controllers/api/scbl.js +3 -0
- package/src/lib/controllers/api/scbl.js.map +1 -1
- package/src/lib/controllers/api/sessions.d.ts +18 -2
- package/src/lib/controllers/api/sessions.d.ts.map +1 -1
- package/src/lib/controllers/api/sessions.js +60 -3
- package/src/lib/controllers/api/sessions.js.map +1 -1
- package/src/lib/controllers/api/sync.js +4 -4
- package/src/lib/controllers/api/sync.js.map +1 -1
- package/src/lib/controllers/api/user.d.ts +2 -0
- package/src/lib/controllers/api/user.d.ts.map +1 -1
- package/src/lib/controllers/api/user.js +95 -3
- package/src/lib/controllers/api/user.js.map +1 -1
- package/src/lib/controllers/identity/deviceController.js +2 -2
- package/src/lib/controllers/identity/deviceController.js.map +1 -1
- package/src/lib/controllers/identity/directoryController.js +1 -1
- package/src/lib/controllers/identity/directoryController.js.map +1 -1
- package/src/lib/controllers/identity/identityProofController.js +4 -6
- package/src/lib/controllers/identity/identityProofController.js.map +1 -1
- package/src/lib/databaseInit.d.ts +3 -3
- package/src/lib/databaseInit.js +5 -5
- package/src/lib/databaseInit.js.map +1 -1
- package/src/lib/datastore/block-document-store.d.ts.map +1 -1
- package/src/lib/datastore/block-document-store.js +18 -6
- package/src/lib/datastore/block-document-store.js.map +1 -1
- package/src/lib/datastore/document-store.d.ts.map +1 -1
- package/src/lib/datastore/memory-document-store.js +5 -2
- package/src/lib/datastore/memory-document-store.js.map +1 -1
- package/src/lib/encryption/encryptedMetadataService.js +2 -0
- package/src/lib/encryption/encryptedMetadataService.js.map +1 -1
- package/src/lib/encryption/encryptionAwareReplication.js +3 -0
- package/src/lib/encryption/encryptionAwareReplication.js.map +1 -1
- package/src/lib/encryption/errors.d.ts.map +1 -1
- package/src/lib/encryption/errors.js +8 -0
- package/src/lib/encryption/errors.js.map +1 -1
- package/src/lib/encryption/poolKeyManager.js +2 -0
- package/src/lib/encryption/poolKeyManager.js.map +1 -1
- package/src/lib/environment.js +10 -0
- package/src/lib/environment.js.map +1 -1
- package/src/lib/errors/express-validation.js +1 -0
- package/src/lib/errors/express-validation.js.map +1 -1
- package/src/lib/errors/invalid-backup-code-version.js +1 -0
- package/src/lib/errors/invalid-backup-code-version.js.map +1 -1
- package/src/lib/errors/memberIndexSchemaValidationError.js +1 -0
- package/src/lib/errors/memberIndexSchemaValidationError.js.map +1 -1
- package/src/lib/errors/missing-validated-data.js +2 -0
- package/src/lib/errors/missing-validated-data.js.map +1 -1
- package/src/lib/errors/token-not-found.js +1 -0
- package/src/lib/errors/token-not-found.js.map +1 -1
- package/src/lib/errors/typed-error-local.js +3 -0
- package/src/lib/errors/typed-error-local.js.map +1 -1
- package/src/lib/interfaces/brightpass/index.d.ts +1 -1
- package/src/lib/interfaces/brightpass/index.d.ts.map +1 -1
- package/src/lib/interfaces/environment.d.ts +1 -1
- package/src/lib/interfaces/environment.d.ts.map +1 -1
- package/src/lib/interfaces/member/memberProfileResponse.d.ts.map +1 -1
- package/src/lib/interfaces/member/operational.d.ts.map +1 -1
- package/src/lib/interfaces/member-init-config.d.ts +1 -1
- package/src/lib/interfaces/member-init-config.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-backup-codes-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-challenge-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-code-count-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-detailed-health-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-discover-block-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-express-validation-error.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-get-block-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-get-node-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-health-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-list-nodes-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-login-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-members-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-mnemonic-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-reconcile-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-register-node-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-registration-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-replicate-block-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-request-user-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-store-block-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-store-cbl-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/api-sync-request-response.d.ts.map +1 -1
- package/src/lib/interfaces/responses/brighthub/api-connection-response.d.ts +21 -0
- package/src/lib/interfaces/responses/brighthub/api-connection-response.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-connection-response.js +3 -0
- package/src/lib/interfaces/responses/brighthub/api-connection-response.js.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-messaging-response.d.ts +39 -0
- package/src/lib/interfaces/responses/brighthub/api-messaging-response.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-messaging-response.js +3 -0
- package/src/lib/interfaces/responses/brighthub/api-messaging-response.js.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-notification-response.d.ts +30 -0
- package/src/lib/interfaces/responses/brighthub/api-notification-response.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-notification-response.js +3 -0
- package/src/lib/interfaces/responses/brighthub/api-notification-response.js.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-post-response.d.ts +21 -0
- package/src/lib/interfaces/responses/brighthub/api-post-response.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-post-response.js +3 -0
- package/src/lib/interfaces/responses/brighthub/api-post-response.js.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-timeline-response.d.ts +14 -0
- package/src/lib/interfaces/responses/brighthub/api-timeline-response.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-timeline-response.js +3 -0
- package/src/lib/interfaces/responses/brighthub/api-timeline-response.js.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-user-profile-response.d.ts +10 -0
- package/src/lib/interfaces/responses/brighthub/api-user-profile-response.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/api-user-profile-response.js +3 -0
- package/src/lib/interfaces/responses/brighthub/api-user-profile-response.js.map +1 -0
- package/src/lib/interfaces/responses/brighthub/index.d.ts +13 -0
- package/src/lib/interfaces/responses/brighthub/index.d.ts.map +1 -0
- package/src/lib/interfaces/responses/brighthub/index.js +9 -0
- package/src/lib/interfaces/responses/brighthub/index.js.map +1 -0
- package/src/lib/interfaces/responses/index.d.ts +1 -0
- package/src/lib/interfaces/responses/index.d.ts.map +1 -1
- package/src/lib/interfaces/storage/storedDocumentTypes.d.ts +1 -1
- package/src/lib/interfaces/storage/storedDocumentTypes.js +1 -1
- package/src/lib/interfaces/storage/userRoleSchema.d.ts.map +1 -1
- package/src/lib/interfaces/storage/userRoleSchema.js +1 -3
- package/src/lib/interfaces/storage/userRoleSchema.js.map +1 -1
- package/src/lib/middlewares.js +31 -31
- package/src/lib/middlewares.js.map +1 -1
- package/src/lib/nodeKeyring.js +2 -0
- package/src/lib/nodeKeyring.js.map +1 -1
- package/src/lib/plugins/brightchain-database-plugin.d.ts +14 -14
- package/src/lib/plugins/brightchain-database-plugin.d.ts.map +1 -1
- package/src/lib/plugins/brightchain-database-plugin.js +36 -34
- package/src/lib/plugins/brightchain-database-plugin.js.map +1 -1
- package/src/lib/routers/api.d.ts +62 -1
- package/src/lib/routers/api.d.ts.map +1 -1
- package/src/lib/routers/api.js +123 -2
- package/src/lib/routers/api.js.map +1 -1
- package/src/lib/routers/app.d.ts.map +1 -1
- package/src/lib/routers/app.js +10 -1
- package/src/lib/routers/app.js.map +1 -1
- package/src/lib/routers/base.js +2 -0
- package/src/lib/routers/base.js.map +1 -1
- package/src/lib/secureEnclaveKeyring.js +4 -2
- package/src/lib/secureEnclaveKeyring.js.map +1 -1
- package/src/lib/services/auth.d.ts.map +1 -1
- package/src/lib/services/auth.js +11 -2
- package/src/lib/services/auth.js.map +1 -1
- package/src/lib/services/base.js +1 -0
- package/src/lib/services/base.js.map +1 -1
- package/src/lib/services/blockServiceFactory.js +2 -0
- package/src/lib/services/blockServiceFactory.js.map +1 -1
- package/src/lib/services/blockStore.js +3 -2
- package/src/lib/services/blockStore.js.map +1 -1
- package/src/lib/services/blocks.js +1 -0
- package/src/lib/services/blocks.js.map +1 -1
- package/src/lib/services/brightChainBackupCodeService.d.ts +114 -0
- package/src/lib/services/brightChainBackupCodeService.d.ts.map +1 -0
- package/src/lib/services/brightChainBackupCodeService.js +303 -0
- package/src/lib/services/brightChainBackupCodeService.js.map +1 -0
- package/src/lib/services/brightchain-authentication-provider.d.ts.map +1 -1
- package/src/lib/services/brightchain-authentication-provider.js +40 -9
- package/src/lib/services/brightchain-authentication-provider.js.map +1 -1
- package/src/lib/services/brightchain-member-init.service.d.ts +17 -17
- package/src/lib/services/brightchain-member-init.service.d.ts.map +1 -1
- package/src/lib/services/brightchain-member-init.service.js +12 -9
- package/src/lib/services/brightchain-member-init.service.js.map +1 -1
- package/src/lib/services/brighthub/connectionService.d.ts +286 -0
- package/src/lib/services/brighthub/connectionService.d.ts.map +1 -0
- package/src/lib/services/brighthub/connectionService.js +1887 -0
- package/src/lib/services/brighthub/connectionService.js.map +1 -0
- package/src/lib/services/brighthub/discoveryService.d.ts +110 -0
- package/src/lib/services/brighthub/discoveryService.d.ts.map +1 -0
- package/src/lib/services/brighthub/discoveryService.js +528 -0
- package/src/lib/services/brighthub/discoveryService.js.map +1 -0
- package/src/lib/services/brighthub/feedService.d.ts +141 -0
- package/src/lib/services/brighthub/feedService.d.ts.map +1 -0
- package/src/lib/services/brighthub/feedService.js +418 -0
- package/src/lib/services/brighthub/feedService.js.map +1 -0
- package/src/lib/services/brighthub/index.d.ts +11 -0
- package/src/lib/services/brighthub/index.d.ts.map +1 -0
- package/src/lib/services/brighthub/index.js +14 -0
- package/src/lib/services/brighthub/index.js.map +1 -0
- package/src/lib/services/brighthub/messagingService.d.ts +109 -0
- package/src/lib/services/brighthub/messagingService.d.ts.map +1 -0
- package/src/lib/services/brighthub/messagingService.js +947 -0
- package/src/lib/services/brighthub/messagingService.js.map +1 -0
- package/src/lib/services/brighthub/messagingService.test-helpers.d.ts +75 -0
- package/src/lib/services/brighthub/messagingService.test-helpers.d.ts.map +1 -0
- package/src/lib/services/brighthub/messagingService.test-helpers.js +237 -0
- package/src/lib/services/brighthub/messagingService.test-helpers.js.map +1 -0
- package/src/lib/services/brighthub/notificationService.d.ts +172 -0
- package/src/lib/services/brighthub/notificationService.d.ts.map +1 -0
- package/src/lib/services/brighthub/notificationService.js +768 -0
- package/src/lib/services/brighthub/notificationService.js.map +1 -0
- package/src/lib/services/brighthub/notificationService.test-helpers.d.ts +75 -0
- package/src/lib/services/brighthub/notificationService.test-helpers.d.ts.map +1 -0
- package/src/lib/services/brighthub/notificationService.test-helpers.js +230 -0
- package/src/lib/services/brighthub/notificationService.test-helpers.js.map +1 -0
- package/src/lib/services/brighthub/postService.d.ts +129 -0
- package/src/lib/services/brighthub/postService.d.ts.map +1 -0
- package/src/lib/services/brighthub/postService.js +470 -0
- package/src/lib/services/brighthub/postService.js.map +1 -0
- package/src/lib/services/brighthub/postService.test-helpers.d.ts +40 -0
- package/src/lib/services/brighthub/postService.test-helpers.d.ts.map +1 -0
- package/src/lib/services/brighthub/postService.test-helpers.js +84 -0
- package/src/lib/services/brighthub/postService.test-helpers.js.map +1 -0
- package/src/lib/services/brighthub/textFormatter.d.ts +64 -0
- package/src/lib/services/brighthub/textFormatter.d.ts.map +1 -0
- package/src/lib/services/brighthub/textFormatter.js +256 -0
- package/src/lib/services/brighthub/textFormatter.js.map +1 -0
- package/src/lib/services/brighthub/threadService.d.ts +79 -0
- package/src/lib/services/brighthub/threadService.d.ts.map +1 -0
- package/src/lib/services/brighthub/threadService.js +246 -0
- package/src/lib/services/brighthub/threadService.js.map +1 -0
- package/src/lib/services/brighthub/userProfileService.d.ts +203 -0
- package/src/lib/services/brighthub/userProfileService.d.ts.map +1 -0
- package/src/lib/services/brighthub/userProfileService.js +868 -0
- package/src/lib/services/brighthub/userProfileService.js.map +1 -0
- package/src/lib/services/brighthub/userProfileService.test-helpers.d.ts +86 -0
- package/src/lib/services/brighthub/userProfileService.test-helpers.d.ts.map +1 -0
- package/src/lib/services/brighthub/userProfileService.test-helpers.js +169 -0
- package/src/lib/services/brighthub/userProfileService.test-helpers.js.map +1 -0
- package/src/lib/services/brighthub/webSocketServer.d.ts +68 -0
- package/src/lib/services/brighthub/webSocketServer.d.ts.map +1 -0
- package/src/lib/services/brighthub/webSocketServer.js +194 -0
- package/src/lib/services/brighthub/webSocketServer.js.map +1 -0
- package/src/lib/services/brightpass/auditLogger.js +10 -5
- package/src/lib/services/brightpass/auditLogger.js.map +1 -1
- package/src/lib/services/brightpass/vaultEncryption.js +8 -8
- package/src/lib/services/brightpass/vaultEncryption.js.map +1 -1
- package/src/lib/services/brightpass.js +16 -8
- package/src/lib/services/brightpass.js.map +1 -1
- package/src/lib/services/cliOperatorPrompt.js +5 -2
- package/src/lib/services/cliOperatorPrompt.js.map +1 -1
- package/src/lib/services/clientWebSocketServer.d.ts +69 -4
- package/src/lib/services/clientWebSocketServer.d.ts.map +1 -1
- package/src/lib/services/clientWebSocketServer.js +188 -10
- package/src/lib/services/clientWebSocketServer.js.map +1 -1
- package/src/lib/services/contentAwareBlocksService.js +2 -0
- package/src/lib/services/contentAwareBlocksService.js.map +1 -1
- package/src/lib/services/contentIngestionService.d.ts.map +1 -1
- package/src/lib/services/contentIngestionService.js +2 -0
- package/src/lib/services/contentIngestionService.js.map +1 -1
- package/src/lib/services/diskQuorumService.js +3 -0
- package/src/lib/services/diskQuorumService.js.map +1 -1
- package/src/lib/services/email.js +5 -0
- package/src/lib/services/email.js.map +1 -1
- package/src/lib/services/eventNotificationSystem.d.ts +51 -10
- package/src/lib/services/eventNotificationSystem.d.ts.map +1 -1
- package/src/lib/services/eventNotificationSystem.js +76 -23
- package/src/lib/services/eventNotificationSystem.js.map +1 -1
- package/src/lib/services/expirationScheduler.js +6 -3
- package/src/lib/services/expirationScheduler.js.map +1 -1
- package/src/lib/services/fakeEmailService.js +3 -4
- package/src/lib/services/fakeEmailService.js.map +1 -1
- package/src/lib/services/fec.js +1 -3
- package/src/lib/services/fec.js.map +1 -1
- package/src/lib/services/fecServiceFactory.js +2 -2
- package/src/lib/services/fecServiceFactory.js.map +1 -1
- package/src/lib/services/fecUsageExample.js +1 -3
- package/src/lib/services/fecUsageExample.js.map +1 -1
- package/src/lib/services/identityExpirationScheduler.d.ts.map +1 -1
- package/src/lib/services/identityExpirationScheduler.js +7 -2
- package/src/lib/services/identityExpirationScheduler.js.map +1 -1
- package/src/lib/services/index.d.ts +3 -2
- package/src/lib/services/index.d.ts.map +1 -1
- package/src/lib/services/index.js +3 -2
- package/src/lib/services/index.js.map +1 -1
- package/src/lib/services/messageEventsWebSocketHandler.js +1 -0
- package/src/lib/services/messageEventsWebSocketHandler.js.map +1 -1
- package/src/lib/services/messagePassingService.js +5 -0
- package/src/lib/services/messagePassingService.js.map +1 -1
- package/src/lib/services/nativeRsFecService.js +3 -5
- package/src/lib/services/nativeRsFecService.js.map +1 -1
- package/src/lib/services/presenceService.js +5 -4
- package/src/lib/services/presenceService.js.map +1 -1
- package/src/lib/services/quorum.js +3 -2
- package/src/lib/services/quorum.js.map +1 -1
- package/src/lib/services/quorumDatabaseAdapter.d.ts +5 -5
- package/src/lib/services/quorumDatabaseAdapter.d.ts.map +1 -1
- package/src/lib/services/quorumDatabaseAdapter.js +5 -3
- package/src/lib/services/quorumDatabaseAdapter.js.map +1 -1
- package/src/lib/services/rbac-input-builder.js +5 -0
- package/src/lib/services/rbac-input-builder.js.map +1 -1
- package/src/lib/services/secureKeyStorage.js +3 -0
- package/src/lib/services/secureKeyStorage.js.map +1 -1
- package/src/lib/services/sessionAdapter.d.ts +4 -4
- package/src/lib/services/sessionAdapter.d.ts.map +1 -1
- package/src/lib/services/sessionAdapter.js +3 -2
- package/src/lib/services/sessionAdapter.js.map +1 -1
- package/src/lib/services/webSocketMessageServer.js +7 -4
- package/src/lib/services/webSocketMessageServer.js.map +1 -1
- package/src/lib/services/webSocketPeerProvider.js +2 -1
- package/src/lib/services/webSocketPeerProvider.js.map +1 -1
- package/src/lib/services/websocketHandler.js +9 -1
- package/src/lib/services/websocketHandler.js.map +1 -1
- package/src/lib/shared-types.d.ts +1 -2
- package/src/lib/shared-types.d.ts.map +1 -1
- package/src/lib/stores/__tests__/helpers/mockCloudBlockStore.d.ts +63 -0
- package/src/lib/stores/__tests__/helpers/mockCloudBlockStore.d.ts.map +1 -0
- package/src/lib/stores/__tests__/helpers/mockCloudBlockStore.js +160 -0
- package/src/lib/stores/__tests__/helpers/mockCloudBlockStore.js.map +1 -0
- package/src/lib/stores/availabilityAwareBlockStore.js +34 -5
- package/src/lib/stores/availabilityAwareBlockStore.js.map +1 -1
- package/src/lib/stores/cloudBlockStoreBase.d.ts +121 -0
- package/src/lib/stores/cloudBlockStoreBase.d.ts.map +1 -0
- package/src/lib/stores/cloudBlockStoreBase.js +1165 -0
- package/src/lib/stores/cloudBlockStoreBase.js.map +1 -0
- package/src/lib/stores/diskBlockAsyncStore.js +9 -5
- package/src/lib/stores/diskBlockAsyncStore.js.map +1 -1
- package/src/lib/stores/diskBlockMetadataStore.js +2 -0
- package/src/lib/stores/diskBlockMetadataStore.js.map +1 -1
- package/src/lib/stores/diskBlockStore.js +10 -8
- package/src/lib/stores/diskBlockStore.js.map +1 -1
- package/src/lib/stores/diskCBLStore.d.ts.map +1 -1
- package/src/lib/stores/diskCBLStore.js +8 -0
- package/src/lib/stores/diskCBLStore.js.map +1 -1
- package/src/lib/stores/index.d.ts +1 -0
- package/src/lib/stores/index.d.ts.map +1 -1
- package/src/lib/stores/index.js +1 -0
- package/src/lib/stores/index.js.map +1 -1
- package/src/lib/systemKeyring.d.ts.map +1 -1
- package/src/lib/systemKeyring.js +5 -4
- package/src/lib/systemKeyring.js.map +1 -1
- package/src/lib/transforms/checksumTransform.js +1 -0
- package/src/lib/transforms/checksumTransform.js.map +1 -1
- package/src/lib/transforms/memoryWritableStream.js +1 -0
- package/src/lib/transforms/memoryWritableStream.js.map +1 -1
- package/src/lib/transforms/xorMultipleTransform.js +3 -0
- package/src/lib/transforms/xorMultipleTransform.js.map +1 -1
- package/src/lib/utils/rehydration.d.ts +1 -1
- package/src/lib/utils/rehydration.js +1 -1
- package/src/lib/utils/serialization.d.ts +1 -1
- package/src/lib/utils/serialization.js +1 -1
- package/src/lib/services/backupCodeService.d.ts +0 -35
- package/src/lib/services/backupCodeService.d.ts.map +0 -1
- package/src/lib/services/backupCodeService.js +0 -109
- package/src/lib/services/backupCodeService.js.map +0 -1
|
@@ -0,0 +1,947 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessagingService = void 0;
|
|
4
|
+
exports.createMessagingService = createMessagingService;
|
|
5
|
+
const brighthub_lib_1 = require("@brightchain/brighthub-lib");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const textFormatter_1 = require("./textFormatter");
|
|
8
|
+
/** Default pagination limit */
|
|
9
|
+
const DEFAULT_PAGE_LIMIT = 20;
|
|
10
|
+
/** Maximum pagination limit */
|
|
11
|
+
const MAX_PAGE_LIMIT = 100;
|
|
12
|
+
// ═══════════════════════════════════════════════════════
|
|
13
|
+
// MessagingService Implementation
|
|
14
|
+
// ═══════════════════════════════════════════════════════
|
|
15
|
+
/**
|
|
16
|
+
* Messaging_Service implementation
|
|
17
|
+
* Handles conversations, messages, reactions, read receipts, and message requests
|
|
18
|
+
* @see Requirements: 39-43
|
|
19
|
+
*/
|
|
20
|
+
class MessagingService {
|
|
21
|
+
conversationsCollection;
|
|
22
|
+
messagesCollection;
|
|
23
|
+
messageRequestsCollection;
|
|
24
|
+
messageReactionsCollection;
|
|
25
|
+
readReceiptsCollection;
|
|
26
|
+
pinnedConversationsCollection;
|
|
27
|
+
archivedConversationsCollection;
|
|
28
|
+
mutedConversationsCollection;
|
|
29
|
+
participantsCollection;
|
|
30
|
+
followsCollection;
|
|
31
|
+
blocksCollection;
|
|
32
|
+
deletedConversationsCollection;
|
|
33
|
+
textFormatter = (0, textFormatter_1.getTextFormatter)();
|
|
34
|
+
constructor(application) {
|
|
35
|
+
this.conversationsCollection = application.getModel('brighthub_conversations');
|
|
36
|
+
this.messagesCollection =
|
|
37
|
+
application.getModel('brighthub_messages');
|
|
38
|
+
this.messageRequestsCollection = application.getModel('brighthub_message_requests');
|
|
39
|
+
this.messageReactionsCollection =
|
|
40
|
+
application.getModel('brighthub_message_reactions');
|
|
41
|
+
this.readReceiptsCollection = application.getModel('brighthub_read_receipts');
|
|
42
|
+
this.pinnedConversationsCollection =
|
|
43
|
+
application.getModel('brighthub_pinned_conversations');
|
|
44
|
+
this.archivedConversationsCollection =
|
|
45
|
+
application.getModel('brighthub_archived_conversations');
|
|
46
|
+
this.mutedConversationsCollection =
|
|
47
|
+
application.getModel('brighthub_muted_conversations');
|
|
48
|
+
this.participantsCollection =
|
|
49
|
+
application.getModel('brighthub_conversation_participants');
|
|
50
|
+
this.followsCollection =
|
|
51
|
+
application.getModel('brighthub_follows');
|
|
52
|
+
this.blocksCollection =
|
|
53
|
+
application.getModel('brighthub_blocks');
|
|
54
|
+
this.deletedConversationsCollection =
|
|
55
|
+
application.getModel('brighthub_deleted_conversations');
|
|
56
|
+
}
|
|
57
|
+
// ═══════════════════════════════════════════════════════
|
|
58
|
+
// Private helpers
|
|
59
|
+
// ═══════════════════════════════════════════════════════
|
|
60
|
+
clampLimit(limit) {
|
|
61
|
+
const l = limit ?? DEFAULT_PAGE_LIMIT;
|
|
62
|
+
return Math.min(Math.max(1, l), MAX_PAGE_LIMIT);
|
|
63
|
+
}
|
|
64
|
+
validateMessageContent(content) {
|
|
65
|
+
if (!content || content.trim().length === 0) {
|
|
66
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.EmptyContent, 'Message content cannot be empty');
|
|
67
|
+
}
|
|
68
|
+
if (this.textFormatter.getCharacterCount(content) > brighthub_lib_1.MAX_MESSAGE_LENGTH) {
|
|
69
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.ContentTooLong, `Message content exceeds maximum of ${brighthub_lib_1.MAX_MESSAGE_LENGTH} characters`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
validateAttachments(attachments) {
|
|
73
|
+
if (!attachments || attachments.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
if (attachments.length > brighthub_lib_1.MAX_MESSAGE_ATTACHMENTS) {
|
|
76
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.TooManyAttachments, `Maximum ${brighthub_lib_1.MAX_MESSAGE_ATTACHMENTS} attachments allowed per message`);
|
|
77
|
+
}
|
|
78
|
+
const totalSize = attachments.reduce((sum, a) => sum + (a.size ?? 0), 0);
|
|
79
|
+
if (totalSize > brighthub_lib_1.MAX_MESSAGE_ATTACHMENT_SIZE) {
|
|
80
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.AttachmentSizeTooLarge, `Total attachment size exceeds ${brighthub_lib_1.MAX_MESSAGE_ATTACHMENT_SIZE / (1024 * 1024)}MB limit`);
|
|
81
|
+
}
|
|
82
|
+
for (const att of attachments) {
|
|
83
|
+
if (!brighthub_lib_1.ALLOWED_MESSAGE_MEDIA_TYPES.includes(att.mimeType)) {
|
|
84
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.InvalidMediaFormat, `Invalid media format: ${att.mimeType}. Allowed: ${brighthub_lib_1.ALLOWED_MESSAGE_MEDIA_TYPES.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
recordToConversation(record) {
|
|
89
|
+
return {
|
|
90
|
+
_id: record._id,
|
|
91
|
+
type: record.type,
|
|
92
|
+
participantIds: record.participantIds,
|
|
93
|
+
name: record.name,
|
|
94
|
+
avatarUrl: record.avatarUrl,
|
|
95
|
+
lastMessageAt: record.lastMessageAt,
|
|
96
|
+
lastMessagePreview: record.lastMessagePreview,
|
|
97
|
+
createdAt: record.createdAt,
|
|
98
|
+
updatedAt: record.updatedAt,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
recordToGroupConversation(record) {
|
|
102
|
+
return {
|
|
103
|
+
...this.recordToConversation(record),
|
|
104
|
+
type: brighthub_lib_1.ConversationType.Group,
|
|
105
|
+
adminIds: record.adminIds ?? [],
|
|
106
|
+
creatorId: record.creatorId ?? '',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
recordToMessage(record) {
|
|
110
|
+
return {
|
|
111
|
+
_id: record._id,
|
|
112
|
+
conversationId: record.conversationId,
|
|
113
|
+
senderId: record.senderId,
|
|
114
|
+
content: record.content,
|
|
115
|
+
formattedContent: record.formattedContent,
|
|
116
|
+
attachments: record.attachments,
|
|
117
|
+
replyToMessageId: record.replyToMessageId,
|
|
118
|
+
forwardedFromId: record.forwardedFromId,
|
|
119
|
+
isEdited: record.isEdited,
|
|
120
|
+
editedAt: record.editedAt,
|
|
121
|
+
isDeleted: record.isDeleted,
|
|
122
|
+
createdAt: record.createdAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async assertParticipant(conversationId, userId) {
|
|
126
|
+
const conv = await this.conversationsCollection
|
|
127
|
+
.findOne({ _id: conversationId })
|
|
128
|
+
.exec();
|
|
129
|
+
if (!conv) {
|
|
130
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.ConversationNotFound, 'Conversation not found');
|
|
131
|
+
}
|
|
132
|
+
// Check if user deleted this conversation from their view
|
|
133
|
+
const deleted = await this.deletedConversationsCollection
|
|
134
|
+
.findOne({
|
|
135
|
+
userId,
|
|
136
|
+
conversationId,
|
|
137
|
+
})
|
|
138
|
+
.exec();
|
|
139
|
+
if (deleted) {
|
|
140
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.ConversationNotFound, 'Conversation not found');
|
|
141
|
+
}
|
|
142
|
+
if (!conv.participantIds.includes(userId)) {
|
|
143
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotParticipant, 'User is not a participant in this conversation');
|
|
144
|
+
}
|
|
145
|
+
return conv;
|
|
146
|
+
}
|
|
147
|
+
async assertGroupAdmin(conv, userId) {
|
|
148
|
+
if (conv.type !== brighthub_lib_1.ConversationType.Group) {
|
|
149
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotAdmin, 'Not a group conversation');
|
|
150
|
+
}
|
|
151
|
+
if (!conv.adminIds?.includes(userId)) {
|
|
152
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotAdmin, 'User is not an admin of this group');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async isBlocked(userId, otherUserId) {
|
|
156
|
+
const block1 = await this.blocksCollection
|
|
157
|
+
.findOne({
|
|
158
|
+
blockerId: userId,
|
|
159
|
+
blockedId: otherUserId,
|
|
160
|
+
})
|
|
161
|
+
.exec();
|
|
162
|
+
if (block1)
|
|
163
|
+
return true;
|
|
164
|
+
const block2 = await this.blocksCollection
|
|
165
|
+
.findOne({
|
|
166
|
+
blockerId: otherUserId,
|
|
167
|
+
blockedId: userId,
|
|
168
|
+
})
|
|
169
|
+
.exec();
|
|
170
|
+
return !!block2;
|
|
171
|
+
}
|
|
172
|
+
// ═══════════════════════════════════════════════════════
|
|
173
|
+
// Conversation Management (Req 39.1, 39.2, 43.1-43.11)
|
|
174
|
+
// ═══════════════════════════════════════════════════════
|
|
175
|
+
async createDirectConversation(userId, otherUserId) {
|
|
176
|
+
if (await this.isBlocked(userId, otherUserId)) {
|
|
177
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.UserBlocked, 'Cannot create conversation with blocked user');
|
|
178
|
+
}
|
|
179
|
+
// Check for existing direct conversation between these two users
|
|
180
|
+
const allConvs = await this.conversationsCollection
|
|
181
|
+
.find({ type: brighthub_lib_1.ConversationType.Direct })
|
|
182
|
+
.exec();
|
|
183
|
+
const existing = allConvs.find((c) => c.participantIds.length === 2 &&
|
|
184
|
+
c.participantIds.includes(userId) &&
|
|
185
|
+
c.participantIds.includes(otherUserId));
|
|
186
|
+
if (existing) {
|
|
187
|
+
// If user previously deleted it, remove the deletion record
|
|
188
|
+
if (this.deletedConversationsCollection.deleteOne) {
|
|
189
|
+
await this.deletedConversationsCollection
|
|
190
|
+
.deleteOne({
|
|
191
|
+
userId,
|
|
192
|
+
conversationId: existing._id,
|
|
193
|
+
})
|
|
194
|
+
.exec();
|
|
195
|
+
}
|
|
196
|
+
return this.recordToConversation(existing);
|
|
197
|
+
}
|
|
198
|
+
const now = new Date().toISOString();
|
|
199
|
+
const record = {
|
|
200
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
201
|
+
type: brighthub_lib_1.ConversationType.Direct,
|
|
202
|
+
participantIds: [userId, otherUserId],
|
|
203
|
+
createdAt: now,
|
|
204
|
+
updatedAt: now,
|
|
205
|
+
};
|
|
206
|
+
await this.conversationsCollection.create(record);
|
|
207
|
+
return this.recordToConversation(record);
|
|
208
|
+
}
|
|
209
|
+
async createGroupConversation(creatorId, participantIds, options) {
|
|
210
|
+
if (!options.name || options.name.trim().length === 0) {
|
|
211
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.InvalidGroupName, 'Group name cannot be empty');
|
|
212
|
+
}
|
|
213
|
+
const allParticipants = [...new Set([creatorId, ...participantIds])];
|
|
214
|
+
if (allParticipants.length > brighthub_lib_1.MAX_GROUP_PARTICIPANTS) {
|
|
215
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.GroupParticipantLimitExceeded, `Group cannot exceed ${brighthub_lib_1.MAX_GROUP_PARTICIPANTS} participants`);
|
|
216
|
+
}
|
|
217
|
+
const now = new Date().toISOString();
|
|
218
|
+
const record = {
|
|
219
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
220
|
+
type: brighthub_lib_1.ConversationType.Group,
|
|
221
|
+
participantIds: allParticipants,
|
|
222
|
+
name: options.name.trim(),
|
|
223
|
+
avatarUrl: options.avatarUrl,
|
|
224
|
+
adminIds: [creatorId],
|
|
225
|
+
creatorId,
|
|
226
|
+
createdAt: now,
|
|
227
|
+
updatedAt: now,
|
|
228
|
+
};
|
|
229
|
+
await this.conversationsCollection.create(record);
|
|
230
|
+
// Create participant records
|
|
231
|
+
for (const pid of allParticipants) {
|
|
232
|
+
await this.participantsCollection.create({
|
|
233
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
234
|
+
conversationId: record._id,
|
|
235
|
+
userId: pid,
|
|
236
|
+
role: pid === creatorId
|
|
237
|
+
? brighthub_lib_1.GroupParticipantRole.Admin
|
|
238
|
+
: brighthub_lib_1.GroupParticipantRole.Participant,
|
|
239
|
+
notificationsEnabled: true,
|
|
240
|
+
joinedAt: now,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return this.recordToGroupConversation(record);
|
|
244
|
+
}
|
|
245
|
+
async getConversation(conversationId, userId) {
|
|
246
|
+
const conv = await this.assertParticipant(conversationId, userId);
|
|
247
|
+
if (conv.type === brighthub_lib_1.ConversationType.Group) {
|
|
248
|
+
return this.recordToGroupConversation(conv);
|
|
249
|
+
}
|
|
250
|
+
return this.recordToConversation(conv);
|
|
251
|
+
}
|
|
252
|
+
async getConversations(userId, options) {
|
|
253
|
+
const limit = this.clampLimit(options?.limit);
|
|
254
|
+
// Get all conversations where user is a participant
|
|
255
|
+
const allConvs = await this.conversationsCollection
|
|
256
|
+
.find({})
|
|
257
|
+
.exec();
|
|
258
|
+
// Get deleted conversation IDs for this user
|
|
259
|
+
const deletedRecords = await this.deletedConversationsCollection
|
|
260
|
+
.find({ userId })
|
|
261
|
+
.exec();
|
|
262
|
+
const deletedIds = new Set(deletedRecords.map((d) => d.conversationId));
|
|
263
|
+
let filtered = allConvs.filter((c) => c.participantIds.includes(userId) && !deletedIds.has(c._id));
|
|
264
|
+
// Apply cursor (ISO timestamp)
|
|
265
|
+
if (options?.cursor) {
|
|
266
|
+
filtered = filtered.filter((c) => (c.lastMessageAt ?? c.createdAt) < options.cursor);
|
|
267
|
+
}
|
|
268
|
+
// Sort by most recent activity
|
|
269
|
+
filtered.sort((a, b) => {
|
|
270
|
+
const aTime = a.lastMessageAt ?? a.createdAt;
|
|
271
|
+
const bTime = b.lastMessageAt ?? b.createdAt;
|
|
272
|
+
return bTime.localeCompare(aTime);
|
|
273
|
+
});
|
|
274
|
+
const items = filtered
|
|
275
|
+
.slice(0, limit)
|
|
276
|
+
.map((c) => c.type === brighthub_lib_1.ConversationType.Group
|
|
277
|
+
? this.recordToGroupConversation(c)
|
|
278
|
+
: this.recordToConversation(c));
|
|
279
|
+
const hasMore = filtered.length > limit;
|
|
280
|
+
const lastItem = items[items.length - 1];
|
|
281
|
+
const cursor = hasMore && lastItem
|
|
282
|
+
? (lastItem.lastMessageAt ?? lastItem.createdAt)
|
|
283
|
+
: undefined;
|
|
284
|
+
return { items, cursor, hasMore };
|
|
285
|
+
}
|
|
286
|
+
async deleteConversation(conversationId, userId) {
|
|
287
|
+
await this.assertParticipant(conversationId, userId);
|
|
288
|
+
// Soft-delete: record that this user deleted the conversation
|
|
289
|
+
const existing = await this.deletedConversationsCollection
|
|
290
|
+
.findOne({ userId, conversationId })
|
|
291
|
+
.exec();
|
|
292
|
+
if (!existing) {
|
|
293
|
+
await this.deletedConversationsCollection.create({
|
|
294
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
295
|
+
userId,
|
|
296
|
+
conversationId,
|
|
297
|
+
deletedAt: new Date().toISOString(),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// ═══════════════════════════════════════════════════════
|
|
302
|
+
// Message CRUD (Req 39.1-39.4, 41.1-41.9)
|
|
303
|
+
// ═══════════════════════════════════════════════════════
|
|
304
|
+
async sendMessage(conversationId, senderId, content, options) {
|
|
305
|
+
this.validateMessageContent(content);
|
|
306
|
+
this.validateAttachments(options?.attachments);
|
|
307
|
+
await this.assertParticipant(conversationId, senderId);
|
|
308
|
+
const formatted = this.textFormatter.format(content);
|
|
309
|
+
const now = new Date().toISOString();
|
|
310
|
+
// Validate replyToMessageId if provided
|
|
311
|
+
if (options?.replyToMessageId) {
|
|
312
|
+
const replyTarget = await this.messagesCollection
|
|
313
|
+
.findOne({ _id: options.replyToMessageId })
|
|
314
|
+
.exec();
|
|
315
|
+
if (!replyTarget || replyTarget.conversationId !== conversationId) {
|
|
316
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageNotFound, 'Reply target message not found in this conversation');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Validate forwardedFromId if provided
|
|
320
|
+
if (options?.forwardedFromId) {
|
|
321
|
+
const forwardSource = await this.messagesCollection
|
|
322
|
+
.findOne({ _id: options.forwardedFromId })
|
|
323
|
+
.exec();
|
|
324
|
+
if (!forwardSource) {
|
|
325
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageNotFound, 'Forwarded message not found');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const record = {
|
|
329
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
330
|
+
conversationId,
|
|
331
|
+
senderId,
|
|
332
|
+
content,
|
|
333
|
+
formattedContent: formatted.formatted,
|
|
334
|
+
attachments: (options?.attachments ?? []),
|
|
335
|
+
replyToMessageId: options?.replyToMessageId,
|
|
336
|
+
forwardedFromId: options?.forwardedFromId,
|
|
337
|
+
isEdited: false,
|
|
338
|
+
isDeleted: false,
|
|
339
|
+
createdAt: now,
|
|
340
|
+
};
|
|
341
|
+
await this.messagesCollection.create(record);
|
|
342
|
+
// Update conversation's last message info
|
|
343
|
+
const preview = content.length > 100 ? content.substring(0, 97) + '...' : content;
|
|
344
|
+
await this.conversationsCollection
|
|
345
|
+
.updateOne({ _id: conversationId }, {
|
|
346
|
+
lastMessageAt: now,
|
|
347
|
+
lastMessagePreview: preview,
|
|
348
|
+
updatedAt: now,
|
|
349
|
+
})
|
|
350
|
+
.exec();
|
|
351
|
+
return this.recordToMessage(record);
|
|
352
|
+
}
|
|
353
|
+
async editMessage(messageId, userId, newContent) {
|
|
354
|
+
this.validateMessageContent(newContent);
|
|
355
|
+
const message = await this.messagesCollection
|
|
356
|
+
.findOne({ _id: messageId })
|
|
357
|
+
.exec();
|
|
358
|
+
if (!message) {
|
|
359
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageNotFound, 'Message not found');
|
|
360
|
+
}
|
|
361
|
+
if (message.senderId !== userId) {
|
|
362
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.Unauthorized, 'Only the sender can edit a message');
|
|
363
|
+
}
|
|
364
|
+
if (message.isDeleted) {
|
|
365
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageAlreadyDeleted, 'Cannot edit a deleted message');
|
|
366
|
+
}
|
|
367
|
+
const createdAt = new Date(message.createdAt).getTime();
|
|
368
|
+
if (Date.now() - createdAt > brighthub_lib_1.MESSAGE_EDIT_WINDOW_MS) {
|
|
369
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.EditWindowExpired, 'Edit window has expired (15 minutes)');
|
|
370
|
+
}
|
|
371
|
+
const formatted = this.textFormatter.format(newContent);
|
|
372
|
+
const now = new Date().toISOString();
|
|
373
|
+
await this.messagesCollection
|
|
374
|
+
.updateOne({ _id: messageId }, {
|
|
375
|
+
content: newContent,
|
|
376
|
+
formattedContent: formatted.formatted,
|
|
377
|
+
isEdited: true,
|
|
378
|
+
editedAt: now,
|
|
379
|
+
})
|
|
380
|
+
.exec();
|
|
381
|
+
return this.recordToMessage({
|
|
382
|
+
...message,
|
|
383
|
+
content: newContent,
|
|
384
|
+
formattedContent: formatted.formatted,
|
|
385
|
+
isEdited: true,
|
|
386
|
+
editedAt: now,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async deleteMessage(messageId, userId) {
|
|
390
|
+
const message = await this.messagesCollection
|
|
391
|
+
.findOne({ _id: messageId })
|
|
392
|
+
.exec();
|
|
393
|
+
if (!message) {
|
|
394
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageNotFound, 'Message not found');
|
|
395
|
+
}
|
|
396
|
+
if (message.senderId !== userId) {
|
|
397
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.Unauthorized, 'Only the sender can delete a message');
|
|
398
|
+
}
|
|
399
|
+
if (message.isDeleted) {
|
|
400
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageAlreadyDeleted, 'Message is already deleted');
|
|
401
|
+
}
|
|
402
|
+
// Soft-delete: replace content with placeholder
|
|
403
|
+
await this.messagesCollection
|
|
404
|
+
.updateOne({ _id: messageId }, {
|
|
405
|
+
content: '',
|
|
406
|
+
formattedContent: '<p><em>This message was deleted</em></p>',
|
|
407
|
+
isDeleted: true,
|
|
408
|
+
attachments: [],
|
|
409
|
+
})
|
|
410
|
+
.exec();
|
|
411
|
+
}
|
|
412
|
+
async getMessages(conversationId, userId, options) {
|
|
413
|
+
await this.assertParticipant(conversationId, userId);
|
|
414
|
+
const limit = this.clampLimit(options?.limit);
|
|
415
|
+
let messages = await this.messagesCollection
|
|
416
|
+
.find({ conversationId })
|
|
417
|
+
.exec();
|
|
418
|
+
// Apply cursor
|
|
419
|
+
if (options?.cursor) {
|
|
420
|
+
messages = messages.filter((m) => m.createdAt < options.cursor);
|
|
421
|
+
}
|
|
422
|
+
// Sort by newest first
|
|
423
|
+
messages.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
424
|
+
const page = messages.slice(0, limit);
|
|
425
|
+
const hasMore = messages.length > limit;
|
|
426
|
+
const lastItem = page[page.length - 1];
|
|
427
|
+
return {
|
|
428
|
+
items: page.map((m) => this.recordToMessage(m)),
|
|
429
|
+
cursor: hasMore && lastItem ? lastItem.createdAt : undefined,
|
|
430
|
+
hasMore,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
// ═══════════════════════════════════════════════════════
|
|
434
|
+
// Reactions (Req 39.8-39.10)
|
|
435
|
+
// ═══════════════════════════════════════════════════════
|
|
436
|
+
async addReaction(messageId, userId, emoji) {
|
|
437
|
+
const message = await this.messagesCollection
|
|
438
|
+
.findOne({ _id: messageId })
|
|
439
|
+
.exec();
|
|
440
|
+
if (!message || message.isDeleted) {
|
|
441
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageNotFound, 'Message not found');
|
|
442
|
+
}
|
|
443
|
+
// Check user is participant
|
|
444
|
+
await this.assertParticipant(message.conversationId, userId);
|
|
445
|
+
// Check for duplicate reaction (same user, same emoji)
|
|
446
|
+
const existing = await this.messageReactionsCollection
|
|
447
|
+
.find({ messageId })
|
|
448
|
+
.exec();
|
|
449
|
+
const duplicate = existing.find((r) => r.userId === userId && r.emoji === emoji);
|
|
450
|
+
if (duplicate) {
|
|
451
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.ReactionAlreadyExists, 'You already reacted with this emoji');
|
|
452
|
+
}
|
|
453
|
+
// Check unique emoji limit per message
|
|
454
|
+
const uniqueEmojis = new Set(existing.map((r) => r.emoji));
|
|
455
|
+
if (!uniqueEmojis.has(emoji) &&
|
|
456
|
+
uniqueEmojis.size >= brighthub_lib_1.MAX_REACTIONS_PER_MESSAGE) {
|
|
457
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.ReactionLimitExceeded, `Maximum ${brighthub_lib_1.MAX_REACTIONS_PER_MESSAGE} unique emoji reactions per message`);
|
|
458
|
+
}
|
|
459
|
+
const record = {
|
|
460
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
461
|
+
messageId,
|
|
462
|
+
userId,
|
|
463
|
+
emoji,
|
|
464
|
+
createdAt: new Date().toISOString(),
|
|
465
|
+
};
|
|
466
|
+
await this.messageReactionsCollection.create(record);
|
|
467
|
+
return {
|
|
468
|
+
_id: record._id,
|
|
469
|
+
messageId: record.messageId,
|
|
470
|
+
userId: record.userId,
|
|
471
|
+
emoji: record.emoji,
|
|
472
|
+
createdAt: record.createdAt,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
async removeReaction(messageId, userId, emoji) {
|
|
476
|
+
const reactions = await this.messageReactionsCollection
|
|
477
|
+
.find({ messageId })
|
|
478
|
+
.exec();
|
|
479
|
+
const reaction = reactions.find((r) => r.userId === userId && r.emoji === emoji);
|
|
480
|
+
if (!reaction) {
|
|
481
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.ReactionNotFound, 'Reaction not found');
|
|
482
|
+
}
|
|
483
|
+
await this.messageReactionsCollection
|
|
484
|
+
.deleteOne({ _id: reaction._id })
|
|
485
|
+
.exec();
|
|
486
|
+
}
|
|
487
|
+
// ═══════════════════════════════════════════════════════
|
|
488
|
+
// Read Receipts & Typing (Req 39.5-39.7)
|
|
489
|
+
// ═══════════════════════════════════════════════════════
|
|
490
|
+
async markAsRead(conversationId, userId, messageId) {
|
|
491
|
+
await this.assertParticipant(conversationId, userId);
|
|
492
|
+
const now = new Date().toISOString();
|
|
493
|
+
// Upsert read receipt
|
|
494
|
+
const existing = await this.readReceiptsCollection
|
|
495
|
+
.find({ conversationId })
|
|
496
|
+
.exec();
|
|
497
|
+
const userReceipt = existing.find((r) => r.userId === userId);
|
|
498
|
+
if (userReceipt) {
|
|
499
|
+
await this.readReceiptsCollection
|
|
500
|
+
.updateOne({ _id: userReceipt._id }, {
|
|
501
|
+
lastReadAt: now,
|
|
502
|
+
lastReadMessageId: messageId,
|
|
503
|
+
})
|
|
504
|
+
.exec();
|
|
505
|
+
return {
|
|
506
|
+
conversationId,
|
|
507
|
+
userId,
|
|
508
|
+
lastReadAt: now,
|
|
509
|
+
lastReadMessageId: messageId,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const record = {
|
|
513
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
514
|
+
conversationId,
|
|
515
|
+
userId,
|
|
516
|
+
lastReadAt: now,
|
|
517
|
+
lastReadMessageId: messageId,
|
|
518
|
+
};
|
|
519
|
+
await this.readReceiptsCollection.create(record);
|
|
520
|
+
return {
|
|
521
|
+
conversationId: record.conversationId,
|
|
522
|
+
userId: record.userId,
|
|
523
|
+
lastReadAt: record.lastReadAt,
|
|
524
|
+
lastReadMessageId: record.lastReadMessageId,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
async sendTypingIndicator(conversationId, userId) {
|
|
528
|
+
await this.assertParticipant(conversationId, userId);
|
|
529
|
+
const expiresAt = new Date(Date.now() + brighthub_lib_1.TYPING_INDICATOR_TIMEOUT_MS).toISOString();
|
|
530
|
+
return { expiresAt };
|
|
531
|
+
}
|
|
532
|
+
// ═══════════════════════════════════════════════════════
|
|
533
|
+
// Message Requests & Privacy (Req 42.1-42.12)
|
|
534
|
+
// ═══════════════════════════════════════════════════════
|
|
535
|
+
async createMessageRequest(senderId, recipientId, messagePreview) {
|
|
536
|
+
if (await this.isBlocked(senderId, recipientId)) {
|
|
537
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.UserBlocked, 'Cannot send message request to this user');
|
|
538
|
+
}
|
|
539
|
+
// Check for existing pending request
|
|
540
|
+
const allRequests = await this.messageRequestsCollection
|
|
541
|
+
.find({ senderId })
|
|
542
|
+
.exec();
|
|
543
|
+
const existing = allRequests.find((r) => r.recipientId === recipientId &&
|
|
544
|
+
r.status === brighthub_lib_1.MessageRequestStatus.Pending);
|
|
545
|
+
if (existing) {
|
|
546
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageRequestAlreadyExists, 'A pending message request already exists for this user');
|
|
547
|
+
}
|
|
548
|
+
const preview = messagePreview.length > 100
|
|
549
|
+
? messagePreview.substring(0, 97) + '...'
|
|
550
|
+
: messagePreview;
|
|
551
|
+
const record = {
|
|
552
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
553
|
+
senderId,
|
|
554
|
+
recipientId,
|
|
555
|
+
messagePreview: preview,
|
|
556
|
+
status: brighthub_lib_1.MessageRequestStatus.Pending,
|
|
557
|
+
createdAt: new Date().toISOString(),
|
|
558
|
+
};
|
|
559
|
+
await this.messageRequestsCollection.create(record);
|
|
560
|
+
return {
|
|
561
|
+
_id: record._id,
|
|
562
|
+
senderId: record.senderId,
|
|
563
|
+
recipientId: record.recipientId,
|
|
564
|
+
messagePreview: record.messagePreview,
|
|
565
|
+
status: record.status,
|
|
566
|
+
createdAt: record.createdAt,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
async acceptMessageRequest(requestId, recipientId) {
|
|
570
|
+
const request = await this.messageRequestsCollection
|
|
571
|
+
.findOne({ _id: requestId })
|
|
572
|
+
.exec();
|
|
573
|
+
if (!request || request.recipientId !== recipientId) {
|
|
574
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageRequestNotFound, 'Message request not found');
|
|
575
|
+
}
|
|
576
|
+
// Update request status
|
|
577
|
+
await this.messageRequestsCollection
|
|
578
|
+
.updateOne({ _id: requestId }, {
|
|
579
|
+
status: brighthub_lib_1.MessageRequestStatus.Accepted,
|
|
580
|
+
})
|
|
581
|
+
.exec();
|
|
582
|
+
// Create a direct conversation
|
|
583
|
+
return this.createDirectConversation(recipientId, request.senderId);
|
|
584
|
+
}
|
|
585
|
+
async declineMessageRequest(requestId, recipientId) {
|
|
586
|
+
const request = await this.messageRequestsCollection
|
|
587
|
+
.findOne({ _id: requestId })
|
|
588
|
+
.exec();
|
|
589
|
+
if (!request || request.recipientId !== recipientId) {
|
|
590
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.MessageRequestNotFound, 'Message request not found');
|
|
591
|
+
}
|
|
592
|
+
await this.messageRequestsCollection
|
|
593
|
+
.updateOne({ _id: requestId }, {
|
|
594
|
+
status: brighthub_lib_1.MessageRequestStatus.Declined,
|
|
595
|
+
})
|
|
596
|
+
.exec();
|
|
597
|
+
}
|
|
598
|
+
async canMessageDirectly(senderId, recipientId) {
|
|
599
|
+
if (await this.isBlocked(senderId, recipientId)) {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
// Check mutual follow
|
|
603
|
+
const senderFollows = await this.followsCollection
|
|
604
|
+
.findOne({
|
|
605
|
+
followerId: senderId,
|
|
606
|
+
followedId: recipientId,
|
|
607
|
+
})
|
|
608
|
+
.exec();
|
|
609
|
+
const recipientFollows = await this.followsCollection
|
|
610
|
+
.findOne({
|
|
611
|
+
followerId: recipientId,
|
|
612
|
+
followedId: senderId,
|
|
613
|
+
})
|
|
614
|
+
.exec();
|
|
615
|
+
return !!(senderFollows && recipientFollows);
|
|
616
|
+
}
|
|
617
|
+
// ═══════════════════════════════════════════════════════
|
|
618
|
+
// Conversation Management Features (Req 43.3-43.11)
|
|
619
|
+
// ═══════════════════════════════════════════════════════
|
|
620
|
+
async pinConversation(conversationId, userId) {
|
|
621
|
+
await this.assertParticipant(conversationId, userId);
|
|
622
|
+
const existing = await this.pinnedConversationsCollection
|
|
623
|
+
.findOne({ userId, conversationId })
|
|
624
|
+
.exec();
|
|
625
|
+
if (existing) {
|
|
626
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.AlreadyPinned, 'Conversation is already pinned');
|
|
627
|
+
}
|
|
628
|
+
// Check pin limit
|
|
629
|
+
const pinned = await this.pinnedConversationsCollection
|
|
630
|
+
.find({ userId })
|
|
631
|
+
.exec();
|
|
632
|
+
if (pinned.length >= brighthub_lib_1.MAX_PINNED_CONVERSATIONS) {
|
|
633
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.PinLimitExceeded, `Maximum ${brighthub_lib_1.MAX_PINNED_CONVERSATIONS} pinned conversations allowed`);
|
|
634
|
+
}
|
|
635
|
+
await this.pinnedConversationsCollection.create({
|
|
636
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
637
|
+
userId,
|
|
638
|
+
conversationId,
|
|
639
|
+
pinnedAt: new Date().toISOString(),
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
async unpinConversation(conversationId, userId) {
|
|
643
|
+
const existing = await this.pinnedConversationsCollection
|
|
644
|
+
.findOne({ userId, conversationId })
|
|
645
|
+
.exec();
|
|
646
|
+
if (!existing) {
|
|
647
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotPinned, 'Conversation is not pinned');
|
|
648
|
+
}
|
|
649
|
+
await this.pinnedConversationsCollection
|
|
650
|
+
.deleteOne({ _id: existing._id })
|
|
651
|
+
.exec();
|
|
652
|
+
}
|
|
653
|
+
async archiveConversation(conversationId, userId) {
|
|
654
|
+
await this.assertParticipant(conversationId, userId);
|
|
655
|
+
const existing = await this.archivedConversationsCollection
|
|
656
|
+
.findOne({
|
|
657
|
+
userId,
|
|
658
|
+
conversationId,
|
|
659
|
+
})
|
|
660
|
+
.exec();
|
|
661
|
+
if (existing) {
|
|
662
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.AlreadyArchived, 'Conversation is already archived');
|
|
663
|
+
}
|
|
664
|
+
await this.archivedConversationsCollection.create({
|
|
665
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
666
|
+
userId,
|
|
667
|
+
conversationId,
|
|
668
|
+
archivedAt: new Date().toISOString(),
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
async unarchiveConversation(conversationId, userId) {
|
|
672
|
+
const existing = await this.archivedConversationsCollection
|
|
673
|
+
.findOne({
|
|
674
|
+
userId,
|
|
675
|
+
conversationId,
|
|
676
|
+
})
|
|
677
|
+
.exec();
|
|
678
|
+
if (!existing) {
|
|
679
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotArchived, 'Conversation is not archived');
|
|
680
|
+
}
|
|
681
|
+
await this.archivedConversationsCollection
|
|
682
|
+
.deleteOne({ _id: existing._id })
|
|
683
|
+
.exec();
|
|
684
|
+
}
|
|
685
|
+
async muteConversation(conversationId, userId, expiresAt) {
|
|
686
|
+
await this.assertParticipant(conversationId, userId);
|
|
687
|
+
const existing = await this.mutedConversationsCollection
|
|
688
|
+
.findOne({ userId, conversationId })
|
|
689
|
+
.exec();
|
|
690
|
+
if (existing) {
|
|
691
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.AlreadyMuted, 'Conversation is already muted');
|
|
692
|
+
}
|
|
693
|
+
await this.mutedConversationsCollection.create({
|
|
694
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
695
|
+
userId,
|
|
696
|
+
conversationId,
|
|
697
|
+
expiresAt,
|
|
698
|
+
mutedAt: new Date().toISOString(),
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
async unmuteConversation(conversationId, userId) {
|
|
702
|
+
const existing = await this.mutedConversationsCollection
|
|
703
|
+
.findOne({ userId, conversationId })
|
|
704
|
+
.exec();
|
|
705
|
+
if (!existing) {
|
|
706
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotMuted, 'Conversation is not muted');
|
|
707
|
+
}
|
|
708
|
+
await this.mutedConversationsCollection
|
|
709
|
+
.deleteOne({ _id: existing._id })
|
|
710
|
+
.exec();
|
|
711
|
+
}
|
|
712
|
+
async searchMessages(userId, query, options) {
|
|
713
|
+
const limit = this.clampLimit(options?.limit);
|
|
714
|
+
const lowerQuery = query.toLowerCase();
|
|
715
|
+
// Get user's conversations
|
|
716
|
+
const allConvs = await this.conversationsCollection
|
|
717
|
+
.find({})
|
|
718
|
+
.exec();
|
|
719
|
+
const userConvIds = allConvs
|
|
720
|
+
.filter((c) => c.participantIds.includes(userId))
|
|
721
|
+
.map((c) => c._id);
|
|
722
|
+
// Search messages in those conversations
|
|
723
|
+
const allMessages = await this.messagesCollection
|
|
724
|
+
.find({})
|
|
725
|
+
.exec();
|
|
726
|
+
let matched = allMessages.filter((m) => userConvIds.includes(m.conversationId) &&
|
|
727
|
+
!m.isDeleted &&
|
|
728
|
+
m.content.toLowerCase().includes(lowerQuery));
|
|
729
|
+
if (options?.cursor) {
|
|
730
|
+
matched = matched.filter((m) => m.createdAt < options.cursor);
|
|
731
|
+
}
|
|
732
|
+
matched.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
733
|
+
const page = matched.slice(0, limit);
|
|
734
|
+
const hasMore = matched.length > limit;
|
|
735
|
+
const lastItem = page[page.length - 1];
|
|
736
|
+
return {
|
|
737
|
+
items: page.map((m) => this.recordToMessage(m)),
|
|
738
|
+
cursor: hasMore && lastItem ? lastItem.createdAt : undefined,
|
|
739
|
+
hasMore,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async searchInConversation(conversationId, userId, query, options) {
|
|
743
|
+
await this.assertParticipant(conversationId, userId);
|
|
744
|
+
const limit = this.clampLimit(options?.limit);
|
|
745
|
+
const lowerQuery = query.toLowerCase();
|
|
746
|
+
let messages = await this.messagesCollection
|
|
747
|
+
.find({ conversationId })
|
|
748
|
+
.exec();
|
|
749
|
+
messages = messages.filter((m) => !m.isDeleted && m.content.toLowerCase().includes(lowerQuery));
|
|
750
|
+
if (options?.cursor) {
|
|
751
|
+
messages = messages.filter((m) => m.createdAt < options.cursor);
|
|
752
|
+
}
|
|
753
|
+
messages.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
754
|
+
const page = messages.slice(0, limit);
|
|
755
|
+
const hasMore = messages.length > limit;
|
|
756
|
+
const lastItem = page[page.length - 1];
|
|
757
|
+
return {
|
|
758
|
+
items: page.map((m) => this.recordToMessage(m)),
|
|
759
|
+
cursor: hasMore && lastItem ? lastItem.createdAt : undefined,
|
|
760
|
+
hasMore,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
// ═══════════════════════════════════════════════════════
|
|
764
|
+
// Group Conversation Management (Req 40.1-40.12)
|
|
765
|
+
// ═══════════════════════════════════════════════════════
|
|
766
|
+
async addParticipants(conversationId, adminId, userIds) {
|
|
767
|
+
const conv = await this.assertParticipant(conversationId, adminId);
|
|
768
|
+
await this.assertGroupAdmin(conv, adminId);
|
|
769
|
+
const newParticipants = userIds.filter((id) => !conv.participantIds.includes(id));
|
|
770
|
+
if (newParticipants.length === 0)
|
|
771
|
+
return;
|
|
772
|
+
const totalAfter = conv.participantIds.length + newParticipants.length;
|
|
773
|
+
if (totalAfter > brighthub_lib_1.MAX_GROUP_PARTICIPANTS) {
|
|
774
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.GroupParticipantLimitExceeded, `Group cannot exceed ${brighthub_lib_1.MAX_GROUP_PARTICIPANTS} participants`);
|
|
775
|
+
}
|
|
776
|
+
const now = new Date().toISOString();
|
|
777
|
+
const updatedParticipants = [...conv.participantIds, ...newParticipants];
|
|
778
|
+
await this.conversationsCollection
|
|
779
|
+
.updateOne({ _id: conversationId }, {
|
|
780
|
+
participantIds: updatedParticipants,
|
|
781
|
+
updatedAt: now,
|
|
782
|
+
})
|
|
783
|
+
.exec();
|
|
784
|
+
for (const uid of newParticipants) {
|
|
785
|
+
await this.participantsCollection.create({
|
|
786
|
+
_id: (0, crypto_1.randomUUID)(),
|
|
787
|
+
conversationId,
|
|
788
|
+
userId: uid,
|
|
789
|
+
role: brighthub_lib_1.GroupParticipantRole.Participant,
|
|
790
|
+
notificationsEnabled: true,
|
|
791
|
+
joinedAt: now,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
async removeParticipant(conversationId, adminId, userId) {
|
|
796
|
+
const conv = await this.assertParticipant(conversationId, adminId);
|
|
797
|
+
await this.assertGroupAdmin(conv, adminId);
|
|
798
|
+
if (!conv.participantIds.includes(userId)) {
|
|
799
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotParticipant, 'User is not a participant in this group');
|
|
800
|
+
}
|
|
801
|
+
const now = new Date().toISOString();
|
|
802
|
+
const updatedParticipants = conv.participantIds.filter((id) => id !== userId);
|
|
803
|
+
const updatedAdmins = (conv.adminIds ?? []).filter((id) => id !== userId);
|
|
804
|
+
await this.conversationsCollection
|
|
805
|
+
.updateOne({ _id: conversationId }, {
|
|
806
|
+
participantIds: updatedParticipants,
|
|
807
|
+
adminIds: updatedAdmins,
|
|
808
|
+
updatedAt: now,
|
|
809
|
+
})
|
|
810
|
+
.exec();
|
|
811
|
+
// Remove participant record
|
|
812
|
+
const participants = await this.participantsCollection
|
|
813
|
+
.find({ conversationId })
|
|
814
|
+
.exec();
|
|
815
|
+
const participantRecord = participants.find((p) => p.userId === userId);
|
|
816
|
+
if (participantRecord) {
|
|
817
|
+
await this.participantsCollection
|
|
818
|
+
.deleteOne({ _id: participantRecord._id })
|
|
819
|
+
.exec();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async promoteToAdmin(conversationId, adminId, userId) {
|
|
823
|
+
const conv = await this.assertParticipant(conversationId, adminId);
|
|
824
|
+
await this.assertGroupAdmin(conv, adminId);
|
|
825
|
+
if (!conv.participantIds.includes(userId)) {
|
|
826
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotParticipant, 'User is not a participant in this group');
|
|
827
|
+
}
|
|
828
|
+
if (conv.adminIds?.includes(userId)) {
|
|
829
|
+
return; // Already admin, idempotent
|
|
830
|
+
}
|
|
831
|
+
const updatedAdmins = [...(conv.adminIds ?? []), userId];
|
|
832
|
+
await this.conversationsCollection
|
|
833
|
+
.updateOne({ _id: conversationId }, {
|
|
834
|
+
adminIds: updatedAdmins,
|
|
835
|
+
updatedAt: new Date().toISOString(),
|
|
836
|
+
})
|
|
837
|
+
.exec();
|
|
838
|
+
// Update participant role
|
|
839
|
+
const participants = await this.participantsCollection
|
|
840
|
+
.find({ conversationId })
|
|
841
|
+
.exec();
|
|
842
|
+
const participantRecord = participants.find((p) => p.userId === userId);
|
|
843
|
+
if (participantRecord) {
|
|
844
|
+
await this.participantsCollection
|
|
845
|
+
.updateOne({ _id: participantRecord._id }, {
|
|
846
|
+
role: brighthub_lib_1.GroupParticipantRole.Admin,
|
|
847
|
+
})
|
|
848
|
+
.exec();
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
async demoteFromAdmin(conversationId, adminId, userId) {
|
|
852
|
+
const conv = await this.assertParticipant(conversationId, adminId);
|
|
853
|
+
await this.assertGroupAdmin(conv, adminId);
|
|
854
|
+
if (!conv.adminIds?.includes(userId)) {
|
|
855
|
+
return; // Not admin, idempotent
|
|
856
|
+
}
|
|
857
|
+
// Prevent demoting the last admin
|
|
858
|
+
if (conv.adminIds.length <= 1) {
|
|
859
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.LastAdminCannotLeave, 'Cannot demote the last admin');
|
|
860
|
+
}
|
|
861
|
+
const updatedAdmins = conv.adminIds.filter((id) => id !== userId);
|
|
862
|
+
await this.conversationsCollection
|
|
863
|
+
.updateOne({ _id: conversationId }, {
|
|
864
|
+
adminIds: updatedAdmins,
|
|
865
|
+
updatedAt: new Date().toISOString(),
|
|
866
|
+
})
|
|
867
|
+
.exec();
|
|
868
|
+
// Update participant role
|
|
869
|
+
const participants = await this.participantsCollection
|
|
870
|
+
.find({ conversationId })
|
|
871
|
+
.exec();
|
|
872
|
+
const participantRecord = participants.find((p) => p.userId === userId);
|
|
873
|
+
if (participantRecord) {
|
|
874
|
+
await this.participantsCollection
|
|
875
|
+
.updateOne({ _id: participantRecord._id }, {
|
|
876
|
+
role: brighthub_lib_1.GroupParticipantRole.Participant,
|
|
877
|
+
})
|
|
878
|
+
.exec();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async leaveGroup(conversationId, userId) {
|
|
882
|
+
const conv = await this.assertParticipant(conversationId, userId);
|
|
883
|
+
if (conv.type !== brighthub_lib_1.ConversationType.Group) {
|
|
884
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.NotAdmin, 'Cannot leave a direct conversation');
|
|
885
|
+
}
|
|
886
|
+
// If user is the last admin, they can't leave
|
|
887
|
+
if (conv.adminIds?.includes(userId) &&
|
|
888
|
+
conv.adminIds.length === 1 &&
|
|
889
|
+
conv.participantIds.length > 1) {
|
|
890
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.LastAdminCannotLeave, 'Last admin must promote another admin before leaving');
|
|
891
|
+
}
|
|
892
|
+
const now = new Date().toISOString();
|
|
893
|
+
const updatedParticipants = conv.participantIds.filter((id) => id !== userId);
|
|
894
|
+
const updatedAdmins = (conv.adminIds ?? []).filter((id) => id !== userId);
|
|
895
|
+
await this.conversationsCollection
|
|
896
|
+
.updateOne({ _id: conversationId }, {
|
|
897
|
+
participantIds: updatedParticipants,
|
|
898
|
+
adminIds: updatedAdmins,
|
|
899
|
+
updatedAt: now,
|
|
900
|
+
})
|
|
901
|
+
.exec();
|
|
902
|
+
// Remove participant record
|
|
903
|
+
const participants = await this.participantsCollection
|
|
904
|
+
.find({ conversationId })
|
|
905
|
+
.exec();
|
|
906
|
+
const participantRecord = participants.find((p) => p.userId === userId);
|
|
907
|
+
if (participantRecord) {
|
|
908
|
+
await this.participantsCollection
|
|
909
|
+
.deleteOne({ _id: participantRecord._id })
|
|
910
|
+
.exec();
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
async updateGroupSettings(conversationId, adminId, updates) {
|
|
914
|
+
const conv = await this.assertParticipant(conversationId, adminId);
|
|
915
|
+
await this.assertGroupAdmin(conv, adminId);
|
|
916
|
+
if (updates.name !== undefined && updates.name.trim().length === 0) {
|
|
917
|
+
throw new brighthub_lib_1.MessagingServiceError(brighthub_lib_1.MessagingErrorCode.InvalidGroupName, 'Group name cannot be empty');
|
|
918
|
+
}
|
|
919
|
+
const now = new Date().toISOString();
|
|
920
|
+
const updateFields = { updatedAt: now };
|
|
921
|
+
if (updates.name !== undefined)
|
|
922
|
+
updateFields.name = updates.name.trim();
|
|
923
|
+
if (updates.avatarUrl !== undefined)
|
|
924
|
+
updateFields.avatarUrl = updates.avatarUrl;
|
|
925
|
+
await this.conversationsCollection
|
|
926
|
+
.updateOne({ _id: conversationId }, updateFields)
|
|
927
|
+
.exec();
|
|
928
|
+
const updated = {
|
|
929
|
+
...conv,
|
|
930
|
+
...updateFields,
|
|
931
|
+
};
|
|
932
|
+
return this.recordToGroupConversation(updated);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
exports.MessagingService = MessagingService;
|
|
936
|
+
// ═══════════════════════════════════════════════════════
|
|
937
|
+
// Factory function
|
|
938
|
+
// ═══════════════════════════════════════════════════════
|
|
939
|
+
/**
|
|
940
|
+
* Create a new MessagingService instance
|
|
941
|
+
* @param application Application with database collections
|
|
942
|
+
* @returns MessagingService instance
|
|
943
|
+
*/
|
|
944
|
+
function createMessagingService(application) {
|
|
945
|
+
return new MessagingService(application);
|
|
946
|
+
}
|
|
947
|
+
//# sourceMappingURL=messagingService.js.map
|