@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
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/push_notifications/di.ts"],
4
+ "sourcesContent": ["import { asValue } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { PushNotificationDelivery } from './data/entities'\nimport { ensurePushStubAdapterRegistered } from './lib/push-stub-adapter'\nimport { pushNotificationService } from './lib/send-custom-push'\n\nexport function register(container: AppContainer) {\n // Test-only: register the network-free `push_stub` channel adapter when\n // `OM_ENABLE_PUSH_STUB_ADAPTER` is set (no-op in production). Lets the integration harness drive\n // the strategy \u2192 delivery-row \u2192 worker \u2192 sendMessage chain without a real provider. Mirrors the\n // communication_channels test-seed adapter registration. See lib/push-stub-adapter.ts.\n ensurePushStubAdapterRegistered()\n\n container.register({\n PushNotificationDelivery: asValue(PushNotificationDelivery),\n // Stateless service backing the admin custom-send push (decoupled via DI). The caller passes\n // its own scoped `resolve` in the call args.\n pushNotificationService: asValue(pushNotificationService),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,eAAe;AAExB,SAAS,gCAAgC;AACzC,SAAS,uCAAuC;AAChD,SAAS,+BAA+B;AAEjC,SAAS,SAAS,WAAyB;AAKhD,kCAAgC;AAEhC,YAAU,SAAS;AAAA,IACjB,0BAA0B,QAAQ,wBAAwB;AAAA;AAAA;AAAA,IAG1D,yBAAyB,QAAQ,uBAAuB;AAAA,EAC1D,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,24 @@
1
+ import { createModuleEvents } from "@open-mercato/shared/modules/events";
2
+ const events = [
3
+ { id: "push_notifications.delivery.sent", label: "Push Delivery Sent", entity: "delivery", category: "lifecycle" },
4
+ {
5
+ id: "push_notifications.delivery.failed",
6
+ label: "Push Delivery Failed",
7
+ entity: "delivery",
8
+ category: "lifecycle",
9
+ // Fires on every failed send attempt for a delivery, not only the terminal one. A retryable
10
+ // failure that will be re-attempted carries `willRetry: true`; the final give-up (and terminal
11
+ // errors like channel_unavailable / no_adapter) omit it. Subscribers that count "deliveries that
12
+ // ultimately failed" MUST filter to `willRetry !== true` to avoid double-counting retries.
13
+ description: "A push delivery attempt failed. Carries willRetry:true when another attempt is scheduled."
14
+ }
15
+ ];
16
+ const eventsConfig = createModuleEvents({ moduleId: "push_notifications", events });
17
+ const emitPushNotificationsEvent = eventsConfig.emit;
18
+ var events_default = eventsConfig;
19
+ export {
20
+ events_default as default,
21
+ emitPushNotificationsEvent,
22
+ eventsConfig
23
+ };
24
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/push_notifications/events.ts"],
4
+ "sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\nconst events = [\n { id: 'push_notifications.delivery.sent', label: 'Push Delivery Sent', entity: 'delivery', category: 'lifecycle' },\n {\n id: 'push_notifications.delivery.failed',\n label: 'Push Delivery Failed',\n entity: 'delivery',\n category: 'lifecycle',\n // Fires on every failed send attempt for a delivery, not only the terminal one. A retryable\n // failure that will be re-attempted carries `willRetry: true`; the final give-up (and terminal\n // errors like channel_unavailable / no_adapter) omit it. Subscribers that count \"deliveries that\n // ultimately failed\" MUST filter to `willRetry !== true` to avoid double-counting retries.\n description: 'A push delivery attempt failed. Carries willRetry:true when another attempt is scheduled.',\n },\n] as const\n\nexport const eventsConfig = createModuleEvents({ moduleId: 'push_notifications', events })\nexport const emitPushNotificationsEvent = eventsConfig.emit\nexport type PushNotificationsEventId = typeof events[number]['id']\nexport default eventsConfig\n"],
5
+ "mappings": "AAAA,SAAS,0BAA0B;AAEnC,MAAM,SAAS;AAAA,EACb,EAAE,IAAI,oCAAoC,OAAO,sBAAsB,QAAQ,YAAY,UAAU,YAAY;AAAA,EACjH;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,IAKV,aAAa;AAAA,EACf;AACF;AAEO,MAAM,eAAe,mBAAmB,EAAE,UAAU,sBAAsB,OAAO,CAAC;AAClF,MAAM,6BAA6B,aAAa;AAEvD,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ const metadata = {
2
+ name: "push_notifications",
3
+ title: "Push Notifications",
4
+ version: "0.1.0",
5
+ description: "Push delivery rails: a `push` notification delivery strategy that fans out to registered devices and sends through the communication_channels hub.",
6
+ author: "Open Mercato Team",
7
+ license: "MIT",
8
+ requires: ["auth", "devices", "notifications", "communication_channels", "integrations"],
9
+ ejectable: true
10
+ };
11
+ import { features } from "./acl.js";
12
+ export {
13
+ features,
14
+ metadata
15
+ };
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/push_notifications/index.ts"],
4
+ "sourcesContent": ["import type { ModuleInfo } from '@open-mercato/shared/modules/registry'\n\nexport const metadata: ModuleInfo = {\n name: 'push_notifications',\n title: 'Push Notifications',\n version: '0.1.0',\n description:\n 'Push delivery rails: a `push` notification delivery strategy that fans out to registered devices and sends through the communication_channels hub.',\n author: 'Open Mercato Team',\n license: 'MIT',\n requires: ['auth', 'devices', 'notifications', 'communication_channels', 'integrations'],\n ejectable: true,\n}\n\nexport { features } from './acl'\n"],
5
+ "mappings": "AAEO,MAAM,WAAuB;AAAA,EAClC,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA,EACT,aACE;AAAA,EACF,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU,CAAC,QAAQ,WAAW,iBAAiB,0BAA0B,cAAc;AAAA,EACvF,WAAW;AACb;AAEA,SAAS,gBAAgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,80 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
5
+ import { createLogger } from "@open-mercato/shared/lib/logger";
6
+ const logger = createLogger("push_notifications");
7
+ const PUSH_FAKE_PROVIDERS_ENV = "OM_PUSH_FAKE_PROVIDERS";
8
+ const LOG_FILE_NAME = "push-fake-messages.jsonl";
9
+ function isPushFakeProvidersEnabled() {
10
+ return parseBooleanToken(process.env[PUSH_FAKE_PROVIDERS_ENV]) === true;
11
+ }
12
+ const warnedFakeProviders = /* @__PURE__ */ new Set();
13
+ function warnPushFakeProvidersActive(provider) {
14
+ if (!isPushFakeProvidersEnabled() || warnedFakeProviders.has(provider)) return;
15
+ warnedFakeProviders.add(provider);
16
+ logger.warn(
17
+ `${provider} push adapter is running in FAKE mode (${PUSH_FAKE_PROVIDERS_ENV} is set): messages are recorded, NOT delivered. This must never be set in production.`,
18
+ { provider, env: PUSH_FAKE_PROVIDERS_ENV }
19
+ );
20
+ }
21
+ function resolveQueueBaseDir() {
22
+ const explicit = process.env.QUEUE_BASE_DIR?.trim();
23
+ if (explicit) return explicit;
24
+ return path.resolve(process.env.OM_TEST_APP_ROOT?.trim() || process.cwd(), ".mercato/queue");
25
+ }
26
+ function resolveFakePushLogPath() {
27
+ return path.join(resolveQueueBaseDir(), LOG_FILE_NAME);
28
+ }
29
+ function pushTokenTail(token) {
30
+ return token.slice(-8);
31
+ }
32
+ function uniquePushTokenTail() {
33
+ return randomBytes(4).toString("hex").toUpperCase();
34
+ }
35
+ function recordFakePush(provider, token, native) {
36
+ if (!isPushFakeProvidersEnabled()) return;
37
+ const entry = {
38
+ provider,
39
+ tokenTail: pushTokenTail(token),
40
+ native,
41
+ at: (/* @__PURE__ */ new Date()).toISOString()
42
+ };
43
+ const filePath = resolveFakePushLogPath();
44
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
45
+ fs.appendFileSync(filePath, `${JSON.stringify(entry)}
46
+ `, "utf8");
47
+ }
48
+ function parseEntry(line) {
49
+ try {
50
+ return JSON.parse(line);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function readFakePushLog() {
56
+ const filePath = resolveFakePushLogPath();
57
+ if (!fs.existsSync(filePath)) return [];
58
+ return fs.readFileSync(filePath, "utf8").split("\n").filter((line) => line.trim().length > 0).map(parseEntry).filter((entry) => entry !== null);
59
+ }
60
+ function findFakePush(provider, tokenTail, sinceIso) {
61
+ return readFakePushLog().filter((entry) => entry.provider === provider && entry.tokenTail === tokenTail).filter((entry) => !sinceIso || entry.at >= sinceIso).pop();
62
+ }
63
+ function clearFakePushLog() {
64
+ const filePath = resolveFakePushLogPath();
65
+ if (fs.existsSync(filePath)) fs.rmSync(filePath);
66
+ }
67
+ export {
68
+ PUSH_FAKE_PROVIDERS_ENV,
69
+ clearFakePushLog,
70
+ findFakePush,
71
+ isPushFakeProvidersEnabled,
72
+ pushTokenTail,
73
+ readFakePushLog,
74
+ recordFakePush,
75
+ resolveFakePushLogPath,
76
+ resolveQueueBaseDir,
77
+ uniquePushTokenTail,
78
+ warnPushFakeProvidersActive
79
+ };
80
+ //# sourceMappingURL=fake-provider-recorder.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/push_notifications/lib/fake-provider-recorder.ts"],
4
+ "sourcesContent": ["import { randomBytes } from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('push_notifications')\n\n/**\n * Test-only sink recording the **provider-native** message each push adapter handed its SDK client.\n *\n * The `channel-{fcm,apns,expo}` fakes replace the SDK *client*, not the adapter \u2014 so the real adapter\n * builds the native message, but nothing persists it: no adapter returns `metadata` on success, and\n * APNs deliberately reports an empty `externalMessageId` (its value reaches the admin-exposed\n * `provider_response`, which must never carry token material). The message is therefore written here,\n * out of band.\n *\n * **Why a file and not an in-memory recorder.** The adapter does not run in the spec's process, and\n * *which* process it runs in is not fixed: the send happens in the Next.js app server (which may\n * process the job inline), in a drain child that `drainIntegrationQueue` spawns whenever\n * `OM_TEST_APP_ROOT` is set, or \u2014 only in the monorepo path \u2014 in the Playwright process itself. A\n * module-level array would live in whichever of those three ran the adapter, and the spec could read it\n * in just one. The queue already crosses exactly these boundaries through `QUEUE_BASE_DIR`, so the sink\n * rides the same directory rather than inventing a second IPC channel.\n *\n * Production safety: every entry point is a no-op unless `OM_PUSH_FAKE_PROVIDERS` is set. Mirrors\n * `ensurePushStubAdapterRegistered()` in ./push-stub-adapter.ts.\n */\nexport const PUSH_FAKE_PROVIDERS_ENV = 'OM_PUSH_FAKE_PROVIDERS'\n\nconst LOG_FILE_NAME = 'push-fake-messages.jsonl'\n\nexport type FakePushProvider = 'fcm' | 'apns' | 'expo'\n\nexport type FakePushEntry = {\n provider: FakePushProvider\n tokenTail: string\n native: Record<string, unknown>\n at: string\n}\n\nexport function isPushFakeProvidersEnabled(): boolean {\n return parseBooleanToken(process.env[PUSH_FAKE_PROVIDERS_ENV]) === true\n}\n\nconst warnedFakeProviders = new Set<FakePushProvider>()\n\n/**\n * Loudly flag \u2014 exactly once per provider per process \u2014 that a push adapter is running against the\n * network-free fake instead of the real SDK. `OM_PUSH_FAKE_PROVIDERS` is a test-harness flag, and a\n * legitimate-but-misplaced `=1` (e.g. a staging env block promoted to prod) would otherwise report\n * every push as `sent` with no delivery, no log line, and no admin signal for days. This warn plus the\n * `degraded` health status (see `makePushClientConfigHealthCheck`) make that misconfiguration visible.\n */\nexport function warnPushFakeProvidersActive(provider: FakePushProvider): void {\n if (!isPushFakeProvidersEnabled() || warnedFakeProviders.has(provider)) return\n warnedFakeProviders.add(provider)\n logger.warn(\n `${provider} push adapter is running in FAKE mode (${PUSH_FAKE_PROVIDERS_ENV} is set): messages are recorded, NOT delivered. This must never be set in production.`,\n { provider, env: PUSH_FAKE_PROVIDERS_ENV },\n )\n}\n\n/**\n * The one place the queue base dir is resolved. The worker process, the drain child, and the spec must\n * all agree, so every caller goes through this: `QUEUE_BASE_DIR` when set (the harness always sets it,\n * and `drainIntegrationQueue` propagates it to its child), else `<appRoot>/.mercato/queue`.\n *\n * In the app/worker process `cwd` IS the app root. The Playwright process runs from the repo root, so\n * `helpers/integration/pushFake` sets `QUEUE_BASE_DIR` before anything reads it \u2014 this fallback is never\n * reached there.\n */\nexport function resolveQueueBaseDir(): string {\n const explicit = process.env.QUEUE_BASE_DIR?.trim()\n if (explicit) return explicit\n return path.resolve(process.env.OM_TEST_APP_ROOT?.trim() || process.cwd(), '.mercato/queue')\n}\n\nexport function resolveFakePushLogPath(): string {\n return path.join(resolveQueueBaseDir(), LOG_FILE_NAME)\n}\n\n/** Token tail used to key entries \u2014 never the raw token (mirrors `token_snapshot` on the delivery row). */\nexport function pushTokenTail(token: string): string {\n return token.slice(-8)\n}\n\n/**\n * An 8-char token tail unique to this run.\n *\n * The sink is append-only and is NOT truncated between runs on the reused-environment path\n * (`tryReuseExistingEnvironment` does not wipe `QUEUE_BASE_DIR`, unlike the fresh path). A compile-time\n * constant tail would therefore let a *previous* run's entry satisfy an assertion before this run's\n * worker had written anything \u2014 a false pass. Uniqueness also isolates specs running in parallel.\n */\nexport function uniquePushTokenTail(): string {\n return randomBytes(4).toString('hex').toUpperCase()\n}\n\nexport function recordFakePush(provider: FakePushProvider, token: string, native: Record<string, unknown>): void {\n if (!isPushFakeProvidersEnabled()) return\n const entry: FakePushEntry = {\n provider,\n tokenTail: pushTokenTail(token),\n native,\n at: new Date().toISOString(),\n }\n const filePath = resolveFakePushLogPath()\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n // A single `O_APPEND` write of one short line \u2014 concurrent deliveries interleave by line, not within one.\n fs.appendFileSync(filePath, `${JSON.stringify(entry)}\\n`, 'utf8')\n}\n\nfunction parseEntry(line: string): FakePushEntry | null {\n try {\n return JSON.parse(line) as FakePushEntry\n } catch {\n // A reader can observe a line the writer has not finished appending. Skipping it lets the caller\n // poll again; throwing would permanently fail the enclosing `expect.poll`.\n return null\n }\n}\n\nexport function readFakePushLog(): FakePushEntry[] {\n const filePath = resolveFakePushLogPath()\n if (!fs.existsSync(filePath)) return []\n return fs\n .readFileSync(filePath, 'utf8')\n .split('\\n')\n .filter((line) => line.trim().length > 0)\n .map(parseEntry)\n .filter((entry): entry is FakePushEntry => entry !== null)\n}\n\n/**\n * Find the message a spec's own device produced. `tokenTail` should come from\n * {@link uniquePushTokenTail}; `sinceIso` additionally rejects entries predating the caller, so a stale\n * sink can never satisfy an assertion.\n */\nexport function findFakePush(\n provider: FakePushProvider,\n tokenTail: string,\n sinceIso?: string,\n): FakePushEntry | undefined {\n return readFakePushLog()\n .filter((entry) => entry.provider === provider && entry.tokenTail === tokenTail)\n .filter((entry) => !sinceIso || entry.at >= sinceIso)\n .pop()\n}\n\nexport function clearFakePushLog(): void {\n const filePath = resolveFakePushLogPath()\n if (fs.existsSync(filePath)) fs.rmSync(filePath)\n}\n"],
5
+ "mappings": "AAAA,SAAS,mBAAmB;AAC5B,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,oBAAoB;AAsBzC,MAAM,0BAA0B;AAEvC,MAAM,gBAAgB;AAWf,SAAS,6BAAsC;AACpD,SAAO,kBAAkB,QAAQ,IAAI,uBAAuB,CAAC,MAAM;AACrE;AAEA,MAAM,sBAAsB,oBAAI,IAAsB;AAS/C,SAAS,4BAA4B,UAAkC;AAC5E,MAAI,CAAC,2BAA2B,KAAK,oBAAoB,IAAI,QAAQ,EAAG;AACxE,sBAAoB,IAAI,QAAQ;AAChC,SAAO;AAAA,IACL,GAAG,QAAQ,0CAA0C,uBAAuB;AAAA,IAC5E,EAAE,UAAU,KAAK,wBAAwB;AAAA,EAC3C;AACF;AAWO,SAAS,sBAA8B;AAC5C,QAAM,WAAW,QAAQ,IAAI,gBAAgB,KAAK;AAClD,MAAI,SAAU,QAAO;AACrB,SAAO,KAAK,QAAQ,QAAQ,IAAI,kBAAkB,KAAK,KAAK,QAAQ,IAAI,GAAG,gBAAgB;AAC7F;AAEO,SAAS,yBAAiC;AAC/C,SAAO,KAAK,KAAK,oBAAoB,GAAG,aAAa;AACvD;AAGO,SAAS,cAAc,OAAuB;AACnD,SAAO,MAAM,MAAM,EAAE;AACvB;AAUO,SAAS,sBAA8B;AAC5C,SAAO,YAAY,CAAC,EAAE,SAAS,KAAK,EAAE,YAAY;AACpD;AAEO,SAAS,eAAe,UAA4B,OAAe,QAAuC;AAC/G,MAAI,CAAC,2BAA2B,EAAG;AACnC,QAAM,QAAuB;AAAA,IAC3B;AAAA,IACA,WAAW,cAAc,KAAK;AAAA,IAC9B;AAAA,IACA,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC7B;AACA,QAAM,WAAW,uBAAuB;AACxC,KAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAExD,KAAG,eAAe,UAAU,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,GAAM,MAAM;AAClE;AAEA,SAAS,WAAW,MAAoC;AACtD,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,kBAAmC;AACjD,QAAM,WAAW,uBAAuB;AACxC,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO,CAAC;AACtC,SAAO,GACJ,aAAa,UAAU,MAAM,EAC7B,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,SAAS,CAAC,EACvC,IAAI,UAAU,EACd,OAAO,CAAC,UAAkC,UAAU,IAAI;AAC7D;AAOO,SAAS,aACd,UACA,WACA,UAC2B;AAC3B,SAAO,gBAAgB,EACpB,OAAO,CAAC,UAAU,MAAM,aAAa,YAAY,MAAM,cAAc,SAAS,EAC9E,OAAO,CAAC,UAAU,CAAC,YAAY,MAAM,MAAM,QAAQ,EACnD,IAAI;AACT;AAEO,SAAS,mBAAyB;AACvC,QAAM,WAAW,uBAAuB;AACxC,MAAI,GAAG,WAAW,QAAQ,EAAG,IAAG,OAAO,QAAQ;AACjD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,54 @@
1
+ import { getNotificationType } from "@open-mercato/core/modules/notifications/lib/notification-type-registry";
2
+ import { fanOutPushDeliveries, PUSH_CHANNEL } from "./push-fanout.js";
3
+ import { PUSH_CHANNEL as PUSH_CHANNEL2 } from "./push-fanout.js";
4
+ const mobilePushDeliveryStrategy = {
5
+ id: PUSH_CHANNEL,
6
+ label: "Mobile push",
7
+ // Attempt push whenever a tenant has a push channel configured; the fan-out short-circuits
8
+ // (no rows, no enqueue) when push is not set up for the tenant/recipient.
9
+ defaultEnabled: true,
10
+ async deliver(ctx) {
11
+ const { notification } = ctx;
12
+ const tenantId = notification.tenantId;
13
+ const userId = notification.recipientUserId;
14
+ const organizationId = notification.organizationId ?? null;
15
+ const type = getNotificationType(notification.type);
16
+ if (!type) return;
17
+ const em = ctx.resolve("em");
18
+ const silent = type.silent === true;
19
+ const data = { ...notification.data ?? {} };
20
+ data.notificationId = notification.id;
21
+ data.type = notification.type;
22
+ if (notification.linkHref) data.linkHref = notification.linkHref;
23
+ const payload = {
24
+ data,
25
+ options: notification.pushOptions ?? void 0,
26
+ silent
27
+ };
28
+ const copy = silent ? void 0 : {
29
+ titleKey: notification.titleKey ?? null,
30
+ bodyKey: notification.bodyKey ?? null,
31
+ titleVariables: notification.titleVariables ?? null,
32
+ bodyVariables: notification.bodyVariables ?? null,
33
+ title: ctx.title,
34
+ body: ctx.body
35
+ };
36
+ await fanOutPushDeliveries({
37
+ em,
38
+ resolve: ctx.resolve,
39
+ scope: { tenantId, organizationId },
40
+ userId,
41
+ notificationId: notification.id,
42
+ notificationTypeId: notification.type,
43
+ payload,
44
+ copy
45
+ });
46
+ }
47
+ };
48
+ var push_delivery_strategy_default = mobilePushDeliveryStrategy;
49
+ export {
50
+ PUSH_CHANNEL2 as PUSH_CHANNEL,
51
+ push_delivery_strategy_default as default,
52
+ mobilePushDeliveryStrategy
53
+ };
54
+ //# sourceMappingURL=push-delivery-strategy.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/push_notifications/lib/push-delivery-strategy.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { NotificationDeliveryStrategy } from '@open-mercato/core/modules/notifications/lib/deliveryStrategies'\nimport { getNotificationType } from '@open-mercato/core/modules/notifications/lib/notification-type-registry'\nimport type { PushOptions } from '@open-mercato/core/modules/communication_channels/lib/push-envelope'\nimport type { NotificationCopySource } from '@open-mercato/core/modules/notifications/lib/notificationCopy'\nimport { fanOutPushDeliveries, PUSH_CHANNEL, type PushFanoutPayload } from './push-fanout'\n\nexport { PUSH_CHANNEL } from './push-fanout'\n\n/**\n * `push` notification delivery strategy.\n *\n * Registered into the notifications strategy seam (see notifications.delivery-strategies.ts).\n * Runs inside the persistent `notifications:deliver` subscriber for every created notification.\n * It only enqueues fast work \u2014 the actual provider send (with retry/backoff) happens in the\n * `send-push` worker, so a slow/unavailable provider never blocks notification creation.\n *\n * Whether a push is silent (content-available wake-up) is a property of the registered\n * notification TYPE (`NotificationTypeDefinition.silent`), never a per-call flag. `silent`\n * controls only HOW the device is notified (background wake-up vs visible alert); it does not\n * imply the push is non-opt-out. Silent types still respect the recipient's per-channel\n * preference unless the type is `nonOptOut` \u2014 to force a silent push, mark the type\n * `nonOptOut: true`. A silent push is therefore just a notification created through the normal\n * `notificationService.create()` flow whose type is `silent: true`; there is no separate helper.\n *\n * The shared device/channel fan-out lives in {@link fanOutPushDeliveries}.\n */\nexport const mobilePushDeliveryStrategy: NotificationDeliveryStrategy = {\n id: PUSH_CHANNEL,\n label: 'Mobile push',\n // Attempt push whenever a tenant has a push channel configured; the fan-out short-circuits\n // (no rows, no enqueue) when push is not set up for the tenant/recipient.\n defaultEnabled: true,\n async deliver(ctx) {\n const { notification } = ctx\n const tenantId = notification.tenantId\n const userId = notification.recipientUserId\n const organizationId = notification.organizationId ?? null\n\n // Skip unknown types (the catalogue is the source of truth for what can notify a user).\n const type = getNotificationType(notification.type)\n if (!type) return\n\n const em = ctx.resolve('em') as EntityManager\n const silent = type.silent === true\n\n // Per-channel opt-out / `nonOptOut` is enforced once, upstream, at create time: the dispatcher\n // only invokes this strategy when `push \u2208 notification.channels` (the resolved target snapshot).\n // `silent` still controls delivery STYLE only (content-available wake-up vs visible alert).\n\n // App-readable data payload: caller-supplied custom fields plus the system identifiers.\n const data: Record<string, string> = { ...(notification.data ?? {}) }\n data.notificationId = notification.id\n data.type = notification.type\n if (notification.linkHref) data.linkHref = notification.linkHref\n\n const payload: PushFanoutPayload = {\n data,\n options: (notification.pushOptions ?? undefined) as PushOptions | undefined,\n silent,\n }\n\n // For visible notifications, hand the raw copy down so the fan-out can translate title/body per\n // device locale. `ctx.title`/`ctx.body` are already resolved in the default locale and serve as\n // the fallback. Silent pushes carry no user-facing copy, so they skip translation entirely.\n const copy: NotificationCopySource | undefined = silent\n ? undefined\n : {\n titleKey: notification.titleKey ?? null,\n bodyKey: notification.bodyKey ?? null,\n titleVariables: notification.titleVariables ?? null,\n bodyVariables: notification.bodyVariables ?? null,\n title: ctx.title,\n body: ctx.body,\n }\n\n await fanOutPushDeliveries({\n em,\n resolve: ctx.resolve,\n scope: { tenantId, organizationId },\n userId,\n notificationId: notification.id,\n notificationTypeId: notification.type,\n payload,\n copy,\n })\n },\n}\n\nexport default mobilePushDeliveryStrategy\n"],
5
+ "mappings": "AAEA,SAAS,2BAA2B;AAGpC,SAAS,sBAAsB,oBAA4C;AAE3E,SAAS,gBAAAA,qBAAoB;AAoBtB,MAAM,6BAA2D;AAAA,EACtE,IAAI;AAAA,EACJ,OAAO;AAAA;AAAA;AAAA,EAGP,gBAAgB;AAAA,EAChB,MAAM,QAAQ,KAAK;AACjB,UAAM,EAAE,aAAa,IAAI;AACzB,UAAM,WAAW,aAAa;AAC9B,UAAM,SAAS,aAAa;AAC5B,UAAM,iBAAiB,aAAa,kBAAkB;AAGtD,UAAM,OAAO,oBAAoB,aAAa,IAAI;AAClD,QAAI,CAAC,KAAM;AAEX,UAAM,KAAK,IAAI,QAAQ,IAAI;AAC3B,UAAM,SAAS,KAAK,WAAW;AAO/B,UAAM,OAA+B,EAAE,GAAI,aAAa,QAAQ,CAAC,EAAG;AACpE,SAAK,iBAAiB,aAAa;AACnC,SAAK,OAAO,aAAa;AACzB,QAAI,aAAa,SAAU,MAAK,WAAW,aAAa;AAExD,UAAM,UAA6B;AAAA,MACjC;AAAA,MACA,SAAU,aAAa,eAAe;AAAA,MACtC;AAAA,IACF;AAKA,UAAM,OAA2C,SAC7C,SACA;AAAA,MACE,UAAU,aAAa,YAAY;AAAA,MACnC,SAAS,aAAa,WAAW;AAAA,MACjC,gBAAgB,aAAa,kBAAkB;AAAA,MAC/C,eAAe,aAAa,iBAAiB;AAAA,MAC7C,OAAO,IAAI;AAAA,MACX,MAAM,IAAI;AAAA,IACZ;AAEJ,UAAM,qBAAqB;AAAA,MACzB;AAAA,MACA,SAAS,IAAI;AAAA,MACb,OAAO,EAAE,UAAU,eAAe;AAAA,MAClC;AAAA,MACA,gBAAgB,aAAa;AAAA,MAC7B,oBAAoB,aAAa;AAAA,MACjC;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,IAAO,iCAAQ;",
6
+ "names": ["PUSH_CHANNEL"]
7
+ }
@@ -0,0 +1,297 @@
1
+ import { raw } from "@mikro-orm/core";
2
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { DEVICE_UNREGISTERED } from "@open-mercato/core/modules/communication_channels/lib/push-adapter";
4
+ import { refreshCredentialsIfNeeded } from "@open-mercato/core/modules/communication_channels/lib/credential-refresh";
5
+ import { calculateBackoffDelayMs } from "@open-mercato/shared/lib/delivery/retry";
6
+ import { createLogger } from "@open-mercato/shared/lib/logger";
7
+ import { PushNotificationDelivery } from "../data/entities.js";
8
+ import { emitPushNotificationsEvent } from "../events.js";
9
+ import { resolveStuckThresholdMs } from "./reclaim-window.js";
10
+ import { enqueuePushDelivery } from "./queue.js";
11
+ const logger = createLogger("push_notifications");
12
+ const MAX_ATTEMPTS = 3;
13
+ const DEFAULT_SEND_TIMEOUT_MS = 6e4;
14
+ const MIN_SEND_TIMEOUT_MS = 5e3;
15
+ const SEND_TIMEOUT_SAFETY_MARGIN_MS = 6e4;
16
+ function resolvePushSendTimeoutMs() {
17
+ const ceiling = Math.max(MIN_SEND_TIMEOUT_MS, resolveStuckThresholdMs() - SEND_TIMEOUT_SAFETY_MARGIN_MS);
18
+ const parsed = Number.parseInt(process.env.OM_PUSH_SEND_TIMEOUT_MS ?? "", 10);
19
+ const configured = Number.isFinite(parsed) && parsed >= MIN_SEND_TIMEOUT_MS ? parsed : DEFAULT_SEND_TIMEOUT_MS;
20
+ return Math.min(configured, ceiling);
21
+ }
22
+ function isUnregistered(result) {
23
+ return result.metadata?.unregistered === true || result.error === DEVICE_UNREGISTERED;
24
+ }
25
+ async function applyFencedTransition(em, lease, patch, fromStatus = "sending") {
26
+ const affected = await em.nativeUpdate(
27
+ PushNotificationDelivery,
28
+ { id: lease.deliveryId, tenantId: lease.tenantId, status: fromStatus, attempts: lease.attempts },
29
+ { ...patch, updatedAt: /* @__PURE__ */ new Date() }
30
+ );
31
+ return affected > 0;
32
+ }
33
+ async function finalize(em, delivery, lease, event, patch) {
34
+ const held = await applyFencedTransition(em, lease, { ...patch, nextRetryAt: null });
35
+ if (!held) {
36
+ logger.warn("Push delivery lease lost before finalize; abandoning", {
37
+ deliveryId: lease.deliveryId,
38
+ tenantId: lease.tenantId
39
+ });
40
+ return { status: delivery.status, deliveryId: delivery.id };
41
+ }
42
+ const finalStatus = patch.status ?? delivery.status;
43
+ await emitPushNotificationsEvent(
44
+ event,
45
+ {
46
+ deliveryId: delivery.id,
47
+ tenantId: delivery.tenantId,
48
+ organizationId: delivery.organizationId ?? null,
49
+ userId: delivery.userId,
50
+ provider: delivery.provider,
51
+ status: finalStatus
52
+ },
53
+ { persistent: true }
54
+ );
55
+ return { status: finalStatus, deliveryId: delivery.id };
56
+ }
57
+ async function failTerminal(em, delivery, lease, reason) {
58
+ return finalize(em, delivery, lease, "push_notifications.delivery.failed", {
59
+ status: "failed",
60
+ lastError: reason
61
+ });
62
+ }
63
+ async function handleRetryableFailure(em, delivery, lease, job, reason, providerResponse) {
64
+ if (lease.attempts < MAX_ATTEMPTS) {
65
+ const delayMs = calculateBackoffDelayMs(lease.attempts);
66
+ const held = await applyFencedTransition(em, lease, {
67
+ status: "pending",
68
+ lastError: reason,
69
+ providerResponse,
70
+ nextRetryAt: new Date(Date.now() + delayMs)
71
+ });
72
+ if (!held) {
73
+ logger.warn("Push delivery lease lost before retry; abandoning", {
74
+ deliveryId: lease.deliveryId,
75
+ tenantId: lease.tenantId
76
+ });
77
+ return { status: delivery.status, deliveryId: delivery.id };
78
+ }
79
+ try {
80
+ await enqueuePushDelivery(job, delayMs);
81
+ } catch (error) {
82
+ const failReason = error instanceof Error ? `retry_scheduling_failed: ${error.message}` : "retry_scheduling_failed";
83
+ const failed = await applyFencedTransition(
84
+ em,
85
+ lease,
86
+ { status: "failed", lastError: failReason, nextRetryAt: null },
87
+ "pending"
88
+ );
89
+ if (failed) {
90
+ await emitPushNotificationsEvent(
91
+ "push_notifications.delivery.failed",
92
+ {
93
+ deliveryId: delivery.id,
94
+ tenantId: delivery.tenantId,
95
+ organizationId: delivery.organizationId ?? null,
96
+ userId: delivery.userId,
97
+ provider: delivery.provider,
98
+ status: "failed"
99
+ },
100
+ { persistent: true }
101
+ );
102
+ }
103
+ return { status: "failed", deliveryId: delivery.id };
104
+ }
105
+ await emitPushNotificationsEvent(
106
+ "push_notifications.delivery.failed",
107
+ {
108
+ deliveryId: delivery.id,
109
+ tenantId: delivery.tenantId,
110
+ organizationId: delivery.organizationId ?? null,
111
+ userId: delivery.userId,
112
+ provider: delivery.provider,
113
+ // The row is reset to `pending` to release the claim for the re-enqueued attempt, but the
114
+ // logical outcome of THIS attempt is "failed, retry scheduled". Emit `retrying` (not the reset
115
+ // row status `pending`) so subscribers keying off `status` aren't misled; `willRetry` still gates
116
+ // ultimate-failure counters.
117
+ status: "retrying",
118
+ willRetry: true
119
+ },
120
+ { persistent: true }
121
+ );
122
+ return { status: "pending", deliveryId: delivery.id };
123
+ }
124
+ return finalize(em, delivery, lease, "push_notifications.delivery.failed", {
125
+ status: "expired",
126
+ lastError: reason,
127
+ providerResponse
128
+ });
129
+ }
130
+ async function softDeleteUnregisteredDevice(resolve, input) {
131
+ try {
132
+ const commandBus = resolve("commandBus");
133
+ await commandBus.execute("devices.user_devices.deactivate", {
134
+ input,
135
+ ctx: {
136
+ container: { resolve },
137
+ auth: null,
138
+ organizationScope: null,
139
+ selectedOrganizationId: input.organizationId,
140
+ organizationIds: input.organizationId ? [input.organizationId] : null,
141
+ systemActor: true
142
+ }
143
+ });
144
+ } catch (error) {
145
+ logger.error("Failed to deactivate unregistered device", {
146
+ userDeviceId: input.id,
147
+ error: error instanceof Error ? error.message : String(error)
148
+ });
149
+ }
150
+ }
151
+ async function sendWithTimeout(adapter, input, timeoutMs) {
152
+ let timer;
153
+ const timeout = new Promise((resolve) => {
154
+ timer = setTimeout(() => resolve({ externalMessageId: "", status: "failed", error: "send_timeout" }), timeoutMs);
155
+ });
156
+ const send = adapter.sendMessage(input);
157
+ send.catch(() => {
158
+ });
159
+ try {
160
+ return await Promise.race([send, timeout]);
161
+ } finally {
162
+ if (timer) clearTimeout(timer);
163
+ }
164
+ }
165
+ async function processPushDeliveryJob(em, job, resolve) {
166
+ const claimed = await em.nativeUpdate(
167
+ PushNotificationDelivery,
168
+ { id: job.deliveryId, tenantId: job.tenantId, status: "pending" },
169
+ { status: "sending", attempts: raw('"attempts" + 1'), updatedAt: /* @__PURE__ */ new Date() }
170
+ );
171
+ const delivery = await em.findOne(
172
+ PushNotificationDelivery,
173
+ { id: job.deliveryId, tenantId: job.tenantId },
174
+ { refresh: true }
175
+ );
176
+ if (!delivery) return null;
177
+ if (claimed === 0) return { status: delivery.status, deliveryId: delivery.id };
178
+ const lease = { deliveryId: delivery.id, tenantId: delivery.tenantId, attempts: delivery.attempts };
179
+ const DeviceRef = resolve("UserDevice");
180
+ const device = await findOneWithDecryption(
181
+ em,
182
+ DeviceRef,
183
+ { id: delivery.userDeviceId, tenantId: job.tenantId, deletedAt: null },
184
+ void 0,
185
+ { tenantId: job.tenantId, organizationId: job.organizationId ?? null }
186
+ );
187
+ if (!device || !device.pushToken) {
188
+ const held = await applyFencedTransition(em, lease, {
189
+ status: "skipped",
190
+ lastError: "device_unavailable",
191
+ nextRetryAt: null
192
+ });
193
+ return { status: held ? "skipped" : delivery.status, deliveryId: delivery.id };
194
+ }
195
+ const ChannelRef = resolve("CommunicationChannel");
196
+ const channel = await em.findOne(ChannelRef, {
197
+ tenantId: job.tenantId,
198
+ providerKey: delivery.provider,
199
+ channelType: "push",
200
+ isActive: true,
201
+ deletedAt: null
202
+ });
203
+ if (!channel) return failTerminal(em, delivery, lease, "channel_unavailable");
204
+ const registry = resolve("channelAdapterRegistry");
205
+ const adapter = registry?.get(delivery.provider);
206
+ if (!adapter) return failTerminal(em, delivery, lease, "no_adapter");
207
+ let credentialsService;
208
+ try {
209
+ credentialsService = resolve("integrationCredentialsService");
210
+ } catch {
211
+ credentialsService = void 0;
212
+ }
213
+ const credentialScope = {
214
+ tenantId: job.tenantId,
215
+ organizationId: channel.organizationId ?? job.tenantId,
216
+ userId: channel.userId ?? null
217
+ };
218
+ let credentials = {};
219
+ if (channel.credentialsRef && credentialsService) {
220
+ credentials = await credentialsService.resolve(`channel_${delivery.provider}`, credentialScope).catch(() => null) ?? {};
221
+ }
222
+ try {
223
+ const refreshed = await refreshCredentialsIfNeeded(
224
+ { adapter, channelId: channel.id, credentials, scope: credentialScope },
225
+ { credentialsService: credentialsService ?? null }
226
+ );
227
+ credentials = refreshed.credentials;
228
+ } catch (error) {
229
+ logger.error("Credential refresh failed; sending with current credentials", {
230
+ deliveryId: delivery.id,
231
+ provider: delivery.provider,
232
+ error: error instanceof Error ? error.message : String(error)
233
+ });
234
+ }
235
+ const scope = { tenantId: job.tenantId, organizationId: job.organizationId ?? job.tenantId };
236
+ const payload = delivery.payload ?? {};
237
+ const body = payload.body ?? payload.title ?? "";
238
+ try {
239
+ const converted = await adapter.convertOutbound({ body, bodyFormat: "text" });
240
+ const result = await sendWithTimeout(
241
+ adapter,
242
+ {
243
+ content: {
244
+ ...converted.content,
245
+ raw: {
246
+ title: payload.title ?? "",
247
+ body: payload.body ?? null,
248
+ data: payload.data ?? {},
249
+ options: payload.options ?? {},
250
+ silent: payload.silent === true
251
+ }
252
+ },
253
+ credentials,
254
+ scope,
255
+ metadata: {
256
+ pushToken: device.pushToken,
257
+ platform: device.platform,
258
+ userDeviceId: device.id,
259
+ provider: delivery.provider
260
+ }
261
+ },
262
+ resolvePushSendTimeoutMs()
263
+ );
264
+ if (result.status === "sent" || result.status === "queued") {
265
+ return finalize(em, delivery, lease, "push_notifications.delivery.sent", {
266
+ status: "sent",
267
+ sentAt: /* @__PURE__ */ new Date(),
268
+ lastError: null,
269
+ providerResponse: result.metadata ?? { externalMessageId: result.externalMessageId }
270
+ });
271
+ }
272
+ if (isUnregistered(result)) {
273
+ const outcome = await finalize(em, delivery, lease, "push_notifications.delivery.failed", {
274
+ status: "failed",
275
+ lastError: DEVICE_UNREGISTERED,
276
+ providerResponse: result.metadata ?? null
277
+ });
278
+ await softDeleteUnregisteredDevice(resolve, {
279
+ id: device.id,
280
+ tenantId: device.tenantId,
281
+ userId: device.userId,
282
+ organizationId: device.organizationId ?? null
283
+ });
284
+ return outcome;
285
+ }
286
+ return handleRetryableFailure(em, delivery, lease, job, result.error ?? "send_failed", result.metadata ?? null);
287
+ } catch (error) {
288
+ const message = error instanceof Error ? error.message : "send_failed";
289
+ return handleRetryableFailure(em, delivery, lease, job, message, null);
290
+ }
291
+ }
292
+ export {
293
+ MAX_ATTEMPTS,
294
+ processPushDeliveryJob,
295
+ softDeleteUnregisteredDevice
296
+ };
297
+ //# sourceMappingURL=push-delivery.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/push_notifications/lib/push-delivery.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { raw, type EntityName } from '@mikro-orm/core'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport type { ChannelAdapter, SendMessageResult } from '@open-mercato/core/modules/communication_channels/lib/adapter'\nimport { DEVICE_UNREGISTERED } from '@open-mercato/core/modules/communication_channels/lib/push-adapter'\nimport { refreshCredentialsIfNeeded } from '@open-mercato/core/modules/communication_channels/lib/credential-refresh'\nimport type { CommunicationChannel } from '@open-mercato/core/modules/communication_channels/data/entities'\nimport type { PushOptions } from '@open-mercato/core/modules/communication_channels/lib/push-envelope'\nimport type { UserDevice } from '@open-mercato/core/modules/devices/data/entities'\nimport { calculateBackoffDelayMs } from '@open-mercato/shared/lib/delivery/retry'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { PushNotificationDelivery } from '../data/entities'\nimport { emitPushNotificationsEvent } from '../events'\nimport { resolveStuckThresholdMs } from './reclaim-window'\nimport { enqueuePushDelivery, type PushDeliveryJob } from './queue'\n\nconst logger = createLogger('push_notifications')\n\nexport const MAX_ATTEMPTS = 3\n\n// Bound the worker's wait on a single provider send. If a send outlived the stuck-reclaim window the\n// reaper would reclaim the row mid-send and a second worker would re-send it, so the wait is capped\n// well under that window (see ./reclaim-window). Configurable via OM_PUSH_SEND_TIMEOUT_MS; clamped to\n// stay below the reclaim window regardless of the configured value.\n//\n// NOTE: without an AbortSignal through the adapter contract this bounds the WORKER's wait, not the\n// underlying socket \u2014 a black-holed SDK call may still complete later, but the lease stays fresh (the\n// worker finalizes/retries within the window) so the reaper never steals it and fenced write-backs keep\n// terminal state consistent. A timeout is surfaced as a retryable `send_timeout` failure.\nconst DEFAULT_SEND_TIMEOUT_MS = 60_000\nconst MIN_SEND_TIMEOUT_MS = 5_000\nconst SEND_TIMEOUT_SAFETY_MARGIN_MS = 60_000\n\nfunction resolvePushSendTimeoutMs(): number {\n const ceiling = Math.max(MIN_SEND_TIMEOUT_MS, resolveStuckThresholdMs() - SEND_TIMEOUT_SAFETY_MARGIN_MS)\n const parsed = Number.parseInt(process.env.OM_PUSH_SEND_TIMEOUT_MS ?? '', 10)\n const configured = Number.isFinite(parsed) && parsed >= MIN_SEND_TIMEOUT_MS ? parsed : DEFAULT_SEND_TIMEOUT_MS\n return Math.min(configured, ceiling)\n}\n\ntype Resolve = <T = unknown>(name: string) => T\n\ninterface ChannelAdapterRegistryLike {\n get(providerKey: string): ChannelAdapter | undefined\n}\n\ninterface CredentialsServiceLike {\n resolve(\n integrationId: string,\n scope: { tenantId: string; organizationId: string; userId?: string | null },\n ): Promise<Record<string, unknown> | null>\n}\n\ntype PushPayload = {\n title?: string\n body?: string | null\n data?: Record<string, string>\n options?: PushOptions\n silent?: boolean\n}\n\ntype ProcessResult = { status: PushNotificationDelivery['status']; deliveryId: string } | null\n\n// Identifies the exact lease a worker holds on a delivery row. `attempts` is the lease-generation token:\n// it is incremented in the claim itself, and the reaper resets `sending`\u2192`pending` WITHOUT touching it,\n// so the next worker's claim bumps it again. Every write-back is fenced on `(status='sending', attempts)`\n// \u2014 if the reaper broke this lease and another worker re-claimed, `attempts` has moved on and the fenced\n// update matches 0 rows, so this worker abandons instead of clobbering the new owner's state.\ntype DeliveryLease = { deliveryId: string; tenantId: string; attempts: number }\n\n// The `unregistered` sentinel is shared (`deviceUnregisteredResult` / `DEVICE_UNREGISTERED`) across the\n// fcm/apns/expo adapters + the stub, so the device soft-delete below fires uniformly regardless of provider.\nfunction isUnregistered(result: SendMessageResult): boolean {\n return result.metadata?.unregistered === true || result.error === DEVICE_UNREGISTERED\n}\n\n// Apply a delivery state transition as a fenced `nativeUpdate` (never `em.flush()`, so a stale\n// identity-map copy can never issue an unfenced `UPDATE ... WHERE id`). Returns `true` iff this worker\n// still held the lease and the write landed; `false` means the lease was lost (reaper reclaimed it) and\n// the caller MUST abandon \u2014 no re-enqueue, no event \u2014 to avoid corrupting the new owner's state.\nasync function applyFencedTransition(\n em: EntityManager,\n lease: DeliveryLease,\n patch: Record<string, unknown>,\n fromStatus: PushNotificationDelivery['status'] = 'sending',\n): Promise<boolean> {\n const affected = await em.nativeUpdate(\n PushNotificationDelivery,\n { id: lease.deliveryId, tenantId: lease.tenantId, status: fromStatus, attempts: lease.attempts },\n { ...patch, updatedAt: new Date() },\n )\n return affected > 0\n}\n\nasync function finalize(\n em: EntityManager,\n delivery: PushNotificationDelivery,\n lease: DeliveryLease,\n event: 'push_notifications.delivery.sent' | 'push_notifications.delivery.failed',\n patch: Record<string, unknown>,\n): Promise<ProcessResult> {\n // A terminal row never has a pending retry scheduled.\n const held = await applyFencedTransition(em, lease, { ...patch, nextRetryAt: null })\n if (!held) {\n // Lease lost \u2014 the reaper reclaimed this row and another actor now owns it. Emitting or re-enqueueing\n // here would corrupt the new owner's terminal state, so abandon quietly.\n logger.warn('Push delivery lease lost before finalize; abandoning', {\n deliveryId: lease.deliveryId,\n tenantId: lease.tenantId,\n })\n return { status: delivery.status, deliveryId: delivery.id }\n }\n const finalStatus = (patch.status as PushNotificationDelivery['status'] | undefined) ?? delivery.status\n await emitPushNotificationsEvent(\n event,\n {\n deliveryId: delivery.id,\n tenantId: delivery.tenantId,\n organizationId: delivery.organizationId ?? null,\n userId: delivery.userId,\n provider: delivery.provider,\n status: finalStatus,\n },\n { persistent: true },\n )\n return { status: finalStatus, deliveryId: delivery.id }\n}\n\nasync function failTerminal(\n em: EntityManager,\n delivery: PushNotificationDelivery,\n lease: DeliveryLease,\n reason: string,\n): Promise<ProcessResult> {\n return finalize(em, delivery, lease, 'push_notifications.delivery.failed', {\n status: 'failed',\n lastError: reason,\n })\n}\n\nasync function handleRetryableFailure(\n em: EntityManager,\n delivery: PushNotificationDelivery,\n lease: DeliveryLease,\n job: PushDeliveryJob,\n reason: string,\n providerResponse: Record<string, unknown> | null,\n): Promise<ProcessResult> {\n // `lease.attempts` is the count already persisted at claim time, so it is the real cap on provider\n // sends (each claim = one send attempt). Compare against it, not a stale in-memory field.\n if (lease.attempts < MAX_ATTEMPTS) {\n // Release the claim (`pending`) and re-enqueue with exponential backoff + jitter; per-delivery\n // isolation. Jitter avoids a thundering herd when a provider outage fails many deliveries at once.\n const delayMs = calculateBackoffDelayMs(lease.attempts)\n const held = await applyFencedTransition(em, lease, {\n status: 'pending',\n lastError: reason,\n providerResponse,\n nextRetryAt: new Date(Date.now() + delayMs),\n })\n if (!held) {\n // Lease lost mid-flight (reaper reclaimed + another worker re-owns it). Do NOT re-enqueue \u2014 that\n // would rewind the new owner's attempt counter and schedule a duplicate job.\n logger.warn('Push delivery lease lost before retry; abandoning', {\n deliveryId: lease.deliveryId,\n tenantId: lease.tenantId,\n })\n return { status: delivery.status, deliveryId: delivery.id }\n }\n try {\n await enqueuePushDelivery(job, delayMs)\n } catch (error) {\n // The row is now `pending` under our lease; fence the terminal failure on that state. If the\n // reaper already grabbed it (it will not \u2014 we just stamped updated_at), the write no-ops.\n const failReason = error instanceof Error ? `retry_scheduling_failed: ${error.message}` : 'retry_scheduling_failed'\n const failed = await applyFencedTransition(\n em,\n lease,\n { status: 'failed', lastError: failReason, nextRetryAt: null },\n 'pending',\n )\n if (failed) {\n await emitPushNotificationsEvent(\n 'push_notifications.delivery.failed',\n {\n deliveryId: delivery.id,\n tenantId: delivery.tenantId,\n organizationId: delivery.organizationId ?? null,\n userId: delivery.userId,\n provider: delivery.provider,\n status: 'failed',\n },\n { persistent: true },\n )\n }\n return { status: 'failed', deliveryId: delivery.id }\n }\n await emitPushNotificationsEvent(\n 'push_notifications.delivery.failed',\n {\n deliveryId: delivery.id,\n tenantId: delivery.tenantId,\n organizationId: delivery.organizationId ?? null,\n userId: delivery.userId,\n provider: delivery.provider,\n // The row is reset to `pending` to release the claim for the re-enqueued attempt, but the\n // logical outcome of THIS attempt is \"failed, retry scheduled\". Emit `retrying` (not the reset\n // row status `pending`) so subscribers keying off `status` aren't misled; `willRetry` still gates\n // ultimate-failure counters.\n status: 'retrying',\n willRetry: true,\n },\n { persistent: true },\n )\n return { status: 'pending', deliveryId: delivery.id }\n }\n // Retries exhausted: distinct terminal `expired` status (vs `failed` for terminal errors), so the\n // admin log can tell \"gave up after N attempts\" from \"channel unavailable / no adapter\".\n return finalize(em, delivery, lease, 'push_notifications.delivery.failed', {\n status: 'expired',\n lastError: reason,\n providerResponse,\n })\n}\n\n// Soft-delete the device through the devices module's own command (audit/events/undo stay\n// consistent) rather than mutating its table directly. Dispatched by command id with a trusted\n// system context \u2014 no business-logic import, no authenticated actor.\nexport async function softDeleteUnregisteredDevice(\n resolve: Resolve,\n input: { id: string; tenantId: string; userId: string; organizationId: string | null },\n): Promise<void> {\n try {\n const commandBus = resolve('commandBus') as CommandBus\n await commandBus.execute('devices.user_devices.deactivate', {\n input,\n ctx: {\n container: { resolve } as never,\n auth: null,\n organizationScope: null,\n selectedOrganizationId: input.organizationId,\n organizationIds: input.organizationId ? [input.organizationId] : null,\n systemActor: true,\n } as never,\n })\n } catch (error) {\n logger.error('Failed to deactivate unregistered device', {\n userDeviceId: input.id,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n}\n\n// Bound the wait on a single provider send (B3a): a hung/black-holed call must not outlive the\n// stuck-reclaim window, or the reaper would reclaim the row mid-send and a second worker would re-send.\n// On timeout we return a retryable `send_timeout` result and move on; the underlying SDK promise is left\n// to settle on its own (its late rejection is swallowed to avoid an unhandled rejection).\nasync function sendWithTimeout(\n adapter: ChannelAdapter,\n input: Parameters<ChannelAdapter['sendMessage']>[0],\n timeoutMs: number,\n): Promise<SendMessageResult> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<SendMessageResult>((resolve) => {\n timer = setTimeout(() => resolve({ externalMessageId: '', status: 'failed', error: 'send_timeout' }), timeoutMs)\n })\n const send = adapter.sendMessage(input)\n send.catch(() => {})\n try {\n return await Promise.race([send, timeout])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/**\n * Send one push delivery row through the communication_channels hub.\n *\n * Idempotent on the delivery id: the row is claimed atomically, so a redelivered job that cannot win\n * the claim (already `sending`/terminal) is a no-op. Mirrors the `test-send` route flow \u2014 resolve\n * channel \u2192 adapter \u2192 credentials \u2192 convertOutbound \u2192 sendMessage \u2014 but adds delivery-row\n * bookkeeping, retry/backoff, and device soft-delete on `unregistered`.\n *\n * Lease fencing (B3): the claim increments `attempts` atomically, and every post-claim state write goes\n * through `applyFencedTransition`, fenced on `(status='sending', attempts=<claimed>)`. If the reaper\n * broke this worker's lease and another worker re-claimed the row (bumping `attempts`), this worker's\n * writes match 0 rows and it abandons \u2014 so a stolen lease can neither rewind the attempt counter nor\n * overwrite the new owner's terminal state.\n */\nexport async function processPushDeliveryJob(\n em: EntityManager,\n job: PushDeliveryJob,\n resolve: Resolve,\n): Promise<ProcessResult> {\n // Atomically claim the row (`pending` \u2192 `sending`) so a redelivered/duplicated job \u2014 inherent to\n // at-least-once queues \u2014 is processed by exactly one worker. Only the worker whose update wins the\n // race proceeds; the loser (or a non-pending/terminal row) is a no-op. Mirrors the `messages`\n // send-email claim pattern, which is stronger than a plain read-then-check status guard.\n // Count the attempt AT CLAIM time, in the same atomic update. `attempts` doubles as the lease token\n // (see DeliveryLease): each claim increments it, the reaper never touches it, so it uniquely tags this\n // lease generation. Persisting it before the send makes MAX_ATTEMPTS a real cap on provider sends\n // (a crash after the send can't lose the increment). Only the worker whose update wins the race\n // proceeds; the loser (or a non-pending/terminal row) is a no-op.\n const claimed = await em.nativeUpdate(\n PushNotificationDelivery,\n { id: job.deliveryId, tenantId: job.tenantId, status: 'pending' },\n { status: 'sending', attempts: raw('\"attempts\" + 1'), updatedAt: new Date() },\n )\n // Fresh read (forked EM per job \u2192 empty identity map \u2192 hits DB) so `attempts` reflects the increment.\n const delivery = await em.findOne(\n PushNotificationDelivery,\n { id: job.deliveryId, tenantId: job.tenantId },\n { refresh: true },\n )\n if (!delivery) return null\n if (claimed === 0) return { status: delivery.status, deliveryId: delivery.id }\n\n const lease: DeliveryLease = { deliveryId: delivery.id, tenantId: delivery.tenantId, attempts: delivery.attempts }\n\n // Resolve the device for its full (secret) push token. Soft via DI token to avoid coupling.\n // `push_token` is encrypted at rest, so decrypt on read (no-op when encryption is disabled).\n const DeviceRef = resolve('UserDevice') as EntityName<UserDevice>\n const device = await findOneWithDecryption(\n em,\n DeviceRef,\n { id: delivery.userDeviceId, tenantId: job.tenantId, deletedAt: null },\n undefined,\n { tenantId: job.tenantId, organizationId: job.organizationId ?? null },\n )\n if (!device || !device.pushToken) {\n const held = await applyFencedTransition(em, lease, {\n status: 'skipped',\n lastError: 'device_unavailable',\n nextRetryAt: null,\n })\n return { status: held ? 'skipped' : delivery.status, deliveryId: delivery.id }\n }\n\n // Resolve the tenant's push channel matching the snapshotted provider.\n const ChannelRef = resolve('CommunicationChannel') as EntityName<CommunicationChannel>\n const channel = await em.findOne(ChannelRef, {\n tenantId: job.tenantId,\n providerKey: delivery.provider,\n channelType: 'push',\n isActive: true,\n deletedAt: null,\n })\n if (!channel) return failTerminal(em, delivery, lease, 'channel_unavailable')\n\n const registry = resolve('channelAdapterRegistry') as ChannelAdapterRegistryLike | undefined\n const adapter = registry?.get(delivery.provider)\n // No provider package registered this provider's adapter (e.g. channel-fcm/apns/expo not installed).\n // Terminal, no retry.\n if (!adapter) return failTerminal(em, delivery, lease, 'no_adapter')\n\n // Resolve credentials (best-effort, mirrors test-send).\n let credentialsService: CredentialsServiceLike | undefined\n try {\n credentialsService = resolve('integrationCredentialsService') as CredentialsServiceLike | undefined\n } catch {\n credentialsService = undefined\n }\n // Credentials are keyed by the CHANNEL's org context, not the notification's: at connect time they\n // are stored under `channel.organizationId ?? tenantId` (see communication_channels\n // connect-credential-channel). Using the notification's `job.organizationId` here would miss the\n // creds whenever a tenant-level (org-less) channel serves an org-scoped notification.\n const credentialScope = {\n tenantId: job.tenantId,\n organizationId: channel.organizationId ?? job.tenantId,\n userId: channel.userId ?? null,\n }\n let credentials: Record<string, unknown> = {}\n if (channel.credentialsRef && credentialsService) {\n credentials = (await credentialsService.resolve(`channel_${delivery.provider}`, credentialScope).catch(() => null)) ?? {}\n }\n\n // Mirror the test-send flow: refresh near-expiry OAuth credentials before sending so providers with\n // short-lived access tokens (FCM/APNs in Phase 4) don't fail with a stale token. No-op for adapters\n // without `refreshCredentials` (e.g. the push_stub). Best-effort \u2014 on failure we keep current creds.\n try {\n const refreshed = await refreshCredentialsIfNeeded(\n { adapter, channelId: channel.id, credentials, scope: credentialScope },\n { credentialsService: credentialsService ?? null },\n )\n credentials = refreshed.credentials\n } catch (error) {\n logger.error('Credential refresh failed; sending with current credentials', {\n deliveryId: delivery.id,\n provider: delivery.provider,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n const scope = { tenantId: job.tenantId, organizationId: job.organizationId ?? job.tenantId }\n const payload = (delivery.payload ?? {}) as PushPayload\n const body = payload.body ?? payload.title ?? ''\n\n try {\n const converted = await adapter.convertOutbound({ body, bodyFormat: 'text' })\n const result = await sendWithTimeout(\n adapter,\n {\n content: {\n ...converted.content,\n raw: {\n title: payload.title ?? '',\n body: payload.body ?? null,\n data: payload.data ?? {},\n options: payload.options ?? {},\n silent: payload.silent === true,\n },\n },\n credentials,\n scope,\n metadata: {\n pushToken: device.pushToken,\n platform: device.platform,\n userDeviceId: device.id,\n provider: delivery.provider,\n },\n },\n resolvePushSendTimeoutMs(),\n )\n\n if (result.status === 'sent' || result.status === 'queued') {\n return finalize(em, delivery, lease, 'push_notifications.delivery.sent', {\n status: 'sent',\n sentAt: new Date(),\n lastError: null,\n providerResponse: result.metadata ?? { externalMessageId: result.externalMessageId },\n })\n }\n\n if (isUnregistered(result)) {\n const outcome = await finalize(em, delivery, lease, 'push_notifications.delivery.failed', {\n status: 'failed',\n lastError: DEVICE_UNREGISTERED,\n providerResponse: result.metadata ?? null,\n })\n await softDeleteUnregisteredDevice(resolve, {\n id: device.id,\n tenantId: device.tenantId,\n userId: device.userId,\n organizationId: device.organizationId ?? null,\n })\n return outcome\n }\n\n return handleRetryableFailure(em, delivery, lease, job, result.error ?? 'send_failed', result.metadata ?? null)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'send_failed'\n return handleRetryableFailure(em, delivery, lease, job, message, null)\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }