CrackedRuby CrackedRuby

Overview

Progressive Web Apps (PWAs) represent a web development approach that combines traditional web technologies with native application capabilities. A PWA delivers an app-like experience through standard web technologies—HTML, CSS, and JavaScript—while providing features historically exclusive to native applications: offline functionality, push notifications, home screen installation, and background synchronization.

The term "Progressive Web App" emerged from Google engineers Alex Russell and Frances Berriman in 2015, describing web applications that progressively enhance based on browser capabilities. The core premise centers on graceful degradation: applications function on all browsers but take advantage of advanced features where available.

PWAs operate through three fundamental technologies: service workers for offline functionality and background processing, web app manifests for installation metadata, and HTTPS for secure contexts. Service workers function as programmable network proxies, intercepting and controlling network requests. The web app manifest provides JSON configuration defining application metadata, icons, display modes, and theme colors. HTTPS ensures secure communication required for service worker registration and sensitive API access.

// Service worker registration in application entry point
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(registration => {
      console.log('Service Worker registered:', registration.scope);
    })
    .catch(error => {
      console.error('Service Worker registration failed:', error);
    });
}

The progressive enhancement model means PWAs work across all browsers and devices while offering enhanced experiences on supporting platforms. On Chrome for Android, users can install PWAs to their home screen with full-screen launch capabilities. On iOS Safari, PWAs function with limited service worker support but still provide offline capabilities and home screen installation.

PWAs address several web development challenges: eliminating app store gatekeepers, reducing deployment friction, enabling instant updates, decreasing application size, and improving engagement through features like push notifications and offline access. Companies adopt PWAs to unify web and mobile experiences under single codebases while maintaining native-like performance characteristics.

Key Principles

PWAs adhere to specific architectural principles distinguishing them from traditional web applications. These principles form the foundation for reliable, performant, app-like experiences.

Progressive Enhancement: Applications function on all browsers regardless of capability level. Basic functionality remains accessible on older browsers while modern browsers receive enhanced features. This approach prevents feature detection from blocking core application access. The service worker API checks for support before registration; applications continue functioning without it.

Responsive Design: PWAs adapt to any screen size, orientation, and input method. The single codebase serves mobile phones, tablets, desktops, and emerging form factors. Responsive design extends beyond viewport adaptation to include input methods (touch, mouse, keyboard) and connection quality (offline, slow 3G, broadband).

Connectivity Independence: Service workers enable operation regardless of network conditions. Applications cache critical resources during installation, intercept network requests, and serve cached responses when offline. This principle extends to background synchronization, deferring actions until connectivity returns.

App-like Interface: PWAs present immersive experiences through full-screen display modes, app-style navigation patterns, and platform-appropriate visual design. The web app manifest defines display preferences while application architecture implements navigation stacks and gesture handling characteristic of native applications.

Fresh Content: Despite aggressive caching, PWAs maintain content freshness through strategic cache invalidation and background updates. Service workers implement cache-first or network-first strategies based on resource criticality. Background sync APIs update content during idle periods.

Secure Contexts: PWAs require HTTPS for service worker registration and sensitive API access. This requirement prevents network-level attacks on service worker code and protects sensitive user data. Development environments permit localhost service worker registration without HTTPS.

Discoverable: Search engines index PWAs as standard websites while supporting enhanced metadata through web app manifests. This discoverability contrasts with native applications requiring app store searches and installations.

Re-engageable: Push notifications and background synchronization maintain user engagement without requiring open applications. Web Push API enables server-initiated notifications while background sync handles deferred operations.

Installable: Users add PWAs to home screens or application launchers through browser prompts or manual menu actions. Installation creates standalone application experiences with dedicated windows, task switcher entries, and operating system integration.

Linkable: PWAs maintain web fundamentals through URL-based navigation and deep linking. Specific application states map to URLs, enabling sharing, bookmarking, and search engine indexing. This linkability differentiates PWAs from native applications requiring custom URL schemes.

Service worker lifecycle management implements these principles. Service workers transition through installation, activation, and idle states. During installation, applications pre-cache critical resources. Activation phase performs cleanup of outdated caches. Idle state handles fetch events, intercepting network requests for offline functionality.

// Service worker lifecycle events
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('app-v1').then(cache => {
      return cache.addAll([
        '/',
        '/styles/main.css',
        '/scripts/app.js',
        '/images/logo.png'
      ]);
    })
  );
});

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.filter(name => name !== 'app-v1')
                  .map(name => caches.delete(name))
      );
    })
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

The manifest file declares application metadata and behavior preferences. Browser implementations parse manifests to generate installation prompts, configure standalone windows, and define theme colors. Manifest properties include name, short name, icons at multiple resolutions, start URL, display mode, orientation preferences, and theme colors.

Ruby Implementation

Ruby web frameworks implement PWA capabilities through service worker generation, manifest configuration, and asset pipeline integration. Rails applications particularly benefit from built-in asset management and caching strategies that align with PWA requirements.

Rails applications serve PWAs by generating service worker JavaScript files, configuring web app manifests, and implementing offline-capable API endpoints. The serviceworker-rails gem provides Rails integration for service worker generation and serving. This gem generates service workers from ERB templates with access to Rails routing and asset pipeline helpers.

# Gemfile
gem 'serviceworker-rails'

# config/initializers/serviceworker.rb
Rails.application.configure do
  config.serviceworker.routes.draw do
    match '/service-worker.js'
    match '/manifest.json'
  end
end

Service worker templates access Rails routing helpers and asset paths, ensuring correct URL generation across environments. The template system supports ERB preprocessing, enabling dynamic cache list generation based on compiled assets.

# app/assets/javascripts/service-worker.js.erb
var CACHE_VERSION = 'v1';
var CACHE_NAME = CACHE_VERSION + ':sw-cache-';

function onInstall(event) {
  event.waitUntil(
    caches.open(CACHE_NAME + 'assets').then(function(cache) {
      return cache.addAll([
        '<%= asset_path "application.js" %>',
        '<%= asset_path "application.css" %>',
        '<%= asset_path "logo.png" %>'
      ]);
    })
  );
}

self.addEventListener('install', onInstall);

Rails applications generate web app manifests through JSON templates with Ruby string interpolation. The manifest controller responds to manifest requests with appropriate content types and caching headers.

# config/routes.rb
get '/manifest.json', to: 'pwa#manifest'

# app/controllers/pwa_controller.rb
class PwaController < ApplicationController
  def manifest
    render json: {
      name: 'Application Name',
      short_name: 'App',
      start_url: root_path,
      display: 'standalone',
      background_color: '#ffffff',
      theme_color: '#000000',
      icons: [
        {
          src: view_context.asset_path('icon-192.png'),
          sizes: '192x192',
          type: 'image/png'
        },
        {
          src: view_context.asset_path('icon-512.png'),
          sizes: '512x512',
          type: 'image/png'
        }
      ]
    }
  end
end

Sinatra applications implement PWAs through route handlers serving service workers and manifests. The lightweight framework requires manual implementation of caching strategies and asset serving.

require 'sinatra'
require 'json'

get '/service-worker.js' do
  content_type 'application/javascript'
  cache_control :public, max_age: 0
  erb :'service-worker.js'
end

get '/manifest.json' do
  content_type 'application/json'
  {
    name: 'Sinatra PWA',
    short_name: 'SPWA',
    start_url: '/',
    display: 'standalone',
    icons: [
      {
        src: '/images/icon-192.png',
        sizes: '192x192',
        type: 'image/png'
      }
    ]
  }.to_json
end

Rails API applications serving PWA frontends implement offline-capable endpoints through conditional request handling and ETag support. The stale? method checks conditional request headers, returning 304 Not Modified responses when cached resources remain valid.

class Api::ArticlesController < ApplicationController
  def index
    @articles = Article.all
    
    if stale?(etag: @articles, last_modified: @articles.maximum(:updated_at))
      render json: @articles
    end
  end
  
  def show
    @article = Article.find(params[:id])
    
    if stale?(@article)
      render json: @article
    end
  end
end

Background job processing integrates with PWA push notifications through web push gems. The webpush gem sends push notifications to subscribed clients using VAPID authentication.

# Gemfile
gem 'webpush'

# app/models/push_subscription.rb
class PushSubscription < ApplicationRecord
  def send_notification(payload)
    message = {
      title: payload[:title],
      body: payload[:body],
      icon: payload[:icon]
    }.to_json
    
    Webpush.payload_send(
      message: message,
      endpoint: endpoint,
      p256dh: p256dh_key,
      auth: auth_key,
      vapid: {
        subject: 'mailto:contact@example.com',
        public_key: ENV['VAPID_PUBLIC_KEY'],
        private_key: ENV['VAPID_PRIVATE_KEY']
      }
    )
  end
end

Rails applications manage push subscription lifecycle through dedicated models and controllers. The subscription controller handles client registration and unregistration, storing subscription endpoints and encryption keys.

class PushSubscriptionsController < ApplicationController
  def create
    subscription = current_user.push_subscriptions.create!(
      endpoint: params[:endpoint],
      p256dh_key: params[:keys][:p256dh],
      auth_key: params[:keys][:auth]
    )
    
    render json: subscription, status: :created
  end
  
  def destroy
    subscription = current_user.push_subscriptions.find(params[:id])
    subscription.destroy
    
    head :no_content
  end
end

Practical Examples

PWA implementations demonstrate offline functionality, installation flows, and background synchronization through concrete scenarios. These examples progress from basic service worker caching to complex offline-capable applications.

Basic Offline Caching: A news reading application caches articles for offline consumption. The service worker intercepts article requests, serving cached versions when offline while attempting network requests first when online.

// service-worker.js
const CACHE_NAME = 'news-reader-v1';
const ARTICLE_CACHE = 'articles';

self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  
  // Cache articles with network-first strategy
  if (url.pathname.startsWith('/articles/')) {
    event.respondWith(
      fetch(event.request)
        .then(response => {
          const responseClone = response.clone();
          caches.open(ARTICLE_CACHE).then(cache => {
            cache.put(event.request, responseClone);
          });
          return response;
        })
        .catch(() => {
          return caches.match(event.request);
        })
    );
    return;
  }
  
  // Default cache-first strategy for static assets
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request);
    })
  );
});

The corresponding Rails controller sets appropriate cache headers enabling service worker caching while maintaining content freshness.

class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    
    fresh_when(@article, public: true)
    expires_in 1.hour, public: true
  end
end

Installation Prompt Management: Applications control installation timing through beforeinstallprompt event handling. This example defers the prompt until users complete specific actions, improving installation conversion rates.

// app.js
let deferredPrompt;
const installButton = document.getElementById('install-button');

window.addEventListener('beforeinstallprompt', event => {
  // Prevent automatic prompt
  event.preventDefault();
  
  // Store event for later use
  deferredPrompt = event;
  
  // Show custom install button
  installButton.style.display = 'block';
});

installButton.addEventListener('click', async () => {
  if (!deferredPrompt) return;
  
  // Show installation prompt
  deferredPrompt.prompt();
  
  // Wait for user response
  const { outcome } = await deferredPrompt.userChoice;
  console.log(`User response: ${outcome}`);
  
  // Clear the deferred prompt
  deferredPrompt = null;
  installButton.style.display = 'none';
});

// Detect successful installation
window.addEventListener('appinstalled', () => {
  console.log('PWA installed successfully');
  deferredPrompt = null;
});

Background Synchronization: A task management application defers task creation when offline, synchronizing with the server when connectivity returns. The background sync API registers sync events during offline periods.

// service-worker.js
self.addEventListener('sync', event => {
  if (event.tag === 'sync-tasks') {
    event.waitUntil(syncTasks());
  }
});

async function syncTasks() {
  const db = await openDatabase();
  const pendingTasks = await db.getAll('pending_tasks');
  
  for (const task of pendingTasks) {
    try {
      const response = await fetch('/api/tasks', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(task)
      });
      
      if (response.ok) {
        await db.delete('pending_tasks', task.id);
      }
    } catch (error) {
      console.error('Sync failed for task:', task.id);
    }
  }
}

// Client-side code
async function createTask(taskData) {
  try {
    const response = await fetch('/api/tasks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(taskData)
    });
    
    if (response.ok) {
      return await response.json();
    }
  } catch (error) {
    // Store task for background sync
    const db = await openDatabase();
    await db.add('pending_tasks', taskData);
    
    // Register sync event
    const registration = await navigator.serviceWorker.ready;
    await registration.sync.register('sync-tasks');
  }
}

The Rails API endpoint handles task creation with idempotency keys preventing duplicate submissions during synchronization.

class Api::TasksController < ApplicationController
  def create
    @task = current_user.tasks.find_or_initialize_by(
      idempotency_key: params[:idempotency_key]
    )
    
    if @task.new_record?
      @task.assign_attributes(task_params)
      @task.save!
    end
    
    render json: @task, status: :created
  end
  
  private
  
  def task_params
    params.require(:task).permit(:title, :description, :due_date)
  end
end

Push Notification Integration: An e-commerce application sends order status updates through push notifications. The implementation handles subscription management, notification delivery, and user interaction.

// app.js - Subscription management
async function subscribeToPushNotifications() {
  const registration = await navigator.serviceWorker.ready;
  
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
  });
  
  // Send subscription to server
  await fetch('/api/push_subscriptions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(subscription)
  });
}

// service-worker.js - Notification handling
self.addEventListener('push', event => {
  const data = event.data.json();
  
  const options = {
    body: data.body,
    icon: data.icon,
    badge: '/images/badge.png',
    data: {
      url: data.url
    },
    actions: [
      { action: 'open', title: 'View Order' },
      { action: 'close', title: 'Dismiss' }
    ]
  };
  
  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});

self.addEventListener('notificationclick', event => {
  event.notification.close();
  
  if (event.action === 'open') {
    event.waitUntil(
      clients.openWindow(event.notification.data.url)
    );
  }
});

Rails background jobs trigger push notifications for relevant events.

class OrderStatusNotificationJob < ApplicationJob
  queue_as :default
  
  def perform(order_id)
    order = Order.find(order_id)
    
    order.user.push_subscriptions.each do |subscription|
      subscription.send_notification(
        title: 'Order Update',
        body: "Your order ##{order.id} has been #{order.status}",
        icon: '/images/notification-icon.png',
        url: Rails.application.routes.url_helpers.order_url(order)
      )
    end
  end
end

Performance Considerations

PWA performance directly impacts user engagement and conversion rates. Service worker caching strategies, asset optimization, and runtime performance optimizations determine application responsiveness under varying network conditions.

Service Worker Caching Strategies: Cache strategy selection affects both initial load performance and content freshness. Cache-first strategies prioritize speed by serving cached resources immediately, falling back to network requests on cache misses. Network-first strategies ensure content freshness at the cost of slower responses during network availability. Stale-while-revalidate strategies balance both concerns, serving cached content while fetching updates.

// Implementing multiple caching strategies
const CACHE_STRATEGIES = {
  CACHE_FIRST: 'cache-first',
  NETWORK_FIRST: 'network-first',
  STALE_WHILE_REVALIDATE: 'stale-while-revalidate'
};

function getCacheStrategy(url) {
  if (url.includes('/api/')) return CACHE_STRATEGIES.NETWORK_FIRST;
  if (url.includes('/static/')) return CACHE_STRATEGIES.CACHE_FIRST;
  return CACHE_STRATEGIES.STALE_WHILE_REVALIDATE;
}

self.addEventListener('fetch', event => {
  const strategy = getCacheStrategy(event.request.url);
  
  switch (strategy) {
    case CACHE_STRATEGIES.CACHE_FIRST:
      event.respondWith(cacheFirst(event.request));
      break;
    case CACHE_STRATEGIES.NETWORK_FIRST:
      event.respondWith(networkFirst(event.request));
      break;
    case CACHE_STRATEGIES.STALE_WHILE_REVALIDATE:
      event.respondWith(staleWhileRevalidate(event.request));
      break;
  }
});

async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME);
  const cachedResponse = await cache.match(request);
  
  const fetchPromise = fetch(request).then(response => {
    cache.put(request, response.clone());
    return response;
  });
  
  return cachedResponse || fetchPromise;
}

Cache size management prevents storage quota exhaustion. Service workers implement cache eviction policies based on access patterns or timestamps.

const MAX_CACHE_SIZE = 50;
const CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days

async function trimCache(cacheName) {
  const cache = await caches.open(cacheName);
  const requests = await cache.keys();
  
  if (requests.length > MAX_CACHE_SIZE) {
    await cache.delete(requests[0]);
    await trimCache(cacheName); // Recursive trim
  }
}

async function removeExpiredCache(cacheName) {
  const cache = await caches.open(cacheName);
  const requests = await cache.keys();
  
  for (const request of requests) {
    const response = await cache.match(request);
    const dateHeader = response.headers.get('date');
    const cachedDate = new Date(dateHeader).getTime();
    
    if (Date.now() - cachedDate > CACHE_EXPIRY) {
      await cache.delete(request);
    }
  }
}

Asset Optimization: Rails asset pipeline optimization reduces initial load times and cache sizes. Precompiling assets, enabling compression, and implementing code splitting minimize bandwidth requirements.

# config/environments/production.rb
Rails.application.configure do
  # Enable asset compression
  config.assets.compress = true
  config.assets.css_compressor = :sass
  config.assets.js_compressor = :terser
  
  # Asset fingerprinting for cache busting
  config.assets.digest = true
  
  # Precompile additional assets
  config.assets.precompile += %w[service-worker.js]
  
  # CDN configuration
  config.action_controller.asset_host = ENV['CDN_HOST']
end

Image optimization reduces payload sizes substantially. Rails applications integrate image processing gems for automatic optimization.

# Gemfile
gem 'image_processing'

# app/models/article.rb
class Article < ApplicationRecord
  has_one_attached :cover_image
  
  def optimized_cover_image
    cover_image.variant(
      resize_to_limit: [1200, 630],
      format: :webp,
      saver: { quality: 85 }
    )
  end
end

Runtime Performance: Application shell architecture improves perceived performance by caching the application skeleton separately from dynamic content. The shell loads instantly while dynamic content fetches asynchronously.

// Service worker caching application shell
const SHELL_CACHE = 'app-shell-v1';
const SHELL_FILES = [
  '/',
  '/index.html',
  '/styles/shell.css',
  '/scripts/shell.js',
  '/images/skeleton.svg'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(SHELL_CACHE).then(cache => {
      return cache.addAll(SHELL_FILES);
    })
  );
});

Code splitting defers non-critical JavaScript loading until necessary. Dynamic imports load features on-demand rather than during initial page load.

// Dynamic import for feature modules
async function loadFeature() {
  const module = await import('./features/analytics.js');
  module.initialize();
}

// Load features after initial render
window.addEventListener('load', () => {
  if ('requestIdleCallback' in window) {
    requestIdleCallback(loadFeature);
  } else {
    setTimeout(loadFeature, 1);
  }
});

Network Performance: HTTP/2 server push eliminates round-trip latency for critical resources. Rails applications configure server push through Link headers.

class ApplicationController < ActionController::Base
  before_action :set_link_headers
  
  private
  
  def set_link_headers
    response.headers['Link'] = [
      "</assets/application.css>; rel=preload; as=style",
      "</assets/application.js>; rel=preload; as=script"
    ].join(', ')
  end
end

Request prioritization ensures critical resources load before less important assets. The Priority Hints API guides browser resource scheduling.

<!-- High priority for above-the-fold images -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">

<!-- Low priority for below-the-fold images -->
<img src="footer.jpg" fetchpriority="low" alt="Footer">

Security Implications

PWA security encompasses service worker integrity, secure contexts, content security policies, and permission management. Service workers execute with significant privileges, requiring careful security consideration.

HTTPS Requirement: Service workers function exclusively over HTTPS, preventing network-level code injection attacks. HTTPS ensures service worker code integrity during transmission and protects sensitive API access. Development environments permit localhost service worker registration without HTTPS for testing purposes.

Rails applications enforce HTTPS through configuration and middleware.

# config/environments/production.rb
Rails.application.configure do
  # Force HTTPS for all requests
  config.force_ssl = true
  
  # HSTS header configuration
  config.ssl_options = {
    hsts: {
      expires: 1.year,
      subdomains: true,
      preload: true
    }
  }
end

Content Security Policy: CSP headers mitigate XSS attacks by restricting resource loading sources. PWAs require CSP configuration permitting service worker registration and web app manifest loading.

class ApplicationController < ActionController::Base
  before_action :set_csp_header
  
  private
  
  def set_csp_header
    response.headers['Content-Security-Policy'] = [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "connect-src 'self'",
      "manifest-src 'self'",
      "worker-src 'self'"
    ].join('; ')
  end
end

Service workers themselves require careful CSP configuration. Inline event handlers and eval() present security risks within service worker code.

// Avoid inline event handlers in service workers
// BAD: Using eval or Function constructor
const code = "console.log('unsafe')";
eval(code); // Security risk

// GOOD: Direct function calls
function safeOperation() {
  console.log('safe');
}
safeOperation();

Cross-Origin Resource Sharing: Service workers intercepting cross-origin requests must respect CORS policies. Opaque responses from cross-origin requests without CORS headers fail storage in cache API.

class Api::BaseController < ApplicationController
  before_action :set_cors_headers
  
  private
  
  def set_cors_headers
    response.headers['Access-Control-Allow-Origin'] = request.headers['Origin'] || '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Max-Age'] = '3600'
  end
end

Permission Management: PWAs request permissions for sensitive features like notifications, geolocation, and camera access. Applications must handle permission states explicitly rather than assuming grants.

async function requestNotificationPermission() {
  const permission = await Notification.requestPermission();
  
  switch (permission) {
    case 'granted':
      console.log('Notification permission granted');
      await subscribeToPush();
      break;
    case 'denied':
      console.log('Notification permission denied');
      // Disable notification features
      break;
    case 'default':
      console.log('Notification permission dismissed');
      // Show permission request later
      break;
  }
}

Service Worker Updates: Service workers update automatically when their code changes, but active service workers continue running until pages close. Malicious service workers potentially persist indefinitely without forced updates.

// Implementing forced service worker updates
navigator.serviceWorker.register('/service-worker.js').then(registration => {
  // Check for updates every hour
  setInterval(() => {
    registration.update();
  }, 60 * 60 * 1000);
  
  // Update on visibility change
  document.addEventListener('visibilitychange', () => {
    if (!document.hidden) {
      registration.update();
    }
  });
});

// Service worker self-update
self.addEventListener('message', event => {
  if (event.data === 'skipWaiting') {
    self.skipWaiting();
  }
});

Data Storage Security: IndexedDB and Cache API storage persists across sessions without encryption. Applications storing sensitive data must implement client-side encryption.

async function storeSecureData(key, data) {
  const encryptionKey = await getEncryptionKey();
  
  const encoder = new TextEncoder();
  const dataBuffer = encoder.encode(JSON.stringify(data));
  
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encryptedData = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    encryptionKey,
    dataBuffer
  );
  
  const db = await openDatabase();
  await db.put('secure_store', {
    key,
    iv: Array.from(iv),
    data: Array.from(new Uint8Array(encryptedData))
  });
}

Tools & Ecosystem

PWA development tools span service worker testing, manifest validation, performance analysis, and framework integration. Rails developers access PWA capabilities through gems, JavaScript libraries, and browser development tools.

Serviceworker-Rails: This gem integrates service worker support into Rails applications. It provides routes configuration, ERB template processing, and asset pipeline integration for service worker files.

# Gemfile
gem 'serviceworker-rails'

# config/initializers/serviceworker.rb
Rails.application.configure do
  config.serviceworker.routes.draw do
    match '/service-worker.js'
    match '/manifest.json'
  end
end

# app/assets/javascripts/service-worker.js.erb
var CACHE_VERSION = 'v<%= Time.now.to_i %>';
var CACHE_URLS = [
  '<%= asset_path "application.css" %>',
  '<%= asset_path "application.js" %>'
];

Webpush: The webpush gem implements Web Push protocol for sending notifications to PWA clients. It handles VAPID authentication, message encryption, and subscription management.

# Gemfile
gem 'webpush'

# Generating VAPID keys
vapid_key = Webpush.generate_key

# Sending push notifications
Webpush.payload_send(
  message: JSON.generate({
    title: 'Notification Title',
    body: 'Notification body text'
  }),
  endpoint: subscription.endpoint,
  p256dh: subscription.p256dh_key,
  auth: subscription.auth_key,
  vapid: {
    subject: 'mailto:contact@example.com',
    public_key: vapid_key.public_key,
    private_key: vapid_key.private_key
  }
)

Workbox: Google's Workbox library simplifies service worker development through pre-built caching strategies, routing, and background sync implementations.

// Using Workbox in service worker
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');

// Configure precaching
workbox.precaching.precacheAndRoute([
  { url: '/index.html', revision: 'v1' },
  { url: '/styles/app.css', revision: 'v1' }
]);

// Routing with strategies
workbox.routing.registerRoute(
  /\.(?:png|jpg|jpeg|svg)$/,
  new workbox.strategies.CacheFirst({
    cacheName: 'images',
    plugins: [
      new workbox.expiration.ExpirationPlugin({
        maxEntries: 50,
        maxAgeSeconds: 30 * 24 * 60 * 60
      })
    ]
  })
);

workbox.routing.registerRoute(
  /\/api\//,
  new workbox.strategies.NetworkFirst({
    cacheName: 'api',
    networkTimeoutSeconds: 3
  })
);

Lighthouse: Chrome's Lighthouse tool audits PWA implementation, scoring applications across performance, accessibility, best practices, and SEO categories. Rails applications integrate Lighthouse audits into CI/CD pipelines.

# Running Lighthouse in CI
task :lighthouse do
  require 'json'
  
  output = `lighthouse https://example.com --output=json`
  results = JSON.parse(output)
  
  pwa_score = results['categories']['pwa']['score']
  
  if pwa_score < 0.9
    puts "PWA score below threshold: #{pwa_score}"
    exit 1
  end
end

PWA Builder: Microsoft's PWA Builder analyzes web applications and generates platform-specific packages for app store distribution. It produces manifests, service workers, and packaging files for Windows, Android, and iOS.

Chrome DevTools: Browser development tools provide service worker debugging, cache inspection, manifest validation, and background sync monitoring. The Application panel displays service worker status, cached resources, and storage quotas.

Service worker debugging requires understanding lifecycle events and cache states. DevTools enables manual service worker updates, unregistration, and skip waiting actions during development.

// Development-only debugging helpers
if (process.env.NODE_ENV === 'development') {
  self.addEventListener('message', event => {
    if (event.data === 'clearCache') {
      caches.keys().then(names => {
        names.forEach(name => caches.delete(name));
      });
    }
  });
}

PWA Asset Generator: This command-line tool generates PWA icons and splash screens from source images. It produces multiple resolutions required for various devices and platforms.

npx pwa-asset-generator logo.svg ./public/icons \
  --background "#ffffff" \
  --manifest ./public/manifest.json

Reference

Web App Manifest Properties

Property Type Description
name string Full application name displayed during installation
short_name string Short name for home screen and launcher
description string Application description for app stores
start_url string URL loaded when launching from home screen
display string Display mode: fullscreen, standalone, minimal-ui, browser
orientation string Preferred orientation: any, portrait, landscape
theme_color string Browser theme color in hex format
background_color string Splash screen background color
icons array Icon objects with src, sizes, type, purpose properties
scope string Navigation scope limiting PWA context
categories array Application categories for app stores

Service Worker Lifecycle Events

Event Timing Purpose
install First registration or updated file Pre-cache critical resources
activate After install, when becoming active Clean up old caches, claim clients
fetch Every network request within scope Intercept and handle requests
sync When connectivity restored Process deferred background tasks
push When push notification received Display notifications to user
notificationclick User clicks notification Handle notification interactions
message When client sends message Communicate with active service worker

Caching Strategies

Strategy Behavior Use Case
Cache First Check cache before network Static assets, infrequently changing content
Network First Attempt network before cache fallback API data, frequently updated content
Cache Only Serve exclusively from cache Pre-cached content, offline-only resources
Network Only Always use network, never cache Real-time data, uncacheable resources
Stale While Revalidate Serve cache while fetching update Balance freshness and speed

Rails Service Worker Integration

Gem Purpose Key Features
serviceworker-rails Service worker integration Routes, ERB templates, asset pipeline support
webpush Push notifications VAPID authentication, message encryption
pwa_rails PWA configuration Manifest generation, icon processing

Cache API Methods

Method Parameters Returns Description
caches.open name Promise Cache Opens or creates named cache
cache.add request Promise void Fetches and caches single request
cache.addAll requests Promise void Fetches and caches multiple requests
cache.put request, response Promise void Stores request-response pair
cache.match request Promise Response or undefined Retrieves cached response
cache.matchAll request, options Promise Array Response Retrieves all matching responses
cache.delete request Promise boolean Removes cached response
cache.keys request, options Promise Array Request Lists cached requests
caches.delete name Promise boolean Deletes entire cache
caches.keys none Promise Array string Lists all cache names

Background Sync API

Method Purpose Requirements
registration.sync.register Register sync event Service worker registration
sync event handler Process deferred operations Service worker context
registration.sync.getTags List pending sync tags Service worker registration

Push API Methods

Method Parameters Returns Description
pushManager.subscribe options Promise PushSubscription Creates push subscription
pushManager.getSubscription none Promise PushSubscription or null Retrieves active subscription
subscription.unsubscribe none Promise boolean Cancels push subscription
pushManager.permissionState options Promise string Checks permission state

Installation Best Practices

Recommendation Implementation Benefit
Defer installation prompt Store beforeinstallprompt event Improved conversion rates
Show custom UI Display contextual install button Better user experience
Detect installation Listen to appinstalled event Track installation success
Handle dismissal Clear deferred prompt reference Prevent repeated prompts
Test add-to-home-screen Verify manifest and HTTPS Ensure installability

Performance Metrics

Metric Target Measurement
First Contentful Paint Under 1.8s Time to first content render
Largest Contentful Paint Under 2.5s Time to main content render
Time to Interactive Under 3.8s Time until fully interactive
Total Blocking Time Under 200ms Sum of blocking task time
Cumulative Layout Shift Under 0.1 Visual stability score

Security Headers

Header Value Purpose
Content-Security-Policy worker-src 'self' Restrict service worker sources
Strict-Transport-Security max-age=31536000 Enforce HTTPS
X-Content-Type-Options nosniff Prevent MIME sniffing
X-Frame-Options DENY Prevent clickjacking
Referrer-Policy strict-origin-when-cross-origin Control referrer information