@open-mercato/core 0.6.8-develop.6983.1.e4f794099e → 0.6.8-develop.6985.1.fb93574faa

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 (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/notification/index.js +6 -0
  3. package/dist/generated/entities/notification/index.js.map +2 -2
  4. package/dist/generated/entities/notification_preference/index.js +19 -0
  5. package/dist/generated/entities/notification_preference/index.js.map +7 -0
  6. package/dist/generated/entities/notification_type/index.js +21 -0
  7. package/dist/generated/entities/notification_type/index.js.map +7 -0
  8. package/dist/generated/entities/notification_type_override/index.js +17 -0
  9. package/dist/generated/entities/notification_type_override/index.js.map +7 -0
  10. package/dist/generated/entities/push_notification_delivery/index.js +41 -0
  11. package/dist/generated/entities/push_notification_delivery/index.js.map +7 -0
  12. package/dist/generated/entities/user_device/index.js +35 -0
  13. package/dist/generated/entities/user_device/index.js.map +7 -0
  14. package/dist/generated/entities.ids.generated.js +12 -1
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +72 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/api.js +2 -1
  19. package/dist/helpers/integration/api.js.map +2 -2
  20. package/dist/helpers/integration/appRoot.js +9 -0
  21. package/dist/helpers/integration/appRoot.js.map +7 -0
  22. package/dist/helpers/integration/dbFixtures.js +1 -4
  23. package/dist/helpers/integration/dbFixtures.js.map +2 -2
  24. package/dist/helpers/integration/pushFake.js +126 -0
  25. package/dist/helpers/integration/pushFake.js.map +7 -0
  26. package/dist/modules/auth/notifications.js +16 -0
  27. package/dist/modules/auth/notifications.js.map +2 -2
  28. package/dist/modules/business_rules/notifications.js +2 -0
  29. package/dist/modules/business_rules/notifications.js.map +2 -2
  30. package/dist/modules/catalog/notifications.js +2 -0
  31. package/dist/modules/catalog/notifications.js.map +2 -2
  32. package/dist/modules/communication_channels/acl.js +9 -0
  33. package/dist/modules/communication_channels/acl.js.map +2 -2
  34. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +6 -2
  37. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +2 -2
  38. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +6 -2
  39. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +2 -2
  40. package/dist/modules/communication_channels/api/get/channels/route.js +2 -1
  41. package/dist/modules/communication_channels/api/get/channels/route.js.map +2 -2
  42. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +10 -5
  43. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  44. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +29 -1
  45. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  46. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +157 -0
  47. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +7 -0
  48. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +8 -1
  49. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
  50. package/dist/modules/communication_channels/commands/admin-delete-channel.js +141 -0
  51. package/dist/modules/communication_channels/commands/admin-delete-channel.js.map +7 -0
  52. package/dist/modules/communication_channels/commands/connect-credential-channel.js +25 -8
  53. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
  54. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +2 -2
  55. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  56. package/dist/modules/communication_channels/commands/push-register.js +1 -1
  57. package/dist/modules/communication_channels/commands/push-register.js.map +2 -2
  58. package/dist/modules/communication_channels/commands/push-unregister.js +4 -3
  59. package/dist/modules/communication_channels/commands/push-unregister.js.map +2 -2
  60. package/dist/modules/communication_channels/data/entities.js +4 -0
  61. package/dist/modules/communication_channels/data/entities.js.map +2 -2
  62. package/dist/modules/communication_channels/data/validators.js +2 -1
  63. package/dist/modules/communication_channels/data/validators.js.map +2 -2
  64. package/dist/modules/communication_channels/lib/access-control.js +11 -1
  65. package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
  66. package/dist/modules/communication_channels/lib/connect-channel.js +20 -12
  67. package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
  68. package/dist/modules/communication_channels/lib/push-adapter.js +68 -0
  69. package/dist/modules/communication_channels/lib/push-adapter.js.map +7 -0
  70. package/dist/modules/communication_channels/lib/push-capabilities.js +31 -0
  71. package/dist/modules/communication_channels/lib/push-capabilities.js.map +7 -0
  72. package/dist/modules/communication_channels/lib/push-connect-error.js +20 -0
  73. package/dist/modules/communication_channels/lib/push-connect-error.js.map +7 -0
  74. package/dist/modules/communication_channels/lib/push-credential-errors.js +41 -0
  75. package/dist/modules/communication_channels/lib/push-credential-errors.js.map +7 -0
  76. package/dist/modules/communication_channels/lib/push-envelope.js +46 -0
  77. package/dist/modules/communication_channels/lib/push-envelope.js.map +7 -0
  78. package/dist/modules/communication_channels/lib/refcounted-client-cache.js +64 -0
  79. package/dist/modules/communication_channels/lib/refcounted-client-cache.js.map +7 -0
  80. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js +13 -0
  81. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js.map +7 -0
  82. package/dist/modules/communication_channels/notifications.js +4 -0
  83. package/dist/modules/communication_channels/notifications.js.map +2 -2
  84. package/dist/modules/communication_channels/setup.js +2 -0
  85. package/dist/modules/communication_channels/setup.js.map +2 -2
  86. package/dist/modules/configs/lib/upgrade-actions.js +21 -0
  87. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  88. package/dist/modules/customer_accounts/notifications.js +12 -0
  89. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  90. package/dist/modules/customers/notifications.js +4 -0
  91. package/dist/modules/customers/notifications.js.map +2 -2
  92. package/dist/modules/devices/acl.js +11 -0
  93. package/dist/modules/devices/acl.js.map +7 -0
  94. package/dist/modules/devices/api/[id]/route.js +167 -0
  95. package/dist/modules/devices/api/[id]/route.js.map +7 -0
  96. package/dist/modules/devices/api/admin/devices/[id]/route.js +219 -0
  97. package/dist/modules/devices/api/admin/devices/[id]/route.js.map +7 -0
  98. package/dist/modules/devices/api/admin/devices/route.js +140 -0
  99. package/dist/modules/devices/api/admin/devices/route.js.map +7 -0
  100. package/dist/modules/devices/api/auth.js +9 -0
  101. package/dist/modules/devices/api/auth.js.map +7 -0
  102. package/dist/modules/devices/api/deviceList.js +53 -0
  103. package/dist/modules/devices/api/deviceList.js.map +7 -0
  104. package/dist/modules/devices/api/deviceOps.js +118 -0
  105. package/dist/modules/devices/api/deviceOps.js.map +7 -0
  106. package/dist/modules/devices/api/openapi.js +19 -0
  107. package/dist/modules/devices/api/openapi.js.map +7 -0
  108. package/dist/modules/devices/api/route.js +143 -0
  109. package/dist/modules/devices/api/route.js.map +7 -0
  110. package/dist/modules/devices/backend/devices/[id]/page.js +135 -0
  111. package/dist/modules/devices/backend/devices/[id]/page.js.map +7 -0
  112. package/dist/modules/devices/backend/devices/[id]/page.meta.js +16 -0
  113. package/dist/modules/devices/backend/devices/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/devices/backend/devices/create/page.js +68 -0
  115. package/dist/modules/devices/backend/devices/create/page.js.map +7 -0
  116. package/dist/modules/devices/backend/devices/create/page.meta.js +16 -0
  117. package/dist/modules/devices/backend/devices/create/page.meta.js.map +7 -0
  118. package/dist/modules/devices/backend/devices/page.js +235 -0
  119. package/dist/modules/devices/backend/devices/page.js.map +7 -0
  120. package/dist/modules/devices/backend/devices/page.meta.js +23 -0
  121. package/dist/modules/devices/backend/devices/page.meta.js.map +7 -0
  122. package/dist/modules/devices/commands/deactivate.js +107 -0
  123. package/dist/modules/devices/commands/deactivate.js.map +7 -0
  124. package/dist/modules/devices/commands/index.js +4 -0
  125. package/dist/modules/devices/commands/index.js.map +7 -0
  126. package/dist/modules/devices/commands/register.js +183 -0
  127. package/dist/modules/devices/commands/register.js.map +7 -0
  128. package/dist/modules/devices/commands/shared.js +74 -0
  129. package/dist/modules/devices/commands/shared.js.map +7 -0
  130. package/dist/modules/devices/commands/update.js +126 -0
  131. package/dist/modules/devices/commands/update.js.map +7 -0
  132. package/dist/modules/devices/data/entities.js +83 -0
  133. package/dist/modules/devices/data/entities.js.map +7 -0
  134. package/dist/modules/devices/data/validators.js +49 -0
  135. package/dist/modules/devices/data/validators.js.map +7 -0
  136. package/dist/modules/devices/di.js +11 -0
  137. package/dist/modules/devices/di.js.map +7 -0
  138. package/dist/modules/devices/encryption.js +12 -0
  139. package/dist/modules/devices/encryption.js.map +7 -0
  140. package/dist/modules/devices/events.js +19 -0
  141. package/dist/modules/devices/events.js.map +7 -0
  142. package/dist/modules/devices/index.js +17 -0
  143. package/dist/modules/devices/index.js.map +7 -0
  144. package/dist/modules/devices/lib/operationMetadata.js +20 -0
  145. package/dist/modules/devices/lib/operationMetadata.js.map +7 -0
  146. package/dist/modules/devices/migrations/Migration20260602120000.js +15 -0
  147. package/dist/modules/devices/migrations/Migration20260602120000.js.map +7 -0
  148. package/dist/modules/devices/migrations/Migration20260630120000.js +15 -0
  149. package/dist/modules/devices/migrations/Migration20260630120000.js.map +7 -0
  150. package/dist/modules/devices/migrations/Migration20260701120000.js +13 -0
  151. package/dist/modules/devices/migrations/Migration20260701120000.js.map +7 -0
  152. package/dist/modules/devices/migrations/Migration20260721120000.js +15 -0
  153. package/dist/modules/devices/migrations/Migration20260721120000.js.map +7 -0
  154. package/dist/modules/devices/setup.js +13 -0
  155. package/dist/modules/devices/setup.js.map +7 -0
  156. package/dist/modules/entities/cli.js +8 -3
  157. package/dist/modules/entities/cli.js.map +2 -2
  158. package/dist/modules/entities/migrations/Migration20260722120000.js +28 -0
  159. package/dist/modules/entities/migrations/Migration20260722120000.js.map +7 -0
  160. package/dist/modules/inbox_ops/notifications.js +2 -0
  161. package/dist/modules/inbox_ops/notifications.js.map +2 -2
  162. package/dist/modules/messages/notifications.js +2 -0
  163. package/dist/modules/messages/notifications.js.map +2 -2
  164. package/dist/modules/notifications/acl.js +3 -1
  165. package/dist/modules/notifications/acl.js.map +2 -2
  166. package/dist/modules/notifications/api/admin/preferences/route.js +167 -0
  167. package/dist/modules/notifications/api/admin/preferences/route.js.map +7 -0
  168. package/dist/modules/notifications/api/channels/route.js +54 -0
  169. package/dist/modules/notifications/api/channels/route.js.map +7 -0
  170. package/dist/modules/notifications/api/preferences/route.js +126 -0
  171. package/dist/modules/notifications/api/preferences/route.js.map +7 -0
  172. package/dist/modules/notifications/api/route.js +8 -1
  173. package/dist/modules/notifications/api/route.js.map +2 -2
  174. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js +187 -0
  175. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js.map +7 -0
  176. package/dist/modules/notifications/api/types/route.js +225 -0
  177. package/dist/modules/notifications/api/types/route.js.map +7 -0
  178. package/dist/modules/notifications/api/unread-count/route.js +5 -1
  179. package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
  180. package/dist/modules/notifications/backend/notifications/user-preferences/page.js +10 -0
  181. package/dist/modules/notifications/backend/notifications/user-preferences/page.js.map +7 -0
  182. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js +28 -0
  183. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js.map +7 -0
  184. package/dist/modules/notifications/backend/profile/notification-preferences/page.js +10 -0
  185. package/dist/modules/notifications/backend/profile/notification-preferences/page.js.map +7 -0
  186. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +33 -0
  187. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +7 -0
  188. package/dist/modules/notifications/data/entities.js +128 -1
  189. package/dist/modules/notifications/data/entities.js.map +2 -2
  190. package/dist/modules/notifications/data/validators.js +95 -2
  191. package/dist/modules/notifications/data/validators.js.map +2 -2
  192. package/dist/modules/notifications/di.js +7 -0
  193. package/dist/modules/notifications/di.js.map +2 -2
  194. package/dist/modules/notifications/events.js +17 -0
  195. package/dist/modules/notifications/events.js.map +2 -2
  196. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js +190 -0
  197. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js.map +7 -0
  198. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js +124 -0
  199. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js.map +7 -0
  200. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +166 -1
  201. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  202. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js +172 -0
  203. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js.map +7 -0
  204. package/dist/modules/notifications/generators.js +63 -0
  205. package/dist/modules/notifications/generators.js.map +7 -0
  206. package/dist/modules/notifications/lib/delivery-strategies-registry.js +19 -0
  207. package/dist/modules/notifications/lib/delivery-strategies-registry.js.map +7 -0
  208. package/dist/modules/notifications/lib/deliveryStrategies.js.map +2 -2
  209. package/dist/modules/notifications/lib/derive-category.js +8 -0
  210. package/dist/modules/notifications/lib/derive-category.js.map +7 -0
  211. package/dist/modules/notifications/lib/notification-channel-registry.js +35 -0
  212. package/dist/modules/notifications/lib/notification-channel-registry.js.map +7 -0
  213. package/dist/modules/notifications/lib/notification-type-registry.js +87 -0
  214. package/dist/modules/notifications/lib/notification-type-registry.js.map +7 -0
  215. package/dist/modules/notifications/lib/notificationCopy.js +14 -0
  216. package/dist/modules/notifications/lib/notificationCopy.js.map +7 -0
  217. package/dist/modules/notifications/lib/notificationFactory.js +4 -1
  218. package/dist/modules/notifications/lib/notificationFactory.js.map +2 -2
  219. package/dist/modules/notifications/lib/notificationMapper.js +2 -0
  220. package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
  221. package/dist/modules/notifications/lib/notificationPreferenceService.js +83 -0
  222. package/dist/modules/notifications/lib/notificationPreferenceService.js.map +7 -0
  223. package/dist/modules/notifications/lib/notificationService.js +83 -28
  224. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  225. package/dist/modules/notifications/lib/notificationVisibility.js +24 -0
  226. package/dist/modules/notifications/lib/notificationVisibility.js.map +7 -0
  227. package/dist/modules/notifications/lib/routeHelpers.js +11 -1
  228. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  229. package/dist/modules/notifications/lib/shouldDeliver.js +46 -0
  230. package/dist/modules/notifications/lib/shouldDeliver.js.map +7 -0
  231. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js +54 -0
  232. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js.map +7 -0
  233. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js +12 -0
  234. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js.map +7 -0
  235. package/dist/modules/notifications/lib/typeCatalogue.js +71 -0
  236. package/dist/modules/notifications/lib/typeCatalogue.js.map +7 -0
  237. package/dist/modules/notifications/lib/typeChannelSettings.js +12 -0
  238. package/dist/modules/notifications/lib/typeChannelSettings.js.map +7 -0
  239. package/dist/modules/notifications/lib/typeOverrides.js +19 -0
  240. package/dist/modules/notifications/lib/typeOverrides.js.map +7 -0
  241. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js +14 -0
  242. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js.map +7 -0
  243. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js +15 -0
  244. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js.map +7 -0
  245. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js +13 -0
  246. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js.map +7 -0
  247. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js +15 -0
  248. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js.map +7 -0
  249. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js +13 -0
  250. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js.map +7 -0
  251. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js +14 -0
  252. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js.map +7 -0
  253. package/dist/modules/notifications/notification-channels.js +26 -0
  254. package/dist/modules/notifications/notification-channels.js.map +7 -0
  255. package/dist/modules/notifications/notifications.delivery-strategies.js +12 -0
  256. package/dist/modules/notifications/notifications.delivery-strategies.js.map +7 -0
  257. package/dist/modules/notifications/setup.js +16 -1
  258. package/dist/modules/notifications/setup.js.map +2 -2
  259. package/dist/modules/notifications/subscribers/deliver-notification.js +66 -57
  260. package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
  261. package/dist/modules/notifications/subscribers/sync-notification-types.js +16 -0
  262. package/dist/modules/notifications/subscribers/sync-notification-types.js.map +7 -0
  263. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js +23 -0
  264. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js.map +7 -0
  265. package/dist/modules/notifications/widgets/injection-table.js +14 -0
  266. package/dist/modules/notifications/widgets/injection-table.js.map +7 -0
  267. package/dist/modules/push_notifications/acl.js +13 -0
  268. package/dist/modules/push_notifications/acl.js.map +7 -0
  269. package/dist/modules/push_notifications/api/custom-send/route.js +141 -0
  270. package/dist/modules/push_notifications/api/custom-send/route.js.map +7 -0
  271. package/dist/modules/push_notifications/api/deliveries/[id]/route.js +110 -0
  272. package/dist/modules/push_notifications/api/deliveries/[id]/route.js.map +7 -0
  273. package/dist/modules/push_notifications/api/deliveries/route.js +56 -0
  274. package/dist/modules/push_notifications/api/deliveries/route.js.map +7 -0
  275. package/dist/modules/push_notifications/api/openapi.js +19 -0
  276. package/dist/modules/push_notifications/api/openapi.js.map +7 -0
  277. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js +103 -0
  278. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js.map +7 -0
  279. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js +13 -0
  280. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js.map +7 -0
  281. package/dist/modules/push_notifications/backend/push_notifications/page.js +213 -0
  282. package/dist/modules/push_notifications/backend/push_notifications/page.js.map +7 -0
  283. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js +23 -0
  284. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js.map +7 -0
  285. package/dist/modules/push_notifications/backend/push_notifications/send/page.js +245 -0
  286. package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +7 -0
  287. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js +26 -0
  288. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js.map +7 -0
  289. package/dist/modules/push_notifications/data/entities.js +92 -0
  290. package/dist/modules/push_notifications/data/entities.js.map +7 -0
  291. package/dist/modules/push_notifications/data/extensions.js +23 -0
  292. package/dist/modules/push_notifications/data/extensions.js.map +7 -0
  293. package/dist/modules/push_notifications/data/validators.js +89 -0
  294. package/dist/modules/push_notifications/data/validators.js.map +7 -0
  295. package/dist/modules/push_notifications/di.js +17 -0
  296. package/dist/modules/push_notifications/di.js.map +7 -0
  297. package/dist/modules/push_notifications/events.js +24 -0
  298. package/dist/modules/push_notifications/events.js.map +7 -0
  299. package/dist/modules/push_notifications/index.js +16 -0
  300. package/dist/modules/push_notifications/index.js.map +7 -0
  301. package/dist/modules/push_notifications/lib/fake-provider-recorder.js +80 -0
  302. package/dist/modules/push_notifications/lib/fake-provider-recorder.js.map +7 -0
  303. package/dist/modules/push_notifications/lib/push-delivery-strategy.js +54 -0
  304. package/dist/modules/push_notifications/lib/push-delivery-strategy.js.map +7 -0
  305. package/dist/modules/push_notifications/lib/push-delivery.js +297 -0
  306. package/dist/modules/push_notifications/lib/push-delivery.js.map +7 -0
  307. package/dist/modules/push_notifications/lib/push-fanout.js +138 -0
  308. package/dist/modules/push_notifications/lib/push-fanout.js.map +7 -0
  309. package/dist/modules/push_notifications/lib/push-health.js +32 -0
  310. package/dist/modules/push_notifications/lib/push-health.js.map +7 -0
  311. package/dist/modules/push_notifications/lib/push-reaper.js +87 -0
  312. package/dist/modules/push_notifications/lib/push-reaper.js.map +7 -0
  313. package/dist/modules/push_notifications/lib/push-receipt-reaper.js +136 -0
  314. package/dist/modules/push_notifications/lib/push-receipt-reaper.js.map +7 -0
  315. package/dist/modules/push_notifications/lib/push-stub-adapter.js +63 -0
  316. package/dist/modules/push_notifications/lib/push-stub-adapter.js.map +7 -0
  317. package/dist/modules/push_notifications/lib/queue.js +53 -0
  318. package/dist/modules/push_notifications/lib/queue.js.map +7 -0
  319. package/dist/modules/push_notifications/lib/reclaim-window.js +13 -0
  320. package/dist/modules/push_notifications/lib/reclaim-window.js.map +7 -0
  321. package/dist/modules/push_notifications/lib/send-custom-push.js +48 -0
  322. package/dist/modules/push_notifications/lib/send-custom-push.js.map +7 -0
  323. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js +16 -0
  324. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js.map +7 -0
  325. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js +13 -0
  326. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js.map +7 -0
  327. package/dist/modules/push_notifications/notifications.delivery-strategies.js +8 -0
  328. package/dist/modules/push_notifications/notifications.delivery-strategies.js.map +7 -0
  329. package/dist/modules/push_notifications/notifications.js +40 -0
  330. package/dist/modules/push_notifications/notifications.js.map +7 -0
  331. package/dist/modules/push_notifications/setup.js +52 -0
  332. package/dist/modules/push_notifications/setup.js.map +7 -0
  333. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js +57 -0
  334. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js.map +7 -0
  335. package/dist/modules/push_notifications/workers/send-push.worker.js +27 -0
  336. package/dist/modules/push_notifications/workers/send-push.worker.js.map +7 -0
  337. package/dist/modules/sales/notifications.js +8 -0
  338. package/dist/modules/sales/notifications.js.map +2 -2
  339. package/dist/modules/staff/notifications.js +6 -0
  340. package/dist/modules/staff/notifications.js.map +2 -2
  341. package/dist/modules/workflows/notifications.js +2 -0
  342. package/dist/modules/workflows/notifications.js.map +2 -2
  343. package/generated/entities/notification/index.ts +3 -0
  344. package/generated/entities/notification_preference/index.ts +8 -0
  345. package/generated/entities/notification_type/index.ts +9 -0
  346. package/generated/entities/notification_type_override/index.ts +7 -0
  347. package/generated/entities/push_notification_delivery/index.ts +19 -0
  348. package/generated/entities/user_device/index.ts +16 -0
  349. package/generated/entities.ids.generated.ts +12 -1
  350. package/generated/entity-fields-registry.ts +72 -0
  351. package/package.json +7 -7
  352. package/src/helpers/integration/api.ts +2 -1
  353. package/src/helpers/integration/appRoot.ts +13 -0
  354. package/src/helpers/integration/dbFixtures.ts +1 -5
  355. package/src/helpers/integration/pushFake.ts +219 -0
  356. package/src/modules/auth/i18n/de.json +1 -0
  357. package/src/modules/auth/i18n/en.json +1 -0
  358. package/src/modules/auth/i18n/es.json +1 -0
  359. package/src/modules/auth/i18n/ko.json +1 -0
  360. package/src/modules/auth/i18n/pl.json +1 -0
  361. package/src/modules/auth/notifications.ts +16 -0
  362. package/src/modules/business_rules/i18n/de.json +2 -1
  363. package/src/modules/business_rules/i18n/en.json +2 -1
  364. package/src/modules/business_rules/i18n/es.json +2 -1
  365. package/src/modules/business_rules/i18n/ko.json +2 -1
  366. package/src/modules/business_rules/i18n/pl.json +2 -1
  367. package/src/modules/business_rules/notifications.ts +2 -0
  368. package/src/modules/catalog/i18n/de.json +2 -1
  369. package/src/modules/catalog/i18n/en.json +2 -1
  370. package/src/modules/catalog/i18n/es.json +2 -1
  371. package/src/modules/catalog/i18n/ko.json +2 -1
  372. package/src/modules/catalog/i18n/pl.json +2 -1
  373. package/src/modules/catalog/notifications.ts +2 -0
  374. package/src/modules/communication_channels/acl.ts +9 -0
  375. package/src/modules/communication_channels/api/delete/admin/channels/[id]/route.ts +168 -0
  376. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +6 -2
  377. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +6 -2
  378. package/src/modules/communication_channels/api/get/channels/route.ts +2 -1
  379. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +16 -5
  380. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +37 -1
  381. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +175 -0
  382. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +11 -1
  383. package/src/modules/communication_channels/commands/admin-delete-channel.ts +203 -0
  384. package/src/modules/communication_channels/commands/connect-credential-channel.ts +63 -15
  385. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +10 -2
  386. package/src/modules/communication_channels/commands/push-register.ts +7 -1
  387. package/src/modules/communication_channels/commands/push-unregister.ts +14 -3
  388. package/src/modules/communication_channels/data/entities.ts +10 -0
  389. package/src/modules/communication_channels/data/validators.ts +1 -0
  390. package/src/modules/communication_channels/i18n/de.json +35 -1
  391. package/src/modules/communication_channels/i18n/en.json +35 -1
  392. package/src/modules/communication_channels/i18n/es.json +35 -1
  393. package/src/modules/communication_channels/i18n/ko.json +35 -1
  394. package/src/modules/communication_channels/i18n/pl.json +35 -1
  395. package/src/modules/communication_channels/lib/access-control.ts +42 -0
  396. package/src/modules/communication_channels/lib/adapter.ts +25 -0
  397. package/src/modules/communication_channels/lib/connect-channel.ts +61 -26
  398. package/src/modules/communication_channels/lib/push-adapter.ts +139 -0
  399. package/src/modules/communication_channels/lib/push-capabilities.ts +42 -0
  400. package/src/modules/communication_channels/lib/push-connect-error.ts +52 -0
  401. package/src/modules/communication_channels/lib/push-credential-errors.ts +58 -0
  402. package/src/modules/communication_channels/lib/push-envelope.ts +104 -0
  403. package/src/modules/communication_channels/lib/refcounted-client-cache.ts +123 -0
  404. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +9 -0
  405. package/src/modules/communication_channels/migrations/Migration20260703123630_communication_channels.ts +20 -0
  406. package/src/modules/communication_channels/notifications.ts +4 -0
  407. package/src/modules/communication_channels/setup.ts +2 -0
  408. package/src/modules/configs/i18n/de.json +5 -1
  409. package/src/modules/configs/i18n/en.json +5 -1
  410. package/src/modules/configs/i18n/es.json +5 -1
  411. package/src/modules/configs/i18n/ko.json +5 -1
  412. package/src/modules/configs/i18n/pl.json +5 -1
  413. package/src/modules/configs/lib/upgrade-actions.ts +21 -0
  414. package/src/modules/customer_accounts/i18n/de.json +2 -1
  415. package/src/modules/customer_accounts/i18n/en.json +2 -1
  416. package/src/modules/customer_accounts/i18n/es.json +2 -1
  417. package/src/modules/customer_accounts/i18n/ko.json +2 -1
  418. package/src/modules/customer_accounts/i18n/pl.json +2 -1
  419. package/src/modules/customer_accounts/notifications.ts +12 -0
  420. package/src/modules/customers/i18n/de.json +2 -1
  421. package/src/modules/customers/i18n/en.json +2 -1
  422. package/src/modules/customers/i18n/es.json +2 -1
  423. package/src/modules/customers/i18n/ko.json +2 -1
  424. package/src/modules/customers/i18n/pl.json +2 -1
  425. package/src/modules/customers/notifications.ts +4 -0
  426. package/src/modules/devices/AGENTS.md +101 -0
  427. package/src/modules/devices/acl.ts +7 -0
  428. package/src/modules/devices/api/[id]/route.ts +180 -0
  429. package/src/modules/devices/api/admin/devices/[id]/route.ts +235 -0
  430. package/src/modules/devices/api/admin/devices/route.ts +160 -0
  431. package/src/modules/devices/api/auth.ts +13 -0
  432. package/src/modules/devices/api/deviceList.ts +54 -0
  433. package/src/modules/devices/api/deviceOps.ts +186 -0
  434. package/src/modules/devices/api/openapi.ts +23 -0
  435. package/src/modules/devices/api/route.ts +165 -0
  436. package/src/modules/devices/backend/devices/[id]/page.meta.ts +12 -0
  437. package/src/modules/devices/backend/devices/[id]/page.tsx +164 -0
  438. package/src/modules/devices/backend/devices/create/page.meta.ts +12 -0
  439. package/src/modules/devices/backend/devices/create/page.tsx +81 -0
  440. package/src/modules/devices/backend/devices/page.meta.ts +21 -0
  441. package/src/modules/devices/backend/devices/page.tsx +259 -0
  442. package/src/modules/devices/commands/deactivate.ts +126 -0
  443. package/src/modules/devices/commands/index.ts +3 -0
  444. package/src/modules/devices/commands/register.ts +199 -0
  445. package/src/modules/devices/commands/shared.ts +119 -0
  446. package/src/modules/devices/commands/update.ts +142 -0
  447. package/src/modules/devices/data/entities.ts +79 -0
  448. package/src/modules/devices/data/validators.ts +65 -0
  449. package/src/modules/devices/di.ts +9 -0
  450. package/src/modules/devices/encryption.ts +15 -0
  451. package/src/modules/devices/events.ts +16 -0
  452. package/src/modules/devices/i18n/de.json +45 -0
  453. package/src/modules/devices/i18n/en.json +45 -0
  454. package/src/modules/devices/i18n/es.json +45 -0
  455. package/src/modules/devices/i18n/ko.json +45 -0
  456. package/src/modules/devices/i18n/pl.json +45 -0
  457. package/src/modules/devices/index.ts +15 -0
  458. package/src/modules/devices/lib/operationMetadata.ts +35 -0
  459. package/src/modules/devices/migrations/.snapshot-open-mercato.json +200 -0
  460. package/src/modules/devices/migrations/Migration20260602120000.ts +15 -0
  461. package/src/modules/devices/migrations/Migration20260630120000.ts +20 -0
  462. package/src/modules/devices/migrations/Migration20260701120000.ts +13 -0
  463. package/src/modules/devices/migrations/Migration20260721120000.ts +18 -0
  464. package/src/modules/devices/setup.ts +11 -0
  465. package/src/modules/entities/cli.ts +16 -2
  466. package/src/modules/entities/migrations/Migration20260722120000.ts +38 -0
  467. package/src/modules/inbox_ops/i18n/de.json +2 -1
  468. package/src/modules/inbox_ops/i18n/en.json +2 -1
  469. package/src/modules/inbox_ops/i18n/es.json +2 -1
  470. package/src/modules/inbox_ops/i18n/ko.json +2 -1
  471. package/src/modules/inbox_ops/i18n/pl.json +2 -1
  472. package/src/modules/inbox_ops/notifications.ts +2 -0
  473. package/src/modules/messages/i18n/de.json +2 -1
  474. package/src/modules/messages/i18n/en.json +2 -1
  475. package/src/modules/messages/i18n/es.json +2 -1
  476. package/src/modules/messages/i18n/ko.json +2 -1
  477. package/src/modules/messages/i18n/pl.json +2 -1
  478. package/src/modules/messages/notifications.ts +2 -0
  479. package/src/modules/notifications/AGENTS.md +54 -0
  480. package/src/modules/notifications/acl.ts +2 -0
  481. package/src/modules/notifications/api/admin/preferences/route.ts +193 -0
  482. package/src/modules/notifications/api/channels/route.ts +54 -0
  483. package/src/modules/notifications/api/preferences/route.ts +131 -0
  484. package/src/modules/notifications/api/route.ts +8 -1
  485. package/src/modules/notifications/api/types/[id]/channels/[channel]/route.ts +226 -0
  486. package/src/modules/notifications/api/types/route.ts +239 -0
  487. package/src/modules/notifications/api/unread-count/route.ts +5 -1
  488. package/src/modules/notifications/backend/notifications/user-preferences/page.meta.ts +26 -0
  489. package/src/modules/notifications/backend/notifications/user-preferences/page.tsx +12 -0
  490. package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +31 -0
  491. package/src/modules/notifications/backend/profile/notification-preferences/page.tsx +12 -0
  492. package/src/modules/notifications/data/entities.ts +144 -0
  493. package/src/modules/notifications/data/validators.ts +116 -0
  494. package/src/modules/notifications/di.ts +7 -0
  495. package/src/modules/notifications/events.ts +23 -0
  496. package/src/modules/notifications/frontend/NotificationPreferenceMatrix.tsx +287 -0
  497. package/src/modules/notifications/frontend/NotificationPreferencesPageClient.tsx +155 -0
  498. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +273 -2
  499. package/src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx +214 -0
  500. package/src/modules/notifications/generators.ts +82 -0
  501. package/src/modules/notifications/i18n/de.json +43 -0
  502. package/src/modules/notifications/i18n/en.json +43 -0
  503. package/src/modules/notifications/i18n/es.json +43 -0
  504. package/src/modules/notifications/i18n/ko.json +43 -0
  505. package/src/modules/notifications/i18n/pl.json +44 -1
  506. package/src/modules/notifications/lib/delivery-strategies-registry.ts +30 -0
  507. package/src/modules/notifications/lib/deliveryStrategies.ts +40 -4
  508. package/src/modules/notifications/lib/derive-category.ts +15 -0
  509. package/src/modules/notifications/lib/notification-channel-registry.ts +59 -0
  510. package/src/modules/notifications/lib/notification-type-registry.ts +182 -0
  511. package/src/modules/notifications/lib/notificationCopy.ts +34 -0
  512. package/src/modules/notifications/lib/notificationFactory.ts +5 -1
  513. package/src/modules/notifications/lib/notificationMapper.ts +2 -0
  514. package/src/modules/notifications/lib/notificationPreferenceService.ts +138 -0
  515. package/src/modules/notifications/lib/notificationService.ts +129 -25
  516. package/src/modules/notifications/lib/notificationVisibility.ts +48 -0
  517. package/src/modules/notifications/lib/routeHelpers.ts +14 -1
  518. package/src/modules/notifications/lib/shouldDeliver.ts +112 -0
  519. package/src/modules/notifications/lib/strategies/email-delivery-strategy.ts +63 -0
  520. package/src/modules/notifications/lib/strategies/in-app-delivery-strategy.ts +21 -0
  521. package/src/modules/notifications/lib/typeCatalogue.ts +141 -0
  522. package/src/modules/notifications/lib/typeChannelSettings.ts +29 -0
  523. package/src/modules/notifications/lib/typeOverrides.ts +46 -0
  524. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +874 -100
  525. package/src/modules/notifications/migrations/Migration20260625122947_notifications.ts +14 -0
  526. package/src/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.ts +15 -0
  527. package/src/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.ts +13 -0
  528. package/src/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.ts +15 -0
  529. package/src/modules/notifications/migrations/Migration20260706120000_notifications_channels.ts +13 -0
  530. package/src/modules/notifications/migrations/Migration20260716123121_notifications.ts +14 -0
  531. package/src/modules/notifications/notification-channels.ts +30 -0
  532. package/src/modules/notifications/notifications.delivery-strategies.ts +15 -0
  533. package/src/modules/notifications/setup.ts +23 -1
  534. package/src/modules/notifications/subscribers/deliver-notification.ts +105 -71
  535. package/src/modules/notifications/subscribers/sync-notification-types.ts +18 -0
  536. package/src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts +35 -0
  537. package/src/modules/notifications/widgets/injection-table.ts +18 -0
  538. package/src/modules/push_notifications/AGENTS.md +92 -0
  539. package/src/modules/push_notifications/acl.ts +9 -0
  540. package/src/modules/push_notifications/api/custom-send/route.ts +175 -0
  541. package/src/modules/push_notifications/api/deliveries/[id]/route.ts +122 -0
  542. package/src/modules/push_notifications/api/deliveries/route.ts +58 -0
  543. package/src/modules/push_notifications/api/openapi.ts +22 -0
  544. package/src/modules/push_notifications/backend/push_notifications/[id]/page.meta.ts +9 -0
  545. package/src/modules/push_notifications/backend/push_notifications/[id]/page.tsx +149 -0
  546. package/src/modules/push_notifications/backend/push_notifications/page.meta.ts +21 -0
  547. package/src/modules/push_notifications/backend/push_notifications/page.tsx +239 -0
  548. package/src/modules/push_notifications/backend/push_notifications/send/page.meta.ts +24 -0
  549. package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +327 -0
  550. package/src/modules/push_notifications/data/entities.ts +111 -0
  551. package/src/modules/push_notifications/data/extensions.ts +29 -0
  552. package/src/modules/push_notifications/data/validators.ts +102 -0
  553. package/src/modules/push_notifications/di.ts +20 -0
  554. package/src/modules/push_notifications/events.ts +21 -0
  555. package/src/modules/push_notifications/i18n/de.json +79 -0
  556. package/src/modules/push_notifications/i18n/en.json +79 -0
  557. package/src/modules/push_notifications/i18n/es.json +79 -0
  558. package/src/modules/push_notifications/i18n/ko.json +79 -0
  559. package/src/modules/push_notifications/i18n/pl.json +79 -0
  560. package/src/modules/push_notifications/index.ts +15 -0
  561. package/src/modules/push_notifications/lib/fake-provider-recorder.ts +154 -0
  562. package/src/modules/push_notifications/lib/push-delivery-strategy.ts +90 -0
  563. package/src/modules/push_notifications/lib/push-delivery.ts +455 -0
  564. package/src/modules/push_notifications/lib/push-fanout.ts +237 -0
  565. package/src/modules/push_notifications/lib/push-health.ts +47 -0
  566. package/src/modules/push_notifications/lib/push-reaper.ts +140 -0
  567. package/src/modules/push_notifications/lib/push-receipt-reaper.ts +210 -0
  568. package/src/modules/push_notifications/lib/push-stub-adapter.ts +80 -0
  569. package/src/modules/push_notifications/lib/queue.ts +74 -0
  570. package/src/modules/push_notifications/lib/reclaim-window.ts +16 -0
  571. package/src/modules/push_notifications/lib/send-custom-push.ts +100 -0
  572. package/src/modules/push_notifications/migrations/.snapshot-open-mercato.json +386 -0
  573. package/src/modules/push_notifications/migrations/Migration20260625150049_push_notifications.ts +16 -0
  574. package/src/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.ts +13 -0
  575. package/src/modules/push_notifications/notifications.delivery-strategies.ts +8 -0
  576. package/src/modules/push_notifications/notifications.ts +50 -0
  577. package/src/modules/push_notifications/setup.ts +92 -0
  578. package/src/modules/push_notifications/workers/reclaim-stuck.worker.ts +78 -0
  579. package/src/modules/push_notifications/workers/send-push.worker.ts +34 -0
  580. package/src/modules/sales/i18n/de.json +1 -0
  581. package/src/modules/sales/i18n/en.json +1 -0
  582. package/src/modules/sales/i18n/es.json +1 -0
  583. package/src/modules/sales/i18n/ko.json +1 -0
  584. package/src/modules/sales/i18n/pl.json +1 -0
  585. package/src/modules/sales/notifications.ts +8 -0
  586. package/src/modules/staff/i18n/de.json +1 -0
  587. package/src/modules/staff/i18n/en.json +1 -0
  588. package/src/modules/staff/i18n/es.json +1 -0
  589. package/src/modules/staff/i18n/ko.json +1 -0
  590. package/src/modules/staff/i18n/pl.json +1 -0
  591. package/src/modules/staff/notifications.ts +6 -0
  592. package/src/modules/workflows/i18n/de.json +1 -0
  593. package/src/modules/workflows/i18n/en.json +1 -0
  594. package/src/modules/workflows/i18n/es.json +1 -0
  595. package/src/modules/workflows/i18n/ko.json +1 -0
  596. package/src/modules/workflows/i18n/pl.json +1 -0
  597. package/src/modules/workflows/notifications.ts +2 -0
@@ -125,6 +125,39 @@
125
125
  "communication_channels.profile.tableTitle": "내 채널",
126
126
  "communication_channels.profile.title": "내 통신 채널",
127
127
  "communication_channels.push.button.reregister": "푸시 다시 등록",
128
+ "communication_channels.push.connect.blocked": "유효성 검사로 인해 연결이 차단되었습니다",
129
+ "communication_channels.push.connect.button.apns": "APNs 연결",
130
+ "communication_channels.push.connect.button.expo": "Expo 연결",
131
+ "communication_channels.push.connect.button.fcm": "FCM 연결",
132
+ "communication_channels.push.connect.cancel": "취소",
133
+ "communication_channels.push.connect.connected": "푸시 제공자가 연결되었습니다.",
134
+ "communication_channels.push.connect.connecting": "연결하는 중…",
135
+ "communication_channels.push.connect.description": "이 채널은 테넌트 전체가 공유합니다 — 이 워크스페이스에 속한 모든 사용자의 기기가 이 채널을 통해 푸시를 받습니다.",
136
+ "communication_channels.push.connect.displayName": "표시 이름",
137
+ "communication_channels.push.connect.errors.fields.invalid": "값이 올바르지 않습니다.",
138
+ "communication_channels.push.connect.errors.fields.invalid_bundle_id": "번들 ID는 com.example.app 형식입니다.",
139
+ "communication_channels.push.connect.errors.fields.invalid_json": "유효한 JSON이 아닙니다. Firebase에서 생성한 서비스 계정 파일을 그대로 붙여넣으세요.",
140
+ "communication_channels.push.connect.errors.fields.invalid_key_id": "APNs 키 ID는 영숫자 10자입니다.",
141
+ "communication_channels.push.connect.errors.fields.invalid_p8": "읽을 수 있는 APNs .p8 서명 키가 아닙니다. BEGIN과 END 줄을 포함해 파일 전체를 붙여넣으세요.",
142
+ "communication_channels.push.connect.errors.fields.invalid_team_id": "Apple 팀 ID는 영숫자 10자입니다.",
143
+ "communication_channels.push.connect.errors.fields.missing_fields": "서비스 계정 JSON에 필수 항목(project_id, client_email, private_key)이 없습니다.",
144
+ "communication_channels.push.connect.errors.fields.required": "필수 입력 항목입니다.",
145
+ "communication_channels.push.connect.errors.mailbox_already_connected": "이 채널은 이미 다른 제공자를 통해 연결되어 있습니다. 다시 연결하기 전에 해당 제공자에서 연결을 해제하세요.",
146
+ "communication_channels.push.connect.errors.provider_not_tenant_scoped": "이 제공자는 사용자별 채널을 연결합니다. 개인 채널 연결 절차를 이용하세요.",
147
+ "communication_channels.push.connect.errors.wrong_scope_for_route": "예상치 못한 채널 범위입니다. 다시 시도하거나 지원팀에 문의하세요.",
148
+ "communication_channels.push.connect.failed": "푸시 제공자를 연결하지 못했습니다.",
149
+ "communication_channels.push.connect.fields.apns.bundleId": "번들 ID",
150
+ "communication_channels.push.connect.fields.apns.keyId": "키 ID",
151
+ "communication_channels.push.connect.fields.apns.p8Key": "APNs .p8 키",
152
+ "communication_channels.push.connect.fields.apns.production": "프로덕션 환경",
153
+ "communication_channels.push.connect.fields.apns.teamId": "팀 ID",
154
+ "communication_channels.push.connect.fields.expo.accessToken": "액세스 토큰 (선택 사항)",
155
+ "communication_channels.push.connect.fields.fcm.appName": "앱 이름 (선택 사항)",
156
+ "communication_channels.push.connect.fields.fcm.serviceAccountJson": "서비스 계정 JSON",
157
+ "communication_channels.push.connect.save": "연결",
158
+ "communication_channels.push.connect.title.apns": "Apple 푸시 알림 서비스 연결",
159
+ "communication_channels.push.connect.title.expo": "Expo 푸시 연결",
160
+ "communication_channels.push.connect.title.fcm": "Firebase 클라우드 메시징 연결",
128
161
  "communication_channels.push.flash.registerFailed": "푸시 등록에 실패했습니다",
129
162
  "communication_channels.push.status.active": "푸시 활성",
130
163
  "communication_channels.push.status.failed": "푸시 실패 — 폴링 사용 중",
@@ -140,5 +173,6 @@
140
173
  "communication_channels.status.disconnected": "연결 끊김",
141
174
  "communication_channels.status.error": "오류",
142
175
  "communication_channels.status.inactive": "비활성",
143
- "communication_channels.status.requiresReauth": "재연결 필요"
176
+ "communication_channels.status.requiresReauth": "재연결 필요",
177
+ "notifications.categories.communication_channels": "커뮤니케이션"
144
178
  }
@@ -125,6 +125,39 @@
125
125
  "communication_channels.profile.tableTitle": "Twoje kanały",
126
126
  "communication_channels.profile.title": "Moje kanały komunikacji",
127
127
  "communication_channels.push.button.reregister": "Zarejestruj push ponownie",
128
+ "communication_channels.push.connect.blocked": "Połączenie zablokowane przez walidację",
129
+ "communication_channels.push.connect.button.apns": "Połącz APNs",
130
+ "communication_channels.push.connect.button.expo": "Połącz Expo",
131
+ "communication_channels.push.connect.button.fcm": "Połącz FCM",
132
+ "communication_channels.push.connect.cancel": "Anuluj",
133
+ "communication_channels.push.connect.connected": "Dostawca push połączony.",
134
+ "communication_channels.push.connect.connecting": "Łączenie…",
135
+ "communication_channels.push.connect.description": "To współdzielony kanał dla całego tenanta — obsługuje urządzenia wszystkich użytkowników w tej przestrzeni.",
136
+ "communication_channels.push.connect.displayName": "Nazwa wyświetlana",
137
+ "communication_channels.push.connect.errors.fields.invalid": "Ta wartość jest nieprawidłowa.",
138
+ "communication_channels.push.connect.errors.fields.invalid_bundle_id": "Identyfikator pakietu wygląda tak: com.example.app.",
139
+ "communication_channels.push.connect.errors.fields.invalid_json": "To nie jest prawidłowy JSON. Wklej plik konta serwisowego dokładnie tak, jak wygenerował go Firebase.",
140
+ "communication_channels.push.connect.errors.fields.invalid_key_id": "Identyfikator klucza APNs składa się z 10 znaków alfanumerycznych.",
141
+ "communication_channels.push.connect.errors.fields.invalid_p8": "To nie jest czytelny klucz podpisujący APNs .p8. Wklej cały plik razem z liniami BEGIN i END.",
142
+ "communication_channels.push.connect.errors.fields.invalid_team_id": "Identyfikator zespołu Apple składa się z 10 znaków alfanumerycznych.",
143
+ "communication_channels.push.connect.errors.fields.missing_fields": "W pliku JSON konta serwisowego brakuje wymaganych pól (project_id, client_email, private_key).",
144
+ "communication_channels.push.connect.errors.fields.required": "To pole jest wymagane.",
145
+ "communication_channels.push.connect.errors.mailbox_already_connected": "Ten kanał jest już połączony przez innego dostawcę. Rozłącz go tam przed ponownym połączeniem.",
146
+ "communication_channels.push.connect.errors.provider_not_tenant_scoped": "Ten dostawca łączy kanały indywidualne; użyj zamiast tego procesu łączenia kanału osobistego.",
147
+ "communication_channels.push.connect.errors.wrong_scope_for_route": "Nieoczekiwany zakres kanału. Spróbuj ponownie lub skontaktuj się z pomocą techniczną.",
148
+ "communication_channels.push.connect.failed": "Nie udało się połączyć dostawcy push.",
149
+ "communication_channels.push.connect.fields.apns.bundleId": "Bundle ID",
150
+ "communication_channels.push.connect.fields.apns.keyId": "Key ID",
151
+ "communication_channels.push.connect.fields.apns.p8Key": "Klucz .p8 APNs",
152
+ "communication_channels.push.connect.fields.apns.production": "Środowisko produkcyjne",
153
+ "communication_channels.push.connect.fields.apns.teamId": "Team ID",
154
+ "communication_channels.push.connect.fields.expo.accessToken": "Token dostępu (opcjonalnie)",
155
+ "communication_channels.push.connect.fields.fcm.appName": "Nazwa aplikacji (opcjonalnie)",
156
+ "communication_channels.push.connect.fields.fcm.serviceAccountJson": "JSON konta usługi",
157
+ "communication_channels.push.connect.save": "Połącz",
158
+ "communication_channels.push.connect.title.apns": "Połącz Apple Push Notification service",
159
+ "communication_channels.push.connect.title.expo": "Połącz Expo Push",
160
+ "communication_channels.push.connect.title.fcm": "Połącz Firebase Cloud Messaging",
128
161
  "communication_channels.push.flash.registerFailed": "Nie udało się zarejestrować push",
129
162
  "communication_channels.push.status.active": "Push aktywny",
130
163
  "communication_channels.push.status.failed": "Push nieudany — używam odpytywania",
@@ -140,5 +173,6 @@
140
173
  "communication_channels.status.disconnected": "Rozłączony",
141
174
  "communication_channels.status.error": "Błąd",
142
175
  "communication_channels.status.inactive": "Nieaktywny",
143
- "communication_channels.status.requiresReauth": "Wymaga ponownego połączenia"
176
+ "communication_channels.status.requiresReauth": "Wymaga ponownego połączenia",
177
+ "notifications.categories.communication_channels": "Komunikacja"
144
178
  }
@@ -17,6 +17,48 @@ import { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'
17
17
 
18
18
  export const ADMIN_FEATURE = 'communication_channels.admin'
19
19
 
20
+ /**
21
+ * Org-scoping fragment for reading/managing channels.
22
+ *
23
+ * Tenant-wide channels are stored with `organization_id IS NULL` (the
24
+ * tenant-scoped push providers FCM/APNs/Expo do this deliberately — see
25
+ * `connect-credential-channel.ts`). They are visible and manageable from ANY
26
+ * org in the tenant, so a read scoped to the caller's selected org must also
27
+ * match the NULL rows; otherwise a channel connected while an admin had a
28
+ * non-null org would be invisible to every listing/detail query (and vice
29
+ * versa). Org-scoped channels (`organization_id = <org>`) stay scoped to their
30
+ * org.
31
+ *
32
+ * Spread the result into a MikroORM `where` object alongside `tenantId` etc.
33
+ */
34
+ export function channelOrgScopeWhere(
35
+ orgId: string | null | undefined,
36
+ ): Record<string, unknown> {
37
+ return orgId
38
+ ? { $or: [{ organizationId: orgId }, { organizationId: null }] }
39
+ : { organizationId: null }
40
+ }
41
+
42
+ /**
43
+ * Same NULL-inclusive org scoping, expressed over a resolved
44
+ * `OrganizationScopeFilter` (`resolveOrganizationScopeFilter`) rather than a
45
+ * single org id.
46
+ *
47
+ * Read routes scope on the caller's *selected* organization (the
48
+ * `om_selected_org` cookie the top-bar switcher writes), not on the org baked
49
+ * into the session token (#5012) — that resolution yields a list of org ids, or
50
+ * `undefined` when the caller is unrestricted (super-admin viewing all orgs).
51
+ * An unrestricted caller sees every channel in the tenant, so the fragment is
52
+ * empty; a restricted one gets their orgs plus the tenant-wide (`NULL`) rows.
53
+ */
54
+ export function channelOrgScopeWhereFromFilter(
55
+ filter: { organizationIds: string[] | undefined } | null | undefined,
56
+ ): Record<string, unknown> {
57
+ const organizationIds = filter?.organizationIds
58
+ if (!organizationIds || organizationIds.length === 0) return {}
59
+ return { $or: [{ organizationId: { $in: organizationIds } }, { organizationId: null }] }
60
+ }
61
+
20
62
  /**
21
63
  * Throws when the caller may not access a specific channel. Returns silently
22
64
  * when access is allowed.
@@ -486,6 +486,14 @@ export interface ValidateCredentialsResult {
486
486
  ok: boolean
487
487
  /** Field-level error messages keyed by credential field name; for `createCrudFormError`. */
488
488
  errors?: Record<string, string>
489
+ /**
490
+ * Stable machine-readable code per failing field, keyed the same way as
491
+ * `errors`. Lets a UI render a localized message instead of the English
492
+ * prose in `errors` (which stays reserved for logs and API consumers), the
493
+ * same split the route-level `code` field already uses. Optional: adapters
494
+ * that don't emit codes keep working and callers fall back to `errors`.
495
+ */
496
+ errorCodes?: Record<string, string>
489
497
  }
490
498
 
491
499
  // ── The adapter contract ─────────────────────────────────────
@@ -494,6 +502,23 @@ export interface ChannelAdapter {
494
502
  readonly providerKey: string
495
503
  readonly channelType: 'whatsapp' | 'slack' | 'email' | 'sms' | string
496
504
 
505
+ /**
506
+ * Scope of a connected channel for this provider. Governs whether the connect
507
+ * flow stamps `CommunicationChannel.user_id` with the connecting user or leaves
508
+ * it NULL (tenant-wide):
509
+ *
510
+ * - `'user'` (default when absent) — one channel per user (Gmail, IMAP). The
511
+ * credential belongs to the connecting user.
512
+ * - `'tenant'` — one shared channel per tenant (`user_id = NULL`), connected by
513
+ * an admin. Used by push providers (FCM/APNs/Expo) whose service account /
514
+ * signing key serves every device in the tenant. Reading (fan-out/delivery)
515
+ * is already scope-agnostic; this only affects the connect/write path.
516
+ *
517
+ * ADDITIVE-ONLY (BACKWARD_COMPATIBILITY.md): existing adapters that omit it keep
518
+ * their per-user behaviour unchanged.
519
+ */
520
+ readonly channelScope?: 'tenant' | 'user'
521
+
497
522
  /** Declare supported features */
498
523
  readonly capabilities: ChannelCapabilities
499
524
 
@@ -36,7 +36,8 @@ export interface CreateConnectedChannelRowArgs {
36
36
  displayName: string
37
37
  externalIdentifier: string | null
38
38
  credentialsRefId: string | null
39
- userId: string
39
+ /** Connecting user, or `null` for a tenant-wide channel (push: FCM/APNs/Expo). */
40
+ userId: string | null
40
41
  scope: { tenantId: string; organizationId: string | null }
41
42
  /**
42
43
  * Explicit poll-interval override (seconds). When omitted, it is derived from
@@ -46,9 +47,11 @@ export interface CreateConnectedChannelRowArgs {
46
47
  }
47
48
 
48
49
  /**
49
- * Create + persist the per-user `CommunicationChannel` row for a connect flow.
50
- * Shared by the credential-connect command and the OAuth callback so both entry
51
- * points use one channel-shape implementation instead of duplicating `em.create`.
50
+ * Create + persist a `CommunicationChannel` row for a connect flow. `userId` is
51
+ * the connecting user for per-user channels (Gmail/IMAP) or `null` for tenant-wide
52
+ * channels (push: FCM/APNs/Expo). Shared by the credential-connect command and the
53
+ * OAuth callback so both entry points use one channel-shape implementation instead
54
+ * of duplicating `em.create`.
52
55
  *
53
56
  * When credentials could not be persisted (`credentialsRefId === null`) the row
54
57
  * is created in `requires_reauth` + `isActive=false` so workers don't poll a
@@ -59,6 +62,18 @@ export async function createConnectedChannelRow(
59
62
  ): Promise<CommunicationChannel> {
60
63
  const { em, adapter, providerKey, displayName, externalIdentifier, credentialsRefId, userId, scope } = args
61
64
  const credentialsAvailable = credentialsRefId !== null
65
+
66
+ // A tenant-wide push channel (FCM/APNs/Expo, `user_id = NULL`) is a mailbox-less channel keyed on
67
+ // (tenant, provider, channel_type='push', user_id NULL), enforced by
68
+ // `communication_channels_tenant_push_provider_uq`. Push credential schemas are `.passthrough()`, so a
69
+ // stray `email`/`username`/`fromAddress` key can leak through the connect command as an
70
+ // `externalIdentifier`. Left in place it would send a push reconnect down the mailbox dedup branch
71
+ // below — which never matches the existing push row — so the INSERT hits the tenant-push unique index,
72
+ // and the mailbox recovery filter can't find the winner ⇒ an unrecoverable 500. Drop the stray
73
+ // identifier for tenant-wide push so the guard, the dedup key, and the stored row all stay consistent
74
+ // with that index.
75
+ const isTenantWidePush = adapter.channelType === 'push' && userId === null
76
+ const effectiveExternalIdentifier = isTenantWidePush ? null : externalIdentifier
62
77
  const pollIntervalSeconds =
63
78
  args.pollIntervalSeconds !== undefined
64
79
  ? args.pollIntervalSeconds
@@ -73,8 +88,8 @@ export async function createConnectedChannelRow(
73
88
  // email across channels — so every message would be ingested (and threaded)
74
89
  // twice. Reconnecting the SAME provider/mailbox is fine (healed below); this
75
90
  // only blocks a DIFFERENT provider for an already-connected address.
76
- if (externalIdentifier) {
77
- const normalized = externalIdentifier.toLowerCase()
91
+ if (effectiveExternalIdentifier) {
92
+ const normalized = effectiveExternalIdentifier.toLowerCase()
78
93
  const userChannels = (await findWithDecryption(
79
94
  em,
80
95
  CommunicationChannel,
@@ -89,28 +104,48 @@ export async function createConnectedChannelRow(
89
104
  existing.externalIdentifier.toLowerCase() === normalized,
90
105
  )
91
106
  if (conflict) {
92
- throw new MailboxAlreadyConnectedError(externalIdentifier, conflict.providerKey)
107
+ throw new MailboxAlreadyConnectedError(effectiveExternalIdentifier, conflict.providerKey)
93
108
  }
94
109
  }
95
110
 
96
- const naturalKey = {
97
- tenantId: scope.tenantId,
98
- userId,
99
- providerKey,
100
- externalIdentifier,
101
- deletedAt: null,
102
- }
111
+ // Heal-on-reconnect dedup key. Two shapes:
112
+ // - mailbox channels (Gmail/IMAP): keyed on (tenant, user, provider, mailbox);
113
+ // only rows with a known `externalIdentifier` participate.
114
+ // - tenant-wide push channels (FCM/APNs/Expo): no mailbox, `user_id = NULL`, so
115
+ // keyed on (tenant, provider, channel_type='push', user_id NULL). Without this
116
+ // every admin reconnect would insert a duplicate (fan-out takes the oldest per
117
+ // provider, so duplicates are silent noise + a concurrent-connect race).
118
+ // `null` ⇒ no dedup key (identifier-less non-push channel): always insert.
119
+ const dedupeFilter = isTenantWidePush
120
+ ? {
121
+ tenantId: scope.tenantId,
122
+ userId: null,
123
+ providerKey,
124
+ channelType: 'push',
125
+ deletedAt: null,
126
+ }
127
+ : effectiveExternalIdentifier
128
+ ? {
129
+ tenantId: scope.tenantId,
130
+ userId,
131
+ providerKey,
132
+ externalIdentifier: effectiveExternalIdentifier,
133
+ deletedAt: null,
134
+ }
135
+ : null
103
136
 
104
- // Heal-on-reconnect: a channel for the same (tenant, user, provider, mailbox)
105
- // already exists when the user re-runs OAuth / reconnects after a
106
- // `requires_reauth`. Update it in place rather than inserting a duplicate row —
107
- // a duplicate would stay `isActive` and keep polling + re-emitting reauth
108
- // banners, and register a second competing push subscription. Only mailboxes
109
- // with a known `externalIdentifier` participate (the unique index is partial).
137
+ // Heal-on-reconnect: a channel for the same dedup key already exists when the
138
+ // user re-runs OAuth / reconnects after a `requires_reauth`, or an admin
139
+ // re-submits a tenant push provider's credentials. Update it in place rather
140
+ // than inserting a duplicate row — a duplicate would stay `isActive` and keep
141
+ // polling + re-emitting reauth banners, and register a second competing push
142
+ // subscription. Backed by the partial unique indexes
143
+ // `communication_channels_user_provider_external_uq` (mailbox) and
144
+ // `communication_channels_tenant_push_provider_uq` (tenant push).
110
145
  const applyConnectionState = (target: CommunicationChannel): void => {
111
146
  target.channelType = adapter.channelType
112
147
  target.displayName = displayName
113
- target.externalIdentifier = externalIdentifier ?? null
148
+ target.externalIdentifier = effectiveExternalIdentifier ?? null
114
149
  target.credentialsRef = credentialsRefId
115
150
  target.capabilities = adapter.capabilities as unknown as Record<string, unknown>
116
151
  target.isActive = credentialsAvailable
@@ -119,8 +154,8 @@ export async function createConnectedChannelRow(
119
154
  target.lastError = credentialsAvailable ? null : 'credentials_persist_failed'
120
155
  }
121
156
 
122
- if (externalIdentifier) {
123
- const existing = await findOneWithDecryption(em, CommunicationChannel, naturalKey, undefined, dscope)
157
+ if (dedupeFilter) {
158
+ const existing = await findOneWithDecryption(em, CommunicationChannel, dedupeFilter, undefined, dscope)
124
159
  if (existing) {
125
160
  applyConnectionState(existing)
126
161
  await em.flush()
@@ -132,7 +167,7 @@ export async function createConnectedChannelRow(
132
167
  providerKey,
133
168
  channelType: adapter.channelType,
134
169
  displayName,
135
- externalIdentifier: externalIdentifier ?? null,
170
+ externalIdentifier: effectiveExternalIdentifier ?? null,
136
171
  credentialsRef: credentialsRefId,
137
172
  capabilities: adapter.capabilities as unknown as Record<string, unknown>,
138
173
  isActive: credentialsAvailable,
@@ -152,9 +187,9 @@ export async function createConnectedChannelRow(
152
187
  // Concurrent connect for the same mailbox won the race (partial unique index
153
188
  // rejected ours). Re-select the winner on a clean fork and heal it so the
154
189
  // caller still gets a single, connected channel.
155
- if (!isUniqueViolation(err) || !externalIdentifier) throw err
190
+ if (!isUniqueViolation(err) || !dedupeFilter) throw err
156
191
  const reEm = em.fork()
157
- const winner = await findOneWithDecryption(reEm, CommunicationChannel, naturalKey, undefined, dscope)
192
+ const winner = await findOneWithDecryption(reEm, CommunicationChannel, dedupeFilter, undefined, dscope)
158
193
  if (!winner) throw err
159
194
  applyConnectionState(winner)
160
195
  await reEm.flush()
@@ -0,0 +1,139 @@
1
+ import type { ZodType } from 'zod'
2
+ import type {
3
+ ChannelAdapter,
4
+ ChannelCapabilities,
5
+ ChannelNativeContent,
6
+ ConvertOutboundInput,
7
+ GetMessageStatusInput,
8
+ InboundMessage,
9
+ MessageStatus,
10
+ NormalizedInboundMessage,
11
+ SendMessageInput,
12
+ SendMessageResult,
13
+ ValidateCredentialsInput,
14
+ ValidateCredentialsResult,
15
+ VerifyWebhookInput,
16
+ } from './adapter'
17
+ import { pushChannelCapabilities } from './push-capabilities'
18
+ import { resolvePushCredentialIssue } from './push-credential-errors'
19
+
20
+ /**
21
+ * Uniform sentinel a push adapter returns for a permanently-invalid device token.
22
+ * The `push_notifications` worker checks `error === DEVICE_UNREGISTERED ||
23
+ * metadata.unregistered === true` and soft-deletes the device — so every push
24
+ * provider (fcm/apns/expo + the test stub) MUST use this exact shape.
25
+ */
26
+ export const DEVICE_UNREGISTERED = 'device_unregistered'
27
+
28
+ /** Build the uniform `device_unregistered` result. Extra metadata is merged in. */
29
+ export function deviceUnregisteredResult(metadata?: Record<string, unknown>): SendMessageResult {
30
+ return {
31
+ externalMessageId: '',
32
+ status: 'failed',
33
+ error: DEVICE_UNREGISTERED,
34
+ metadata: { unregistered: true, ...(metadata ?? {}) },
35
+ }
36
+ }
37
+
38
+ /** Standard result when a send is attempted without a device token. */
39
+ export const MISSING_PUSH_TOKEN_RESULT: SendMessageResult = {
40
+ externalMessageId: '',
41
+ status: 'failed',
42
+ error: 'missing_push_token',
43
+ }
44
+
45
+ /**
46
+ * Outcome of checking one provider delivery receipt for a previously-accepted push.
47
+ * Emitted only for tickets whose receipt is already available (`resolved`); a ticket whose
48
+ * receipt is not yet ready is omitted so the caller retries it on a later sweep.
49
+ */
50
+ export interface PushReceiptOutcome {
51
+ /** The provider ticket id returned by the synchronous send (persisted as `externalMessageId`). */
52
+ ticketId: string
53
+ /** The receipt reports the device token is permanently invalid ⇒ soft-delete the device. */
54
+ unregistered: boolean
55
+ }
56
+
57
+ /**
58
+ * Optional capability for providers whose "device unregistered" signal arrives in an ASYNCHRONOUS
59
+ * receipt phase rather than the synchronous send ticket (Expo). Implemented by the Expo adapter and
60
+ * polled by the push_notifications receipt reaper; FCM/APNs prune synchronously in `sendMessage` and
61
+ * do not implement it. Additive to `ChannelAdapter` — feature-detected via `supportsReceiptChecking`.
62
+ */
63
+ export interface PushReceiptChecker {
64
+ /**
65
+ * Fetch delivery receipts for previously-accepted push tickets and report which map to a
66
+ * permanently-unregistered device token. Return an entry ONLY for tickets whose receipt is ready;
67
+ * omit tickets with no receipt yet so the caller re-checks them later. Reuses the same tenant
68
+ * credentials as the send path.
69
+ */
70
+ checkReceipts(ticketIds: string[], credentials: unknown): Promise<PushReceiptOutcome[]>
71
+ }
72
+
73
+ /** Feature-detect the optional `PushReceiptChecker` capability on a resolved channel adapter. */
74
+ export function supportsReceiptChecking(adapter: unknown): adapter is PushReceiptChecker {
75
+ return typeof (adapter as { checkReceipts?: unknown } | null | undefined)?.checkReceipts === 'function'
76
+ }
77
+
78
+ /** Extract the per-call push token from `SendMessageInput.metadata`, or null if absent. */
79
+ export function readPushToken(input: SendMessageInput): string | null {
80
+ const token = typeof input.metadata?.pushToken === 'string' ? input.metadata.pushToken : ''
81
+ return token.length > 0 ? token : null
82
+ }
83
+
84
+ /**
85
+ * Shared base for outbound-only mobile-push `ChannelAdapter`s (fcm/apns/expo and
86
+ * the test `push_stub`). Concrete adapters provide just `providerKey`,
87
+ * `credentialsSchema`, and `sendMessage`; the push capabilities baseline, the
88
+ * no-op inbound surface (push is outbound-only), the passthrough `convertOutbound`,
89
+ * and schema-driven `validateCredentials` are shared so the push family stays
90
+ * consistent. This is push-specific and does NOT change the per-package
91
+ * standalone convention used by the email channel adapters (gmail/imap).
92
+ */
93
+ export abstract class BasePushChannelAdapter implements ChannelAdapter {
94
+ abstract readonly providerKey: string
95
+ readonly channelType = 'push'
96
+ // Push credentials (service account / signing key) serve every device in the
97
+ // tenant, so a push channel is tenant-wide (user_id = NULL), connected by an
98
+ // admin — never a per-user channel. Covers fcm/apns/expo + the test stub.
99
+ readonly channelScope = 'tenant' as const
100
+ readonly capabilities: ChannelCapabilities = pushChannelCapabilities
101
+
102
+ /** Zod schema for this provider's tenant-level credentials; drives `validateCredentials`. */
103
+ protected abstract readonly credentialsSchema: ZodType<unknown>
104
+
105
+ abstract sendMessage(input: SendMessageInput): Promise<SendMessageResult>
106
+
107
+ async convertOutbound(input: ConvertOutboundInput): Promise<ChannelNativeContent> {
108
+ return { content: { text: input.body, bodyFormat: input.bodyFormat }, metadata: input.channelMetadata ?? {} }
109
+ }
110
+
111
+ async verifyWebhook(_input: VerifyWebhookInput): Promise<InboundMessage> {
112
+ return { raw: {}, eventType: 'other', metadata: { reason: `${this.providerKey}-no-webhook` } }
113
+ }
114
+
115
+ async getStatus(_input: GetMessageStatusInput): Promise<MessageStatus> {
116
+ return { status: 'sent' }
117
+ }
118
+
119
+ async normalizeInbound(_raw: InboundMessage): Promise<NormalizedInboundMessage> {
120
+ throw new Error(`[internal] ${this.providerKey} push adapter normalizeInbound is not used (push is outbound-only)`)
121
+ }
122
+
123
+ async validateCredentials(input: ValidateCredentialsInput): Promise<ValidateCredentialsResult> {
124
+ const parsed = this.credentialsSchema.safeParse(input.credentials)
125
+ if (parsed.success) return { ok: true }
126
+ const errors: Record<string, string> = {}
127
+ const errorCodes: Record<string, string> = {}
128
+ for (const issue of parsed.error.issues) {
129
+ const key = issue.path[0]
130
+ if (typeof key !== 'string' || errors[key]) continue
131
+ // Schemas emit a stable code as the issue message; keep the English prose
132
+ // for API consumers and hand the code to the UI so it can localize.
133
+ const resolved = resolvePushCredentialIssue(issue.message)
134
+ errors[key] = resolved.message
135
+ errorCodes[key] = resolved.code
136
+ }
137
+ return { ok: false, errors, errorCodes }
138
+ }
139
+ }
@@ -0,0 +1,42 @@
1
+ import type { ChannelCapabilities } from './adapter'
2
+
3
+ /**
4
+ * Baseline capability profile shared by every mobile-push channel provider
5
+ * (FCM / APNs / Expo) and the test-only `push_stub` adapter. Mirrors the
6
+ * `baseEmailCapabilities` pattern so the push contract has a single source of
7
+ * truth: push is a fire-and-forget outbound channel with no threading, history,
8
+ * reactions, or rich content, and it is real-time (`realtimePush: true`) so the
9
+ * hub never schedules polling for it.
10
+ *
11
+ * Providers spread this and override only what genuinely differs.
12
+ */
13
+ export const pushChannelCapabilities: ChannelCapabilities = {
14
+ // Core
15
+ threading: false,
16
+ richText: false,
17
+ fileSharing: false,
18
+ readReceipts: false,
19
+ deliveryReceipts: false,
20
+ typingIndicators: false,
21
+
22
+ // Extended
23
+ reactions: false,
24
+ multiReactionPerUser: false,
25
+ editMessage: false,
26
+ deleteMessage: false,
27
+ presence: false,
28
+ richBlocks: false,
29
+ interactiveComponents: false,
30
+ inlineImages: false,
31
+ conversationHistory: false,
32
+ contactCards: false,
33
+ locationSharing: false,
34
+ voiceNotes: false,
35
+ stickers: false,
36
+
37
+ // Body formats
38
+ supportedBodyFormats: ['text'],
39
+
40
+ // Push is real-time; no polling.
41
+ realtimePush: true,
42
+ }
@@ -0,0 +1,52 @@
1
+ export type PushConnectErrorBody = {
2
+ code?: string | null
3
+ fieldErrors?: Record<string, string> | null
4
+ fieldErrorCodes?: Record<string, string> | null
5
+ }
6
+
7
+ export type PushConnectTranslate = (key: string, fallback: string) => string
8
+
9
+ /**
10
+ * Map a push connect route's structured error `code`
11
+ * (`provider_not_tenant_scoped`, `mailbox_already_connected`,
12
+ * `wrong_scope_for_route`, …) to a localized
13
+ * `communication_channels.push.connect.errors.<code>` message, falling back to
14
+ * the generic `push.connect.failed` when the response carries no code or no
15
+ * matching translation exists. The raw English `error` string the route also
16
+ * returns stays reserved for logs/API consumers so the shipped locale files
17
+ * apply on the failure path.
18
+ */
19
+ export function resolvePushConnectErrorMessage(
20
+ translate: PushConnectTranslate,
21
+ body: PushConnectErrorBody | undefined,
22
+ ): string {
23
+ const fallback = translate('communication_channels.push.connect.failed', 'Could not connect push provider.')
24
+ if (!body?.code) return fallback
25
+ return translate(`communication_channels.push.connect.errors.${body.code}`, fallback)
26
+ }
27
+
28
+ /**
29
+ * Localize the per-field credential-validation errors a 422 connect response
30
+ * carries. Each field's `fieldErrorCodes` entry maps to
31
+ * `communication_channels.push.connect.errors.fields.<code>`; when a field has
32
+ * no code (an adapter that predates the code contract) or the locale ships no
33
+ * matching key, the raw English `fieldErrors` string is used so the operator
34
+ * still sees something actionable rather than a blank field.
35
+ *
36
+ * Returns a map ready for a widget's `setFieldErrors`.
37
+ */
38
+ export function resolvePushConnectFieldErrors(
39
+ translate: PushConnectTranslate,
40
+ body: PushConnectErrorBody | undefined,
41
+ ): Record<string, string> {
42
+ const fieldErrors = body?.fieldErrors ?? {}
43
+ const fieldErrorCodes = body?.fieldErrorCodes ?? {}
44
+ const resolved: Record<string, string> = {}
45
+ for (const [field, message] of Object.entries(fieldErrors)) {
46
+ const code = fieldErrorCodes[field]
47
+ resolved[field] = code
48
+ ? translate(`communication_channels.push.connect.errors.fields.${code}`, message)
49
+ : message
50
+ }
51
+ return resolved
52
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Stable field-error codes for push credential validation (fcm/apns/expo).
3
+ *
4
+ * Push credential schemas emit one of these codes as the zod issue `message`
5
+ * instead of prose. {@link BasePushChannelAdapter.validateCredentials} then
6
+ * splits each issue into two channels:
7
+ *
8
+ * - `errors[field]` — English prose from {@link PUSH_CREDENTIAL_ERROR_MESSAGES},
9
+ * reserved for logs and API consumers.
10
+ * - `errorCodes[field]` — the code, which the connect widgets map to
11
+ * `communication_channels.push.connect.errors.fields.<code>`
12
+ * so operators see a localized message.
13
+ *
14
+ * This mirrors the route-level split already used by `push-connect-error.ts`.
15
+ * An issue whose message is not a known code degrades to {@link PUSH_CREDENTIAL_ERROR_INVALID}
16
+ * with the raw zod message kept in `errors`, so a schema change can never leak
17
+ * an untranslated internal string into the UI.
18
+ */
19
+
20
+ export const PUSH_CREDENTIAL_ERROR_REQUIRED = 'required'
21
+ export const PUSH_CREDENTIAL_ERROR_INVALID = 'invalid'
22
+ export const PUSH_CREDENTIAL_ERROR_INVALID_JSON = 'invalid_json'
23
+ export const PUSH_CREDENTIAL_ERROR_MISSING_FIELDS = 'missing_fields'
24
+ export const PUSH_CREDENTIAL_ERROR_INVALID_P8 = 'invalid_p8'
25
+ export const PUSH_CREDENTIAL_ERROR_INVALID_KEY_ID = 'invalid_key_id'
26
+ export const PUSH_CREDENTIAL_ERROR_INVALID_TEAM_ID = 'invalid_team_id'
27
+ export const PUSH_CREDENTIAL_ERROR_INVALID_BUNDLE_ID = 'invalid_bundle_id'
28
+
29
+ /**
30
+ * English prose per code. Kept as the `errors` payload so existing API
31
+ * consumers and log readers still get a human-readable string.
32
+ */
33
+ export const PUSH_CREDENTIAL_ERROR_MESSAGES: Record<string, string> = {
34
+ [PUSH_CREDENTIAL_ERROR_REQUIRED]: 'This field is required.',
35
+ [PUSH_CREDENTIAL_ERROR_INVALID]: 'This value is invalid.',
36
+ [PUSH_CREDENTIAL_ERROR_INVALID_JSON]: 'This is not valid JSON. Paste the service account file exactly as Firebase generated it.',
37
+ [PUSH_CREDENTIAL_ERROR_MISSING_FIELDS]: 'The service account JSON is missing required fields (project_id, client_email, private_key).',
38
+ [PUSH_CREDENTIAL_ERROR_INVALID_P8]: 'This is not a readable APNs .p8 signing key. Paste the whole file including the BEGIN and END lines.',
39
+ [PUSH_CREDENTIAL_ERROR_INVALID_KEY_ID]: 'An APNs Key ID is 10 alphanumeric characters.',
40
+ [PUSH_CREDENTIAL_ERROR_INVALID_TEAM_ID]: 'An Apple Team ID is 10 alphanumeric characters.',
41
+ [PUSH_CREDENTIAL_ERROR_INVALID_BUNDLE_ID]: 'A Bundle ID looks like com.example.app.',
42
+ }
43
+
44
+ export function isPushCredentialErrorCode(value: string): boolean {
45
+ return Object.prototype.hasOwnProperty.call(PUSH_CREDENTIAL_ERROR_MESSAGES, value)
46
+ }
47
+
48
+ /**
49
+ * Split a zod issue message into `{ code, message }`. Known codes resolve to
50
+ * their English prose; anything else is reported as {@link PUSH_CREDENTIAL_ERROR_INVALID}
51
+ * while preserving the original text for logs.
52
+ */
53
+ export function resolvePushCredentialIssue(issueMessage: string): { code: string; message: string } {
54
+ if (isPushCredentialErrorCode(issueMessage)) {
55
+ return { code: issueMessage, message: PUSH_CREDENTIAL_ERROR_MESSAGES[issueMessage] }
56
+ }
57
+ return { code: PUSH_CREDENTIAL_ERROR_INVALID, message: issueMessage }
58
+ }