@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.
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 +259 -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 +425 -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,278 @@
1
+ import crypto from 'node:crypto'
2
+
3
+ /**
4
+ * Spec C § Phase C2 — Verify a Gmail Pub/Sub push request.
5
+ *
6
+ * Pub/Sub push subscriptions authenticate with a Google-signed RS256 JWT
7
+ * passed in the `Authorization: Bearer …` header. The token's claims contain
8
+ * - `iss`: `https://accounts.google.com`
9
+ * - `aud`: the configured audience (typically the webhook URL)
10
+ * - `email`: the publishing service-account address (e.g.
11
+ * `gmail-api-push@system.gserviceaccount.com` for Gmail watch)
12
+ * - `email_verified: true`
13
+ *
14
+ * The default verifier downloads Google's public x509 certs and caches them
15
+ * for an hour. Tests inject a mock verifier via `setGmailPubSubVerifier(...)`.
16
+ */
17
+
18
+ const GOOGLE_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs'
19
+ const CERT_CACHE_TTL_MS = 60 * 60 * 1000
20
+ const CERT_FETCH_TIMEOUT_MS = 5000
21
+ // Google mints OIDC tokens with one of these two issuer strings.
22
+ const GOOGLE_ACCEPTED_ISSUERS = new Set(['https://accounts.google.com', 'accounts.google.com'])
23
+
24
+ export interface GmailPubSubJwtClaims {
25
+ iss: string
26
+ aud: string | string[]
27
+ email?: string
28
+ emailVerified?: boolean
29
+ exp: number
30
+ iat: number
31
+ sub?: string
32
+ }
33
+
34
+ export interface GmailPubSubVerifyInput {
35
+ authorizationHeader: string | null | undefined
36
+ expectedAudience: string
37
+ expectedEmail: string
38
+ }
39
+
40
+ export interface GmailPubSubVerifier {
41
+ verify(input: GmailPubSubVerifyInput): Promise<GmailPubSubJwtClaims>
42
+ }
43
+
44
+ export class GmailPubSubJwtError extends Error {
45
+ readonly code: 'missing_token' | 'invalid_format' | 'invalid_signature' | 'expired' | 'wrong_issuer' | 'wrong_audience' | 'wrong_email' | 'fetch_certs_failed'
46
+ constructor(message: string, code: GmailPubSubJwtError['code']) {
47
+ super(message)
48
+ this.name = 'GmailPubSubJwtError'
49
+ this.code = code
50
+ }
51
+ }
52
+
53
+ interface CertCacheEntry {
54
+ certs: Record<string, string>
55
+ fetchedAt: number
56
+ }
57
+
58
+ class FetchGmailPubSubVerifier implements GmailPubSubVerifier {
59
+ private certCache: CertCacheEntry | null = null
60
+
61
+ async verify(input: GmailPubSubVerifyInput): Promise<GmailPubSubJwtClaims> {
62
+ const token = extractBearer(input.authorizationHeader)
63
+ if (!token) throw new GmailPubSubJwtError('Missing Authorization bearer token', 'missing_token')
64
+
65
+ const parts = token.split('.')
66
+ if (parts.length !== 3) {
67
+ throw new GmailPubSubJwtError('JWT must have three dot-separated parts', 'invalid_format')
68
+ }
69
+ const [headerB64, payloadB64, signatureB64] = parts
70
+ let header: Record<string, unknown>
71
+ let claims: GmailPubSubJwtClaims
72
+ try {
73
+ header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf-8')) as Record<string, unknown>
74
+ claims = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8')) as GmailPubSubJwtClaims
75
+ } catch {
76
+ throw new GmailPubSubJwtError('JWT header/payload not parseable', 'invalid_format')
77
+ }
78
+ const kid = typeof header.kid === 'string' ? header.kid : null
79
+ const alg = typeof header.alg === 'string' ? header.alg : null
80
+ if (!kid || alg !== 'RS256') {
81
+ throw new GmailPubSubJwtError(`Unsupported JWT alg/kid: alg=${alg ?? '?'} kid=${kid ?? '?'}`, 'invalid_format')
82
+ }
83
+
84
+ const certs = await this.getCerts()
85
+ const cert = certs[kid]
86
+ if (!cert) {
87
+ // Cert rotated; refetch once and retry.
88
+ this.certCache = null
89
+ const refreshed = await this.getCerts()
90
+ const fresh = refreshed[kid]
91
+ if (!fresh) throw new GmailPubSubJwtError(`No cert for kid=${kid}`, 'invalid_signature')
92
+ verifySignature(`${headerB64}.${payloadB64}`, signatureB64, fresh)
93
+ } else {
94
+ verifySignature(`${headerB64}.${payloadB64}`, signatureB64, cert)
95
+ }
96
+
97
+ validateClaims(claims, input)
98
+ return claims
99
+ }
100
+
101
+ private async getCerts(): Promise<Record<string, string>> {
102
+ if (this.certCache && Date.now() - this.certCache.fetchedAt < CERT_CACHE_TTL_MS) {
103
+ return this.certCache.certs
104
+ }
105
+ let res: Response
106
+ const controller = new AbortController()
107
+ const timer = setTimeout(() => controller.abort(), CERT_FETCH_TIMEOUT_MS)
108
+ try {
109
+ res = await fetch(GOOGLE_CERTS_URL, { signal: controller.signal })
110
+ } catch (err) {
111
+ throw new GmailPubSubJwtError(
112
+ `Failed to fetch Google certs: ${err instanceof Error ? err.message : String(err)}`,
113
+ 'fetch_certs_failed',
114
+ )
115
+ } finally {
116
+ clearTimeout(timer)
117
+ }
118
+ if (!res.ok) {
119
+ throw new GmailPubSubJwtError(`Google certs endpoint returned ${res.status}`, 'fetch_certs_failed')
120
+ }
121
+ let parsed: unknown
122
+ try {
123
+ parsed = await res.json()
124
+ } catch (err) {
125
+ throw new GmailPubSubJwtError(
126
+ `Google certs endpoint returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
127
+ 'fetch_certs_failed',
128
+ )
129
+ }
130
+ const certs = toCertMap(parsed)
131
+ if (!certs) {
132
+ // Never cache an empty/malformed payload — a single bad 200 would otherwise
133
+ // disable Gmail push verification for the whole CERT_CACHE_TTL_MS window.
134
+ throw new GmailPubSubJwtError('Google certs endpoint returned an unexpected shape', 'fetch_certs_failed')
135
+ }
136
+ this.certCache = { certs, fetchedAt: Date.now() }
137
+ return certs
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Validates that a parsed Google certs response is a non-empty `kid → PEM` map.
143
+ * Returns the typed map on success, or `null` when the shape is unusable so the
144
+ * caller can fail closed instead of caching garbage.
145
+ */
146
+ function toCertMap(value: unknown): Record<string, string> | null {
147
+ if (value == null || typeof value !== 'object' || Array.isArray(value)) return null
148
+ const entries = Object.entries(value as Record<string, unknown>)
149
+ if (entries.length === 0) return null
150
+ const certs: Record<string, string> = {}
151
+ for (const [kid, pem] of entries) {
152
+ if (typeof pem !== 'string' || pem.length === 0) return null
153
+ certs[kid] = pem
154
+ }
155
+ return certs
156
+ }
157
+
158
+ function extractBearer(header: string | null | undefined): string | null {
159
+ if (!header) return null
160
+ const match = /^Bearer\s+(.+)$/i.exec(header.trim())
161
+ return match ? match[1].trim() : null
162
+ }
163
+
164
+ function verifySignature(input: string, signatureB64: string, cert: string): void {
165
+ const verifier = crypto.createVerify('RSA-SHA256')
166
+ verifier.update(input)
167
+ verifier.end()
168
+ const signature = Buffer.from(signatureB64, 'base64url')
169
+ const ok = verifier.verify(cert, signature)
170
+ if (!ok) {
171
+ throw new GmailPubSubJwtError('JWT signature verification failed', 'invalid_signature')
172
+ }
173
+ }
174
+
175
+ function validateClaims(claims: GmailPubSubJwtClaims, input: GmailPubSubVerifyInput): void {
176
+ const now = Math.floor(Date.now() / 1000)
177
+ // Fail closed: a token without a numeric `exp` has no expiry, so a captured
178
+ // push JWT could otherwise be replayed indefinitely. Require `exp` and reject
179
+ // anything already past it (with a small clock-skew allowance).
180
+ if (typeof claims.exp !== 'number' || claims.exp < now - 5) {
181
+ throw new GmailPubSubJwtError('JWT expired or missing exp', 'expired')
182
+ }
183
+ // Reject a future-dated `iat` beyond the clock-skew allowance: a token
184
+ // "issued" in the future signals a forged token or a badly-skewed clock.
185
+ // Reuses the `expired` code (both are temporal-validity failures → 401).
186
+ if (typeof claims.iat === 'number' && claims.iat > now + 5) {
187
+ throw new GmailPubSubJwtError('JWT issued in the future', 'expired')
188
+ }
189
+ // Verify the issuer is Google (defense-in-depth alongside the signature +
190
+ // service-account email checks; the file header documents this requirement).
191
+ if (typeof claims.iss !== 'string' || !GOOGLE_ACCEPTED_ISSUERS.has(claims.iss)) {
192
+ throw new GmailPubSubJwtError(
193
+ `JWT issuer not accepted (got ${typeof claims.iss === 'string' ? claims.iss : 'none'})`,
194
+ 'wrong_issuer',
195
+ )
196
+ }
197
+ const audOk = Array.isArray(claims.aud)
198
+ ? claims.aud.includes(input.expectedAudience)
199
+ : claims.aud === input.expectedAudience
200
+ if (!audOk) {
201
+ throw new GmailPubSubJwtError(
202
+ `JWT audience mismatch (expected ${input.expectedAudience})`,
203
+ 'wrong_audience',
204
+ )
205
+ }
206
+ // Google uses `email_verified` in the wire format; our type uses camelCase.
207
+ // Accept either to be defensive.
208
+ const emailVerified =
209
+ claims.emailVerified === true ||
210
+ (claims as unknown as { email_verified?: boolean }).email_verified === true
211
+ if (!emailVerified || claims.email !== input.expectedEmail) {
212
+ throw new GmailPubSubJwtError(
213
+ `JWT email mismatch (expected ${input.expectedEmail})`,
214
+ 'wrong_email',
215
+ )
216
+ }
217
+ }
218
+
219
+ let cachedVerifier: GmailPubSubVerifier | null = null
220
+
221
+ export function getGmailPubSubVerifier(): GmailPubSubVerifier {
222
+ if (!cachedVerifier) cachedVerifier = new FetchGmailPubSubVerifier()
223
+ return cachedVerifier
224
+ }
225
+
226
+ export function setGmailPubSubVerifier(verifier: GmailPubSubVerifier | null): void {
227
+ cachedVerifier = verifier
228
+ }
229
+
230
+ /**
231
+ * Decode a Pub/Sub envelope from the webhook body.
232
+ *
233
+ * Shape: `{ message: { data: base64<JSON>, messageId, publishTime, attributes }, subscription }`.
234
+ *
235
+ * Gmail's payload (`data` field) decodes to `{ emailAddress, historyId }`.
236
+ */
237
+ export interface PubSubEnvelope {
238
+ message: {
239
+ data: string
240
+ messageId: string
241
+ publishTime?: string
242
+ attributes?: Record<string, string>
243
+ }
244
+ subscription?: string
245
+ }
246
+
247
+ export interface GmailPushPayload {
248
+ emailAddress: string
249
+ historyId: string | number
250
+ }
251
+
252
+ export function decodeGmailPubSubBody(rawBody: string): GmailPushPayload {
253
+ let envelope: PubSubEnvelope
254
+ try {
255
+ envelope = JSON.parse(rawBody) as PubSubEnvelope
256
+ } catch {
257
+ throw new GmailPubSubJwtError('Body is not valid JSON', 'invalid_format')
258
+ }
259
+ if (!envelope.message?.data) {
260
+ throw new GmailPubSubJwtError('Envelope missing message.data', 'invalid_format')
261
+ }
262
+ let payloadText: string
263
+ try {
264
+ payloadText = Buffer.from(envelope.message.data, 'base64').toString('utf-8')
265
+ } catch {
266
+ throw new GmailPubSubJwtError('message.data not base64 decodable', 'invalid_format')
267
+ }
268
+ let payload: GmailPushPayload
269
+ try {
270
+ payload = JSON.parse(payloadText) as GmailPushPayload
271
+ } catch {
272
+ throw new GmailPubSubJwtError('message.data JSON not parseable', 'invalid_format')
273
+ }
274
+ if (!payload.emailAddress || payload.historyId === undefined) {
275
+ throw new GmailPubSubJwtError('message.data missing emailAddress or historyId', 'invalid_format')
276
+ }
277
+ return payload
278
+ }
@@ -0,0 +1,215 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import {
4
+ CommunicationChannel,
5
+ ExternalConversation,
6
+ ExternalMessage,
7
+ MessageChannelLink,
8
+ } from '../data/entities'
9
+
10
+ /**
11
+ * Hub-side mutation guards (Phase 4 of the email integration spec).
12
+ *
13
+ * These guards are plain functions that any caller (CRUD route, command,
14
+ * subscriber) can invoke to enforce hub invariants before mutating state.
15
+ * They throw `ChannelMutationBlockedError` on violations so the caller can
16
+ * map the error to an HTTP 4xx (typically 422) without leaking internals.
17
+ *
18
+ * Why functions, not the generic `validateCrudMutationGuard`:
19
+ * The hub's writes are channel-shaped rather than entity-shaped — "deleting a
20
+ * channel with unread inbound" is not a per-entity invariant the generic
21
+ * CRUD guard layer can express. These functions encode hub semantics directly
22
+ * and stay invocation-site agnostic.
23
+ */
24
+
25
+ export type ChannelMutationGuardReason =
26
+ | 'channel_has_inbound_history'
27
+ | 'channel_requires_reauth'
28
+ | 'channel_disconnected'
29
+ | 'channel_not_found'
30
+ /**
31
+ * @deprecated Use `channel_has_inbound_history`. Kept for one minor release
32
+ * so external callers/tests catch a transition window. Round-2 F8 rename.
33
+ */
34
+ | 'channel_has_unread_inbound'
35
+
36
+ export class ChannelMutationBlockedError extends Error {
37
+ readonly reason: ChannelMutationGuardReason
38
+ readonly channelId: string
39
+ /** Field-level error message keyed by `channelId` — for `createCrudFormError`. */
40
+ readonly errors: Record<string, string>
41
+
42
+ constructor(reason: ChannelMutationGuardReason, channelId: string, message: string) {
43
+ super(message)
44
+ this.name = 'ChannelMutationBlockedError'
45
+ this.reason = reason
46
+ this.channelId = channelId
47
+ this.errors = { channelId: message }
48
+ }
49
+ }
50
+
51
+ export interface ChannelScope {
52
+ tenantId: string
53
+ organizationId?: string | null
54
+ }
55
+
56
+ export interface GuardChannelDeleteInput {
57
+ channelId: string
58
+ scope: ChannelScope
59
+ /** Bypass the unread-inbound check; used by admin "force delete" actions. */
60
+ force?: boolean
61
+ }
62
+
63
+ /**
64
+ * Guard `channel.delete`.
65
+ *
66
+ * Blocks when the channel still has ANY inbound `MessageChannelLink` rows.
67
+ * Implementation note: the hub doesn't track per-message read state — that
68
+ * lives on the messages module's `MessageRecipient.read_at`. Counting unread
69
+ * across the module boundary would require either raw cross-module SQL
70
+ * (forbidden by AGENTS.md) or a QueryEngine round-trip per delete. We pick
71
+ * the simpler safety contract: block delete when ANY inbound link exists,
72
+ * with `force: true` as the escape hatch.
73
+ *
74
+ * Pass `force: true` to bypass — exposed to admins so they can hard-delete a
75
+ * channel whose mailbox they no longer care about (e.g. ex-employee
76
+ * offboarding).
77
+ */
78
+ export async function guardChannelDelete(
79
+ em: EntityManager,
80
+ input: GuardChannelDeleteInput,
81
+ ): Promise<void> {
82
+ const channel = await findOneWithDecryption(
83
+ em,
84
+ CommunicationChannel,
85
+ {
86
+ id: input.channelId,
87
+ tenantId: input.scope.tenantId,
88
+ organizationId: input.scope.organizationId ?? null,
89
+ deletedAt: null,
90
+ },
91
+ undefined,
92
+ input.scope,
93
+ )
94
+ if (!channel) {
95
+ throw new ChannelMutationBlockedError(
96
+ 'channel_not_found',
97
+ input.channelId,
98
+ 'Channel not found in this tenant scope.',
99
+ )
100
+ }
101
+ if (input.force) return
102
+
103
+ const inboundCount = await countInboundLinksForChannel(em, input.channelId, input.scope)
104
+ if (inboundCount > 0) {
105
+ throw new ChannelMutationBlockedError(
106
+ 'channel_has_inbound_history',
107
+ input.channelId,
108
+ `Channel has ${inboundCount} inbound message${inboundCount === 1 ? '' : 's'} in history. Pass force=true to delete anyway.`,
109
+ )
110
+ }
111
+ }
112
+
113
+ export interface GuardOutboundCreateInput {
114
+ channelId: string
115
+ scope: ChannelScope
116
+ }
117
+
118
+ /**
119
+ * Guard `message.create` for outbound sends.
120
+ *
121
+ * Blocks when the target channel is in `requires_reauth` or `disconnected` —
122
+ * outbound sends through a re-auth-needed channel will fail at the provider
123
+ * anyway, and blocking here gives a deterministic 422 with a field-level error
124
+ * instead of an opaque 500. The hub's `mark_channel_requires_reauth` command
125
+ * sets the status when refresh fails (slice 3b).
126
+ */
127
+ export async function guardOutboundCreate(
128
+ em: EntityManager,
129
+ input: GuardOutboundCreateInput,
130
+ ): Promise<void> {
131
+ const channel = await findOneWithDecryption(
132
+ em,
133
+ CommunicationChannel,
134
+ {
135
+ id: input.channelId,
136
+ tenantId: input.scope.tenantId,
137
+ organizationId: input.scope.organizationId ?? null,
138
+ deletedAt: null,
139
+ },
140
+ undefined,
141
+ input.scope,
142
+ )
143
+ if (!channel) {
144
+ throw new ChannelMutationBlockedError(
145
+ 'channel_not_found',
146
+ input.channelId,
147
+ 'Channel not found in this tenant scope.',
148
+ )
149
+ }
150
+ if (channel.status === 'requires_reauth') {
151
+ throw new ChannelMutationBlockedError(
152
+ 'channel_requires_reauth',
153
+ input.channelId,
154
+ 'This channel needs reconnection before it can send messages. Open Profile -> Communication channels to reconnect.',
155
+ )
156
+ }
157
+ if (channel.status === 'disconnected') {
158
+ throw new ChannelMutationBlockedError(
159
+ 'channel_disconnected',
160
+ input.channelId,
161
+ 'This channel is disconnected and cannot send messages.',
162
+ )
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Returns the count of inbound `MessageChannelLink` rows the bridge created for
168
+ * this channel. Used as the safety check before allowing a channel delete.
169
+ *
170
+ * Path: `external_conversations` (by channelId) → `message_channel_links` (by
171
+ * externalConversationId, direction='inbound'). Both tables are hub-owned, so
172
+ * we stay on the right side of the cross-module isolation rule. No raw SQL.
173
+ *
174
+ * Round-2 F8 rename (2026-05-26): was `countUnreadInboundForChannel`, but the
175
+ * function never counted "unread" — it always counted all inbound links. The
176
+ * old name is preserved as a `@deprecated` alias for one minor version.
177
+ */
178
+ export async function countInboundLinksForChannel(
179
+ em: EntityManager,
180
+ channelId: string,
181
+ scope: ChannelScope,
182
+ ): Promise<number> {
183
+ const conversations = await findWithDecryption(
184
+ em,
185
+ ExternalConversation,
186
+ {
187
+ channelId,
188
+ tenantId: scope.tenantId,
189
+ organizationId: scope.organizationId ?? null,
190
+ },
191
+ { fields: ['id'] },
192
+ scope,
193
+ )
194
+ if (conversations.length === 0) return 0
195
+ const conversationIds = (conversations as Array<{ id: string }>).map((c) => c.id)
196
+ const count = await em.count(
197
+ MessageChannelLink,
198
+ {
199
+ externalConversationId: { $in: conversationIds },
200
+ direction: 'inbound',
201
+ tenantId: scope.tenantId,
202
+ organizationId: scope.organizationId ?? null,
203
+ },
204
+ )
205
+ return typeof count === 'number' ? count : 0
206
+ }
207
+
208
+ /**
209
+ * @deprecated Use `countInboundLinksForChannel`. Kept for one minor release so
210
+ * external callers catch the rename window. Removed in the next minor.
211
+ */
212
+ export const countUnreadInboundForChannel = countInboundLinksForChannel
213
+
214
+ // Re-export entity types so callers can keep their typing tight.
215
+ export type { CommunicationChannel, ExternalMessage, MessageChannelLink }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Minimal shape of the integrations credentials service `resolve` we depend on.
3
+ * Declared locally so this helper compiles even when the integrations module is
4
+ * disabled in a downstream app.
5
+ */
6
+ type CredentialsResolver = {
7
+ resolve: (
8
+ integrationId: string,
9
+ scope: { tenantId: string; organizationId: string; userId?: string | null },
10
+ ) => Promise<Record<string, unknown> | null>
11
+ }
12
+
13
+ export type OAuthClientCredentialsScope = {
14
+ tenantId: string
15
+ organizationId: string | null
16
+ }
17
+
18
+ /**
19
+ * Resolve a provider's tenant-level OAuth *client application* credentials
20
+ * (clientId / clientSecret / scopes) configured by an admin under the
21
+ * `channel_<provider>` integration in the Integrations UI.
22
+ *
23
+ * Why `channel_<provider>` and NOT `oauth_<provider>`: each provider package
24
+ * registers its OAuth client-credential fields on the `channel_<provider>`
25
+ * integration — that is the row the admin edits and the health check reads.
26
+ * Earlier code resolved a phantom `oauth_<provider>` id that nothing ever
27
+ * writes, so every connect / code-exchange / refresh failed with
28
+ * "Invalid … OAuth client credentials: expected string, received undefined"
29
+ * even while the integration showed as configured and healthy.
30
+ *
31
+ * Scoping: the client app config is stored at TENANT scope (`userId = null`),
32
+ * distinct from the per-user OAuth *tokens* that live under the SAME
33
+ * `channel_<provider>` id at USER scope. We therefore always resolve at
34
+ * `userId: null`, trying the caller's organization first and then the
35
+ * organization-agnostic (`organizationId: null`) row, so a single platform /
36
+ * tenant OAuth app can serve every organization (and so a config saved while
37
+ * the admin had no active organization is still found).
38
+ *
39
+ * Returns `null` when no usable client row exists — callers MUST surface an
40
+ * actionable "provider not configured" error instead of handing an empty
41
+ * object to the adapter.
42
+ */
43
+ export async function resolveOAuthClientCredentials(
44
+ credentialsService: CredentialsResolver | null | undefined,
45
+ providerKey: string,
46
+ scope: OAuthClientCredentialsScope,
47
+ ): Promise<Record<string, unknown> | null> {
48
+ if (!credentialsService) return null
49
+ const integrationId = `channel_${providerKey}`
50
+ const organizations: Array<string | null> = [scope.organizationId, null]
51
+ const tried = new Set<string | null>()
52
+ for (const organizationId of organizations) {
53
+ if (tried.has(organizationId)) continue
54
+ tried.add(organizationId)
55
+ let row: Record<string, unknown> | null = null
56
+ try {
57
+ // `organizationId` may be `null` to match the organization-agnostic row;
58
+ // the credentials filter translates `null` into a SQL `IS NULL` match.
59
+ row = await credentialsService.resolve(integrationId, {
60
+ tenantId: scope.tenantId,
61
+ organizationId: organizationId as unknown as string,
62
+ userId: null,
63
+ })
64
+ } catch (resolveErr) {
65
+ // A resolve error (e.g. a transient DB issue) for this org scope shouldn't
66
+ // abort the lookup — fall through to the next scope — but surface it so a
67
+ // real misconfiguration isn't silently swallowed.
68
+ console.warn(
69
+ '[internal] [communication_channels] resolveOAuthClientCredentials: credential resolve failed for an org scope:',
70
+ resolveErr instanceof Error ? resolveErr.message : resolveErr,
71
+ )
72
+ row = null
73
+ }
74
+ if (row && typeof row.clientId === 'string' && row.clientId.length > 0) {
75
+ return row
76
+ }
77
+ }
78
+ return null
79
+ }