@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd
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/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
- package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
- package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_token/index.js +17 -0
- package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
- package/dist/generated/entities/communication_channel/index.js +43 -0
- package/dist/generated/entities/communication_channel/index.js.map +7 -0
- package/dist/generated/entities/customer_interaction/index.js +4 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entities/external_conversation/index.js +25 -0
- package/dist/generated/entities/external_conversation/index.js.map +7 -0
- package/dist/generated/entities/external_message/index.js +25 -0
- package/dist/generated/entities/external_message/index.js.map +7 -0
- package/dist/generated/entities/integration_credentials/index.js +3 -1
- package/dist/generated/entities/integration_credentials/index.js.map +2 -2
- package/dist/generated/entities/message/index.js +2 -0
- package/dist/generated/entities/message/index.js.map +2 -2
- package/dist/generated/entities/message_channel_link/index.js +33 -0
- package/dist/generated/entities/message_channel_link/index.js.map +7 -0
- package/dist/generated/entities/message_reaction/index.js +25 -0
- package/dist/generated/entities/message_reaction/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +117 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/authFixtures.js +2 -1
- package/dist/helpers/integration/authFixtures.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
- package/dist/modules/communication_channels/acl.js +47 -0
- package/dist/modules/communication_channels/acl.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
- package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
- package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/interceptors.js +68 -0
- package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-register.js +146 -0
- package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-renew.js +23 -0
- package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
- package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/data/enrichers.js +286 -0
- package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
- package/dist/modules/communication_channels/data/entities.js +447 -0
- package/dist/modules/communication_channels/data/entities.js.map +7 -0
- package/dist/modules/communication_channels/data/extensions.js +67 -0
- package/dist/modules/communication_channels/data/extensions.js.map +7 -0
- package/dist/modules/communication_channels/data/validators.js +123 -0
- package/dist/modules/communication_channels/data/validators.js.map +7 -0
- package/dist/modules/communication_channels/di.js +35 -0
- package/dist/modules/communication_channels/di.js.map +7 -0
- package/dist/modules/communication_channels/encryption.js +12 -0
- package/dist/modules/communication_channels/encryption.js.map +7 -0
- package/dist/modules/communication_channels/events.js +124 -0
- package/dist/modules/communication_channels/events.js.map +7 -0
- package/dist/modules/communication_channels/index.js +20 -0
- package/dist/modules/communication_channels/index.js.map +7 -0
- package/dist/modules/communication_channels/lib/access-control.js +43 -0
- package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter.js +1 -0
- package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
- package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
- package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
- package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
- package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-contact.js +14 -0
- package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-mime.js +259 -0
- package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
- package/dist/modules/communication_channels/lib/error-classification.js +101 -0
- package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
- package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
- package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
- package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
- package/dist/modules/communication_channels/lib/provider-health.js +24 -0
- package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-state.js +19 -0
- package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/queue.js +54 -0
- package/dist/modules/communication_channels/lib/queue.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
- package/dist/modules/communication_channels/lib/registry.js +67 -0
- package/dist/modules/communication_channels/lib/registry.js.map +7 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
- package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
- package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/system-user.js +22 -0
- package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +68 -0
- package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-token.js +219 -0
- package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/notifications.client.js +51 -0
- package/dist/modules/communication_channels/notifications.client.js.map +7 -0
- package/dist/modules/communication_channels/notifications.handlers.js +53 -0
- package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
- package/dist/modules/communication_channels/notifications.js +56 -0
- package/dist/modules/communication_channels/notifications.js.map +7 -0
- package/dist/modules/communication_channels/setup.js +105 -0
- package/dist/modules/communication_channels/setup.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
- package/dist/modules/communication_channels/widgets/components.js +7 -0
- package/dist/modules/communication_channels/widgets/components.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
- package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
- package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
- package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
- package/dist/modules/customers/acl.js +18 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +9 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +18 -7
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
- package/dist/modules/customers/api/interactions/counts/route.js +6 -0
- package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +26 -7
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
- package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/route.js +12 -4
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +46 -5
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +16 -0
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +2 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +10 -0
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
- package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
- package/dist/modules/customers/data/enrichers.js +133 -2
- package/dist/modules/customers/data/enrichers.js.map +2 -2
- package/dist/modules/customers/data/entities.js +18 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/extensions.js +16 -0
- package/dist/modules/customers/data/extensions.js.map +7 -0
- package/dist/modules/customers/encryption.js +11 -0
- package/dist/modules/customers/encryption.js.map +2 -2
- package/dist/modules/customers/events.js +4 -1
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
- package/dist/modules/customers/lib/kysely.js.map +2 -2
- package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
- package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
- package/dist/modules/customers/lib/personEmailThreads.js +205 -0
- package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
- package/dist/modules/customers/lib/visibilityFilter.js +51 -0
- package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
- package/dist/modules/customers/setup.js +2 -1
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
- package/dist/modules/integrations/data/entities.js +8 -1
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +29 -14
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
- package/dist/modules/messages/commands/messages.js +70 -8
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
- package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/data/entities.js +8 -1
- package/dist/modules/messages/data/entities.js.map +2 -2
- package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
- package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
- package/dist/modules/messages/widgets/injection-table.js +7 -0
- package/dist/modules/messages/widgets/injection-table.js.map +7 -0
- package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
- package/generated/entities/channel_thread_mapping/index.ts +11 -0
- package/generated/entities/channel_thread_token/index.ts +7 -0
- package/generated/entities/communication_channel/index.ts +20 -0
- package/generated/entities/customer_interaction/index.ts +2 -0
- package/generated/entities/external_conversation/index.ts +11 -0
- package/generated/entities/external_message/index.ts +11 -0
- package/generated/entities/integration_credentials/index.ts +1 -0
- package/generated/entities/message/index.ts +1 -0
- package/generated/entities/message_channel_link/index.ts +15 -0
- package/generated/entities/message_reaction/index.ts +11 -0
- package/generated/entities.ids.generated.ts +11 -0
- package/generated/entity-fields-registry.ts +117 -0
- package/package.json +9 -7
- package/src/helpers/integration/authFixtures.ts +4 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
- package/src/modules/communication_channels/acl.ts +43 -0
- package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
- package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
- package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
- package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
- package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
- package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
- package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
- package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
- package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
- package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
- package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
- package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
- package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
- package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
- package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
- package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
- package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
- package/src/modules/communication_channels/commands/interceptors.ts +104 -0
- package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
- package/src/modules/communication_channels/commands/push-register.ts +203 -0
- package/src/modules/communication_channels/commands/push-renew.ts +49 -0
- package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
- package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
- package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
- package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
- package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
- package/src/modules/communication_channels/data/enrichers.ts +413 -0
- package/src/modules/communication_channels/data/entities.ts +546 -0
- package/src/modules/communication_channels/data/extensions.ts +76 -0
- package/src/modules/communication_channels/data/validators.ts +138 -0
- package/src/modules/communication_channels/di.ts +40 -0
- package/src/modules/communication_channels/encryption.ts +44 -0
- package/src/modules/communication_channels/events.ts +122 -0
- package/src/modules/communication_channels/i18n/de.json +138 -0
- package/src/modules/communication_channels/i18n/en.json +138 -0
- package/src/modules/communication_channels/i18n/es.json +138 -0
- package/src/modules/communication_channels/i18n/pl.json +138 -0
- package/src/modules/communication_channels/index.ts +19 -0
- package/src/modules/communication_channels/lib/access-control.ts +110 -0
- package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
- package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
- package/src/modules/communication_channels/lib/adapter.ts +605 -0
- package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
- package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
- package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
- package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
- package/src/modules/communication_channels/lib/email-contact.ts +17 -0
- package/src/modules/communication_channels/lib/email-mime.ts +425 -0
- package/src/modules/communication_channels/lib/error-classification.ts +144 -0
- package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
- package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
- package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
- package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
- package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
- package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
- package/src/modules/communication_channels/lib/provider-health.ts +47 -0
- package/src/modules/communication_channels/lib/push-state.ts +38 -0
- package/src/modules/communication_channels/lib/queue.ts +66 -0
- package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
- package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
- package/src/modules/communication_channels/lib/registry.ts +99 -0
- package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
- package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
- package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
- package/src/modules/communication_channels/lib/system-user.ts +74 -0
- package/src/modules/communication_channels/lib/test-seed.ts +140 -0
- package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
- package/src/modules/communication_channels/lib/thread-token.ts +355 -0
- package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
- package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
- package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
- package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
- package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
- package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
- package/src/modules/communication_channels/notifications.client.ts +50 -0
- package/src/modules/communication_channels/notifications.handlers.ts +86 -0
- package/src/modules/communication_channels/notifications.ts +52 -0
- package/src/modules/communication_channels/setup.ts +158 -0
- package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
- package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
- package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
- package/src/modules/communication_channels/widgets/components.ts +36 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
- package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
- package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
- package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
- package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
- package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
- package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
- package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
- package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
- package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
- package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
- package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
- package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
- package/src/modules/customers/acl.ts +18 -0
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/companies/[id]/route.ts +21 -1
- package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
- package/src/modules/customers/api/interactions/counts/route.ts +10 -0
- package/src/modules/customers/api/interactions/route.ts +51 -5
- package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
- package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
- package/src/modules/customers/api/people/[id]/route.ts +17 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/commands/deals.ts +65 -6
- package/src/modules/customers/commands/interactions.ts +30 -0
- package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
- package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
- package/src/modules/customers/components/detail/DealForm.tsx +2 -1
- package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
- package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
- package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
- package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
- package/src/modules/customers/data/enrichers.ts +252 -1
- package/src/modules/customers/data/entities.ts +46 -1
- package/src/modules/customers/data/extensions.ts +26 -0
- package/src/modules/customers/encryption.ts +11 -0
- package/src/modules/customers/events.ts +4 -0
- package/src/modules/customers/i18n/de.json +41 -0
- package/src/modules/customers/i18n/en.json +41 -0
- package/src/modules/customers/i18n/es.json +41 -0
- package/src/modules/customers/i18n/pl.json +41 -0
- package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
- package/src/modules/customers/lib/kysely.ts +16 -0
- package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
- package/src/modules/customers/lib/personEmailThreads.ts +325 -0
- package/src/modules/customers/lib/visibilityFilter.ts +152 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
- package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
- package/src/modules/customers/setup.ts +1 -0
- package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
- package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
- package/src/modules/integrations/AGENTS.md +9 -0
- package/src/modules/integrations/data/entities.ts +21 -1
- package/src/modules/integrations/lib/credentials-service.ts +49 -13
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
- package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
- package/src/modules/messages/commands/messages.ts +101 -8
- package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
- package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
- package/src/modules/messages/data/entities.ts +11 -0
- package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
- package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
- package/src/modules/messages/widgets/injection-table.ts +29 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import type { CommandBus, CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
|
|
5
|
+
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
6
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
|
+
import { emitCommunicationChannelsEvent } from '../events'
|
|
8
|
+
import { resolveContact } from '../lib/contact-resolver'
|
|
9
|
+
import type { ChannelAdapterRegistry } from '../lib/registry'
|
|
10
|
+
import type { NormalizedInboundMessage } from '../lib/adapter'
|
|
11
|
+
import { matchThread, type ThreadMatch } from '../lib/thread-matcher'
|
|
12
|
+
import {
|
|
13
|
+
ChannelThreadMapping,
|
|
14
|
+
CommunicationChannel,
|
|
15
|
+
ExternalConversation,
|
|
16
|
+
ExternalMessage,
|
|
17
|
+
MessageChannelLink,
|
|
18
|
+
} from '../data/entities'
|
|
19
|
+
import { normalizedInboundMessageSchema } from '../data/validators'
|
|
20
|
+
import { resolveCommunicationChannelsSystemUserId } from '../lib/system-user'
|
|
21
|
+
import { isUniqueViolation } from '../lib/pg-errors'
|
|
22
|
+
|
|
23
|
+
const ingestInputSchema = z.object({
|
|
24
|
+
channelId: z.string().uuid(),
|
|
25
|
+
providerKey: z.string().min(1),
|
|
26
|
+
channelType: z.string().min(1),
|
|
27
|
+
scope: z.object({
|
|
28
|
+
tenantId: z.string().uuid(),
|
|
29
|
+
organizationId: z.string().uuid().nullable(),
|
|
30
|
+
}),
|
|
31
|
+
message: normalizedInboundMessageSchema,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export type IngestInboundMessageInput = z.infer<typeof ingestInputSchema>
|
|
35
|
+
|
|
36
|
+
export type IngestInboundMessageResult = {
|
|
37
|
+
status: 'created' | 'duplicate'
|
|
38
|
+
messageId?: string
|
|
39
|
+
externalConversationId?: string
|
|
40
|
+
externalMessageId?: string
|
|
41
|
+
channelLinkId?: string
|
|
42
|
+
threadMappingId?: string
|
|
43
|
+
contactPersonId?: string | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID = 'communication_channels.message.ingest_inbound'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Idempotently ingest a normalized inbound channel message.
|
|
50
|
+
*
|
|
51
|
+
* Steps (per SPEC-045d §6):
|
|
52
|
+
* 1. Dedup by `(channel_id, external_message_id)` — if a MessageChannelLink already
|
|
53
|
+
* exists for that pair, return `{ status: 'duplicate' }` without side effects.
|
|
54
|
+
* 2. Create or load `ExternalConversation` by `(channel_id, external_conversation_id)`.
|
|
55
|
+
* 3. Create or load `ChannelThreadMapping` (1:1 with ExternalConversation).
|
|
56
|
+
* 4. Resolve CRM contact via adapter + QueryEngine (best-effort).
|
|
57
|
+
* 5. Compose the platform `Message` via `messages.messages.compose` (separate transaction).
|
|
58
|
+
* 6. Create `ExternalMessage` + `MessageChannelLink`.
|
|
59
|
+
* 7. Emit `communication_channels.message.received` (and `.conversation.created` / `.contact.resolved` when applicable).
|
|
60
|
+
*
|
|
61
|
+
* The two-transaction model (compose-message-then-record-link) is acceptable for v1;
|
|
62
|
+
* the link's unique-on-message-id constraint is the safety net against orphans. See
|
|
63
|
+
* the pre-implementation analysis for a discussion of single-transaction alternatives.
|
|
64
|
+
*/
|
|
65
|
+
const ingestInboundMessageCommand: CommandHandler<IngestInboundMessageInput, IngestInboundMessageResult> = {
|
|
66
|
+
id: COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID,
|
|
67
|
+
async execute(rawInput, ctx) {
|
|
68
|
+
const input = ingestInputSchema.parse(rawInput) as IngestInboundMessageInput
|
|
69
|
+
|
|
70
|
+
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
71
|
+
const adapterRegistry = ctx.container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
|
|
72
|
+
const dscope = {
|
|
73
|
+
tenantId: input.scope.tenantId,
|
|
74
|
+
organizationId: input.scope.organizationId ?? null,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// (1) Dedup: short-circuit if we've already processed this provider message.
|
|
78
|
+
// The unique constraint is on `messageId`, not (channel, externalMessageId).
|
|
79
|
+
// We must dedup by joining against ExternalMessage which IS uniquely indexed by
|
|
80
|
+
// (channel_id, external_message_id). Hub-side dedup is the authoritative gate.
|
|
81
|
+
const existingExternal = await findOneWithDecryption(
|
|
82
|
+
em,
|
|
83
|
+
ExternalMessage,
|
|
84
|
+
{
|
|
85
|
+
channelId: input.channelId,
|
|
86
|
+
externalMessageId: input.message.externalMessageId,
|
|
87
|
+
tenantId: input.scope.tenantId,
|
|
88
|
+
organizationId: input.scope.organizationId ?? null,
|
|
89
|
+
},
|
|
90
|
+
undefined,
|
|
91
|
+
dscope,
|
|
92
|
+
)
|
|
93
|
+
if (existingExternal) {
|
|
94
|
+
return {
|
|
95
|
+
status: 'duplicate',
|
|
96
|
+
externalConversationId: existingExternal.conversationId,
|
|
97
|
+
externalMessageId: existingExternal.id,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// (1b) Spec B § Sent-folder dedup.
|
|
102
|
+
//
|
|
103
|
+
// When an outbound message lands in the user's IMAP Sent folder (or
|
|
104
|
+
// when Gmail's "send and archive" deposits it in All Mail), the next
|
|
105
|
+
// poll will re-fetch it from INBOX as if it were inbound. Skip it
|
|
106
|
+
// here using the RFC 5322 `Message-ID` header — we recorded it on the
|
|
107
|
+
// outbound `MessageChannelLink.channelMetadata.messageId` at send time.
|
|
108
|
+
//
|
|
109
|
+
// We dedup ONLY on outbound links (direction='outbound') for the same
|
|
110
|
+
// channel — that way an inbound copy of someone ELSE's email that
|
|
111
|
+
// happens to share a Message-ID is still ingested normally.
|
|
112
|
+
const incomingMessageId = (() => {
|
|
113
|
+
const fromMeta = (input.message.channelMetadata as Record<string, unknown> | undefined)?.messageId
|
|
114
|
+
if (typeof fromMeta === 'string' && fromMeta.length > 0) return fromMeta
|
|
115
|
+
return null
|
|
116
|
+
})()
|
|
117
|
+
if (incomingMessageId) {
|
|
118
|
+
// MikroORM v7 dropped the Knex builder in favour of Kysely/raw SQL.
|
|
119
|
+
// We use a positional-placeholder raw query for the JSONB
|
|
120
|
+
// `channel_metadata->>messageId` comparison.
|
|
121
|
+
try {
|
|
122
|
+
const sentFolderHit = await em.getConnection().execute<Array<{ id: string }>>(
|
|
123
|
+
`SELECT link.id FROM message_channel_links AS link
|
|
124
|
+
INNER JOIN external_conversations AS conv
|
|
125
|
+
ON conv.id = link.external_conversation_id
|
|
126
|
+
WHERE link.tenant_id = ?
|
|
127
|
+
AND ((?::uuid IS NULL AND link.organization_id IS NULL) OR link.organization_id = ?::uuid)
|
|
128
|
+
AND conv.tenant_id = ?
|
|
129
|
+
AND ((?::uuid IS NULL AND conv.organization_id IS NULL) OR conv.organization_id = ?::uuid)
|
|
130
|
+
AND conv.channel_id = ?
|
|
131
|
+
AND link.direction = 'outbound'
|
|
132
|
+
AND link.channel_metadata->>'messageId' = ?
|
|
133
|
+
LIMIT 1`,
|
|
134
|
+
[
|
|
135
|
+
input.scope.tenantId,
|
|
136
|
+
input.scope.organizationId ?? null,
|
|
137
|
+
input.scope.organizationId ?? null,
|
|
138
|
+
input.scope.tenantId,
|
|
139
|
+
input.scope.organizationId ?? null,
|
|
140
|
+
input.scope.organizationId ?? null,
|
|
141
|
+
input.channelId,
|
|
142
|
+
incomingMessageId,
|
|
143
|
+
],
|
|
144
|
+
)
|
|
145
|
+
if (Array.isArray(sentFolderHit) && sentFolderHit.length > 0) {
|
|
146
|
+
return {
|
|
147
|
+
status: 'duplicate',
|
|
148
|
+
externalConversationId: input.message.externalConversationId,
|
|
149
|
+
externalMessageId: input.message.externalMessageId,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (dedupErr) {
|
|
153
|
+
// Sent-folder dedup is best-effort — a failure here must not abort
|
|
154
|
+
// ingest (better a possible duplicate than a lost inbound message).
|
|
155
|
+
console.warn(
|
|
156
|
+
'[communication_channels:ingest-inbound] sent-folder dedup query failed, continuing:',
|
|
157
|
+
dedupErr instanceof Error ? dedupErr.message : dedupErr,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Channel + adapter lookup (the channel must exist + be active).
|
|
163
|
+
const channel = await findOneWithDecryption(
|
|
164
|
+
em,
|
|
165
|
+
CommunicationChannel,
|
|
166
|
+
{
|
|
167
|
+
id: input.channelId,
|
|
168
|
+
tenantId: input.scope.tenantId,
|
|
169
|
+
organizationId: input.scope.organizationId ?? null,
|
|
170
|
+
deletedAt: null,
|
|
171
|
+
},
|
|
172
|
+
undefined,
|
|
173
|
+
dscope,
|
|
174
|
+
)
|
|
175
|
+
if (!channel) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`[internal] Channel ${input.channelId} not found for tenant ${input.scope.tenantId} (or has been deleted)`,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
if (!channel.isActive) {
|
|
181
|
+
throw new Error(`[internal] Channel ${input.channelId} is inactive; refusing to ingest`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const adapter = adapterRegistry.get(input.providerKey)
|
|
185
|
+
if (!adapter) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`[internal] No ChannelAdapter registered for providerKey '${input.providerKey}'. ` +
|
|
188
|
+
'Check that the provider package is enabled in modules.ts.',
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// (2) ExternalConversation upsert by (channel_id, externalConversationId).
|
|
193
|
+
const m = input.message
|
|
194
|
+
let conversation = await findOneWithDecryption(
|
|
195
|
+
em,
|
|
196
|
+
ExternalConversation,
|
|
197
|
+
{
|
|
198
|
+
channelId: input.channelId,
|
|
199
|
+
externalConversationId: m.externalConversationId,
|
|
200
|
+
tenantId: input.scope.tenantId,
|
|
201
|
+
organizationId: input.scope.organizationId ?? null,
|
|
202
|
+
},
|
|
203
|
+
undefined,
|
|
204
|
+
dscope,
|
|
205
|
+
)
|
|
206
|
+
let conversationCreated = false
|
|
207
|
+
if (!conversation) {
|
|
208
|
+
conversation = em.create(ExternalConversation, {
|
|
209
|
+
channelId: input.channelId,
|
|
210
|
+
externalConversationId: m.externalConversationId,
|
|
211
|
+
subject: m.subject ?? null,
|
|
212
|
+
tenantId: input.scope.tenantId,
|
|
213
|
+
organizationId: input.scope.organizationId ?? null,
|
|
214
|
+
lastMessageAt: m.timestamp ?? new Date(),
|
|
215
|
+
})
|
|
216
|
+
em.persist(conversation)
|
|
217
|
+
conversationCreated = true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// (3) ChannelThreadMapping upsert (1:1 with ExternalConversation per tenant).
|
|
221
|
+
let mapping = await findOneWithDecryption(
|
|
222
|
+
em,
|
|
223
|
+
ChannelThreadMapping,
|
|
224
|
+
{
|
|
225
|
+
externalConversationId: conversation.id,
|
|
226
|
+
tenantId: input.scope.tenantId,
|
|
227
|
+
organizationId: input.scope.organizationId ?? null,
|
|
228
|
+
},
|
|
229
|
+
undefined,
|
|
230
|
+
dscope,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
// Last-activity bump on an existing conversation. Applied AFTER the mapping
|
|
234
|
+
// lookup, immediately before the flush, so the scalar mutation and its flush
|
|
235
|
+
// stay adjacent with no query in between (core flush-ordering rule — a query
|
|
236
|
+
// between a scalar mutation and `em.flush()` can drop the change under some
|
|
237
|
+
// flush modes / subscriber configurations).
|
|
238
|
+
if (
|
|
239
|
+
!conversationCreated &&
|
|
240
|
+
m.timestamp &&
|
|
241
|
+
(!conversation.lastMessageAt || m.timestamp > conversation.lastMessageAt)
|
|
242
|
+
) {
|
|
243
|
+
conversation.lastMessageAt = m.timestamp
|
|
244
|
+
}
|
|
245
|
+
// We'll fill `messageThreadId` after composing the platform Message (since the
|
|
246
|
+
// first inbound message becomes the thread root in the messages module).
|
|
247
|
+
await em.flush()
|
|
248
|
+
|
|
249
|
+
// (3b) Spec B — layered thread match.
|
|
250
|
+
//
|
|
251
|
+
// Resolve the inbound message to an existing platform thread using
|
|
252
|
+
// (in priority order):
|
|
253
|
+
// 1. Crypto token in References / In-Reply-To header (high confidence)
|
|
254
|
+
// 2. Crypto token in body hidden span or plain-text marker (high)
|
|
255
|
+
// 3. JWZ on Message-Id ↔ stored `MessageChannelLink.channelMetadata.messageId` (medium)
|
|
256
|
+
// 4. Subject + participants in last 30 days, same channel (low)
|
|
257
|
+
//
|
|
258
|
+
// The matcher returns `null` when nothing hits — in that case we fall
|
|
259
|
+
// back to the existing `ChannelThreadMapping`-by-conversation-id lookup
|
|
260
|
+
// (which also returns null on first-ever inbound, in which case the
|
|
261
|
+
// compose command opens a new thread).
|
|
262
|
+
const metaForMatcher = (m.channelMetadata ?? {}) as Record<string, unknown>
|
|
263
|
+
let threadMatch: ThreadMatch | null = null
|
|
264
|
+
try {
|
|
265
|
+
threadMatch = await matchThread(
|
|
266
|
+
{
|
|
267
|
+
channelId: input.channelId,
|
|
268
|
+
tenantId: input.scope.tenantId,
|
|
269
|
+
organizationId: input.scope.organizationId ?? null,
|
|
270
|
+
messageId: extractStringFromMeta(metaForMatcher, 'messageId') ?? m.externalMessageId,
|
|
271
|
+
inReplyTo:
|
|
272
|
+
m.replyToExternalId ?? extractStringFromMeta(metaForMatcher, 'inReplyTo'),
|
|
273
|
+
references: extractStringArrayFromMeta(metaForMatcher, 'references'),
|
|
274
|
+
subject: m.subject ?? '',
|
|
275
|
+
fromAddress:
|
|
276
|
+
extractStringFromMeta(metaForMatcher, 'from') ?? m.senderIdentifier,
|
|
277
|
+
toAddresses: extractStringArrayFromMeta(metaForMatcher, 'to'),
|
|
278
|
+
ccAddresses: extractStringArrayFromMeta(metaForMatcher, 'cc'),
|
|
279
|
+
bodyPlain: m.bodyFormat === 'html' ? null : m.body ?? null,
|
|
280
|
+
bodyHtml: m.bodyFormat === 'html' ? m.body ?? null : null,
|
|
281
|
+
receivedAt: m.timestamp ?? new Date(),
|
|
282
|
+
},
|
|
283
|
+
{ em },
|
|
284
|
+
)
|
|
285
|
+
} catch (matcherErr) {
|
|
286
|
+
// Matcher failure must not block ingest — fall back to the existing
|
|
287
|
+
// conversation-based thread mapping so the message still lands.
|
|
288
|
+
console.warn(
|
|
289
|
+
'[communication_channels:ingest-inbound] thread matcher failed, falling back to conversation mapping:',
|
|
290
|
+
matcherErr instanceof Error ? matcherErr.message : matcherErr,
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// (4) Contact resolution (best-effort, advisory).
|
|
295
|
+
let contactHint: {
|
|
296
|
+
matchedPersonId?: string | null
|
|
297
|
+
email?: string
|
|
298
|
+
displayName?: string
|
|
299
|
+
} | null = null
|
|
300
|
+
try {
|
|
301
|
+
contactHint = await resolveContact(
|
|
302
|
+
{
|
|
303
|
+
adapter,
|
|
304
|
+
senderIdentifier: m.senderIdentifier,
|
|
305
|
+
senderDisplayName: m.senderDisplayName,
|
|
306
|
+
channelMetadata: m.channelMetadata,
|
|
307
|
+
credentials: {}, // credentials decrypted at the webhook route; resolver doesn't re-fetch
|
|
308
|
+
scope: {
|
|
309
|
+
tenantId: input.scope.tenantId,
|
|
310
|
+
organizationId: input.scope.organizationId ?? input.scope.tenantId,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{ container: ctx.container },
|
|
314
|
+
)
|
|
315
|
+
} catch (contactErr) {
|
|
316
|
+
// Best-effort: contact resolution is advisory and must not abort ingest.
|
|
317
|
+
// Log like the sibling dedup/matcher catches so a misbehaving resolver is
|
|
318
|
+
// visible in operator logs instead of failing silently.
|
|
319
|
+
console.warn(
|
|
320
|
+
'[communication_channels:ingest-inbound] contact resolution failed, continuing without a CRM match:',
|
|
321
|
+
contactErr instanceof Error ? contactErr.message : contactErr,
|
|
322
|
+
)
|
|
323
|
+
contactHint = null
|
|
324
|
+
}
|
|
325
|
+
const matchedPersonId = contactHint?.matchedPersonId ?? null
|
|
326
|
+
if (matchedPersonId && conversation.contactPersonId !== matchedPersonId) {
|
|
327
|
+
conversation.contactPersonId = matchedPersonId
|
|
328
|
+
// Flush this scalar mutation before the system-user lookup below queries the
|
|
329
|
+
// same EntityManager. SPEC-018: a query between a scalar mutation and its
|
|
330
|
+
// flush can silently discard the pending UPDATE (mirrors the lastMessageAt
|
|
331
|
+
// bump above).
|
|
332
|
+
await em.flush()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// (5) Compose the platform Message via the messages module command.
|
|
336
|
+
//
|
|
337
|
+
// Sanitize against the `messages` module's validators (max 50_000 char body
|
|
338
|
+
// + non-empty subject) so real-world emails don't get rejected mid-ingest:
|
|
339
|
+
// - HTML emails routinely exceed 50k (Gmail signatures, marketing
|
|
340
|
+
// templates, RFC 5322 multipart). Truncate with a marker rather than
|
|
341
|
+
// drop the whole message — the full raw body is still in
|
|
342
|
+
// ExternalMessage.rawPayload if needed for forensic / forward use.
|
|
343
|
+
// - Some legitimate messages have no subject (notifications, bounce
|
|
344
|
+
// digests). Substitute a placeholder instead of failing ingest.
|
|
345
|
+
const MAX_COMPOSE_BODY = 50_000
|
|
346
|
+
const TRUNCATE_MARKER =
|
|
347
|
+
'\n\n[…message truncated by Open Mercato — full body preserved in ExternalMessage.rawPayload]'
|
|
348
|
+
const rawBody = m.body ?? ''
|
|
349
|
+
const truncatedBody =
|
|
350
|
+
rawBody.length > MAX_COMPOSE_BODY
|
|
351
|
+
? rawBody.slice(0, MAX_COMPOSE_BODY - TRUNCATE_MARKER.length) + TRUNCATE_MARKER
|
|
352
|
+
: rawBody
|
|
353
|
+
const safeSubject = (m.subject ?? '').trim() || '(no subject)'
|
|
354
|
+
|
|
355
|
+
const composeInput = {
|
|
356
|
+
type: `channel.${input.providerKey}`,
|
|
357
|
+
visibility: 'public' as const,
|
|
358
|
+
sourceEntityType: 'communication_channels.external_conversation',
|
|
359
|
+
sourceEntityId: conversation.id,
|
|
360
|
+
externalEmail: contactHint?.email ?? undefined,
|
|
361
|
+
externalName: contactHint?.displayName ?? m.senderDisplayName,
|
|
362
|
+
recipients: mapping?.assignedUserId
|
|
363
|
+
? [{ userId: mapping.assignedUserId, type: 'to' as const }]
|
|
364
|
+
: [],
|
|
365
|
+
subject: safeSubject,
|
|
366
|
+
body: truncatedBody,
|
|
367
|
+
bodyFormat: (m.bodyFormat === 'html' ? 'text' : m.bodyFormat) as 'text' | 'markdown',
|
|
368
|
+
priority: 'normal' as const,
|
|
369
|
+
sendViaEmail: false,
|
|
370
|
+
// Spec B: matcher-resolved thread id takes priority over the existing
|
|
371
|
+
// conversation-based mapping. Falls through to `mapping?.messageThreadId`
|
|
372
|
+
// when the matcher returned null (no token / JWZ / subject hit).
|
|
373
|
+
parentMessageId: threadMatch?.messageThreadId ?? mapping?.messageThreadId,
|
|
374
|
+
isDraft: false,
|
|
375
|
+
// Stable dedup key so a retried ingest (after a transient failure between
|
|
376
|
+
// compose and the ExternalMessage anchor insert) reuses the message
|
|
377
|
+
// composed by the first attempt instead of duplicating it. Mirrors the
|
|
378
|
+
// (channel, externalMessageId) ExternalMessage anchor's natural key.
|
|
379
|
+
idempotencyKey: m.externalMessageId
|
|
380
|
+
? `cc:${input.channelId}:${m.externalMessageId}`
|
|
381
|
+
: undefined,
|
|
382
|
+
tenantId: input.scope.tenantId,
|
|
383
|
+
organizationId: input.scope.organizationId,
|
|
384
|
+
userId: await resolveCommunicationChannelsSystemUserId(
|
|
385
|
+
em,
|
|
386
|
+
input.scope.tenantId,
|
|
387
|
+
mapping?.assignedUserId ?? null,
|
|
388
|
+
),
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
392
|
+
const composeResult = await commandBus.execute<typeof composeInput, { id: string; threadId: string | null }>(
|
|
393
|
+
'messages.messages.compose',
|
|
394
|
+
{
|
|
395
|
+
input: composeInput,
|
|
396
|
+
ctx: passthroughCommandCtx(ctx, input.scope),
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
const message = composeResult.result
|
|
400
|
+
if (!message?.id) {
|
|
401
|
+
throw new Error('messages.messages.compose did not return a message id')
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// (3 continued) Create or update ChannelThreadMapping now that we have a threadId.
|
|
405
|
+
if (!mapping) {
|
|
406
|
+
mapping = em.create(ChannelThreadMapping, {
|
|
407
|
+
externalConversationId: conversation.id,
|
|
408
|
+
messageThreadId: message.threadId ?? message.id,
|
|
409
|
+
channelId: input.channelId,
|
|
410
|
+
providerKey: input.providerKey,
|
|
411
|
+
externalThreadRef: m.externalConversationId,
|
|
412
|
+
tenantId: input.scope.tenantId,
|
|
413
|
+
organizationId: input.scope.organizationId ?? null,
|
|
414
|
+
})
|
|
415
|
+
em.persist(mapping)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// (6) Create ExternalMessage + MessageChannelLink (hub-side records).
|
|
419
|
+
//
|
|
420
|
+
// The PrimaryKey for both uses `defaultRaw: 'gen_random_uuid()'` — a
|
|
421
|
+
// Postgres-side default that doesn't populate the entity's `id` field
|
|
422
|
+
// until after the INSERT returns. If we let MikroORM generate both, then
|
|
423
|
+
// `em.create(MessageChannelLink, { externalMessageId: externalMessage.id })`
|
|
424
|
+
// reads `undefined` for `externalMessage.id` (it hasn't been flushed yet)
|
|
425
|
+
// and writes NULL to `message_channel_links.external_message_id`,
|
|
426
|
+
// breaking the FK and causing downstream joins to silently return 0 rows.
|
|
427
|
+
//
|
|
428
|
+
// Pre-generating both UUIDs client-side fixes the cross-row reference
|
|
429
|
+
// problem and keeps the single-transaction flush semantics intact.
|
|
430
|
+
const externalMessageRowId = randomUUID()
|
|
431
|
+
const channelLinkRowId = randomUUID()
|
|
432
|
+
const externalMessage = em.create(ExternalMessage, {
|
|
433
|
+
id: externalMessageRowId,
|
|
434
|
+
channelId: input.channelId,
|
|
435
|
+
conversationId: conversation.id,
|
|
436
|
+
externalMessageId: m.externalMessageId,
|
|
437
|
+
direction: 'inbound',
|
|
438
|
+
senderIdentifier: m.senderIdentifier,
|
|
439
|
+
senderDisplayName: m.senderDisplayName ?? null,
|
|
440
|
+
providerTimestamp: m.timestamp,
|
|
441
|
+
tenantId: input.scope.tenantId,
|
|
442
|
+
organizationId: input.scope.organizationId ?? null,
|
|
443
|
+
})
|
|
444
|
+
em.persist(externalMessage)
|
|
445
|
+
|
|
446
|
+
// Spec B: annotate the link with which thread-matcher strategy resolved
|
|
447
|
+
// this message (or `'new-thread'` when matcher returned null and we
|
|
448
|
+
// opened a fresh thread). Surfaced to observability + future UI ("this
|
|
449
|
+
// thread match is low-confidence — confirm or move").
|
|
450
|
+
const matcherAnnotatedMetadata: Record<string, unknown> = {
|
|
451
|
+
...((m.channelMetadata as Record<string, unknown> | undefined) ?? {}),
|
|
452
|
+
threadMatchStrategy: threadMatch?.matchedBy ?? 'new-thread',
|
|
453
|
+
threadMatchConfidence: threadMatch?.confidence ?? 'low',
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const channelLink = em.create(MessageChannelLink, {
|
|
457
|
+
id: channelLinkRowId,
|
|
458
|
+
messageId: message.id,
|
|
459
|
+
externalConversationId: conversation.id,
|
|
460
|
+
externalMessageId: externalMessageRowId,
|
|
461
|
+
providerKey: input.providerKey,
|
|
462
|
+
channelType: input.channelType,
|
|
463
|
+
direction: 'inbound',
|
|
464
|
+
deliveryStatus: 'received',
|
|
465
|
+
channelPayload: m.channelPayload,
|
|
466
|
+
channelContentType: m.channelContentType,
|
|
467
|
+
channelMetadata: matcherAnnotatedMetadata,
|
|
468
|
+
tenantId: input.scope.tenantId,
|
|
469
|
+
organizationId: input.scope.organizationId ?? null,
|
|
470
|
+
})
|
|
471
|
+
em.persist(channelLink)
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
await em.flush()
|
|
475
|
+
} catch (flushErr) {
|
|
476
|
+
// Concurrency guard: the pre-check at (1) is not atomic with this insert,
|
|
477
|
+
// so a poll re-fetch racing a push notification (or two push deliveries)
|
|
478
|
+
// can both pass the check and reach here. The `(channel_id,
|
|
479
|
+
// external_message_id)` unique index rejects the loser with a 23505. Treat
|
|
480
|
+
// that as a duplicate — returning here (instead of throwing) prevents the
|
|
481
|
+
// message from being dead-lettered and retried forever. The winning job
|
|
482
|
+
// already recorded the message + link.
|
|
483
|
+
if (isUniqueViolation(flushErr)) {
|
|
484
|
+
return {
|
|
485
|
+
status: 'duplicate',
|
|
486
|
+
externalConversationId: conversation.id,
|
|
487
|
+
externalMessageId: m.externalMessageId,
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
throw flushErr
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// (7) Emit events — order matters for downstream subscribers.
|
|
494
|
+
if (conversationCreated) {
|
|
495
|
+
await emitCommunicationChannelsEvent(
|
|
496
|
+
'communication_channels.conversation.created',
|
|
497
|
+
{
|
|
498
|
+
conversationId: conversation.id,
|
|
499
|
+
channelId: input.channelId,
|
|
500
|
+
providerKey: input.providerKey,
|
|
501
|
+
channelType: input.channelType,
|
|
502
|
+
externalConversationId: m.externalConversationId,
|
|
503
|
+
tenantId: input.scope.tenantId,
|
|
504
|
+
organizationId: input.scope.organizationId ?? null,
|
|
505
|
+
},
|
|
506
|
+
{ persistent: true },
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
if (matchedPersonId) {
|
|
510
|
+
await emitCommunicationChannelsEvent(
|
|
511
|
+
'communication_channels.contact.resolved',
|
|
512
|
+
{
|
|
513
|
+
conversationId: conversation.id,
|
|
514
|
+
contactPersonId: matchedPersonId,
|
|
515
|
+
providerKey: input.providerKey,
|
|
516
|
+
tenantId: input.scope.tenantId,
|
|
517
|
+
organizationId: input.scope.organizationId ?? null,
|
|
518
|
+
},
|
|
519
|
+
{ persistent: true },
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
await emitCommunicationChannelsEvent(
|
|
523
|
+
'communication_channels.message.received',
|
|
524
|
+
{
|
|
525
|
+
messageId: message.id,
|
|
526
|
+
externalMessageId: externalMessage.id,
|
|
527
|
+
channelLinkId: channelLink.id,
|
|
528
|
+
conversationId: conversation.id,
|
|
529
|
+
channelId: input.channelId,
|
|
530
|
+
providerKey: input.providerKey,
|
|
531
|
+
channelType: input.channelType,
|
|
532
|
+
direction: 'inbound',
|
|
533
|
+
tenantId: input.scope.tenantId,
|
|
534
|
+
organizationId: input.scope.organizationId ?? null,
|
|
535
|
+
},
|
|
536
|
+
{ persistent: true },
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
status: 'created',
|
|
541
|
+
messageId: message.id,
|
|
542
|
+
externalConversationId: conversation.id,
|
|
543
|
+
externalMessageId: externalMessage.id,
|
|
544
|
+
channelLinkId: channelLink.id,
|
|
545
|
+
threadMappingId: mapping.id,
|
|
546
|
+
contactPersonId: matchedPersonId,
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Build a runtime context for the nested `messages.messages.compose` call.
|
|
554
|
+
*
|
|
555
|
+
* The compose command expects a `CommandRuntimeContext`. For inbound webhook
|
|
556
|
+
* processing there is no platform user; we pass `auth: null` and use the tenant
|
|
557
|
+
* scope from our input.
|
|
558
|
+
*/
|
|
559
|
+
function passthroughCommandCtx(
|
|
560
|
+
parent: CommandRuntimeContext,
|
|
561
|
+
scope: IngestInboundMessageInput['scope'],
|
|
562
|
+
): CommandRuntimeContext {
|
|
563
|
+
return {
|
|
564
|
+
container: parent.container,
|
|
565
|
+
auth: null,
|
|
566
|
+
organizationScope: null,
|
|
567
|
+
selectedOrganizationId: scope.organizationId ?? null,
|
|
568
|
+
organizationIds: scope.organizationId ? [scope.organizationId] : null,
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Pull a string value from the provider's `channelMetadata` map. Returns
|
|
574
|
+
* `null` (not `undefined`) when the key is absent or the value isn't a
|
|
575
|
+
* string — keeps the matcher's input shape predictable.
|
|
576
|
+
*/
|
|
577
|
+
function extractStringFromMeta(
|
|
578
|
+
meta: Record<string, unknown>,
|
|
579
|
+
key: string,
|
|
580
|
+
): string | null {
|
|
581
|
+
const value = meta[key]
|
|
582
|
+
if (typeof value === 'string' && value.length > 0) return value
|
|
583
|
+
return null
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Pull a string[] value from the provider's `channelMetadata` map.
|
|
588
|
+
* Filters out non-string entries defensively. Returns an empty array
|
|
589
|
+
* when the key is absent or the value isn't an array.
|
|
590
|
+
*/
|
|
591
|
+
function extractStringArrayFromMeta(
|
|
592
|
+
meta: Record<string, unknown>,
|
|
593
|
+
key: string,
|
|
594
|
+
): string[] {
|
|
595
|
+
const value = meta[key]
|
|
596
|
+
if (!Array.isArray(value)) return []
|
|
597
|
+
return value.filter((item): item is string => typeof item === 'string')
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
registerCommand(ingestInboundMessageCommand)
|
|
601
|
+
|
|
602
|
+
export default ingestInboundMessageCommand
|