@djangocfg/nextjs 2.1.36 → 2.1.38
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 +146 -1
- package/dist/config/index.d.mts +7 -428
- package/dist/config/index.mjs +80 -396
- package/dist/config/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +80 -396
- package/dist/index.mjs.map +1 -1
- package/dist/plugin-DuRJ_Jq6.d.mts +100 -0
- package/dist/pwa/cli.d.mts +1 -0
- package/dist/pwa/cli.mjs +140 -0
- package/dist/pwa/cli.mjs.map +1 -0
- package/dist/pwa/index.d.mts +274 -0
- package/dist/pwa/index.mjs +327 -0
- package/dist/pwa/index.mjs.map +1 -0
- package/dist/pwa/server/index.d.mts +86 -0
- package/dist/pwa/server/index.mjs +175 -0
- package/dist/pwa/server/index.mjs.map +1 -0
- package/dist/pwa/server/routes.d.mts +2 -0
- package/dist/pwa/server/routes.mjs +149 -0
- package/dist/pwa/server/routes.mjs.map +1 -0
- package/dist/pwa/worker/index.d.mts +56 -0
- package/dist/pwa/worker/index.mjs +97 -0
- package/dist/pwa/worker/index.mjs.map +1 -0
- package/dist/routes-DXA29sS_.d.mts +68 -0
- package/package.json +39 -8
- package/src/config/createNextConfig.ts +9 -13
- package/src/config/index.ts +2 -20
- package/src/config/plugins/devStartup.ts +35 -36
- package/src/config/plugins/index.ts +1 -1
- package/src/config/utils/index.ts +0 -1
- package/src/index.ts +4 -0
- package/src/pwa/cli.ts +171 -0
- package/src/pwa/index.ts +9 -0
- package/src/pwa/manifest.ts +355 -0
- package/src/pwa/notifications.ts +192 -0
- package/src/pwa/plugin.ts +194 -0
- package/src/pwa/server/index.ts +23 -0
- package/src/pwa/server/push.ts +166 -0
- package/src/pwa/server/routes.ts +137 -0
- package/src/pwa/worker/index.ts +174 -0
- package/src/pwa/worker/package.json +3 -0
- package/src/config/plugins/pwa.ts +0 -616
- package/src/config/utils/manifest.ts +0 -214
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/pwa/notifications.ts
|
|
9
|
+
function isPushNotificationSupported() {
|
|
10
|
+
return "serviceWorker" in navigator && "PushManager" in window && "Notification" in window;
|
|
11
|
+
}
|
|
12
|
+
function getNotificationPermission() {
|
|
13
|
+
if (!("Notification" in window)) {
|
|
14
|
+
return "denied";
|
|
15
|
+
}
|
|
16
|
+
return Notification.permission;
|
|
17
|
+
}
|
|
18
|
+
async function requestNotificationPermission() {
|
|
19
|
+
if (!("Notification" in window)) {
|
|
20
|
+
throw new Error("Notifications are not supported");
|
|
21
|
+
}
|
|
22
|
+
const permission = await Notification.requestPermission();
|
|
23
|
+
return permission;
|
|
24
|
+
}
|
|
25
|
+
function urlBase64ToUint8Array(base64String) {
|
|
26
|
+
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
27
|
+
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
28
|
+
const rawData = window.atob(base64);
|
|
29
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
30
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
31
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
32
|
+
}
|
|
33
|
+
return outputArray;
|
|
34
|
+
}
|
|
35
|
+
async function subscribeToPushNotifications(options) {
|
|
36
|
+
if (!isPushNotificationSupported()) {
|
|
37
|
+
throw new Error("Push notifications are not supported");
|
|
38
|
+
}
|
|
39
|
+
const permission = await requestNotificationPermission();
|
|
40
|
+
if (permission !== "granted") {
|
|
41
|
+
throw new Error(`Notification permission ${permission}`);
|
|
42
|
+
}
|
|
43
|
+
const registration = await navigator.serviceWorker.ready;
|
|
44
|
+
let subscription = await registration.pushManager.getSubscription();
|
|
45
|
+
if (subscription) {
|
|
46
|
+
return subscription;
|
|
47
|
+
}
|
|
48
|
+
const convertedVapidKey = urlBase64ToUint8Array(options.vapidPublicKey);
|
|
49
|
+
subscription = await registration.pushManager.subscribe({
|
|
50
|
+
userVisibleOnly: options.userVisibleOnly ?? true,
|
|
51
|
+
applicationServerKey: convertedVapidKey
|
|
52
|
+
});
|
|
53
|
+
return subscription;
|
|
54
|
+
}
|
|
55
|
+
async function unsubscribeFromPushNotifications() {
|
|
56
|
+
if (!isPushNotificationSupported()) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const registration = await navigator.serviceWorker.ready;
|
|
60
|
+
const subscription = await registration.pushManager.getSubscription();
|
|
61
|
+
if (subscription) {
|
|
62
|
+
return await subscription.unsubscribe();
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
async function getPushSubscription() {
|
|
67
|
+
if (!isPushNotificationSupported()) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const registration = await navigator.serviceWorker.ready;
|
|
71
|
+
return await registration.pushManager.getSubscription();
|
|
72
|
+
}
|
|
73
|
+
async function showLocalNotification(options) {
|
|
74
|
+
if (!("Notification" in window)) {
|
|
75
|
+
throw new Error("Notifications are not supported");
|
|
76
|
+
}
|
|
77
|
+
const permission = await requestNotificationPermission();
|
|
78
|
+
if (permission !== "granted") {
|
|
79
|
+
throw new Error(`Notification permission ${permission}`);
|
|
80
|
+
}
|
|
81
|
+
const registration = await navigator.serviceWorker.ready;
|
|
82
|
+
await registration.showNotification(options.title, {
|
|
83
|
+
body: options.body,
|
|
84
|
+
icon: options.icon,
|
|
85
|
+
badge: options.badge,
|
|
86
|
+
tag: options.tag,
|
|
87
|
+
data: options.data,
|
|
88
|
+
requireInteraction: options.requireInteraction
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/pwa/manifest.ts
|
|
93
|
+
function createViewport(config) {
|
|
94
|
+
return {
|
|
95
|
+
width: "device-width",
|
|
96
|
+
initialScale: 1,
|
|
97
|
+
maximumScale: 1,
|
|
98
|
+
themeColor: config.themeColor || "#000000"
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createManifestMetadata(config) {
|
|
102
|
+
return {
|
|
103
|
+
manifest: "/manifest.json",
|
|
104
|
+
appleWebApp: {
|
|
105
|
+
capable: true,
|
|
106
|
+
statusBarStyle: "default",
|
|
107
|
+
title: config.shortName || config.name
|
|
108
|
+
},
|
|
109
|
+
applicationName: config.name,
|
|
110
|
+
formatDetection: {
|
|
111
|
+
telephone: false
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function createScreenshot(input) {
|
|
116
|
+
const config = typeof input === "string" ? { src: input } : input;
|
|
117
|
+
let { src, width, height, label, form_factor } = config;
|
|
118
|
+
const ext = src.split(".").pop()?.toLowerCase();
|
|
119
|
+
const typeMap = {
|
|
120
|
+
png: "image/png",
|
|
121
|
+
jpg: "image/jpeg",
|
|
122
|
+
jpeg: "image/jpeg",
|
|
123
|
+
webp: "image/webp",
|
|
124
|
+
svg: "image/svg+xml"
|
|
125
|
+
};
|
|
126
|
+
const type = ext ? typeMap[ext] || "image/png" : "image/png";
|
|
127
|
+
const filename = src.toLowerCase();
|
|
128
|
+
const dimensionMatch = filename.match(/(\d{3,4})x(\d{3,4})/);
|
|
129
|
+
if (dimensionMatch && !width && !height) {
|
|
130
|
+
width = parseInt(dimensionMatch[1], 10);
|
|
131
|
+
height = parseInt(dimensionMatch[2], 10);
|
|
132
|
+
}
|
|
133
|
+
let autoFormFactor = "wide";
|
|
134
|
+
if (filename.includes("mobile") || filename.includes("phone") || filename.includes("narrow")) {
|
|
135
|
+
autoFormFactor = "narrow";
|
|
136
|
+
} else if (filename.includes("desktop") || filename.includes("laptop") || filename.includes("wide")) {
|
|
137
|
+
autoFormFactor = "wide";
|
|
138
|
+
} else if (width && height) {
|
|
139
|
+
const aspectRatio = width / height;
|
|
140
|
+
autoFormFactor = aspectRatio > 1.2 ? "wide" : "narrow";
|
|
141
|
+
}
|
|
142
|
+
const finalFormFactor = form_factor || autoFormFactor;
|
|
143
|
+
const defaultDimensions = finalFormFactor === "wide" ? { width: 1920, height: 1080 } : { width: 390, height: 844 };
|
|
144
|
+
const finalWidth = width || defaultDimensions.width;
|
|
145
|
+
const finalHeight = height || defaultDimensions.height;
|
|
146
|
+
const autoLabel = finalFormFactor === "wide" ? "Desktop screenshot" : "Mobile screenshot";
|
|
147
|
+
return {
|
|
148
|
+
src,
|
|
149
|
+
sizes: `${finalWidth}x${finalHeight}`,
|
|
150
|
+
type,
|
|
151
|
+
form_factor: finalFormFactor,
|
|
152
|
+
label: label || autoLabel
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function createScreenshots(inputs) {
|
|
156
|
+
return inputs.map(createScreenshot);
|
|
157
|
+
}
|
|
158
|
+
function createManifest(config) {
|
|
159
|
+
return () => {
|
|
160
|
+
let manifestIcons;
|
|
161
|
+
if (Array.isArray(config.icons)) {
|
|
162
|
+
manifestIcons = config.icons;
|
|
163
|
+
} else if (config.icons) {
|
|
164
|
+
const { logo192, logo384, logo512 } = config.icons;
|
|
165
|
+
manifestIcons = [
|
|
166
|
+
...logo192 ? [
|
|
167
|
+
{
|
|
168
|
+
src: logo192,
|
|
169
|
+
sizes: "192x192",
|
|
170
|
+
type: "image/png",
|
|
171
|
+
purpose: "maskable"
|
|
172
|
+
}
|
|
173
|
+
] : [],
|
|
174
|
+
...logo384 ? [
|
|
175
|
+
{
|
|
176
|
+
src: logo384,
|
|
177
|
+
sizes: "384x384",
|
|
178
|
+
type: "image/png"
|
|
179
|
+
}
|
|
180
|
+
] : [],
|
|
181
|
+
...logo512 ? [
|
|
182
|
+
{
|
|
183
|
+
src: logo512,
|
|
184
|
+
sizes: "512x512",
|
|
185
|
+
type: "image/png"
|
|
186
|
+
}
|
|
187
|
+
] : []
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
const manifest = {
|
|
191
|
+
name: config.name,
|
|
192
|
+
short_name: config.shortName || config.name,
|
|
193
|
+
description: config.description || config.name,
|
|
194
|
+
id: config.id || config.startUrl || "/",
|
|
195
|
+
start_url: config.startUrl || "/",
|
|
196
|
+
scope: config.scope || "/",
|
|
197
|
+
display: config.display || "standalone",
|
|
198
|
+
orientation: config.orientation || "portrait",
|
|
199
|
+
background_color: config.backgroundColor || "#000000",
|
|
200
|
+
theme_color: config.themeColor || "#ffffff",
|
|
201
|
+
lang: config.lang || "en",
|
|
202
|
+
dir: config.dir || "ltr",
|
|
203
|
+
icons: manifestIcons
|
|
204
|
+
// Removed forced gcm_sender_id to avoid potential conflicts with VAPID
|
|
205
|
+
// gcm_sender_id: '103953800507',
|
|
206
|
+
};
|
|
207
|
+
if (config.screenshots && config.screenshots.length > 0) {
|
|
208
|
+
manifest.screenshots = config.screenshots;
|
|
209
|
+
}
|
|
210
|
+
if (config.protocol_handlers && config.protocol_handlers.length > 0) {
|
|
211
|
+
manifest.protocol_handlers = config.protocol_handlers;
|
|
212
|
+
}
|
|
213
|
+
return manifest;
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function generateManifest(config) {
|
|
217
|
+
return createManifest(config)();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/pwa/plugin.ts
|
|
221
|
+
import { consola } from "consola";
|
|
222
|
+
function withPWA(nextConfig, options = {}) {
|
|
223
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
224
|
+
const defaultOptions = {
|
|
225
|
+
swSrc: "app/sw.ts",
|
|
226
|
+
swDest: "public/sw.js",
|
|
227
|
+
disable: options.disable !== void 0 ? options.disable : isDev,
|
|
228
|
+
cacheOnNavigation: true,
|
|
229
|
+
reloadOnOnline: true,
|
|
230
|
+
...options
|
|
231
|
+
};
|
|
232
|
+
try {
|
|
233
|
+
const withSerwistInit = __require("@serwist/next").default;
|
|
234
|
+
const withSerwist = withSerwistInit({
|
|
235
|
+
swSrc: defaultOptions.swSrc,
|
|
236
|
+
swDest: defaultOptions.swDest,
|
|
237
|
+
disable: defaultOptions.disable,
|
|
238
|
+
cacheOnNavigation: defaultOptions.cacheOnNavigation,
|
|
239
|
+
reloadOnOnline: defaultOptions.reloadOnOnline,
|
|
240
|
+
...defaultOptions.serwistOptions
|
|
241
|
+
});
|
|
242
|
+
return withSerwist(nextConfig);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
consola.error("Failed to configure Serwist:", error);
|
|
245
|
+
return nextConfig;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function getServiceWorkerTemplate() {
|
|
249
|
+
return `/**
|
|
250
|
+
* Service Worker (Serwist)
|
|
251
|
+
*
|
|
252
|
+
* Modern PWA service worker using Serwist
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
import { defaultCache } from '@serwist/next/worker';
|
|
256
|
+
import { Serwist } from 'serwist';
|
|
257
|
+
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
declare const self: any;
|
|
260
|
+
|
|
261
|
+
const serwist = new Serwist({
|
|
262
|
+
// Precache entries injected by Serwist build plugin
|
|
263
|
+
precacheEntries: self.__SW_MANIFEST,
|
|
264
|
+
|
|
265
|
+
// Skip waiting - activate new SW immediately
|
|
266
|
+
skipWaiting: true,
|
|
267
|
+
|
|
268
|
+
// Take control of all clients immediately
|
|
269
|
+
clientsClaim: true,
|
|
270
|
+
|
|
271
|
+
// Enable navigation preload for faster loads
|
|
272
|
+
navigationPreload: true,
|
|
273
|
+
|
|
274
|
+
// Use default Next.js runtime caching strategies
|
|
275
|
+
runtimeCaching: defaultCache,
|
|
276
|
+
|
|
277
|
+
// Fallback pages for offline
|
|
278
|
+
fallbacks: {
|
|
279
|
+
entries: [
|
|
280
|
+
{
|
|
281
|
+
url: '/_offline',
|
|
282
|
+
matcher({ request }) {
|
|
283
|
+
return request.destination === 'document';
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
serwist.addEventListeners();
|
|
291
|
+
`;
|
|
292
|
+
}
|
|
293
|
+
var defaultRuntimeCaching = [];
|
|
294
|
+
function createApiCacheRule() {
|
|
295
|
+
consola.warn("createApiCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
function createStaticAssetRule() {
|
|
299
|
+
consola.warn("createStaticAssetRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
|
|
300
|
+
return {};
|
|
301
|
+
}
|
|
302
|
+
function createCdnCacheRule() {
|
|
303
|
+
consola.warn("createCdnCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker");
|
|
304
|
+
return {};
|
|
305
|
+
}
|
|
306
|
+
export {
|
|
307
|
+
createApiCacheRule,
|
|
308
|
+
createCdnCacheRule,
|
|
309
|
+
createManifest,
|
|
310
|
+
createManifestMetadata,
|
|
311
|
+
createScreenshot,
|
|
312
|
+
createScreenshots,
|
|
313
|
+
createStaticAssetRule,
|
|
314
|
+
createViewport,
|
|
315
|
+
defaultRuntimeCaching,
|
|
316
|
+
generateManifest,
|
|
317
|
+
getNotificationPermission,
|
|
318
|
+
getPushSubscription,
|
|
319
|
+
getServiceWorkerTemplate,
|
|
320
|
+
isPushNotificationSupported,
|
|
321
|
+
requestNotificationPermission,
|
|
322
|
+
showLocalNotification,
|
|
323
|
+
subscribeToPushNotifications,
|
|
324
|
+
unsubscribeFromPushNotifications,
|
|
325
|
+
withPWA
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/pwa/notifications.ts","../../src/pwa/manifest.ts","../../src/pwa/plugin.ts"],"sourcesContent":["/**\n * PWA Push Notifications Client Utilities\n *\n * Functions for requesting permission, subscribing to push notifications,\n * and sending test notifications\n */\n\n/**\n * Check if push notifications are supported\n */\nexport function isPushNotificationSupported(): boolean {\n return (\n 'serviceWorker' in navigator &&\n 'PushManager' in window &&\n 'Notification' in window\n );\n}\n\n/**\n * Get current notification permission status\n */\nexport function getNotificationPermission(): NotificationPermission {\n if (!('Notification' in window)) {\n return 'denied';\n }\n return Notification.permission;\n}\n\n/**\n * Request notification permission from user\n */\nexport async function requestNotificationPermission(): Promise<NotificationPermission> {\n if (!('Notification' in window)) {\n throw new Error('Notifications are not supported');\n }\n\n const permission = await Notification.requestPermission();\n return permission;\n}\n\n/**\n * Convert base64 VAPID key to Uint8Array\n */\nfunction urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');\n\n const rawData = window.atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray;\n}\n\nexport interface PushSubscriptionOptions {\n /**\n * VAPID public key (base64 encoded)\n * Generate with: npx web-push generate-vapid-keys\n */\n vapidPublicKey: string;\n\n /**\n * User visible only (required for Chrome)\n * @default true\n */\n userVisibleOnly?: boolean;\n}\n\n/**\n * Subscribe to push notifications\n *\n * @example\n * ```ts\n * const subscription = await subscribeToPushNotifications({\n * vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,\n * });\n *\n * // Send subscription to your backend\n * await fetch('/api/push/subscribe', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify(subscription),\n * });\n * ```\n */\nexport async function subscribeToPushNotifications(\n options: PushSubscriptionOptions\n): Promise<PushSubscription> {\n if (!isPushNotificationSupported()) {\n throw new Error('Push notifications are not supported');\n }\n\n // Request permission first\n const permission = await requestNotificationPermission();\n if (permission !== 'granted') {\n throw new Error(`Notification permission ${permission}`);\n }\n\n // Get service worker registration\n const registration = await navigator.serviceWorker.ready;\n\n // Check if already subscribed\n let subscription = await registration.pushManager.getSubscription();\n\n if (subscription) {\n return subscription;\n }\n\n // Subscribe to push notifications\n const convertedVapidKey = urlBase64ToUint8Array(options.vapidPublicKey);\n\n subscription = await registration.pushManager.subscribe({\n userVisibleOnly: options.userVisibleOnly ?? true,\n applicationServerKey: convertedVapidKey as BufferSource,\n });\n\n return subscription;\n}\n\n/**\n * Unsubscribe from push notifications\n */\nexport async function unsubscribeFromPushNotifications(): Promise<boolean> {\n if (!isPushNotificationSupported()) {\n return false;\n }\n\n const registration = await navigator.serviceWorker.ready;\n const subscription = await registration.pushManager.getSubscription();\n\n if (subscription) {\n return await subscription.unsubscribe();\n }\n\n return false;\n}\n\n/**\n * Get current push subscription\n */\nexport async function getPushSubscription(): Promise<PushSubscription | null> {\n if (!isPushNotificationSupported()) {\n return null;\n }\n\n const registration = await navigator.serviceWorker.ready;\n return await registration.pushManager.getSubscription();\n}\n\n/**\n * Show a local notification (doesn't require push)\n *\n * @example\n * ```ts\n * await showLocalNotification({\n * title: 'Hello!',\n * body: 'This is a test notification',\n * icon: '/icon.png',\n * data: { url: '/some-page' },\n * });\n * ```\n */\nexport async function showLocalNotification(options: {\n title: string;\n body?: string;\n icon?: string;\n badge?: string;\n tag?: string;\n data?: any;\n requireInteraction?: boolean;\n}): Promise<void> {\n if (!('Notification' in window)) {\n throw new Error('Notifications are not supported');\n }\n\n const permission = await requestNotificationPermission();\n if (permission !== 'granted') {\n throw new Error(`Notification permission ${permission}`);\n }\n\n const registration = await navigator.serviceWorker.ready;\n await registration.showNotification(options.title, {\n body: options.body,\n icon: options.icon,\n badge: options.badge,\n tag: options.tag,\n data: options.data,\n requireInteraction: options.requireInteraction,\n });\n}\n","/**\n * PWA Manifest Metadata Utilities\n *\n * Helper functions for creating Next.js metadata for PWA manifest\n */\n\nimport type { Metadata, MetadataRoute, Viewport } from 'next';\n\nexport interface ManifestConfig {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: {\n src: string;\n sizes: string;\n type?: string;\n purpose?: string;\n }[];\n}\n\n/**\n * Icon paths configuration\n */\nexport interface IconPaths {\n logo192?: string;\n logo384?: string;\n logo512?: string;\n}\n\n/**\n * Protocol handler configuration\n * Allows your PWA to register as a handler for custom protocols\n *\n * @example\n * ```typescript\n * {\n * protocol: \"web+music\",\n * url: \"/play?track=%s\"\n * }\n * ```\n */\nexport interface ProtocolHandler {\n /** Protocol scheme (e.g., \"web+music\", \"mailto\", \"magnet\") */\n protocol: string;\n /** URL template with %s placeholder for the protocol parameter */\n url: string;\n}\n\n/**\n * Create viewport configuration for Next.js app\n *\n * @example\n * ```typescript\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createViewport(config: { themeColor?: string }): Viewport {\n return {\n width: 'device-width',\n initialScale: 1,\n maximumScale: 1,\n themeColor: config.themeColor || '#000000',\n };\n}\n\n/**\n * Create manifest metadata for Next.js app\n *\n * Note: themeColor and viewport should be exported separately using createViewport()\n *\n * @example\n * ```typescript\n * export const metadata: Metadata = {\n * ...createManifestMetadata({\n * name: 'My App',\n * shortName: 'App',\n * description: 'My awesome app',\n * }),\n * };\n *\n * export const viewport: Viewport = createViewport({\n * themeColor: '#ffffff',\n * });\n * ```\n */\nexport function createManifestMetadata(config: ManifestConfig): Metadata {\n return {\n manifest: '/manifest.json',\n appleWebApp: {\n capable: true,\n statusBarStyle: 'default',\n title: config.shortName || config.name,\n },\n applicationName: config.name,\n formatDetection: {\n telephone: false,\n },\n };\n}\n\n/**\n * Create Next.js manifest function\n *\n * Use this in your app/manifest.ts file\n *\n * @example\n * ```typescript\n * // app/manifest.ts\n * import { createManifest } from '@djangocfg/nextjs/config';\n * import { settings } from '@core/settings';\n *\n * export default createManifest({\n * name: settings.app.name,\n * description: settings.app.description,\n * icons: {\n * logo192: settings.app.icons.logo192,\n * logo384: settings.app.icons.logo384,\n * logo512: settings.app.icons.logo512,\n * },\n * });\n * ```\n */\nexport interface ScreenshotConfig {\n src: string;\n sizes: string;\n type?: string;\n form_factor?: 'narrow' | 'wide';\n label?: string;\n}\n\n/**\n * Smart screenshot configuration\n * Automatically detects everything from path or uses defaults\n */\nexport interface SmartScreenshotInput {\n src: string;\n /** Form factor (auto-detected from filename if contains 'desktop'/'mobile', or use default) */\n form_factor?: 'narrow' | 'wide';\n /** Optional label (auto-generated from form_factor) */\n label?: string;\n /** Optional width (defaults based on form_factor) */\n width?: number;\n /** Optional height (defaults based on form_factor) */\n height?: number;\n}\n\n/**\n * Create screenshot config with smart defaults\n * Automatically detects type, sizes, form_factor from path or uses sensible defaults\n *\n * @example\n * ```typescript\n * // Minimal - everything auto-detected\n * createScreenshot({ src: '/screenshots/desktop-view.png' })\n * // → form_factor: 'wide', sizes: '1920x1080', type: 'image/png', label: 'Desktop screenshot'\n *\n * createScreenshot({ src: '/screenshots/mobile.png' })\n * // → form_factor: 'narrow', sizes: '390x844', type: 'image/png', label: 'Mobile screenshot'\n *\n * // With custom dimensions\n * createScreenshot({ src: '/screenshots/tablet.png', width: 1024, height: 768 })\n * ```\n */\nexport function createScreenshot(input: SmartScreenshotInput | string): ScreenshotConfig {\n // Allow string shorthand\n const config = typeof input === 'string' ? { src: input } : input;\n let { src, width, height, label, form_factor } = config;\n\n // Auto-detect image type from extension\n const ext = src.split('.').pop()?.toLowerCase();\n const typeMap: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n };\n const type = ext ? typeMap[ext] || 'image/png' : 'image/png';\n\n // Try to parse dimensions from filename (e.g., \"1920x1080.png\" or \"screenshot-390x844.png\")\n const filename = src.toLowerCase();\n const dimensionMatch = filename.match(/(\\d{3,4})x(\\d{3,4})/);\n if (dimensionMatch && !width && !height) {\n width = parseInt(dimensionMatch[1], 10);\n height = parseInt(dimensionMatch[2], 10);\n }\n\n // Auto-detect form_factor from filename if not provided\n let autoFormFactor: 'narrow' | 'wide' = 'wide'; // Default to wide\n if (filename.includes('mobile') || filename.includes('phone') || filename.includes('narrow')) {\n autoFormFactor = 'narrow';\n } else if (filename.includes('desktop') || filename.includes('laptop') || filename.includes('wide')) {\n autoFormFactor = 'wide';\n } else if (width && height) {\n // Calculate from dimensions if provided or parsed\n const aspectRatio = width / height;\n autoFormFactor = aspectRatio > 1.2 ? 'wide' : 'narrow';\n }\n\n const finalFormFactor = form_factor || autoFormFactor;\n\n // Default dimensions based on form_factor (only if not parsed from filename)\n const defaultDimensions = finalFormFactor === 'wide'\n ? { width: 1920, height: 1080 } // Desktop default\n : { width: 390, height: 844 }; // Mobile default (iPhone 14)\n\n const finalWidth = width || defaultDimensions.width;\n const finalHeight = height || defaultDimensions.height;\n\n // Auto-generate label\n const autoLabel = finalFormFactor === 'wide'\n ? 'Desktop screenshot'\n : 'Mobile screenshot';\n\n return {\n src,\n sizes: `${finalWidth}x${finalHeight}`,\n type,\n form_factor: finalFormFactor,\n label: label || autoLabel,\n };\n}\n\n/**\n * Create multiple screenshots from array\n * Supports string shorthand or full config objects\n *\n * @example\n * ```typescript\n * // Minimal - just paths\n * createScreenshots([\n * '/screenshots/desktop.png', // Auto: wide, 1920x1080\n * '/screenshots/mobile.png', // Auto: narrow, 390x844\n * ])\n *\n * // Mixed\n * createScreenshots([\n * '/screenshots/desktop.png',\n * { src: '/screenshots/tablet.png', width: 1024, height: 768 },\n * ])\n * ```\n */\nexport function createScreenshots(inputs: Array<SmartScreenshotInput | string>): ScreenshotConfig[] {\n return inputs.map(createScreenshot);\n}\n\nexport function createManifest(config: {\n name: string;\n shortName?: string;\n description?: string;\n themeColor?: string;\n backgroundColor?: string;\n display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n orientation?: 'portrait' | 'landscape' | 'any';\n id?: string;\n startUrl?: string;\n scope?: string;\n lang?: string;\n dir?: 'ltr' | 'rtl' | 'auto';\n icons?: IconPaths | ManifestConfig['icons'];\n screenshots?: ScreenshotConfig[];\n protocol_handlers?: ProtocolHandler[];\n}): () => MetadataRoute.Manifest {\n return () => {\n // Convert IconPaths to manifest icons format\n let manifestIcons: MetadataRoute.Manifest['icons'];\n\n if (Array.isArray(config.icons)) {\n // Already in manifest format\n manifestIcons = config.icons as MetadataRoute.Manifest['icons'];\n } else if (config.icons) {\n // Convert IconPaths to manifest icons\n const { logo192, logo384, logo512 } = config.icons as IconPaths;\n manifestIcons = [\n ...(logo192\n ? [\n {\n src: logo192,\n sizes: '192x192',\n type: 'image/png',\n purpose: 'maskable' as const,\n },\n ]\n : []),\n ...(logo384\n ? [\n {\n src: logo384,\n sizes: '384x384',\n type: 'image/png',\n },\n ]\n : []),\n ...(logo512\n ? [\n {\n src: logo512,\n sizes: '512x512',\n type: 'image/png',\n },\n ]\n : []),\n ];\n }\n\n const manifest: MetadataRoute.Manifest = {\n name: config.name,\n short_name: config.shortName || config.name,\n description: config.description || config.name,\n id: config.id || config.startUrl || '/',\n start_url: config.startUrl || '/',\n scope: config.scope || '/',\n display: config.display || 'standalone',\n orientation: config.orientation || 'portrait',\n background_color: config.backgroundColor || '#000000',\n theme_color: config.themeColor || '#ffffff',\n lang: config.lang || 'en',\n dir: config.dir || 'ltr',\n icons: manifestIcons,\n // Removed forced gcm_sender_id to avoid potential conflicts with VAPID\n // gcm_sender_id: '103953800507',\n };\n\n // Add screenshots if provided (for Richer PWA Install UI)\n if (config.screenshots && config.screenshots.length > 0) {\n (manifest as any).screenshots = config.screenshots;\n }\n\n // Add protocol handlers if provided\n if (config.protocol_handlers && config.protocol_handlers.length > 0) {\n (manifest as any).protocol_handlers = config.protocol_handlers;\n }\n\n return manifest;\n };\n}\n\n/**\n * Generate manifest.json content (legacy)\n *\n * @deprecated Use createManifest() instead\n */\nexport function generateManifest(config: ManifestConfig): Record<string, any> {\n return createManifest(config)();\n}\n","/**\n * PWA (Progressive Web App) Plugin\n *\n * Configures Serwist for service worker and offline support\n * Modern PWA solution for Next.js 15+ with App Router\n *\n * @see https://serwist.pages.dev/\n */\n\nimport type { NextConfig } from 'next';\nimport { consola } from 'consola';\n\nexport interface PWAPluginOptions {\n /**\n * Destination directory for service worker files\n * @default 'public'\n * @deprecated Use swDest instead\n */\n dest?: string;\n\n /**\n * Path to service worker source file (relative to project root)\n * @default 'app/sw.ts'\n */\n swSrc?: string;\n\n /**\n * Destination for compiled service worker\n * @default 'public/sw.js'\n */\n swDest?: string;\n\n /**\n * Disable PWA completely\n * @default false in production, true in development\n * @example disable: process.env.NODE_ENV === 'development'\n */\n disable?: boolean;\n\n /**\n * Cache on navigation - cache pages when navigating\n * @default true\n */\n cacheOnNavigation?: boolean;\n\n /**\n * Reload app when device goes back online\n * @default true\n */\n reloadOnOnline?: boolean;\n\n /**\n * Additional Serwist options\n * @see https://serwist.pages.dev/docs/next/configuring\n */\n serwistOptions?: Record<string, any>;\n}\n\n/**\n * Add PWA configuration to Next.js config using Serwist\n *\n * @example Basic usage\n * ```ts\n * import { createBaseNextConfig, withPWA } from '@djangocfg/nextjs/config';\n *\n * const nextConfig = createBaseNextConfig({...});\n *\n * export default withPWA(nextConfig, {\n * swSrc: 'app/sw.ts',\n * disable: process.env.NODE_ENV === 'development',\n * });\n * ```\n *\n * @example Integrated with createBaseNextConfig\n * ```ts\n * import { createBaseNextConfig } from '@djangocfg/nextjs/config';\n *\n * const config = createBaseNextConfig({\n * pwa: {\n * swSrc: 'app/sw.ts',\n * disable: false,\n * },\n * });\n *\n * export default config;\n * ```\n */\nexport function withPWA(\n nextConfig: NextConfig,\n options: PWAPluginOptions = {}\n): NextConfig {\n const isDev = process.env.NODE_ENV === 'development';\n\n const defaultOptions: PWAPluginOptions = {\n swSrc: 'app/sw.ts',\n swDest: 'public/sw.js',\n disable: options.disable !== undefined ? options.disable : isDev,\n cacheOnNavigation: true,\n reloadOnOnline: true,\n ...options,\n };\n\n try {\n const withSerwistInit = require('@serwist/next').default;\n\n const withSerwist = withSerwistInit({\n swSrc: defaultOptions.swSrc,\n swDest: defaultOptions.swDest,\n disable: defaultOptions.disable,\n cacheOnNavigation: defaultOptions.cacheOnNavigation,\n reloadOnOnline: defaultOptions.reloadOnOnline,\n ...defaultOptions.serwistOptions,\n });\n\n return withSerwist(nextConfig);\n } catch (error) {\n consola.error('Failed to configure Serwist:', error);\n return nextConfig;\n }\n}\n\n/**\n * Get service worker template content\n *\n * Returns ready-to-use service worker code for app/sw.ts\n *\n * @example\n * ```ts\n * import { getServiceWorkerTemplate } from '@djangocfg/nextjs/config';\n *\n * // Copy this to your app/sw.ts file\n * console.log(getServiceWorkerTemplate());\n * ```\n */\nexport function getServiceWorkerTemplate(): string {\n return `/**\n * Service Worker (Serwist)\n *\n * Modern PWA service worker using Serwist\n */\n\nimport { defaultCache } from '@serwist/next/worker';\nimport { Serwist } from 'serwist';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ndeclare const self: any;\n\nconst serwist = new Serwist({\n // Precache entries injected by Serwist build plugin\n precacheEntries: self.__SW_MANIFEST,\n\n // Skip waiting - activate new SW immediately\n skipWaiting: true,\n\n // Take control of all clients immediately\n clientsClaim: true,\n\n // Enable navigation preload for faster loads\n navigationPreload: true,\n\n // Use default Next.js runtime caching strategies\n runtimeCaching: defaultCache,\n\n // Fallback pages for offline\n fallbacks: {\n entries: [\n {\n url: '/_offline',\n matcher({ request }) {\n return request.destination === 'document';\n },\n },\n ],\n },\n});\n\nserwist.addEventListeners();\n`;\n}\n\n// Backward compatibility exports (deprecated)\nexport const defaultRuntimeCaching = [];\nexport function createApiCacheRule() {\n consola.warn('createApiCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\nexport function createStaticAssetRule() {\n consola.warn('createStaticAssetRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\nexport function createCdnCacheRule() {\n consola.warn('createCdnCacheRule is deprecated with Serwist. Use defaultCache from @serwist/next/worker');\n return {};\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,86 @@
|
|
|
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 };
|
|
@@ -0,0 +1,175 @@
|
|
|
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 webpush from "web-push";
|
|
9
|
+
import { consola } from "consola";
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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 webpush, { PushSubscription } from 'web-push';\nimport { consola } from 'consola';\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';\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"]}
|