@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4397.1.9a65481757
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 +269 -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 +442 -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,571 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
|
+
import { CustomerEntity, CustomerInteraction } from '../data/entities'
|
|
4
|
+
import { findPeopleByAddresses, normalizeAddresses } from './findPeopleByAddresses'
|
|
5
|
+
import { emitCustomersEvent } from '../events'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared implementation for the link-channel-message subscribers.
|
|
9
|
+
*
|
|
10
|
+
* The auto-discovery scanner requires a single string `event` value on each
|
|
11
|
+
* subscriber file. Because we must handle both `communication_channels.message.received`
|
|
12
|
+
* AND `.sent`, we use TWO thin subscriber files (link-channel-message-received.ts
|
|
13
|
+
* and link-channel-message-sent.ts) that both delegate here.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ── Types ─────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Payload emitted by `communication_channels.message.received` and
|
|
20
|
+
* `communication_channels.message.sent`.
|
|
21
|
+
*
|
|
22
|
+
* The canonical field is `channelLinkId` (as emitted by the hub). The alias
|
|
23
|
+
* `messageChannelLinkId` is kept for test stubs and legacy compatibility.
|
|
24
|
+
*/
|
|
25
|
+
type LinkChannelMessagePayload = {
|
|
26
|
+
eventType?: string
|
|
27
|
+
/** UUID of the MessageChannelLink row (canonical hub field name). */
|
|
28
|
+
channelLinkId?: string
|
|
29
|
+
/** Alias used in some older stubs / test payloads. Prefer channelLinkId. */
|
|
30
|
+
messageChannelLinkId?: string
|
|
31
|
+
channelId?: string | null
|
|
32
|
+
tenantId?: string
|
|
33
|
+
organizationId?: string | null
|
|
34
|
+
providerKey?: string | null
|
|
35
|
+
direction?: 'inbound' | 'outbound' | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type SubscriberContext = {
|
|
39
|
+
resolve: <T = unknown>(name: string) => T
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Constants ─────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const POSTGRES_UNIQUE_VIOLATION = '23505'
|
|
45
|
+
|
|
46
|
+
// ── Main handler ──────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export default async function handler(
|
|
49
|
+
payload: LinkChannelMessagePayload,
|
|
50
|
+
ctx: SubscriberContext,
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
// Resolve the link ID from either field name.
|
|
53
|
+
const linkId = payload?.channelLinkId ?? payload?.messageChannelLinkId
|
|
54
|
+
if (typeof linkId !== 'string' || !linkId) return
|
|
55
|
+
|
|
56
|
+
// Fail-closed when tenantId is missing — unscoped queries are unsafe.
|
|
57
|
+
if (typeof payload.tenantId !== 'string' || !payload.tenantId) return
|
|
58
|
+
|
|
59
|
+
const tenantId = payload.tenantId
|
|
60
|
+
const organizationId = payload.organizationId ?? null
|
|
61
|
+
// CustomerEntity and CustomerInteraction are organization-scoped. Without an
|
|
62
|
+
// organization id, fail closed instead of linking tenant-wide by email/thread.
|
|
63
|
+
if (!organizationId) return
|
|
64
|
+
const dscope = { tenantId, organizationId }
|
|
65
|
+
|
|
66
|
+
// em.fork() gives us an isolated identity map for this event.
|
|
67
|
+
const em = (ctx.resolve('em') as EntityManager).fork()
|
|
68
|
+
|
|
69
|
+
// ── (1) Load the MessageChannelLink row ───────────────────────────────
|
|
70
|
+
//
|
|
71
|
+
// The customers module MUST NOT import MessageChannelLink from the
|
|
72
|
+
// communication_channels module (cross-module ORM boundary rule in AGENTS.md).
|
|
73
|
+
// We use the entity class name as a string so MikroORM's identity map resolves
|
|
74
|
+
// it at runtime — the generated entity registry includes the hub's entities.
|
|
75
|
+
// Read through findOneWithDecryption so any encrypted columns on the hub
|
|
76
|
+
// entity are transparently decrypted (per the Encryption section in AGENTS.md).
|
|
77
|
+
const link = (await findOneWithDecryption(
|
|
78
|
+
em,
|
|
79
|
+
'MessageChannelLink' as any,
|
|
80
|
+
{ id: linkId, tenantId, organizationId } as any,
|
|
81
|
+
undefined,
|
|
82
|
+
dscope,
|
|
83
|
+
)) as Record<string, unknown> | null
|
|
84
|
+
|
|
85
|
+
if (!link) return
|
|
86
|
+
|
|
87
|
+
const metaJson = (link.channelMetadata ?? null) as Record<string, unknown> | null
|
|
88
|
+
const payloadJson = (link.channelPayload ?? null) as Record<string, unknown> | null
|
|
89
|
+
|
|
90
|
+
// ── (2) Resolve the channel to get its owner userId ───────────────────
|
|
91
|
+
//
|
|
92
|
+
// The channel.userId is needed for two purposes:
|
|
93
|
+
// - authorUserId on the CustomerInteraction row
|
|
94
|
+
// - default visibility ('private' for user-scoped, 'shared' for tenant-scoped)
|
|
95
|
+
//
|
|
96
|
+
// We look up the channel only when channelId is provided in the event payload.
|
|
97
|
+
let channelUserId: string | null = null
|
|
98
|
+
if (typeof payload.channelId === 'string' && payload.channelId) {
|
|
99
|
+
const channel = (await findOneWithDecryption(
|
|
100
|
+
em,
|
|
101
|
+
'CommunicationChannel' as any,
|
|
102
|
+
{ id: payload.channelId, tenantId, organizationId } as any,
|
|
103
|
+
undefined,
|
|
104
|
+
dscope,
|
|
105
|
+
)) as { userId?: string | null } | null
|
|
106
|
+
channelUserId = channel?.userId ?? null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── (3) Collect recipient addresses ───────────────────────────────────
|
|
110
|
+
//
|
|
111
|
+
// The channel metadata shape differs by provider and direction:
|
|
112
|
+
// - Outbound (Gmail): subject/to/cc/bcc/from are in channelMetadata
|
|
113
|
+
// (merged from GmailEmailNativeMetadata by deliver-outbound-message.ts)
|
|
114
|
+
// - Inbound (Gmail): from/to/cc/bcc/subject are in channelPayload
|
|
115
|
+
// (from NormalizedInboundMessage.channelPayload)
|
|
116
|
+
//
|
|
117
|
+
// We check channelMetadata first (works for outbound + any provider that
|
|
118
|
+
// writes addresses there), then fall back to channelPayload for inbound.
|
|
119
|
+
const rawAddresses: unknown[] = []
|
|
120
|
+
|
|
121
|
+
// For inbound IMAP messages we ALWAYS read addresses from `channelPayload`,
|
|
122
|
+
// not `channelMetadata`. The IMAP adapter stores raw provider headers in
|
|
123
|
+
// `channelMetadata` (where `from` is a JSON-stringified string that's
|
|
124
|
+
// useless for address matching), and the structured normalized addresses
|
|
125
|
+
// in `channelPayload.from` / `.to` / `.cc` / `.bcc`. Reading metadata
|
|
126
|
+
// first poisoned `rawAddresses` with stringified-JSON garbage so the
|
|
127
|
+
// payload-fallback never ran.
|
|
128
|
+
//
|
|
129
|
+
// Priority: payloadJson (canonical normalized shape) → metaJson fallback
|
|
130
|
+
// (for legacy/outbound providers that still write addresses there).
|
|
131
|
+
if (payloadJson) {
|
|
132
|
+
collectAddressField(rawAddresses, payloadJson.from)
|
|
133
|
+
collectAddressField(rawAddresses, payloadJson.to)
|
|
134
|
+
collectAddressField(rawAddresses, payloadJson.cc)
|
|
135
|
+
collectAddressField(rawAddresses, payloadJson.bcc)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (rawAddresses.length === 0 && metaJson) {
|
|
139
|
+
collectAddressField(rawAddresses, metaJson.from)
|
|
140
|
+
collectAddressField(rawAddresses, metaJson.to)
|
|
141
|
+
collectAddressField(rawAddresses, metaJson.cc)
|
|
142
|
+
collectAddressField(rawAddresses, metaJson.bcc)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const normalized = normalizeAddresses(rawAddresses as string[])
|
|
146
|
+
|
|
147
|
+
// ── (4) Optional explicit crmPersonId hint ────────────────────────────
|
|
148
|
+
//
|
|
149
|
+
// The outbound compose route stores `crmPersonId` in channelMetadata so the
|
|
150
|
+
// subscriber can link to the intended Person even if their address isn't in
|
|
151
|
+
// the recipient list (e.g. typo in To: field).
|
|
152
|
+
const crmPersonIdHint =
|
|
153
|
+
typeof metaJson?.crmPersonId === 'string' ? (metaJson!.crmPersonId as string) : null
|
|
154
|
+
|
|
155
|
+
// Defense-in-depth: the crmPersonId hint is written by the compose route
|
|
156
|
+
// (which already verifies tenant ownership), but the subscriber MUST re-verify
|
|
157
|
+
// the hinted Person belongs to THIS tenant before linking. Otherwise a stale or
|
|
158
|
+
// forged hint could attach an interaction to a Person in another tenant.
|
|
159
|
+
let crmPersonId: string | null = null
|
|
160
|
+
if (crmPersonIdHint) {
|
|
161
|
+
const hintedPerson = await findOneWithDecryption(
|
|
162
|
+
em,
|
|
163
|
+
CustomerEntity,
|
|
164
|
+
{ id: crmPersonIdHint, kind: 'person', tenantId, organizationId, deletedAt: null } as any,
|
|
165
|
+
undefined,
|
|
166
|
+
dscope,
|
|
167
|
+
)
|
|
168
|
+
if (hintedPerson) crmPersonId = crmPersonIdHint
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Early exit: no addresses AND no hint → nothing to link.
|
|
172
|
+
if (normalized.length === 0 && !crmPersonId) {
|
|
173
|
+
// Before giving up, try threading-inheritance (TC-CRM-EMAIL-005).
|
|
174
|
+
await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── (5) Resolve People by address ────────────────────────────────────
|
|
179
|
+
const matched = await findPeopleByAddresses(em, normalized, tenantId, organizationId)
|
|
180
|
+
const personIdSet = new Set<string>(matched.map((m) => m.id))
|
|
181
|
+
if (crmPersonId) personIdSet.add(crmPersonId)
|
|
182
|
+
|
|
183
|
+
if (personIdSet.size === 0) {
|
|
184
|
+
// Try threading-inheritance before giving up.
|
|
185
|
+
await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── (6) Determine visibility ──────────────────────────────────────────
|
|
190
|
+
//
|
|
191
|
+
// Priority order:
|
|
192
|
+
// 1. Explicit `crmVisibility` in channelMetadata (set by compose route)
|
|
193
|
+
// 2. Channel owner: 'private' if user-scoped, 'shared' if tenant-scoped
|
|
194
|
+
const linkDirection =
|
|
195
|
+
link.direction === 'inbound' || link.direction === 'outbound' ? link.direction : null
|
|
196
|
+
const visibility: 'private' | 'shared' = resolveVisibility(metaJson, channelUserId, linkDirection)
|
|
197
|
+
|
|
198
|
+
// ── (7) Extract subject / body / timestamps ───────────────────────────
|
|
199
|
+
const subject =
|
|
200
|
+
typeof metaJson?.subject === 'string'
|
|
201
|
+
? (metaJson!.subject as string)
|
|
202
|
+
: typeof payloadJson?.subject === 'string'
|
|
203
|
+
? (payloadJson!.subject as string)
|
|
204
|
+
: null
|
|
205
|
+
|
|
206
|
+
const bodyText =
|
|
207
|
+
typeof metaJson?.bodyText === 'string'
|
|
208
|
+
? (metaJson!.bodyText as string)
|
|
209
|
+
: typeof payloadJson?.text === 'string'
|
|
210
|
+
? (payloadJson!.text as string)
|
|
211
|
+
: null
|
|
212
|
+
|
|
213
|
+
const occurredAt = link.createdAt instanceof Date ? link.createdAt : new Date()
|
|
214
|
+
const providerKey =
|
|
215
|
+
typeof link.providerKey === 'string' ? (link.providerKey as string) : null
|
|
216
|
+
|
|
217
|
+
// ── (8) INSERT one CustomerInteraction per matched Person ─────────────
|
|
218
|
+
await persistInteractions(
|
|
219
|
+
em,
|
|
220
|
+
personIdSet,
|
|
221
|
+
{
|
|
222
|
+
linkId,
|
|
223
|
+
tenantId,
|
|
224
|
+
organizationId,
|
|
225
|
+
interactionType: 'email',
|
|
226
|
+
title: subject,
|
|
227
|
+
body: bodyText,
|
|
228
|
+
authorUserId: channelUserId,
|
|
229
|
+
occurredAt,
|
|
230
|
+
visibility,
|
|
231
|
+
channelProviderKey: providerKey,
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Threading-inheritance fallback ────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* When direct address matching finds 0 people AND crmPersonId is absent, look
|
|
240
|
+
* up parent message references from channelMetadata (`inReplyTo` + `references`).
|
|
241
|
+
*
|
|
242
|
+
* For each reference (an RFC2822 Message-ID), find a prior MessageChannelLink
|
|
243
|
+
* in this tenant whose `channelMetadata.messageId` matches. For each such
|
|
244
|
+
* parent link, find any existing email CustomerInteraction rows where
|
|
245
|
+
* `externalMessageId = parent.id` — and link THIS message to the same Persons.
|
|
246
|
+
*
|
|
247
|
+
* This enables TC-CRM-EMAIL-005: a reply from an unknown address is still
|
|
248
|
+
* attached to alice's timeline because the original thread was.
|
|
249
|
+
*/
|
|
250
|
+
async function handleThreadingInheritance(
|
|
251
|
+
em: EntityManager,
|
|
252
|
+
_link: Record<string, unknown>,
|
|
253
|
+
linkId: string,
|
|
254
|
+
tenantId: string,
|
|
255
|
+
organizationId: string | null,
|
|
256
|
+
channelUserId: string | null,
|
|
257
|
+
metaJson: Record<string, unknown> | null,
|
|
258
|
+
payloadJson: Record<string, unknown> | null,
|
|
259
|
+
): Promise<void> {
|
|
260
|
+
// ── Primary: inherit Person(s) from the hub's authoritative thread ──────
|
|
261
|
+
//
|
|
262
|
+
// The hub threads an inbound reply into the same `messages.message.thread_id`
|
|
263
|
+
// as the outbound that started the conversation (via the thread token /
|
|
264
|
+
// subject+participants matcher). That outbound is already linked to the CRM
|
|
265
|
+
// Person (through the `crmPersonId` hint set by the compose route). So a reply
|
|
266
|
+
// inherits the Person of any existing email interaction in the same thread.
|
|
267
|
+
//
|
|
268
|
+
// This is the dependable join where the alternatives are not:
|
|
269
|
+
// - Address matching (`findPeopleByAddresses`) filters the *encrypted*
|
|
270
|
+
// `primary_email` column by a plaintext value, which never matches when
|
|
271
|
+
// tenant data encryption is on (ciphertext != plaintext).
|
|
272
|
+
// - RFC Message-IDs are rewritten by some providers (e.g. Gmail) on send,
|
|
273
|
+
// so the legacy In-Reply-To/References inheritance below also misses.
|
|
274
|
+
// The hub thread id survives both, so we resolve by it first.
|
|
275
|
+
const inboundMessageId = typeof _link.messageId === 'string' ? _link.messageId : null
|
|
276
|
+
if (inboundMessageId && organizationId) {
|
|
277
|
+
const threadPersonRows = (await em.getConnection().execute(
|
|
278
|
+
`SELECT DISTINCT ci.entity_id AS entity_id
|
|
279
|
+
FROM messages inbound_m
|
|
280
|
+
JOIN messages thread_m ON thread_m.thread_id = inbound_m.thread_id
|
|
281
|
+
JOIN message_channel_links mcl ON mcl.message_id = thread_m.id
|
|
282
|
+
JOIN customer_interactions ci ON ci.external_message_id = mcl.id
|
|
283
|
+
WHERE inbound_m.id = ?
|
|
284
|
+
AND inbound_m.tenant_id = ?
|
|
285
|
+
AND inbound_m.organization_id = ?
|
|
286
|
+
AND inbound_m.deleted_at IS NULL
|
|
287
|
+
AND inbound_m.thread_id IS NOT NULL
|
|
288
|
+
AND thread_m.tenant_id = ?
|
|
289
|
+
AND thread_m.organization_id = ?
|
|
290
|
+
AND thread_m.deleted_at IS NULL
|
|
291
|
+
AND mcl.tenant_id = ?
|
|
292
|
+
AND mcl.organization_id = ?
|
|
293
|
+
AND ci.tenant_id = ?
|
|
294
|
+
AND ci.organization_id = ?
|
|
295
|
+
AND ci.interaction_type = 'email'
|
|
296
|
+
AND ci.deleted_at IS NULL
|
|
297
|
+
AND ci.entity_id IS NOT NULL
|
|
298
|
+
LIMIT 200`,
|
|
299
|
+
[
|
|
300
|
+
inboundMessageId,
|
|
301
|
+
tenantId,
|
|
302
|
+
organizationId,
|
|
303
|
+
tenantId,
|
|
304
|
+
organizationId,
|
|
305
|
+
tenantId,
|
|
306
|
+
organizationId,
|
|
307
|
+
tenantId,
|
|
308
|
+
organizationId,
|
|
309
|
+
],
|
|
310
|
+
)) as Array<{ entity_id: string }>
|
|
311
|
+
const threadPersonIds = new Set<string>(
|
|
312
|
+
threadPersonRows.map((row) => row.entity_id).filter((id): id is string => !!id),
|
|
313
|
+
)
|
|
314
|
+
if (threadPersonIds.size > 0) {
|
|
315
|
+
const subject =
|
|
316
|
+
typeof metaJson?.subject === 'string'
|
|
317
|
+
? (metaJson.subject as string)
|
|
318
|
+
: typeof payloadJson?.subject === 'string'
|
|
319
|
+
? (payloadJson.subject as string)
|
|
320
|
+
: null
|
|
321
|
+
const bodyText =
|
|
322
|
+
typeof payloadJson?.text === 'string'
|
|
323
|
+
? (payloadJson.text as string)
|
|
324
|
+
: typeof metaJson?.bodyText === 'string'
|
|
325
|
+
? (metaJson.bodyText as string)
|
|
326
|
+
: null
|
|
327
|
+
const occurredAt = _link.createdAt instanceof Date ? (_link.createdAt as Date) : new Date()
|
|
328
|
+
const providerKey = typeof _link.providerKey === 'string' ? (_link.providerKey as string) : null
|
|
329
|
+
await persistInteractions(em, threadPersonIds, {
|
|
330
|
+
linkId,
|
|
331
|
+
tenantId,
|
|
332
|
+
organizationId,
|
|
333
|
+
interactionType: 'email',
|
|
334
|
+
title: subject,
|
|
335
|
+
body: bodyText,
|
|
336
|
+
authorUserId: channelUserId,
|
|
337
|
+
occurredAt,
|
|
338
|
+
visibility: channelUserId ? 'private' : 'shared',
|
|
339
|
+
channelProviderKey: providerKey,
|
|
340
|
+
})
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Fallback: legacy In-Reply-To / References Message-ID inheritance ─────
|
|
346
|
+
// Collect reference message-ids from inReplyTo + references
|
|
347
|
+
const refIds: string[] = []
|
|
348
|
+
const inReplyTo =
|
|
349
|
+
typeof metaJson?.inReplyTo === 'string'
|
|
350
|
+
? stripBrackets(metaJson!.inReplyTo as string)
|
|
351
|
+
: typeof payloadJson?.inReplyTo === 'string'
|
|
352
|
+
? stripBrackets(payloadJson!.inReplyTo as string)
|
|
353
|
+
: null
|
|
354
|
+
if (inReplyTo) refIds.push(inReplyTo)
|
|
355
|
+
|
|
356
|
+
const refs =
|
|
357
|
+
Array.isArray(metaJson?.references)
|
|
358
|
+
? (metaJson!.references as unknown[])
|
|
359
|
+
: Array.isArray(payloadJson?.references)
|
|
360
|
+
? (payloadJson!.references as unknown[])
|
|
361
|
+
: []
|
|
362
|
+
for (const r of refs) {
|
|
363
|
+
if (typeof r === 'string') {
|
|
364
|
+
const stripped = stripBrackets(r)
|
|
365
|
+
if (stripped && !refIds.includes(stripped)) refIds.push(stripped)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (refIds.length === 0) return
|
|
370
|
+
|
|
371
|
+
// Find parent MessageChannelLinks whose channelMetadata.messageId is in refIds.
|
|
372
|
+
// Bounded lookup: narrow to the candidate Message-IDs directly in SQL instead
|
|
373
|
+
// of loading every link in the tenant and filtering in JS (which does not scale
|
|
374
|
+
// on a busy mailbox — this path runs for every inbound reply that didn't match a
|
|
375
|
+
// Person by address). channel_metadata is NOT an encrypted column, so a
|
|
376
|
+
// parameterized raw query is safe; we match both bracketed (`<id>`) and
|
|
377
|
+
// unbracketed (`id`) Message-ID storage forms, capped to a sane page.
|
|
378
|
+
const dscope = { tenantId, organizationId }
|
|
379
|
+
const messageIdCandidates = Array.from(
|
|
380
|
+
new Set(refIds.flatMap((ref) => [ref, `<${ref}>`])),
|
|
381
|
+
)
|
|
382
|
+
const placeholders = messageIdCandidates.map(() => '?').join(', ')
|
|
383
|
+
const parentLinkRows = (await em.getConnection().execute(
|
|
384
|
+
`SELECT id FROM message_channel_links
|
|
385
|
+
WHERE tenant_id = ?
|
|
386
|
+
AND organization_id = ?
|
|
387
|
+
AND channel_metadata->>'messageId' IN (${placeholders})
|
|
388
|
+
LIMIT 200`,
|
|
389
|
+
[tenantId, organizationId, ...messageIdCandidates],
|
|
390
|
+
)) as Array<{ id: string }>
|
|
391
|
+
|
|
392
|
+
const matchedParentIds = parentLinkRows.map((parentLink) => parentLink.id)
|
|
393
|
+
|
|
394
|
+
if (matchedParentIds.length === 0) return
|
|
395
|
+
|
|
396
|
+
// Find existing email CustomerInteraction rows for those parent links.
|
|
397
|
+
const parentInteractions = (await findWithDecryption(
|
|
398
|
+
em,
|
|
399
|
+
CustomerInteraction,
|
|
400
|
+
{
|
|
401
|
+
externalMessageId: { $in: matchedParentIds },
|
|
402
|
+
tenantId,
|
|
403
|
+
organizationId,
|
|
404
|
+
interactionType: 'email',
|
|
405
|
+
deletedAt: null,
|
|
406
|
+
} as any,
|
|
407
|
+
undefined,
|
|
408
|
+
dscope,
|
|
409
|
+
)) as Array<{ entity: { id: string } }>
|
|
410
|
+
|
|
411
|
+
const inheritedPersonIdSet = new Set<string>(
|
|
412
|
+
parentInteractions.map((pi) => pi.entity?.id).filter(Boolean) as string[],
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if (inheritedPersonIdSet.size === 0) return
|
|
416
|
+
|
|
417
|
+
const visibility: 'private' | 'shared' = channelUserId ? 'private' : 'shared'
|
|
418
|
+
const link = _link
|
|
419
|
+
const inheritedMeta = (link.channelMetadata ?? null) as Record<string, unknown> | null
|
|
420
|
+
const subject =
|
|
421
|
+
typeof inheritedMeta?.subject === 'string' ? (inheritedMeta.subject as string) : null
|
|
422
|
+
const occurredAt = link.createdAt instanceof Date ? (link.createdAt as Date) : new Date()
|
|
423
|
+
const providerKey =
|
|
424
|
+
typeof link.providerKey === 'string' ? (link.providerKey as string) : null
|
|
425
|
+
|
|
426
|
+
await persistInteractions(
|
|
427
|
+
em,
|
|
428
|
+
inheritedPersonIdSet,
|
|
429
|
+
{
|
|
430
|
+
linkId,
|
|
431
|
+
tenantId,
|
|
432
|
+
organizationId,
|
|
433
|
+
interactionType: 'email',
|
|
434
|
+
title: subject,
|
|
435
|
+
body: null,
|
|
436
|
+
authorUserId: channelUserId,
|
|
437
|
+
occurredAt,
|
|
438
|
+
visibility,
|
|
439
|
+
channelProviderKey: providerKey,
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
interface InteractionData {
|
|
447
|
+
linkId: string
|
|
448
|
+
tenantId: string
|
|
449
|
+
organizationId: string | null
|
|
450
|
+
interactionType: string
|
|
451
|
+
title: string | null
|
|
452
|
+
body: string | null
|
|
453
|
+
authorUserId: string | null
|
|
454
|
+
occurredAt: Date
|
|
455
|
+
visibility: 'private' | 'shared'
|
|
456
|
+
channelProviderKey: string | null
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function persistInteractions(
|
|
460
|
+
em: EntityManager,
|
|
461
|
+
personIdSet: Set<string>,
|
|
462
|
+
data: InteractionData,
|
|
463
|
+
): Promise<void> {
|
|
464
|
+
for (const personId of personIdSet) {
|
|
465
|
+
// Fork per row so a unique-violation on one row doesn't poison the identity
|
|
466
|
+
// map for subsequent rows. The parent em's connection pool is reused.
|
|
467
|
+
const rowEm = em.fork()
|
|
468
|
+
// Use em.getReference so we satisfy the ManyToOne relation without a
|
|
469
|
+
// redundant SELECT — MikroORM will flush the FK column directly.
|
|
470
|
+
const entityRef = rowEm.getReference(CustomerEntity, personId)
|
|
471
|
+
const interaction = rowEm.create(CustomerInteraction, {
|
|
472
|
+
tenantId: data.tenantId,
|
|
473
|
+
organizationId: data.organizationId,
|
|
474
|
+
entity: entityRef,
|
|
475
|
+
interactionType: data.interactionType,
|
|
476
|
+
title: data.title,
|
|
477
|
+
body: data.body,
|
|
478
|
+
authorUserId: data.authorUserId,
|
|
479
|
+
occurredAt: data.occurredAt,
|
|
480
|
+
// Emails are logged AFTER they're sent/received — they are not
|
|
481
|
+
// scheduled work. Without an explicit status the entity default
|
|
482
|
+
// ('planned') combined with a past `occurredAt` makes the activity
|
|
483
|
+
// timeline render the email as "overdue", which is the wrong UX.
|
|
484
|
+
// The canonical "completed" value is `'done'` per
|
|
485
|
+
// `validators.ts:interactionStatusValues = ['planned', 'done', 'canceled']`
|
|
486
|
+
// — `'completed'` is a legacy spelling that the enricher accepts
|
|
487
|
+
// defensively but the activity timeline `isOverdue` predicate does NOT.
|
|
488
|
+
status: 'done',
|
|
489
|
+
externalMessageId: data.linkId,
|
|
490
|
+
visibility: data.visibility,
|
|
491
|
+
channelProviderKey: data.channelProviderKey,
|
|
492
|
+
} as any)
|
|
493
|
+
try {
|
|
494
|
+
await rowEm.flush()
|
|
495
|
+
} catch (err) {
|
|
496
|
+
// Idempotency: swallow unique-violation on (entity_id, external_message_id).
|
|
497
|
+
// The partial unique index `customer_interactions_email_dedupe_uq` guarantees
|
|
498
|
+
// at-most-once semantics across retries.
|
|
499
|
+
const code = (err as { code?: string }).code
|
|
500
|
+
if (code !== POSTGRES_UNIQUE_VIOLATION) throw err
|
|
501
|
+
// Duplicate (retried delivery) — already linked, skip the refresh signal.
|
|
502
|
+
continue
|
|
503
|
+
}
|
|
504
|
+
// Live-refresh signal for the CRM Person page (clientBroadcast → SSE). The
|
|
505
|
+
// interaction is already persisted, so a failed emit must not abort linking
|
|
506
|
+
// or fail this persistent (retried) subscriber.
|
|
507
|
+
try {
|
|
508
|
+
await emitCustomersEvent('customers.email.linked', {
|
|
509
|
+
personId,
|
|
510
|
+
interactionId: interaction.id,
|
|
511
|
+
tenantId: data.tenantId,
|
|
512
|
+
organizationId: data.organizationId,
|
|
513
|
+
})
|
|
514
|
+
} catch {
|
|
515
|
+
/* swallow — UI refresh signal is non-critical */
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function resolveVisibility(
|
|
521
|
+
metaJson: Record<string, unknown> | null,
|
|
522
|
+
channelUserId: string | null,
|
|
523
|
+
direction: 'inbound' | 'outbound' | null,
|
|
524
|
+
): 'private' | 'shared' {
|
|
525
|
+
// The explicit `crmVisibility` override is written ONLY by the outbound compose
|
|
526
|
+
// route. Inbound `channelMetadata` is provider-derived (and therefore attacker-
|
|
527
|
+
// influenceable), so it MUST NOT be able to downgrade a user-owned channel's
|
|
528
|
+
// inbound mail from private → shared. Honor the override on outbound only.
|
|
529
|
+
if (direction === 'outbound') {
|
|
530
|
+
if (metaJson?.crmVisibility === 'shared') return 'shared'
|
|
531
|
+
if (metaJson?.crmVisibility === 'private') return 'private'
|
|
532
|
+
}
|
|
533
|
+
// Tenant-scoped channels (no userId) → shared; user-scoped → private.
|
|
534
|
+
return channelUserId ? 'private' : 'shared'
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Collect email address strings from a field that may be:
|
|
539
|
+
* - a plain string: 'alice@example.com'
|
|
540
|
+
* - an array of strings: ['alice@example.com', 'bob@example.com']
|
|
541
|
+
* - an object with an `address` property: { address: 'alice@example.com', name: 'Alice' }
|
|
542
|
+
* - an array of such objects
|
|
543
|
+
*/
|
|
544
|
+
function collectAddressField(out: unknown[], value: unknown): void {
|
|
545
|
+
if (!value) return
|
|
546
|
+
if (typeof value === 'string') {
|
|
547
|
+
out.push(value)
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
if (Array.isArray(value)) {
|
|
551
|
+
for (const item of value) {
|
|
552
|
+
if (typeof item === 'string') {
|
|
553
|
+
out.push(item)
|
|
554
|
+
} else if (item && typeof item === 'object' && typeof (item as Record<string, unknown>).address === 'string') {
|
|
555
|
+
out.push((item as Record<string, unknown>).address as string)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
if (typeof value === 'object' && typeof (value as Record<string, unknown>).address === 'string') {
|
|
561
|
+
out.push((value as Record<string, unknown>).address as string)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function stripBrackets(value: string): string {
|
|
566
|
+
const trimmed = value.trim()
|
|
567
|
+
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
568
|
+
return trimmed.slice(1, -1)
|
|
569
|
+
}
|
|
570
|
+
return trimmed
|
|
571
|
+
}
|