@djangocfg/nextjs 2.1.138 → 2.1.140
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/README.md +1 -138
- package/dist/config/index.mjs +4 -18
- package/dist/config/index.mjs.map +1 -1
- package/dist/i18n/routing.d.mts +4 -4
- package/dist/index.mjs +4 -18
- package/dist/index.mjs.map +1 -1
- package/dist/pwa/index.d.mts +1 -80
- package/dist/pwa/index.mjs +0 -91
- package/dist/pwa/index.mjs.map +1 -1
- package/dist/pwa/worker/index.d.mts +0 -13
- package/dist/pwa/worker/index.mjs +1 -57
- package/dist/pwa/worker/index.mjs.map +1 -1
- package/package.json +11 -25
- package/src/pwa/index.ts +0 -1
- package/src/pwa/worker/index.ts +0 -81
- package/dist/pwa/cli.d.mts +0 -1
- package/dist/pwa/cli.mjs +0 -140
- package/dist/pwa/cli.mjs.map +0 -1
- package/dist/pwa/server/index.d.mts +0 -86
- package/dist/pwa/server/index.mjs +0 -175
- package/dist/pwa/server/index.mjs.map +0 -1
- package/dist/pwa/server/routes.d.mts +0 -2
- package/dist/pwa/server/routes.mjs +0 -149
- package/dist/pwa/server/routes.mjs.map +0 -1
- package/dist/routes-DXA29sS_.d.mts +0 -68
- package/src/pwa/cli.ts +0 -171
- package/src/pwa/notifications.ts +0 -192
- package/src/pwa/server/index.ts +0 -23
- package/src/pwa/server/push.ts +0 -166
- package/src/pwa/server/routes.ts +0 -138
package/dist/pwa/cli.mjs
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/pwa/cli.ts
|
|
4
|
-
import { consola } from "consola";
|
|
5
|
-
import webpush from "web-push";
|
|
6
|
-
var args = process.argv.slice(2);
|
|
7
|
-
var command = args[0];
|
|
8
|
-
async function generateVapidKeys() {
|
|
9
|
-
consola.box("VAPID Keys Generator");
|
|
10
|
-
consola.info("Generating new VAPID key pair...\n");
|
|
11
|
-
const vapidKeys = webpush.generateVAPIDKeys();
|
|
12
|
-
consola.success("\u2705 VAPID keys generated!\n");
|
|
13
|
-
consola.log("Add these to your .env.local:\n");
|
|
14
|
-
consola.log(`NEXT_PUBLIC_VAPID_PUBLIC_KEY=${vapidKeys.publicKey}`);
|
|
15
|
-
consola.log(`VAPID_PRIVATE_KEY=${vapidKeys.privateKey}`);
|
|
16
|
-
consola.log(`VAPID_MAILTO=mailto:your-email@example.com
|
|
17
|
-
`);
|
|
18
|
-
consola.info("Public key (share with clients):");
|
|
19
|
-
consola.log(` ${vapidKeys.publicKey}
|
|
20
|
-
`);
|
|
21
|
-
consola.info("Private key (keep secret):");
|
|
22
|
-
consola.log(` ${vapidKeys.privateKey}
|
|
23
|
-
`);
|
|
24
|
-
}
|
|
25
|
-
async function sendTestPush() {
|
|
26
|
-
consola.box("Send Test Push Notification");
|
|
27
|
-
const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
|
|
28
|
-
const privateKey = process.env.VAPID_PRIVATE_KEY;
|
|
29
|
-
const mailto = process.env.VAPID_MAILTO || "mailto:test@example.com";
|
|
30
|
-
if (!publicKey || !privateKey) {
|
|
31
|
-
consola.error("\u274C VAPID keys not configured!");
|
|
32
|
-
consola.info("\nGenerate keys with:");
|
|
33
|
-
consola.log(" pnpm pwa vapid\n");
|
|
34
|
-
consola.info("Then add to .env.local:");
|
|
35
|
-
consola.log(" NEXT_PUBLIC_VAPID_PUBLIC_KEY=...");
|
|
36
|
-
consola.log(" VAPID_PRIVATE_KEY=...");
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
const subscriptionArg = args[1];
|
|
40
|
-
if (!subscriptionArg) {
|
|
41
|
-
consola.error("\u274C Subscription required!\n");
|
|
42
|
-
consola.info("Usage:");
|
|
43
|
-
consola.log(` pnpm pwa send '{"endpoint":"...","keys":{...}}'
|
|
44
|
-
`);
|
|
45
|
-
consola.info("To subscribe and get the JSON, visit the playground:");
|
|
46
|
-
consola.log(" http://djangocfg.com/layouts/a2hs-hint\n");
|
|
47
|
-
consola.info("Or use the helper command:");
|
|
48
|
-
consola.log(" pnpm exec djangocfg-pwa status\n");
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
try {
|
|
52
|
-
const subscription = JSON.parse(subscriptionArg);
|
|
53
|
-
webpush.setVapidDetails(mailto, publicKey, privateKey);
|
|
54
|
-
const payload = JSON.stringify({
|
|
55
|
-
title: "Test Push from CLI",
|
|
56
|
-
body: "This is a test notification sent from @djangocfg/nextjs CLI",
|
|
57
|
-
icon: "/static/logos/192x192.png",
|
|
58
|
-
badge: "/static/logos/192x192.png"
|
|
59
|
-
});
|
|
60
|
-
consola.info("Sending push notification...\n");
|
|
61
|
-
await webpush.sendNotification(subscription, payload);
|
|
62
|
-
consola.success("\u2705 Push notification sent successfully!");
|
|
63
|
-
} catch (err) {
|
|
64
|
-
consola.error("\u274C Failed to send push:", err instanceof Error ? err.message : err);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
async function showStatus() {
|
|
69
|
-
consola.box("PWA Status");
|
|
70
|
-
const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
|
|
71
|
-
const privateKey = process.env.VAPID_PRIVATE_KEY;
|
|
72
|
-
const mailto = process.env.VAPID_MAILTO;
|
|
73
|
-
consola.log("Environment Variables:");
|
|
74
|
-
consola.log(` NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${publicKey ? "\u2705 Set" : "\u274C Not set"}`);
|
|
75
|
-
consola.log(` VAPID_PRIVATE_KEY: ${privateKey ? "\u2705 Set" : "\u274C Not set"}`);
|
|
76
|
-
consola.log(` VAPID_MAILTO: ${mailto || "\u26A0\uFE0F Not set (optional)"}
|
|
77
|
-
`);
|
|
78
|
-
if (!publicKey || !privateKey) {
|
|
79
|
-
consola.warn("\u26A0\uFE0F VAPID keys not configured. Run: pnpm pwa vapid\n");
|
|
80
|
-
} else {
|
|
81
|
-
consola.success("\u2705 VAPID keys configured\n");
|
|
82
|
-
consola.log("Public Key:");
|
|
83
|
-
consola.log(` ${publicKey.slice(0, 40)}...
|
|
84
|
-
`);
|
|
85
|
-
}
|
|
86
|
-
consola.log("Quick Start:");
|
|
87
|
-
consola.log(" 1. Generate VAPID keys: pnpm pwa vapid");
|
|
88
|
-
consola.log(" 2. Add to .env.local");
|
|
89
|
-
consola.log(" 3. Visit playground: http://djangocfg.com/layouts/a2hs-hint");
|
|
90
|
-
consola.log(" 4. Send test push: pnpm pwa send '<subscription>'\n");
|
|
91
|
-
}
|
|
92
|
-
async function showInfo() {
|
|
93
|
-
consola.box("DjangoCFG PWA CLI");
|
|
94
|
-
consola.log("Commands:");
|
|
95
|
-
consola.log(" vapid Generate VAPID keys");
|
|
96
|
-
consola.log(" send <subscription> Send test push notification");
|
|
97
|
-
consola.log(" status Show PWA configuration status");
|
|
98
|
-
consola.log(" info Show this help\n");
|
|
99
|
-
consola.log("Examples:");
|
|
100
|
-
consola.log(" # Generate VAPID keys");
|
|
101
|
-
consola.log(" pnpm pwa vapid\n");
|
|
102
|
-
consola.log(" # Check status");
|
|
103
|
-
consola.log(" pnpm pwa status\n");
|
|
104
|
-
consola.log(" # Subscribe");
|
|
105
|
-
consola.log(" Visit: http://djangocfg.com/layouts/a2hs-hint\n");
|
|
106
|
-
consola.log(" # Send test push");
|
|
107
|
-
consola.log(` pnpm pwa send '{"endpoint":"...","keys":{"p256dh":"...","auth":"..."}}'
|
|
108
|
-
`);
|
|
109
|
-
consola.log("Documentation:");
|
|
110
|
-
consola.log(" https://djangocfg.com/docs/pwa");
|
|
111
|
-
}
|
|
112
|
-
async function main() {
|
|
113
|
-
switch (command) {
|
|
114
|
-
case "vapid":
|
|
115
|
-
case "v":
|
|
116
|
-
case "keys":
|
|
117
|
-
await generateVapidKeys();
|
|
118
|
-
break;
|
|
119
|
-
case "send":
|
|
120
|
-
case "s":
|
|
121
|
-
case "push":
|
|
122
|
-
await sendTestPush();
|
|
123
|
-
break;
|
|
124
|
-
case "status":
|
|
125
|
-
case "st":
|
|
126
|
-
await showStatus();
|
|
127
|
-
break;
|
|
128
|
-
case "info":
|
|
129
|
-
case "i":
|
|
130
|
-
case "help":
|
|
131
|
-
case "h":
|
|
132
|
-
await showInfo();
|
|
133
|
-
break;
|
|
134
|
-
default:
|
|
135
|
-
await showInfo();
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
main();
|
|
140
|
-
//# sourceMappingURL=cli.mjs.map
|
package/dist/pwa/cli.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pwa/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * DjangoCFG PWA CLI\n *\n * Usage:\n * pnpm pwa vapid # Generate VAPID keys\n * pnpm pwa send # Send test push notification\n * pnpm pwa status # Show PWA status\n * pnpm pwa info # Show help\n */\n\nimport { consola } from 'consola';\nimport webpush from 'web-push';\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nasync function generateVapidKeys() {\n consola.box('VAPID Keys Generator');\n consola.info('Generating new VAPID key pair...\\n');\n\n const vapidKeys = webpush.generateVAPIDKeys();\n\n consola.success('✅ VAPID keys generated!\\n');\n consola.log('Add these to your .env.local:\\n');\n consola.log(`NEXT_PUBLIC_VAPID_PUBLIC_KEY=${vapidKeys.publicKey}`);\n consola.log(`VAPID_PRIVATE_KEY=${vapidKeys.privateKey}`);\n consola.log(`VAPID_MAILTO=mailto:your-email@example.com\\n`);\n\n consola.info('Public key (share with clients):');\n consola.log(` ${vapidKeys.publicKey}\\n`);\n consola.info('Private key (keep secret):');\n consola.log(` ${vapidKeys.privateKey}\\n`);\n}\n\nasync function sendTestPush() {\n consola.box('Send Test Push Notification');\n\n const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;\n const privateKey = process.env.VAPID_PRIVATE_KEY;\n const mailto = process.env.VAPID_MAILTO || 'mailto:test@example.com';\n\n if (!publicKey || !privateKey) {\n consola.error('❌ VAPID keys not configured!');\n consola.info('\\nGenerate keys with:');\n consola.log(' pnpm pwa vapid\\n');\n consola.info('Then add to .env.local:');\n consola.log(' NEXT_PUBLIC_VAPID_PUBLIC_KEY=...');\n consola.log(' VAPID_PRIVATE_KEY=...');\n process.exit(1);\n }\n\n // Get subscription from args or use example\n const subscriptionArg = args[1];\n if (!subscriptionArg) {\n consola.error('❌ Subscription required!\\n');\n consola.info('Usage:');\n consola.log(' pnpm pwa send \\'{\"endpoint\":\"...\",\"keys\":{...}}\\'\\n');\n consola.info('To subscribe and get the JSON, visit the playground:');\n consola.log(' http://djangocfg.com/layouts/a2hs-hint\\n');\n consola.info('Or use the helper command:');\n consola.log(' pnpm exec djangocfg-pwa status\\n');\n process.exit(1);\n }\n\n try {\n const subscription = JSON.parse(subscriptionArg);\n\n webpush.setVapidDetails(mailto, publicKey, privateKey);\n\n const payload = JSON.stringify({\n title: 'Test Push from CLI',\n body: 'This is a test notification sent from @djangocfg/nextjs CLI',\n icon: '/static/logos/192x192.png',\n badge: '/static/logos/192x192.png',\n });\n\n consola.info('Sending push notification...\\n');\n await webpush.sendNotification(subscription, payload);\n consola.success('✅ Push notification sent successfully!');\n } catch (err) {\n consola.error('❌ Failed to send push:', err instanceof Error ? err.message : err);\n process.exit(1);\n }\n}\n\nasync function showStatus() {\n consola.box('PWA Status');\n\n const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;\n const privateKey = process.env.VAPID_PRIVATE_KEY;\n const mailto = process.env.VAPID_MAILTO;\n\n consola.log('Environment Variables:');\n consola.log(` NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${publicKey ? '✅ Set' : '❌ Not set'}`);\n consola.log(` VAPID_PRIVATE_KEY: ${privateKey ? '✅ Set' : '❌ Not set'}`);\n consola.log(` VAPID_MAILTO: ${mailto || '⚠️ Not set (optional)'}\\n`);\n\n if (!publicKey || !privateKey) {\n consola.warn('⚠️ VAPID keys not configured. Run: pnpm pwa vapid\\n');\n } else {\n consola.success('✅ VAPID keys configured\\n');\n consola.log('Public Key:');\n consola.log(` ${publicKey.slice(0, 40)}...\\n`);\n }\n\n consola.log('Quick Start:');\n consola.log(' 1. Generate VAPID keys: pnpm pwa vapid');\n consola.log(' 2. Add to .env.local');\n consola.log(' 3. Visit playground: http://djangocfg.com/layouts/a2hs-hint');\n consola.log(' 4. Send test push: pnpm pwa send \\'<subscription>\\'\\n');\n}\n\nasync function showInfo() {\n consola.box('DjangoCFG PWA CLI');\n consola.log('Commands:');\n consola.log(' vapid Generate VAPID keys');\n consola.log(' send <subscription> Send test push notification');\n consola.log(' status Show PWA configuration status');\n consola.log(' info Show this help\\n');\n\n consola.log('Examples:');\n consola.log(' # Generate VAPID keys');\n consola.log(' pnpm pwa vapid\\n');\n\n consola.log(' # Check status');\n consola.log(' pnpm pwa status\\n');\n\n consola.log(' # Subscribe');\n consola.log(' Visit: http://djangocfg.com/layouts/a2hs-hint\\n');\n\n consola.log(' # Send test push');\n consola.log(' pnpm pwa send \\'{\"endpoint\":\"...\",\"keys\":{\"p256dh\":\"...\",\"auth\":\"...\"}}\\'\\n');\n\n consola.log('Documentation:');\n consola.log(' https://djangocfg.com/docs/pwa');\n}\n\nasync function main() {\n switch (command) {\n case 'vapid':\n case 'v':\n case 'keys':\n await generateVapidKeys();\n break;\n\n case 'send':\n case 's':\n case 'push':\n await sendTestPush();\n break;\n\n case 'status':\n case 'st':\n await showStatus();\n break;\n\n case 'info':\n case 'i':\n case 'help':\n case 'h':\n await showInfo();\n break;\n\n default:\n await showInfo();\n break;\n }\n}\n\nmain();\n"],"mappings":"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","names":[]}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { PushSubscription } from 'web-push';
|
|
2
|
-
export { r as routes } from '../../routes-DXA29sS_.mjs';
|
|
3
|
-
import 'next/server';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Server-side Push Notification Utilities
|
|
7
|
-
*
|
|
8
|
-
* VAPID-based Web Push notifications using web-push library
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Check if VAPID keys are configured
|
|
13
|
-
*/
|
|
14
|
-
declare function isVapidConfigured(): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Get VAPID keys from environment
|
|
17
|
-
*/
|
|
18
|
-
declare function getVapidKeys(): {
|
|
19
|
-
publicKey: string;
|
|
20
|
-
privateKey: string;
|
|
21
|
-
mailto: string;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Configure VAPID keys for web-push
|
|
25
|
-
* Call this once at app startup
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* // In your API route or middleware
|
|
30
|
-
* import { configureVapid } from '@djangocfg/nextjs/pwa/server';
|
|
31
|
-
*
|
|
32
|
-
* configureVapid(); // Uses env vars automatically
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
declare function configureVapid(options?: {
|
|
36
|
-
publicKey?: string;
|
|
37
|
-
privateKey?: string;
|
|
38
|
-
mailto?: string;
|
|
39
|
-
}): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* Send push notification to a subscription
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```typescript
|
|
45
|
-
* await sendPushNotification(subscription, {
|
|
46
|
-
* title: 'Hello!',
|
|
47
|
-
* body: 'Test notification',
|
|
48
|
-
* data: { url: '/page' },
|
|
49
|
-
* });
|
|
50
|
-
* ```
|
|
51
|
-
*/
|
|
52
|
-
declare function sendPushNotification(subscription: PushSubscription, notification: {
|
|
53
|
-
title: string;
|
|
54
|
-
body?: string;
|
|
55
|
-
icon?: string;
|
|
56
|
-
badge?: string;
|
|
57
|
-
data?: any;
|
|
58
|
-
tag?: string;
|
|
59
|
-
requireInteraction?: boolean;
|
|
60
|
-
}): Promise<void>;
|
|
61
|
-
/**
|
|
62
|
-
* Send push notification to multiple subscriptions
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```typescript
|
|
66
|
-
* const results = await sendPushToMultiple(subscriptions, {
|
|
67
|
-
* title: 'Broadcast message',
|
|
68
|
-
* body: 'Sent to all users',
|
|
69
|
-
* });
|
|
70
|
-
* console.log(`Sent: ${results.successful}, Failed: ${results.failed}`);
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
73
|
-
declare function sendPushToMultiple(subscriptions: PushSubscription[], notification: Parameters<typeof sendPushNotification>[1]): Promise<{
|
|
74
|
-
successful: number;
|
|
75
|
-
failed: number;
|
|
76
|
-
errors: Array<{
|
|
77
|
-
subscription: PushSubscription;
|
|
78
|
-
error: Error;
|
|
79
|
-
}>;
|
|
80
|
-
}>;
|
|
81
|
-
/**
|
|
82
|
-
* Validate push subscription format
|
|
83
|
-
*/
|
|
84
|
-
declare function validateSubscription(subscription: any): subscription is PushSubscription;
|
|
85
|
-
|
|
86
|
-
export { configureVapid, getVapidKeys, isVapidConfigured, sendPushNotification, sendPushToMultiple, validateSubscription };
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __export = (target, all) => {
|
|
3
|
-
for (var name in all)
|
|
4
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
// src/pwa/server/push.ts
|
|
8
|
-
import { consola } from "consola";
|
|
9
|
-
import webpush from "web-push";
|
|
10
|
-
var vapidConfigured = false;
|
|
11
|
-
function isVapidConfigured() {
|
|
12
|
-
return vapidConfigured;
|
|
13
|
-
}
|
|
14
|
-
function getVapidKeys() {
|
|
15
|
-
const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
|
|
16
|
-
const privateKey = process.env.VAPID_PRIVATE_KEY;
|
|
17
|
-
const mailto = process.env.VAPID_MAILTO || "mailto:noreply@example.com";
|
|
18
|
-
return { publicKey, privateKey, mailto };
|
|
19
|
-
}
|
|
20
|
-
function configureVapid(options) {
|
|
21
|
-
const { publicKey, privateKey, mailto } = options || getVapidKeys();
|
|
22
|
-
if (!publicKey || !privateKey) {
|
|
23
|
-
consola.warn(
|
|
24
|
-
"\u26A0\uFE0F VAPID keys not configured!\n Generate keys: npx web-push generate-vapid-keys\n Add to .env.local:\n NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\n VAPID_PRIVATE_KEY=your_private_key\n Push notifications will not work without VAPID keys."
|
|
25
|
-
);
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
try {
|
|
29
|
-
webpush.setVapidDetails(mailto, publicKey, privateKey);
|
|
30
|
-
vapidConfigured = true;
|
|
31
|
-
consola.success("\u2705 VAPID keys configured for push notifications");
|
|
32
|
-
return true;
|
|
33
|
-
} catch (error) {
|
|
34
|
-
consola.error("Failed to configure VAPID keys:", error.message);
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function sendPushNotification(subscription, notification) {
|
|
39
|
-
if (!vapidConfigured) {
|
|
40
|
-
const configured = configureVapid();
|
|
41
|
-
if (!configured) {
|
|
42
|
-
throw new Error("VAPID keys not configured. Cannot send push notification.");
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const payload = JSON.stringify({
|
|
46
|
-
title: notification.title,
|
|
47
|
-
body: notification.body || "",
|
|
48
|
-
icon: notification.icon,
|
|
49
|
-
badge: notification.badge,
|
|
50
|
-
data: notification.data,
|
|
51
|
-
tag: notification.tag,
|
|
52
|
-
requireInteraction: notification.requireInteraction
|
|
53
|
-
});
|
|
54
|
-
const result = await webpush.sendNotification(subscription, payload);
|
|
55
|
-
console.log("\u2705 Push Sent to FCM:", {
|
|
56
|
-
statusCode: result.statusCode,
|
|
57
|
-
headers: result.headers,
|
|
58
|
-
bodyLength: result.body?.length
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
async function sendPushToMultiple(subscriptions2, notification) {
|
|
62
|
-
const results = await Promise.allSettled(
|
|
63
|
-
subscriptions2.map((sub) => sendPushNotification(sub, notification))
|
|
64
|
-
);
|
|
65
|
-
const successful = results.filter((r) => r.status === "fulfilled").length;
|
|
66
|
-
const failed = results.filter((r) => r.status === "rejected").length;
|
|
67
|
-
const errors = results.map((r, i) => r.status === "rejected" ? { subscription: subscriptions2[i], error: r.reason } : null).filter((e) => e !== null);
|
|
68
|
-
return { successful, failed, errors };
|
|
69
|
-
}
|
|
70
|
-
function validateSubscription(subscription) {
|
|
71
|
-
return subscription && typeof subscription === "object" && typeof subscription.endpoint === "string" && subscription.keys && typeof subscription.keys.p256dh === "string" && typeof subscription.keys.auth === "string";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/pwa/server/routes.ts
|
|
75
|
-
var routes_exports = {};
|
|
76
|
-
__export(routes_exports, {
|
|
77
|
-
GET: () => GET,
|
|
78
|
-
POST: () => POST,
|
|
79
|
-
handleGetSubscriptions: () => handleGetSubscriptions,
|
|
80
|
-
handleSend: () => handleSend,
|
|
81
|
-
handleSubscribe: () => handleSubscribe
|
|
82
|
-
});
|
|
83
|
-
import { NextResponse } from "next/server";
|
|
84
|
-
var subscriptions = /* @__PURE__ */ new Set();
|
|
85
|
-
async function handleSubscribe(request) {
|
|
86
|
-
try {
|
|
87
|
-
const subscription = await request.json();
|
|
88
|
-
if (!validateSubscription(subscription)) {
|
|
89
|
-
return NextResponse.json(
|
|
90
|
-
{ error: "Invalid subscription format" },
|
|
91
|
-
{ status: 400 }
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
subscriptions.add(JSON.stringify(subscription));
|
|
95
|
-
console.log("\u2705 Push subscription saved:", {
|
|
96
|
-
endpoint: subscription.endpoint.substring(0, 50) + "...",
|
|
97
|
-
total: subscriptions.size
|
|
98
|
-
});
|
|
99
|
-
return NextResponse.json({
|
|
100
|
-
success: true,
|
|
101
|
-
message: "Subscription saved",
|
|
102
|
-
totalSubscriptions: subscriptions.size
|
|
103
|
-
});
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error("Subscription error:", error);
|
|
106
|
-
return NextResponse.json(
|
|
107
|
-
{
|
|
108
|
-
error: "Failed to save subscription",
|
|
109
|
-
details: error.message
|
|
110
|
-
},
|
|
111
|
-
{ status: 500 }
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
async function handleGetSubscriptions() {
|
|
116
|
-
return NextResponse.json({
|
|
117
|
-
totalSubscriptions: subscriptions.size,
|
|
118
|
-
subscriptions: Array.from(subscriptions).map((sub) => {
|
|
119
|
-
const parsed = JSON.parse(sub);
|
|
120
|
-
return {
|
|
121
|
-
endpoint: parsed.endpoint.substring(0, 50) + "..."
|
|
122
|
-
};
|
|
123
|
-
})
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async function handleSend(request) {
|
|
127
|
-
try {
|
|
128
|
-
const { subscription, notification } = await request.json();
|
|
129
|
-
if (!subscription) {
|
|
130
|
-
return NextResponse.json(
|
|
131
|
-
{ error: "Subscription is required" },
|
|
132
|
-
{ status: 400 }
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
if (!validateSubscription(subscription)) {
|
|
136
|
-
return NextResponse.json(
|
|
137
|
-
{ error: "Invalid subscription format" },
|
|
138
|
-
{ status: 400 }
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
configureVapid();
|
|
142
|
-
await sendPushNotification(subscription, {
|
|
143
|
-
title: notification?.title || "Test Notification",
|
|
144
|
-
body: notification?.body || "This is a test push notification",
|
|
145
|
-
icon: notification?.icon,
|
|
146
|
-
badge: notification?.badge,
|
|
147
|
-
data: notification?.data
|
|
148
|
-
});
|
|
149
|
-
return NextResponse.json({
|
|
150
|
-
success: true,
|
|
151
|
-
message: "Push notification sent"
|
|
152
|
-
});
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error("Push notification error:", error);
|
|
155
|
-
return NextResponse.json(
|
|
156
|
-
{
|
|
157
|
-
error: "Failed to send push notification",
|
|
158
|
-
details: error.message
|
|
159
|
-
},
|
|
160
|
-
{ status: 500 }
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
var POST = handleSubscribe;
|
|
165
|
-
var GET = handleGetSubscriptions;
|
|
166
|
-
export {
|
|
167
|
-
configureVapid,
|
|
168
|
-
getVapidKeys,
|
|
169
|
-
isVapidConfigured,
|
|
170
|
-
routes_exports as routes,
|
|
171
|
-
sendPushNotification,
|
|
172
|
-
sendPushToMultiple,
|
|
173
|
-
validateSubscription
|
|
174
|
-
};
|
|
175
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/pwa/server/push.ts","../../../src/pwa/server/routes.ts"],"sourcesContent":["/**\n * Server-side Push Notification Utilities\n *\n * VAPID-based Web Push notifications using web-push library\n */\n\nimport { consola } from 'consola';\nimport webpush, { PushSubscription } from 'web-push';\n\nlet vapidConfigured = false;\n\n/**\n * Check if VAPID keys are configured\n */\nexport function isVapidConfigured(): boolean {\n return vapidConfigured;\n}\n\n/**\n * Get VAPID keys from environment\n */\nexport function getVapidKeys() {\n const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;\n const privateKey = process.env.VAPID_PRIVATE_KEY;\n const mailto = process.env.VAPID_MAILTO || 'mailto:noreply@example.com';\n\n return { publicKey, privateKey, mailto };\n}\n\n/**\n * Configure VAPID keys for web-push\n * Call this once at app startup\n *\n * @example\n * ```typescript\n * // In your API route or middleware\n * import { configureVapid } from '@djangocfg/nextjs/pwa/server';\n *\n * configureVapid(); // Uses env vars automatically\n * ```\n */\nexport function configureVapid(options?: {\n publicKey?: string;\n privateKey?: string;\n mailto?: string;\n}): boolean {\n const { publicKey, privateKey, mailto } = options || getVapidKeys();\n\n if (!publicKey || !privateKey) {\n consola.warn(\n '⚠️ VAPID keys not configured!\\n' +\n ' Generate keys: npx web-push generate-vapid-keys\\n' +\n ' Add to .env.local:\\n' +\n ' NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\\n' +\n ' VAPID_PRIVATE_KEY=your_private_key\\n' +\n ' Push notifications will not work without VAPID keys.'\n );\n return false;\n }\n\n try {\n webpush.setVapidDetails(mailto, publicKey, privateKey);\n vapidConfigured = true;\n consola.success('✅ VAPID keys configured for push notifications');\n return true;\n } catch (error: any) {\n consola.error('Failed to configure VAPID keys:', error.message);\n return false;\n }\n}\n\n/**\n * Send push notification to a subscription\n *\n * @example\n * ```typescript\n * await sendPushNotification(subscription, {\n * title: 'Hello!',\n * body: 'Test notification',\n * data: { url: '/page' },\n * });\n * ```\n */\nexport async function sendPushNotification(\n subscription: PushSubscription,\n notification: {\n title: string;\n body?: string;\n icon?: string;\n badge?: string;\n data?: any;\n tag?: string;\n requireInteraction?: boolean;\n }\n): Promise<void> {\n if (!vapidConfigured) {\n const configured = configureVapid();\n if (!configured) {\n throw new Error('VAPID keys not configured. Cannot send push notification.');\n }\n }\n\n const payload = JSON.stringify({\n title: notification.title,\n body: notification.body || '',\n icon: notification.icon,\n badge: notification.badge,\n data: notification.data,\n tag: notification.tag,\n requireInteraction: notification.requireInteraction,\n });\n\n const result = await webpush.sendNotification(subscription, payload);\n console.log('✅ Push Sent to FCM:', {\n statusCode: result.statusCode,\n headers: result.headers,\n bodyLength: result.body?.length\n });\n}\n\n/**\n * Send push notification to multiple subscriptions\n *\n * @example\n * ```typescript\n * const results = await sendPushToMultiple(subscriptions, {\n * title: 'Broadcast message',\n * body: 'Sent to all users',\n * });\n * console.log(`Sent: ${results.successful}, Failed: ${results.failed}`);\n * ```\n */\nexport async function sendPushToMultiple(\n subscriptions: PushSubscription[],\n notification: Parameters<typeof sendPushNotification>[1]\n): Promise<{\n successful: number;\n failed: number;\n errors: Array<{ subscription: PushSubscription; error: Error }>;\n}> {\n const results = await Promise.allSettled(\n subscriptions.map((sub) => sendPushNotification(sub, notification))\n );\n\n const successful = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n const errors = results\n .map((r, i) => (r.status === 'rejected' ? { subscription: subscriptions[i], error: r.reason } : null))\n .filter((e): e is NonNullable<typeof e> => e !== null);\n\n return { successful, failed, errors };\n}\n\n/**\n * Validate push subscription format\n */\nexport function validateSubscription(subscription: any): subscription is PushSubscription {\n return (\n subscription &&\n typeof subscription === 'object' &&\n typeof subscription.endpoint === 'string' &&\n subscription.keys &&\n typeof subscription.keys.p256dh === 'string' &&\n typeof subscription.keys.auth === 'string'\n );\n}\n","/**\n * Ready-to-use Push Notification Route Handlers\n *\n * Import these in your app/api/push/ routes\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\n\nimport { configureVapid, sendPushNotification, validateSubscription } from './push';\n\n// In-memory storage для demo (в production используй БД)\nconst subscriptions = new Set<string>();\n\n/**\n * POST /api/push/subscribe\n * Save push subscription\n *\n * @example\n * ```typescript\n * // app/api/push/subscribe/route.ts\n * export { POST } from '@djangocfg/nextjs/pwa/server/routes';\n * ```\n */\nexport async function handleSubscribe(request: NextRequest) {\n try {\n const subscription = await request.json();\n\n if (!validateSubscription(subscription)) {\n return NextResponse.json(\n { error: 'Invalid subscription format' },\n { status: 400 }\n );\n }\n\n // Сохраняем subscription (в demo просто в памяти)\n subscriptions.add(JSON.stringify(subscription));\n\n console.log('✅ Push subscription saved:', {\n endpoint: subscription.endpoint.substring(0, 50) + '...',\n total: subscriptions.size,\n });\n\n return NextResponse.json({\n success: true,\n message: 'Subscription saved',\n totalSubscriptions: subscriptions.size,\n });\n } catch (error: any) {\n console.error('Subscription error:', error);\n\n return NextResponse.json(\n {\n error: 'Failed to save subscription',\n details: error.message,\n },\n { status: 500 }\n );\n }\n}\n\n/**\n * GET /api/push/subscribe\n * Get all subscriptions (for testing)\n */\nexport async function handleGetSubscriptions() {\n return NextResponse.json({\n totalSubscriptions: subscriptions.size,\n subscriptions: Array.from(subscriptions).map((sub) => {\n const parsed = JSON.parse(sub);\n return {\n endpoint: parsed.endpoint.substring(0, 50) + '...',\n };\n }),\n });\n}\n\n/**\n * POST /api/push/send\n * Send push notification\n *\n * @example\n * ```typescript\n * // app/api/push/send/route.ts\n * export { POST as handleSend as POST } from '@djangocfg/nextjs/pwa/server/routes';\n * ```\n */\nexport async function handleSend(request: NextRequest) {\n try {\n const { subscription, notification } = await request.json();\n\n if (!subscription) {\n return NextResponse.json(\n { error: 'Subscription is required' },\n { status: 400 }\n );\n }\n\n if (!validateSubscription(subscription)) {\n return NextResponse.json(\n { error: 'Invalid subscription format' },\n { status: 400 }\n );\n }\n\n // Configure VAPID if not already configured\n configureVapid();\n\n await sendPushNotification(subscription, {\n title: notification?.title || 'Test Notification',\n body: notification?.body || 'This is a test push notification',\n icon: notification?.icon,\n badge: notification?.badge,\n data: notification?.data,\n });\n\n return NextResponse.json({\n success: true,\n message: 'Push notification sent',\n });\n } catch (error: any) {\n console.error('Push notification error:', error);\n\n return NextResponse.json(\n {\n error: 'Failed to send push notification',\n details: error.message,\n },\n { status: 500 }\n );\n }\n}\n\n/**\n * Combined route handlers\n * Use like: export { POST, GET } from '@djangocfg/nextjs/pwa/server/routes'\n */\nexport const POST = handleSubscribe;\nexport const GET = handleGetSubscriptions;\n"],"mappings":"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","names":["subscriptions"]}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
// src/pwa/server/routes.ts
|
|
2
|
-
import { NextResponse } from "next/server";
|
|
3
|
-
|
|
4
|
-
// src/pwa/server/push.ts
|
|
5
|
-
import { consola } from "consola";
|
|
6
|
-
import webpush from "web-push";
|
|
7
|
-
var vapidConfigured = false;
|
|
8
|
-
function getVapidKeys() {
|
|
9
|
-
const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
|
|
10
|
-
const privateKey = process.env.VAPID_PRIVATE_KEY;
|
|
11
|
-
const mailto = process.env.VAPID_MAILTO || "mailto:noreply@example.com";
|
|
12
|
-
return { publicKey, privateKey, mailto };
|
|
13
|
-
}
|
|
14
|
-
function configureVapid(options) {
|
|
15
|
-
const { publicKey, privateKey, mailto } = options || getVapidKeys();
|
|
16
|
-
if (!publicKey || !privateKey) {
|
|
17
|
-
consola.warn(
|
|
18
|
-
"\u26A0\uFE0F VAPID keys not configured!\n Generate keys: npx web-push generate-vapid-keys\n Add to .env.local:\n NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\n VAPID_PRIVATE_KEY=your_private_key\n Push notifications will not work without VAPID keys."
|
|
19
|
-
);
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
webpush.setVapidDetails(mailto, publicKey, privateKey);
|
|
24
|
-
vapidConfigured = true;
|
|
25
|
-
consola.success("\u2705 VAPID keys configured for push notifications");
|
|
26
|
-
return true;
|
|
27
|
-
} catch (error) {
|
|
28
|
-
consola.error("Failed to configure VAPID keys:", error.message);
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
async function sendPushNotification(subscription, notification) {
|
|
33
|
-
if (!vapidConfigured) {
|
|
34
|
-
const configured = configureVapid();
|
|
35
|
-
if (!configured) {
|
|
36
|
-
throw new Error("VAPID keys not configured. Cannot send push notification.");
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const payload = JSON.stringify({
|
|
40
|
-
title: notification.title,
|
|
41
|
-
body: notification.body || "",
|
|
42
|
-
icon: notification.icon,
|
|
43
|
-
badge: notification.badge,
|
|
44
|
-
data: notification.data,
|
|
45
|
-
tag: notification.tag,
|
|
46
|
-
requireInteraction: notification.requireInteraction
|
|
47
|
-
});
|
|
48
|
-
const result = await webpush.sendNotification(subscription, payload);
|
|
49
|
-
console.log("\u2705 Push Sent to FCM:", {
|
|
50
|
-
statusCode: result.statusCode,
|
|
51
|
-
headers: result.headers,
|
|
52
|
-
bodyLength: result.body?.length
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function validateSubscription(subscription) {
|
|
56
|
-
return subscription && typeof subscription === "object" && typeof subscription.endpoint === "string" && subscription.keys && typeof subscription.keys.p256dh === "string" && typeof subscription.keys.auth === "string";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/pwa/server/routes.ts
|
|
60
|
-
var subscriptions = /* @__PURE__ */ new Set();
|
|
61
|
-
async function handleSubscribe(request) {
|
|
62
|
-
try {
|
|
63
|
-
const subscription = await request.json();
|
|
64
|
-
if (!validateSubscription(subscription)) {
|
|
65
|
-
return NextResponse.json(
|
|
66
|
-
{ error: "Invalid subscription format" },
|
|
67
|
-
{ status: 400 }
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
subscriptions.add(JSON.stringify(subscription));
|
|
71
|
-
console.log("\u2705 Push subscription saved:", {
|
|
72
|
-
endpoint: subscription.endpoint.substring(0, 50) + "...",
|
|
73
|
-
total: subscriptions.size
|
|
74
|
-
});
|
|
75
|
-
return NextResponse.json({
|
|
76
|
-
success: true,
|
|
77
|
-
message: "Subscription saved",
|
|
78
|
-
totalSubscriptions: subscriptions.size
|
|
79
|
-
});
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error("Subscription error:", error);
|
|
82
|
-
return NextResponse.json(
|
|
83
|
-
{
|
|
84
|
-
error: "Failed to save subscription",
|
|
85
|
-
details: error.message
|
|
86
|
-
},
|
|
87
|
-
{ status: 500 }
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async function handleGetSubscriptions() {
|
|
92
|
-
return NextResponse.json({
|
|
93
|
-
totalSubscriptions: subscriptions.size,
|
|
94
|
-
subscriptions: Array.from(subscriptions).map((sub) => {
|
|
95
|
-
const parsed = JSON.parse(sub);
|
|
96
|
-
return {
|
|
97
|
-
endpoint: parsed.endpoint.substring(0, 50) + "..."
|
|
98
|
-
};
|
|
99
|
-
})
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
async function handleSend(request) {
|
|
103
|
-
try {
|
|
104
|
-
const { subscription, notification } = await request.json();
|
|
105
|
-
if (!subscription) {
|
|
106
|
-
return NextResponse.json(
|
|
107
|
-
{ error: "Subscription is required" },
|
|
108
|
-
{ status: 400 }
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
if (!validateSubscription(subscription)) {
|
|
112
|
-
return NextResponse.json(
|
|
113
|
-
{ error: "Invalid subscription format" },
|
|
114
|
-
{ status: 400 }
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
configureVapid();
|
|
118
|
-
await sendPushNotification(subscription, {
|
|
119
|
-
title: notification?.title || "Test Notification",
|
|
120
|
-
body: notification?.body || "This is a test push notification",
|
|
121
|
-
icon: notification?.icon,
|
|
122
|
-
badge: notification?.badge,
|
|
123
|
-
data: notification?.data
|
|
124
|
-
});
|
|
125
|
-
return NextResponse.json({
|
|
126
|
-
success: true,
|
|
127
|
-
message: "Push notification sent"
|
|
128
|
-
});
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error("Push notification error:", error);
|
|
131
|
-
return NextResponse.json(
|
|
132
|
-
{
|
|
133
|
-
error: "Failed to send push notification",
|
|
134
|
-
details: error.message
|
|
135
|
-
},
|
|
136
|
-
{ status: 500 }
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
var POST = handleSubscribe;
|
|
141
|
-
var GET = handleGetSubscriptions;
|
|
142
|
-
export {
|
|
143
|
-
GET,
|
|
144
|
-
POST,
|
|
145
|
-
handleGetSubscriptions,
|
|
146
|
-
handleSend,
|
|
147
|
-
handleSubscribe
|
|
148
|
-
};
|
|
149
|
-
//# sourceMappingURL=routes.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/pwa/server/routes.ts","../../../src/pwa/server/push.ts"],"sourcesContent":["/**\n * Ready-to-use Push Notification Route Handlers\n *\n * Import these in your app/api/push/ routes\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\n\nimport { configureVapid, sendPushNotification, validateSubscription } from './push';\n\n// In-memory storage для demo (в production используй БД)\nconst subscriptions = new Set<string>();\n\n/**\n * POST /api/push/subscribe\n * Save push subscription\n *\n * @example\n * ```typescript\n * // app/api/push/subscribe/route.ts\n * export { POST } from '@djangocfg/nextjs/pwa/server/routes';\n * ```\n */\nexport async function handleSubscribe(request: NextRequest) {\n try {\n const subscription = await request.json();\n\n if (!validateSubscription(subscription)) {\n return NextResponse.json(\n { error: 'Invalid subscription format' },\n { status: 400 }\n );\n }\n\n // Сохраняем subscription (в demo просто в памяти)\n subscriptions.add(JSON.stringify(subscription));\n\n console.log('✅ Push subscription saved:', {\n endpoint: subscription.endpoint.substring(0, 50) + '...',\n total: subscriptions.size,\n });\n\n return NextResponse.json({\n success: true,\n message: 'Subscription saved',\n totalSubscriptions: subscriptions.size,\n });\n } catch (error: any) {\n console.error('Subscription error:', error);\n\n return NextResponse.json(\n {\n error: 'Failed to save subscription',\n details: error.message,\n },\n { status: 500 }\n );\n }\n}\n\n/**\n * GET /api/push/subscribe\n * Get all subscriptions (for testing)\n */\nexport async function handleGetSubscriptions() {\n return NextResponse.json({\n totalSubscriptions: subscriptions.size,\n subscriptions: Array.from(subscriptions).map((sub) => {\n const parsed = JSON.parse(sub);\n return {\n endpoint: parsed.endpoint.substring(0, 50) + '...',\n };\n }),\n });\n}\n\n/**\n * POST /api/push/send\n * Send push notification\n *\n * @example\n * ```typescript\n * // app/api/push/send/route.ts\n * export { POST as handleSend as POST } from '@djangocfg/nextjs/pwa/server/routes';\n * ```\n */\nexport async function handleSend(request: NextRequest) {\n try {\n const { subscription, notification } = await request.json();\n\n if (!subscription) {\n return NextResponse.json(\n { error: 'Subscription is required' },\n { status: 400 }\n );\n }\n\n if (!validateSubscription(subscription)) {\n return NextResponse.json(\n { error: 'Invalid subscription format' },\n { status: 400 }\n );\n }\n\n // Configure VAPID if not already configured\n configureVapid();\n\n await sendPushNotification(subscription, {\n title: notification?.title || 'Test Notification',\n body: notification?.body || 'This is a test push notification',\n icon: notification?.icon,\n badge: notification?.badge,\n data: notification?.data,\n });\n\n return NextResponse.json({\n success: true,\n message: 'Push notification sent',\n });\n } catch (error: any) {\n console.error('Push notification error:', error);\n\n return NextResponse.json(\n {\n error: 'Failed to send push notification',\n details: error.message,\n },\n { status: 500 }\n );\n }\n}\n\n/**\n * Combined route handlers\n * Use like: export { POST, GET } from '@djangocfg/nextjs/pwa/server/routes'\n */\nexport const POST = handleSubscribe;\nexport const GET = handleGetSubscriptions;\n","/**\n * Server-side Push Notification Utilities\n *\n * VAPID-based Web Push notifications using web-push library\n */\n\nimport { consola } from 'consola';\nimport webpush, { PushSubscription } from 'web-push';\n\nlet vapidConfigured = false;\n\n/**\n * Check if VAPID keys are configured\n */\nexport function isVapidConfigured(): boolean {\n return vapidConfigured;\n}\n\n/**\n * Get VAPID keys from environment\n */\nexport function getVapidKeys() {\n const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;\n const privateKey = process.env.VAPID_PRIVATE_KEY;\n const mailto = process.env.VAPID_MAILTO || 'mailto:noreply@example.com';\n\n return { publicKey, privateKey, mailto };\n}\n\n/**\n * Configure VAPID keys for web-push\n * Call this once at app startup\n *\n * @example\n * ```typescript\n * // In your API route or middleware\n * import { configureVapid } from '@djangocfg/nextjs/pwa/server';\n *\n * configureVapid(); // Uses env vars automatically\n * ```\n */\nexport function configureVapid(options?: {\n publicKey?: string;\n privateKey?: string;\n mailto?: string;\n}): boolean {\n const { publicKey, privateKey, mailto } = options || getVapidKeys();\n\n if (!publicKey || !privateKey) {\n consola.warn(\n '⚠️ VAPID keys not configured!\\n' +\n ' Generate keys: npx web-push generate-vapid-keys\\n' +\n ' Add to .env.local:\\n' +\n ' NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\\n' +\n ' VAPID_PRIVATE_KEY=your_private_key\\n' +\n ' Push notifications will not work without VAPID keys.'\n );\n return false;\n }\n\n try {\n webpush.setVapidDetails(mailto, publicKey, privateKey);\n vapidConfigured = true;\n consola.success('✅ VAPID keys configured for push notifications');\n return true;\n } catch (error: any) {\n consola.error('Failed to configure VAPID keys:', error.message);\n return false;\n }\n}\n\n/**\n * Send push notification to a subscription\n *\n * @example\n * ```typescript\n * await sendPushNotification(subscription, {\n * title: 'Hello!',\n * body: 'Test notification',\n * data: { url: '/page' },\n * });\n * ```\n */\nexport async function sendPushNotification(\n subscription: PushSubscription,\n notification: {\n title: string;\n body?: string;\n icon?: string;\n badge?: string;\n data?: any;\n tag?: string;\n requireInteraction?: boolean;\n }\n): Promise<void> {\n if (!vapidConfigured) {\n const configured = configureVapid();\n if (!configured) {\n throw new Error('VAPID keys not configured. Cannot send push notification.');\n }\n }\n\n const payload = JSON.stringify({\n title: notification.title,\n body: notification.body || '',\n icon: notification.icon,\n badge: notification.badge,\n data: notification.data,\n tag: notification.tag,\n requireInteraction: notification.requireInteraction,\n });\n\n const result = await webpush.sendNotification(subscription, payload);\n console.log('✅ Push Sent to FCM:', {\n statusCode: result.statusCode,\n headers: result.headers,\n bodyLength: result.body?.length\n });\n}\n\n/**\n * Send push notification to multiple subscriptions\n *\n * @example\n * ```typescript\n * const results = await sendPushToMultiple(subscriptions, {\n * title: 'Broadcast message',\n * body: 'Sent to all users',\n * });\n * console.log(`Sent: ${results.successful}, Failed: ${results.failed}`);\n * ```\n */\nexport async function sendPushToMultiple(\n subscriptions: PushSubscription[],\n notification: Parameters<typeof sendPushNotification>[1]\n): Promise<{\n successful: number;\n failed: number;\n errors: Array<{ subscription: PushSubscription; error: Error }>;\n}> {\n const results = await Promise.allSettled(\n subscriptions.map((sub) => sendPushNotification(sub, notification))\n );\n\n const successful = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n const errors = results\n .map((r, i) => (r.status === 'rejected' ? { subscription: subscriptions[i], error: r.reason } : null))\n .filter((e): e is NonNullable<typeof e> => e !== null);\n\n return { successful, failed, errors };\n}\n\n/**\n * Validate push subscription format\n */\nexport function validateSubscription(subscription: any): subscription is PushSubscription {\n return (\n subscription &&\n typeof subscription === 'object' &&\n typeof subscription.endpoint === 'string' &&\n subscription.keys &&\n typeof subscription.keys.p256dh === 'string' &&\n typeof subscription.keys.auth === 'string'\n );\n}\n"],"mappings":"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","names":[]}
|