@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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/notification/index.js +6 -0
- package/dist/generated/entities/notification/index.js.map +2 -2
- package/dist/generated/entities/notification_preference/index.js +19 -0
- package/dist/generated/entities/notification_preference/index.js.map +7 -0
- package/dist/generated/entities/notification_type/index.js +21 -0
- package/dist/generated/entities/notification_type/index.js.map +7 -0
- package/dist/generated/entities/notification_type_override/index.js +17 -0
- package/dist/generated/entities/notification_type_override/index.js.map +7 -0
- package/dist/generated/entities/push_notification_delivery/index.js +41 -0
- package/dist/generated/entities/push_notification_delivery/index.js.map +7 -0
- package/dist/generated/entities/user_device/index.js +35 -0
- package/dist/generated/entities/user_device/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +12 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/api.js +2 -1
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/appRoot.js +9 -0
- package/dist/helpers/integration/appRoot.js.map +7 -0
- package/dist/helpers/integration/dbFixtures.js +1 -4
- package/dist/helpers/integration/dbFixtures.js.map +2 -2
- package/dist/helpers/integration/pushFake.js +126 -0
- package/dist/helpers/integration/pushFake.js.map +7 -0
- package/dist/modules/auth/notifications.js +16 -0
- package/dist/modules/auth/notifications.js.map +2 -2
- package/dist/modules/business_rules/notifications.js +2 -0
- package/dist/modules/business_rules/notifications.js.map +2 -2
- package/dist/modules/catalog/notifications.js +2 -0
- package/dist/modules/catalog/notifications.js.map +2 -2
- package/dist/modules/communication_channels/acl.js +9 -0
- package/dist/modules/communication_channels/acl.js.map +2 -2
- package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js +133 -0
- package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js.map +7 -0
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +6 -2
- package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +2 -2
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js +6 -2
- package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +2 -2
- package/dist/modules/communication_channels/api/get/channels/route.js +2 -1
- package/dist/modules/communication_channels/api/get/channels/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +10 -5
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +29 -1
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +157 -0
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +7 -0
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +8 -1
- package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/admin-delete-channel.js +141 -0
- package/dist/modules/communication_channels/commands/admin-delete-channel.js.map +7 -0
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +25 -8
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/commands/push-register.js +1 -1
- package/dist/modules/communication_channels/commands/push-register.js.map +2 -2
- package/dist/modules/communication_channels/commands/push-unregister.js +4 -3
- package/dist/modules/communication_channels/commands/push-unregister.js.map +2 -2
- package/dist/modules/communication_channels/data/entities.js +4 -0
- package/dist/modules/communication_channels/data/entities.js.map +2 -2
- package/dist/modules/communication_channels/data/validators.js +2 -1
- package/dist/modules/communication_channels/data/validators.js.map +2 -2
- package/dist/modules/communication_channels/lib/access-control.js +11 -1
- package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
- package/dist/modules/communication_channels/lib/connect-channel.js +20 -12
- package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
- package/dist/modules/communication_channels/lib/push-adapter.js +68 -0
- package/dist/modules/communication_channels/lib/push-adapter.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-capabilities.js +31 -0
- package/dist/modules/communication_channels/lib/push-capabilities.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-connect-error.js +20 -0
- package/dist/modules/communication_channels/lib/push-connect-error.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-credential-errors.js +41 -0
- package/dist/modules/communication_channels/lib/push-credential-errors.js.map +7 -0
- package/dist/modules/communication_channels/lib/push-envelope.js +46 -0
- package/dist/modules/communication_channels/lib/push-envelope.js.map +7 -0
- package/dist/modules/communication_channels/lib/refcounted-client-cache.js +64 -0
- package/dist/modules/communication_channels/lib/refcounted-client-cache.js.map +7 -0
- package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js +13 -0
- package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js.map +7 -0
- package/dist/modules/communication_channels/notifications.js +4 -0
- package/dist/modules/communication_channels/notifications.js.map +2 -2
- package/dist/modules/communication_channels/setup.js +2 -0
- package/dist/modules/communication_channels/setup.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +21 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/notifications.js +12 -0
- package/dist/modules/customer_accounts/notifications.js.map +2 -2
- package/dist/modules/customers/notifications.js +4 -0
- package/dist/modules/customers/notifications.js.map +2 -2
- package/dist/modules/devices/acl.js +11 -0
- package/dist/modules/devices/acl.js.map +7 -0
- package/dist/modules/devices/api/[id]/route.js +167 -0
- package/dist/modules/devices/api/[id]/route.js.map +7 -0
- package/dist/modules/devices/api/admin/devices/[id]/route.js +219 -0
- package/dist/modules/devices/api/admin/devices/[id]/route.js.map +7 -0
- package/dist/modules/devices/api/admin/devices/route.js +140 -0
- package/dist/modules/devices/api/admin/devices/route.js.map +7 -0
- package/dist/modules/devices/api/auth.js +9 -0
- package/dist/modules/devices/api/auth.js.map +7 -0
- package/dist/modules/devices/api/deviceList.js +53 -0
- package/dist/modules/devices/api/deviceList.js.map +7 -0
- package/dist/modules/devices/api/deviceOps.js +118 -0
- package/dist/modules/devices/api/deviceOps.js.map +7 -0
- package/dist/modules/devices/api/openapi.js +19 -0
- package/dist/modules/devices/api/openapi.js.map +7 -0
- package/dist/modules/devices/api/route.js +143 -0
- package/dist/modules/devices/api/route.js.map +7 -0
- package/dist/modules/devices/backend/devices/[id]/page.js +135 -0
- package/dist/modules/devices/backend/devices/[id]/page.js.map +7 -0
- package/dist/modules/devices/backend/devices/[id]/page.meta.js +16 -0
- package/dist/modules/devices/backend/devices/[id]/page.meta.js.map +7 -0
- package/dist/modules/devices/backend/devices/create/page.js +68 -0
- package/dist/modules/devices/backend/devices/create/page.js.map +7 -0
- package/dist/modules/devices/backend/devices/create/page.meta.js +16 -0
- package/dist/modules/devices/backend/devices/create/page.meta.js.map +7 -0
- package/dist/modules/devices/backend/devices/page.js +235 -0
- package/dist/modules/devices/backend/devices/page.js.map +7 -0
- package/dist/modules/devices/backend/devices/page.meta.js +23 -0
- package/dist/modules/devices/backend/devices/page.meta.js.map +7 -0
- package/dist/modules/devices/commands/deactivate.js +107 -0
- package/dist/modules/devices/commands/deactivate.js.map +7 -0
- package/dist/modules/devices/commands/index.js +4 -0
- package/dist/modules/devices/commands/index.js.map +7 -0
- package/dist/modules/devices/commands/register.js +183 -0
- package/dist/modules/devices/commands/register.js.map +7 -0
- package/dist/modules/devices/commands/shared.js +74 -0
- package/dist/modules/devices/commands/shared.js.map +7 -0
- package/dist/modules/devices/commands/update.js +126 -0
- package/dist/modules/devices/commands/update.js.map +7 -0
- package/dist/modules/devices/data/entities.js +83 -0
- package/dist/modules/devices/data/entities.js.map +7 -0
- package/dist/modules/devices/data/validators.js +49 -0
- package/dist/modules/devices/data/validators.js.map +7 -0
- package/dist/modules/devices/di.js +11 -0
- package/dist/modules/devices/di.js.map +7 -0
- package/dist/modules/devices/encryption.js +12 -0
- package/dist/modules/devices/encryption.js.map +7 -0
- package/dist/modules/devices/events.js +19 -0
- package/dist/modules/devices/events.js.map +7 -0
- package/dist/modules/devices/index.js +17 -0
- package/dist/modules/devices/index.js.map +7 -0
- package/dist/modules/devices/lib/operationMetadata.js +20 -0
- package/dist/modules/devices/lib/operationMetadata.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260602120000.js +15 -0
- package/dist/modules/devices/migrations/Migration20260602120000.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260630120000.js +15 -0
- package/dist/modules/devices/migrations/Migration20260630120000.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260701120000.js +13 -0
- package/dist/modules/devices/migrations/Migration20260701120000.js.map +7 -0
- package/dist/modules/devices/migrations/Migration20260721120000.js +15 -0
- package/dist/modules/devices/migrations/Migration20260721120000.js.map +7 -0
- package/dist/modules/devices/setup.js +13 -0
- package/dist/modules/devices/setup.js.map +7 -0
- package/dist/modules/entities/cli.js +8 -3
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/entities/migrations/Migration20260722120000.js +28 -0
- package/dist/modules/entities/migrations/Migration20260722120000.js.map +7 -0
- package/dist/modules/inbox_ops/notifications.js +2 -0
- package/dist/modules/inbox_ops/notifications.js.map +2 -2
- package/dist/modules/messages/notifications.js +2 -0
- package/dist/modules/messages/notifications.js.map +2 -2
- package/dist/modules/notifications/acl.js +3 -1
- package/dist/modules/notifications/acl.js.map +2 -2
- package/dist/modules/notifications/api/admin/preferences/route.js +167 -0
- package/dist/modules/notifications/api/admin/preferences/route.js.map +7 -0
- package/dist/modules/notifications/api/channels/route.js +54 -0
- package/dist/modules/notifications/api/channels/route.js.map +7 -0
- package/dist/modules/notifications/api/preferences/route.js +126 -0
- package/dist/modules/notifications/api/preferences/route.js.map +7 -0
- package/dist/modules/notifications/api/route.js +8 -1
- package/dist/modules/notifications/api/route.js.map +2 -2
- package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js +187 -0
- package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js.map +7 -0
- package/dist/modules/notifications/api/types/route.js +225 -0
- package/dist/modules/notifications/api/types/route.js.map +7 -0
- package/dist/modules/notifications/api/unread-count/route.js +5 -1
- package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
- package/dist/modules/notifications/backend/notifications/user-preferences/page.js +10 -0
- package/dist/modules/notifications/backend/notifications/user-preferences/page.js.map +7 -0
- package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js +28 -0
- package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js.map +7 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.js +10 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.js.map +7 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +33 -0
- package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +7 -0
- package/dist/modules/notifications/data/entities.js +128 -1
- package/dist/modules/notifications/data/entities.js.map +2 -2
- package/dist/modules/notifications/data/validators.js +95 -2
- package/dist/modules/notifications/data/validators.js.map +2 -2
- package/dist/modules/notifications/di.js +7 -0
- package/dist/modules/notifications/di.js.map +2 -2
- package/dist/modules/notifications/events.js +17 -0
- package/dist/modules/notifications/events.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js +190 -0
- package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js +124 -0
- package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js.map +7 -0
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +166 -1
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js +172 -0
- package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js.map +7 -0
- package/dist/modules/notifications/generators.js +63 -0
- package/dist/modules/notifications/generators.js.map +7 -0
- package/dist/modules/notifications/lib/delivery-strategies-registry.js +19 -0
- package/dist/modules/notifications/lib/delivery-strategies-registry.js.map +7 -0
- package/dist/modules/notifications/lib/deliveryStrategies.js.map +2 -2
- package/dist/modules/notifications/lib/derive-category.js +8 -0
- package/dist/modules/notifications/lib/derive-category.js.map +7 -0
- package/dist/modules/notifications/lib/notification-channel-registry.js +35 -0
- package/dist/modules/notifications/lib/notification-channel-registry.js.map +7 -0
- package/dist/modules/notifications/lib/notification-type-registry.js +87 -0
- package/dist/modules/notifications/lib/notification-type-registry.js.map +7 -0
- package/dist/modules/notifications/lib/notificationCopy.js +14 -0
- package/dist/modules/notifications/lib/notificationCopy.js.map +7 -0
- package/dist/modules/notifications/lib/notificationFactory.js +4 -1
- package/dist/modules/notifications/lib/notificationFactory.js.map +2 -2
- package/dist/modules/notifications/lib/notificationMapper.js +2 -0
- package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
- package/dist/modules/notifications/lib/notificationPreferenceService.js +83 -0
- package/dist/modules/notifications/lib/notificationPreferenceService.js.map +7 -0
- package/dist/modules/notifications/lib/notificationService.js +83 -28
- package/dist/modules/notifications/lib/notificationService.js.map +2 -2
- package/dist/modules/notifications/lib/notificationVisibility.js +24 -0
- package/dist/modules/notifications/lib/notificationVisibility.js.map +7 -0
- package/dist/modules/notifications/lib/routeHelpers.js +11 -1
- package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
- package/dist/modules/notifications/lib/shouldDeliver.js +46 -0
- package/dist/modules/notifications/lib/shouldDeliver.js.map +7 -0
- package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js +54 -0
- package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js.map +7 -0
- package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js +12 -0
- package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js.map +7 -0
- package/dist/modules/notifications/lib/typeCatalogue.js +71 -0
- package/dist/modules/notifications/lib/typeCatalogue.js.map +7 -0
- package/dist/modules/notifications/lib/typeChannelSettings.js +12 -0
- package/dist/modules/notifications/lib/typeChannelSettings.js.map +7 -0
- package/dist/modules/notifications/lib/typeOverrides.js +19 -0
- package/dist/modules/notifications/lib/typeOverrides.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js +14 -0
- package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js +15 -0
- package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js +13 -0
- package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js +15 -0
- package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js +13 -0
- package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js.map +7 -0
- package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js +14 -0
- package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js.map +7 -0
- package/dist/modules/notifications/notification-channels.js +26 -0
- package/dist/modules/notifications/notification-channels.js.map +7 -0
- package/dist/modules/notifications/notifications.delivery-strategies.js +12 -0
- package/dist/modules/notifications/notifications.delivery-strategies.js.map +7 -0
- package/dist/modules/notifications/setup.js +16 -1
- package/dist/modules/notifications/setup.js.map +2 -2
- package/dist/modules/notifications/subscribers/deliver-notification.js +66 -57
- package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
- package/dist/modules/notifications/subscribers/sync-notification-types.js +16 -0
- package/dist/modules/notifications/subscribers/sync-notification-types.js.map +7 -0
- package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js +23 -0
- package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js.map +7 -0
- package/dist/modules/notifications/widgets/injection-table.js +14 -0
- package/dist/modules/notifications/widgets/injection-table.js.map +7 -0
- package/dist/modules/push_notifications/acl.js +13 -0
- package/dist/modules/push_notifications/acl.js.map +7 -0
- package/dist/modules/push_notifications/api/custom-send/route.js +141 -0
- package/dist/modules/push_notifications/api/custom-send/route.js.map +7 -0
- package/dist/modules/push_notifications/api/deliveries/[id]/route.js +110 -0
- package/dist/modules/push_notifications/api/deliveries/[id]/route.js.map +7 -0
- package/dist/modules/push_notifications/api/deliveries/route.js +56 -0
- package/dist/modules/push_notifications/api/deliveries/route.js.map +7 -0
- package/dist/modules/push_notifications/api/openapi.js +19 -0
- package/dist/modules/push_notifications/api/openapi.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js +103 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js +13 -0
- package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.js +213 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.meta.js +23 -0
- package/dist/modules/push_notifications/backend/push_notifications/page.meta.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js +245 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +7 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js +26 -0
- package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js.map +7 -0
- package/dist/modules/push_notifications/data/entities.js +92 -0
- package/dist/modules/push_notifications/data/entities.js.map +7 -0
- package/dist/modules/push_notifications/data/extensions.js +23 -0
- package/dist/modules/push_notifications/data/extensions.js.map +7 -0
- package/dist/modules/push_notifications/data/validators.js +89 -0
- package/dist/modules/push_notifications/data/validators.js.map +7 -0
- package/dist/modules/push_notifications/di.js +17 -0
- package/dist/modules/push_notifications/di.js.map +7 -0
- package/dist/modules/push_notifications/events.js +24 -0
- package/dist/modules/push_notifications/events.js.map +7 -0
- package/dist/modules/push_notifications/index.js +16 -0
- package/dist/modules/push_notifications/index.js.map +7 -0
- package/dist/modules/push_notifications/lib/fake-provider-recorder.js +80 -0
- package/dist/modules/push_notifications/lib/fake-provider-recorder.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-delivery-strategy.js +54 -0
- package/dist/modules/push_notifications/lib/push-delivery-strategy.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-delivery.js +297 -0
- package/dist/modules/push_notifications/lib/push-delivery.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-fanout.js +138 -0
- package/dist/modules/push_notifications/lib/push-fanout.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-health.js +32 -0
- package/dist/modules/push_notifications/lib/push-health.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-reaper.js +87 -0
- package/dist/modules/push_notifications/lib/push-reaper.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-receipt-reaper.js +136 -0
- package/dist/modules/push_notifications/lib/push-receipt-reaper.js.map +7 -0
- package/dist/modules/push_notifications/lib/push-stub-adapter.js +63 -0
- package/dist/modules/push_notifications/lib/push-stub-adapter.js.map +7 -0
- package/dist/modules/push_notifications/lib/queue.js +53 -0
- package/dist/modules/push_notifications/lib/queue.js.map +7 -0
- package/dist/modules/push_notifications/lib/reclaim-window.js +13 -0
- package/dist/modules/push_notifications/lib/reclaim-window.js.map +7 -0
- package/dist/modules/push_notifications/lib/send-custom-push.js +48 -0
- package/dist/modules/push_notifications/lib/send-custom-push.js.map +7 -0
- package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js +16 -0
- package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js.map +7 -0
- package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js +13 -0
- package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js.map +7 -0
- package/dist/modules/push_notifications/notifications.delivery-strategies.js +8 -0
- package/dist/modules/push_notifications/notifications.delivery-strategies.js.map +7 -0
- package/dist/modules/push_notifications/notifications.js +40 -0
- package/dist/modules/push_notifications/notifications.js.map +7 -0
- package/dist/modules/push_notifications/setup.js +52 -0
- package/dist/modules/push_notifications/setup.js.map +7 -0
- package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js +57 -0
- package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js.map +7 -0
- package/dist/modules/push_notifications/workers/send-push.worker.js +27 -0
- package/dist/modules/push_notifications/workers/send-push.worker.js.map +7 -0
- package/dist/modules/sales/notifications.js +8 -0
- package/dist/modules/sales/notifications.js.map +2 -2
- package/dist/modules/staff/notifications.js +6 -0
- package/dist/modules/staff/notifications.js.map +2 -2
- package/dist/modules/workflows/notifications.js +2 -0
- package/dist/modules/workflows/notifications.js.map +2 -2
- package/generated/entities/notification/index.ts +3 -0
- package/generated/entities/notification_preference/index.ts +8 -0
- package/generated/entities/notification_type/index.ts +9 -0
- package/generated/entities/notification_type_override/index.ts +7 -0
- package/generated/entities/push_notification_delivery/index.ts +19 -0
- package/generated/entities/user_device/index.ts +16 -0
- package/generated/entities.ids.generated.ts +12 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/api.ts +2 -1
- package/src/helpers/integration/appRoot.ts +13 -0
- package/src/helpers/integration/dbFixtures.ts +1 -5
- package/src/helpers/integration/pushFake.ts +219 -0
- package/src/modules/auth/i18n/de.json +1 -0
- package/src/modules/auth/i18n/en.json +1 -0
- package/src/modules/auth/i18n/es.json +1 -0
- package/src/modules/auth/i18n/ko.json +1 -0
- package/src/modules/auth/i18n/pl.json +1 -0
- package/src/modules/auth/notifications.ts +16 -0
- package/src/modules/business_rules/i18n/de.json +2 -1
- package/src/modules/business_rules/i18n/en.json +2 -1
- package/src/modules/business_rules/i18n/es.json +2 -1
- package/src/modules/business_rules/i18n/ko.json +2 -1
- package/src/modules/business_rules/i18n/pl.json +2 -1
- package/src/modules/business_rules/notifications.ts +2 -0
- package/src/modules/catalog/i18n/de.json +2 -1
- package/src/modules/catalog/i18n/en.json +2 -1
- package/src/modules/catalog/i18n/es.json +2 -1
- package/src/modules/catalog/i18n/ko.json +2 -1
- package/src/modules/catalog/i18n/pl.json +2 -1
- package/src/modules/catalog/notifications.ts +2 -0
- package/src/modules/communication_channels/acl.ts +9 -0
- package/src/modules/communication_channels/api/delete/admin/channels/[id]/route.ts +168 -0
- package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +6 -2
- package/src/modules/communication_channels/api/get/channels/[id]/route.ts +6 -2
- package/src/modules/communication_channels/api/get/channels/route.ts +2 -1
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +16 -5
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +37 -1
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +175 -0
- package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +11 -1
- package/src/modules/communication_channels/commands/admin-delete-channel.ts +203 -0
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +63 -15
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +10 -2
- package/src/modules/communication_channels/commands/push-register.ts +7 -1
- package/src/modules/communication_channels/commands/push-unregister.ts +14 -3
- package/src/modules/communication_channels/data/entities.ts +10 -0
- package/src/modules/communication_channels/data/validators.ts +1 -0
- package/src/modules/communication_channels/i18n/de.json +35 -1
- package/src/modules/communication_channels/i18n/en.json +35 -1
- package/src/modules/communication_channels/i18n/es.json +35 -1
- package/src/modules/communication_channels/i18n/ko.json +35 -1
- package/src/modules/communication_channels/i18n/pl.json +35 -1
- package/src/modules/communication_channels/lib/access-control.ts +42 -0
- package/src/modules/communication_channels/lib/adapter.ts +25 -0
- package/src/modules/communication_channels/lib/connect-channel.ts +61 -26
- package/src/modules/communication_channels/lib/push-adapter.ts +139 -0
- package/src/modules/communication_channels/lib/push-capabilities.ts +42 -0
- package/src/modules/communication_channels/lib/push-connect-error.ts +52 -0
- package/src/modules/communication_channels/lib/push-credential-errors.ts +58 -0
- package/src/modules/communication_channels/lib/push-envelope.ts +104 -0
- package/src/modules/communication_channels/lib/refcounted-client-cache.ts +123 -0
- package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +9 -0
- package/src/modules/communication_channels/migrations/Migration20260703123630_communication_channels.ts +20 -0
- package/src/modules/communication_channels/notifications.ts +4 -0
- package/src/modules/communication_channels/setup.ts +2 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +21 -0
- package/src/modules/customer_accounts/i18n/de.json +2 -1
- package/src/modules/customer_accounts/i18n/en.json +2 -1
- package/src/modules/customer_accounts/i18n/es.json +2 -1
- package/src/modules/customer_accounts/i18n/ko.json +2 -1
- package/src/modules/customer_accounts/i18n/pl.json +2 -1
- package/src/modules/customer_accounts/notifications.ts +12 -0
- package/src/modules/customers/i18n/de.json +2 -1
- package/src/modules/customers/i18n/en.json +2 -1
- package/src/modules/customers/i18n/es.json +2 -1
- package/src/modules/customers/i18n/ko.json +2 -1
- package/src/modules/customers/i18n/pl.json +2 -1
- package/src/modules/customers/notifications.ts +4 -0
- package/src/modules/devices/AGENTS.md +101 -0
- package/src/modules/devices/acl.ts +7 -0
- package/src/modules/devices/api/[id]/route.ts +180 -0
- package/src/modules/devices/api/admin/devices/[id]/route.ts +235 -0
- package/src/modules/devices/api/admin/devices/route.ts +160 -0
- package/src/modules/devices/api/auth.ts +13 -0
- package/src/modules/devices/api/deviceList.ts +54 -0
- package/src/modules/devices/api/deviceOps.ts +186 -0
- package/src/modules/devices/api/openapi.ts +23 -0
- package/src/modules/devices/api/route.ts +165 -0
- package/src/modules/devices/backend/devices/[id]/page.meta.ts +12 -0
- package/src/modules/devices/backend/devices/[id]/page.tsx +164 -0
- package/src/modules/devices/backend/devices/create/page.meta.ts +12 -0
- package/src/modules/devices/backend/devices/create/page.tsx +81 -0
- package/src/modules/devices/backend/devices/page.meta.ts +21 -0
- package/src/modules/devices/backend/devices/page.tsx +259 -0
- package/src/modules/devices/commands/deactivate.ts +126 -0
- package/src/modules/devices/commands/index.ts +3 -0
- package/src/modules/devices/commands/register.ts +199 -0
- package/src/modules/devices/commands/shared.ts +119 -0
- package/src/modules/devices/commands/update.ts +142 -0
- package/src/modules/devices/data/entities.ts +79 -0
- package/src/modules/devices/data/validators.ts +65 -0
- package/src/modules/devices/di.ts +9 -0
- package/src/modules/devices/encryption.ts +15 -0
- package/src/modules/devices/events.ts +16 -0
- package/src/modules/devices/i18n/de.json +45 -0
- package/src/modules/devices/i18n/en.json +45 -0
- package/src/modules/devices/i18n/es.json +45 -0
- package/src/modules/devices/i18n/ko.json +45 -0
- package/src/modules/devices/i18n/pl.json +45 -0
- package/src/modules/devices/index.ts +15 -0
- package/src/modules/devices/lib/operationMetadata.ts +35 -0
- package/src/modules/devices/migrations/.snapshot-open-mercato.json +200 -0
- package/src/modules/devices/migrations/Migration20260602120000.ts +15 -0
- package/src/modules/devices/migrations/Migration20260630120000.ts +20 -0
- package/src/modules/devices/migrations/Migration20260701120000.ts +13 -0
- package/src/modules/devices/migrations/Migration20260721120000.ts +18 -0
- package/src/modules/devices/setup.ts +11 -0
- package/src/modules/entities/cli.ts +16 -2
- package/src/modules/entities/migrations/Migration20260722120000.ts +38 -0
- package/src/modules/inbox_ops/i18n/de.json +2 -1
- package/src/modules/inbox_ops/i18n/en.json +2 -1
- package/src/modules/inbox_ops/i18n/es.json +2 -1
- package/src/modules/inbox_ops/i18n/ko.json +2 -1
- package/src/modules/inbox_ops/i18n/pl.json +2 -1
- package/src/modules/inbox_ops/notifications.ts +2 -0
- package/src/modules/messages/i18n/de.json +2 -1
- package/src/modules/messages/i18n/en.json +2 -1
- package/src/modules/messages/i18n/es.json +2 -1
- package/src/modules/messages/i18n/ko.json +2 -1
- package/src/modules/messages/i18n/pl.json +2 -1
- package/src/modules/messages/notifications.ts +2 -0
- package/src/modules/notifications/AGENTS.md +54 -0
- package/src/modules/notifications/acl.ts +2 -0
- package/src/modules/notifications/api/admin/preferences/route.ts +193 -0
- package/src/modules/notifications/api/channels/route.ts +54 -0
- package/src/modules/notifications/api/preferences/route.ts +131 -0
- package/src/modules/notifications/api/route.ts +8 -1
- package/src/modules/notifications/api/types/[id]/channels/[channel]/route.ts +226 -0
- package/src/modules/notifications/api/types/route.ts +239 -0
- package/src/modules/notifications/api/unread-count/route.ts +5 -1
- package/src/modules/notifications/backend/notifications/user-preferences/page.meta.ts +26 -0
- package/src/modules/notifications/backend/notifications/user-preferences/page.tsx +12 -0
- package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +31 -0
- package/src/modules/notifications/backend/profile/notification-preferences/page.tsx +12 -0
- package/src/modules/notifications/data/entities.ts +144 -0
- package/src/modules/notifications/data/validators.ts +116 -0
- package/src/modules/notifications/di.ts +7 -0
- package/src/modules/notifications/events.ts +23 -0
- package/src/modules/notifications/frontend/NotificationPreferenceMatrix.tsx +287 -0
- package/src/modules/notifications/frontend/NotificationPreferencesPageClient.tsx +155 -0
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +273 -2
- package/src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx +214 -0
- package/src/modules/notifications/generators.ts +82 -0
- package/src/modules/notifications/i18n/de.json +43 -0
- package/src/modules/notifications/i18n/en.json +43 -0
- package/src/modules/notifications/i18n/es.json +43 -0
- package/src/modules/notifications/i18n/ko.json +43 -0
- package/src/modules/notifications/i18n/pl.json +44 -1
- package/src/modules/notifications/lib/delivery-strategies-registry.ts +30 -0
- package/src/modules/notifications/lib/deliveryStrategies.ts +40 -4
- package/src/modules/notifications/lib/derive-category.ts +15 -0
- package/src/modules/notifications/lib/notification-channel-registry.ts +59 -0
- package/src/modules/notifications/lib/notification-type-registry.ts +182 -0
- package/src/modules/notifications/lib/notificationCopy.ts +34 -0
- package/src/modules/notifications/lib/notificationFactory.ts +5 -1
- package/src/modules/notifications/lib/notificationMapper.ts +2 -0
- package/src/modules/notifications/lib/notificationPreferenceService.ts +138 -0
- package/src/modules/notifications/lib/notificationService.ts +129 -25
- package/src/modules/notifications/lib/notificationVisibility.ts +48 -0
- package/src/modules/notifications/lib/routeHelpers.ts +14 -1
- package/src/modules/notifications/lib/shouldDeliver.ts +112 -0
- package/src/modules/notifications/lib/strategies/email-delivery-strategy.ts +63 -0
- package/src/modules/notifications/lib/strategies/in-app-delivery-strategy.ts +21 -0
- package/src/modules/notifications/lib/typeCatalogue.ts +141 -0
- package/src/modules/notifications/lib/typeChannelSettings.ts +29 -0
- package/src/modules/notifications/lib/typeOverrides.ts +46 -0
- package/src/modules/notifications/migrations/.snapshot-open-mercato.json +874 -100
- package/src/modules/notifications/migrations/Migration20260625122947_notifications.ts +14 -0
- package/src/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.ts +15 -0
- package/src/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.ts +13 -0
- package/src/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.ts +15 -0
- package/src/modules/notifications/migrations/Migration20260706120000_notifications_channels.ts +13 -0
- package/src/modules/notifications/migrations/Migration20260716123121_notifications.ts +14 -0
- package/src/modules/notifications/notification-channels.ts +30 -0
- package/src/modules/notifications/notifications.delivery-strategies.ts +15 -0
- package/src/modules/notifications/setup.ts +23 -1
- package/src/modules/notifications/subscribers/deliver-notification.ts +105 -71
- package/src/modules/notifications/subscribers/sync-notification-types.ts +18 -0
- package/src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts +35 -0
- package/src/modules/notifications/widgets/injection-table.ts +18 -0
- package/src/modules/push_notifications/AGENTS.md +92 -0
- package/src/modules/push_notifications/acl.ts +9 -0
- package/src/modules/push_notifications/api/custom-send/route.ts +175 -0
- package/src/modules/push_notifications/api/deliveries/[id]/route.ts +122 -0
- package/src/modules/push_notifications/api/deliveries/route.ts +58 -0
- package/src/modules/push_notifications/api/openapi.ts +22 -0
- package/src/modules/push_notifications/backend/push_notifications/[id]/page.meta.ts +9 -0
- package/src/modules/push_notifications/backend/push_notifications/[id]/page.tsx +149 -0
- package/src/modules/push_notifications/backend/push_notifications/page.meta.ts +21 -0
- package/src/modules/push_notifications/backend/push_notifications/page.tsx +239 -0
- package/src/modules/push_notifications/backend/push_notifications/send/page.meta.ts +24 -0
- package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +327 -0
- package/src/modules/push_notifications/data/entities.ts +111 -0
- package/src/modules/push_notifications/data/extensions.ts +29 -0
- package/src/modules/push_notifications/data/validators.ts +102 -0
- package/src/modules/push_notifications/di.ts +20 -0
- package/src/modules/push_notifications/events.ts +21 -0
- package/src/modules/push_notifications/i18n/de.json +79 -0
- package/src/modules/push_notifications/i18n/en.json +79 -0
- package/src/modules/push_notifications/i18n/es.json +79 -0
- package/src/modules/push_notifications/i18n/ko.json +79 -0
- package/src/modules/push_notifications/i18n/pl.json +79 -0
- package/src/modules/push_notifications/index.ts +15 -0
- package/src/modules/push_notifications/lib/fake-provider-recorder.ts +154 -0
- package/src/modules/push_notifications/lib/push-delivery-strategy.ts +90 -0
- package/src/modules/push_notifications/lib/push-delivery.ts +455 -0
- package/src/modules/push_notifications/lib/push-fanout.ts +237 -0
- package/src/modules/push_notifications/lib/push-health.ts +47 -0
- package/src/modules/push_notifications/lib/push-reaper.ts +140 -0
- package/src/modules/push_notifications/lib/push-receipt-reaper.ts +210 -0
- package/src/modules/push_notifications/lib/push-stub-adapter.ts +80 -0
- package/src/modules/push_notifications/lib/queue.ts +74 -0
- package/src/modules/push_notifications/lib/reclaim-window.ts +16 -0
- package/src/modules/push_notifications/lib/send-custom-push.ts +100 -0
- package/src/modules/push_notifications/migrations/.snapshot-open-mercato.json +386 -0
- package/src/modules/push_notifications/migrations/Migration20260625150049_push_notifications.ts +16 -0
- package/src/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.ts +13 -0
- package/src/modules/push_notifications/notifications.delivery-strategies.ts +8 -0
- package/src/modules/push_notifications/notifications.ts +50 -0
- package/src/modules/push_notifications/setup.ts +92 -0
- package/src/modules/push_notifications/workers/reclaim-stuck.worker.ts +78 -0
- package/src/modules/push_notifications/workers/send-push.worker.ts +34 -0
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/ko.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/sales/notifications.ts +8 -0
- package/src/modules/staff/i18n/de.json +1 -0
- package/src/modules/staff/i18n/en.json +1 -0
- package/src/modules/staff/i18n/es.json +1 -0
- package/src/modules/staff/i18n/ko.json +1 -0
- package/src/modules/staff/i18n/pl.json +1 -0
- package/src/modules/staff/notifications.ts +6 -0
- package/src/modules/workflows/i18n/de.json +1 -0
- package/src/modules/workflows/i18n/en.json +1 -0
- package/src/modules/workflows/i18n/es.json +1 -0
- package/src/modules/workflows/i18n/ko.json +1 -0
- package/src/modules/workflows/i18n/pl.json +1 -0
- package/src/modules/workflows/notifications.ts +2 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { raw, type EntityName } from '@mikro-orm/core'
|
|
3
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
5
|
+
import type { ChannelAdapter, SendMessageResult } from '@open-mercato/core/modules/communication_channels/lib/adapter'
|
|
6
|
+
import { DEVICE_UNREGISTERED } from '@open-mercato/core/modules/communication_channels/lib/push-adapter'
|
|
7
|
+
import { refreshCredentialsIfNeeded } from '@open-mercato/core/modules/communication_channels/lib/credential-refresh'
|
|
8
|
+
import type { CommunicationChannel } from '@open-mercato/core/modules/communication_channels/data/entities'
|
|
9
|
+
import type { PushOptions } from '@open-mercato/core/modules/communication_channels/lib/push-envelope'
|
|
10
|
+
import type { UserDevice } from '@open-mercato/core/modules/devices/data/entities'
|
|
11
|
+
import { calculateBackoffDelayMs } from '@open-mercato/shared/lib/delivery/retry'
|
|
12
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
13
|
+
import { PushNotificationDelivery } from '../data/entities'
|
|
14
|
+
import { emitPushNotificationsEvent } from '../events'
|
|
15
|
+
import { resolveStuckThresholdMs } from './reclaim-window'
|
|
16
|
+
import { enqueuePushDelivery, type PushDeliveryJob } from './queue'
|
|
17
|
+
|
|
18
|
+
const logger = createLogger('push_notifications')
|
|
19
|
+
|
|
20
|
+
export const MAX_ATTEMPTS = 3
|
|
21
|
+
|
|
22
|
+
// Bound the worker's wait on a single provider send. If a send outlived the stuck-reclaim window the
|
|
23
|
+
// reaper would reclaim the row mid-send and a second worker would re-send it, so the wait is capped
|
|
24
|
+
// well under that window (see ./reclaim-window). Configurable via OM_PUSH_SEND_TIMEOUT_MS; clamped to
|
|
25
|
+
// stay below the reclaim window regardless of the configured value.
|
|
26
|
+
//
|
|
27
|
+
// NOTE: without an AbortSignal through the adapter contract this bounds the WORKER's wait, not the
|
|
28
|
+
// underlying socket — a black-holed SDK call may still complete later, but the lease stays fresh (the
|
|
29
|
+
// worker finalizes/retries within the window) so the reaper never steals it and fenced write-backs keep
|
|
30
|
+
// terminal state consistent. A timeout is surfaced as a retryable `send_timeout` failure.
|
|
31
|
+
const DEFAULT_SEND_TIMEOUT_MS = 60_000
|
|
32
|
+
const MIN_SEND_TIMEOUT_MS = 5_000
|
|
33
|
+
const SEND_TIMEOUT_SAFETY_MARGIN_MS = 60_000
|
|
34
|
+
|
|
35
|
+
function resolvePushSendTimeoutMs(): number {
|
|
36
|
+
const ceiling = Math.max(MIN_SEND_TIMEOUT_MS, resolveStuckThresholdMs() - SEND_TIMEOUT_SAFETY_MARGIN_MS)
|
|
37
|
+
const parsed = Number.parseInt(process.env.OM_PUSH_SEND_TIMEOUT_MS ?? '', 10)
|
|
38
|
+
const configured = Number.isFinite(parsed) && parsed >= MIN_SEND_TIMEOUT_MS ? parsed : DEFAULT_SEND_TIMEOUT_MS
|
|
39
|
+
return Math.min(configured, ceiling)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Resolve = <T = unknown>(name: string) => T
|
|
43
|
+
|
|
44
|
+
interface ChannelAdapterRegistryLike {
|
|
45
|
+
get(providerKey: string): ChannelAdapter | undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface CredentialsServiceLike {
|
|
49
|
+
resolve(
|
|
50
|
+
integrationId: string,
|
|
51
|
+
scope: { tenantId: string; organizationId: string; userId?: string | null },
|
|
52
|
+
): Promise<Record<string, unknown> | null>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type PushPayload = {
|
|
56
|
+
title?: string
|
|
57
|
+
body?: string | null
|
|
58
|
+
data?: Record<string, string>
|
|
59
|
+
options?: PushOptions
|
|
60
|
+
silent?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type ProcessResult = { status: PushNotificationDelivery['status']; deliveryId: string } | null
|
|
64
|
+
|
|
65
|
+
// Identifies the exact lease a worker holds on a delivery row. `attempts` is the lease-generation token:
|
|
66
|
+
// it is incremented in the claim itself, and the reaper resets `sending`→`pending` WITHOUT touching it,
|
|
67
|
+
// so the next worker's claim bumps it again. Every write-back is fenced on `(status='sending', attempts)`
|
|
68
|
+
// — if the reaper broke this lease and another worker re-claimed, `attempts` has moved on and the fenced
|
|
69
|
+
// update matches 0 rows, so this worker abandons instead of clobbering the new owner's state.
|
|
70
|
+
type DeliveryLease = { deliveryId: string; tenantId: string; attempts: number }
|
|
71
|
+
|
|
72
|
+
// The `unregistered` sentinel is shared (`deviceUnregisteredResult` / `DEVICE_UNREGISTERED`) across the
|
|
73
|
+
// fcm/apns/expo adapters + the stub, so the device soft-delete below fires uniformly regardless of provider.
|
|
74
|
+
function isUnregistered(result: SendMessageResult): boolean {
|
|
75
|
+
return result.metadata?.unregistered === true || result.error === DEVICE_UNREGISTERED
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Apply a delivery state transition as a fenced `nativeUpdate` (never `em.flush()`, so a stale
|
|
79
|
+
// identity-map copy can never issue an unfenced `UPDATE ... WHERE id`). Returns `true` iff this worker
|
|
80
|
+
// still held the lease and the write landed; `false` means the lease was lost (reaper reclaimed it) and
|
|
81
|
+
// the caller MUST abandon — no re-enqueue, no event — to avoid corrupting the new owner's state.
|
|
82
|
+
async function applyFencedTransition(
|
|
83
|
+
em: EntityManager,
|
|
84
|
+
lease: DeliveryLease,
|
|
85
|
+
patch: Record<string, unknown>,
|
|
86
|
+
fromStatus: PushNotificationDelivery['status'] = 'sending',
|
|
87
|
+
): Promise<boolean> {
|
|
88
|
+
const affected = await em.nativeUpdate(
|
|
89
|
+
PushNotificationDelivery,
|
|
90
|
+
{ id: lease.deliveryId, tenantId: lease.tenantId, status: fromStatus, attempts: lease.attempts },
|
|
91
|
+
{ ...patch, updatedAt: new Date() },
|
|
92
|
+
)
|
|
93
|
+
return affected > 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function finalize(
|
|
97
|
+
em: EntityManager,
|
|
98
|
+
delivery: PushNotificationDelivery,
|
|
99
|
+
lease: DeliveryLease,
|
|
100
|
+
event: 'push_notifications.delivery.sent' | 'push_notifications.delivery.failed',
|
|
101
|
+
patch: Record<string, unknown>,
|
|
102
|
+
): Promise<ProcessResult> {
|
|
103
|
+
// A terminal row never has a pending retry scheduled.
|
|
104
|
+
const held = await applyFencedTransition(em, lease, { ...patch, nextRetryAt: null })
|
|
105
|
+
if (!held) {
|
|
106
|
+
// Lease lost — the reaper reclaimed this row and another actor now owns it. Emitting or re-enqueueing
|
|
107
|
+
// here would corrupt the new owner's terminal state, so abandon quietly.
|
|
108
|
+
logger.warn('Push delivery lease lost before finalize; abandoning', {
|
|
109
|
+
deliveryId: lease.deliveryId,
|
|
110
|
+
tenantId: lease.tenantId,
|
|
111
|
+
})
|
|
112
|
+
return { status: delivery.status, deliveryId: delivery.id }
|
|
113
|
+
}
|
|
114
|
+
const finalStatus = (patch.status as PushNotificationDelivery['status'] | undefined) ?? delivery.status
|
|
115
|
+
await emitPushNotificationsEvent(
|
|
116
|
+
event,
|
|
117
|
+
{
|
|
118
|
+
deliveryId: delivery.id,
|
|
119
|
+
tenantId: delivery.tenantId,
|
|
120
|
+
organizationId: delivery.organizationId ?? null,
|
|
121
|
+
userId: delivery.userId,
|
|
122
|
+
provider: delivery.provider,
|
|
123
|
+
status: finalStatus,
|
|
124
|
+
},
|
|
125
|
+
{ persistent: true },
|
|
126
|
+
)
|
|
127
|
+
return { status: finalStatus, deliveryId: delivery.id }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function failTerminal(
|
|
131
|
+
em: EntityManager,
|
|
132
|
+
delivery: PushNotificationDelivery,
|
|
133
|
+
lease: DeliveryLease,
|
|
134
|
+
reason: string,
|
|
135
|
+
): Promise<ProcessResult> {
|
|
136
|
+
return finalize(em, delivery, lease, 'push_notifications.delivery.failed', {
|
|
137
|
+
status: 'failed',
|
|
138
|
+
lastError: reason,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function handleRetryableFailure(
|
|
143
|
+
em: EntityManager,
|
|
144
|
+
delivery: PushNotificationDelivery,
|
|
145
|
+
lease: DeliveryLease,
|
|
146
|
+
job: PushDeliveryJob,
|
|
147
|
+
reason: string,
|
|
148
|
+
providerResponse: Record<string, unknown> | null,
|
|
149
|
+
): Promise<ProcessResult> {
|
|
150
|
+
// `lease.attempts` is the count already persisted at claim time, so it is the real cap on provider
|
|
151
|
+
// sends (each claim = one send attempt). Compare against it, not a stale in-memory field.
|
|
152
|
+
if (lease.attempts < MAX_ATTEMPTS) {
|
|
153
|
+
// Release the claim (`pending`) and re-enqueue with exponential backoff + jitter; per-delivery
|
|
154
|
+
// isolation. Jitter avoids a thundering herd when a provider outage fails many deliveries at once.
|
|
155
|
+
const delayMs = calculateBackoffDelayMs(lease.attempts)
|
|
156
|
+
const held = await applyFencedTransition(em, lease, {
|
|
157
|
+
status: 'pending',
|
|
158
|
+
lastError: reason,
|
|
159
|
+
providerResponse,
|
|
160
|
+
nextRetryAt: new Date(Date.now() + delayMs),
|
|
161
|
+
})
|
|
162
|
+
if (!held) {
|
|
163
|
+
// Lease lost mid-flight (reaper reclaimed + another worker re-owns it). Do NOT re-enqueue — that
|
|
164
|
+
// would rewind the new owner's attempt counter and schedule a duplicate job.
|
|
165
|
+
logger.warn('Push delivery lease lost before retry; abandoning', {
|
|
166
|
+
deliveryId: lease.deliveryId,
|
|
167
|
+
tenantId: lease.tenantId,
|
|
168
|
+
})
|
|
169
|
+
return { status: delivery.status, deliveryId: delivery.id }
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await enqueuePushDelivery(job, delayMs)
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// The row is now `pending` under our lease; fence the terminal failure on that state. If the
|
|
175
|
+
// reaper already grabbed it (it will not — we just stamped updated_at), the write no-ops.
|
|
176
|
+
const failReason = error instanceof Error ? `retry_scheduling_failed: ${error.message}` : 'retry_scheduling_failed'
|
|
177
|
+
const failed = await applyFencedTransition(
|
|
178
|
+
em,
|
|
179
|
+
lease,
|
|
180
|
+
{ status: 'failed', lastError: failReason, nextRetryAt: null },
|
|
181
|
+
'pending',
|
|
182
|
+
)
|
|
183
|
+
if (failed) {
|
|
184
|
+
await emitPushNotificationsEvent(
|
|
185
|
+
'push_notifications.delivery.failed',
|
|
186
|
+
{
|
|
187
|
+
deliveryId: delivery.id,
|
|
188
|
+
tenantId: delivery.tenantId,
|
|
189
|
+
organizationId: delivery.organizationId ?? null,
|
|
190
|
+
userId: delivery.userId,
|
|
191
|
+
provider: delivery.provider,
|
|
192
|
+
status: 'failed',
|
|
193
|
+
},
|
|
194
|
+
{ persistent: true },
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
return { status: 'failed', deliveryId: delivery.id }
|
|
198
|
+
}
|
|
199
|
+
await emitPushNotificationsEvent(
|
|
200
|
+
'push_notifications.delivery.failed',
|
|
201
|
+
{
|
|
202
|
+
deliveryId: delivery.id,
|
|
203
|
+
tenantId: delivery.tenantId,
|
|
204
|
+
organizationId: delivery.organizationId ?? null,
|
|
205
|
+
userId: delivery.userId,
|
|
206
|
+
provider: delivery.provider,
|
|
207
|
+
// The row is reset to `pending` to release the claim for the re-enqueued attempt, but the
|
|
208
|
+
// logical outcome of THIS attempt is "failed, retry scheduled". Emit `retrying` (not the reset
|
|
209
|
+
// row status `pending`) so subscribers keying off `status` aren't misled; `willRetry` still gates
|
|
210
|
+
// ultimate-failure counters.
|
|
211
|
+
status: 'retrying',
|
|
212
|
+
willRetry: true,
|
|
213
|
+
},
|
|
214
|
+
{ persistent: true },
|
|
215
|
+
)
|
|
216
|
+
return { status: 'pending', deliveryId: delivery.id }
|
|
217
|
+
}
|
|
218
|
+
// Retries exhausted: distinct terminal `expired` status (vs `failed` for terminal errors), so the
|
|
219
|
+
// admin log can tell "gave up after N attempts" from "channel unavailable / no adapter".
|
|
220
|
+
return finalize(em, delivery, lease, 'push_notifications.delivery.failed', {
|
|
221
|
+
status: 'expired',
|
|
222
|
+
lastError: reason,
|
|
223
|
+
providerResponse,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Soft-delete the device through the devices module's own command (audit/events/undo stay
|
|
228
|
+
// consistent) rather than mutating its table directly. Dispatched by command id with a trusted
|
|
229
|
+
// system context — no business-logic import, no authenticated actor.
|
|
230
|
+
export async function softDeleteUnregisteredDevice(
|
|
231
|
+
resolve: Resolve,
|
|
232
|
+
input: { id: string; tenantId: string; userId: string; organizationId: string | null },
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
try {
|
|
235
|
+
const commandBus = resolve('commandBus') as CommandBus
|
|
236
|
+
await commandBus.execute('devices.user_devices.deactivate', {
|
|
237
|
+
input,
|
|
238
|
+
ctx: {
|
|
239
|
+
container: { resolve } as never,
|
|
240
|
+
auth: null,
|
|
241
|
+
organizationScope: null,
|
|
242
|
+
selectedOrganizationId: input.organizationId,
|
|
243
|
+
organizationIds: input.organizationId ? [input.organizationId] : null,
|
|
244
|
+
systemActor: true,
|
|
245
|
+
} as never,
|
|
246
|
+
})
|
|
247
|
+
} catch (error) {
|
|
248
|
+
logger.error('Failed to deactivate unregistered device', {
|
|
249
|
+
userDeviceId: input.id,
|
|
250
|
+
error: error instanceof Error ? error.message : String(error),
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Bound the wait on a single provider send (B3a): a hung/black-holed call must not outlive the
|
|
256
|
+
// stuck-reclaim window, or the reaper would reclaim the row mid-send and a second worker would re-send.
|
|
257
|
+
// On timeout we return a retryable `send_timeout` result and move on; the underlying SDK promise is left
|
|
258
|
+
// to settle on its own (its late rejection is swallowed to avoid an unhandled rejection).
|
|
259
|
+
async function sendWithTimeout(
|
|
260
|
+
adapter: ChannelAdapter,
|
|
261
|
+
input: Parameters<ChannelAdapter['sendMessage']>[0],
|
|
262
|
+
timeoutMs: number,
|
|
263
|
+
): Promise<SendMessageResult> {
|
|
264
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
265
|
+
const timeout = new Promise<SendMessageResult>((resolve) => {
|
|
266
|
+
timer = setTimeout(() => resolve({ externalMessageId: '', status: 'failed', error: 'send_timeout' }), timeoutMs)
|
|
267
|
+
})
|
|
268
|
+
const send = adapter.sendMessage(input)
|
|
269
|
+
send.catch(() => {})
|
|
270
|
+
try {
|
|
271
|
+
return await Promise.race([send, timeout])
|
|
272
|
+
} finally {
|
|
273
|
+
if (timer) clearTimeout(timer)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Send one push delivery row through the communication_channels hub.
|
|
279
|
+
*
|
|
280
|
+
* Idempotent on the delivery id: the row is claimed atomically, so a redelivered job that cannot win
|
|
281
|
+
* the claim (already `sending`/terminal) is a no-op. Mirrors the `test-send` route flow — resolve
|
|
282
|
+
* channel → adapter → credentials → convertOutbound → sendMessage — but adds delivery-row
|
|
283
|
+
* bookkeeping, retry/backoff, and device soft-delete on `unregistered`.
|
|
284
|
+
*
|
|
285
|
+
* Lease fencing (B3): the claim increments `attempts` atomically, and every post-claim state write goes
|
|
286
|
+
* through `applyFencedTransition`, fenced on `(status='sending', attempts=<claimed>)`. If the reaper
|
|
287
|
+
* broke this worker's lease and another worker re-claimed the row (bumping `attempts`), this worker's
|
|
288
|
+
* writes match 0 rows and it abandons — so a stolen lease can neither rewind the attempt counter nor
|
|
289
|
+
* overwrite the new owner's terminal state.
|
|
290
|
+
*/
|
|
291
|
+
export async function processPushDeliveryJob(
|
|
292
|
+
em: EntityManager,
|
|
293
|
+
job: PushDeliveryJob,
|
|
294
|
+
resolve: Resolve,
|
|
295
|
+
): Promise<ProcessResult> {
|
|
296
|
+
// Atomically claim the row (`pending` → `sending`) so a redelivered/duplicated job — inherent to
|
|
297
|
+
// at-least-once queues — is processed by exactly one worker. Only the worker whose update wins the
|
|
298
|
+
// race proceeds; the loser (or a non-pending/terminal row) is a no-op. Mirrors the `messages`
|
|
299
|
+
// send-email claim pattern, which is stronger than a plain read-then-check status guard.
|
|
300
|
+
// Count the attempt AT CLAIM time, in the same atomic update. `attempts` doubles as the lease token
|
|
301
|
+
// (see DeliveryLease): each claim increments it, the reaper never touches it, so it uniquely tags this
|
|
302
|
+
// lease generation. Persisting it before the send makes MAX_ATTEMPTS a real cap on provider sends
|
|
303
|
+
// (a crash after the send can't lose the increment). Only the worker whose update wins the race
|
|
304
|
+
// proceeds; the loser (or a non-pending/terminal row) is a no-op.
|
|
305
|
+
const claimed = await em.nativeUpdate(
|
|
306
|
+
PushNotificationDelivery,
|
|
307
|
+
{ id: job.deliveryId, tenantId: job.tenantId, status: 'pending' },
|
|
308
|
+
{ status: 'sending', attempts: raw('"attempts" + 1'), updatedAt: new Date() },
|
|
309
|
+
)
|
|
310
|
+
// Fresh read (forked EM per job → empty identity map → hits DB) so `attempts` reflects the increment.
|
|
311
|
+
const delivery = await em.findOne(
|
|
312
|
+
PushNotificationDelivery,
|
|
313
|
+
{ id: job.deliveryId, tenantId: job.tenantId },
|
|
314
|
+
{ refresh: true },
|
|
315
|
+
)
|
|
316
|
+
if (!delivery) return null
|
|
317
|
+
if (claimed === 0) return { status: delivery.status, deliveryId: delivery.id }
|
|
318
|
+
|
|
319
|
+
const lease: DeliveryLease = { deliveryId: delivery.id, tenantId: delivery.tenantId, attempts: delivery.attempts }
|
|
320
|
+
|
|
321
|
+
// Resolve the device for its full (secret) push token. Soft via DI token to avoid coupling.
|
|
322
|
+
// `push_token` is encrypted at rest, so decrypt on read (no-op when encryption is disabled).
|
|
323
|
+
const DeviceRef = resolve('UserDevice') as EntityName<UserDevice>
|
|
324
|
+
const device = await findOneWithDecryption(
|
|
325
|
+
em,
|
|
326
|
+
DeviceRef,
|
|
327
|
+
{ id: delivery.userDeviceId, tenantId: job.tenantId, deletedAt: null },
|
|
328
|
+
undefined,
|
|
329
|
+
{ tenantId: job.tenantId, organizationId: job.organizationId ?? null },
|
|
330
|
+
)
|
|
331
|
+
if (!device || !device.pushToken) {
|
|
332
|
+
const held = await applyFencedTransition(em, lease, {
|
|
333
|
+
status: 'skipped',
|
|
334
|
+
lastError: 'device_unavailable',
|
|
335
|
+
nextRetryAt: null,
|
|
336
|
+
})
|
|
337
|
+
return { status: held ? 'skipped' : delivery.status, deliveryId: delivery.id }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Resolve the tenant's push channel matching the snapshotted provider.
|
|
341
|
+
const ChannelRef = resolve('CommunicationChannel') as EntityName<CommunicationChannel>
|
|
342
|
+
const channel = await em.findOne(ChannelRef, {
|
|
343
|
+
tenantId: job.tenantId,
|
|
344
|
+
providerKey: delivery.provider,
|
|
345
|
+
channelType: 'push',
|
|
346
|
+
isActive: true,
|
|
347
|
+
deletedAt: null,
|
|
348
|
+
})
|
|
349
|
+
if (!channel) return failTerminal(em, delivery, lease, 'channel_unavailable')
|
|
350
|
+
|
|
351
|
+
const registry = resolve('channelAdapterRegistry') as ChannelAdapterRegistryLike | undefined
|
|
352
|
+
const adapter = registry?.get(delivery.provider)
|
|
353
|
+
// No provider package registered this provider's adapter (e.g. channel-fcm/apns/expo not installed).
|
|
354
|
+
// Terminal, no retry.
|
|
355
|
+
if (!adapter) return failTerminal(em, delivery, lease, 'no_adapter')
|
|
356
|
+
|
|
357
|
+
// Resolve credentials (best-effort, mirrors test-send).
|
|
358
|
+
let credentialsService: CredentialsServiceLike | undefined
|
|
359
|
+
try {
|
|
360
|
+
credentialsService = resolve('integrationCredentialsService') as CredentialsServiceLike | undefined
|
|
361
|
+
} catch {
|
|
362
|
+
credentialsService = undefined
|
|
363
|
+
}
|
|
364
|
+
// Credentials are keyed by the CHANNEL's org context, not the notification's: at connect time they
|
|
365
|
+
// are stored under `channel.organizationId ?? tenantId` (see communication_channels
|
|
366
|
+
// connect-credential-channel). Using the notification's `job.organizationId` here would miss the
|
|
367
|
+
// creds whenever a tenant-level (org-less) channel serves an org-scoped notification.
|
|
368
|
+
const credentialScope = {
|
|
369
|
+
tenantId: job.tenantId,
|
|
370
|
+
organizationId: channel.organizationId ?? job.tenantId,
|
|
371
|
+
userId: channel.userId ?? null,
|
|
372
|
+
}
|
|
373
|
+
let credentials: Record<string, unknown> = {}
|
|
374
|
+
if (channel.credentialsRef && credentialsService) {
|
|
375
|
+
credentials = (await credentialsService.resolve(`channel_${delivery.provider}`, credentialScope).catch(() => null)) ?? {}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Mirror the test-send flow: refresh near-expiry OAuth credentials before sending so providers with
|
|
379
|
+
// short-lived access tokens (FCM/APNs in Phase 4) don't fail with a stale token. No-op for adapters
|
|
380
|
+
// without `refreshCredentials` (e.g. the push_stub). Best-effort — on failure we keep current creds.
|
|
381
|
+
try {
|
|
382
|
+
const refreshed = await refreshCredentialsIfNeeded(
|
|
383
|
+
{ adapter, channelId: channel.id, credentials, scope: credentialScope },
|
|
384
|
+
{ credentialsService: credentialsService ?? null },
|
|
385
|
+
)
|
|
386
|
+
credentials = refreshed.credentials
|
|
387
|
+
} catch (error) {
|
|
388
|
+
logger.error('Credential refresh failed; sending with current credentials', {
|
|
389
|
+
deliveryId: delivery.id,
|
|
390
|
+
provider: delivery.provider,
|
|
391
|
+
error: error instanceof Error ? error.message : String(error),
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const scope = { tenantId: job.tenantId, organizationId: job.organizationId ?? job.tenantId }
|
|
396
|
+
const payload = (delivery.payload ?? {}) as PushPayload
|
|
397
|
+
const body = payload.body ?? payload.title ?? ''
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const converted = await adapter.convertOutbound({ body, bodyFormat: 'text' })
|
|
401
|
+
const result = await sendWithTimeout(
|
|
402
|
+
adapter,
|
|
403
|
+
{
|
|
404
|
+
content: {
|
|
405
|
+
...converted.content,
|
|
406
|
+
raw: {
|
|
407
|
+
title: payload.title ?? '',
|
|
408
|
+
body: payload.body ?? null,
|
|
409
|
+
data: payload.data ?? {},
|
|
410
|
+
options: payload.options ?? {},
|
|
411
|
+
silent: payload.silent === true,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
credentials,
|
|
415
|
+
scope,
|
|
416
|
+
metadata: {
|
|
417
|
+
pushToken: device.pushToken,
|
|
418
|
+
platform: device.platform,
|
|
419
|
+
userDeviceId: device.id,
|
|
420
|
+
provider: delivery.provider,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
resolvePushSendTimeoutMs(),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
if (result.status === 'sent' || result.status === 'queued') {
|
|
427
|
+
return finalize(em, delivery, lease, 'push_notifications.delivery.sent', {
|
|
428
|
+
status: 'sent',
|
|
429
|
+
sentAt: new Date(),
|
|
430
|
+
lastError: null,
|
|
431
|
+
providerResponse: result.metadata ?? { externalMessageId: result.externalMessageId },
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (isUnregistered(result)) {
|
|
436
|
+
const outcome = await finalize(em, delivery, lease, 'push_notifications.delivery.failed', {
|
|
437
|
+
status: 'failed',
|
|
438
|
+
lastError: DEVICE_UNREGISTERED,
|
|
439
|
+
providerResponse: result.metadata ?? null,
|
|
440
|
+
})
|
|
441
|
+
await softDeleteUnregisteredDevice(resolve, {
|
|
442
|
+
id: device.id,
|
|
443
|
+
tenantId: device.tenantId,
|
|
444
|
+
userId: device.userId,
|
|
445
|
+
organizationId: device.organizationId ?? null,
|
|
446
|
+
})
|
|
447
|
+
return outcome
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return handleRetryableFailure(em, delivery, lease, job, result.error ?? 'send_failed', result.metadata ?? null)
|
|
451
|
+
} catch (error) {
|
|
452
|
+
const message = error instanceof Error ? error.message : 'send_failed'
|
|
453
|
+
return handleRetryableFailure(em, delivery, lease, job, message, null)
|
|
454
|
+
}
|
|
455
|
+
}
|