@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4397.1.9a65481757

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
  3. package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
  4. package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
  5. package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
  6. package/dist/generated/entities/channel_thread_token/index.js +17 -0
  7. package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
  8. package/dist/generated/entities/communication_channel/index.js +43 -0
  9. package/dist/generated/entities/communication_channel/index.js.map +7 -0
  10. package/dist/generated/entities/customer_interaction/index.js +4 -0
  11. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  12. package/dist/generated/entities/external_conversation/index.js +25 -0
  13. package/dist/generated/entities/external_conversation/index.js.map +7 -0
  14. package/dist/generated/entities/external_message/index.js +25 -0
  15. package/dist/generated/entities/external_message/index.js.map +7 -0
  16. package/dist/generated/entities/integration_credentials/index.js +3 -1
  17. package/dist/generated/entities/integration_credentials/index.js.map +2 -2
  18. package/dist/generated/entities/message/index.js +2 -0
  19. package/dist/generated/entities/message/index.js.map +2 -2
  20. package/dist/generated/entities/message_channel_link/index.js +33 -0
  21. package/dist/generated/entities/message_channel_link/index.js.map +7 -0
  22. package/dist/generated/entities/message_reaction/index.js +25 -0
  23. package/dist/generated/entities/message_reaction/index.js.map +7 -0
  24. package/dist/generated/entities.ids.generated.js +11 -0
  25. package/dist/generated/entities.ids.generated.js.map +2 -2
  26. package/dist/generated/entity-fields-registry.js +117 -0
  27. package/dist/generated/entity-fields-registry.js.map +2 -2
  28. package/dist/helpers/integration/authFixtures.js +2 -1
  29. package/dist/helpers/integration/authFixtures.js.map +2 -2
  30. package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
  31. package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
  32. package/dist/modules/communication_channels/acl.js +47 -0
  33. package/dist/modules/communication_channels/acl.js.map +7 -0
  34. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
  37. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
  38. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
  39. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
  40. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
  41. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
  42. package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
  43. package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
  44. package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
  45. package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
  46. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
  47. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
  48. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
  49. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
  50. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
  51. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
  52. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
  53. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
  54. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
  55. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
  56. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
  57. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
  58. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
  59. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
  60. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
  61. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
  62. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
  63. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
  64. package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
  65. package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
  66. package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
  67. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
  68. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
  69. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
  70. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
  71. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
  72. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
  73. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
  74. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
  75. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
  76. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
  77. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
  78. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
  79. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
  80. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
  81. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
  82. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
  83. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
  84. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
  85. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
  86. package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
  87. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
  88. package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
  89. package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
  90. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
  91. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
  92. package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
  93. package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
  94. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
  95. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
  96. package/dist/modules/communication_channels/commands/interceptors.js +68 -0
  97. package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
  98. package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
  99. package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
  100. package/dist/modules/communication_channels/commands/push-register.js +146 -0
  101. package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
  102. package/dist/modules/communication_channels/commands/push-renew.js +23 -0
  103. package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
  104. package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
  105. package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
  106. package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
  107. package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
  108. package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
  109. package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
  110. package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
  111. package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
  112. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
  113. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
  114. package/dist/modules/communication_channels/data/enrichers.js +286 -0
  115. package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
  116. package/dist/modules/communication_channels/data/entities.js +447 -0
  117. package/dist/modules/communication_channels/data/entities.js.map +7 -0
  118. package/dist/modules/communication_channels/data/extensions.js +67 -0
  119. package/dist/modules/communication_channels/data/extensions.js.map +7 -0
  120. package/dist/modules/communication_channels/data/validators.js +123 -0
  121. package/dist/modules/communication_channels/data/validators.js.map +7 -0
  122. package/dist/modules/communication_channels/di.js +35 -0
  123. package/dist/modules/communication_channels/di.js.map +7 -0
  124. package/dist/modules/communication_channels/encryption.js +12 -0
  125. package/dist/modules/communication_channels/encryption.js.map +7 -0
  126. package/dist/modules/communication_channels/events.js +124 -0
  127. package/dist/modules/communication_channels/events.js.map +7 -0
  128. package/dist/modules/communication_channels/index.js +20 -0
  129. package/dist/modules/communication_channels/index.js.map +7 -0
  130. package/dist/modules/communication_channels/lib/access-control.js +43 -0
  131. package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
  132. package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
  133. package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
  134. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
  135. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
  136. package/dist/modules/communication_channels/lib/adapter.js +1 -0
  137. package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
  138. package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
  139. package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
  140. package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
  141. package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
  142. package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
  143. package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
  144. package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
  145. package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
  146. package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
  147. package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
  148. package/dist/modules/communication_channels/lib/email-contact.js +14 -0
  149. package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
  150. package/dist/modules/communication_channels/lib/email-mime.js +269 -0
  151. package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
  152. package/dist/modules/communication_channels/lib/error-classification.js +101 -0
  153. package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
  154. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
  155. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
  156. package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
  157. package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
  158. package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
  159. package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
  160. package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
  161. package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
  162. package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
  163. package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
  164. package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
  165. package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
  166. package/dist/modules/communication_channels/lib/provider-health.js +24 -0
  167. package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
  168. package/dist/modules/communication_channels/lib/push-state.js +19 -0
  169. package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
  170. package/dist/modules/communication_channels/lib/queue.js +54 -0
  171. package/dist/modules/communication_channels/lib/queue.js.map +7 -0
  172. package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
  173. package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
  174. package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
  175. package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
  176. package/dist/modules/communication_channels/lib/registry.js +67 -0
  177. package/dist/modules/communication_channels/lib/registry.js.map +7 -0
  178. package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
  179. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
  180. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
  181. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
  182. package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
  183. package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
  184. package/dist/modules/communication_channels/lib/system-user.js +22 -0
  185. package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
  186. package/dist/modules/communication_channels/lib/test-seed.js +68 -0
  187. package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
  188. package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
  189. package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
  190. package/dist/modules/communication_channels/lib/thread-token.js +219 -0
  191. package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
  192. package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
  193. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
  194. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
  195. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
  196. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
  197. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
  198. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
  199. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
  200. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
  201. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
  202. package/dist/modules/communication_channels/notifications.client.js +51 -0
  203. package/dist/modules/communication_channels/notifications.client.js.map +7 -0
  204. package/dist/modules/communication_channels/notifications.handlers.js +53 -0
  205. package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
  206. package/dist/modules/communication_channels/notifications.js +56 -0
  207. package/dist/modules/communication_channels/notifications.js.map +7 -0
  208. package/dist/modules/communication_channels/setup.js +105 -0
  209. package/dist/modules/communication_channels/setup.js.map +7 -0
  210. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
  211. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
  212. package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
  213. package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
  214. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
  215. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
  216. package/dist/modules/communication_channels/widgets/components.js +7 -0
  217. package/dist/modules/communication_channels/widgets/components.js.map +7 -0
  218. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
  219. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
  220. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
  221. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
  222. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
  223. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
  224. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
  225. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
  226. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
  227. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
  228. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
  229. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
  230. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
  231. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
  232. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
  233. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
  234. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
  235. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
  236. package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
  237. package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
  238. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
  239. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
  240. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
  241. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
  242. package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
  243. package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
  244. package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
  245. package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
  246. package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
  247. package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
  248. package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
  249. package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
  250. package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
  251. package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
  252. package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
  253. package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
  254. package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
  255. package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
  256. package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
  257. package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
  258. package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
  259. package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
  260. package/dist/modules/customers/acl.js +18 -0
  261. package/dist/modules/customers/acl.js.map +2 -2
  262. package/dist/modules/customers/api/activities/route.js +9 -0
  263. package/dist/modules/customers/api/activities/route.js.map +2 -2
  264. package/dist/modules/customers/api/companies/[id]/route.js +18 -7
  265. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  266. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
  267. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
  268. package/dist/modules/customers/api/interactions/counts/route.js +6 -0
  269. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  270. package/dist/modules/customers/api/interactions/route.js +26 -7
  271. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  272. package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
  273. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
  274. package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
  275. package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
  276. package/dist/modules/customers/api/people/[id]/route.js +12 -4
  277. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  278. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
  279. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  280. package/dist/modules/customers/commands/deals.js +46 -5
  281. package/dist/modules/customers/commands/deals.js.map +2 -2
  282. package/dist/modules/customers/commands/interactions.js +16 -0
  283. package/dist/modules/customers/commands/interactions.js.map +2 -2
  284. package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
  285. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  286. package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
  287. package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
  288. package/dist/modules/customers/components/detail/DealForm.js +2 -1
  289. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  290. package/dist/modules/customers/components/detail/DealsSection.js +10 -0
  291. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  292. package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
  293. package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
  294. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
  295. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
  296. package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
  297. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  298. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
  299. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
  300. package/dist/modules/customers/data/enrichers.js +133 -2
  301. package/dist/modules/customers/data/enrichers.js.map +2 -2
  302. package/dist/modules/customers/data/entities.js +18 -0
  303. package/dist/modules/customers/data/entities.js.map +2 -2
  304. package/dist/modules/customers/data/extensions.js +16 -0
  305. package/dist/modules/customers/data/extensions.js.map +7 -0
  306. package/dist/modules/customers/encryption.js +11 -0
  307. package/dist/modules/customers/encryption.js.map +2 -2
  308. package/dist/modules/customers/events.js +4 -1
  309. package/dist/modules/customers/events.js.map +2 -2
  310. package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
  311. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
  312. package/dist/modules/customers/lib/kysely.js.map +2 -2
  313. package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
  314. package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
  315. package/dist/modules/customers/lib/personEmailThreads.js +205 -0
  316. package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
  317. package/dist/modules/customers/lib/visibilityFilter.js +51 -0
  318. package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
  319. package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
  320. package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
  321. package/dist/modules/customers/setup.js +2 -1
  322. package/dist/modules/customers/setup.js.map +2 -2
  323. package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
  324. package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
  325. package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
  326. package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
  327. package/dist/modules/integrations/data/entities.js +8 -1
  328. package/dist/modules/integrations/data/entities.js.map +2 -2
  329. package/dist/modules/integrations/lib/credentials-service.js +29 -14
  330. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  331. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
  332. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
  333. package/dist/modules/messages/commands/messages.js +70 -8
  334. package/dist/modules/messages/commands/messages.js.map +2 -2
  335. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
  336. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
  337. package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
  338. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  339. package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
  340. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  341. package/dist/modules/messages/data/entities.js +8 -1
  342. package/dist/modules/messages/data/entities.js.map +2 -2
  343. package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
  344. package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
  345. package/dist/modules/messages/widgets/injection-table.js +7 -0
  346. package/dist/modules/messages/widgets/injection-table.js.map +7 -0
  347. package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
  348. package/generated/entities/channel_thread_mapping/index.ts +11 -0
  349. package/generated/entities/channel_thread_token/index.ts +7 -0
  350. package/generated/entities/communication_channel/index.ts +20 -0
  351. package/generated/entities/customer_interaction/index.ts +2 -0
  352. package/generated/entities/external_conversation/index.ts +11 -0
  353. package/generated/entities/external_message/index.ts +11 -0
  354. package/generated/entities/integration_credentials/index.ts +1 -0
  355. package/generated/entities/message/index.ts +1 -0
  356. package/generated/entities/message_channel_link/index.ts +15 -0
  357. package/generated/entities/message_reaction/index.ts +11 -0
  358. package/generated/entities.ids.generated.ts +11 -0
  359. package/generated/entity-fields-registry.ts +117 -0
  360. package/package.json +9 -7
  361. package/src/helpers/integration/authFixtures.ts +4 -1
  362. package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
  363. package/src/modules/communication_channels/acl.ts +43 -0
  364. package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
  365. package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
  366. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
  367. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
  368. package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
  369. package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
  370. package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
  371. package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
  372. package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
  373. package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
  374. package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
  375. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
  376. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
  377. package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
  378. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
  379. package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
  380. package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
  381. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
  382. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
  383. package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
  384. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
  385. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
  386. package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
  387. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
  388. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
  389. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
  390. package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
  391. package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
  392. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
  393. package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
  394. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
  395. package/src/modules/communication_channels/commands/interceptors.ts +104 -0
  396. package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
  397. package/src/modules/communication_channels/commands/push-register.ts +203 -0
  398. package/src/modules/communication_channels/commands/push-renew.ts +49 -0
  399. package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
  400. package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
  401. package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
  402. package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
  403. package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
  404. package/src/modules/communication_channels/data/enrichers.ts +413 -0
  405. package/src/modules/communication_channels/data/entities.ts +546 -0
  406. package/src/modules/communication_channels/data/extensions.ts +76 -0
  407. package/src/modules/communication_channels/data/validators.ts +138 -0
  408. package/src/modules/communication_channels/di.ts +40 -0
  409. package/src/modules/communication_channels/encryption.ts +44 -0
  410. package/src/modules/communication_channels/events.ts +122 -0
  411. package/src/modules/communication_channels/i18n/de.json +138 -0
  412. package/src/modules/communication_channels/i18n/en.json +138 -0
  413. package/src/modules/communication_channels/i18n/es.json +138 -0
  414. package/src/modules/communication_channels/i18n/pl.json +138 -0
  415. package/src/modules/communication_channels/index.ts +19 -0
  416. package/src/modules/communication_channels/lib/access-control.ts +110 -0
  417. package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
  418. package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
  419. package/src/modules/communication_channels/lib/adapter.ts +605 -0
  420. package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
  421. package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
  422. package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
  423. package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
  424. package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
  425. package/src/modules/communication_channels/lib/email-contact.ts +17 -0
  426. package/src/modules/communication_channels/lib/email-mime.ts +442 -0
  427. package/src/modules/communication_channels/lib/error-classification.ts +144 -0
  428. package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
  429. package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
  430. package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
  431. package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
  432. package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
  433. package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
  434. package/src/modules/communication_channels/lib/provider-health.ts +47 -0
  435. package/src/modules/communication_channels/lib/push-state.ts +38 -0
  436. package/src/modules/communication_channels/lib/queue.ts +66 -0
  437. package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
  438. package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
  439. package/src/modules/communication_channels/lib/registry.ts +99 -0
  440. package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
  441. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
  442. package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
  443. package/src/modules/communication_channels/lib/system-user.ts +74 -0
  444. package/src/modules/communication_channels/lib/test-seed.ts +140 -0
  445. package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
  446. package/src/modules/communication_channels/lib/thread-token.ts +355 -0
  447. package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
  448. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
  449. package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
  450. package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
  451. package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
  452. package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
  453. package/src/modules/communication_channels/notifications.client.ts +50 -0
  454. package/src/modules/communication_channels/notifications.handlers.ts +86 -0
  455. package/src/modules/communication_channels/notifications.ts +52 -0
  456. package/src/modules/communication_channels/setup.ts +158 -0
  457. package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
  458. package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
  459. package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
  460. package/src/modules/communication_channels/widgets/components.ts +36 -0
  461. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
  462. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
  463. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
  464. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
  465. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
  466. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
  467. package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
  468. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
  469. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
  470. package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
  471. package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
  472. package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
  473. package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
  474. package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
  475. package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
  476. package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
  477. package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
  478. package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
  479. package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
  480. package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
  481. package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
  482. package/src/modules/customers/acl.ts +18 -0
  483. package/src/modules/customers/api/activities/route.ts +13 -0
  484. package/src/modules/customers/api/companies/[id]/route.ts +21 -1
  485. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
  486. package/src/modules/customers/api/interactions/counts/route.ts +10 -0
  487. package/src/modules/customers/api/interactions/route.ts +51 -5
  488. package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
  489. package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
  490. package/src/modules/customers/api/people/[id]/route.ts +17 -2
  491. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
  492. package/src/modules/customers/commands/deals.ts +65 -6
  493. package/src/modules/customers/commands/interactions.ts +30 -0
  494. package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
  495. package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
  496. package/src/modules/customers/components/detail/DealForm.tsx +2 -1
  497. package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
  498. package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
  499. package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
  500. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
  501. package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
  502. package/src/modules/customers/data/enrichers.ts +252 -1
  503. package/src/modules/customers/data/entities.ts +46 -1
  504. package/src/modules/customers/data/extensions.ts +26 -0
  505. package/src/modules/customers/encryption.ts +11 -0
  506. package/src/modules/customers/events.ts +4 -0
  507. package/src/modules/customers/i18n/de.json +41 -0
  508. package/src/modules/customers/i18n/en.json +41 -0
  509. package/src/modules/customers/i18n/es.json +41 -0
  510. package/src/modules/customers/i18n/pl.json +41 -0
  511. package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
  512. package/src/modules/customers/lib/kysely.ts +16 -0
  513. package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
  514. package/src/modules/customers/lib/personEmailThreads.ts +325 -0
  515. package/src/modules/customers/lib/visibilityFilter.ts +152 -0
  516. package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
  517. package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
  518. package/src/modules/customers/setup.ts +1 -0
  519. package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
  520. package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
  521. package/src/modules/integrations/AGENTS.md +9 -0
  522. package/src/modules/integrations/data/entities.ts +21 -1
  523. package/src/modules/integrations/lib/credentials-service.ts +49 -13
  524. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
  525. package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
  526. package/src/modules/messages/commands/messages.ts +101 -8
  527. package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
  528. package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
  529. package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
  530. package/src/modules/messages/data/entities.ts +11 -0
  531. package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
  532. package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
  533. package/src/modules/messages/widgets/injection-table.ts +29 -0
@@ -0,0 +1,442 @@
1
+ import crypto from 'node:crypto'
2
+ import type { NormalizedInboundMessage, NormalizedAttachment } from './adapter'
3
+ import { EMAIL_MAX_ATTACHMENT_BYTES } from './email-capabilities'
4
+
5
+ /**
6
+ * Aggregate ceiling for all attachments on a single inbound message. Inbound
7
+ * mail is untrusted, so without a cap a malicious/large message would be fully
8
+ * base64-buffered in memory (~1.33x raw bytes) and persisted. Allow a small
9
+ * multiple of the per-attachment limit for legitimate multi-file emails.
10
+ */
11
+ const TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES = EMAIL_MAX_ATTACHMENT_BYTES * 2
12
+
13
+ /**
14
+ * Shared email MIME helpers for the email channel providers (Gmail, IMAP).
15
+ * Outbound assembly, inbound parsing, header/address normalization,
16
+ * and threading-id extraction all live here so every provider shares one
17
+ * correct implementation instead of copy-pasting (which previously let Gmail's
18
+ * `extractHeaders` drift into a Map-handling bug that IMAP had already fixed).
19
+ *
20
+ * Provider-specific transport (Gmail History API, IMAP UID sync)
21
+ * stays in each package — this module only owns the format-level plumbing.
22
+ */
23
+
24
+ // ── Outbound MIME helpers ─────────────────────────────────────
25
+
26
+ export function stringOrUndefined(value: unknown): string | undefined {
27
+ if (typeof value !== 'string') return undefined
28
+ const trimmed = value.trim()
29
+ return trimmed.length > 0 ? trimmed : undefined
30
+ }
31
+
32
+ export function toAddressList(value: unknown): string[] {
33
+ if (Array.isArray(value)) return value.map((v) => String(v).trim()).filter((v) => v.length > 0)
34
+ if (typeof value === 'string') {
35
+ return value
36
+ .split(/[,;]\s*/)
37
+ .map((v) => v.trim())
38
+ .filter((v) => v.length > 0)
39
+ }
40
+ return []
41
+ }
42
+
43
+ export function referencesFromMeta(value: unknown): string[] | undefined {
44
+ if (!Array.isArray(value)) return undefined
45
+ return value.filter((entry): entry is string => typeof entry === 'string')
46
+ }
47
+
48
+ /**
49
+ * Strip every `<tag>…</tag>` block (and its contents) for a single tag name.
50
+ * Inbound HTML is untrusted, so the matcher must resist evasion: the close tag
51
+ * allows trailing attributes/whitespace (`</script >`, `</style foo>`), the `i`
52
+ * flag covers mixed case, and the replacement loops until the string is stable
53
+ * so a payload split across nested or reconstructed tags cannot survive a
54
+ * single pass (`<scr<script>ipt>` collapsing back into `<script>`).
55
+ */
56
+ function stripTagBlocks(html: string, tag: string): string {
57
+ const blockPattern = new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}[^>]*>`, 'gi')
58
+ let previous: string
59
+ let current = html
60
+ do {
61
+ previous = current
62
+ current = current.replace(blockPattern, ' ')
63
+ } while (current !== previous)
64
+ return current
65
+ }
66
+
67
+ export function htmlToText(html: string): string {
68
+ return stripTagBlocks(stripTagBlocks(html, 'style'), 'script')
69
+ .replace(/<br\s*\/?>(?=\s*)/gi, '\n')
70
+ .replace(/<\/p\s*>/gi, '\n\n')
71
+ .replace(/<[^>]+>/g, '')
72
+ .replace(/&nbsp;/gi, ' ')
73
+ .replace(/&lt;/gi, '<')
74
+ .replace(/&gt;/gi, '>')
75
+ .replace(/&quot;/gi, '"')
76
+ .replace(/&amp;/gi, '&')
77
+ .replace(/\n{3,}/g, '\n\n')
78
+ .trim()
79
+ }
80
+
81
+ export function escapeQuotes(value: string): string {
82
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
83
+ }
84
+
85
+ /**
86
+ * Collapse CR/LF/TAB in an email header value to a single space to prevent
87
+ * RFC 5322 header injection — e.g. a Subject smuggling an extra
88
+ * `Bcc:`/`Content-Type:` header or splitting the message into a body.
89
+ * Collapsing (rather than folding) is safe for the short structured headers
90
+ * we emit (Subject, addresses, Message-ID, References).
91
+ */
92
+ export function sanitizeHeaderValue(value: string): string {
93
+ return value.replace(/[\r\n\t]+/g, ' ').trim()
94
+ }
95
+
96
+ export function ensureBrackets(value: string): string {
97
+ const trimmed = sanitizeHeaderValue(value)
98
+ if (trimmed.startsWith('<') && trimmed.endsWith('>')) return trimmed
99
+ return `<${trimmed}>`
100
+ }
101
+
102
+ function isPureAscii(value: string): boolean {
103
+ // eslint-disable-next-line no-control-regex
104
+ return /^[\x00-\x7F]*$/.test(value)
105
+ }
106
+
107
+ /**
108
+ * Encode a single header value as an RFC 2047 "B" (base64) encoded-word when it
109
+ * contains non-ASCII characters, so 8-bit text like "Café" survives strict MTAs
110
+ * that treat header bytes as 7-bit ASCII. Pure-ASCII values are returned
111
+ * unchanged. Apply only AFTER `sanitizeHeaderValue` so the CR/LF injection guard
112
+ * still runs against the raw value.
113
+ */
114
+ export function encodeHeaderWord(value: string): string {
115
+ if (isPureAscii(value)) return value
116
+ return `=?utf-8?B?${Buffer.from(value, 'utf-8').toString('base64')}?=`
117
+ }
118
+
119
+ /**
120
+ * Encode the display-name part of a single address header value, leaving the
121
+ * `<addr@domain>` untouched (per RFC 2047, encoded-words are not permitted
122
+ * inside the addr-spec). Inputs without a bracketed address are treated as a
123
+ * bare display name / address and encoded only when non-ASCII.
124
+ */
125
+ export function encodeAddressHeaderWord(value: string): string {
126
+ if (isPureAscii(value)) return value
127
+ const match = value.match(/^(.*?)(\s*<[^>]*>)\s*$/)
128
+ if (match) {
129
+ const [, displayPart, addrPart] = match
130
+ const displayName = displayPart.replace(/^"|"$/g, '').trim()
131
+ if (!displayName) return value
132
+ return `${encodeHeaderWord(displayName)}${addrPart}`
133
+ }
134
+ return encodeHeaderWord(value)
135
+ }
136
+
137
+ /**
138
+ * Generate an RFC 5322 Message-ID rooted in the sender's domain. Used as a
139
+ * downstream idempotency key, so entropy comes from `crypto.randomUUID()`
140
+ * rather than `Math.random()`.
141
+ */
142
+ export function generateMessageId(fromAddress: string, fallbackDomain = 'localhost'): string {
143
+ const domain = fromAddress.split('@')[1] ?? fallbackDomain
144
+ return `<${crypto.randomUUID()}@${domain}>`
145
+ }
146
+
147
+ export interface AssembleRfc2822Input {
148
+ from: string
149
+ to: string[]
150
+ cc: string[]
151
+ bcc: string[]
152
+ subject: string | undefined
153
+ text: string | undefined
154
+ html: string | undefined
155
+ inReplyTo: string | undefined
156
+ references: string[] | undefined
157
+ messageId: string
158
+ }
159
+
160
+ /**
161
+ * Render one MIME body part's CTE header + body content. Non-ASCII bodies are
162
+ * base64-encoded (CRLF-wrapped at 76 cols) and labelled
163
+ * `Content-Transfer-Encoding: base64` so 8-bit text survives strict MTAs;
164
+ * pure-ASCII bodies stay `7bit` and verbatim.
165
+ */
166
+ function encodeBodyPart(content: string): { cte: string; body: string } {
167
+ if (isPureAscii(content)) return { cte: '7bit', body: content }
168
+ const base64 = Buffer.from(content, 'utf-8').toString('base64')
169
+ const wrapped = base64.match(/.{1,76}/g)?.join('\r\n') ?? base64
170
+ return { cte: 'base64', body: wrapped }
171
+ }
172
+
173
+ /**
174
+ * Assemble a raw RFC2822 message (used by transports that send the encoded
175
+ * message directly, e.g. Gmail `users.messages.send`). Emits a
176
+ * `multipart/alternative` body when both html and text are present, otherwise a
177
+ * single-part text or html body.
178
+ */
179
+ export function assembleRfc2822(input: AssembleRfc2822Input): Buffer {
180
+ const boundary = `omc_${crypto.randomUUID()}`
181
+ const headers: string[] = []
182
+ headers.push(`From: ${encodeAddressHeaderWord(sanitizeHeaderValue(input.from))}`)
183
+ headers.push(`To: ${input.to.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(', ')}`)
184
+ if (input.cc.length) headers.push(`Cc: ${input.cc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(', ')}`)
185
+ if (input.bcc.length) headers.push(`Bcc: ${input.bcc.map((value) => encodeAddressHeaderWord(sanitizeHeaderValue(value))).join(', ')}`)
186
+ if (input.subject) headers.push(`Subject: ${encodeHeaderWord(sanitizeHeaderValue(input.subject))}`)
187
+ headers.push(`Message-ID: ${ensureBrackets(input.messageId)}`)
188
+ if (input.inReplyTo) headers.push(`In-Reply-To: ${ensureBrackets(input.inReplyTo)}`)
189
+ if (input.references && input.references.length) {
190
+ headers.push(`References: ${input.references.map(ensureBrackets).join(' ')}`)
191
+ }
192
+ headers.push('MIME-Version: 1.0')
193
+ headers.push(`Date: ${new Date().toUTCString()}`)
194
+
195
+ if (input.html && input.text) {
196
+ headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`)
197
+ const textPart = encodeBodyPart(input.text)
198
+ const htmlPart = encodeBodyPart(input.html)
199
+ const body = [
200
+ '',
201
+ `--${boundary}`,
202
+ 'Content-Type: text/plain; charset=utf-8',
203
+ `Content-Transfer-Encoding: ${textPart.cte}`,
204
+ '',
205
+ textPart.body,
206
+ `--${boundary}`,
207
+ 'Content-Type: text/html; charset=utf-8',
208
+ `Content-Transfer-Encoding: ${htmlPart.cte}`,
209
+ '',
210
+ htmlPart.body,
211
+ `--${boundary}--`,
212
+ '',
213
+ ].join('\r\n')
214
+ return Buffer.from(headers.join('\r\n') + body, 'utf-8')
215
+ }
216
+
217
+ if (input.html) {
218
+ const htmlPart = encodeBodyPart(input.html)
219
+ headers.push('Content-Type: text/html; charset=utf-8')
220
+ headers.push(`Content-Transfer-Encoding: ${htmlPart.cte}`)
221
+ return Buffer.from(headers.join('\r\n') + '\r\n\r\n' + htmlPart.body, 'utf-8')
222
+ }
223
+
224
+ const textPart = encodeBodyPart(input.text ?? '')
225
+ headers.push('Content-Type: text/plain; charset=utf-8')
226
+ headers.push(`Content-Transfer-Encoding: ${textPart.cte}`)
227
+ return Buffer.from(headers.join('\r\n') + '\r\n\r\n' + textPart.body, 'utf-8')
228
+ }
229
+
230
+ // ── Inbound MIME parsing ──────────────────────────────────────
231
+
232
+ export interface ParsedMail {
233
+ messageId?: string | null
234
+ inReplyTo?: string | null
235
+ references?: string | string[] | null
236
+ from?: { value?: Array<{ address?: string; name?: string }> }
237
+ to?: { value?: Array<{ address?: string; name?: string }> }
238
+ cc?: { value?: Array<{ address?: string; name?: string }> }
239
+ bcc?: { value?: Array<{ address?: string; name?: string }> }
240
+ subject?: string | null
241
+ html?: string | false
242
+ text?: string
243
+ date?: string | Date | null
244
+ attachments?: ParsedAttachment[]
245
+ headers?: Map<string, unknown> | Record<string, unknown>
246
+ }
247
+
248
+ export interface ParsedAttachment {
249
+ content?: Buffer | Uint8Array
250
+ contentType?: string
251
+ filename?: string
252
+ size?: number
253
+ contentDisposition?: string
254
+ cid?: string
255
+ }
256
+
257
+ export function stripBrackets(value: string | undefined | null): string | undefined {
258
+ if (!value) return undefined
259
+ const trimmed = value.trim()
260
+ if (!trimmed) return undefined
261
+ if (trimmed.startsWith('<') && trimmed.endsWith('>')) return trimmed.slice(1, -1)
262
+ return trimmed
263
+ }
264
+
265
+ export function parseReferences(value: string | string[] | undefined | null): string[] {
266
+ if (!value) return []
267
+ if (Array.isArray(value)) return value.map((v) => stripBrackets(v)).filter((v): v is string => Boolean(v))
268
+ return value
269
+ .split(/\s+/)
270
+ .map((segment) => stripBrackets(segment))
271
+ .filter((segment): segment is string => Boolean(segment))
272
+ }
273
+
274
+ export function normalizeAttachments(attachments: ParsedAttachment[]): NormalizedAttachment[] {
275
+ const out: NormalizedAttachment[] = []
276
+ let totalBytes = 0
277
+ for (const att of attachments) {
278
+ if (!att.content) continue
279
+ const byteLength = att.content.byteLength
280
+ if (byteLength > EMAIL_MAX_ATTACHMENT_BYTES) {
281
+ console.warn(
282
+ `[email-mime] dropping oversized inbound attachment "${att.filename ?? 'attachment'}" (${byteLength} bytes > ${EMAIL_MAX_ATTACHMENT_BYTES} cap)`,
283
+ )
284
+ continue
285
+ }
286
+ if (totalBytes + byteLength > TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES) {
287
+ console.warn(
288
+ `[email-mime] aggregate inbound attachment size exceeded ${TOTAL_INBOUND_ATTACHMENTS_MAX_BYTES} bytes; dropping remaining attachments`,
289
+ )
290
+ break
291
+ }
292
+ totalBytes += byteLength
293
+ const base64 = Buffer.isBuffer(att.content)
294
+ ? att.content.toString('base64')
295
+ : Buffer.from(att.content).toString('base64')
296
+ out.push({
297
+ url: `data:${att.contentType ?? 'application/octet-stream'};base64,${base64}`,
298
+ mimeType: att.contentType ?? 'application/octet-stream',
299
+ fileName: att.filename ?? 'attachment',
300
+ fileSize: att.size,
301
+ inline: Boolean(att.contentDisposition && /inline/i.test(att.contentDisposition)) || Boolean(att.cid),
302
+ })
303
+ }
304
+ return out
305
+ }
306
+
307
+ function stringifyHeaderValue(value: unknown): string | undefined {
308
+ if (typeof value === 'string') return value
309
+ if (Array.isArray(value)) return value.map((v) => String(v)).join(', ')
310
+ if (value instanceof Date) return value.toISOString()
311
+ if (value && typeof value === 'object') {
312
+ try {
313
+ return JSON.stringify(value)
314
+ } catch {
315
+ return undefined
316
+ }
317
+ }
318
+ if (value === undefined || value === null) return undefined
319
+ return String(value)
320
+ }
321
+
322
+ /**
323
+ * Flatten parsed MIME headers to a `Record<string, string>`.
324
+ *
325
+ * mailparser returns `headers` as a `Map<string, unknown>`. `Object.entries` on
326
+ * a Map yields an empty array (Maps have no enumerable own properties), so we
327
+ * iterate Map entries directly, with a Record fallback for test fakes.
328
+ */
329
+ export function extractHeaders(headers: ParsedMail['headers']): Record<string, string> {
330
+ if (!headers) return {}
331
+ const out: Record<string, string> = {}
332
+ if (headers instanceof Map) {
333
+ for (const [key, value] of headers.entries()) {
334
+ const stringified = stringifyHeaderValue(value)
335
+ if (stringified !== undefined) out[String(key)] = stringified
336
+ }
337
+ return out
338
+ }
339
+ for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
340
+ const stringified = stringifyHeaderValue(value)
341
+ if (stringified !== undefined) out[key] = stringified
342
+ }
343
+ return out
344
+ }
345
+
346
+ export interface NormalizeMimeInboundOptions {
347
+ /**
348
+ * The parsed MIME message. Providers parse with their own `mailparser`
349
+ * dependency (Gmail/IMAP) and pass the result here, so the hub stays free of a
350
+ * MIME-parser dependency while still owning the normalization logic.
351
+ */
352
+ parsed: ParsedMail
353
+ /** External identifier of the receiving channel (typically the account's email). */
354
+ accountIdentifier: string
355
+ /** Deterministic id used when the MIME message carries no `Message-ID` header. */
356
+ fallbackMessageId: string
357
+ /** Compute the conversation grouping id from the resolved message id + references. */
358
+ resolveConversationId: (context: { messageId: string; references: string[] }) => string
359
+ /** Fallback timestamp when the parsed message has no Date header. */
360
+ fallbackDate?: Date
361
+ /** Provider-specific fields merged into `channelMetadata`. */
362
+ channelMetadata?: (parsed: ParsedMail) => Record<string, unknown>
363
+ /** Provider-specific fields merged into `channelPayload`. */
364
+ channelPayload?: (parsed: ParsedMail) => Record<string, unknown>
365
+ }
366
+
367
+ /**
368
+ * Build the hub's canonical `NormalizedInboundMessage` from a parsed MIME
369
+ * message. Providers supply the bits that genuinely differ (message-id
370
+ * fallback, conversation grouping, extra metadata) and inherit the shared
371
+ * threading / attachment / header logic.
372
+ */
373
+ export function normalizeMimeInbound(options: NormalizeMimeInboundOptions): NormalizedInboundMessage {
374
+ const { parsed } = options
375
+
376
+ const messageId = stripBrackets(parsed.messageId) ?? options.fallbackMessageId
377
+ const inReplyTo = stripBrackets(parsed.inReplyTo)
378
+ const references = parseReferences(parsed.references)
379
+ const conversationId = options.resolveConversationId({ messageId, references })
380
+
381
+ const from = parsed.from?.value?.[0]
382
+ const subject = parsed.subject?.trim() || undefined
383
+ const bodyHtml = parsed.html && typeof parsed.html === 'string' ? parsed.html : undefined
384
+ const bodyText = typeof parsed.text === 'string' ? parsed.text : undefined
385
+ const body = bodyHtml ?? bodyText ?? ''
386
+ const bodyFormat: 'text' | 'html' = bodyHtml ? 'html' : 'text'
387
+
388
+ const attachments = normalizeAttachments(parsed.attachments ?? [])
389
+
390
+ const channelMetadata: Record<string, unknown> = {
391
+ ...(options.channelMetadata?.(parsed) ?? {}),
392
+ messageId,
393
+ inReplyTo: inReplyTo ?? null,
394
+ references,
395
+ headers: extractHeaders(parsed.headers),
396
+ }
397
+
398
+ const channelPayload: Record<string, unknown> = {
399
+ subject,
400
+ from: from ? { address: from.address, name: from.name } : null,
401
+ to: parsed.to?.value ?? [],
402
+ cc: parsed.cc?.value ?? [],
403
+ bcc: parsed.bcc?.value ?? [],
404
+ html: bodyHtml ?? null,
405
+ text: bodyText ?? null,
406
+ messageId,
407
+ ...(options.channelPayload?.(parsed) ?? {}),
408
+ }
409
+
410
+ return {
411
+ externalMessageId: messageId,
412
+ externalConversationId: conversationId,
413
+ senderIdentifier: from?.address ?? options.accountIdentifier,
414
+ senderDisplayName: from?.name?.trim() || undefined,
415
+ subject,
416
+ body,
417
+ bodyFormat,
418
+ attachments,
419
+ timestamp: parsed.date ? new Date(parsed.date) : options.fallbackDate ?? new Date(),
420
+ replyToExternalId: inReplyTo ?? undefined,
421
+ channelPayload,
422
+ channelContentType: 'email/mime',
423
+ channelMetadata,
424
+ }
425
+ }
426
+
427
+ // ── Provider sync cursor helpers ──────────────────────────────
428
+
429
+ /** Encode a provider channel-state object into an opaque base64 sync cursor. */
430
+ export function encodeCursor(state: unknown): string {
431
+ return Buffer.from(JSON.stringify(state)).toString('base64')
432
+ }
433
+
434
+ /** Decode a base64 sync cursor back into its object form, or `null` if malformed. */
435
+ export function decodeCursor(value: string | null | undefined): unknown {
436
+ if (!value) return null
437
+ try {
438
+ return JSON.parse(Buffer.from(value, 'base64').toString('utf-8'))
439
+ } catch {
440
+ return null
441
+ }
442
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Classify provider errors for outbound retry decisions.
3
+ *
4
+ * Transient errors (network, 429, 5xx, timeout) → retry with backoff.
5
+ * Permanent errors (4xx auth/validation/quota) → fail fast, no retry.
6
+ *
7
+ * Adapters can also throw classified errors directly by setting `transient: false`
8
+ * on their thrown Error instance. This helper falls back to heuristics when an
9
+ * adapter throws plain Error instances.
10
+ */
11
+
12
+ export type ErrorClassification = {
13
+ transient: boolean
14
+ /** HTTP status if known; helps with backoff decisions (e.g., `Retry-After`) */
15
+ status?: number
16
+ /** Human-readable summary suitable for logging. */
17
+ message: string
18
+ }
19
+
20
+ const TRANSIENT_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524])
21
+
22
+ /**
23
+ * Postgres SQLSTATEs that are transient (the operation can succeed on retry).
24
+ * These reach the inbound-ingest classifier when a DB blip happens mid-ingest;
25
+ * treating them as permanent would dead-letter the message AND advance the IMAP
26
+ * cursor, silently losing inbound mail (the exact "cursor drift" failure the
27
+ * email spec set out to prevent). Driver errors expose the SQLSTATE on `.code`.
28
+ */
29
+ const TRANSIENT_PG_SQLSTATES = new Set([
30
+ '40001', // serialization_failure
31
+ '40P01', // deadlock_detected
32
+ '55P03', // lock_not_available
33
+ '53300', // too_many_connections
34
+ '53400', // configuration_limit_exceeded
35
+ '57P01', // admin_shutdown
36
+ '57P02', // crash_shutdown
37
+ '57P03', // cannot_connect_now (db starting up)
38
+ '08000', // connection_exception
39
+ '08001', // sqlclient_unable_to_establish_sqlconnection
40
+ '08003', // connection_does_not_exist
41
+ '08006', // connection_failure
42
+ ])
43
+
44
+ const TRANSIENT_CODE_PATTERNS = [
45
+ /ECONNRESET/i,
46
+ /ETIMEDOUT/i,
47
+ /ECONNREFUSED/i,
48
+ /ENOTFOUND/i,
49
+ /EAI_AGAIN/i,
50
+ /socket hang up/i,
51
+ /network timeout/i,
52
+ /\btimed?\s*out\b/i,
53
+ /service unavailable/i,
54
+ /rate limit/i,
55
+ /too many requests/i,
56
+ /temporarily unavailable/i,
57
+ /try again later/i,
58
+ // imapflow surfaces these on TLS/socket-level drops that almost always
59
+ // recover on the next attempt. Treating them as permanent kills the
60
+ // channel after a single bad packet (regression observed during the demo:
61
+ // a network hiccup put a freshly-connected mailbox into a non-recoverable
62
+ // `error` state until the operator manually clicked "Retry").
63
+ /unexpected close/i,
64
+ /connection not available/i,
65
+ /connection closed/i,
66
+ /server closed connection/i,
67
+ // Postgres transient failures surfaced as text by the ORM wrapper (the
68
+ // SQLSTATE on `.code` is the primary signal; these catch wrapped errors that
69
+ // only carry the message). See TRANSIENT_PG_SQLSTATES.
70
+ /deadlock detected/i,
71
+ /could not serialize access/i,
72
+ /connection terminated/i,
73
+ /too many clients already/i,
74
+ /the database system is (starting up|shutting down|in recovery)/i,
75
+ ]
76
+
77
+ export function classifyOutboundError(error: unknown): ErrorClassification {
78
+ if (!error) {
79
+ return { transient: false, message: 'Unknown error' }
80
+ }
81
+
82
+ if (error instanceof Error) {
83
+ // Explicit hint from a classification-aware adapter.
84
+ const explicit = (error as Error & { transient?: boolean; status?: number }).transient
85
+ const status = (error as Error & { status?: number }).status
86
+ if (explicit !== undefined) {
87
+ return { transient: Boolean(explicit), status, message: error.message }
88
+ }
89
+ // Postgres driver errors expose the SQLSTATE on `.code`. A transient DB
90
+ // failure during inbound ingest MUST classify as transient so the caller
91
+ // aborts without advancing the cursor (no mail loss) rather than dead-lettering.
92
+ const code = (error as Error & { code?: string }).code
93
+ if (typeof code === 'string' && TRANSIENT_PG_SQLSTATES.has(code)) {
94
+ return { transient: true, status, message: error.message }
95
+ }
96
+ if (typeof status === 'number') {
97
+ return {
98
+ transient: TRANSIENT_STATUS_CODES.has(status),
99
+ status,
100
+ message: error.message,
101
+ }
102
+ }
103
+ const haystack = `${error.name} ${error.message}`
104
+ const matchesPattern = TRANSIENT_CODE_PATTERNS.some((pattern) => pattern.test(haystack))
105
+ return { transient: matchesPattern, message: error.message }
106
+ }
107
+
108
+ return { transient: false, message: String(error) }
109
+ }
110
+
111
+ /**
112
+ * Decide whether a classified provider error means the channel's stored
113
+ * credentials are no longer valid and the user must re-authorize.
114
+ *
115
+ * A 401, or an `invalid_grant` / `unauthorized` message, is unrecoverable by
116
+ * retry: the access token is rejected and (for OAuth) the refresh token is
117
+ * likely revoked too. Provider adapters may also surface the explicit
118
+ * `requires_reauth` sentinel instead of a status — e.g. Gmail
119
+ * `sendMessage` returns `{ status: 'failed', error: 'requires_reauth' }` on a
120
+ * 401, which the outbound command rethrows as a status-less `Error`. Match that
121
+ * sentinel too so the channel still flips to `requires_reauth`. Callers flip the
122
+ * channel so the operator gets a clear signal. Kept identical to the inbound
123
+ * poll path so inbound and outbound failures behave consistently.
124
+ */
125
+ export function isReauthError(classification: ErrorClassification): boolean {
126
+ return (
127
+ classification.status === 401 ||
128
+ /unauthorized|invalid_grant|requires_reauth/i.test(classification.message)
129
+ )
130
+ }
131
+
132
+ /**
133
+ * Compute exponential backoff delay (ms) for the given attempt number.
134
+ *
135
+ * Attempt 1 (= first retry) → 1s, attempt 2 → 2s, attempt 3 → 4s, ...
136
+ * Capped at 60s. Plus a small jitter so concurrent failures don't all retry
137
+ * simultaneously.
138
+ */
139
+ export function computeBackoffMs(attemptNumber: number): number {
140
+ const base = 1000 * Math.pow(2, Math.max(0, attemptNumber - 1))
141
+ const capped = Math.min(base, 60_000)
142
+ const jitter = Math.floor(Math.random() * 500)
143
+ return capped + jitter
144
+ }