@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,228 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OAuth state-cookie helper for the communication_channels hub.
|
|
5
|
+
*
|
|
6
|
+
* **Ported (re-implemented locally), NOT imported, from `packages/enterprise/src/modules/sso/lib/state-cookie.ts`.**
|
|
7
|
+
* Root `AGENTS.md` rule: `@open-mercato/core` MUST NOT import from `@open-mercato/enterprise`.
|
|
8
|
+
*
|
|
9
|
+
* Design (per email integration spec § OSS Independence + § Hub Deltas → Delta 7):
|
|
10
|
+
* - AES-256-GCM payload encryption.
|
|
11
|
+
* - HKDF (SHA-256) key derivation from `OM_HUB_OAUTH_STATE_KEY` (falling back to
|
|
12
|
+
* `KMS_MASTER_KEY`). In production those dedicated keys are required; only in
|
|
13
|
+
* dev/test do we fall back to `JWT_SECRET` so envs that configure one secret
|
|
14
|
+
* still work. Production refuses the `JWT_SECRET` fallback so a session-secret
|
|
15
|
+
* leak cannot also forge OAuth-state cookies.
|
|
16
|
+
* - 5-minute TTL — short window to bound replay surface.
|
|
17
|
+
* - Payload binds the initiating `userId` so the callback rejects state cookies
|
|
18
|
+
* used by a different session.
|
|
19
|
+
*
|
|
20
|
+
* The output is a base64url string that we set on an HttpOnly + SameSite=Lax cookie.
|
|
21
|
+
* Forgery requires the encryption key (KMS-managed in production).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const ALGORITHM = 'aes-256-gcm'
|
|
25
|
+
const IV_LENGTH = 12
|
|
26
|
+
const TAG_LENGTH = 16
|
|
27
|
+
export const COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS = 5 * 60 * 1000
|
|
28
|
+
const HKDF_SALT = Buffer.from('open-mercato-channels-oauth-state-v1')
|
|
29
|
+
const HKDF_INFO = Buffer.from('communication_channels-oauth-state-cookie')
|
|
30
|
+
|
|
31
|
+
export const COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME =
|
|
32
|
+
'om_cc_oauth_state'
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_OAUTH_RETURN_URL = '/backend/profile/communication-channels'
|
|
35
|
+
|
|
36
|
+
/** Errors thrown by the helpers. Stable for tests + route mapping. */
|
|
37
|
+
export class OAuthStateError extends Error {
|
|
38
|
+
override name = 'OAuthStateError'
|
|
39
|
+
constructor(
|
|
40
|
+
message: string,
|
|
41
|
+
readonly code:
|
|
42
|
+
| 'missing_secret'
|
|
43
|
+
| 'invalid_cookie'
|
|
44
|
+
| 'expired'
|
|
45
|
+
| 'user_mismatch'
|
|
46
|
+
| 'decrypt_failed',
|
|
47
|
+
) {
|
|
48
|
+
super(message)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Canonical state-cookie payload — provider-agnostic. Each OAuth provider
|
|
54
|
+
* adapter (Gmail, …) packs its own per-flow nonce / verifier into
|
|
55
|
+
* the `extra` field rather than extending this shape.
|
|
56
|
+
*/
|
|
57
|
+
export interface OAuthStatePayload {
|
|
58
|
+
/** Nonce-like opaque value mirrored into the OAuth `state` query parameter. */
|
|
59
|
+
state: string
|
|
60
|
+
/** Per-flow CSRF nonce, returned alongside the OAuth response. */
|
|
61
|
+
nonce: string
|
|
62
|
+
/** Tenant-scoped user that initiated the flow. Validated on callback. */
|
|
63
|
+
userId: string
|
|
64
|
+
/** Tenant scope so the callback can pin the channel to the same tenant. */
|
|
65
|
+
tenantId: string
|
|
66
|
+
/** Optional organization id for multi-org tenants. */
|
|
67
|
+
organizationId?: string | null
|
|
68
|
+
/** Provider key (e.g. `gmail`) — routes the callback. */
|
|
69
|
+
providerKey: string
|
|
70
|
+
/** Where to redirect on success. Defaults to the profile page in the route. */
|
|
71
|
+
returnUrl?: string
|
|
72
|
+
/** Wall-clock expiry (ms since epoch). */
|
|
73
|
+
expiresAt: number
|
|
74
|
+
/** Provider-specific extras (PKCE code_verifier, scopes, login_hint, …). */
|
|
75
|
+
extra?: Record<string, unknown>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isSafeOAuthReturnUrl(value: string | null | undefined): value is string {
|
|
79
|
+
if (typeof value !== 'string') return false
|
|
80
|
+
if (value.length === 0 || value.length > 2048) return false
|
|
81
|
+
if (!value.startsWith('/') || value.startsWith('//')) return false
|
|
82
|
+
if (value.includes('\\')) return false
|
|
83
|
+
try {
|
|
84
|
+
const base = new URL('https://open-mercato.local')
|
|
85
|
+
const parsed = new URL(value, base)
|
|
86
|
+
return parsed.origin === base.origin && parsed.pathname.startsWith('/')
|
|
87
|
+
} catch {
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function normalizeOAuthReturnUrl(
|
|
93
|
+
value: string | null | undefined,
|
|
94
|
+
fallback: string = DEFAULT_OAUTH_RETURN_URL,
|
|
95
|
+
): string {
|
|
96
|
+
return isSafeOAuthReturnUrl(value) ? value : fallback
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function deriveKey(secret: string): Buffer {
|
|
100
|
+
return Buffer.from(crypto.hkdfSync('sha256', secret, HKDF_SALT, HKDF_INFO, 32))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getSecret(): string {
|
|
104
|
+
const dedicated = process.env.OM_HUB_OAUTH_STATE_KEY ?? process.env.KMS_MASTER_KEY
|
|
105
|
+
if (dedicated) return dedicated
|
|
106
|
+
// No dedicated key configured. Fail closed in production rather than deriving
|
|
107
|
+
// the state-cookie key from JWT_SECRET (the platform session-signing secret) —
|
|
108
|
+
// sharing that key means a JWT_SECRET leak also lets an attacker forge OAuth
|
|
109
|
+
// state cookies, bypassing the userId/tenant binding. In non-production we fall
|
|
110
|
+
// back to JWT_SECRET so dev/test envs that only configure one secret still work.
|
|
111
|
+
if (process.env.NODE_ENV === 'production') {
|
|
112
|
+
throw new Error('[internal] OM_HUB_OAUTH_STATE_KEY or KMS_MASTER_KEY required in production')
|
|
113
|
+
}
|
|
114
|
+
const fallback = process.env.JWT_SECRET
|
|
115
|
+
if (!fallback) {
|
|
116
|
+
throw new OAuthStateError(
|
|
117
|
+
'OM_HUB_OAUTH_STATE_KEY (or fallback KMS_MASTER_KEY / JWT_SECRET) must be set',
|
|
118
|
+
'missing_secret',
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
return fallback
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Encrypt + sign a state payload. Output is a base64url string suitable for a cookie. */
|
|
125
|
+
export function encryptOAuthState(payload: OAuthStatePayload): string {
|
|
126
|
+
const key = deriveKey(getSecret())
|
|
127
|
+
const iv = crypto.randomBytes(IV_LENGTH)
|
|
128
|
+
const json = JSON.stringify(payload)
|
|
129
|
+
|
|
130
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
|
|
131
|
+
const ciphertext = Buffer.concat([cipher.update(json, 'utf8'), cipher.final()])
|
|
132
|
+
const tag = cipher.getAuthTag()
|
|
133
|
+
|
|
134
|
+
return Buffer.concat([iv, tag, ciphertext]).toString('base64url')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Decrypt + verify a state cookie. Returns the payload or `null` if the cookie
|
|
139
|
+
* is malformed / tampered. Returns the payload (NOT null) when the cookie has
|
|
140
|
+
* expired — callers should check `expiresAt` themselves with the verification
|
|
141
|
+
* helper below for stable status codes.
|
|
142
|
+
*/
|
|
143
|
+
export function decryptOAuthState(cookie: string): OAuthStatePayload | null {
|
|
144
|
+
try {
|
|
145
|
+
const key = deriveKey(getSecret())
|
|
146
|
+
const combined = Buffer.from(cookie, 'base64url')
|
|
147
|
+
if (combined.length < IV_LENGTH + TAG_LENGTH) return null
|
|
148
|
+
|
|
149
|
+
const iv = combined.subarray(0, IV_LENGTH)
|
|
150
|
+
const tag = combined.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH)
|
|
151
|
+
const ciphertext = combined.subarray(IV_LENGTH + TAG_LENGTH)
|
|
152
|
+
|
|
153
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
|
|
154
|
+
decipher.setAuthTag(tag)
|
|
155
|
+
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8')
|
|
156
|
+
|
|
157
|
+
return JSON.parse(decrypted) as OAuthStatePayload
|
|
158
|
+
} catch {
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Verify a state cookie against the current session.
|
|
165
|
+
*
|
|
166
|
+
* Throws an {@link OAuthStateError} with a stable `code` field on any check
|
|
167
|
+
* failure so route handlers can map to consistent HTTP responses + redirect
|
|
168
|
+
* flash codes.
|
|
169
|
+
*/
|
|
170
|
+
export function verifyOAuthState(input: {
|
|
171
|
+
cookie: string | null | undefined
|
|
172
|
+
expectedUserId: string
|
|
173
|
+
expectedProviderKey?: string
|
|
174
|
+
expectedState?: string
|
|
175
|
+
now?: number
|
|
176
|
+
}): OAuthStatePayload {
|
|
177
|
+
if (!input.cookie) {
|
|
178
|
+
throw new OAuthStateError('Missing state cookie', 'invalid_cookie')
|
|
179
|
+
}
|
|
180
|
+
const payload = decryptOAuthState(input.cookie)
|
|
181
|
+
if (!payload) {
|
|
182
|
+
throw new OAuthStateError('Invalid state cookie', 'decrypt_failed')
|
|
183
|
+
}
|
|
184
|
+
const now = input.now ?? Date.now()
|
|
185
|
+
if (payload.expiresAt < now) {
|
|
186
|
+
throw new OAuthStateError('State cookie expired', 'expired')
|
|
187
|
+
}
|
|
188
|
+
if (payload.userId !== input.expectedUserId) {
|
|
189
|
+
throw new OAuthStateError('State cookie userId mismatch', 'user_mismatch')
|
|
190
|
+
}
|
|
191
|
+
if (input.expectedProviderKey && payload.providerKey !== input.expectedProviderKey) {
|
|
192
|
+
throw new OAuthStateError('State cookie providerKey mismatch', 'invalid_cookie')
|
|
193
|
+
}
|
|
194
|
+
if (input.expectedState && payload.state !== input.expectedState) {
|
|
195
|
+
throw new OAuthStateError('State cookie state nonce mismatch', 'invalid_cookie')
|
|
196
|
+
}
|
|
197
|
+
return payload
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a fresh state payload + matching `state` query parameter. PKCE
|
|
202
|
+
* verifiers are NOT generated here — the provider adapter decides whether it
|
|
203
|
+
* needs PKCE and packs the verifier into `extra` itself.
|
|
204
|
+
*/
|
|
205
|
+
export function createOAuthState(params: {
|
|
206
|
+
userId: string
|
|
207
|
+
tenantId: string
|
|
208
|
+
organizationId?: string | null
|
|
209
|
+
providerKey: string
|
|
210
|
+
returnUrl?: string
|
|
211
|
+
extra?: Record<string, unknown>
|
|
212
|
+
}): { payload: OAuthStatePayload; cookie: string; stateParam: string } {
|
|
213
|
+
const state = crypto.randomBytes(32).toString('base64url')
|
|
214
|
+
const nonce = crypto.randomBytes(16).toString('base64url')
|
|
215
|
+
const payload: OAuthStatePayload = {
|
|
216
|
+
state,
|
|
217
|
+
nonce,
|
|
218
|
+
userId: params.userId,
|
|
219
|
+
tenantId: params.tenantId,
|
|
220
|
+
organizationId: params.organizationId ?? null,
|
|
221
|
+
providerKey: params.providerKey,
|
|
222
|
+
returnUrl: params.returnUrl,
|
|
223
|
+
extra: params.extra,
|
|
224
|
+
expiresAt: Date.now() + COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS,
|
|
225
|
+
}
|
|
226
|
+
const cookie = encryptOAuthState(payload)
|
|
227
|
+
return { payload, cookie, stateParam: state }
|
|
228
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared OAuth2 token primitives for email channel providers. The authorize-URL
|
|
3
|
+
* shape, PKCE usage, and userinfo handling differ per provider (e.g. Gmail)
|
|
4
|
+
* and stay in each package — but the token response shape, the
|
|
5
|
+
* form-urlencoded token POST, and the expiry computation are identical, so they
|
|
6
|
+
* live here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface OAuthTokenResponse {
|
|
10
|
+
access_token: string
|
|
11
|
+
refresh_token?: string
|
|
12
|
+
expires_in?: number
|
|
13
|
+
scope?: string
|
|
14
|
+
token_type?: string
|
|
15
|
+
id_token?: string
|
|
16
|
+
error?: string
|
|
17
|
+
error_description?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Compute the absolute access-token expiry from `expires_in`, or `undefined` when absent. */
|
|
21
|
+
export function tokenResponseToExpiresAt(
|
|
22
|
+
token: OAuthTokenResponse,
|
|
23
|
+
nowMs: number = Date.now(),
|
|
24
|
+
): Date | undefined {
|
|
25
|
+
if (typeof token.expires_in !== 'number') return undefined
|
|
26
|
+
return new Date(nowMs + token.expires_in * 1000)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Hard timeout for a token endpoint round-trip (ms). Overridable via env. */
|
|
30
|
+
const DEFAULT_OAUTH_TOKEN_TIMEOUT_MS = 10_000
|
|
31
|
+
|
|
32
|
+
function resolveTokenTimeoutMs(): number {
|
|
33
|
+
const fromEnv = Number.parseInt(process.env.OM_OAUTH_TOKEN_TIMEOUT_MS ?? '', 10)
|
|
34
|
+
return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : DEFAULT_OAUTH_TOKEN_TIMEOUT_MS
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* POST a form-urlencoded body to an OAuth token endpoint and return the parsed
|
|
39
|
+
* token response. Throws `${errorLabel}: <reason>` when the endpoint returns a
|
|
40
|
+
* non-2xx status, an `error` field, a non-JSON body, or does not respond within
|
|
41
|
+
* the timeout. Bounding the request matters because token refresh sits on the
|
|
42
|
+
* critical path of every poll/send — a hung token endpoint must fail fast, not
|
|
43
|
+
* block the worker, and a proxy HTML error page must surface the real status
|
|
44
|
+
* rather than a confusing JSON `SyntaxError`.
|
|
45
|
+
*/
|
|
46
|
+
export async function requestOAuthToken(
|
|
47
|
+
tokenUrl: string,
|
|
48
|
+
params: URLSearchParams,
|
|
49
|
+
options: { errorLabel: string },
|
|
50
|
+
): Promise<OAuthTokenResponse> {
|
|
51
|
+
const controller = new AbortController()
|
|
52
|
+
const timeout = setTimeout(() => controller.abort(), resolveTokenTimeoutMs())
|
|
53
|
+
let res: Response
|
|
54
|
+
try {
|
|
55
|
+
res = await fetch(tokenUrl, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
58
|
+
body: params.toString(),
|
|
59
|
+
signal: controller.signal,
|
|
60
|
+
})
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
63
|
+
throw new Error(`${options.errorLabel}: token endpoint timed out`)
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`${options.errorLabel}: ${err instanceof Error ? err.message : String(err)}`)
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timeout)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const raw = await res.text()
|
|
71
|
+
let body: OAuthTokenResponse
|
|
72
|
+
try {
|
|
73
|
+
body = JSON.parse(raw) as OAuthTokenResponse
|
|
74
|
+
} catch {
|
|
75
|
+
throw new Error(`${options.errorLabel}: non-JSON response (status ${res.status})`)
|
|
76
|
+
}
|
|
77
|
+
if (!res.ok || body.error) {
|
|
78
|
+
throw new Error(`${options.errorLabel}: ${body.error_description ?? body.error ?? res.statusText}`)
|
|
79
|
+
}
|
|
80
|
+
return body
|
|
81
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect a Postgres unique-constraint violation (SQLSTATE 23505) regardless of
|
|
3
|
+
* the ORM/driver layer that surfaces it. Shared across the hub's commands and
|
|
4
|
+
* lib helpers so duplicate-insert handling stays consistent module-wide.
|
|
5
|
+
*/
|
|
6
|
+
export function isUniqueViolation(err: unknown): boolean {
|
|
7
|
+
if (!err || typeof err !== 'object') return false
|
|
8
|
+
const code = (err as { code?: string }).code
|
|
9
|
+
if (code === '23505') return true // Postgres unique_violation
|
|
10
|
+
const message = (err as { message?: string }).message
|
|
11
|
+
return typeof message === 'string' && /duplicate key value|unique constraint/i.test(message)
|
|
12
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ZodType } from 'zod'
|
|
2
|
+
import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
|
|
3
|
+
|
|
4
|
+
export type EmailHealthStatus = 'healthy' | 'degraded' | 'unhealthy'
|
|
5
|
+
|
|
6
|
+
export interface EmailHealthCheckResult {
|
|
7
|
+
status: EmailHealthStatus
|
|
8
|
+
message?: string
|
|
9
|
+
details?: Record<string, unknown>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EmailHealthCheck {
|
|
13
|
+
check: (credentials: Record<string, unknown> | null, scope: IntegrationScope) => Promise<EmailHealthCheckResult>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a liveness probe for an OAuth-client-config integration. There is no
|
|
18
|
+
* access token at this layer (the hub passes the tenant-scoped OAuth client
|
|
19
|
+
* config, not per-user channel tokens), so a network call would always 401. The
|
|
20
|
+
* cheap, deterministic probe is: confirm the client config is present and
|
|
21
|
+
* well-formed. Per-user token validity is exercised on the channel itself
|
|
22
|
+
* (send / poll surface `requires_reauth`).
|
|
23
|
+
*/
|
|
24
|
+
export function makeClientConfigHealthCheck<T>(options: {
|
|
25
|
+
schema: ZodType<T>
|
|
26
|
+
providerLabel: string
|
|
27
|
+
healthyDetails?: (parsed: T) => Record<string, unknown>
|
|
28
|
+
}): EmailHealthCheck {
|
|
29
|
+
return {
|
|
30
|
+
async check(credentials) {
|
|
31
|
+
const parsed = options.schema.safeParse(credentials ?? {})
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
const first = parsed.error.issues[0]
|
|
34
|
+
return {
|
|
35
|
+
status: 'unhealthy',
|
|
36
|
+
message: `${options.providerLabel} OAuth client config invalid: ${first?.message ?? 'unknown validation error'}`,
|
|
37
|
+
details: { reason: 'invalid_oauth_client' },
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
status: 'healthy',
|
|
42
|
+
message: `${options.providerLabel} OAuth client configured`,
|
|
43
|
+
details: { clientIdConfigured: true, ...(options.healthyDetails?.(parsed.data) ?? {}) },
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-state keys owned by the push-delivery lifecycle (Spec C), written by the
|
|
3
|
+
* push register/renew commands rather than the sync cursor. They must survive a
|
|
4
|
+
* sync-cursor replacement so watch/subscription renewal keeps working.
|
|
5
|
+
*/
|
|
6
|
+
export const PUSH_STATE_KEYS = [
|
|
7
|
+
'pushStatus',
|
|
8
|
+
'watchExpirationMs',
|
|
9
|
+
'pubsubTopic',
|
|
10
|
+
'lastPushError',
|
|
11
|
+
] as const
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Replace the channel sync-cursor state with the adapter's freshly decoded cursor
|
|
15
|
+
* while carrying forward the hub-owned push keys the cursor does not manage.
|
|
16
|
+
*
|
|
17
|
+
* This MUST be a full replace, never a `{ ...previous, ...next }` spread: adapters
|
|
18
|
+
* signal "drain finished" by OMITTING the mid-drain resumption tokens
|
|
19
|
+
* (`pendingHistoryPageToken`, `pendingMessagesPageToken`) from
|
|
20
|
+
* `next` (they are set to `undefined`, which `JSON.stringify` drops from the encoded
|
|
21
|
+
* cursor). A spread would retain a stale token from `previous` and mis-route the
|
|
22
|
+
* next push/poll cycle, so the poll worker and both push-sync workers share this
|
|
23
|
+
* helper to stay consistent.
|
|
24
|
+
*/
|
|
25
|
+
export function preservePushState(
|
|
26
|
+
previous: unknown,
|
|
27
|
+
next: Record<string, unknown>,
|
|
28
|
+
): Record<string, unknown> {
|
|
29
|
+
const prev =
|
|
30
|
+
previous && typeof previous === 'object' && !Array.isArray(previous)
|
|
31
|
+
? (previous as Record<string, unknown>)
|
|
32
|
+
: {}
|
|
33
|
+
const merged: Record<string, unknown> = { ...next }
|
|
34
|
+
for (const key of PUSH_STATE_KEYS) {
|
|
35
|
+
if (!(key in merged) && key in prev) merged[key] = prev[key]
|
|
36
|
+
}
|
|
37
|
+
return merged
|
|
38
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createModuleQueue, type Queue } from '@open-mercato/queue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Queue helper for the communication_channels hub. Mirrors the
|
|
5
|
+
* shipping_carriers pattern (`getShippingCarrierQueue`) so the route
|
|
6
|
+
* and the worker share the same queue instance.
|
|
7
|
+
*
|
|
8
|
+
* Worker concurrency is also tunable via env (`COMMUNICATION_CHANNELS_QUEUE_CONCURRENCY`)
|
|
9
|
+
* with a sensible default of 10 (per SPEC-045d §6 inbound flow) and a hard ceiling of
|
|
10
|
+
* 20 (ARCHITECTURE §19 caps queue/worker concurrency at 20).
|
|
11
|
+
*/
|
|
12
|
+
const queues = new Map<string, Queue<Record<string, unknown>>>()
|
|
13
|
+
|
|
14
|
+
export function getCommunicationChannelsQueue(queueName: string): Queue<Record<string, unknown>> {
|
|
15
|
+
const existing = queues.get(queueName)
|
|
16
|
+
if (existing) return existing
|
|
17
|
+
|
|
18
|
+
const concurrency = Math.min(
|
|
19
|
+
20,
|
|
20
|
+
Math.max(
|
|
21
|
+
1,
|
|
22
|
+
Number.parseInt(process.env.COMMUNICATION_CHANNELS_QUEUE_CONCURRENCY ?? '10', 10) || 10,
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
const created = createModuleQueue<Record<string, unknown>>(queueName, { concurrency })
|
|
26
|
+
queues.set(queueName, created)
|
|
27
|
+
return created
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Canonical queue names exposed by the hub. */
|
|
31
|
+
export const COMMUNICATION_CHANNELS_QUEUES = {
|
|
32
|
+
inbound: 'communication-channels-inbound',
|
|
33
|
+
outbound: 'communication-channels-outbound',
|
|
34
|
+
reactions: 'communication-channels-reactions',
|
|
35
|
+
/**
|
|
36
|
+
* Per-channel polling queue (email integration spec — Phase 0 Delta 6).
|
|
37
|
+
* Populated by `poll-tick` every scheduler tick; one entry per due channel.
|
|
38
|
+
* Processed by `workers/poll-channel.ts`.
|
|
39
|
+
*/
|
|
40
|
+
poll: 'communication-channels-poll',
|
|
41
|
+
/**
|
|
42
|
+
* Hub-internal tick queue (email integration spec — Phase 0 Delta 6).
|
|
43
|
+
* One job per scheduler tick (60s default); worker enumerates due channels
|
|
44
|
+
* and fans out to the `poll` queue.
|
|
45
|
+
*/
|
|
46
|
+
pollTick: 'communication-channels-poll-tick',
|
|
47
|
+
/**
|
|
48
|
+
* Operator-triggered channel-history import queue (Spec B § Phase B6).
|
|
49
|
+
* One job per `/import-history` call; worker `channel-import-history` runs
|
|
50
|
+
* with concurrency 1 to avoid hammering the provider with parallel scans.
|
|
51
|
+
*/
|
|
52
|
+
importHistory: 'communication-channels-import-history',
|
|
53
|
+
/**
|
|
54
|
+
* Spec C § Phase C2 — Gmail Pub/Sub push delivery. The webhook enqueues
|
|
55
|
+
* one job per verified notification; the worker calls
|
|
56
|
+
* `adapter.applyPushNotification` (which delegates to `history.list`).
|
|
57
|
+
*/
|
|
58
|
+
gmailHistorySync: 'communication-channels-gmail-history-sync',
|
|
59
|
+
/**
|
|
60
|
+
* Spec C § Phase C4 — Renewal cron queues (daily / 2h cadence).
|
|
61
|
+
*/
|
|
62
|
+
gmailRenewWatch: 'communication-channels-gmail-renew-watch',
|
|
63
|
+
} as const
|
|
64
|
+
|
|
65
|
+
export type CommunicationChannelsQueueName =
|
|
66
|
+
(typeof COMMUNICATION_CHANNELS_QUEUES)[keyof typeof COMMUNICATION_CHANNELS_QUEUES]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { InboundReactionEvent } from './adapter'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Discriminated union of reaction-queue job payloads.
|
|
5
|
+
*
|
|
6
|
+
* Split into its own file so command modules can reference these types without
|
|
7
|
+
* forming a circular import with the worker module (which references commands).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type ReactionScope = {
|
|
11
|
+
tenantId: string
|
|
12
|
+
organizationId: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ReactionJobBase = {
|
|
16
|
+
providerKey: string
|
|
17
|
+
channelId: string
|
|
18
|
+
scope: ReactionScope
|
|
19
|
+
/** Attempt number, 1-based. */
|
|
20
|
+
attempt?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ReactionInboundJob = ReactionJobBase & {
|
|
24
|
+
kind: 'inbound'
|
|
25
|
+
channelType: string
|
|
26
|
+
event: InboundReactionEvent
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ReactionOutboundSendJob = ReactionJobBase & {
|
|
30
|
+
kind: 'outbound_send'
|
|
31
|
+
messageId: string
|
|
32
|
+
reactionId: string
|
|
33
|
+
emoji: string
|
|
34
|
+
/** External conversation reference for the provider call (e.g. Slack thread_ts). */
|
|
35
|
+
conversationId?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ReactionOutboundRemoveJob = ReactionJobBase & {
|
|
39
|
+
kind: 'outbound_remove'
|
|
40
|
+
messageId: string
|
|
41
|
+
emoji: string
|
|
42
|
+
externalReactionId: string | null
|
|
43
|
+
conversationId?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ReactionProcessorPayload =
|
|
47
|
+
| ReactionInboundJob
|
|
48
|
+
| ReactionOutboundSendJob
|
|
49
|
+
| ReactionOutboundRemoveJob
|
|
50
|
+
|
|
51
|
+
export const REACTION_PROCESSOR_MAX_ATTEMPTS = 3
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ChannelCapabilities } from './adapter'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reaction semantics — capability-driven decisions about how the hub applies an
|
|
5
|
+
* incoming or outgoing reaction.
|
|
6
|
+
*
|
|
7
|
+
* The platform-side `MessageReaction` table can record any number of rows per
|
|
8
|
+
* (messageId, reactor); the channel-specific semantics decide whether to keep
|
|
9
|
+
* existing reactions when a new one arrives:
|
|
10
|
+
*
|
|
11
|
+
* - **Multi-per-user** (Slack default; `multiReactionPerUser: true`):
|
|
12
|
+
* keep all existing reactions from the same reactor; just insert the new one.
|
|
13
|
+
*
|
|
14
|
+
* - **Single-per-user** (WhatsApp default; `multiReactionPerUser: false`):
|
|
15
|
+
* delete all existing reactions from the same reactor for the same message,
|
|
16
|
+
* then insert the new one. This matches WhatsApp's "one reaction per user"
|
|
17
|
+
* UI behaviour.
|
|
18
|
+
*
|
|
19
|
+
* The helper functions here are pure — they don't talk to the database. The
|
|
20
|
+
* command layer that consumes them is responsible for the actual DELETE/INSERT.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns true when the channel's capabilities allow multiple reactions from
|
|
25
|
+
* the same reactor on the same message (Slack-like).
|
|
26
|
+
*/
|
|
27
|
+
export function allowsMultipleReactionsPerUser(
|
|
28
|
+
capabilities: Pick<ChannelCapabilities, 'multiReactionPerUser'> | null | undefined,
|
|
29
|
+
): boolean {
|
|
30
|
+
// Default to false for safety — a missing capability declaration should not
|
|
31
|
+
// accidentally enable multi-reactions. Real adapters explicitly declare both
|
|
32
|
+
// booleans per SPEC-045d §1.1.
|
|
33
|
+
return capabilities?.multiReactionPerUser === true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Computes what mutation a new inbound `added` reaction implies, given the
|
|
38
|
+
* channel's capabilities. Pure — no DB calls. The caller maps the result to
|
|
39
|
+
* SQL.
|
|
40
|
+
*
|
|
41
|
+
* @returns `'insert'` to just persist the new reaction; `'replace'` to delete
|
|
42
|
+
* every reaction from the same reactor for the same message before inserting.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveInboundAddMutation(
|
|
45
|
+
capabilities: Pick<ChannelCapabilities, 'multiReactionPerUser'> | null | undefined,
|
|
46
|
+
): 'insert' | 'replace' {
|
|
47
|
+
return allowsMultipleReactionsPerUser(capabilities) ? 'insert' : 'replace'
|
|
48
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ChannelAdapter } from './adapter'
|
|
2
|
+
import { validateAdapterCapabilities } from './adapter-compat'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Channel adapter registry — process-level singleton backed by `globalThis`.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the `shipping_carriers/lib/adapter-registry.ts` pattern so that
|
|
8
|
+
* unauthenticated webhook routes (which have not yet built a tenant-scoped DI
|
|
9
|
+
* container) can resolve adapters by `providerKey`. DI consumers continue to
|
|
10
|
+
* resolve the same registry via the `channelAdapterRegistry` binding declared
|
|
11
|
+
* in `di.ts` — that binding is a thin proxy over these functions.
|
|
12
|
+
*
|
|
13
|
+
* The registry validates each adapter at registration time (see
|
|
14
|
+
* `validateAdapterCapabilities`) and refuses duplicate `providerKey` registrations.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const CHANNEL_ADAPTER_REGISTRY_KEY = Symbol.for('@open-mercato/communication-channels/adapter-registry')
|
|
18
|
+
|
|
19
|
+
type GlobalWithChannelRegistry = typeof globalThis & {
|
|
20
|
+
[CHANNEL_ADAPTER_REGISTRY_KEY]?: Map<string, ChannelAdapter>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getRegistryMap(): Map<string, ChannelAdapter> {
|
|
24
|
+
const scope = globalThis as GlobalWithChannelRegistry
|
|
25
|
+
if (!scope[CHANNEL_ADAPTER_REGISTRY_KEY]) {
|
|
26
|
+
scope[CHANNEL_ADAPTER_REGISTRY_KEY] = new Map<string, ChannelAdapter>()
|
|
27
|
+
}
|
|
28
|
+
return scope[CHANNEL_ADAPTER_REGISTRY_KEY]!
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function registerChannelAdapter(adapter: ChannelAdapter): () => void {
|
|
32
|
+
validateAdapterCapabilities(adapter)
|
|
33
|
+
const map = getRegistryMap()
|
|
34
|
+
if (map.has(adapter.providerKey)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`ChannelAdapter '${adapter.providerKey}' is already registered. ` +
|
|
37
|
+
'Each provider package must declare a unique providerKey.',
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
map.set(adapter.providerKey, adapter)
|
|
41
|
+
return () => {
|
|
42
|
+
map.delete(adapter.providerKey)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getChannelAdapter(providerKey: string): ChannelAdapter | undefined {
|
|
47
|
+
return getRegistryMap().get(providerKey)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function listChannelAdapters(): ChannelAdapter[] {
|
|
51
|
+
return Array.from(getRegistryMap().values())
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function listChannelAdapterProviderKeys(): string[] {
|
|
55
|
+
return Array.from(getRegistryMap().keys())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hasChannelAdapter(providerKey: string): boolean {
|
|
59
|
+
return getRegistryMap().has(providerKey)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clear the registry. Primarily for tests that need a fresh registry between cases.
|
|
64
|
+
* NOT for production use — at runtime, adapters are registered once at boot.
|
|
65
|
+
*/
|
|
66
|
+
export function clearChannelAdapters(): void {
|
|
67
|
+
getRegistryMap().clear()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Class wrapper kept for DI consumers and test ergonomics. All instances back
|
|
72
|
+
* onto the same `globalThis` storage so DI consumers and direct module callers
|
|
73
|
+
* see the same registry.
|
|
74
|
+
*/
|
|
75
|
+
export class ChannelAdapterRegistry {
|
|
76
|
+
register(adapter: ChannelAdapter): void {
|
|
77
|
+
registerChannelAdapter(adapter)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get(providerKey: string): ChannelAdapter | undefined {
|
|
81
|
+
return getChannelAdapter(providerKey)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
list(): ChannelAdapter[] {
|
|
85
|
+
return listChannelAdapters()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
providerKeys(): string[] {
|
|
89
|
+
return listChannelAdapterProviderKeys()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
has(providerKey: string): boolean {
|
|
93
|
+
return hasChannelAdapter(providerKey)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clear(): void {
|
|
97
|
+
clearChannelAdapters()
|
|
98
|
+
}
|
|
99
|
+
}
|