@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
- package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
- package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
- package/dist/generated/entities/channel_thread_token/index.js +17 -0
- package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
- package/dist/generated/entities/communication_channel/index.js +43 -0
- package/dist/generated/entities/communication_channel/index.js.map +7 -0
- package/dist/generated/entities/customer_interaction/index.js +4 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entities/external_conversation/index.js +25 -0
- package/dist/generated/entities/external_conversation/index.js.map +7 -0
- package/dist/generated/entities/external_message/index.js +25 -0
- package/dist/generated/entities/external_message/index.js.map +7 -0
- package/dist/generated/entities/integration_credentials/index.js +3 -1
- package/dist/generated/entities/integration_credentials/index.js.map +2 -2
- package/dist/generated/entities/message/index.js +2 -0
- package/dist/generated/entities/message/index.js.map +2 -2
- package/dist/generated/entities/message_channel_link/index.js +33 -0
- package/dist/generated/entities/message_channel_link/index.js.map +7 -0
- package/dist/generated/entities/message_reaction/index.js +25 -0
- package/dist/generated/entities/message_reaction/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +11 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +117 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/authFixtures.js +2 -1
- package/dist/helpers/integration/authFixtures.js.map +2 -2
- package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
- package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
- package/dist/modules/communication_channels/acl.js +47 -0
- package/dist/modules/communication_channels/acl.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
- package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
- package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
- package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
- package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
- package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
- package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
- package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
- package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
- package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
- package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
- package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
- package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
- package/dist/modules/communication_channels/commands/interceptors.js +68 -0
- package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
- package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-register.js +146 -0
- package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-renew.js +23 -0
- package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
- package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
- package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
- package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
- package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
- package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
- package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
- package/dist/modules/communication_channels/data/enrichers.js +286 -0
- package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
- package/dist/modules/communication_channels/data/entities.js +447 -0
- package/dist/modules/communication_channels/data/entities.js.map +7 -0
- package/dist/modules/communication_channels/data/extensions.js +67 -0
- package/dist/modules/communication_channels/data/extensions.js.map +7 -0
- package/dist/modules/communication_channels/data/validators.js +123 -0
- package/dist/modules/communication_channels/data/validators.js.map +7 -0
- package/dist/modules/communication_channels/di.js +35 -0
- package/dist/modules/communication_channels/di.js.map +7 -0
- package/dist/modules/communication_channels/encryption.js +12 -0
- package/dist/modules/communication_channels/encryption.js.map +7 -0
- package/dist/modules/communication_channels/events.js +124 -0
- package/dist/modules/communication_channels/events.js.map +7 -0
- package/dist/modules/communication_channels/index.js +20 -0
- package/dist/modules/communication_channels/index.js.map +7 -0
- package/dist/modules/communication_channels/lib/access-control.js +43 -0
- package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
- package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
- package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
- package/dist/modules/communication_channels/lib/adapter.js +1 -0
- package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
- package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
- package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
- package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
- package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
- package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
- package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
- package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-contact.js +14 -0
- package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
- package/dist/modules/communication_channels/lib/email-mime.js +259 -0
- package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
- package/dist/modules/communication_channels/lib/error-classification.js +101 -0
- package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
- package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
- package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
- package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
- package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
- package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
- package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
- package/dist/modules/communication_channels/lib/provider-health.js +24 -0
- package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-state.js +19 -0
- package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
- package/dist/modules/communication_channels/lib/queue.js +54 -0
- package/dist/modules/communication_channels/lib/queue.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
- package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
- package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
- package/dist/modules/communication_channels/lib/registry.js +67 -0
- package/dist/modules/communication_channels/lib/registry.js.map +7 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
- package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
- package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
- package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
- package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/system-user.js +22 -0
- package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +68 -0
- package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
- package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
- package/dist/modules/communication_channels/lib/thread-token.js +219 -0
- package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
- package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
- package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
- package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
- package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/notifications.client.js +51 -0
- package/dist/modules/communication_channels/notifications.client.js.map +7 -0
- package/dist/modules/communication_channels/notifications.handlers.js +53 -0
- package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
- package/dist/modules/communication_channels/notifications.js +56 -0
- package/dist/modules/communication_channels/notifications.js.map +7 -0
- package/dist/modules/communication_channels/setup.js +105 -0
- package/dist/modules/communication_channels/setup.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
- package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
- package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
- package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
- package/dist/modules/communication_channels/widgets/components.js +7 -0
- package/dist/modules/communication_channels/widgets/components.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
- package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
- package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
- package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
- package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
- package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
- package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
- package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
- package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
- package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
- package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
- package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
- package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
- package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
- package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
- package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
- package/dist/modules/customers/acl.js +18 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/activities/route.js +9 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/customers/api/companies/[id]/route.js +18 -7
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
- package/dist/modules/customers/api/interactions/counts/route.js +6 -0
- package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +26 -7
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
- package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
- package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
- package/dist/modules/customers/api/people/[id]/route.js +12 -4
- package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +46 -5
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +16 -0
- package/dist/modules/customers/commands/interactions.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
- package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
- package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +2 -1
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealsSection.js +10 -0
- package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
- package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
- package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
- package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
- package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
- package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
- package/dist/modules/customers/data/enrichers.js +133 -2
- package/dist/modules/customers/data/enrichers.js.map +2 -2
- package/dist/modules/customers/data/entities.js +18 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/extensions.js +16 -0
- package/dist/modules/customers/data/extensions.js.map +7 -0
- package/dist/modules/customers/encryption.js +11 -0
- package/dist/modules/customers/encryption.js.map +2 -2
- package/dist/modules/customers/events.js +4 -1
- package/dist/modules/customers/events.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
- package/dist/modules/customers/lib/kysely.js.map +2 -2
- package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
- package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
- package/dist/modules/customers/lib/personEmailThreads.js +205 -0
- package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
- package/dist/modules/customers/lib/visibilityFilter.js +51 -0
- package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
- package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
- package/dist/modules/customers/setup.js +2 -1
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
- package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
- package/dist/modules/integrations/data/entities.js +8 -1
- package/dist/modules/integrations/data/entities.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +29 -14
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
- package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
- package/dist/modules/messages/commands/messages.js +70 -8
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
- package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
- package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
- package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
- package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
- package/dist/modules/messages/data/entities.js +8 -1
- package/dist/modules/messages/data/entities.js.map +2 -2
- package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
- package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
- package/dist/modules/messages/widgets/injection-table.js +7 -0
- package/dist/modules/messages/widgets/injection-table.js.map +7 -0
- package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
- package/generated/entities/channel_thread_mapping/index.ts +11 -0
- package/generated/entities/channel_thread_token/index.ts +7 -0
- package/generated/entities/communication_channel/index.ts +20 -0
- package/generated/entities/customer_interaction/index.ts +2 -0
- package/generated/entities/external_conversation/index.ts +11 -0
- package/generated/entities/external_message/index.ts +11 -0
- package/generated/entities/integration_credentials/index.ts +1 -0
- package/generated/entities/message/index.ts +1 -0
- package/generated/entities/message_channel_link/index.ts +15 -0
- package/generated/entities/message_reaction/index.ts +11 -0
- package/generated/entities.ids.generated.ts +11 -0
- package/generated/entity-fields-registry.ts +117 -0
- package/package.json +9 -7
- package/src/helpers/integration/authFixtures.ts +4 -1
- package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
- package/src/modules/communication_channels/acl.ts +43 -0
- package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
- package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
- package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
- package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
- package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
- package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
- package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
- package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
- package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
- package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
- package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
- package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
- package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
- package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
- package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
- package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
- package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
- package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
- package/src/modules/communication_channels/commands/interceptors.ts +104 -0
- package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
- package/src/modules/communication_channels/commands/push-register.ts +203 -0
- package/src/modules/communication_channels/commands/push-renew.ts +49 -0
- package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
- package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
- package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
- package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
- package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
- package/src/modules/communication_channels/data/enrichers.ts +413 -0
- package/src/modules/communication_channels/data/entities.ts +546 -0
- package/src/modules/communication_channels/data/extensions.ts +76 -0
- package/src/modules/communication_channels/data/validators.ts +138 -0
- package/src/modules/communication_channels/di.ts +40 -0
- package/src/modules/communication_channels/encryption.ts +44 -0
- package/src/modules/communication_channels/events.ts +122 -0
- package/src/modules/communication_channels/i18n/de.json +138 -0
- package/src/modules/communication_channels/i18n/en.json +138 -0
- package/src/modules/communication_channels/i18n/es.json +138 -0
- package/src/modules/communication_channels/i18n/pl.json +138 -0
- package/src/modules/communication_channels/index.ts +19 -0
- package/src/modules/communication_channels/lib/access-control.ts +110 -0
- package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
- package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
- package/src/modules/communication_channels/lib/adapter.ts +605 -0
- package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
- package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
- package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
- package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
- package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
- package/src/modules/communication_channels/lib/email-contact.ts +17 -0
- package/src/modules/communication_channels/lib/email-mime.ts +425 -0
- package/src/modules/communication_channels/lib/error-classification.ts +144 -0
- package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
- package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
- package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
- package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
- package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
- package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
- package/src/modules/communication_channels/lib/provider-health.ts +47 -0
- package/src/modules/communication_channels/lib/push-state.ts +38 -0
- package/src/modules/communication_channels/lib/queue.ts +66 -0
- package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
- package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
- package/src/modules/communication_channels/lib/registry.ts +99 -0
- package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
- package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
- package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
- package/src/modules/communication_channels/lib/system-user.ts +74 -0
- package/src/modules/communication_channels/lib/test-seed.ts +140 -0
- package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
- package/src/modules/communication_channels/lib/thread-token.ts +355 -0
- package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
- package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
- package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
- package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
- package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
- package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
- package/src/modules/communication_channels/notifications.client.ts +50 -0
- package/src/modules/communication_channels/notifications.handlers.ts +86 -0
- package/src/modules/communication_channels/notifications.ts +52 -0
- package/src/modules/communication_channels/setup.ts +158 -0
- package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
- package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
- package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
- package/src/modules/communication_channels/widgets/components.ts +36 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
- package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
- package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
- package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
- package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
- package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
- package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
- package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
- package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
- package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
- package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
- package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
- package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
- package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
- package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
- package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
- package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
- package/src/modules/customers/acl.ts +18 -0
- package/src/modules/customers/api/activities/route.ts +13 -0
- package/src/modules/customers/api/companies/[id]/route.ts +21 -1
- package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
- package/src/modules/customers/api/interactions/counts/route.ts +10 -0
- package/src/modules/customers/api/interactions/route.ts +51 -5
- package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
- package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
- package/src/modules/customers/api/people/[id]/route.ts +17 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
- package/src/modules/customers/commands/deals.ts +65 -6
- package/src/modules/customers/commands/interactions.ts +30 -0
- package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
- package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
- package/src/modules/customers/components/detail/DealForm.tsx +2 -1
- package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
- package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
- package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
- package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
- package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
- package/src/modules/customers/data/enrichers.ts +252 -1
- package/src/modules/customers/data/entities.ts +46 -1
- package/src/modules/customers/data/extensions.ts +26 -0
- package/src/modules/customers/encryption.ts +11 -0
- package/src/modules/customers/events.ts +4 -0
- package/src/modules/customers/i18n/de.json +41 -0
- package/src/modules/customers/i18n/en.json +41 -0
- package/src/modules/customers/i18n/es.json +41 -0
- package/src/modules/customers/i18n/pl.json +41 -0
- package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
- package/src/modules/customers/lib/kysely.ts +16 -0
- package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
- package/src/modules/customers/lib/personEmailThreads.ts +325 -0
- package/src/modules/customers/lib/visibilityFilter.ts +152 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
- package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
- package/src/modules/customers/setup.ts +1 -0
- package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
- package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
- package/src/modules/integrations/AGENTS.md +9 -0
- package/src/modules/integrations/data/entities.ts +21 -1
- package/src/modules/integrations/lib/credentials-service.ts +49 -13
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
- package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
- package/src/modules/messages/commands/messages.ts +101 -8
- package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
- package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
- package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
- package/src/modules/messages/data/entities.ts +11 -0
- package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
- package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
- package/src/modules/messages/widgets/injection-table.ts +29 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { EMAIL_MAX_ATTACHMENT_BYTES } from "./email-capabilities.js";
|
|
3
|
+
const TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES = EMAIL_MAX_ATTACHMENT_BYTES * 2;
|
|
4
|
+
function stringOrUndefined(value) {
|
|
5
|
+
if (typeof value !== "string") return void 0;
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
8
|
+
}
|
|
9
|
+
function toAddressList(value) {
|
|
10
|
+
if (Array.isArray(value)) return value.map((v) => String(v).trim()).filter((v) => v.length > 0);
|
|
11
|
+
if (typeof value === "string") {
|
|
12
|
+
return value.split(/[,;]\s*/).map((v) => v.trim()).filter((v) => v.length > 0);
|
|
13
|
+
}
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
function referencesFromMeta(value) {
|
|
17
|
+
if (!Array.isArray(value)) return void 0;
|
|
18
|
+
return value.filter((entry) => typeof entry === "string");
|
|
19
|
+
}
|
|
20
|
+
function htmlToText(html) {
|
|
21
|
+
return html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, " ").replace(/<script[^>]*>[\s\S]*?<\/script>/gi, " ").replace(/<br\s*\/?>(?=\s*)/gi, "\n").replace(/<\/p\s*>/gi, "\n\n").replace(/<[^>]+>/g, "").replace(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/\n{3,}/g, "\n\n").trim();
|
|
22
|
+
}
|
|
23
|
+
function escapeQuotes(value) {
|
|
24
|
+
return value.replace(/"/g, '\\"');
|
|
25
|
+
}
|
|
26
|
+
function sanitizeHeaderValue(value) {
|
|
27
|
+
return value.replace(/[\r\n\t]+/g, " ").trim();
|
|
28
|
+
}
|
|
29
|
+
function ensureBrackets(value) {
|
|
30
|
+
const trimmed = sanitizeHeaderValue(value);
|
|
31
|
+
if (trimmed.startsWith("<") && trimmed.endsWith(">")) return trimmed;
|
|
32
|
+
return `<${trimmed}>`;
|
|
33
|
+
}
|
|
34
|
+
function isPureAscii(value) {
|
|
35
|
+
return /^[\x00-\x7F]*$/.test(value);
|
|
36
|
+
}
|
|
37
|
+
function encodeHeaderWord(value) {
|
|
38
|
+
if (isPureAscii(value)) return value;
|
|
39
|
+
return `=?utf-8?B?${Buffer.from(value, "utf-8").toString("base64")}?=`;
|
|
40
|
+
}
|
|
41
|
+
function encodeAddressHeaderWord(value) {
|
|
42
|
+
if (isPureAscii(value)) return value;
|
|
43
|
+
const match = value.match(/^(.*?)(\s*<[^>]*>)\s*$/);
|
|
44
|
+
if (match) {
|
|
45
|
+
const [, displayPart, addrPart] = match;
|
|
46
|
+
const displayName = displayPart.replace(/^"|"$/g, "").trim();
|
|
47
|
+
if (!displayName) return value;
|
|
48
|
+
return `${encodeHeaderWord(displayName)}${addrPart}`;
|
|
49
|
+
}
|
|
50
|
+
return encodeHeaderWord(value);
|
|
51
|
+
}
|
|
52
|
+
function generateMessageId(fromAddress, fallbackDomain = "localhost") {
|
|
53
|
+
const domain = fromAddress.split("@")[1] ?? fallbackDomain;
|
|
54
|
+
return `<${crypto.randomUUID()}@${domain}>`;
|
|
55
|
+
}
|
|
56
|
+
function encodeBodyPart(content) {
|
|
57
|
+
if (isPureAscii(content)) return { cte: "7bit", body: content };
|
|
58
|
+
const base64 = Buffer.from(content, "utf-8").toString("base64");
|
|
59
|
+
const wrapped = base64.match(/.{1,76}/g)?.join("\r\n") ?? base64;
|
|
60
|
+
return { cte: "base64", body: wrapped };
|
|
61
|
+
}
|
|
62
|
+
function assembleRfc2822(input) {
|
|
63
|
+
const boundary = `omc_${crypto.randomUUID()}`;
|
|
64
|
+
const headers = [];
|
|
65
|
+
headers.push(`From: ${encodeAddressHeaderWord(sanitizeHeaderValue(input.from))}`);
|
|
66
|
+
headers.push(`To: ${input.to.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(", ")}`);
|
|
67
|
+
if (input.cc.length) headers.push(`Cc: ${input.cc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(", ")}`);
|
|
68
|
+
if (input.bcc.length) headers.push(`Bcc: ${input.bcc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(", ")}`);
|
|
69
|
+
if (input.subject) headers.push(`Subject: ${encodeHeaderWord(sanitizeHeaderValue(input.subject))}`);
|
|
70
|
+
headers.push(`Message-ID: ${ensureBrackets(input.messageId)}`);
|
|
71
|
+
if (input.inReplyTo) headers.push(`In-Reply-To: ${ensureBrackets(input.inReplyTo)}`);
|
|
72
|
+
if (input.references && input.references.length) {
|
|
73
|
+
headers.push(`References: ${input.references.map(ensureBrackets).join(" ")}`);
|
|
74
|
+
}
|
|
75
|
+
headers.push("MIME-Version: 1.0");
|
|
76
|
+
headers.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
77
|
+
if (input.html && input.text) {
|
|
78
|
+
headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
79
|
+
const textPart2 = encodeBodyPart(input.text);
|
|
80
|
+
const htmlPart = encodeBodyPart(input.html);
|
|
81
|
+
const body = [
|
|
82
|
+
"",
|
|
83
|
+
`--${boundary}`,
|
|
84
|
+
"Content-Type: text/plain; charset=utf-8",
|
|
85
|
+
`Content-Transfer-Encoding: ${textPart2.cte}`,
|
|
86
|
+
"",
|
|
87
|
+
textPart2.body,
|
|
88
|
+
`--${boundary}`,
|
|
89
|
+
"Content-Type: text/html; charset=utf-8",
|
|
90
|
+
`Content-Transfer-Encoding: ${htmlPart.cte}`,
|
|
91
|
+
"",
|
|
92
|
+
htmlPart.body,
|
|
93
|
+
`--${boundary}--`,
|
|
94
|
+
""
|
|
95
|
+
].join("\r\n");
|
|
96
|
+
return Buffer.from(headers.join("\r\n") + body, "utf-8");
|
|
97
|
+
}
|
|
98
|
+
if (input.html) {
|
|
99
|
+
const htmlPart = encodeBodyPart(input.html);
|
|
100
|
+
headers.push("Content-Type: text/html; charset=utf-8");
|
|
101
|
+
headers.push(`Content-Transfer-Encoding: ${htmlPart.cte}`);
|
|
102
|
+
return Buffer.from(headers.join("\r\n") + "\r\n\r\n" + htmlPart.body, "utf-8");
|
|
103
|
+
}
|
|
104
|
+
const textPart = encodeBodyPart(input.text ?? "");
|
|
105
|
+
headers.push("Content-Type: text/plain; charset=utf-8");
|
|
106
|
+
headers.push(`Content-Transfer-Encoding: ${textPart.cte}`);
|
|
107
|
+
return Buffer.from(headers.join("\r\n") + "\r\n\r\n" + textPart.body, "utf-8");
|
|
108
|
+
}
|
|
109
|
+
function stripBrackets(value) {
|
|
110
|
+
if (!value) return void 0;
|
|
111
|
+
const trimmed = value.trim();
|
|
112
|
+
if (!trimmed) return void 0;
|
|
113
|
+
if (trimmed.startsWith("<") && trimmed.endsWith(">")) return trimmed.slice(1, -1);
|
|
114
|
+
return trimmed;
|
|
115
|
+
}
|
|
116
|
+
function parseReferences(value) {
|
|
117
|
+
if (!value) return [];
|
|
118
|
+
if (Array.isArray(value)) return value.map((v) => stripBrackets(v)).filter((v) => Boolean(v));
|
|
119
|
+
return value.split(/\s+/).map((segment) => stripBrackets(segment)).filter((segment) => Boolean(segment));
|
|
120
|
+
}
|
|
121
|
+
function normalizeAttachments(attachments) {
|
|
122
|
+
const out = [];
|
|
123
|
+
let totalBytes = 0;
|
|
124
|
+
for (const att of attachments) {
|
|
125
|
+
if (!att.content) continue;
|
|
126
|
+
const byteLength = att.content.byteLength;
|
|
127
|
+
if (byteLength > EMAIL_MAX_ATTACHMENT_BYTES) {
|
|
128
|
+
console.warn(
|
|
129
|
+
`[email-mime] dropping oversized inbound attachment "${att.filename ?? "attachment"}" (${byteLength} bytes > ${EMAIL_MAX_ATTACHMENT_BYTES} cap)`
|
|
130
|
+
);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (totalBytes + byteLength > TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES) {
|
|
134
|
+
console.warn(
|
|
135
|
+
`[email-mime] aggregate inbound attachment size exceeded ${TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES} bytes; dropping remaining attachments`
|
|
136
|
+
);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
totalBytes += byteLength;
|
|
140
|
+
const base64 = Buffer.isBuffer(att.content) ? att.content.toString("base64") : Buffer.from(att.content).toString("base64");
|
|
141
|
+
out.push({
|
|
142
|
+
url: `data:${att.contentType ?? "application/octet-stream"};base64,${base64}`,
|
|
143
|
+
mimeType: att.contentType ?? "application/octet-stream",
|
|
144
|
+
fileName: att.filename ?? "attachment",
|
|
145
|
+
fileSize: att.size,
|
|
146
|
+
inline: Boolean(att.contentDisposition && /inline/i.test(att.contentDisposition)) || Boolean(att.cid)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function stringifyHeaderValue(value) {
|
|
152
|
+
if (typeof value === "string") return value;
|
|
153
|
+
if (Array.isArray(value)) return value.map((v) => String(v)).join(", ");
|
|
154
|
+
if (value instanceof Date) return value.toISOString();
|
|
155
|
+
if (value && typeof value === "object") {
|
|
156
|
+
try {
|
|
157
|
+
return JSON.stringify(value);
|
|
158
|
+
} catch {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (value === void 0 || value === null) return void 0;
|
|
163
|
+
return String(value);
|
|
164
|
+
}
|
|
165
|
+
function extractHeaders(headers) {
|
|
166
|
+
if (!headers) return {};
|
|
167
|
+
const out = {};
|
|
168
|
+
if (headers instanceof Map) {
|
|
169
|
+
for (const [key, value] of headers.entries()) {
|
|
170
|
+
const stringified = stringifyHeaderValue(value);
|
|
171
|
+
if (stringified !== void 0) out[String(key)] = stringified;
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
175
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
176
|
+
const stringified = stringifyHeaderValue(value);
|
|
177
|
+
if (stringified !== void 0) out[key] = stringified;
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function normalizeMimeInbound(options) {
|
|
182
|
+
const { parsed } = options;
|
|
183
|
+
const messageId = stripBrackets(parsed.messageId) ?? options.fallbackMessageId;
|
|
184
|
+
const inReplyTo = stripBrackets(parsed.inReplyTo);
|
|
185
|
+
const references = parseReferences(parsed.references);
|
|
186
|
+
const conversationId = options.resolveConversationId({ messageId, references });
|
|
187
|
+
const from = parsed.from?.value?.[0];
|
|
188
|
+
const subject = parsed.subject?.trim() || void 0;
|
|
189
|
+
const bodyHtml = parsed.html && typeof parsed.html === "string" ? parsed.html : void 0;
|
|
190
|
+
const bodyText = typeof parsed.text === "string" ? parsed.text : void 0;
|
|
191
|
+
const body = bodyHtml ?? bodyText ?? "";
|
|
192
|
+
const bodyFormat = bodyHtml ? "html" : "text";
|
|
193
|
+
const attachments = normalizeAttachments(parsed.attachments ?? []);
|
|
194
|
+
const channelMetadata = {
|
|
195
|
+
...options.channelMetadata?.(parsed) ?? {},
|
|
196
|
+
messageId,
|
|
197
|
+
inReplyTo: inReplyTo ?? null,
|
|
198
|
+
references,
|
|
199
|
+
headers: extractHeaders(parsed.headers)
|
|
200
|
+
};
|
|
201
|
+
const channelPayload = {
|
|
202
|
+
subject,
|
|
203
|
+
from: from ? { address: from.address, name: from.name } : null,
|
|
204
|
+
to: parsed.to?.value ?? [],
|
|
205
|
+
cc: parsed.cc?.value ?? [],
|
|
206
|
+
bcc: parsed.bcc?.value ?? [],
|
|
207
|
+
html: bodyHtml ?? null,
|
|
208
|
+
text: bodyText ?? null,
|
|
209
|
+
messageId,
|
|
210
|
+
...options.channelPayload?.(parsed) ?? {}
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
externalMessageId: messageId,
|
|
214
|
+
externalConversationId: conversationId,
|
|
215
|
+
senderIdentifier: from?.address ?? options.accountIdentifier,
|
|
216
|
+
senderDisplayName: from?.name?.trim() || void 0,
|
|
217
|
+
subject,
|
|
218
|
+
body,
|
|
219
|
+
bodyFormat,
|
|
220
|
+
attachments,
|
|
221
|
+
timestamp: parsed.date ? new Date(parsed.date) : options.fallbackDate ?? /* @__PURE__ */ new Date(),
|
|
222
|
+
replyToExternalId: inReplyTo ?? void 0,
|
|
223
|
+
channelPayload,
|
|
224
|
+
channelContentType: "email/mime",
|
|
225
|
+
channelMetadata
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function encodeCursor(state) {
|
|
229
|
+
return Buffer.from(JSON.stringify(state)).toString("base64");
|
|
230
|
+
}
|
|
231
|
+
function decodeCursor(value) {
|
|
232
|
+
if (!value) return null;
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(Buffer.from(value, "base64").toString("utf-8"));
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export {
|
|
240
|
+
assembleRfc2822,
|
|
241
|
+
decodeCursor,
|
|
242
|
+
encodeAddressHeaderWord,
|
|
243
|
+
encodeCursor,
|
|
244
|
+
encodeHeaderWord,
|
|
245
|
+
ensureBrackets,
|
|
246
|
+
escapeQuotes,
|
|
247
|
+
extractHeaders,
|
|
248
|
+
generateMessageId,
|
|
249
|
+
htmlToText,
|
|
250
|
+
normalizeAttachments,
|
|
251
|
+
normalizeMimeInbound,
|
|
252
|
+
parseReferences,
|
|
253
|
+
referencesFromMeta,
|
|
254
|
+
sanitizeHeaderValue,
|
|
255
|
+
stringOrUndefined,
|
|
256
|
+
stripBrackets,
|
|
257
|
+
toAddressList
|
|
258
|
+
};
|
|
259
|
+
//# sourceMappingURL=email-mime.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/communication_channels/lib/email-mime.ts"],
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto'\nimport type { NormalizedInboundMessage, NormalizedAttachment } from './adapter'\nimport { EMAIL_MAX_ATTACHMENT_BYTES } from './email-capabilities'\n\n/**\n * Aggregate ceiling for all attachments on a single inbound message. Inbound\n * mail is untrusted, so without a cap a malicious/large message would be fully\n * base64-buffered in memory (~1.33x raw bytes) and persisted. Allow a small\n * multiple of the per-attachment limit for legitimate multi-file emails.\n */\nconst TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES = EMAIL_MAX_ATTACHMENT_BYTES * 2\n\n/**\n * Shared email MIME helpers for the email channel providers (Gmail, IMAP).\n * Outbound assembly, inbound parsing, header/address normalization,\n * and threading-id extraction all live here so every provider shares one\n * correct implementation instead of copy-pasting (which previously let Gmail's\n * `extractHeaders` drift into a Map-handling bug that IMAP had already fixed).\n *\n * Provider-specific transport (Gmail History API, IMAP UID sync)\n * stays in each package \u2014 this module only owns the format-level plumbing.\n */\n\n// \u2500\u2500 Outbound MIME helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function stringOrUndefined(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\nexport function toAddressList(value: unknown): string[] {\n if (Array.isArray(value)) return value.map((v) => String(v).trim()).filter((v) => v.length > 0)\n if (typeof value === 'string') {\n return value\n .split(/[,;]\\s*/)\n .map((v) => v.trim())\n .filter((v) => v.length > 0)\n }\n return []\n}\n\nexport function referencesFromMeta(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined\n return value.filter((entry): entry is string => typeof entry === 'string')\n}\n\nexport function htmlToText(html: string): string {\n return html\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<br\\s*\\/?>(?=\\s*)/gi, '\\n')\n .replace(/<\\/p\\s*>/gi, '\\n\\n')\n .replace(/<[^>]+>/g, '')\n .replace(/ /gi, ' ')\n .replace(/&/gi, '&')\n .replace(/</gi, '<')\n .replace(/>/gi, '>')\n .replace(/"/gi, '\"')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n}\n\nexport function escapeQuotes(value: string): string {\n return value.replace(/\"/g, '\\\\\"')\n}\n\n/**\n * Collapse CR/LF/TAB in an email header value to a single space to prevent\n * RFC 5322 header injection \u2014 e.g. a Subject smuggling an extra\n * `Bcc:`/`Content-Type:` header or splitting the message into a body.\n * Collapsing (rather than folding) is safe for the short structured headers\n * we emit (Subject, addresses, Message-ID, References).\n */\nexport function sanitizeHeaderValue(value: string): string {\n return value.replace(/[\\r\\n\\t]+/g, ' ').trim()\n}\n\nexport function ensureBrackets(value: string): string {\n const trimmed = sanitizeHeaderValue(value)\n if (trimmed.startsWith('<') && trimmed.endsWith('>')) return trimmed\n return `<${trimmed}>`\n}\n\nfunction isPureAscii(value: string): boolean {\n // eslint-disable-next-line no-control-regex\n return /^[\\x00-\\x7F]*$/.test(value)\n}\n\n/**\n * Encode a single header value as an RFC 2047 \"B\" (base64) encoded-word when it\n * contains non-ASCII characters, so 8-bit text like \"Caf\u00E9\" survives strict MTAs\n * that treat header bytes as 7-bit ASCII. Pure-ASCII values are returned\n * unchanged. Apply only AFTER `sanitizeHeaderValue` so the CR/LF injection guard\n * still runs against the raw value.\n */\nexport function encodeHeaderWord(value: string): string {\n if (isPureAscii(value)) return value\n return `=?utf-8?B?${Buffer.from(value, 'utf-8').toString('base64')}?=`\n}\n\n/**\n * Encode the display-name part of a single address header value, leaving the\n * `<addr@domain>` untouched (per RFC 2047, encoded-words are not permitted\n * inside the addr-spec). Inputs without a bracketed address are treated as a\n * bare display name / address and encoded only when non-ASCII.\n */\nexport function encodeAddressHeaderWord(value: string): string {\n if (isPureAscii(value)) return value\n const match = value.match(/^(.*?)(\\s*<[^>]*>)\\s*$/)\n if (match) {\n const [, displayPart, addrPart] = match\n const displayName = displayPart.replace(/^\"|\"$/g, '').trim()\n if (!displayName) return value\n return `${encodeHeaderWord(displayName)}${addrPart}`\n }\n return encodeHeaderWord(value)\n}\n\n/**\n * Generate an RFC 5322 Message-ID rooted in the sender's domain. Used as a\n * downstream idempotency key, so entropy comes from `crypto.randomUUID()`\n * rather than `Math.random()`.\n */\nexport function generateMessageId(fromAddress: string, fallbackDomain = 'localhost'): string {\n const domain = fromAddress.split('@')[1] ?? fallbackDomain\n return `<${crypto.randomUUID()}@${domain}>`\n}\n\nexport interface AssembleRfc2822Input {\n from: string\n to: string[]\n cc: string[]\n bcc: string[]\n subject: string | undefined\n text: string | undefined\n html: string | undefined\n inReplyTo: string | undefined\n references: string[] | undefined\n messageId: string\n}\n\n/**\n * Render one MIME body part's CTE header + body content. Non-ASCII bodies are\n * base64-encoded (CRLF-wrapped at 76 cols) and labelled\n * `Content-Transfer-Encoding: base64` so 8-bit text survives strict MTAs;\n * pure-ASCII bodies stay `7bit` and verbatim.\n */\nfunction encodeBodyPart(content: string): { cte: string; body: string } {\n if (isPureAscii(content)) return { cte: '7bit', body: content }\n const base64 = Buffer.from(content, 'utf-8').toString('base64')\n const wrapped = base64.match(/.{1,76}/g)?.join('\\r\\n') ?? base64\n return { cte: 'base64', body: wrapped }\n}\n\n/**\n * Assemble a raw RFC2822 message (used by transports that send the encoded\n * message directly, e.g. Gmail `users.messages.send`). Emits a\n * `multipart/alternative` body when both html and text are present, otherwise a\n * single-part text or html body.\n */\nexport function assembleRfc2822(input: AssembleRfc2822Input): Buffer {\n const boundary = `omc_${crypto.randomUUID()}`\n const headers: string[] = []\n headers.push(`From: ${encodeAddressHeaderWord(sanitizeHeaderValue(input.from))}`)\n headers.push(`To: ${input.to.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(', ')}`)\n if (input.cc.length) headers.push(`Cc: ${input.cc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(', ')}`)\n if (input.bcc.length) headers.push(`Bcc: ${input.bcc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(', ')}`)\n if (input.subject) headers.push(`Subject: ${encodeHeaderWord(sanitizeHeaderValue(input.subject))}`)\n headers.push(`Message-ID: ${ensureBrackets(input.messageId)}`)\n if (input.inReplyTo) headers.push(`In-Reply-To: ${ensureBrackets(input.inReplyTo)}`)\n if (input.references && input.references.length) {\n headers.push(`References: ${input.references.map(ensureBrackets).join(' ')}`)\n }\n headers.push('MIME-Version: 1.0')\n headers.push(`Date: ${new Date().toUTCString()}`)\n\n if (input.html && input.text) {\n headers.push(`Content-Type: multipart/alternative; boundary=\"${boundary}\"`)\n const textPart = encodeBodyPart(input.text)\n const htmlPart = encodeBodyPart(input.html)\n const body = [\n '',\n `--${boundary}`,\n 'Content-Type: text/plain; charset=utf-8',\n `Content-Transfer-Encoding: ${textPart.cte}`,\n '',\n textPart.body,\n `--${boundary}`,\n 'Content-Type: text/html; charset=utf-8',\n `Content-Transfer-Encoding: ${htmlPart.cte}`,\n '',\n htmlPart.body,\n `--${boundary}--`,\n '',\n ].join('\\r\\n')\n return Buffer.from(headers.join('\\r\\n') + body, 'utf-8')\n }\n\n if (input.html) {\n const htmlPart = encodeBodyPart(input.html)\n headers.push('Content-Type: text/html; charset=utf-8')\n headers.push(`Content-Transfer-Encoding: ${htmlPart.cte}`)\n return Buffer.from(headers.join('\\r\\n') + '\\r\\n\\r\\n' + htmlPart.body, 'utf-8')\n }\n\n const textPart = encodeBodyPart(input.text ?? '')\n headers.push('Content-Type: text/plain; charset=utf-8')\n headers.push(`Content-Transfer-Encoding: ${textPart.cte}`)\n return Buffer.from(headers.join('\\r\\n') + '\\r\\n\\r\\n' + textPart.body, 'utf-8')\n}\n\n// \u2500\u2500 Inbound MIME parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ParsedMail {\n messageId?: string | null\n inReplyTo?: string | null\n references?: string | string[] | null\n from?: { value?: Array<{ address?: string; name?: string }> }\n to?: { value?: Array<{ address?: string; name?: string }> }\n cc?: { value?: Array<{ address?: string; name?: string }> }\n bcc?: { value?: Array<{ address?: string; name?: string }> }\n subject?: string | null\n html?: string | false\n text?: string\n date?: string | Date | null\n attachments?: ParsedAttachment[]\n headers?: Map<string, unknown> | Record<string, unknown>\n}\n\nexport interface ParsedAttachment {\n content?: Buffer | Uint8Array\n contentType?: string\n filename?: string\n size?: number\n contentDisposition?: string\n cid?: string\n}\n\nexport function stripBrackets(value: string | undefined | null): string | undefined {\n if (!value) return undefined\n const trimmed = value.trim()\n if (!trimmed) return undefined\n if (trimmed.startsWith('<') && trimmed.endsWith('>')) return trimmed.slice(1, -1)\n return trimmed\n}\n\nexport function parseReferences(value: string | string[] | undefined | null): string[] {\n if (!value) return []\n if (Array.isArray(value)) return value.map((v) => stripBrackets(v)).filter((v): v is string => Boolean(v))\n return value\n .split(/\\s+/)\n .map((segment) => stripBrackets(segment))\n .filter((segment): segment is string => Boolean(segment))\n}\n\nexport function normalizeAttachments(attachments: ParsedAttachment[]): NormalizedAttachment[] {\n const out: NormalizedAttachment[] = []\n let totalBytes = 0\n for (const att of attachments) {\n if (!att.content) continue\n const byteLength = att.content.byteLength\n if (byteLength > EMAIL_MAX_ATTACHMENT_BYTES) {\n console.warn(\n `[email-mime] dropping oversized inbound attachment \"${att.filename ?? 'attachment'}\" (${byteLength} bytes > ${EMAIL_MAX_ATTACHMENT_BYTES} cap)`,\n )\n continue\n }\n if (totalBytes + byteLength > TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES) {\n console.warn(\n `[email-mime] aggregate inbound attachment size exceeded ${TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES} bytes; dropping remaining attachments`,\n )\n break\n }\n totalBytes += byteLength\n const base64 = Buffer.isBuffer(att.content)\n ? att.content.toString('base64')\n : Buffer.from(att.content).toString('base64')\n out.push({\n url: `data:${att.contentType ?? 'application/octet-stream'};base64,${base64}`,\n mimeType: att.contentType ?? 'application/octet-stream',\n fileName: att.filename ?? 'attachment',\n fileSize: att.size,\n inline: Boolean(att.contentDisposition && /inline/i.test(att.contentDisposition)) || Boolean(att.cid),\n })\n }\n return out\n}\n\nfunction stringifyHeaderValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) return value.map((v) => String(v)).join(', ')\n if (value instanceof Date) return value.toISOString()\n if (value && typeof value === 'object') {\n try {\n return JSON.stringify(value)\n } catch {\n return undefined\n }\n }\n if (value === undefined || value === null) return undefined\n return String(value)\n}\n\n/**\n * Flatten parsed MIME headers to a `Record<string, string>`.\n *\n * mailparser returns `headers` as a `Map<string, unknown>`. `Object.entries` on\n * a Map yields an empty array (Maps have no enumerable own properties), so we\n * iterate Map entries directly, with a Record fallback for test fakes.\n */\nexport function extractHeaders(headers: ParsedMail['headers']): Record<string, string> {\n if (!headers) return {}\n const out: Record<string, string> = {}\n if (headers instanceof Map) {\n for (const [key, value] of headers.entries()) {\n const stringified = stringifyHeaderValue(value)\n if (stringified !== undefined) out[String(key)] = stringified\n }\n return out\n }\n for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {\n const stringified = stringifyHeaderValue(value)\n if (stringified !== undefined) out[key] = stringified\n }\n return out\n}\n\nexport interface NormalizeMimeInboundOptions {\n /**\n * The parsed MIME message. Providers parse with their own `mailparser`\n * dependency (Gmail/IMAP) and pass the result here, so the hub stays free of a\n * MIME-parser dependency while still owning the normalization logic.\n */\n parsed: ParsedMail\n /** External identifier of the receiving channel (typically the account's email). */\n accountIdentifier: string\n /** Deterministic id used when the MIME message carries no `Message-ID` header. */\n fallbackMessageId: string\n /** Compute the conversation grouping id from the resolved message id + references. */\n resolveConversationId: (context: { messageId: string; references: string[] }) => string\n /** Fallback timestamp when the parsed message has no Date header. */\n fallbackDate?: Date\n /** Provider-specific fields merged into `channelMetadata`. */\n channelMetadata?: (parsed: ParsedMail) => Record<string, unknown>\n /** Provider-specific fields merged into `channelPayload`. */\n channelPayload?: (parsed: ParsedMail) => Record<string, unknown>\n}\n\n/**\n * Build the hub's canonical `NormalizedInboundMessage` from a parsed MIME\n * message. Providers supply the bits that genuinely differ (message-id\n * fallback, conversation grouping, extra metadata) and inherit the shared\n * threading / attachment / header logic.\n */\nexport function normalizeMimeInbound(options: NormalizeMimeInboundOptions): NormalizedInboundMessage {\n const { parsed } = options\n\n const messageId = stripBrackets(parsed.messageId) ?? options.fallbackMessageId\n const inReplyTo = stripBrackets(parsed.inReplyTo)\n const references = parseReferences(parsed.references)\n const conversationId = options.resolveConversationId({ messageId, references })\n\n const from = parsed.from?.value?.[0]\n const subject = parsed.subject?.trim() || undefined\n const bodyHtml = parsed.html && typeof parsed.html === 'string' ? parsed.html : undefined\n const bodyText = typeof parsed.text === 'string' ? parsed.text : undefined\n const body = bodyHtml ?? bodyText ?? ''\n const bodyFormat: 'text' | 'html' = bodyHtml ? 'html' : 'text'\n\n const attachments = normalizeAttachments(parsed.attachments ?? [])\n\n const channelMetadata: Record<string, unknown> = {\n ...(options.channelMetadata?.(parsed) ?? {}),\n messageId,\n inReplyTo: inReplyTo ?? null,\n references,\n headers: extractHeaders(parsed.headers),\n }\n\n const channelPayload: Record<string, unknown> = {\n subject,\n from: from ? { address: from.address, name: from.name } : null,\n to: parsed.to?.value ?? [],\n cc: parsed.cc?.value ?? [],\n bcc: parsed.bcc?.value ?? [],\n html: bodyHtml ?? null,\n text: bodyText ?? null,\n messageId,\n ...(options.channelPayload?.(parsed) ?? {}),\n }\n\n return {\n externalMessageId: messageId,\n externalConversationId: conversationId,\n senderIdentifier: from?.address ?? options.accountIdentifier,\n senderDisplayName: from?.name?.trim() || undefined,\n subject,\n body,\n bodyFormat,\n attachments,\n timestamp: parsed.date ? new Date(parsed.date) : options.fallbackDate ?? new Date(),\n replyToExternalId: inReplyTo ?? undefined,\n channelPayload,\n channelContentType: 'email/mime',\n channelMetadata,\n }\n}\n\n// \u2500\u2500 Provider sync cursor helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Encode a provider channel-state object into an opaque base64 sync cursor. */\nexport function encodeCursor(state: unknown): string {\n return Buffer.from(JSON.stringify(state)).toString('base64')\n}\n\n/** Decode a base64 sync cursor back into its object form, or `null` if malformed. */\nexport function decodeCursor(value: string | null | undefined): unknown {\n if (!value) return null\n try {\n return JSON.parse(Buffer.from(value, 'base64').toString('utf-8'))\n } catch {\n return null\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["textPart"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const TRANSIENT_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524]);
|
|
2
|
+
const TRANSIENT_PG_SQLSTATES = /* @__PURE__ */ new Set([
|
|
3
|
+
"40001",
|
|
4
|
+
// serialization_failure
|
|
5
|
+
"40P01",
|
|
6
|
+
// deadlock_detected
|
|
7
|
+
"55P03",
|
|
8
|
+
// lock_not_available
|
|
9
|
+
"53300",
|
|
10
|
+
// too_many_connections
|
|
11
|
+
"53400",
|
|
12
|
+
// configuration_limit_exceeded
|
|
13
|
+
"57P01",
|
|
14
|
+
// admin_shutdown
|
|
15
|
+
"57P02",
|
|
16
|
+
// crash_shutdown
|
|
17
|
+
"57P03",
|
|
18
|
+
// cannot_connect_now (db starting up)
|
|
19
|
+
"08000",
|
|
20
|
+
// connection_exception
|
|
21
|
+
"08001",
|
|
22
|
+
// sqlclient_unable_to_establish_sqlconnection
|
|
23
|
+
"08003",
|
|
24
|
+
// connection_does_not_exist
|
|
25
|
+
"08006"
|
|
26
|
+
// connection_failure
|
|
27
|
+
]);
|
|
28
|
+
const TRANSIENT_CODE_PATTERNS = [
|
|
29
|
+
/ECONNRESET/i,
|
|
30
|
+
/ETIMEDOUT/i,
|
|
31
|
+
/ECONNREFUSED/i,
|
|
32
|
+
/ENOTFOUND/i,
|
|
33
|
+
/EAI_AGAIN/i,
|
|
34
|
+
/socket hang up/i,
|
|
35
|
+
/network timeout/i,
|
|
36
|
+
/\btimed?\s*out\b/i,
|
|
37
|
+
/service unavailable/i,
|
|
38
|
+
/rate limit/i,
|
|
39
|
+
/too many requests/i,
|
|
40
|
+
/temporarily unavailable/i,
|
|
41
|
+
/try again later/i,
|
|
42
|
+
// imapflow surfaces these on TLS/socket-level drops that almost always
|
|
43
|
+
// recover on the next attempt. Treating them as permanent kills the
|
|
44
|
+
// channel after a single bad packet (regression observed during the demo:
|
|
45
|
+
// a network hiccup put a freshly-connected mailbox into a non-recoverable
|
|
46
|
+
// `error` state until the operator manually clicked "Retry").
|
|
47
|
+
/unexpected close/i,
|
|
48
|
+
/connection not available/i,
|
|
49
|
+
/connection closed/i,
|
|
50
|
+
/server closed connection/i,
|
|
51
|
+
// Postgres transient failures surfaced as text by the ORM wrapper (the
|
|
52
|
+
// SQLSTATE on `.code` is the primary signal; these catch wrapped errors that
|
|
53
|
+
// only carry the message). See TRANSIENT_PG_SQLSTATES.
|
|
54
|
+
/deadlock detected/i,
|
|
55
|
+
/could not serialize access/i,
|
|
56
|
+
/connection terminated/i,
|
|
57
|
+
/too many clients already/i,
|
|
58
|
+
/the database system is (starting up|shutting down|in recovery)/i
|
|
59
|
+
];
|
|
60
|
+
function classifyOutboundError(error) {
|
|
61
|
+
if (!error) {
|
|
62
|
+
return { transient: false, message: "Unknown error" };
|
|
63
|
+
}
|
|
64
|
+
if (error instanceof Error) {
|
|
65
|
+
const explicit = error.transient;
|
|
66
|
+
const status = error.status;
|
|
67
|
+
if (explicit !== void 0) {
|
|
68
|
+
return { transient: Boolean(explicit), status, message: error.message };
|
|
69
|
+
}
|
|
70
|
+
const code = error.code;
|
|
71
|
+
if (typeof code === "string" && TRANSIENT_PG_SQLSTATES.has(code)) {
|
|
72
|
+
return { transient: true, status, message: error.message };
|
|
73
|
+
}
|
|
74
|
+
if (typeof status === "number") {
|
|
75
|
+
return {
|
|
76
|
+
transient: TRANSIENT_STATUS_CODES.has(status),
|
|
77
|
+
status,
|
|
78
|
+
message: error.message
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const haystack = `${error.name} ${error.message}`;
|
|
82
|
+
const matchesPattern = TRANSIENT_CODE_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
83
|
+
return { transient: matchesPattern, message: error.message };
|
|
84
|
+
}
|
|
85
|
+
return { transient: false, message: String(error) };
|
|
86
|
+
}
|
|
87
|
+
function isReauthError(classification) {
|
|
88
|
+
return classification.status === 401 || /unauthorized|invalid_grant|requires_reauth/i.test(classification.message);
|
|
89
|
+
}
|
|
90
|
+
function computeBackoffMs(attemptNumber) {
|
|
91
|
+
const base = 1e3 * Math.pow(2, Math.max(0, attemptNumber - 1));
|
|
92
|
+
const capped = Math.min(base, 6e4);
|
|
93
|
+
const jitter = Math.floor(Math.random() * 500);
|
|
94
|
+
return capped + jitter;
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
classifyOutboundError,
|
|
98
|
+
computeBackoffMs,
|
|
99
|
+
isReauthError
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=error-classification.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/communication_channels/lib/error-classification.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Classify provider errors for outbound retry decisions.\n *\n * Transient errors (network, 429, 5xx, timeout) \u2192 retry with backoff.\n * Permanent errors (4xx auth/validation/quota) \u2192 fail fast, no retry.\n *\n * Adapters can also throw classified errors directly by setting `transient: false`\n * on their thrown Error instance. This helper falls back to heuristics when an\n * adapter throws plain Error instances.\n */\n\nexport type ErrorClassification = {\n transient: boolean\n /** HTTP status if known; helps with backoff decisions (e.g., `Retry-After`) */\n status?: number\n /** Human-readable summary suitable for logging. */\n message: string\n}\n\nconst TRANSIENT_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524])\n\n/**\n * Postgres SQLSTATEs that are transient (the operation can succeed on retry).\n * These reach the inbound-ingest classifier when a DB blip happens mid-ingest;\n * treating them as permanent would dead-letter the message AND advance the IMAP\n * cursor, silently losing inbound mail (the exact \"cursor drift\" failure the\n * email spec set out to prevent). Driver errors expose the SQLSTATE on `.code`.\n */\nconst TRANSIENT_PG_SQLSTATES = new Set([\n '40001', // serialization_failure\n '40P01', // deadlock_detected\n '55P03', // lock_not_available\n '53300', // too_many_connections\n '53400', // configuration_limit_exceeded\n '57P01', // admin_shutdown\n '57P02', // crash_shutdown\n '57P03', // cannot_connect_now (db starting up)\n '08000', // connection_exception\n '08001', // sqlclient_unable_to_establish_sqlconnection\n '08003', // connection_does_not_exist\n '08006', // connection_failure\n])\n\nconst TRANSIENT_CODE_PATTERNS = [\n /ECONNRESET/i,\n /ETIMEDOUT/i,\n /ECONNREFUSED/i,\n /ENOTFOUND/i,\n /EAI_AGAIN/i,\n /socket hang up/i,\n /network timeout/i,\n /\\btimed?\\s*out\\b/i,\n /service unavailable/i,\n /rate limit/i,\n /too many requests/i,\n /temporarily unavailable/i,\n /try again later/i,\n // imapflow surfaces these on TLS/socket-level drops that almost always\n // recover on the next attempt. Treating them as permanent kills the\n // channel after a single bad packet (regression observed during the demo:\n // a network hiccup put a freshly-connected mailbox into a non-recoverable\n // `error` state until the operator manually clicked \"Retry\").\n /unexpected close/i,\n /connection not available/i,\n /connection closed/i,\n /server closed connection/i,\n // Postgres transient failures surfaced as text by the ORM wrapper (the\n // SQLSTATE on `.code` is the primary signal; these catch wrapped errors that\n // only carry the message). See TRANSIENT_PG_SQLSTATES.\n /deadlock detected/i,\n /could not serialize access/i,\n /connection terminated/i,\n /too many clients already/i,\n /the database system is (starting up|shutting down|in recovery)/i,\n]\n\nexport function classifyOutboundError(error: unknown): ErrorClassification {\n if (!error) {\n return { transient: false, message: 'Unknown error' }\n }\n\n if (error instanceof Error) {\n // Explicit hint from a classification-aware adapter.\n const explicit = (error as Error & { transient?: boolean; status?: number }).transient\n const status = (error as Error & { status?: number }).status\n if (explicit !== undefined) {\n return { transient: Boolean(explicit), status, message: error.message }\n }\n // Postgres driver errors expose the SQLSTATE on `.code`. A transient DB\n // failure during inbound ingest MUST classify as transient so the caller\n // aborts without advancing the cursor (no mail loss) rather than dead-lettering.\n const code = (error as Error & { code?: string }).code\n if (typeof code === 'string' && TRANSIENT_PG_SQLSTATES.has(code)) {\n return { transient: true, status, message: error.message }\n }\n if (typeof status === 'number') {\n return {\n transient: TRANSIENT_STATUS_CODES.has(status),\n status,\n message: error.message,\n }\n }\n const haystack = `${error.name} ${error.message}`\n const matchesPattern = TRANSIENT_CODE_PATTERNS.some((pattern) => pattern.test(haystack))\n return { transient: matchesPattern, message: error.message }\n }\n\n return { transient: false, message: String(error) }\n}\n\n/**\n * Decide whether a classified provider error means the channel's stored\n * credentials are no longer valid and the user must re-authorize.\n *\n * A 401, or an `invalid_grant` / `unauthorized` message, is unrecoverable by\n * retry: the access token is rejected and (for OAuth) the refresh token is\n * likely revoked too. Provider adapters may also surface the explicit\n * `requires_reauth` sentinel instead of a status \u2014 e.g. Gmail\n * `sendMessage` returns `{ status: 'failed', error: 'requires_reauth' }` on a\n * 401, which the outbound command rethrows as a status-less `Error`. Match that\n * sentinel too so the channel still flips to `requires_reauth`. Callers flip the\n * channel so the operator gets a clear signal. Kept identical to the inbound\n * poll path so inbound and outbound failures behave consistently.\n */\nexport function isReauthError(classification: ErrorClassification): boolean {\n return (\n classification.status === 401 ||\n /unauthorized|invalid_grant|requires_reauth/i.test(classification.message)\n )\n}\n\n/**\n * Compute exponential backoff delay (ms) for the given attempt number.\n *\n * Attempt 1 (= first retry) \u2192 1s, attempt 2 \u2192 2s, attempt 3 \u2192 4s, ...\n * Capped at 60s. Plus a small jitter so concurrent failures don't all retry\n * simultaneously.\n */\nexport function computeBackoffMs(attemptNumber: number): number {\n const base = 1000 * Math.pow(2, Math.max(0, attemptNumber - 1))\n const capped = Math.min(base, 60_000)\n const jitter = Math.floor(Math.random() * 500)\n return capped + jitter\n}\n"],
|
|
5
|
+
"mappings": "AAmBA,MAAM,yBAAyB,oBAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAS9F,MAAM,yBAAyB,oBAAI,IAAI;AAAA,EACrC;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF,CAAC;AAED,MAAM,0BAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,sBAAsB,OAAqC;AACzE,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,WAAW,OAAO,SAAS,gBAAgB;AAAA,EACtD;AAEA,MAAI,iBAAiB,OAAO;AAE1B,UAAM,WAAY,MAA2D;AAC7E,UAAM,SAAU,MAAsC;AACtD,QAAI,aAAa,QAAW;AAC1B,aAAO,EAAE,WAAW,QAAQ,QAAQ,GAAG,QAAQ,SAAS,MAAM,QAAQ;AAAA,IACxE;AAIA,UAAM,OAAQ,MAAoC;AAClD,QAAI,OAAO,SAAS,YAAY,uBAAuB,IAAI,IAAI,GAAG;AAChE,aAAO,EAAE,WAAW,MAAM,QAAQ,SAAS,MAAM,QAAQ;AAAA,IAC3D;AACA,QAAI,OAAO,WAAW,UAAU;AAC9B,aAAO;AAAA,QACL,WAAW,uBAAuB,IAAI,MAAM;AAAA,QAC5C;AAAA,QACA,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AACA,UAAM,WAAW,GAAG,MAAM,IAAI,IAAI,MAAM,OAAO;AAC/C,UAAM,iBAAiB,wBAAwB,KAAK,CAAC,YAAY,QAAQ,KAAK,QAAQ,CAAC;AACvF,WAAO,EAAE,WAAW,gBAAgB,SAAS,MAAM,QAAQ;AAAA,EAC7D;AAEA,SAAO,EAAE,WAAW,OAAO,SAAS,OAAO,KAAK,EAAE;AACpD;AAgBO,SAAS,cAAc,gBAA8C;AAC1E,SACE,eAAe,WAAW,OAC1B,8CAA8C,KAAK,eAAe,OAAO;AAE7E;AASO,SAAS,iBAAiB,eAA+B;AAC9D,QAAM,OAAO,MAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,gBAAgB,CAAC,CAAC;AAC9D,QAAM,SAAS,KAAK,IAAI,MAAM,GAAM;AACpC,QAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AAC7C,SAAO,SAAS;AAClB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
const GOOGLE_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs";
|
|
3
|
+
const CERT_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
4
|
+
const CERT_FETCH_TIMEOUT_MS = 5e3;
|
|
5
|
+
const GOOGLE_ACCEPTED_ISSUERS = /* @__PURE__ */ new Set(["https://accounts.google.com", "accounts.google.com"]);
|
|
6
|
+
class GmailPubSubJwtError extends Error {
|
|
7
|
+
constructor(message, code) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "GmailPubSubJwtError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class FetchGmailPubSubVerifier {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.certCache = null;
|
|
16
|
+
}
|
|
17
|
+
async verify(input) {
|
|
18
|
+
const token = extractBearer(input.authorizationHeader);
|
|
19
|
+
if (!token) throw new GmailPubSubJwtError("Missing Authorization bearer token", "missing_token");
|
|
20
|
+
const parts = token.split(".");
|
|
21
|
+
if (parts.length !== 3) {
|
|
22
|
+
throw new GmailPubSubJwtError("JWT must have three dot-separated parts", "invalid_format");
|
|
23
|
+
}
|
|
24
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
25
|
+
let header;
|
|
26
|
+
let claims;
|
|
27
|
+
try {
|
|
28
|
+
header = JSON.parse(Buffer.from(headerB64, "base64url").toString("utf-8"));
|
|
29
|
+
claims = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8"));
|
|
30
|
+
} catch {
|
|
31
|
+
throw new GmailPubSubJwtError("JWT header/payload not parseable", "invalid_format");
|
|
32
|
+
}
|
|
33
|
+
const kid = typeof header.kid === "string" ? header.kid : null;
|
|
34
|
+
const alg = typeof header.alg === "string" ? header.alg : null;
|
|
35
|
+
if (!kid || alg !== "RS256") {
|
|
36
|
+
throw new GmailPubSubJwtError(`Unsupported JWT alg/kid: alg=${alg ?? "?"} kid=${kid ?? "?"}`, "invalid_format");
|
|
37
|
+
}
|
|
38
|
+
const certs = await this.getCerts();
|
|
39
|
+
const cert = certs[kid];
|
|
40
|
+
if (!cert) {
|
|
41
|
+
this.certCache = null;
|
|
42
|
+
const refreshed = await this.getCerts();
|
|
43
|
+
const fresh = refreshed[kid];
|
|
44
|
+
if (!fresh) throw new GmailPubSubJwtError(`No cert for kid=${kid}`, "invalid_signature");
|
|
45
|
+
verifySignature(`${headerB64}.${payloadB64}`, signatureB64, fresh);
|
|
46
|
+
} else {
|
|
47
|
+
verifySignature(`${headerB64}.${payloadB64}`, signatureB64, cert);
|
|
48
|
+
}
|
|
49
|
+
validateClaims(claims, input);
|
|
50
|
+
return claims;
|
|
51
|
+
}
|
|
52
|
+
async getCerts() {
|
|
53
|
+
if (this.certCache && Date.now() - this.certCache.fetchedAt < CERT_CACHE_TTL_MS) {
|
|
54
|
+
return this.certCache.certs;
|
|
55
|
+
}
|
|
56
|
+
let res;
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => controller.abort(), CERT_FETCH_TIMEOUT_MS);
|
|
59
|
+
try {
|
|
60
|
+
res = await fetch(GOOGLE_CERTS_URL, { signal: controller.signal });
|
|
61
|
+
} catch (err) {
|
|
62
|
+
throw new GmailPubSubJwtError(
|
|
63
|
+
`Failed to fetch Google certs: ${err instanceof Error ? err.message : String(err)}`,
|
|
64
|
+
"fetch_certs_failed"
|
|
65
|
+
);
|
|
66
|
+
} finally {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
}
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
throw new GmailPubSubJwtError(`Google certs endpoint returned ${res.status}`, "fetch_certs_failed");
|
|
71
|
+
}
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = await res.json();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new GmailPubSubJwtError(
|
|
77
|
+
`Google certs endpoint returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
78
|
+
"fetch_certs_failed"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const certs = toCertMap(parsed);
|
|
82
|
+
if (!certs) {
|
|
83
|
+
throw new GmailPubSubJwtError("Google certs endpoint returned an unexpected shape", "fetch_certs_failed");
|
|
84
|
+
}
|
|
85
|
+
this.certCache = { certs, fetchedAt: Date.now() };
|
|
86
|
+
return certs;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function toCertMap(value) {
|
|
90
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
91
|
+
const entries = Object.entries(value);
|
|
92
|
+
if (entries.length === 0) return null;
|
|
93
|
+
const certs = {};
|
|
94
|
+
for (const [kid, pem] of entries) {
|
|
95
|
+
if (typeof pem !== "string" || pem.length === 0) return null;
|
|
96
|
+
certs[kid] = pem;
|
|
97
|
+
}
|
|
98
|
+
return certs;
|
|
99
|
+
}
|
|
100
|
+
function extractBearer(header) {
|
|
101
|
+
if (!header) return null;
|
|
102
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
103
|
+
return match ? match[1].trim() : null;
|
|
104
|
+
}
|
|
105
|
+
function verifySignature(input, signatureB64, cert) {
|
|
106
|
+
const verifier = crypto.createVerify("RSA-SHA256");
|
|
107
|
+
verifier.update(input);
|
|
108
|
+
verifier.end();
|
|
109
|
+
const signature = Buffer.from(signatureB64, "base64url");
|
|
110
|
+
const ok = verifier.verify(cert, signature);
|
|
111
|
+
if (!ok) {
|
|
112
|
+
throw new GmailPubSubJwtError("JWT signature verification failed", "invalid_signature");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function validateClaims(claims, input) {
|
|
116
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
117
|
+
if (typeof claims.exp !== "number" || claims.exp < now - 5) {
|
|
118
|
+
throw new GmailPubSubJwtError("JWT expired or missing exp", "expired");
|
|
119
|
+
}
|
|
120
|
+
if (typeof claims.iat === "number" && claims.iat > now + 5) {
|
|
121
|
+
throw new GmailPubSubJwtError("JWT issued in the future", "expired");
|
|
122
|
+
}
|
|
123
|
+
if (typeof claims.iss !== "string" || !GOOGLE_ACCEPTED_ISSUERS.has(claims.iss)) {
|
|
124
|
+
throw new GmailPubSubJwtError(
|
|
125
|
+
`JWT issuer not accepted (got ${typeof claims.iss === "string" ? claims.iss : "none"})`,
|
|
126
|
+
"wrong_issuer"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const audOk = Array.isArray(claims.aud) ? claims.aud.includes(input.expectedAudience) : claims.aud === input.expectedAudience;
|
|
130
|
+
if (!audOk) {
|
|
131
|
+
throw new GmailPubSubJwtError(
|
|
132
|
+
`JWT audience mismatch (expected ${input.expectedAudience})`,
|
|
133
|
+
"wrong_audience"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const emailVerified = claims.emailVerified === true || claims.email_verified === true;
|
|
137
|
+
if (!emailVerified || claims.email !== input.expectedEmail) {
|
|
138
|
+
throw new GmailPubSubJwtError(
|
|
139
|
+
`JWT email mismatch (expected ${input.expectedEmail})`,
|
|
140
|
+
"wrong_email"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let cachedVerifier = null;
|
|
145
|
+
function getGmailPubSubVerifier() {
|
|
146
|
+
if (!cachedVerifier) cachedVerifier = new FetchGmailPubSubVerifier();
|
|
147
|
+
return cachedVerifier;
|
|
148
|
+
}
|
|
149
|
+
function setGmailPubSubVerifier(verifier) {
|
|
150
|
+
cachedVerifier = verifier;
|
|
151
|
+
}
|
|
152
|
+
function decodeGmailPubSubBody(rawBody) {
|
|
153
|
+
let envelope;
|
|
154
|
+
try {
|
|
155
|
+
envelope = JSON.parse(rawBody);
|
|
156
|
+
} catch {
|
|
157
|
+
throw new GmailPubSubJwtError("Body is not valid JSON", "invalid_format");
|
|
158
|
+
}
|
|
159
|
+
if (!envelope.message?.data) {
|
|
160
|
+
throw new GmailPubSubJwtError("Envelope missing message.data", "invalid_format");
|
|
161
|
+
}
|
|
162
|
+
let payloadText;
|
|
163
|
+
try {
|
|
164
|
+
payloadText = Buffer.from(envelope.message.data, "base64").toString("utf-8");
|
|
165
|
+
} catch {
|
|
166
|
+
throw new GmailPubSubJwtError("message.data not base64 decodable", "invalid_format");
|
|
167
|
+
}
|
|
168
|
+
let payload;
|
|
169
|
+
try {
|
|
170
|
+
payload = JSON.parse(payloadText);
|
|
171
|
+
} catch {
|
|
172
|
+
throw new GmailPubSubJwtError("message.data JSON not parseable", "invalid_format");
|
|
173
|
+
}
|
|
174
|
+
if (!payload.emailAddress || payload.historyId === void 0) {
|
|
175
|
+
throw new GmailPubSubJwtError("message.data missing emailAddress or historyId", "invalid_format");
|
|
176
|
+
}
|
|
177
|
+
return payload;
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
GmailPubSubJwtError,
|
|
181
|
+
decodeGmailPubSubBody,
|
|
182
|
+
getGmailPubSubVerifier,
|
|
183
|
+
setGmailPubSubVerifier
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=gmail-pubsub-jwt.js.map
|