# @mvp-factory/holy-pwa

Progressive Web App (PWA) utilities and templates extracted from Holy Habit project with manifest generation, service worker management, and offline support.

## Features

- 🏗️ **Manifest Generator** - Template-based PWA manifest.json generation
- ⚙️ **Service Worker Generator** - Configurable service worker with caching strategies
- 📱 **PWA Manager** - Complete PWA lifecycle management and installation handling
- 🔧 **Multiple Templates** - Pre-configured templates for different app types
- 🎯 **TypeScript Support** - Full TypeScript definitions and type safety
- 💾 **Offline Support** - Advanced caching strategies and offline functionality
- 🔔 **Push Notifications** - Built-in push notification support
- 🔄 **Background Sync** - Offline data synchronization capabilities
- 🎨 **Icon Management** - Automated PWA icon generation and management
- 📊 **Update Management** - Automatic PWA update detection and application

## Installation

```bash
npm install @mvp-factory/holy-pwa
```

## Quick Start

### 1. Generate PWA Manifest

```typescript
import { HolyPWA } from '@mvp-factory/holy-pwa';

// Create manifest with template
const manifestGenerator = HolyPWA.manifestTemplates.productivity({
  name: 'My Productivity App',
  shortName: 'ProductivityApp',
  description: 'A powerful productivity application',
  themeColor: '#3b82f6',
  backgroundColor: '#ffffff',
  icons: {
    sizes: [72, 96, 128, 144, 152, 192, 384, 512],
    basePath: '/assets/icons'
  }
});

// Generate manifest.json content
const manifestJSON = manifestGenerator.generateJSON();
console.log(manifestJSON);
```

### 2. Generate Service Worker

```typescript
import { HolyPWA } from '@mvp-factory/holy-pwa';

// Create service worker with advanced template
const serviceWorkerGenerator = HolyPWA.serviceWorkerTemplates.advanced({
  cacheName: 'my-app',
  version: '1.0.0',
  urlsToCache: [
    '/',
    '/assets/css/style.css',
    '/assets/js/main.js',
    '/offline.html'
  ],
  enableBackgroundSync: true,
  enablePushNotifications: true
});

// Generate service worker JavaScript code
const serviceWorkerCode = serviceWorkerGenerator.generate();
console.log(serviceWorkerCode);
```

### 3. Initialize PWA Manager

```typescript
import { HolyPWA } from '@mvp-factory/holy-pwa';

// Create PWA manager with lifecycle events
const pwaManager = HolyPWA.createManager({
  onInstall: () => console.log('PWA installed'),
  onUpdateAvailable: (version) => console.log(`Update available: ${version}`),
  onOffline: () => console.log('App is offline'),
  onOnline: () => console.log('App is back online')
});

// Initialize with service worker
await pwaManager.initialize('/service-worker.js');

// Check installation state
const installState = pwaManager.getInstallationState();
if (installState.isInstallable) {
  const userChoice = await pwaManager.promptInstall();
  console.log('User choice:', userChoice);
}
```

## Core Components

### ManifestGenerator

Template-based PWA manifest generation:

```typescript
import { ManifestGenerator, PWAConfig } from '@mvp-factory/holy-pwa';

const config: PWAConfig = {
  name: 'My App',
  shortName: 'MyApp',
  description: 'My awesome application',
  themeColor: '#3b82f6',
  backgroundColor: '#ffffff',
  startUrl: '/',
  display: 'standalone',
  icons: {
    sizes: [192, 512],
    basePath: '/icons'
  },
  shortcuts: [
    {
      name: 'New Document',
      short_name: 'New Doc',
      description: 'Create new document',
      url: '/new'
    }
  ]
};

const generator = new ManifestGenerator(config);
const manifest = generator.generate();
```

### ServiceWorkerGenerator

Configurable service worker with caching strategies:

```typescript
import { ServiceWorkerGenerator, ServiceWorkerConfig } from '@mvp-factory/holy-pwa';

const config: ServiceWorkerConfig = {
  cacheName: 'my-pwa',
  version: '1.0.0',
  urlsToCache: ['/', '/app.css', '/app.js'],
  networkFirst: ['/api/'],
  cacheFirst: ['/assets/', '/images/'],
  staleWhileRevalidate: ['/data/'],
  enableBackgroundSync: true,
  enablePushNotifications: true,
  offlinePageUrl: '/offline.html'
};

const generator = new ServiceWorkerGenerator(config);
const serviceWorkerCode = generator.generate();

// Write to file
import fs from 'fs';
fs.writeFileSync('public/service-worker.js', serviceWorkerCode);
```

### PWA Manager

Complete PWA lifecycle management:

```typescript
import { PWAManager, PWALifecycleEvents } from '@mvp-factory/holy-pwa';

const events: PWALifecycleEvents = {
  onInstall: () => {
    console.log('PWA installed successfully');
    showInstallSuccessMessage();
  },
  onUpdateAvailable: (version) => {
    showUpdateNotification(version);
  },
  onUpdateApplied: () => {
    showUpdateAppliedMessage();
  },
  onOffline: () => {
    showOfflineBanner();
  },
  onOnline: () => {
    hideOfflineBanner();
    syncOfflineData();
  }
};

const pwaManager = new PWAManager(events);

// Initialize
await pwaManager.initialize();

// Check capabilities
const capabilities = pwaManager.getCapabilities();
console.log('PWA Capabilities:', capabilities);

// Handle installation
if (capabilities.installPrompt) {
  document.getElementById('install-button').addEventListener('click', async () => {
    try {
      const result = await pwaManager.promptInstall();
      console.log('Install result:', result);
    } catch (error) {
      console.error('Install failed:', error);
    }
  });
}

// Handle updates
document.getElementById('update-button').addEventListener('click', async () => {
  const hasUpdate = await pwaManager.checkForUpdates();
  if (hasUpdate) {
    await pwaManager.applyUpdate();
  }
});

// Push notifications
if (capabilities.pushNotifications) {
  const subscription = await pwaManager.subscribeToPush('YOUR_VAPID_KEY');
  console.log('Push subscription:', subscription);
}
```

## Templates

### Manifest Templates

Pre-configured manifest templates for different app types:

```typescript
import { ManifestGenerator } from '@mvp-factory/holy-pwa';

// Productivity app
const productivityManifest = ManifestGenerator.createTemplates().productivity({
  name: 'Task Manager',
  shortName: 'TaskManager',
  description: 'Manage your tasks efficiently'
});

// Social app
const socialManifest = ManifestGenerator.createTemplates().social({
  name: 'Social Connect',
  shortName: 'SocialApp',
  description: 'Connect with friends'
});

// E-commerce app
const ecommerceManifest = ManifestGenerator.createTemplates().ecommerce({
  name: 'Online Store',
  shortName: 'Store',
  description: 'Shop your favorite products'
});
```

### Service Worker Templates

Pre-configured service worker templates:

```typescript
import { ServiceWorkerGenerator } from '@mvp-factory/holy-pwa';

// Basic service worker
const basicSW = ServiceWorkerGenerator.createTemplates().basic({
  cacheName: 'basic-app',
  version: '1.0.0',
  urlsToCache: ['/', '/app.css', '/app.js']
});

// Advanced service worker with all features
const advancedSW = ServiceWorkerGenerator.createTemplates().advanced({
  cacheName: 'advanced-app',
  version: '1.0.0',
  enableBackgroundSync: true,
  enablePushNotifications: true
});

// Offline-first service worker
const offlineSW = ServiceWorkerGenerator.createTemplates().offlineFirst({
  cacheName: 'offline-app',
  version: '1.0.0'
});
```

## Configuration Options

### PWA Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `name` | string | Required | Full app name |
| `shortName` | string | Required | Short app name |
| `description` | string | Required | App description |
| `themeColor` | string | Required | Theme color (hex) |
| `backgroundColor` | string | Required | Background color (hex) |
| `startUrl` | string | `'/'` | App start URL |
| `display` | string | `'standalone'` | Display mode |
| `orientation` | string | `'portrait'` | Screen orientation |
| `scope` | string | `'/'` | App scope |
| `lang` | string | `'en-US'` | App language |
| `categories` | string[] | `['productivity']` | App categories |

### Service Worker Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `cacheName` | string | Required | Cache name prefix |
| `version` | string | Required | Cache version |
| `urlsToCache` | string[] | Required | URLs to cache on install |
| `networkFirst` | string[] | `[]` | Network-first patterns |
| `cacheFirst` | string[] | `[]` | Cache-first patterns |
| `staleWhileRevalidate` | string[] | `[]` | Stale-while-revalidate patterns |
| `networkOnly` | string[] | `[]` | Network-only patterns |
| `enableBackgroundSync` | boolean | `false` | Enable background sync |
| `enablePushNotifications` | boolean | `false` | Enable push notifications |
| `offlinePageUrl` | string | `'/offline.html'` | Offline page URL |

## Advanced Usage

### Custom Caching Strategies

```typescript
const serviceWorker = new ServiceWorkerGenerator({
  cacheName: 'custom-app',
  version: '2.1.0',
  urlsToCache: ['/', '/app.css'],
  
  // Network first for API calls
  networkFirst: ['/api/', '/data/'],
  
  // Cache first for static assets
  cacheFirst: ['/assets/', '/images/', '/fonts/'],
  
  // Stale while revalidate for dynamic content
  staleWhileRevalidate: ['/posts/', '/news/'],
  
  // Network only for real-time features
  networkOnly: ['/websocket/', '/stream/'],
  
  // Enable advanced features
  enableBackgroundSync: true,
  enablePushNotifications: true,
  skipWaiting: true,
  clientsClaim: true
});
```

### PWA Installation Flow

```typescript
class PWAInstaller {
  private pwaManager: PWAManager;
  
  constructor() {
    this.pwaManager = new PWAManager({
      onInstall: this.onInstalled.bind(this),
      onUpdateAvailable: this.onUpdateAvailable.bind(this)
    });
  }
  
  async initialize() {
    await this.pwaManager.initialize();
    this.setupInstallButton();
    this.checkForUpdates();
  }
  
  private setupInstallButton() {
    const installButton = document.getElementById('install-pwa');
    const state = this.pwaManager.getInstallationState();
    
    if (state.isInstalled) {
      installButton.style.display = 'none';
    } else if (state.isInstallable) {
      installButton.style.display = 'block';
      installButton.addEventListener('click', this.handleInstall.bind(this));
    }
  }
  
  private async handleInstall() {
    try {
      const result = await this.pwaManager.promptInstall();
      if (result === 'accepted') {
        this.showSuccessMessage('App installed successfully!');
      }
    } catch (error) {
      this.showErrorMessage('Installation failed. Please try again.');
    }
  }
  
  private async checkForUpdates() {
    const hasUpdate = await this.pwaManager.checkForUpdates();
    if (hasUpdate) {
      this.showUpdatePrompt();
    }
  }
  
  private onInstalled() {
    document.getElementById('install-pwa').style.display = 'none';
    this.showSuccessMessage('Welcome to the app!');
  }
  
  private onUpdateAvailable(version: string) {
    this.showUpdateNotification(`New version (${version}) available!`);
  }
}

// Initialize PWA installer
const installer = new PWAInstaller();
installer.initialize();
```

### Push Notifications Setup

```typescript
class PushNotificationManager {
  private pwaManager: PWAManager;
  private vapidKey = 'YOUR_VAPID_PUBLIC_KEY';
  
  constructor(pwaManager: PWAManager) {
    this.pwaManager = pwaManager;
  }
  
  async setupPushNotifications() {
    try {
      // Request permission
      const permission = await this.pwaManager.requestNotificationPermission();
      
      if (permission === 'granted') {
        // Subscribe to push notifications
        const subscription = await this.pwaManager.subscribeToPush(this.vapidKey);
        
        // Send subscription to server
        await this.sendSubscriptionToServer(subscription);
        
        console.log('Push notifications enabled');
      }
    } catch (error) {
      console.error('Push notification setup failed:', error);
    }
  }
  
  private async sendSubscriptionToServer(subscription: PushSubscription) {
    await fetch('/api/push/subscribe', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        subscription: subscription.toJSON()
      })
    });
  }
  
  async unsubscribe() {
    const success = await this.pwaManager.unsubscribeFromPush();
    if (success) {
      // Notify server
      await fetch('/api/push/unsubscribe', { method: 'POST' });
    }
    return success;
  }
}
```

### Offline Data Management

```typescript
class OfflineDataManager {
  private pwaManager: PWAManager;
  
  constructor(pwaManager: PWAManager) {
    this.pwaManager = pwaManager;
    this.setupOfflineHandling();
  }
  
  private setupOfflineHandling() {
    // Listen for online/offline events
    window.addEventListener('online', this.handleOnline.bind(this));
    window.addEventListener('offline', this.handleOffline.bind(this));
  }
  
  private handleOffline() {
    console.log('App went offline');
    this.showOfflineBanner();
    this.enableOfflineMode();
  }
  
  private handleOnline() {
    console.log('App is back online');
    this.hideOfflineBanner();
    this.syncOfflineData();
  }
  
  private async syncOfflineData() {
    const offlineData = this.getOfflineData();
    
    for (const item of offlineData) {
      try {
        await this.syncItem(item);
        this.removeOfflineItem(item.id);
      } catch (error) {
        console.error('Sync failed for item:', item.id, error);
      }
    }
  }
  
  private getOfflineData(): any[] {
    const data = localStorage.getItem('offline-data');
    return data ? JSON.parse(data) : [];
  }
  
  private async syncItem(item: any) {
    const response = await fetch('/api/sync', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(item)
    });
    
    if (!response.ok) {
      throw new Error(`Sync failed: ${response.statusText}`);
    }
  }
  
  private removeOfflineItem(id: string) {
    const data = this.getOfflineData();
    const filtered = data.filter(item => item.id !== id);
    localStorage.setItem('offline-data', JSON.stringify(filtered));
  }
}
```

## Complete Example

Here's a complete example of setting up a PWA:

```typescript
import { HolyPWA, PWAConfig, ServiceWorkerConfig } from '@mvp-factory/holy-pwa';
import fs from 'fs';

// 1. Generate manifest.json
const manifestConfig: PWAConfig = {
  name: 'My Awesome PWA',
  shortName: 'AwesomePWA',
  description: 'An awesome progressive web application',
  themeColor: '#3b82f6',
  backgroundColor: '#ffffff',
  icons: {
    sizes: [72, 96, 128, 144, 152, 192, 384, 512],
    basePath: '/assets/icons'
  },
  shortcuts: [
    {
      name: 'New Task',
      short_name: 'New Task',
      description: 'Create a new task',
      url: '/new-task'
    }
  ]
};

const manifestGenerator = new HolyPWA.ManifestGenerator(manifestConfig);
const manifestJSON = manifestGenerator.generateJSON();

// Write manifest.json
fs.writeFileSync('public/manifest.json', manifestJSON);

// 2. Generate service worker
const swConfig: ServiceWorkerConfig = {
  cacheName: 'awesome-pwa',
  version: '1.0.0',
  urlsToCache: [
    '/',
    '/assets/css/app.css',
    '/assets/js/app.js',
    '/offline.html'
  ],
  networkFirst: ['/api/'],
  cacheFirst: ['/assets/', '/images/'],
  enableBackgroundSync: true,
  enablePushNotifications: true,
  offlinePageUrl: '/offline.html'
};

const swGenerator = new HolyPWA.ServiceWorkerGenerator(swConfig);
const serviceWorkerCode = swGenerator.generate();

// Write service worker
fs.writeFileSync('public/service-worker.js', serviceWorkerCode);

// 3. Initialize PWA in client-side code
const pwaManager = HolyPWA.createManager({
  onInstall: () => console.log('PWA installed!'),
  onUpdateAvailable: (version) => console.log(`Update available: ${version}`),
  onOffline: () => document.body.classList.add('offline'),
  onOnline: () => document.body.classList.remove('offline')
});

// Initialize on page load
document.addEventListener('DOMContentLoaded', async () => {
  await pwaManager.initialize('/service-worker.js');
  
  // Setup install button
  const installButton = document.getElementById('install-btn');
  const state = pwaManager.getInstallationState();
  
  if (state.isInstallable) {
    installButton.style.display = 'block';
    installButton.addEventListener('click', async () => {
      await pwaManager.promptInstall();
    });
  }
});
```

## HTML Integration

Add to your HTML `<head>`:

```html
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">

<!-- Theme colors -->
<meta name="theme-color" content="#3b82f6">
<meta name="apple-mobile-web-app-status-bar-style" content="default">

<!-- PWA meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="My PWA">
<meta name="mobile-web-app-capable" content="yes">

<!-- Icons -->
<link rel="apple-touch-icon" href="/assets/icons/icon-192x192.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icons/icon-192x192.png">

<!-- Service Worker Registration -->
<script>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register('/service-worker.js')
        .then(registration => {
          console.log('SW registered: ', registration);
        })
        .catch(registrationError => {
          console.log('SW registration failed: ', registrationError);
        });
    });
  }
</script>
```

## Error Handling

```typescript
import { PWAError, ManifestError, ServiceWorkerError } from '@mvp-factory/holy-pwa';

try {
  const pwaManager = new PWAManager();
  await pwaManager.initialize();
} catch (error) {
  if (error instanceof PWAError) {
    switch (error.code) {
      case 'SERVICE_WORKER_FAILED':
        console.error('Service Worker failed:', error.message);
        break;
      case 'INSTALL_FAILED':
        console.error('Installation failed:', error.message);
        break;
      case 'NOTIFICATION_DENIED':
        console.error('Notifications denied:', error.message);
        break;
      default:
        console.error('PWA error:', error.message);
    }
  }
}
```

## Testing

```bash
# Run tests
npm test

# Test PWA features
npm run test:pwa

# Lighthouse PWA audit
npx lighthouse https://your-app.com --view
```

## Development vs Production

### Development

```typescript
// Enable debugging and verbose logging
const pwaManager = HolyPWA.createManager({
  onInstall: () => console.log('[DEV] PWA installed'),
  onUpdateAvailable: (v) => console.log(`[DEV] Update: ${v}`),
  onOffline: () => console.log('[DEV] Offline'),
  onOnline: () => console.log('[DEV] Online')
});

// Use development service worker
const swGenerator = HolyPWA.serviceWorkerTemplates.advanced({
  cacheName: 'dev-pwa',
  version: `dev-${Date.now()}`, // Unique version for development
  enableBackgroundSync: false,  // Disable for development
  skipWaiting: true,           // Always update immediately
  clientsClaim: true
});
```

### Production

```typescript
// Production PWA manager
const pwaManager = HolyPWA.createManager({
  onInstall: () => analytics.track('pwa_installed'),
  onUpdateAvailable: showUpdatePrompt,
  onOffline: enableOfflineMode,
  onOnline: syncOfflineData
});

// Production service worker
const swGenerator = HolyPWA.serviceWorkerTemplates.advanced({
  cacheName: 'prod-pwa',
  version: process.env.APP_VERSION,
  enableBackgroundSync: true,
  enablePushNotifications: true,
  skipWaiting: false,  // Wait for user confirmation
  clientsClaim: false
});
```

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

MIT © MVP Factory

## Support

- 📧 Email: support@mvp-factory.dev
- 🐛 Issues: [GitHub Issues](https://github.com/mvp-factory/modules/issues)
- 📖 Documentation: [Full Documentation](https://docs.mvp-factory.dev/modules/holy-pwa)

---

**Extracted from Holy Habit project** - Battle-tested PWA system used in production with advanced offline support, push notifications, and seamless app-like experience.