@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,85 +1,6 @@
1
1
  import { Viewport, Metadata, MetadataRoute } from 'next';
2
2
  export { P as PWAPluginOptions, c as createApiCacheRule, b as createCdnCacheRule, a as createStaticAssetRule, d as defaultRuntimeCaching, g as getServiceWorkerTemplate, w as withPWA } from '../plugin-DuRJ_Jq6.mjs';
3
3
 
4
- /**
5
- * PWA Push Notifications Client Utilities
6
- *
7
- * Functions for requesting permission, subscribing to push notifications,
8
- * and sending test notifications
9
- */
10
- /**
11
- * Check if push notifications are supported
12
- */
13
- declare function isPushNotificationSupported(): boolean;
14
- /**
15
- * Get current notification permission status
16
- */
17
- declare function getNotificationPermission(): NotificationPermission;
18
- /**
19
- * Request notification permission from user
20
- */
21
- declare function requestNotificationPermission(): Promise<NotificationPermission>;
22
- interface PushSubscriptionOptions {
23
- /**
24
- * VAPID public key (base64 encoded)
25
- * Generate with: npx web-push generate-vapid-keys
26
- */
27
- vapidPublicKey: string;
28
- /**
29
- * User visible only (required for Chrome)
30
- * @default true
31
- */
32
- userVisibleOnly?: boolean;
33
- }
34
- /**
35
- * Subscribe to push notifications
36
- *
37
- * @example
38
- * ```ts
39
- * const subscription = await subscribeToPushNotifications({
40
- * vapidPublicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
41
- * });
42
- *
43
- * // Send subscription to your backend
44
- * await fetch('/api/push/subscribe', {
45
- * method: 'POST',
46
- * headers: { 'Content-Type': 'application/json' },
47
- * body: JSON.stringify(subscription),
48
- * });
49
- * ```
50
- */
51
- declare function subscribeToPushNotifications(options: PushSubscriptionOptions): Promise<PushSubscription>;
52
- /**
53
- * Unsubscribe from push notifications
54
- */
55
- declare function unsubscribeFromPushNotifications(): Promise<boolean>;
56
- /**
57
- * Get current push subscription
58
- */
59
- declare function getPushSubscription(): Promise<PushSubscription | null>;
60
- /**
61
- * Show a local notification (doesn't require push)
62
- *
63
- * @example
64
- * ```ts
65
- * await showLocalNotification({
66
- * title: 'Hello!',
67
- * body: 'This is a test notification',
68
- * icon: '/icon.png',
69
- * data: { url: '/some-page' },
70
- * });
71
- * ```
72
- */
73
- declare function showLocalNotification(options: {
74
- title: string;
75
- body?: string;
76
- icon?: string;
77
- badge?: string;
78
- tag?: string;
79
- data?: any;
80
- requireInteraction?: boolean;
81
- }): Promise<void>;
82
-
83
4
  /**
84
5
  * PWA Manifest Metadata Utilities
85
6
  *
@@ -271,4 +192,4 @@ declare function createManifest(config: {
271
192
  */
272
193
  declare function generateManifest(config: ManifestConfig): Record<string, any>;
273
194
 
274
- export { type IconPaths, type ManifestConfig, type ProtocolHandler, type PushSubscriptionOptions, type ScreenshotConfig, type SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest, getNotificationPermission, getPushSubscription, isPushNotificationSupported, requestNotificationPermission, showLocalNotification, subscribeToPushNotifications, unsubscribeFromPushNotifications };
195
+ export { type IconPaths, type ManifestConfig, type ProtocolHandler, type ScreenshotConfig, type SmartScreenshotInput, createManifest, createManifestMetadata, createScreenshot, createScreenshots, createViewport, generateManifest };
@@ -5,90 +5,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
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
8
  // src/pwa/manifest.ts
93
9
  function createViewport(config) {
94
10
  return {
@@ -319,14 +235,7 @@ export {
319
235
  createViewport,
320
236
  defaultRuntimeCaching,
321
237
  generateManifest,
322
- getNotificationPermission,
323
- getPushSubscription,
324
238
  getServiceWorkerTemplate,
325
- isPushNotificationSupported,
326
- requestNotificationPermission,
327
- showLocalNotification,
328
- subscribeToPushNotifications,
329
- unsubscribeFromPushNotifications,
330
239
  withPWA
331
240
  };
332
241
  //# sourceMappingURL=index.mjs.map
@@ -1 +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 const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\n\n // Determine if PWA should be disabled:\n // - Explicitly disabled via options\n // - In development mode (default)\n // - Static build (output: 'export' doesn't support service workers)\n const shouldDisable = options.disable !== undefined\n ? options.disable\n : (isDev || isStaticBuild);\n\n const defaultOptions: PWAPluginOptions = {\n swSrc: 'app/sw.ts',\n swDest: 'public/sw.js',\n disable: shouldDisable,\n cacheOnNavigation: true,\n reloadOnOnline: true,\n ...options,\n };\n\n try {\n // Suppress Turbopack warning - it only applies to dev mode, not production builds\n // The warning is misleading when running `next build` with Turbopack\n // See: https://github.com/serwist/serwist/issues/54\n if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {\n process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = '1';\n }\n\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":[]}
1
+ {"version":3,"sources":["../../src/pwa/manifest.ts","../../src/pwa/plugin.ts"],"sourcesContent":["/**\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 const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\n\n // Determine if PWA should be disabled:\n // - Explicitly disabled via options\n // - In development mode (default)\n // - Static build (output: 'export' doesn't support service workers)\n const shouldDisable = options.disable !== undefined\n ? options.disable\n : (isDev || isStaticBuild);\n\n const defaultOptions: PWAPluginOptions = {\n swSrc: 'app/sw.ts',\n swDest: 'public/sw.js',\n disable: shouldDisable,\n cacheOnNavigation: true,\n reloadOnOnline: true,\n ...options,\n };\n\n try {\n // Suppress Turbopack warning - it only applies to dev mode, not production builds\n // The warning is misleading when running `next build` with Turbopack\n // See: https://github.com/serwist/serwist/issues/54\n if (!process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING) {\n process.env.SERWIST_SUPPRESS_TURBOPACK_WARNING = '1';\n }\n\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":[]}
@@ -24,19 +24,6 @@ interface ServiceWorkerOptions {
24
24
  * @default true
25
25
  */
26
26
  navigationPreload?: boolean;
27
- /**
28
- * Enable push notifications
29
- * @default false
30
- */
31
- enablePushNotifications?: boolean;
32
- /**
33
- * Default notification icon
34
- */
35
- notificationIcon?: string;
36
- /**
37
- * Default notification badge
38
- */
39
- notificationBadge?: string;
40
27
  }
41
28
  /**
42
29
  * Create and initialize Serwist service worker
@@ -6,10 +6,7 @@ function createServiceWorker(options = {}) {
6
6
  offlineFallback = "/_offline",
7
7
  skipWaiting = true,
8
8
  clientsClaim = true,
9
- navigationPreload = true,
10
- enablePushNotifications = false,
11
- notificationIcon,
12
- notificationBadge
9
+ navigationPreload = true
13
10
  } = options;
14
11
  const self = globalThis;
15
12
  console.log("SW: Worker Initialized \u{1F680}");
@@ -37,59 +34,6 @@ function createServiceWorker(options = {}) {
37
34
  }
38
35
  });
39
36
  serwist.addEventListeners();
40
- if (enablePushNotifications) {
41
- self.addEventListener("push", (event) => {
42
- let data;
43
- try {
44
- data = event.data?.json() || {};
45
- console.log("SW: Push Received (JSON)", data);
46
- } catch (err) {
47
- console.log("SW: Push Received (Raw)", event.data?.text());
48
- data = {};
49
- }
50
- const {
51
- title = "Notification",
52
- body = "",
53
- icon = notificationIcon,
54
- badge = notificationBadge,
55
- data: notificationData = {},
56
- tag,
57
- requireInteraction = false,
58
- vibrate = [200, 100, 200],
59
- // Default vibration pattern
60
- silent = false
61
- // Default to playing sound
62
- } = data;
63
- event.waitUntil(
64
- self.registration.showNotification(title, {
65
- body,
66
- icon,
67
- badge,
68
- data: notificationData,
69
- tag,
70
- requireInteraction,
71
- vibrate,
72
- silent
73
- })
74
- );
75
- });
76
- self.addEventListener("notificationclick", (event) => {
77
- event.notification.close();
78
- const urlToOpen = event.notification.data?.url || "/";
79
- event.waitUntil(
80
- self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
81
- for (const client of clientList) {
82
- if (client.url === urlToOpen && "focus" in client) {
83
- return client.focus();
84
- }
85
- }
86
- if (self.clients.openWindow) {
87
- return self.clients.openWindow(urlToOpen);
88
- }
89
- })
90
- );
91
- });
92
- }
93
37
  }
94
38
  export {
95
39
  createServiceWorker
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/pwa/worker/index.ts"],"sourcesContent":["/**\n * Service Worker Utilities\n *\n * Ready-to-use Serwist configuration for Next.js PWA\n */\n\n/// <reference lib=\"webworker\" />\n\nimport { Serwist } from 'serwist';\n\nimport { defaultCache } from '@serwist/next/worker';\n\nexport interface ServiceWorkerOptions {\n /**\n * Offline fallback URL\n * @default '/_offline'\n */\n offlineFallback?: string;\n\n /**\n * Skip waiting - activate new SW immediately\n * @default true\n */\n skipWaiting?: boolean;\n\n /**\n * Take control of all clients immediately\n * @default true\n */\n clientsClaim?: boolean;\n\n /**\n * Enable navigation preload for faster loads\n * @default true\n */\n navigationPreload?: boolean;\n\n /**\n * Enable push notifications\n * @default false\n */\n enablePushNotifications?: boolean;\n\n /**\n * Default notification icon\n */\n notificationIcon?: string;\n\n /**\n * Default notification badge\n */\n notificationBadge?: string;\n}\n\n/**\n * Create and initialize Serwist service worker\n *\n * @example\n * ```ts\n * // app/sw.ts\n * import { createServiceWorker } from '@djangocfg/nextjs/worker';\n *\n * createServiceWorker({\n * offlineFallback: '/_offline',\n * });\n * ```\n */\nexport function createServiceWorker(options: ServiceWorkerOptions = {}) {\n const {\n offlineFallback = '/_offline',\n skipWaiting = true,\n clientsClaim = true,\n navigationPreload = true,\n enablePushNotifications = false,\n notificationIcon,\n notificationBadge,\n } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const self = globalThis as any;\n console.log('SW: Worker Initialized 🚀');\n\n const 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,\n\n // Take control of all clients immediately\n clientsClaim,\n\n // Enable navigation preload for faster loads\n navigationPreload,\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: offlineFallback,\n matcher({ request }) {\n return request.destination === 'document';\n },\n },\n ],\n },\n });\n\n serwist.addEventListeners();\n\n // Push notification support\n if (enablePushNotifications) {\n // Handle push events\n self.addEventListener('push', (event: PushEvent) => {\n let data;\n try {\n data = event.data?.json() || {};\n console.log('SW: Push Received (JSON)', data);\n } catch (err) {\n console.log('SW: Push Received (Raw)', event.data?.text());\n data = {};\n }\n\n const {\n title = 'Notification',\n body = '',\n icon = notificationIcon,\n badge = notificationBadge,\n data: notificationData = {},\n tag,\n requireInteraction = false,\n vibrate = [200, 100, 200], // Default vibration pattern\n silent = false, // Default to playing sound\n } = data;\n\n event.waitUntil(\n self.registration.showNotification(title, {\n body,\n icon,\n badge,\n data: notificationData,\n tag,\n requireInteraction,\n vibrate,\n silent,\n })\n );\n });\n\n // Handle notification clicks\n self.addEventListener('notificationclick', (event: NotificationEvent) => {\n event.notification.close();\n\n const urlToOpen = event.notification.data?.url || '/';\n\n event.waitUntil(\n self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList: readonly WindowClient[]) => {\n // Check if there's already a window open with the URL\n for (const client of clientList) {\n if (client.url === urlToOpen && 'focus' in client) {\n return client.focus();\n }\n }\n // If not, open a new window\n if (self.clients.openWindow) {\n return self.clients.openWindow(urlToOpen);\n }\n })\n );\n });\n }\n}\n"],"mappings":";AAQA,SAAS,eAAe;AAExB,SAAS,oBAAoB;AAyDtB,SAAS,oBAAoB,UAAgC,CAAC,GAAG;AACtE,QAAM;AAAA,IACJ,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,oBAAoB;AAAA,IACpB,0BAA0B;AAAA,IAC1B;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,OAAO;AACb,UAAQ,IAAI,kCAA2B;AAEvC,QAAM,UAAU,IAAI,QAAQ;AAAA;AAAA,IAE1B,iBAAiB,KAAK;AAAA;AAAA,IAGtB;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA,gBAAgB;AAAA;AAAA,IAGhB,WAAW;AAAA,MACT,SAAS;AAAA,QACP;AAAA,UACE,KAAK;AAAA,UACL,QAAQ,EAAE,QAAQ,GAAG;AACnB,mBAAO,QAAQ,gBAAgB;AAAA,UACjC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ,kBAAkB;AAG1B,MAAI,yBAAyB;AAE3B,SAAK,iBAAiB,QAAQ,CAAC,UAAqB;AAClD,UAAI;AACJ,UAAI;AACF,eAAO,MAAM,MAAM,KAAK,KAAK,CAAC;AAC9B,gBAAQ,IAAI,4BAA4B,IAAI;AAAA,MAC9C,SAAS,KAAK;AACZ,gBAAQ,IAAI,2BAA2B,MAAM,MAAM,KAAK,CAAC;AACzD,eAAO,CAAC;AAAA,MACV;AAEA,YAAM;AAAA,QACJ,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,MAAM,mBAAmB,CAAC;AAAA,QAC1B;AAAA,QACA,qBAAqB;AAAA,QACrB,UAAU,CAAC,KAAK,KAAK,GAAG;AAAA;AAAA,QACxB,SAAS;AAAA;AAAA,MACX,IAAI;AAEJ,YAAM;AAAA,QACJ,KAAK,aAAa,iBAAiB,OAAO;AAAA,UACxC;AAAA,UACA;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,SAAK,iBAAiB,qBAAqB,CAAC,UAA6B;AACvE,YAAM,aAAa,MAAM;AAEzB,YAAM,YAAY,MAAM,aAAa,MAAM,OAAO;AAElD,YAAM;AAAA,QACJ,KAAK,QAAQ,SAAS,EAAE,MAAM,UAAU,qBAAqB,KAAK,CAAC,EAAE,KAAK,CAAC,eAAwC;AAEjH,qBAAW,UAAU,YAAY;AAC/B,gBAAI,OAAO,QAAQ,aAAa,WAAW,QAAQ;AACjD,qBAAO,OAAO,MAAM;AAAA,YACtB;AAAA,UACF;AAEA,cAAI,KAAK,QAAQ,YAAY;AAC3B,mBAAO,KAAK,QAAQ,WAAW,SAAS;AAAA,UAC1C;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/pwa/worker/index.ts"],"sourcesContent":["/**\n * Service Worker Utilities\n *\n * Ready-to-use Serwist configuration for Next.js PWA\n */\n\n/// <reference lib=\"webworker\" />\n\nimport { Serwist } from 'serwist';\n\nimport { defaultCache } from '@serwist/next/worker';\n\nexport interface ServiceWorkerOptions {\n /**\n * Offline fallback URL\n * @default '/_offline'\n */\n offlineFallback?: string;\n\n /**\n * Skip waiting - activate new SW immediately\n * @default true\n */\n skipWaiting?: boolean;\n\n /**\n * Take control of all clients immediately\n * @default true\n */\n clientsClaim?: boolean;\n\n /**\n * Enable navigation preload for faster loads\n * @default true\n */\n navigationPreload?: boolean;\n}\n\n/**\n * Create and initialize Serwist service worker\n *\n * @example\n * ```ts\n * // app/sw.ts\n * import { createServiceWorker } from '@djangocfg/nextjs/worker';\n *\n * createServiceWorker({\n * offlineFallback: '/_offline',\n * });\n * ```\n */\nexport function createServiceWorker(options: ServiceWorkerOptions = {}) {\n const {\n offlineFallback = '/_offline',\n skipWaiting = true,\n clientsClaim = true,\n navigationPreload = true,\n } = options;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const self = globalThis as any;\n console.log('SW: Worker Initialized 🚀');\n\n const 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,\n\n // Take control of all clients immediately\n clientsClaim,\n\n // Enable navigation preload for faster loads\n navigationPreload,\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: offlineFallback,\n matcher({ request }) {\n return request.destination === 'document';\n },\n },\n ],\n },\n });\n\n serwist.addEventListeners();\n}\n"],"mappings":";AAQA,SAAS,eAAe;AAExB,SAAS,oBAAoB;AAyCtB,SAAS,oBAAoB,UAAgC,CAAC,GAAG;AACtE,QAAM;AAAA,IACJ,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,oBAAoB;AAAA,EACtB,IAAI;AAGJ,QAAM,OAAO;AACb,UAAQ,IAAI,kCAA2B;AAEvC,QAAM,UAAU,IAAI,QAAQ;AAAA;AAAA,IAE1B,iBAAiB,KAAK;AAAA;AAAA,IAGtB;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA;AAAA;AAAA,IAGA,gBAAgB;AAAA;AAAA,IAGhB,WAAW;AAAA,MACT,SAAS;AAAA,QACP;AAAA,UACE,KAAK;AAAA,UACL,QAAQ,EAAE,QAAQ,GAAG;AACnB,mBAAO,QAAQ,gBAAgB;AAAA,UACjC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,UAAQ,kBAAkB;AAC5B;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.138",
3
+ "version": "2.1.140",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -89,16 +89,6 @@
89
89
  "import": "./dist/pwa/worker/index.mjs",
90
90
  "default": "./dist/pwa/worker/index.mjs"
91
91
  },
92
- "./pwa/server": {
93
- "types": "./dist/pwa/server/index.d.mts",
94
- "import": "./dist/pwa/server/index.mjs",
95
- "default": "./dist/pwa/server/index.mjs"
96
- },
97
- "./pwa/server/routes": {
98
- "types": "./dist/pwa/server/routes.d.mts",
99
- "import": "./dist/pwa/server/routes.mjs",
100
- "default": "./dist/pwa/server/routes.mjs"
101
- },
102
92
  "./i18n": {
103
93
  "types": "./dist/i18n/index.d.mts",
104
94
  "import": "./dist/i18n/index.mjs",
@@ -147,8 +137,7 @@
147
137
  "LICENSE"
148
138
  ],
149
139
  "bin": {
150
- "djangocfg-docs": "./dist/ai/cli.mjs",
151
- "djangocfg-pwa": "./dist/pwa/cli.mjs"
140
+ "djangocfg-docs": "./dist/ai/cli.mjs"
152
141
  },
153
142
  "scripts": {
154
143
  "build": "tsup",
@@ -156,12 +145,11 @@
156
145
  "clean": "rm -rf dist",
157
146
  "lint": "eslint .",
158
147
  "check": "tsc --noEmit",
159
- "ai-docs": "tsx src/ai/cli.ts",
160
- "pwa": "tsx src/pwa/cli.ts"
148
+ "ai-docs": "tsx src/ai/cli.ts"
161
149
  },
162
150
  "peerDependencies": {
163
- "@djangocfg/i18n": "^2.1.138",
164
- "@djangocfg/ui-core": "^2.1.138",
151
+ "@djangocfg/i18n": "^2.1.140",
152
+ "@djangocfg/ui-core": "^2.1.140",
165
153
  "next": "^16.0.10"
166
154
  },
167
155
  "peerDependenciesMeta": {
@@ -177,20 +165,18 @@
177
165
  "consola": "^3.4.2",
178
166
  "next-intl": "^4.1.0",
179
167
  "semver": "^7.7.3",
180
- "serwist": "^9.2.3",
181
- "web-push": "^3.6.7"
168
+ "serwist": "^9.2.3"
182
169
  },
183
170
  "devDependencies": {
184
- "@djangocfg/i18n": "^2.1.138",
185
- "@djangocfg/ui-core": "^2.1.138",
186
- "@djangocfg/imgai": "^2.1.138",
187
- "@djangocfg/layouts": "^2.1.138",
188
- "@djangocfg/typescript-config": "^2.1.138",
171
+ "@djangocfg/i18n": "^2.1.140",
172
+ "@djangocfg/ui-core": "^2.1.140",
173
+ "@djangocfg/imgai": "^2.1.140",
174
+ "@djangocfg/layouts": "^2.1.140",
175
+ "@djangocfg/typescript-config": "^2.1.140",
189
176
  "@types/node": "^24.7.2",
190
177
  "@types/react": "19.2.2",
191
178
  "@types/react-dom": "19.2.1",
192
179
  "@types/semver": "^7.7.1",
193
- "@types/web-push": "^3.6.4",
194
180
  "@types/webpack": "^5.28.5",
195
181
  "@vercel/og": "^0.8.5",
196
182
  "eslint": "^9.37.0",
package/src/pwa/index.ts CHANGED
@@ -4,6 +4,5 @@
4
4
  * Client-side utilities for Progressive Web Apps
5
5
  */
6
6
 
7
- export * from './notifications';
8
7
  export * from './manifest';
9
8
  export * from './plugin';
@@ -34,22 +34,6 @@ export interface ServiceWorkerOptions {
34
34
  * @default true
35
35
  */
36
36
  navigationPreload?: boolean;
37
-
38
- /**
39
- * Enable push notifications
40
- * @default false
41
- */
42
- enablePushNotifications?: boolean;
43
-
44
- /**
45
- * Default notification icon
46
- */
47
- notificationIcon?: string;
48
-
49
- /**
50
- * Default notification badge
51
- */
52
- notificationBadge?: string;
53
37
  }
54
38
 
55
39
  /**
@@ -71,9 +55,6 @@ export function createServiceWorker(options: ServiceWorkerOptions = {}) {
71
55
  skipWaiting = true,
72
56
  clientsClaim = true,
73
57
  navigationPreload = true,
74
- enablePushNotifications = false,
75
- notificationIcon,
76
- notificationBadge,
77
58
  } = options;
78
59
 
79
60
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -110,66 +91,4 @@ export function createServiceWorker(options: ServiceWorkerOptions = {}) {
110
91
  });
111
92
 
112
93
  serwist.addEventListeners();
113
-
114
- // Push notification support
115
- if (enablePushNotifications) {
116
- // Handle push events
117
- self.addEventListener('push', (event: PushEvent) => {
118
- let data;
119
- try {
120
- data = event.data?.json() || {};
121
- console.log('SW: Push Received (JSON)', data);
122
- } catch (err) {
123
- console.log('SW: Push Received (Raw)', event.data?.text());
124
- data = {};
125
- }
126
-
127
- const {
128
- title = 'Notification',
129
- body = '',
130
- icon = notificationIcon,
131
- badge = notificationBadge,
132
- data: notificationData = {},
133
- tag,
134
- requireInteraction = false,
135
- vibrate = [200, 100, 200], // Default vibration pattern
136
- silent = false, // Default to playing sound
137
- } = data;
138
-
139
- event.waitUntil(
140
- self.registration.showNotification(title, {
141
- body,
142
- icon,
143
- badge,
144
- data: notificationData,
145
- tag,
146
- requireInteraction,
147
- vibrate,
148
- silent,
149
- })
150
- );
151
- });
152
-
153
- // Handle notification clicks
154
- self.addEventListener('notificationclick', (event: NotificationEvent) => {
155
- event.notification.close();
156
-
157
- const urlToOpen = event.notification.data?.url || '/';
158
-
159
- event.waitUntil(
160
- self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList: readonly WindowClient[]) => {
161
- // Check if there's already a window open with the URL
162
- for (const client of clientList) {
163
- if (client.url === urlToOpen && 'focus' in client) {
164
- return client.focus();
165
- }
166
- }
167
- // If not, open a new window
168
- if (self.clients.openWindow) {
169
- return self.clients.openWindow(urlToOpen);
170
- }
171
- })
172
- );
173
- });
174
- }
175
94
  }
@@ -1 +0,0 @@
1
- #!/usr/bin/env node