@endora-commerce/mod-pwa 0.100.0

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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/admin/api/pwa-client.d.ts +15 -0
  4. package/dist/admin/api/pwa-client.d.ts.map +1 -0
  5. package/dist/admin/api/pwa-client.js +56 -0
  6. package/dist/admin/api/pwa-client.js.map +1 -0
  7. package/dist/admin/components/PushAudienceRuleBuilder.d.ts +19 -0
  8. package/dist/admin/components/PushAudienceRuleBuilder.d.ts.map +1 -0
  9. package/dist/admin/components/PushAudienceRuleBuilder.js +211 -0
  10. package/dist/admin/components/PushAudienceRuleBuilder.js.map +1 -0
  11. package/dist/admin/index.d.ts +39 -0
  12. package/dist/admin/index.d.ts.map +1 -0
  13. package/dist/admin/index.js +61 -0
  14. package/dist/admin/index.js.map +1 -0
  15. package/dist/admin/pages/PwaPage.d.ts +16 -0
  16. package/dist/admin/pages/PwaPage.d.ts.map +1 -0
  17. package/dist/admin/pages/PwaPage.js +207 -0
  18. package/dist/admin/pages/PwaPage.js.map +1 -0
  19. package/dist/backend/entities/push-message-delivery.entity.d.ts +18 -0
  20. package/dist/backend/entities/push-message-delivery.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/push-message-delivery.entity.js +68 -0
  22. package/dist/backend/entities/push-message-delivery.entity.js.map +1 -0
  23. package/dist/backend/entities/push-message.entity.d.ts +26 -0
  24. package/dist/backend/entities/push-message.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/push-message.entity.js +102 -0
  26. package/dist/backend/entities/push-message.entity.js.map +1 -0
  27. package/dist/backend/entities/push-subscription.entity.d.ts +49 -0
  28. package/dist/backend/entities/push-subscription.entity.d.ts.map +1 -0
  29. package/dist/backend/entities/push-subscription.entity.js +117 -0
  30. package/dist/backend/entities/push-subscription.entity.js.map +1 -0
  31. package/dist/backend/entities/pwa-icon-rendition.entity.d.ts +21 -0
  32. package/dist/backend/entities/pwa-icon-rendition.entity.d.ts.map +1 -0
  33. package/dist/backend/entities/pwa-icon-rendition.entity.js +69 -0
  34. package/dist/backend/entities/pwa-icon-rendition.entity.js.map +1 -0
  35. package/dist/backend/index.d.ts +100 -0
  36. package/dist/backend/index.d.ts.map +1 -0
  37. package/dist/backend/index.js +107 -0
  38. package/dist/backend/index.js.map +1 -0
  39. package/dist/backend/plugin.d.ts +119 -0
  40. package/dist/backend/plugin.d.ts.map +1 -0
  41. package/dist/backend/plugin.js +109 -0
  42. package/dist/backend/plugin.js.map +1 -0
  43. package/dist/backend/request-actor.d.ts +24 -0
  44. package/dist/backend/request-actor.d.ts.map +1 -0
  45. package/dist/backend/request-actor.js +29 -0
  46. package/dist/backend/request-actor.js.map +1 -0
  47. package/dist/backend/routes.admin.d.ts +52 -0
  48. package/dist/backend/routes.admin.d.ts.map +1 -0
  49. package/dist/backend/routes.admin.js +222 -0
  50. package/dist/backend/routes.admin.js.map +1 -0
  51. package/dist/backend/routes.storefront.d.ts +29 -0
  52. package/dist/backend/routes.storefront.d.ts.map +1 -0
  53. package/dist/backend/routes.storefront.js +66 -0
  54. package/dist/backend/routes.storefront.js.map +1 -0
  55. package/dist/backend/services/cross-module-context.d.ts +90 -0
  56. package/dist/backend/services/cross-module-context.d.ts.map +1 -0
  57. package/dist/backend/services/cross-module-context.js +84 -0
  58. package/dist/backend/services/cross-module-context.js.map +1 -0
  59. package/dist/backend/services/providers/web-push-provider.d.ts +31 -0
  60. package/dist/backend/services/providers/web-push-provider.d.ts.map +1 -0
  61. package/dist/backend/services/providers/web-push-provider.js +72 -0
  62. package/dist/backend/services/providers/web-push-provider.js.map +1 -0
  63. package/dist/backend/services/push-audience-evaluator.d.ts +24 -0
  64. package/dist/backend/services/push-audience-evaluator.d.ts.map +1 -0
  65. package/dist/backend/services/push-audience-evaluator.js +29 -0
  66. package/dist/backend/services/push-audience-evaluator.js.map +1 -0
  67. package/dist/backend/services/push-delivery-queue.d.ts +14 -0
  68. package/dist/backend/services/push-delivery-queue.d.ts.map +1 -0
  69. package/dist/backend/services/push-delivery-queue.js +30 -0
  70. package/dist/backend/services/push-delivery-queue.js.map +1 -0
  71. package/dist/backend/services/push-event-subscriber.d.ts +86 -0
  72. package/dist/backend/services/push-event-subscriber.d.ts.map +1 -0
  73. package/dist/backend/services/push-event-subscriber.js +109 -0
  74. package/dist/backend/services/push-event-subscriber.js.map +1 -0
  75. package/dist/backend/services/push-message-service.d.ts +58 -0
  76. package/dist/backend/services/push-message-service.d.ts.map +1 -0
  77. package/dist/backend/services/push-message-service.js +145 -0
  78. package/dist/backend/services/push-message-service.js.map +1 -0
  79. package/dist/backend/services/push-provider-registry.d.ts +17 -0
  80. package/dist/backend/services/push-provider-registry.d.ts.map +1 -0
  81. package/dist/backend/services/push-provider-registry.js +32 -0
  82. package/dist/backend/services/push-provider-registry.js.map +1 -0
  83. package/dist/backend/services/push-subscription-service.d.ts +67 -0
  84. package/dist/backend/services/push-subscription-service.d.ts.map +1 -0
  85. package/dist/backend/services/push-subscription-service.js +165 -0
  86. package/dist/backend/services/push-subscription-service.js.map +1 -0
  87. package/dist/backend/services/pwa-config-resolver.d.ts +29 -0
  88. package/dist/backend/services/pwa-config-resolver.d.ts.map +1 -0
  89. package/dist/backend/services/pwa-config-resolver.js +92 -0
  90. package/dist/backend/services/pwa-config-resolver.js.map +1 -0
  91. package/dist/backend/services/pwa-icon-service.d.ts +44 -0
  92. package/dist/backend/services/pwa-icon-service.d.ts.map +1 -0
  93. package/dist/backend/services/pwa-icon-service.js +105 -0
  94. package/dist/backend/services/pwa-icon-service.js.map +1 -0
  95. package/dist/backend/workers/push-delivery-worker.d.ts +17 -0
  96. package/dist/backend/workers/push-delivery-worker.d.ts.map +1 -0
  97. package/dist/backend/workers/push-delivery-worker.js +105 -0
  98. package/dist/backend/workers/push-delivery-worker.js.map +1 -0
  99. package/dist/manifest.d.ts +184 -0
  100. package/dist/manifest.d.ts.map +1 -0
  101. package/dist/manifest.js +207 -0
  102. package/dist/manifest.js.map +1 -0
  103. package/dist/migrations/20260625T144228_pwa_init.d.ts +18 -0
  104. package/dist/migrations/20260625T144228_pwa_init.d.ts.map +1 -0
  105. package/dist/migrations/20260625T144228_pwa_init.js +105 -0
  106. package/dist/migrations/20260625T144228_pwa_init.js.map +1 -0
  107. package/dist/migrations/20260830T172022_pwa_organization_attribution.d.ts +102 -0
  108. package/dist/migrations/20260830T172022_pwa_organization_attribution.d.ts.map +1 -0
  109. package/dist/migrations/20260830T172022_pwa_organization_attribution.js +169 -0
  110. package/dist/migrations/20260830T172022_pwa_organization_attribution.js.map +1 -0
  111. package/dist/migrations/index.d.ts +28 -0
  112. package/dist/migrations/index.d.ts.map +1 -0
  113. package/dist/migrations/index.js +31 -0
  114. package/dist/migrations/index.js.map +1 -0
  115. package/docs/pwa.md +93 -0
  116. package/i18n/en.json +43 -0
  117. package/i18n/pl.json +43 -0
  118. package/package.json +105 -0
  119. package/tailwind.css +14 -0
@@ -0,0 +1,109 @@
1
+ import { z } from 'zod';
2
+ import { PwaConfigResolver } from './services/pwa-config-resolver.js';
3
+ import { PwaIconService } from './services/pwa-icon-service.js';
4
+ import { PushSubscriptionService } from './services/push-subscription-service.js';
5
+ import { PushMessageService } from './services/push-message-service.js';
6
+ import { PushProviderRegistry } from './services/push-provider-registry.js';
7
+ import { WebPushProvider } from './services/providers/web-push-provider.js';
8
+ import { createPushEventHandlers, } from './services/push-event-subscriber.js';
9
+ import { createPushDeliveryQueue, createPushDeliveryWorker, } from './services/push-delivery-queue.js';
10
+ import { makePushDeliveryProcessor } from './workers/push-delivery-worker.js';
11
+ import { registerPwaStorefrontRoutes, } from './routes.storefront.js';
12
+ import { registerPwaAdminRoutes, } from './routes.admin.js';
13
+ const BoolSchema = z.boolean();
14
+ export function pwaModule(options) {
15
+ const configResolver = new PwaConfigResolver(options.settings, options.emFactory);
16
+ const iconService = new PwaIconService(options.emFactory, options.assetUpload);
17
+ // Feature 087 Group B / D-187 — the subscription registry stamps the
18
+ // organisation an owned device belongs to, derived from the owning account
19
+ // through the gated port this module already resolves. Passed rather than
20
+ // optional: `push_subscriptions_organization_attribution_chk` refuses a row
21
+ // this service could not attribute, so a composition with no way to answer
22
+ // the question cannot write the table at all.
23
+ const subscriptionService = new PushSubscriptionService(options.emFactory, options.customerAccounts);
24
+ const providerRegistry = new PushProviderRegistry();
25
+ providerRegistry.register(new WebPushProvider(options.settings, options.vapidSubject));
26
+ const deliveryQueue = createPushDeliveryQueue(options.redis);
27
+ const messageService = new PushMessageService(options.emFactory, deliveryQueue, options.customerAccounts, options.organizationDetails);
28
+ // Where the two auto-triggers write a skipped or failed push. Composition
29
+ // runs before `buildServer`, so `options.log` is the best a root can offer at
30
+ // construction time; `app.log` is the log the platform actually collects, and
31
+ // the plugin below moves the holder onto it. Nothing can emit an in-process
32
+ // event before that plugin has run, so the fallback is a type obligation
33
+ // rather than a path.
34
+ let eventLogger = options.log;
35
+ // Auto-triggered push (FR-024) — producer only; enqueues, never sends inline.
36
+ // `backend.ts` registers these two through `ctx.subscribe`, so they stop with
37
+ // the module (issue #107).
38
+ const pushEventHandlers = createPushEventHandlers({
39
+ messageService,
40
+ log: () => eventLogger,
41
+ resolveOrderTarget: options.resolveOrderTarget,
42
+ ...(options.resolveQuoteTarget ? { resolveQuoteTarget: options.resolveQuoteTarget } : {}),
43
+ isPushEnabled: async (salesChannelId) => {
44
+ try {
45
+ return await options.settings.get('pwa.push_enabled', salesChannelId, BoolSchema);
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ },
51
+ });
52
+ // Push-delivery consumer (Principle X). Separable entrypoint; co-located by
53
+ // default given low push volume, split-out-able under load via BACKEND_ROLE.
54
+ //
55
+ // It is **handed to `backend.ts`** rather than started here, and that is the
56
+ // Constitution XVII repair rather than tidiness: this line used to read
57
+ // `createPushDeliveryWorker(options.redis, processor);`, the value dropped on
58
+ // the floor. A worker outside `ctx.worker` is in no per-module registry, so
59
+ // `pauseWorkersFor('pwa')` reached nothing and the presence reconcile had
60
+ // nothing to reconcile — an operator who switched `pwa` off went on having
61
+ // push notifications delivered to their customers' devices, on both axes,
62
+ // while the module's own admin and storefront surfaces refused. That is what
63
+ // made it invisible, and it is the same shape issue #107 found for
64
+ // subscribers; `check:subscribe-seam` now refuses it.
65
+ const workers = options.runWorkers
66
+ ? [
67
+ createPushDeliveryWorker(options.redis, makePushDeliveryProcessor({
68
+ emFactory: options.emFactory,
69
+ registry: providerRegistry,
70
+ })),
71
+ ]
72
+ : [];
73
+ const handle = {
74
+ pushEventHandlers,
75
+ configResolver,
76
+ iconService,
77
+ subscriptionService,
78
+ messageService,
79
+ providerRegistry,
80
+ deliveryQueue,
81
+ };
82
+ const plugin = async (app) => {
83
+ eventLogger = app.log;
84
+ await registerPwaStorefrontRoutes(app, {
85
+ configResolver,
86
+ iconService,
87
+ subscriptionService,
88
+ resolveAssetUrl: options.resolveAssetUrl,
89
+ });
90
+ await registerPwaAdminRoutes(app, {
91
+ requireAdmin: options.requireAdmin,
92
+ emFactory: options.emFactory,
93
+ configResolver,
94
+ iconService,
95
+ subscriptionService,
96
+ messageService,
97
+ settingsWrite: options.settingsWrite,
98
+ customerAccounts: options.customerAccounts,
99
+ organizationDetails: options.organizationDetails,
100
+ customerGroups: options.customerGroups,
101
+ settingsRead: options.settings,
102
+ resolveScopeChannelId: async (salesChannelId) => salesChannelId ?? (await options.defaultChannelId()),
103
+ channelCodeForId: options.channelCodeForId,
104
+ resolveAuditContext: options.resolveAuditContext,
105
+ });
106
+ };
107
+ return { plugin, handle, workers };
108
+ }
109
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/backend/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { FastifyRequest } from 'fastify';
2
+ import '@endora-commerce/platform/http';
3
+ /**
4
+ * The signed-in customer behind a storefront request, or `null`.
5
+ *
6
+ * A push subscription is legitimately anonymous — the same browser can register
7
+ * a device with nobody signed in — so this narrows rather than refuses, and it
8
+ * is deliberately **not** the platform's `customerAccountIdResolver`, which
9
+ * throws 401 for a caller who is not a customer. Feature 087 Group B / D-187
10
+ * turns the answer into the row's attribution: an owned device carries its
11
+ * account and that account's organisation, and an ownerless one carries
12
+ * neither.
13
+ *
14
+ * It reads `request.actor` directly, which is the drain of a
15
+ * `resolveCustomerAccountId` option both composition roots supplied
16
+ * (`specs/110-instance-repository/` T118c). That option was the older shape,
17
+ * from when the augmentation was `auth`'s and a packaged module could not name
18
+ * it; a knob a root fills on a module's behalf is a knob that drifts between the
19
+ * two roots, and this one had — production read `request.actor` and the harness
20
+ * `request.testActor`. `registerTestAuth` mirrors each resolved actor onto both
21
+ * properties, so one read answers correctly under either composition.
22
+ */
23
+ export declare function callingCustomerAccountId(request: FastifyRequest): string | null;
24
+ //# sourceMappingURL=request-actor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-actor.d.ts","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAK9C,OAAO,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAE/E"}
@@ -0,0 +1,29 @@
1
+ // The `request.actor` augmentation, which is the platform's own since
2
+ // `specs/110-instance-repository/` T118b — a side-effect import of the `./http`
3
+ // barrel is how the `declare module 'fastify'` block reaches this package's
4
+ // program, and reaching a published barrel is not a `whole-file-reach`.
5
+ import '@endora-commerce/platform/http';
6
+ /**
7
+ * The signed-in customer behind a storefront request, or `null`.
8
+ *
9
+ * A push subscription is legitimately anonymous — the same browser can register
10
+ * a device with nobody signed in — so this narrows rather than refuses, and it
11
+ * is deliberately **not** the platform's `customerAccountIdResolver`, which
12
+ * throws 401 for a caller who is not a customer. Feature 087 Group B / D-187
13
+ * turns the answer into the row's attribution: an owned device carries its
14
+ * account and that account's organisation, and an ownerless one carries
15
+ * neither.
16
+ *
17
+ * It reads `request.actor` directly, which is the drain of a
18
+ * `resolveCustomerAccountId` option both composition roots supplied
19
+ * (`specs/110-instance-repository/` T118c). That option was the older shape,
20
+ * from when the augmentation was `auth`'s and a packaged module could not name
21
+ * it; a knob a root fills on a module's behalf is a knob that drifts between the
22
+ * two roots, and this one had — production read `request.actor` and the harness
23
+ * `request.testActor`. `registerTestAuth` mirrors each resolved actor onto both
24
+ * properties, so one read answers correctly under either composition.
25
+ */
26
+ export function callingCustomerAccountId(request) {
27
+ return request.actor.kind === 'customer' ? request.actor.customerAccountId : null;
28
+ }
29
+ //# sourceMappingURL=request-actor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-actor.js","sourceRoot":"","sources":["../../src/backend/request-actor.ts"],"names":[],"mappings":"AACA,sEAAsE;AACtE,gFAAgF;AAChF,4EAA4E;AAC5E,wEAAwE;AACxE,OAAO,gCAAgC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAuB;IAC9D,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC;AACpF,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
3
+ import { z } from 'zod';
4
+ import type { CustomerAccountReadPort, CustomerGroupReadPort, OrganizationDetailsPort } from '@endora-commerce/contracts';
5
+ import type { PwaConfigResolver } from './services/pwa-config-resolver.js';
6
+ import { type PwaIconService } from './services/pwa-icon-service.js';
7
+ import type { PushSubscriptionService } from './services/push-subscription-service.js';
8
+ import type { PushMessageService } from './services/push-message-service.js';
9
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
10
+ export interface AdminAuditContext {
11
+ actorAdminUserId: string | null;
12
+ }
13
+ /** Write port over SettingsAdminService (global vs per-channel value writes). */
14
+ export interface SettingsWritePort {
15
+ setValueForAllChannels(code: string, rawValue: unknown, expectedVersion: string | null, actor: AdminAuditContext): Promise<unknown>;
16
+ setValueForSubset(code: string, channelCodes: string[], rawValue: unknown, expectedVersion: string | null, actor: AdminAuditContext): Promise<unknown>;
17
+ /** Remove per-channel override rows so the channel inherits the global value. */
18
+ resetValues(code: string, channelCodes: string[] | undefined, actor: AdminAuditContext): Promise<unknown>;
19
+ }
20
+ export interface PwaAdminRoutesDeps {
21
+ requireAdmin: RequireAdminFactory;
22
+ emFactory: () => EntityManager;
23
+ configResolver: PwaConfigResolver;
24
+ iconService: PwaIconService;
25
+ subscriptionService: PushSubscriptionService;
26
+ messageService: PushMessageService;
27
+ settingsWrite: SettingsWritePort;
28
+ /**
29
+ * The three Rule Builder pickers' rows, asked of the modules that own them
30
+ * (feature 075, Phase C). Each is gated at its owner's registration seam, so
31
+ * a picker over a switched-off module answers 503 `MODULE_DISABLED` instead
32
+ * of listing targets the operator can no longer reach.
33
+ */
34
+ customerAccounts: CustomerAccountReadPort;
35
+ organizationDetails: OrganizationDetailsPort;
36
+ customerGroups: CustomerGroupReadPort;
37
+ settingsRead: {
38
+ get<T>(code: string, channelId: string | null, schema: z.ZodType<T>): Promise<T>;
39
+ };
40
+ /**
41
+ * Resolve a salesChannelId query value (or null) to the channel to read/write.
42
+ * Answers `null` when the deployment has no channel at all, which is a
43
+ * platform-wide read rather than the `'default'` sentinel it used to be
44
+ * (feature 072, D-41).
45
+ */
46
+ resolveScopeChannelId: (salesChannelId: string | null) => Promise<string | null>;
47
+ /** Map a channel id to its code (subset writes are keyed by code). */
48
+ channelCodeForId: (channelId: string) => Promise<string | null>;
49
+ resolveAuditContext: (request: FastifyRequest) => AdminAuditContext;
50
+ }
51
+ export declare function registerPwaAdminRoutes(app: FastifyInstance, deps: PwaAdminRoutesDeps): Promise<void>;
52
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IAChC,sBAAsB,CACpB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iBAAiB,CACf,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,iFAAiF;IACjF,WAAW,CACT,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,EAClC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAKD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,mBAAmB,CAAC;IAClC,SAAS,EAAE,MAAM,aAAa,CAAC;IAC/B,cAAc,EAAE,iBAAiB,CAAC;IAClC,WAAW,EAAE,cAAc,CAAC;IAC5B,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,cAAc,EAAE,kBAAkB,CAAC;IACnC,aAAa,EAAE,iBAAiB,CAAC;IACjC;;;;;OAKG;IACH,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C,cAAc,EAAE,qBAAqB,CAAC;IACtC,YAAY,EAAE;QACZ,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KAClF,CAAC;IACF;;;;;OAKG;IACH,qBAAqB,EAAE,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjF,sEAAsE;IACtE,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,mBAAmB,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,iBAAiB,CAAC;CACrE;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CA8Of"}
@@ -0,0 +1,222 @@
1
+ import webpush from 'web-push';
2
+ import { z } from 'zod';
3
+ import { PWA_PERMISSIONS, PWA_SETTING_CODES, CreatePushMessageRequestSchema, UpdatePwaConfigRequestSchema, } from '@endora-commerce/contracts';
4
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
5
+ import { PwaIconInvalid } from './services/pwa-icon-service.js';
6
+ const StringSchema = z.string();
7
+ const BoolSchema = z.boolean();
8
+ export async function registerPwaAdminRoutes(app, deps) {
9
+ const readGate = deps.requireAdmin(PWA_PERMISSIONS.READ);
10
+ const writeGate = deps.requireAdmin(PWA_PERMISSIONS.WRITE);
11
+ const sendGate = deps.requireAdmin(PWA_PERMISSIONS.SEND_PUSH);
12
+ const safeGet = async (code, channelId, fallback = '') => {
13
+ try {
14
+ return await deps.settingsRead.get(code, channelId, StringSchema);
15
+ }
16
+ catch {
17
+ return fallback;
18
+ }
19
+ };
20
+ const safeBool = async (code, channelId) => {
21
+ try {
22
+ return await deps.settingsRead.get(code, channelId, BoolSchema);
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ };
28
+ // GET /admin/pwa/config — effective config + secret isSet flags.
29
+ app.get('/api/v1/admin/pwa/config', { preHandler: readGate }, async (request, reply) => {
30
+ const salesChannelId = request.query.salesChannelId ?? null;
31
+ const channelId = await deps.resolveScopeChannelId(salesChannelId);
32
+ const [appName, shortName, themeColor, backgroundColor, displayMode, iconAssetId, vapidPublicKey, vapidPrivate, fcm] = await Promise.all([
33
+ safeGet(PWA_SETTING_CODES.APP_NAME, channelId, 'B2B Platform'),
34
+ safeGet(PWA_SETTING_CODES.SHORT_NAME, channelId, 'B2B'),
35
+ safeGet(PWA_SETTING_CODES.THEME_COLOR, channelId, '#1d4ed8'),
36
+ safeGet(PWA_SETTING_CODES.BACKGROUND_COLOR, channelId, '#fafafa'),
37
+ safeGet(PWA_SETTING_CODES.DISPLAY_MODE, channelId, 'standalone'),
38
+ safeGet(PWA_SETTING_CODES.ICON_ASSET_ID, channelId, ''),
39
+ safeGet(PWA_SETTING_CODES.VAPID_PUBLIC_KEY, channelId, ''),
40
+ safeGet(PWA_SETTING_CODES.VAPID_PRIVATE_KEY, channelId, ''),
41
+ safeGet(PWA_SETTING_CODES.FCM_SERVICE_ACCOUNT, channelId, ''),
42
+ ]);
43
+ const [cachingEnabled, pushEnabled] = await Promise.all([
44
+ safeBool(PWA_SETTING_CODES.CACHING_ENABLED, channelId),
45
+ safeBool(PWA_SETTING_CODES.PUSH_ENABLED, channelId),
46
+ ]);
47
+ return reply.send({
48
+ salesChannelId,
49
+ appName,
50
+ shortName,
51
+ themeColor,
52
+ backgroundColor,
53
+ displayMode,
54
+ iconAssetId,
55
+ cachingEnabled,
56
+ pushEnabled,
57
+ vapidPublicKey,
58
+ vapidPrivateKeyIsSet: vapidPrivate.length > 0,
59
+ fcmServiceAccountIsSet: fcm.length > 0,
60
+ });
61
+ });
62
+ // PUT /admin/pwa/config — write a subset of settings for global or a channel.
63
+ app.put('/api/v1/admin/pwa/config', { preHandler: writeGate }, async (request, reply) => {
64
+ const body = UpdatePwaConfigRequestSchema.parse(request.body);
65
+ const actor = deps.resolveAuditContext(request);
66
+ const salesChannelId = body.salesChannelId ?? null;
67
+ const writes = [];
68
+ if (body.appName !== undefined)
69
+ writes.push({ code: PWA_SETTING_CODES.APP_NAME, value: body.appName });
70
+ if (body.shortName !== undefined)
71
+ writes.push({ code: PWA_SETTING_CODES.SHORT_NAME, value: body.shortName });
72
+ if (body.themeColor !== undefined)
73
+ writes.push({ code: PWA_SETTING_CODES.THEME_COLOR, value: body.themeColor });
74
+ if (body.backgroundColor !== undefined)
75
+ writes.push({ code: PWA_SETTING_CODES.BACKGROUND_COLOR, value: body.backgroundColor });
76
+ if (body.displayMode !== undefined)
77
+ writes.push({ code: PWA_SETTING_CODES.DISPLAY_MODE, value: body.displayMode });
78
+ if (body.cachingEnabled !== undefined)
79
+ writes.push({ code: PWA_SETTING_CODES.CACHING_ENABLED, value: body.cachingEnabled });
80
+ if (body.pushEnabled !== undefined)
81
+ writes.push({ code: PWA_SETTING_CODES.PUSH_ENABLED, value: body.pushEnabled });
82
+ if (body.vapidPublicKey !== undefined)
83
+ writes.push({ code: PWA_SETTING_CODES.VAPID_PUBLIC_KEY, value: body.vapidPublicKey });
84
+ if (body.vapidPrivateKey !== undefined)
85
+ writes.push({ code: PWA_SETTING_CODES.VAPID_PRIVATE_KEY, value: body.vapidPrivateKey });
86
+ if (body.fcmServiceAccount !== undefined)
87
+ writes.push({ code: PWA_SETTING_CODES.FCM_SERVICE_ACCOUNT, value: body.fcmServiceAccount });
88
+ const channelCode = salesChannelId ? await deps.channelCodeForId(salesChannelId) : null;
89
+ for (const w of writes) {
90
+ if (channelCode) {
91
+ await deps.settingsWrite.setValueForSubset(w.code, [channelCode], w.value, null, actor);
92
+ }
93
+ else {
94
+ await deps.settingsWrite.setValueForAllChannels(w.code, w.value, null, actor);
95
+ }
96
+ }
97
+ return reply.send({ updated: writes.length });
98
+ });
99
+ // POST /admin/pwa/config/reset — drop a channel's per-channel overrides so it
100
+ // inherits the global PWA config again. Only meaningful for a concrete channel.
101
+ app.post('/api/v1/admin/pwa/config/reset', { preHandler: writeGate }, async (request, reply) => {
102
+ const body = z.object({ salesChannelId: z.string().uuid() }).parse(request.body);
103
+ const channelCode = await deps.channelCodeForId(body.salesChannelId);
104
+ if (!channelCode) {
105
+ return reply.code(400).send({
106
+ error: { code: 'PWA_CHANNEL_UNKNOWN', message: 'Unknown sales channel.' },
107
+ });
108
+ }
109
+ const actor = deps.resolveAuditContext(request);
110
+ const codes = Object.values(PWA_SETTING_CODES);
111
+ for (const code of codes) {
112
+ await deps.settingsWrite.resetValues(code, [channelCode], actor);
113
+ }
114
+ return reply.send({ reset: codes.length });
115
+ });
116
+ // POST /admin/pwa/vapid/generate — generate + persist a VAPID key pair.
117
+ app.post('/api/v1/admin/pwa/vapid/generate', { preHandler: writeGate }, async (request, reply) => {
118
+ const actor = deps.resolveAuditContext(request);
119
+ const keys = webpush.generateVAPIDKeys();
120
+ await deps.settingsWrite.setValueForAllChannels(PWA_SETTING_CODES.VAPID_PUBLIC_KEY, keys.publicKey, null, actor);
121
+ await deps.settingsWrite.setValueForAllChannels(PWA_SETTING_CODES.VAPID_PRIVATE_KEY, keys.privateKey, null, actor);
122
+ return reply.send({ publicKey: keys.publicKey });
123
+ });
124
+ // POST /admin/pwa/icon — multipart source upload + sharp rendition derivation.
125
+ app.post('/api/v1/admin/pwa/icon', { preHandler: writeGate }, async (request, reply) => {
126
+ if (!request.isMultipart()) {
127
+ return reply.code(400).send({ error: { code: 'PWA_ICON_INVALID', message: 'Upload requires multipart/form-data with a `file` part.' } });
128
+ }
129
+ const file = await request.file();
130
+ if (!file) {
131
+ return reply.code(400).send({ error: { code: 'PWA_ICON_INVALID', message: 'Missing `file` part.' } });
132
+ }
133
+ const buffer = await file.toBuffer();
134
+ const salesChannelId = request.query.salesChannelId ?? null;
135
+ try {
136
+ const result = await deps.iconService.ingest({
137
+ salesChannelId,
138
+ declaredMime: file.mimetype,
139
+ buffer,
140
+ });
141
+ // Record the source asset id on the channel/global config.
142
+ const actor = deps.resolveAuditContext(request);
143
+ const channelCode = salesChannelId ? await deps.channelCodeForId(salesChannelId) : null;
144
+ if (channelCode) {
145
+ await deps.settingsWrite.setValueForSubset(PWA_SETTING_CODES.ICON_ASSET_ID, [channelCode], result.sourceAssetId, null, actor);
146
+ }
147
+ else {
148
+ await deps.settingsWrite.setValueForAllChannels(PWA_SETTING_CODES.ICON_ASSET_ID, result.sourceAssetId, null, actor);
149
+ }
150
+ return reply.code(201).send(result);
151
+ }
152
+ catch (err) {
153
+ if (err instanceof PwaIconInvalid) {
154
+ return reply.code(400).send({ error: { code: err.code, message: err.message } });
155
+ }
156
+ throw err;
157
+ }
158
+ });
159
+ // GET /admin/pwa/subscriptions — subscriber counts for a channel.
160
+ app.get('/api/v1/admin/pwa/subscriptions', { preHandler: readGate }, async (request, reply) => {
161
+ const channelId = await deps.resolveScopeChannelId(request.query.salesChannelId ?? null);
162
+ const stats = await deps.subscriptionService.statsForChannel(channelId);
163
+ return reply.send(stats);
164
+ });
165
+ // POST /admin/pwa/messages — admin-initiated send (FR-024).
166
+ app.post('/api/v1/admin/pwa/messages', { preHandler: sendGate }, async (request, reply) => {
167
+ const body = CreatePushMessageRequestSchema.parse(request.body);
168
+ const config = await deps.configResolver.getPublicConfig(body.salesChannelId);
169
+ if (!config.pushEnabled || !config.vapidPublicKey) {
170
+ return reply.code(503).send({ error: { code: 'PWA_PUSH_UNCONFIGURED', message: 'Push is not configured for this channel.' } });
171
+ }
172
+ const actor = deps.resolveAuditContext(request);
173
+ const result = await deps.messageService.createAndEnqueue({
174
+ salesChannelId: body.salesChannelId,
175
+ title: body.title,
176
+ body: body.body,
177
+ url: body.url ?? null,
178
+ iconUrl: body.iconUrl ?? null,
179
+ audience: body.audience,
180
+ trigger: 'admin',
181
+ createdByAdminUserId: actor.actorAdminUserId,
182
+ });
183
+ return reply.code(202).send(result);
184
+ });
185
+ // ---- Audience Rule Builder pickers (targeting criteria) -----------------
186
+ // Lists feeding the admin Rule Builder when composing a targeted send.
187
+ // Gated by the page's base read permission so a read-only viewer can still
188
+ // see the available targets.
189
+ app.get('/api/v1/admin/pwa/rule-targets/sales-channels', { preHandler: readGate }, async () => {
190
+ const em = deps.emFactory();
191
+ const rows = await em.find(SalesChannel, {}, { orderBy: { code: 'asc' } });
192
+ return { data: { items: rows.map((r) => ({ id: r.id, code: r.code, name: r.name })) } };
193
+ });
194
+ app.get('/api/v1/admin/pwa/rule-targets/customer-groups', { preHandler: readGate }, async () => {
195
+ const rows = await deps.customerGroups.listAll();
196
+ return { data: { items: rows.map((r) => ({ id: r.id, code: r.code, name: r.name })) } };
197
+ });
198
+ app.get('/api/v1/admin/pwa/rule-targets/organizations', { preHandler: readGate }, async (request) => {
199
+ const limit = Math.min(200, Math.max(1, Number(request.query.limit ?? '100')));
200
+ const search = (request.query.search ?? '').trim();
201
+ // `searchByName` matches the diacritic-folded `name_search` column the
202
+ // owner maintains, so "lodz" now finds "Łódź" — the local `$ilike` on
203
+ // `name` never did.
204
+ const rows = await deps.organizationDetails.searchByName(search, limit);
205
+ return { data: { items: rows.map((r) => ({ id: r.id, name: r.name, taxId: r.taxId })) } };
206
+ });
207
+ app.get('/api/v1/admin/pwa/rule-targets/customers', { preHandler: readGate }, async (request) => {
208
+ const limit = Math.min(200, Math.max(1, Number(request.query.limit ?? '100')));
209
+ const search = (request.query.search ?? '').trim();
210
+ const rows = await deps.customerAccounts.searchByEmail(search, limit);
211
+ return {
212
+ data: {
213
+ items: rows.map((r) => ({
214
+ id: r.id,
215
+ email: r.email,
216
+ organizationId: r.organizationId ?? null,
217
+ })),
218
+ },
219
+ };
220
+ });
221
+ }
222
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { PwaConfigResolver } from './services/pwa-config-resolver.js';
3
+ import type { PwaIconService } from './services/pwa-icon-service.js';
4
+ import type { PushSubscriptionService } from './services/push-subscription-service.js';
5
+ export interface PwaStorefrontRoutesDeps {
6
+ configResolver: PwaConfigResolver;
7
+ iconService: PwaIconService;
8
+ subscriptionService: PushSubscriptionService;
9
+ /**
10
+ * A stored asset id to a servable URL, over `assets_library`' published
11
+ * `assetsLibraryPort` (`specs/110-instance-repository/` T118c). Absolute:
12
+ * `assets_library` resolves the origin itself (D-223).
13
+ */
14
+ resolveAssetUrl: (assetId: string) => Promise<string | null>;
15
+ }
16
+ /**
17
+ * **`resolveChannelId` is gone, and it was never called.**
18
+ *
19
+ * Both composition roots built a `resolveChannelIdByCode` closure — a
20
+ * `getByCode` with a system-default fallback — `plugin.ts` threaded it here as
21
+ * `resolveChannelId`, and the three routes below read `getResolvedChannel`
22
+ * instead, which is the platform's resolved request channel and the sanctioned
23
+ * accessor (Constitution XII). So the option was dead the whole way down: two
24
+ * closures, one option, one dependency field, and no call site. It was one of
25
+ * the eight members of `PwaBridge`, and the only one T118c deleted rather than
26
+ * drained.
27
+ */
28
+ export declare function registerPwaStorefrontRoutes(app: FastifyInstance, deps: PwaStorefrontRoutesDeps): Promise<void>;
29
+ //# sourceMappingURL=routes.storefront.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.d.ts","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAIvF,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,iBAAiB,CAAC;IAClC,WAAW,EAAE,cAAc,CAAC;IAC5B,mBAAmB,EAAE,uBAAuB,CAAC;IAC7C;;;;OAIG;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED;;;;;;;;;;;GAWG;AAEH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAoDf"}
@@ -0,0 +1,66 @@
1
+ import { PushSubscriptionInputSchema, PushSubscriptionDeleteSchema, } from '@endora-commerce/contracts';
2
+ import { getResolvedChannel } from '@endora-commerce/platform/kernel';
3
+ import { callingCustomerAccountId } from './request-actor.js';
4
+ /**
5
+ * **`resolveChannelId` is gone, and it was never called.**
6
+ *
7
+ * Both composition roots built a `resolveChannelIdByCode` closure — a
8
+ * `getByCode` with a system-default fallback — `plugin.ts` threaded it here as
9
+ * `resolveChannelId`, and the three routes below read `getResolvedChannel`
10
+ * instead, which is the platform's resolved request channel and the sanctioned
11
+ * accessor (Constitution XII). So the option was dead the whole way down: two
12
+ * closures, one option, one dependency field, and no call site. It was one of
13
+ * the eight members of `PwaBridge`, and the only one T118c deleted rather than
14
+ * drained.
15
+ */
16
+ export async function registerPwaStorefrontRoutes(app, deps) {
17
+ // GET config — manifest identity + toggles + public VAPID key (FR-001/011).
18
+ app.get('/api/v1/storefront/pwa/config', async (request, reply) => {
19
+ const channelId = getResolvedChannel(request).id;
20
+ const config = await deps.configResolver.getPublicConfig(channelId);
21
+ reply.header('Cache-Control', 'public, max-age=60');
22
+ return reply.send(config);
23
+ });
24
+ // GET icon rendition — redirect to the stored asset URL (FR-005).
25
+ app.get('/api/v1/storefront/pwa/icons/:spec', async (request, reply) => {
26
+ const match = /^(\d+)-(any|maskable)\.png$/.exec(request.params.spec);
27
+ if (!match)
28
+ return reply.code(404).send();
29
+ const size = Number(match[1]);
30
+ const purpose = match[2];
31
+ const channelId = getResolvedChannel(request).id;
32
+ const assetId = await deps.iconService.resolveRenditionAssetId(channelId, size, purpose);
33
+ if (!assetId)
34
+ return reply.code(404).send();
35
+ const url = await deps.resolveAssetUrl(assetId);
36
+ if (!url)
37
+ return reply.code(404).send();
38
+ return reply.redirect(url, 302);
39
+ });
40
+ // POST subscribe — register (upsert) a Web-Push subscription (FR-017).
41
+ app.post('/api/v1/storefront/pwa/subscriptions', async (request, reply) => {
42
+ const channelId = getResolvedChannel(request).id;
43
+ const config = await deps.configResolver.getPublicConfig(channelId);
44
+ if (!config.pushEnabled) {
45
+ return reply.code(403).send({ error: { code: 'PWA_PUSH_DISABLED', message: 'Push is disabled for this channel.' } });
46
+ }
47
+ if (!config.vapidPublicKey) {
48
+ return reply.code(503).send({ error: { code: 'PWA_PUSH_UNCONFIGURED', message: 'Push is not configured.' } });
49
+ }
50
+ const body = PushSubscriptionInputSchema.parse(request.body);
51
+ const customerAccountId = callingCustomerAccountId(request);
52
+ const result = await deps.subscriptionService.register({
53
+ ...body,
54
+ salesChannelId: channelId,
55
+ customerAccountId,
56
+ });
57
+ return reply.code(result.created ? 201 : 200).send({ id: result.id, status: result.status });
58
+ });
59
+ // DELETE subscribe — revoke (FR-018). Idempotent.
60
+ app.delete('/api/v1/storefront/pwa/subscriptions', async (request, reply) => {
61
+ const body = PushSubscriptionDeleteSchema.parse(request.body);
62
+ await deps.subscriptionService.revoke(body.endpoint);
63
+ return reply.code(204).send();
64
+ });
65
+ }
66
+ //# sourceMappingURL=routes.storefront.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.js","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"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"}