podverse-notifications 1.0.0 → 5.1.20-alpha.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 (44) hide show
  1. package/README.md +257 -1
  2. package/dist/config/index.d.ts +17 -0
  3. package/dist/config/index.d.ts.map +1 -0
  4. package/dist/config/index.js +30 -0
  5. package/dist/index.d.ts +4 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +19 -1
  8. package/dist/services/notifications/i18nNotifications.d.ts +4 -0
  9. package/dist/services/notifications/i18nNotifications.d.ts.map +1 -0
  10. package/dist/services/notifications/i18nNotifications.js +49 -0
  11. package/dist/services/notifications/index.d.ts +3 -0
  12. package/dist/services/notifications/index.d.ts.map +1 -0
  13. package/dist/services/notifications/index.js +18 -0
  14. package/dist/services/notifications/notificationOrchestrator.d.ts +34 -0
  15. package/dist/services/notifications/notificationOrchestrator.d.ts.map +1 -0
  16. package/dist/services/notifications/notificationOrchestrator.js +104 -0
  17. package/dist/services/unifiedpush/index.d.ts +4 -0
  18. package/dist/services/unifiedpush/index.d.ts.map +1 -0
  19. package/dist/services/unifiedpush/index.js +7 -0
  20. package/dist/services/unifiedpush/unifiedpushHelpers.d.ts +5 -0
  21. package/dist/services/unifiedpush/unifiedpushHelpers.d.ts.map +1 -0
  22. package/dist/services/unifiedpush/unifiedpushHelpers.js +2 -0
  23. package/dist/services/unifiedpush/unifiedpushNotification.d.ts +25 -0
  24. package/dist/services/unifiedpush/unifiedpushNotification.d.ts.map +1 -0
  25. package/dist/services/unifiedpush/unifiedpushNotification.js +90 -0
  26. package/dist/services/unifiedpush/unifiedpushNotificationOrchestrator.d.ts +15 -0
  27. package/dist/services/unifiedpush/unifiedpushNotificationOrchestrator.d.ts.map +1 -0
  28. package/dist/services/unifiedpush/unifiedpushNotificationOrchestrator.js +27 -0
  29. package/dist/services/webpush/index.d.ts +5 -0
  30. package/dist/services/webpush/index.d.ts.map +1 -0
  31. package/dist/services/webpush/index.js +10 -0
  32. package/dist/services/webpush/webpushAdmin.d.ts +4 -0
  33. package/dist/services/webpush/webpushAdmin.d.ts.map +1 -0
  34. package/dist/services/webpush/webpushAdmin.js +28 -0
  35. package/dist/services/webpush/webpushHelpers.d.ts +8 -0
  36. package/dist/services/webpush/webpushHelpers.d.ts.map +1 -0
  37. package/dist/services/webpush/webpushHelpers.js +2 -0
  38. package/dist/services/webpush/webpushNotification.d.ts +17 -0
  39. package/dist/services/webpush/webpushNotification.d.ts.map +1 -0
  40. package/dist/services/webpush/webpushNotification.js +69 -0
  41. package/dist/services/webpush/webpushNotificationOrchestrator.d.ts +15 -0
  42. package/dist/services/webpush/webpushNotificationOrchestrator.d.ts.map +1 -0
  43. package/dist/services/webpush/webpushNotificationOrchestrator.js +27 -0
  44. package/package.json +5 -3
package/README.md CHANGED
@@ -1,2 +1,258 @@
1
1
  # podverse-notifications
2
- Push notification helper module for Podverse use cases
2
+
3
+ Push notification module for Podverse. Handles notification orchestration for multiple services.
4
+
5
+ ## Features
6
+
7
+ - **Web Push** - Native browser push notifications using the W3C Push API (no third-party dependency)
8
+ - **Firebase FCM** - Firebase Cloud Messaging for mobile apps (via podverse-external-services)
9
+ - **i18n Support** - Localized notification prefixes
10
+ - **Service Orchestration** - Unified API for sending notifications across multiple services
11
+
12
+ ## Services
13
+
14
+ ### Web Push (Native)
15
+
16
+ Web Push uses the W3C Push API standard. It's built into modern browsers and doesn't require any third-party service. The browser's built-in push service handles message delivery:
17
+
18
+ - **Chrome/Edge**: Google's push service
19
+ - **Firefox**: Mozilla's push service
20
+ - **Safari**: Apple's push service
21
+
22
+ ### Firebase FCM (Third-Party)
23
+
24
+ Firebase Cloud Messaging is used for mobile app notifications (iOS/Android). This is handled by `podverse-external-services` since it's a third-party dependency.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install podverse-notifications
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ ### Environment Variables
35
+
36
+ ```env
37
+ # Web Push Configuration
38
+ WEBPUSH_ENABLED=true
39
+ WEBPUSH_VAPID_PUBLIC_KEY=your-vapid-public-key
40
+ WEBPUSH_VAPID_PRIVATE_KEY=your-vapid-private-key
41
+ WEBPUSH_VAPID_SUBJECT=mailto:contact@podverse.fm
42
+
43
+ # Web URL Configuration (for notification links)
44
+ WEB_PROTOCOL=https
45
+ WEB_DOMAIN=podverse.fm
46
+ WEB_ICON_IMAGE_PATH=https://podverse.fm/favicon/web-app-manifest-192x192.png
47
+ ```
48
+
49
+ ### Environment-Specific Configuration
50
+
51
+ #### Local Development
52
+
53
+ For local development, set environment variables in your `.env` file or shell:
54
+
55
+ ```env
56
+ WEBPUSH_ENABLED=true
57
+ WEBPUSH_VAPID_PUBLIC_KEY=your-dev-vapid-public-key
58
+ WEBPUSH_VAPID_PRIVATE_KEY=your-dev-vapid-private-key
59
+ WEBPUSH_VAPID_SUBJECT=mailto:dev@example.com
60
+
61
+ WEB_PROTOCOL=http
62
+ WEB_DOMAIN=localhost:3000
63
+ ```
64
+
65
+ #### Alpha/Staging
66
+
67
+ For alpha/staging environments, configure via Docker environment or secrets:
68
+
69
+ ```env
70
+ WEBPUSH_ENABLED=true
71
+ WEBPUSH_VAPID_PUBLIC_KEY=your-alpha-vapid-public-key
72
+ WEBPUSH_VAPID_PRIVATE_KEY=your-alpha-vapid-private-key
73
+ WEBPUSH_VAPID_SUBJECT=mailto:contact@podverse.fm
74
+
75
+ WEB_PROTOCOL=https
76
+ WEB_DOMAIN=alpha.podverse.fm
77
+ ```
78
+
79
+ #### Production
80
+
81
+ For production, use secrets management (Kubernetes secrets, Docker secrets, etc.):
82
+
83
+ ```env
84
+ WEBPUSH_ENABLED=true
85
+ WEBPUSH_VAPID_PUBLIC_KEY=your-production-vapid-public-key
86
+ WEBPUSH_VAPID_PRIVATE_KEY=your-production-vapid-private-key # Keep secret!
87
+ WEBPUSH_VAPID_SUBJECT=mailto:contact@podverse.fm
88
+
89
+ WEB_PROTOCOL=https
90
+ WEB_DOMAIN=podverse.fm
91
+ ```
92
+
93
+ ### Key Matching Requirement
94
+
95
+ **Important:** The VAPID public key in `podverse-notifications` (backend) must match the key in `podverse-web` (frontend):
96
+
97
+ | Service | Environment Variable | Key Type |
98
+ |---------|---------------------|----------|
99
+ | podverse-web | `NEXT_PUBLIC_WEBPUSH_VAPID_PUBLIC_KEY` | Public only |
100
+ | podverse-notifications | `WEBPUSH_VAPID_PUBLIC_KEY` | Public |
101
+ | podverse-notifications | `WEBPUSH_VAPID_PRIVATE_KEY` | Private (secret) |
102
+
103
+ If keys don't match, push notifications will fail with authentication errors.
104
+
105
+ ## Generating VAPID Keys
106
+
107
+ VAPID (Voluntary Application Server Identification) keys are required for Web Push notifications.
108
+
109
+ ### Method 1: Using web-push CLI (Recommended)
110
+
111
+ ```bash
112
+ # Install web-push globally
113
+ npm install -g web-push
114
+
115
+ # Generate VAPID keys
116
+ web-push generate-vapid-keys
117
+ ```
118
+
119
+ Output:
120
+ ```
121
+ =======================================
122
+
123
+ Public Key:
124
+ BNxIq7...your-public-key...
125
+
126
+ Private Key:
127
+ AkT3Xy...your-private-key...
128
+
129
+ =======================================
130
+ ```
131
+
132
+ ### Method 2: Using npx (No Install)
133
+
134
+ ```bash
135
+ npx web-push generate-vapid-keys
136
+ ```
137
+
138
+ ### Method 3: Using Node.js Script
139
+
140
+ ```javascript
141
+ const webpush = require('web-push');
142
+ const keys = webpush.generateVAPIDKeys();
143
+ console.log('Public Key:', keys.publicKey);
144
+ console.log('Private Key:', keys.privateKey);
145
+ ```
146
+
147
+ ### Important Notes
148
+
149
+ - **Keep the private key secret** - Only the backend needs it
150
+ - **Use the same key pair everywhere** - Frontend (public only) and backend (public + private) must use the same key pair
151
+ - **Don't regenerate unnecessarily** - Changing keys invalidates all existing push subscriptions
152
+ - **VAPID subject** - Should be a `mailto:` or `https://` URL identifying your service
153
+
154
+ ## Usage
155
+
156
+ ### Notification Orchestrator
157
+
158
+ The main entry point for sending notifications:
159
+
160
+ ```typescript
161
+ import { notificationOrchestrator } from 'podverse-notifications';
162
+
163
+ // Send Web Push notifications (native, no third-party)
164
+ // The endpoint URL is provided by the browser when the user subscribes
165
+ await notificationOrchestrator({
166
+ service: 'webpush',
167
+ subscriptions: [
168
+ {
169
+ endpoint: 'https://push.example.com/...', // Browser-provided endpoint
170
+ keys: {
171
+ p256dh: 'public-key-from-subscription',
172
+ auth: 'auth-secret-from-subscription'
173
+ }
174
+ }
175
+ ],
176
+ messageText: 'New Episode: My Podcast',
177
+ messageType: 'new-episode',
178
+ locale: 'en',
179
+ linkIdText: 'episode-id-text'
180
+ });
181
+
182
+ // Send Firebase FCM notifications (for mobile apps)
183
+ await notificationOrchestrator({
184
+ service: 'firebase',
185
+ tokens: ['fcm-token-1', 'fcm-token-2'],
186
+ platform: 'android', // or 'ios'
187
+ messageText: 'New Episode: My Podcast',
188
+ messageType: 'new-episode',
189
+ locale: 'en',
190
+ linkIdText: 'episode-id-text'
191
+ });
192
+ ```
193
+
194
+ ### Message Types
195
+
196
+ Supported notification message types:
197
+
198
+ | Type | Description |
199
+ |------|-------------|
200
+ | `new` | Generic new content |
201
+ | `new-episode` | New podcast episode |
202
+ | `new-podcast` | New podcast added |
203
+ | `new-video` | New video |
204
+ | `new-video-channel` | New video channel |
205
+ | `new-track` | New music track |
206
+ | `new-album` | New music album |
207
+ | `livestream-started` | Livestream is now live |
208
+ | `livestream-scheduled` | Livestream scheduled |
209
+
210
+ ### Direct Web Push API
211
+
212
+ For lower-level access:
213
+
214
+ ```typescript
215
+ import {
216
+ sendWebPushNotificationBatch,
217
+ WebPushSubscription
218
+ } from 'podverse-notifications';
219
+
220
+ const subscriptions: WebPushSubscription[] = [
221
+ {
222
+ endpoint: 'https://...',
223
+ keys: { p256dh: '...', auth: '...' }
224
+ }
225
+ ];
226
+
227
+ const results = await sendWebPushNotificationBatch(subscriptions, {
228
+ title: 'Notification Title',
229
+ body: 'Notification body text',
230
+ icon: 'https://example.com/icon.png',
231
+ link: '/episode/abc123'
232
+ });
233
+ ```
234
+
235
+ ## Architecture
236
+
237
+ ```
238
+ podverse-notifications/
239
+ ├── src/
240
+ │ ├── config/
241
+ │ │ └── index.ts # Configuration and URL helpers
242
+ │ ├── services/
243
+ │ │ ├── notifications/
244
+ │ │ │ ├── i18nNotifications.ts # Localized message prefixes
245
+ │ │ │ ├── notificationOrchestrator.ts # Main orchestrator
246
+ │ │ │ └── index.ts
247
+ │ │ └── webpush/
248
+ │ │ ├── webpushAdmin.ts # web-push initialization
249
+ │ │ ├── webpushHelpers.ts # Types and utilities
250
+ │ │ ├── webpushNotification.ts # Send notifications
251
+ │ │ ├── webpushNotificationOrchestrator.ts
252
+ │ │ └── index.ts
253
+ │ └── index.ts
254
+ ```
255
+
256
+ ## License
257
+
258
+ AGPLv3
@@ -0,0 +1,17 @@
1
+ export declare const config: {
2
+ web: {
3
+ protocol: string;
4
+ host: string;
5
+ icon_image_path: string;
6
+ };
7
+ webpush: {
8
+ enabled: boolean;
9
+ vapid_public_key: string;
10
+ vapid_private_key: string;
11
+ vapid_subject: string;
12
+ };
13
+ };
14
+ export declare function getWebBaseUrl(): string;
15
+ export declare function getWebBaseUrlWithPath(path: string): string;
16
+ export declare function getWebIconImageUrl(): string;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;;;;;;;;;CAYlB,CAAA;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.config = void 0;
4
+ exports.getWebBaseUrl = getWebBaseUrl;
5
+ exports.getWebBaseUrlWithPath = getWebBaseUrlWithPath;
6
+ exports.getWebIconImageUrl = getWebIconImageUrl;
7
+ exports.config = {
8
+ web: {
9
+ protocol: process.env.WEB_PROTOCOL || "http",
10
+ host: process.env.WEB_DOMAIN || "localhost",
11
+ icon_image_path: process.env.WEB_ICON_IMAGE_PATH || ""
12
+ },
13
+ webpush: {
14
+ enabled: process.env.WEBPUSH_ENABLED === "true",
15
+ vapid_public_key: process.env.WEBPUSH_VAPID_PUBLIC_KEY || "",
16
+ vapid_private_key: process.env.WEBPUSH_VAPID_PRIVATE_KEY || "",
17
+ vapid_subject: process.env.WEBPUSH_VAPID_SUBJECT || "mailto:contact@podverse.fm"
18
+ }
19
+ };
20
+ function getWebBaseUrl() {
21
+ return `${exports.config.web.protocol}://${exports.config.web.host}`;
22
+ }
23
+ function getWebBaseUrlWithPath(path) {
24
+ const base = getWebBaseUrl();
25
+ const cleanPath = path.startsWith('/') ? path : `/${path}`;
26
+ return `${base}${cleanPath}`;
27
+ }
28
+ function getWebIconImageUrl() {
29
+ return `${getWebBaseUrl()}${exports.config.web.icon_image_path}`;
30
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,5 @@
1
+ export * from './config';
2
+ export * from './services/notifications';
3
+ export * from './services/webpush';
4
+ export * from './services/unifiedpush';
1
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,20 @@
1
1
  "use strict";
2
- console.log('Starting notification service...');
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./config"), exports);
18
+ __exportStar(require("./services/notifications"), exports);
19
+ __exportStar(require("./services/webpush"), exports);
20
+ __exportStar(require("./services/unifiedpush"), exports);
@@ -0,0 +1,4 @@
1
+ export type NotificationMessageType = 'new' | 'new-episode' | 'new-podcast' | 'new-video' | 'new-video-channel' | 'new-track' | 'new-album' | 'livestream-started' | 'livestream-scheduled';
2
+ export type NotificationLocaleMap = Record<NotificationMessageType, string>;
3
+ export declare const i18nNotifications: Record<string, NotificationLocaleMap>;
4
+ //# sourceMappingURL=i18nNotifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18nNotifications.d.ts","sourceRoot":"","sources":["../../../src/services/notifications/i18nNotifications.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,uBAAuB,GAAG,KAAK,GACvC,aAAa,GAAG,aAAa,GAC7B,WAAW,GAAG,mBAAmB,GACjC,WAAW,GAAG,WAAW,GACzB,oBAAoB,GAAG,sBAAsB,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CA6CnE,CAAC"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.i18nNotifications = void 0;
4
+ exports.i18nNotifications = {
5
+ "en": {
6
+ "new": "",
7
+ "new-episode": "",
8
+ "new-podcast": "",
9
+ "new-video": "",
10
+ "new-video-channel": "",
11
+ "new-track": "",
12
+ "new-album": "",
13
+ "livestream-started": "Live: ",
14
+ "livestream-scheduled": "Live Scheduled: "
15
+ },
16
+ "es": {
17
+ "new": "",
18
+ "new-episode": "",
19
+ "new-podcast": "",
20
+ "new-video": "",
21
+ "new-video-channel": "",
22
+ "new-track": "",
23
+ "new-album": "",
24
+ "livestream-started": "En vivo: ",
25
+ "livestream-scheduled": "En vivo programado: "
26
+ },
27
+ "fr": {
28
+ "new": "",
29
+ "new-episode": "",
30
+ "new-podcast": "",
31
+ "new-video": "",
32
+ "new-video-channel": "",
33
+ "new-track": "",
34
+ "new-album": "",
35
+ "livestream-started": "En direct: ",
36
+ "livestream-scheduled": "En direct programmé: "
37
+ },
38
+ "el-GR": {
39
+ "new": "",
40
+ "new-episode": "",
41
+ "new-podcast": "",
42
+ "new-video": "",
43
+ "new-video-channel": "",
44
+ "new-track": "",
45
+ "new-album": "",
46
+ "livestream-started": "Ζωντανά: ",
47
+ "livestream-scheduled": "Προγραμματισμένα ζωντανά: "
48
+ }
49
+ };
@@ -0,0 +1,3 @@
1
+ export * from './i18nNotifications';
2
+ export * from './notificationOrchestrator';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/notifications/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./i18nNotifications"), exports);
18
+ __exportStar(require("./notificationOrchestrator"), exports);
@@ -0,0 +1,34 @@
1
+ import { WebPushSubscription } from '../webpush';
2
+ import { UPSubscription } from '../unifiedpush';
3
+ import { NotificationMessageType } from './i18nNotifications';
4
+ export type NotificationPlatform = 'web' | 'android' | 'ios';
5
+ export type NotificationService = 'firebase' | 'webpush' | 'unifiedpush';
6
+ type BaseNotificationOrchestratorParams = {
7
+ messageText: string;
8
+ messageType: NotificationMessageType;
9
+ locale: string;
10
+ icon?: string;
11
+ linkIdText?: string;
12
+ mediumId: number;
13
+ data?: Record<string, unknown>;
14
+ };
15
+ type FirebaseNotificationOrchestratorParams = BaseNotificationOrchestratorParams & {
16
+ service: 'firebase';
17
+ tokens: string[];
18
+ platform: NotificationPlatform;
19
+ channelId?: string;
20
+ badge?: number;
21
+ sound?: string;
22
+ };
23
+ type WebPushNotificationOrchestratorParams = BaseNotificationOrchestratorParams & {
24
+ service: 'webpush';
25
+ subscriptions: WebPushSubscription[];
26
+ };
27
+ type UnifiedPushNotificationOrchestratorParams = BaseNotificationOrchestratorParams & {
28
+ service: 'unifiedpush';
29
+ subscriptions: UPSubscription[];
30
+ };
31
+ export type NotificationOrchestratorParams = FirebaseNotificationOrchestratorParams | WebPushNotificationOrchestratorParams | UnifiedPushNotificationOrchestratorParams;
32
+ export declare function notificationOrchestrator(params: NotificationOrchestratorParams): Promise<any[]>;
33
+ export {};
34
+ //# sourceMappingURL=notificationOrchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notificationOrchestrator.d.ts","sourceRoot":"","sources":["../../../src/services/notifications/notificationOrchestrator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAqB,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAEjF,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;AAC7D,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;AAmCzE,KAAK,kCAAkC,GAAG;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,uBAAuB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAGF,KAAK,sCAAsC,GAAG,kCAAkC,GAAG;IACjF,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF,KAAK,qCAAqC,GAAG,kCAAkC,GAAG;IAChF,OAAO,EAAE,SAAS,CAAC;IACnB,aAAa,EAAE,mBAAmB,EAAE,CAAC;CACtC,CAAC;AAGF,KAAK,yCAAyC,GAAG,kCAAkC,GAAG;IACpF,OAAO,EAAE,aAAa,CAAC;IACvB,aAAa,EAAE,cAAc,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,8BAA8B,GACtC,sCAAsC,GACtC,qCAAqC,GACrC,yCAAyC,CAAC;AAS9C,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,8BAA8B,kBAoDpF"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.notificationOrchestrator = notificationOrchestrator;
13
+ const podverse_helpers_1 = require("podverse-helpers");
14
+ const podverse_external_services_1 = require("podverse-external-services");
15
+ const webpush_1 = require("../webpush");
16
+ const unifiedpush_1 = require("../unifiedpush");
17
+ const i18nNotifications_1 = require("./i18nNotifications");
18
+ /**
19
+ * Gets the URL path prefix for a given notification message type
20
+ * @param messageType - The type of notification message
21
+ * @param mediumId - Medium ID for medium-specific paths (e.g., livestreams)
22
+ */
23
+ function getLinkPathFromMessageType(messageType, mediumId) {
24
+ switch (messageType) {
25
+ case 'new-episode':
26
+ return '/episode';
27
+ case 'new-podcast':
28
+ return '/podcast';
29
+ case 'new-video':
30
+ return '/video';
31
+ case 'new-video-channel':
32
+ return '/channel';
33
+ case 'new-track':
34
+ return '/track';
35
+ case 'new-album':
36
+ return '/album';
37
+ case 'livestream-started':
38
+ case 'livestream-scheduled':
39
+ // Music livestreams use /music/livestream, all others use /podcast/livestream
40
+ if (mediumId === podverse_helpers_1.MediumEnum.Music) {
41
+ return '/music/livestream';
42
+ }
43
+ return '/podcast/livestream';
44
+ case 'new':
45
+ default:
46
+ return '';
47
+ }
48
+ }
49
+ function getFinalText(messageText, messageType, locale) {
50
+ const baseLocale = locale.includes('-') ? locale.split('-')[0] : locale;
51
+ const localeMap = i18nNotifications_1.i18nNotifications[locale] || i18nNotifications_1.i18nNotifications[baseLocale] || i18nNotifications_1.i18nNotifications.en;
52
+ const prefix = localeMap[messageType] || i18nNotifications_1.i18nNotifications.en[messageType];
53
+ return `${prefix}${messageText}`;
54
+ }
55
+ function notificationOrchestrator(params) {
56
+ return __awaiter(this, void 0, void 0, function* () {
57
+ const { service, messageText, messageType, locale, linkIdText, mediumId, icon, data } = params;
58
+ const finalText = getFinalText(messageText, messageType, locale);
59
+ // Construct the link from messageType, mediumId, and linkIdText
60
+ let link;
61
+ if (linkIdText) {
62
+ const pathPrefix = getLinkPathFromMessageType(messageType, mediumId);
63
+ link = pathPrefix ? `${pathPrefix}/${linkIdText}` : undefined;
64
+ }
65
+ switch (service) {
66
+ case 'firebase': {
67
+ const firebaseParams = params;
68
+ return yield (0, podverse_external_services_1.firebaseNotificationBatchOrchestrator)({
69
+ tokens: firebaseParams.tokens,
70
+ platform: firebaseParams.platform,
71
+ finalText,
72
+ link,
73
+ icon,
74
+ channelId: firebaseParams.channelId,
75
+ badge: firebaseParams.badge,
76
+ sound: firebaseParams.sound,
77
+ data,
78
+ });
79
+ }
80
+ case 'webpush': {
81
+ const webpushParams = params;
82
+ return yield (0, webpush_1.webpushNotificationBatchOrchestrator)({
83
+ subscriptions: webpushParams.subscriptions,
84
+ finalText,
85
+ link,
86
+ icon,
87
+ data,
88
+ });
89
+ }
90
+ case 'unifiedpush': {
91
+ const upParams = params;
92
+ return yield (0, unifiedpush_1.unifiedpushNotificationBatchOrchestrator)({
93
+ subscriptions: upParams.subscriptions,
94
+ finalText,
95
+ link,
96
+ icon,
97
+ data,
98
+ });
99
+ }
100
+ default:
101
+ throw new Error(`Unsupported notification service: ${service}`);
102
+ }
103
+ });
104
+ }
@@ -0,0 +1,4 @@
1
+ export { UPSubscription } from './unifiedpushHelpers';
2
+ export { sendUPNotificationBatch } from './unifiedpushNotification';
3
+ export { unifiedpushNotificationBatchOrchestrator } from './unifiedpushNotificationOrchestrator';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/unifiedpush/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,wCAAwC,EAAE,MAAM,uCAAuC,CAAC"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unifiedpushNotificationBatchOrchestrator = exports.sendUPNotificationBatch = void 0;
4
+ var unifiedpushNotification_1 = require("./unifiedpushNotification");
5
+ Object.defineProperty(exports, "sendUPNotificationBatch", { enumerable: true, get: function () { return unifiedpushNotification_1.sendUPNotificationBatch; } });
6
+ var unifiedpushNotificationOrchestrator_1 = require("./unifiedpushNotificationOrchestrator");
7
+ Object.defineProperty(exports, "unifiedpushNotificationBatchOrchestrator", { enumerable: true, get: function () { return unifiedpushNotificationOrchestrator_1.unifiedpushNotificationBatchOrchestrator; } });
@@ -0,0 +1,5 @@
1
+ export type UPSubscription = {
2
+ up_endpoint: string;
3
+ up_auth_key: string | null;
4
+ };
5
+ //# sourceMappingURL=unifiedpushHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unifiedpushHelpers.d.ts","sourceRoot":"","sources":["../../../src/services/unifiedpush/unifiedpushHelpers.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,25 @@
1
+ import { UPSubscription } from './unifiedpushHelpers';
2
+ type UPPayload = {
3
+ title: string;
4
+ body?: string;
5
+ icon?: string;
6
+ image?: string;
7
+ link?: string;
8
+ data?: Record<string, unknown>;
9
+ };
10
+ type UPResult = {
11
+ success: boolean;
12
+ endpoint: string;
13
+ error?: string;
14
+ };
15
+ /**
16
+ * Sends a notification to a Unified Push endpoint.
17
+ *
18
+ * Unified Push is a simple protocol - just POST the notification data to the endpoint.
19
+ * The endpoint is typically a service like ntfy.sh that the user has configured.
20
+ *
21
+ * @see https://unifiedpush.org/spec/android/
22
+ */
23
+ export declare function sendUPNotificationBatch(subscriptions: UPSubscription[], payload: UPPayload): Promise<UPResult[]>;
24
+ export {};
25
+ //# sourceMappingURL=unifiedpushNotification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unifiedpushNotification.d.ts","sourceRoot":"","sources":["../../../src/services/unifiedpush/unifiedpushNotification.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,KAAK,SAAS,GAAG;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,cAAc,EAAE,EAC/B,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAyErB"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.sendUPNotificationBatch = sendUPNotificationBatch;
13
+ const podverse_helpers_1 = require("podverse-helpers");
14
+ const config_1 = require("../../config");
15
+ /**
16
+ * Sends a notification to a Unified Push endpoint.
17
+ *
18
+ * Unified Push is a simple protocol - just POST the notification data to the endpoint.
19
+ * The endpoint is typically a service like ntfy.sh that the user has configured.
20
+ *
21
+ * @see https://unifiedpush.org/spec/android/
22
+ */
23
+ function sendUPNotificationBatch(subscriptions, payload) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ var _a;
26
+ const chunks = (0, podverse_helpers_1.chunkArray)(subscriptions, 100);
27
+ const allResults = [];
28
+ for (const chunk of chunks) {
29
+ const chunkResults = yield Promise.allSettled(chunk.map((subscription) => __awaiter(this, void 0, void 0, function* () {
30
+ try {
31
+ // Use ntfy header-based format for proper notification display
32
+ // @see https://docs.ntfy.sh/publish/#publish-as-json
33
+ const headers = {
34
+ 'Content-Type': 'text/plain',
35
+ 'X-Title': payload.title,
36
+ 'X-Click': payload.link ? (0, config_1.getWebBaseUrlWithPath)(payload.link) : (0, config_1.getWebBaseUrl)(),
37
+ 'X-Icon': payload.icon || (0, config_1.getWebIconImageUrl)(),
38
+ };
39
+ // Add image as attachment for preview
40
+ if (payload.image) {
41
+ headers['X-Attach'] = payload.image;
42
+ }
43
+ // Add authorization if auth key is provided
44
+ if (subscription.up_auth_key) {
45
+ headers['Authorization'] = `Bearer ${subscription.up_auth_key}`;
46
+ }
47
+ // Message body is sent as plain text
48
+ const messageBody = payload.body || '';
49
+ const response = yield fetch(subscription.up_endpoint, {
50
+ method: 'POST',
51
+ headers,
52
+ body: messageBody,
53
+ });
54
+ if (!response.ok) {
55
+ const errorText = yield response.text().catch(() => 'Unknown error');
56
+ console.error(`UP send failed for ${subscription.up_endpoint}: ${response.status} - ${errorText}`);
57
+ return {
58
+ success: false,
59
+ endpoint: subscription.up_endpoint,
60
+ error: `HTTP ${response.status}: ${errorText}`
61
+ };
62
+ }
63
+ return { success: true, endpoint: subscription.up_endpoint };
64
+ }
65
+ catch (error) {
66
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
67
+ console.error(`UP send failed for ${subscription.up_endpoint}:`, errorMessage);
68
+ return {
69
+ success: false,
70
+ endpoint: subscription.up_endpoint,
71
+ error: errorMessage
72
+ };
73
+ }
74
+ })));
75
+ for (const result of chunkResults) {
76
+ if (result.status === 'fulfilled') {
77
+ allResults.push(result.value);
78
+ }
79
+ else {
80
+ allResults.push({
81
+ success: false,
82
+ endpoint: 'unknown',
83
+ error: ((_a = result.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Promise rejected'
84
+ });
85
+ }
86
+ }
87
+ }
88
+ return allResults;
89
+ });
90
+ }
@@ -0,0 +1,15 @@
1
+ import { UPSubscription } from './unifiedpushHelpers';
2
+ type UPOrchestratorParams = {
3
+ subscriptions: UPSubscription[];
4
+ finalText: string;
5
+ icon?: string;
6
+ link?: string;
7
+ data?: Record<string, unknown>;
8
+ };
9
+ export declare function unifiedpushNotificationBatchOrchestrator(params: UPOrchestratorParams): Promise<{
10
+ success: boolean;
11
+ endpoint: string;
12
+ error?: string;
13
+ }[]>;
14
+ export {};
15
+ //# sourceMappingURL=unifiedpushNotificationOrchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unifiedpushNotificationOrchestrator.d.ts","sourceRoot":"","sources":["../../../src/services/unifiedpush/unifiedpushNotificationOrchestrator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,KAAK,oBAAoB,GAAG;IAC1B,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,wBAAsB,wCAAwC,CAAC,MAAM,EAAE,oBAAoB;;;;KAc1F"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.unifiedpushNotificationBatchOrchestrator = unifiedpushNotificationBatchOrchestrator;
13
+ const unifiedpushNotification_1 = require("./unifiedpushNotification");
14
+ function unifiedpushNotificationBatchOrchestrator(params) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ const { subscriptions, finalText, icon, link, data } = params;
17
+ const payload = {
18
+ title: finalText,
19
+ body: '',
20
+ icon,
21
+ link,
22
+ data,
23
+ };
24
+ console.log(`[unifiedpushNotificationBatchOrchestrator] Sending to ${subscriptions.length} subscriptions`);
25
+ return yield (0, unifiedpushNotification_1.sendUPNotificationBatch)(subscriptions, payload);
26
+ });
27
+ }
@@ -0,0 +1,5 @@
1
+ export { webpushAdmin, isWebPushEnabled } from './webpushAdmin';
2
+ export { WebPushSubscription } from './webpushHelpers';
3
+ export { sendWebPushNotificationBatch } from './webpushNotification';
4
+ export { webpushNotificationBatchOrchestrator } from './webpushNotificationOrchestrator';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/webpush/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,oCAAoC,EAAE,MAAM,mCAAmC,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.webpushNotificationBatchOrchestrator = exports.sendWebPushNotificationBatch = exports.isWebPushEnabled = exports.webpushAdmin = void 0;
4
+ var webpushAdmin_1 = require("./webpushAdmin");
5
+ Object.defineProperty(exports, "webpushAdmin", { enumerable: true, get: function () { return webpushAdmin_1.webpushAdmin; } });
6
+ Object.defineProperty(exports, "isWebPushEnabled", { enumerable: true, get: function () { return webpushAdmin_1.isWebPushEnabled; } });
7
+ var webpushNotification_1 = require("./webpushNotification");
8
+ Object.defineProperty(exports, "sendWebPushNotificationBatch", { enumerable: true, get: function () { return webpushNotification_1.sendWebPushNotificationBatch; } });
9
+ var webpushNotificationOrchestrator_1 = require("./webpushNotificationOrchestrator");
10
+ Object.defineProperty(exports, "webpushNotificationBatchOrchestrator", { enumerable: true, get: function () { return webpushNotificationOrchestrator_1.webpushNotificationBatchOrchestrator; } });
@@ -0,0 +1,4 @@
1
+ import webpush from 'web-push';
2
+ export declare const webpushAdmin: typeof webpush | null;
3
+ export declare const isWebPushEnabled: boolean;
4
+ //# sourceMappingURL=webpushAdmin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpushAdmin.d.ts","sourceRoot":"","sources":["../../../src/services/webpush/webpushAdmin.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,UAAU,CAAC;AAyB/B,eAAO,MAAM,YAAY,uBAAsC,CAAC;AAChE,eAAO,MAAM,gBAAgB,SAAqB,CAAC"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isWebPushEnabled = exports.webpushAdmin = void 0;
7
+ const web_push_1 = __importDefault(require("web-push"));
8
+ const config_1 = require("../../config");
9
+ let webpushInitialized = false;
10
+ if (!config_1.config.webpush.enabled) {
11
+ console.warn("Web Push notifications are disabled in the configuration.");
12
+ }
13
+ else if (!config_1.config.webpush.vapid_public_key || !config_1.config.webpush.vapid_private_key) {
14
+ console.warn("Web Push VAPID keys are not configured.");
15
+ }
16
+ else {
17
+ console.log("Web Push notifications are enabled in the configuration.");
18
+ try {
19
+ web_push_1.default.setVapidDetails(config_1.config.webpush.vapid_subject, config_1.config.webpush.vapid_public_key, config_1.config.webpush.vapid_private_key);
20
+ webpushInitialized = true;
21
+ console.log("Web Push Admin Initialized Successfully");
22
+ }
23
+ catch (error) {
24
+ console.error("Web Push Admin Initialization Failed:", error);
25
+ }
26
+ }
27
+ exports.webpushAdmin = webpushInitialized ? web_push_1.default : null;
28
+ exports.isWebPushEnabled = webpushInitialized;
@@ -0,0 +1,8 @@
1
+ export type WebPushSubscription = {
2
+ endpoint: string;
3
+ keys: {
4
+ p256dh: string;
5
+ auth: string;
6
+ };
7
+ };
8
+ //# sourceMappingURL=webpushHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpushHelpers.d.ts","sourceRoot":"","sources":["../../../src/services/webpush/webpushHelpers.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ import { WebPushSubscription } from './webpushHelpers';
2
+ type WebPushPayload = {
3
+ title: string;
4
+ body?: string;
5
+ icon?: string;
6
+ image?: string;
7
+ link?: string;
8
+ data?: Record<string, unknown>;
9
+ };
10
+ type WebPushResult = {
11
+ success: boolean;
12
+ endpoint: string;
13
+ error?: string;
14
+ };
15
+ export declare function sendWebPushNotificationBatch(subscriptions: WebPushSubscription[], payload: WebPushPayload): Promise<WebPushResult[]>;
16
+ export {};
17
+ //# sourceMappingURL=webpushNotification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpushNotification.d.ts","sourceRoot":"","sources":["../../../src/services/webpush/webpushNotification.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGvD,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,mBAAmB,EAAE,EACpC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,EAAE,CAAC,CA2D1B"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.sendWebPushNotificationBatch = sendWebPushNotificationBatch;
13
+ const podverse_helpers_1 = require("podverse-helpers");
14
+ const webpushAdmin_1 = require("./webpushAdmin");
15
+ const config_1 = require("../../config");
16
+ function sendWebPushNotificationBatch(subscriptions, payload) {
17
+ return __awaiter(this, void 0, void 0, function* () {
18
+ var _a;
19
+ if (!webpushAdmin_1.webpushAdmin) {
20
+ throw new Error("Web Push Admin is not initialized");
21
+ }
22
+ const webpush = webpushAdmin_1.webpushAdmin;
23
+ const chunks = (0, podverse_helpers_1.chunkArray)(subscriptions, 100);
24
+ const allResults = [];
25
+ for (const chunk of chunks) {
26
+ const notificationPayload = JSON.stringify({
27
+ title: payload.title,
28
+ body: payload.body || "",
29
+ icon: payload.icon || (0, config_1.getWebIconImageUrl)(),
30
+ image: payload.image,
31
+ link: payload.link ? (0, config_1.getWebBaseUrlWithPath)(payload.link) : (0, config_1.getWebBaseUrl)(),
32
+ data: payload.data,
33
+ });
34
+ const chunkResults = yield Promise.allSettled(chunk.map((subscription) => __awaiter(this, void 0, void 0, function* () {
35
+ try {
36
+ yield webpush.sendNotification({
37
+ endpoint: subscription.endpoint,
38
+ keys: subscription.keys,
39
+ }, notificationPayload, {
40
+ TTL: 86400, // 24 hours
41
+ urgency: 'normal',
42
+ });
43
+ return { success: true, endpoint: subscription.endpoint };
44
+ }
45
+ catch (error) {
46
+ console.error(`Web Push send failed for ${subscription.endpoint}:`, error.message);
47
+ return {
48
+ success: false,
49
+ endpoint: subscription.endpoint,
50
+ error: error.message || 'Unknown error'
51
+ };
52
+ }
53
+ })));
54
+ for (const result of chunkResults) {
55
+ if (result.status === 'fulfilled') {
56
+ allResults.push(result.value);
57
+ }
58
+ else {
59
+ allResults.push({
60
+ success: false,
61
+ endpoint: 'unknown',
62
+ error: ((_a = result.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Promise rejected'
63
+ });
64
+ }
65
+ }
66
+ }
67
+ return allResults;
68
+ });
69
+ }
@@ -0,0 +1,15 @@
1
+ import { WebPushSubscription } from './webpushHelpers';
2
+ type WebPushOrchestratorParams = {
3
+ subscriptions: WebPushSubscription[];
4
+ finalText: string;
5
+ icon?: string;
6
+ link?: string;
7
+ data?: Record<string, unknown>;
8
+ };
9
+ export declare function webpushNotificationBatchOrchestrator(params: WebPushOrchestratorParams): Promise<{
10
+ success: boolean;
11
+ endpoint: string;
12
+ error?: string;
13
+ }[]>;
14
+ export {};
15
+ //# sourceMappingURL=webpushNotificationOrchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpushNotificationOrchestrator.d.ts","sourceRoot":"","sources":["../../../src/services/webpush/webpushNotificationOrchestrator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,KAAK,yBAAyB,GAAG;IAC/B,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,wBAAsB,oCAAoC,CAAC,MAAM,EAAE,yBAAyB;;;;KAc3F"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.webpushNotificationBatchOrchestrator = webpushNotificationBatchOrchestrator;
13
+ const webpushNotification_1 = require("./webpushNotification");
14
+ function webpushNotificationBatchOrchestrator(params) {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ const { subscriptions, finalText, icon, link, data } = params;
17
+ const payload = {
18
+ title: finalText,
19
+ body: '',
20
+ icon,
21
+ link,
22
+ data,
23
+ };
24
+ console.log(`[webpushNotificationBatchOrchestrator] Sending to ${subscriptions.length} subscriptions`);
25
+ return yield (0, webpushNotification_1.sendWebPushNotificationBatch)(subscriptions, payload);
26
+ });
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podverse-notifications",
3
- "version": "1.0.0",
3
+ "version": "5.1.20-alpha.0",
4
4
  "description": "Push notification helper module for Podverse use cases",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,14 +17,16 @@
17
17
  "license": "AGPLv3",
18
18
  "dependencies": {
19
19
  "module-alias": "^2.2.3",
20
- "podverse-external-services": "^5.1.12",
21
- "podverse-helpers": "^5.1.12"
20
+ "podverse-external-services": "^5.1.20-alpha.0",
21
+ "podverse-helpers": "^5.1.20-alpha.0",
22
+ "web-push": "^3.6.7"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@eslint/config-array": "^0.21.0",
25
26
  "@eslint/eslintrc": "^3.3.1",
26
27
  "@eslint/js": "^9.35.0",
27
28
  "@types/node": "^24.4.0",
29
+ "@types/web-push": "^3.6.4",
28
30
  "eslint": "^9.35.0",
29
31
  "nodemon": "^3.1.10",
30
32
  "ts-node": "^10.9.2",