@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.
@@ -1,68 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
-
3
- /**
4
- * Ready-to-use Push Notification Route Handlers
5
- *
6
- * Import these in your app/api/push/ routes
7
- */
8
-
9
- /**
10
- * POST /api/push/subscribe
11
- * Save push subscription
12
- *
13
- * @example
14
- * ```typescript
15
- * // app/api/push/subscribe/route.ts
16
- * export { POST } from '@djangocfg/nextjs/pwa/server/routes';
17
- * ```
18
- */
19
- declare function handleSubscribe(request: NextRequest): Promise<NextResponse<{
20
- error: string;
21
- }> | NextResponse<{
22
- success: boolean;
23
- message: string;
24
- totalSubscriptions: number;
25
- }>>;
26
- /**
27
- * GET /api/push/subscribe
28
- * Get all subscriptions (for testing)
29
- */
30
- declare function handleGetSubscriptions(): Promise<NextResponse<{
31
- totalSubscriptions: number;
32
- subscriptions: {
33
- endpoint: string;
34
- }[];
35
- }>>;
36
- /**
37
- * POST /api/push/send
38
- * Send push notification
39
- *
40
- * @example
41
- * ```typescript
42
- * // app/api/push/send/route.ts
43
- * export { POST as handleSend as POST } from '@djangocfg/nextjs/pwa/server/routes';
44
- * ```
45
- */
46
- declare function handleSend(request: NextRequest): Promise<NextResponse<{
47
- error: string;
48
- }> | NextResponse<{
49
- success: boolean;
50
- message: string;
51
- }>>;
52
- /**
53
- * Combined route handlers
54
- * Use like: export { POST, GET } from '@djangocfg/nextjs/pwa/server/routes'
55
- */
56
- declare const POST: typeof handleSubscribe;
57
- declare const GET: typeof handleGetSubscriptions;
58
-
59
- declare const routes_GET: typeof GET;
60
- declare const routes_POST: typeof POST;
61
- declare const routes_handleGetSubscriptions: typeof handleGetSubscriptions;
62
- declare const routes_handleSend: typeof handleSend;
63
- declare const routes_handleSubscribe: typeof handleSubscribe;
64
- declare namespace routes {
65
- export { routes_GET as GET, routes_POST as POST, routes_handleGetSubscriptions as handleGetSubscriptions, routes_handleSend as handleSend, routes_handleSubscribe as handleSubscribe };
66
- }
67
-
68
- export { GET as G, POST as P, handleGetSubscriptions as a, handleSend as b, handleSubscribe as h, routes as r };
package/src/pwa/cli.ts DELETED
@@ -1,171 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * DjangoCFG PWA CLI
4
- *
5
- * Usage:
6
- * pnpm pwa vapid # Generate VAPID keys
7
- * pnpm pwa send # Send test push notification
8
- * pnpm pwa status # Show PWA status
9
- * pnpm pwa info # Show help
10
- */
11
-
12
- import { consola } from 'consola';
13
- import webpush from 'web-push';
14
-
15
- const args = process.argv.slice(2);
16
- const command = args[0];
17
-
18
- async function generateVapidKeys() {
19
- consola.box('VAPID Keys Generator');
20
- consola.info('Generating new VAPID key pair...\n');
21
-
22
- const vapidKeys = webpush.generateVAPIDKeys();
23
-
24
- consola.success('✅ VAPID keys generated!\n');
25
- consola.log('Add these to your .env.local:\n');
26
- consola.log(`NEXT_PUBLIC_VAPID_PUBLIC_KEY=${vapidKeys.publicKey}`);
27
- consola.log(`VAPID_PRIVATE_KEY=${vapidKeys.privateKey}`);
28
- consola.log(`VAPID_MAILTO=mailto:your-email@example.com\n`);
29
-
30
- consola.info('Public key (share with clients):');
31
- consola.log(` ${vapidKeys.publicKey}\n`);
32
- consola.info('Private key (keep secret):');
33
- consola.log(` ${vapidKeys.privateKey}\n`);
34
- }
35
-
36
- async function sendTestPush() {
37
- consola.box('Send Test Push Notification');
38
-
39
- const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
40
- const privateKey = process.env.VAPID_PRIVATE_KEY;
41
- const mailto = process.env.VAPID_MAILTO || 'mailto:test@example.com';
42
-
43
- if (!publicKey || !privateKey) {
44
- consola.error('❌ VAPID keys not configured!');
45
- consola.info('\nGenerate keys with:');
46
- consola.log(' pnpm pwa vapid\n');
47
- consola.info('Then add to .env.local:');
48
- consola.log(' NEXT_PUBLIC_VAPID_PUBLIC_KEY=...');
49
- consola.log(' VAPID_PRIVATE_KEY=...');
50
- process.exit(1);
51
- }
52
-
53
- // Get subscription from args or use example
54
- const subscriptionArg = args[1];
55
- if (!subscriptionArg) {
56
- consola.error('❌ Subscription required!\n');
57
- consola.info('Usage:');
58
- consola.log(' pnpm pwa send \'{"endpoint":"...","keys":{...}}\'\n');
59
- consola.info('To subscribe and get the JSON, visit the playground:');
60
- consola.log(' http://djangocfg.com/layouts/a2hs-hint\n');
61
- consola.info('Or use the helper command:');
62
- consola.log(' pnpm exec djangocfg-pwa status\n');
63
- process.exit(1);
64
- }
65
-
66
- try {
67
- const subscription = JSON.parse(subscriptionArg);
68
-
69
- webpush.setVapidDetails(mailto, publicKey, privateKey);
70
-
71
- const payload = JSON.stringify({
72
- title: 'Test Push from CLI',
73
- body: 'This is a test notification sent from @djangocfg/nextjs CLI',
74
- icon: '/static/logos/192x192.png',
75
- badge: '/static/logos/192x192.png',
76
- });
77
-
78
- consola.info('Sending push notification...\n');
79
- await webpush.sendNotification(subscription, payload);
80
- consola.success('✅ Push notification sent successfully!');
81
- } catch (err) {
82
- consola.error('❌ Failed to send push:', err instanceof Error ? err.message : err);
83
- process.exit(1);
84
- }
85
- }
86
-
87
- async function showStatus() {
88
- consola.box('PWA Status');
89
-
90
- const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
91
- const privateKey = process.env.VAPID_PRIVATE_KEY;
92
- const mailto = process.env.VAPID_MAILTO;
93
-
94
- consola.log('Environment Variables:');
95
- consola.log(` NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${publicKey ? '✅ Set' : '❌ Not set'}`);
96
- consola.log(` VAPID_PRIVATE_KEY: ${privateKey ? '✅ Set' : '❌ Not set'}`);
97
- consola.log(` VAPID_MAILTO: ${mailto || '⚠️ Not set (optional)'}\n`);
98
-
99
- if (!publicKey || !privateKey) {
100
- consola.warn('⚠️ VAPID keys not configured. Run: pnpm pwa vapid\n');
101
- } else {
102
- consola.success('✅ VAPID keys configured\n');
103
- consola.log('Public Key:');
104
- consola.log(` ${publicKey.slice(0, 40)}...\n`);
105
- }
106
-
107
- consola.log('Quick Start:');
108
- consola.log(' 1. Generate VAPID keys: pnpm pwa vapid');
109
- consola.log(' 2. Add to .env.local');
110
- consola.log(' 3. Visit playground: http://djangocfg.com/layouts/a2hs-hint');
111
- consola.log(' 4. Send test push: pnpm pwa send \'<subscription>\'\n');
112
- }
113
-
114
- async function showInfo() {
115
- consola.box('DjangoCFG PWA CLI');
116
- consola.log('Commands:');
117
- consola.log(' vapid Generate VAPID keys');
118
- consola.log(' send <subscription> Send test push notification');
119
- consola.log(' status Show PWA configuration status');
120
- consola.log(' info Show this help\n');
121
-
122
- consola.log('Examples:');
123
- consola.log(' # Generate VAPID keys');
124
- consola.log(' pnpm pwa vapid\n');
125
-
126
- consola.log(' # Check status');
127
- consola.log(' pnpm pwa status\n');
128
-
129
- consola.log(' # Subscribe');
130
- consola.log(' Visit: http://djangocfg.com/layouts/a2hs-hint\n');
131
-
132
- consola.log(' # Send test push');
133
- consola.log(' pnpm pwa send \'{"endpoint":"...","keys":{"p256dh":"...","auth":"..."}}\'\n');
134
-
135
- consola.log('Documentation:');
136
- consola.log(' https://djangocfg.com/docs/pwa');
137
- }
138
-
139
- async function main() {
140
- switch (command) {
141
- case 'vapid':
142
- case 'v':
143
- case 'keys':
144
- await generateVapidKeys();
145
- break;
146
-
147
- case 'send':
148
- case 's':
149
- case 'push':
150
- await sendTestPush();
151
- break;
152
-
153
- case 'status':
154
- case 'st':
155
- await showStatus();
156
- break;
157
-
158
- case 'info':
159
- case 'i':
160
- case 'help':
161
- case 'h':
162
- await showInfo();
163
- break;
164
-
165
- default:
166
- await showInfo();
167
- break;
168
- }
169
- }
170
-
171
- main();
@@ -1,192 +0,0 @@
1
- /**
2
- * PWA Push Notifications Client Utilities
3
- *
4
- * Functions for requesting permission, subscribing to push notifications,
5
- * and sending test notifications
6
- */
7
-
8
- /**
9
- * Check if push notifications are supported
10
- */
11
- export function isPushNotificationSupported(): boolean {
12
- return (
13
- 'serviceWorker' in navigator &&
14
- 'PushManager' in window &&
15
- 'Notification' in window
16
- );
17
- }
18
-
19
- /**
20
- * Get current notification permission status
21
- */
22
- export function getNotificationPermission(): NotificationPermission {
23
- if (!('Notification' in window)) {
24
- return 'denied';
25
- }
26
- return Notification.permission;
27
- }
28
-
29
- /**
30
- * Request notification permission from user
31
- */
32
- export async function requestNotificationPermission(): Promise<NotificationPermission> {
33
- if (!('Notification' in window)) {
34
- throw new Error('Notifications are not supported');
35
- }
36
-
37
- const permission = await Notification.requestPermission();
38
- return permission;
39
- }
40
-
41
- /**
42
- * Convert base64 VAPID key to Uint8Array
43
- */
44
- function urlBase64ToUint8Array(base64String: string): Uint8Array {
45
- const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
46
- const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
47
-
48
- const rawData = window.atob(base64);
49
- const outputArray = new Uint8Array(rawData.length);
50
-
51
- for (let i = 0; i < rawData.length; ++i) {
52
- outputArray[i] = rawData.charCodeAt(i);
53
- }
54
- return outputArray;
55
- }
56
-
57
- export interface PushSubscriptionOptions {
58
- /**
59
- * VAPID public key (base64 encoded)
60
- * Generate with: npx web-push generate-vapid-keys
61
- */
62
- vapidPublicKey: string;
63
-
64
- /**
65
- * User visible only (required for Chrome)
66
- * @default true
67
- */
68
- userVisibleOnly?: boolean;
69
- }
70
-
71
- /**
72
- * Subscribe to push notifications
73
- *
74
- * @example
75
- * ```ts
76
- * const subscription = await subscribeToPushNotifications({
77
- * vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
78
- * });
79
- *
80
- * // Send subscription to your backend
81
- * await fetch('/api/push/subscribe', {
82
- * method: 'POST',
83
- * headers: { 'Content-Type': 'application/json' },
84
- * body: JSON.stringify(subscription),
85
- * });
86
- * ```
87
- */
88
- export async function subscribeToPushNotifications(
89
- options: PushSubscriptionOptions
90
- ): Promise<PushSubscription> {
91
- if (!isPushNotificationSupported()) {
92
- throw new Error('Push notifications are not supported');
93
- }
94
-
95
- // Request permission first
96
- const permission = await requestNotificationPermission();
97
- if (permission !== 'granted') {
98
- throw new Error(`Notification permission ${permission}`);
99
- }
100
-
101
- // Get service worker registration
102
- const registration = await navigator.serviceWorker.ready;
103
-
104
- // Check if already subscribed
105
- let subscription = await registration.pushManager.getSubscription();
106
-
107
- if (subscription) {
108
- return subscription;
109
- }
110
-
111
- // Subscribe to push notifications
112
- const convertedVapidKey = urlBase64ToUint8Array(options.vapidPublicKey);
113
-
114
- subscription = await registration.pushManager.subscribe({
115
- userVisibleOnly: options.userVisibleOnly ?? true,
116
- applicationServerKey: convertedVapidKey as BufferSource,
117
- });
118
-
119
- return subscription;
120
- }
121
-
122
- /**
123
- * Unsubscribe from push notifications
124
- */
125
- export async function unsubscribeFromPushNotifications(): Promise<boolean> {
126
- if (!isPushNotificationSupported()) {
127
- return false;
128
- }
129
-
130
- const registration = await navigator.serviceWorker.ready;
131
- const subscription = await registration.pushManager.getSubscription();
132
-
133
- if (subscription) {
134
- return await subscription.unsubscribe();
135
- }
136
-
137
- return false;
138
- }
139
-
140
- /**
141
- * Get current push subscription
142
- */
143
- export async function getPushSubscription(): Promise<PushSubscription | null> {
144
- if (!isPushNotificationSupported()) {
145
- return null;
146
- }
147
-
148
- const registration = await navigator.serviceWorker.ready;
149
- return await registration.pushManager.getSubscription();
150
- }
151
-
152
- /**
153
- * Show a local notification (doesn't require push)
154
- *
155
- * @example
156
- * ```ts
157
- * await showLocalNotification({
158
- * title: 'Hello!',
159
- * body: 'This is a test notification',
160
- * icon: '/icon.png',
161
- * data: { url: '/some-page' },
162
- * });
163
- * ```
164
- */
165
- export async function showLocalNotification(options: {
166
- title: string;
167
- body?: string;
168
- icon?: string;
169
- badge?: string;
170
- tag?: string;
171
- data?: any;
172
- requireInteraction?: boolean;
173
- }): Promise<void> {
174
- if (!('Notification' in window)) {
175
- throw new Error('Notifications are not supported');
176
- }
177
-
178
- const permission = await requestNotificationPermission();
179
- if (permission !== 'granted') {
180
- throw new Error(`Notification permission ${permission}`);
181
- }
182
-
183
- const registration = await navigator.serviceWorker.ready;
184
- await registration.showNotification(options.title, {
185
- body: options.body,
186
- icon: options.icon,
187
- badge: options.badge,
188
- tag: options.tag,
189
- data: options.data,
190
- requireInteraction: options.requireInteraction,
191
- });
192
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * Server-side PWA Utilities
3
- *
4
- * Push notifications, VAPID configuration, and route handlers
5
- *
6
- * @example
7
- * ```typescript
8
- * // Configure VAPID keys
9
- * import { configureVapid } from '@djangocfg/nextjs/pwa/server';
10
- * configureVapid();
11
- *
12
- * // Use ready-made route handlers
13
- * // app/api/push/subscribe/route.ts
14
- * export { POST, GET } from '@djangocfg/nextjs/pwa/server/routes';
15
- *
16
- * // Or use utilities directly
17
- * import { sendPushNotification } from '@djangocfg/nextjs/pwa/server';
18
- * await sendPushNotification(subscription, { title: 'Hello!' });
19
- * ```
20
- */
21
-
22
- export * from './push';
23
- export * as routes from './routes';
@@ -1,166 +0,0 @@
1
- /**
2
- * Server-side Push Notification Utilities
3
- *
4
- * VAPID-based Web Push notifications using web-push library
5
- */
6
-
7
- import { consola } from 'consola';
8
- import webpush, { PushSubscription } from 'web-push';
9
-
10
- let vapidConfigured = false;
11
-
12
- /**
13
- * Check if VAPID keys are configured
14
- */
15
- export function isVapidConfigured(): boolean {
16
- return vapidConfigured;
17
- }
18
-
19
- /**
20
- * Get VAPID keys from environment
21
- */
22
- export function getVapidKeys() {
23
- const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
24
- const privateKey = process.env.VAPID_PRIVATE_KEY;
25
- const mailto = process.env.VAPID_MAILTO || 'mailto:noreply@example.com';
26
-
27
- return { publicKey, privateKey, mailto };
28
- }
29
-
30
- /**
31
- * Configure VAPID keys for web-push
32
- * Call this once at app startup
33
- *
34
- * @example
35
- * ```typescript
36
- * // In your API route or middleware
37
- * import { configureVapid } from '@djangocfg/nextjs/pwa/server';
38
- *
39
- * configureVapid(); // Uses env vars automatically
40
- * ```
41
- */
42
- export function configureVapid(options?: {
43
- publicKey?: string;
44
- privateKey?: string;
45
- mailto?: string;
46
- }): boolean {
47
- const { publicKey, privateKey, mailto } = options || getVapidKeys();
48
-
49
- if (!publicKey || !privateKey) {
50
- consola.warn(
51
- '⚠️ VAPID keys not configured!\n' +
52
- ' Generate keys: npx web-push generate-vapid-keys\n' +
53
- ' Add to .env.local:\n' +
54
- ' NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key\n' +
55
- ' VAPID_PRIVATE_KEY=your_private_key\n' +
56
- ' Push notifications will not work without VAPID keys.'
57
- );
58
- return false;
59
- }
60
-
61
- try {
62
- webpush.setVapidDetails(mailto, publicKey, privateKey);
63
- vapidConfigured = true;
64
- consola.success('✅ VAPID keys configured for push notifications');
65
- return true;
66
- } catch (error: any) {
67
- consola.error('Failed to configure VAPID keys:', error.message);
68
- return false;
69
- }
70
- }
71
-
72
- /**
73
- * Send push notification to a subscription
74
- *
75
- * @example
76
- * ```typescript
77
- * await sendPushNotification(subscription, {
78
- * title: 'Hello!',
79
- * body: 'Test notification',
80
- * data: { url: '/page' },
81
- * });
82
- * ```
83
- */
84
- export async function sendPushNotification(
85
- subscription: PushSubscription,
86
- notification: {
87
- title: string;
88
- body?: string;
89
- icon?: string;
90
- badge?: string;
91
- data?: any;
92
- tag?: string;
93
- requireInteraction?: boolean;
94
- }
95
- ): Promise<void> {
96
- if (!vapidConfigured) {
97
- const configured = configureVapid();
98
- if (!configured) {
99
- throw new Error('VAPID keys not configured. Cannot send push notification.');
100
- }
101
- }
102
-
103
- const payload = JSON.stringify({
104
- title: notification.title,
105
- body: notification.body || '',
106
- icon: notification.icon,
107
- badge: notification.badge,
108
- data: notification.data,
109
- tag: notification.tag,
110
- requireInteraction: notification.requireInteraction,
111
- });
112
-
113
- const result = await webpush.sendNotification(subscription, payload);
114
- console.log('✅ Push Sent to FCM:', {
115
- statusCode: result.statusCode,
116
- headers: result.headers,
117
- bodyLength: result.body?.length
118
- });
119
- }
120
-
121
- /**
122
- * Send push notification to multiple subscriptions
123
- *
124
- * @example
125
- * ```typescript
126
- * const results = await sendPushToMultiple(subscriptions, {
127
- * title: 'Broadcast message',
128
- * body: 'Sent to all users',
129
- * });
130
- * console.log(`Sent: ${results.successful}, Failed: ${results.failed}`);
131
- * ```
132
- */
133
- export async function sendPushToMultiple(
134
- subscriptions: PushSubscription[],
135
- notification: Parameters<typeof sendPushNotification>[1]
136
- ): Promise<{
137
- successful: number;
138
- failed: number;
139
- errors: Array<{ subscription: PushSubscription; error: Error }>;
140
- }> {
141
- const results = await Promise.allSettled(
142
- subscriptions.map((sub) => sendPushNotification(sub, notification))
143
- );
144
-
145
- const successful = results.filter((r) => r.status === 'fulfilled').length;
146
- const failed = results.filter((r) => r.status === 'rejected').length;
147
- const errors = results
148
- .map((r, i) => (r.status === 'rejected' ? { subscription: subscriptions[i], error: r.reason } : null))
149
- .filter((e): e is NonNullable<typeof e> => e !== null);
150
-
151
- return { successful, failed, errors };
152
- }
153
-
154
- /**
155
- * Validate push subscription format
156
- */
157
- export function validateSubscription(subscription: any): subscription is PushSubscription {
158
- return (
159
- subscription &&
160
- typeof subscription === 'object' &&
161
- typeof subscription.endpoint === 'string' &&
162
- subscription.keys &&
163
- typeof subscription.keys.p256dh === 'string' &&
164
- typeof subscription.keys.auth === 'string'
165
- );
166
- }