Elysia Tools

Explora herramientas, ejemplos, explicaciones visuales y guías temáticas en un solo espacio claro.

Categorías populares

Temas

Idioma
ZHESPTRUFRDEEN

Elysia Tools

Navegación

Tecnologías web

Service Workers para Apps Web Offline

Service Workers para funcionalidad offline, sincronización en fondo e interceptación de red

Ejemplos

Entradas de esta colección

CódigojavascriptSimple

Configuración Básica de Service Worker

Registrar un service worker y habilitar caché offline

Dificultad

3/10

Tiempo estimado

15 min

Etiquetas

service worker, offline, cache, pwa

Requisitos previos

JavaScript, Basic web development

javascript
// Basic Service Worker Setup
// service-worker.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/icon.png'
];

// Install event - cache files
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

// Fetch event - serve from cache when offline
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // Cache hit - return response
        if (response) {
          return response;
        }

        // Clone the request
        const fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(response => {
          // Check if valid response
          if(!response || response.status !== 200 || response.type !== 'basic') {
            return response;
          }

          // Clone the response
          const responseToCache = response.clone();

          caches.open(CACHE_NAME)
            .then(cache => {
              cache.put(event.request, responseToCache);
            });

          return response;
        });
      })
  );
});

// Activate event - clean up old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            console.log('Deleting old cache:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

// main.js - Register service worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then(registration => {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);

        // Check for updates
        registration.addEventListener('updatefound', () => {
          const newWorker = registration.installing;

          newWorker.addEventListener('statechange', () => {
            if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
              // New content is available, show update notification
              if (confirm('New version available! Reload to update?')) {
                window.location.reload();
              }
            }
          });
        });

      })
      .catch(err => {
        console.log('ServiceWorker registration failed: ', err);
      });
  });

  // Listen for controlling service worker
  navigator.serviceWorker.addEventListener('controllerchange', () => {
    console.log('ServiceWorker controller changed');
    window.location.reload();
  });
}
CódigojavascriptIntermedio

Sincronización en Fondo

Sincronizar datos cuando se restaura la conexión de red

Dificultad

6/10

Tiempo estimado

25 min

Etiquetas

background sync, offline, pwa, indexeddb

Requisitos previos

JavaScript, Service Workers, IndexedDB

javascript
// Background Sync Service Worker
// service-worker.js
self.addEventListener('sync', event => {
  if (event.tag === 'background-sync') {
    event.waitUntil(syncData());
  }
});

async function syncData() {
  try {
    // Get all pending sync requests from IndexedDB
    const pendingRequests = await getPendingRequests();

    for (const request of pendingRequests) {
      try {
        const response = await fetch(request.url, {
          method: request.method,
          headers: request.headers,
          body: request.body
        });

        if (response.ok) {
          // Remove successful request from pending
          await removePendingRequest(request.id);
          console.log('Sync successful for request:', request.id);
        } else {
          console.error('Sync failed for request:', request.id);
        }
      } catch (error) {
        console.error('Network error during sync:', error);
        throw error; // This will retry the sync
      }
    }

    // Show notification if sync completed
    if (await getPendingRequestsCount() === 0) {
      self.registration.showNotification('Sync Complete', {
        body: 'All your data has been synchronized',
        icon: '/sync-icon.png'
      });
    }
  } catch (error) {
    console.error('Background sync failed:', error);
  }
}

// IndexedDB helpers for pending requests
async function getPendingRequests() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sync-db', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => {
      const db = request.result;
      const transaction = db.transaction(['requests'], 'readonly');
      const store = transaction.objectStore('requests');
      const getAll = store.getAll();

      getAll.onsuccess = () => resolve(getAll.result);
      getAll.onerror = () => reject(getAll.error);
    };

    request.onupgradeneeded = () => {
      const db = request.result;
      db.createObjectStore('requests', { keyPath: 'id' });
    };
  });
}

async function removePendingRequest(id) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('sync-db', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => {
      const db = request.result;
      const transaction = db.transaction(['requests'], 'readwrite');
      const store = transaction.objectStore('requests');
      const deleteRequest = store.delete(id);

      deleteRequest.onsuccess = () => resolve();
      deleteRequest.onerror = () => reject(deleteRequest.error);
    };
  });
}

// main.js - Register background sync
class BackgroundSyncManager {
  constructor() {
    this.registration = null;
  }

  async init() {
    this.registration = await navigator.serviceWorker.ready;
  }

  async registerSync() {
    if ('serviceWorker' in navigator &&
        typeof ServiceWorkerRegistration !== 'undefined' &&
        'sync' in ServiceWorkerRegistration.prototype) {
      try {
        await this.registration.sync.register('background-sync');
        console.log('Background sync registered');
        return true;
      } catch (error) {
        console.error('Background sync registration failed:', error);
        return false;
      }
    } else {
      console.warn('Background sync not supported');
      return false;
    }
  }

  async addPendingRequest(requestData) {
    const request = {
      id: Date.now().toString(),
      ...requestData,
      timestamp: Date.now()
    };

    // Save to IndexedDB
    const db = await this.openDB();
    const transaction = db.transaction(['requests'], 'readwrite');
    const store = transaction.objectStore('requests');
    await store.add(request);

    // Try to register background sync
    const syncRegistered = await this.registerSync();

    if (!syncRegistered) {
      // Fallback: try immediate sync
      this.tryImmediateSync(request);
    }

    return request.id;
  }

  async openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('sync-db', 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = () => {
        const db = request.result;
        db.createObjectStore('requests', { keyPath: 'id' });
      };
    });
  }

  async tryImmediateSync(request) {
    try {
      const response = await fetch(request.url, {
        method: request.method,
        headers: request.headers,
        body: request.body
      });

      if (response.ok) {
        await this.removePendingRequest(request.id);
        console.log('Immediate sync successful');
      } else {
        console.log('Immediate sync failed, keeping in pending queue');
      }
    } catch (error) {
      console.log('Network unavailable, keeping in pending queue');
    }
  }
}

// Usage example
const syncManager = new BackgroundSyncManager();

// Initialize when app loads
window.addEventListener('load', async () => {
  await syncManager.init();
});

// Example: Save data with background sync
async function saveDataOffline(data) {
  const requestData = {
    url: '/api/save',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  };

  const requestId = await syncManager.addPendingRequest(requestData);

  // Show user that data will be synced
  alert('Your data will be synced when you're back online');
}
CódigojavascriptIntermedio

Notificaciones Push

Recibir y manejar notificaciones push con service workers

Dificultad

7/10

Tiempo estimado

30 min

Etiquetas

push notifications, pwa, service worker, messaging

Requisitos previos

JavaScript, Service Workers, HTTPS setup

javascript
// Push Notifications with Service Worker
// service-worker.js
self.addEventListener('push', event => {
  const options = {
    body: event.data ? event.data.text() : 'New notification',
    icon: '/notification-icon.png',
    badge: '/notification-badge.png',
    vibrate: [100, 50, 100],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: 1
    },
    actions: [
      {
        action: 'explore',
        title: 'Explore this new world',
        icon: '/images/checkmark.png'
      },
      {
        action: 'close',
        title: 'Close notification',
        icon: '/images/xmark.png'
      }
    ]
  };

  event.waitUntil(
    self.registration.showNotification('Push Notification', options)
  );
});

// Handle notification clicks
self.addEventListener('notificationclick', event => {
  event.notification.close();

  if (event.action === 'explore') {
    // Open the app to specific page
    event.waitUntil(
      clients.openWindow('/explore')
    );
  } else if (event.action === 'close') {
    // Just close the notification
    console.log('Notification closed');
  } else {
    // Default action - open main app
    event.waitUntil(
      clients.matchAll().then(clientList => {
        for (const client of clientList) {
          if (client.url === '/' && 'focus' in client) {
            return client.focus();
          }
        }
        if (clients.openWindow) {
          return clients.openWindow('/');
        }
      })
    );
  }
});

// Handle notification close
self.addEventListener('notificationclose', event => {
  console.log('Notification closed:', event.notification);
});

// main.js - Push notification subscription
class PushNotificationManager {
  constructor() {
    this.subscription = null;
    this.publicKey = 'YOUR_VAPID_PUBLIC_KEY';
  }

  async init() {
    // Check for service worker support
    if (!('serviceWorker' in navigator)) {
      console.error('Service Worker not supported');
      return false;
    }

    // Check for push notification support
    if (!('PushManager' in window)) {
      console.error('Push notifications not supported');
      return false;
    }

    // Register service worker
    const registration = await navigator.serviceWorker.register('/push-service-worker.js');
    console.log('Service Worker registered');

    this.registration = registration;
    return true;
  }

  async subscribeToPush() {
    try {
      // Subscribe to push notifications
      this.subscription = await this.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: this.urlBase64ToUint8Array(this.publicKey)
      });

      console.log('User is subscribed:', this.subscription);

      // Send subscription to server
      await this.sendSubscriptionToServer(this.subscription);

      return this.subscription;
    } catch (error) {
      console.error('Failed to subscribe to push notifications:', error);
      return null;
    }
  }

  async unsubscribeFromPush() {
    try {
      const result = await this.subscription.unsubscribe();
      console.log('Unsubscribed:', result);

      // Remove subscription from server
      await this.removeSubscriptionFromServer(this.subscription);

      this.subscription = null;
      return result;
    } catch (error) {
      console.error('Failed to unsubscribe:', error);
      return false;
    }
  }

  async sendSubscriptionToServer(subscription) {
    try {
      const response = await fetch('/api/subscribe', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(subscription)
      });

      if (!response.ok) {
        throw new Error('Failed to send subscription to server');
      }

      return await response.json();
    } catch (error) {
      console.error('Error sending subscription to server:', error);
      throw error;
    }
  }

  async removeSubscriptionFromServer(subscription) {
    try {
      const response = await fetch('/api/unsubscribe', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(subscription)
      });

      if (!response.ok) {
        throw new Error('Failed to remove subscription from server');
      }
    } catch (error) {
      console.error('Error removing subscription from server:', error);
      throw error;
    }
  }

  async requestNotificationPermission() {
    const permission = await Notification.requestPermission();

    if (permission === 'granted') {
      console.log('Notification permission granted');
      return true;
    } else {
      console.log('Notification permission denied');
      return false;
    }
  }

  // Helper function to convert VAPID key
  urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
      .replace(/-/g, '+')
      .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
      outputArray[i] = rawData.charCodeAt(i);
    }

    return outputArray;
  }

  async checkSubscriptionStatus() {
    this.subscription = await this.registration.pushManager.getSubscription();

    if (this.subscription) {
      console.log('User is already subscribed');
      return this.subscription;
    } else {
      console.log('User is not subscribed');
      return null;
    }
  }
}

// Usage example
const pushManager = new PushNotificationManager();

// Initialize push notifications
async function initPushNotifications() {
  const initialized = await pushManager.init();
  if (!initialized) return;

  // Request permission
  const permissionGranted = await pushManager.requestNotificationPermission();
  if (!permissionGranted) return;

  // Check if already subscribed
  const existingSubscription = await pushManager.checkSubscriptionStatus();

  if (!existingSubscription) {
    // Subscribe to push notifications
    await pushManager.subscribeToPush();
  }
}

// Subscribe button handler
document.getElementById('subscribe-btn').addEventListener('click', async () => {
  await initPushNotifications();
});

// Unsubscribe button handler
document.getElementById('unsubscribe-btn').addEventListener('click', async () => {
  await pushManager.unsubscribeFromPush();
});

// Test local notification
document.getElementById('test-notification').addEventListener('click', async () => {
  if (Notification.permission === 'granted') {
    new Notification('Test Notification', {
      body: 'This is a test notification!',
      icon: '/icon.png'
    });
  } else {
    console.log('Notification permission not granted');
  }
});
CódigojavascriptIntermedio

Estrategias de Caché Avanzadas

Implementar diferentes estrategias de caché para rendimiento óptimo

Dificultad

8/10

Tiempo estimado

35 min

Etiquetas

caching, performance, offline, pwa, strategies

Requisitos previos

JavaScript, Service Workers, Performance optimization

javascript
// Advanced Service Worker Caching Strategies
// service-worker.js

// Different cache strategies
class CacheStrategies {
  // Cache First - try cache first, fallback to network
  static cacheFirst(request) {
    return caches.match(request)
      .then(response => {
        return response || fetch(request);
      });
  }

  // Network First - try network first, fallback to cache
  static networkFirst(request) {
    return fetch(request)
      .then(response => {
        // Cache successful responses
        if (response.status === 200) {
          const responseClone = response.clone();
          caches.open('dynamic-cache-v1')
            .then(cache => cache.put(request, responseClone));
        }
        return response;
      })
      .catch(() => {
        // Fallback to cache
        return caches.match(request);
      });
  }

  // Stale While Revalidate - serve from cache, update in background
  static staleWhileRevalidate(request) {
    const cachePromise = caches.match(request);
    const networkPromise = fetch(request)
      .then(response => {
        const responseClone = response.clone();
        caches.open('dynamic-cache-v1')
          .then(cache => cache.put(request, responseClone));
        return response;
      });

    return cachePromise
      .then(response => response || networkPromise)
      .catch(() => networkPromise);
  }

  // Network Only - always fetch from network
  static networkOnly(request) {
    return fetch(request);
  }

  // Cache Only - always serve from cache
  static cacheOnly(request) {
    return caches.match(request);
  }
}

// Cache management
class CacheManager {
  constructor() {
    this.caches = {
      static: 'static-cache-v1',
      dynamic: 'dynamic-cache-v1',
      api: 'api-cache-v1',
      images: 'images-cache-v1'
    };

    this.cacheRules = [
      // Static assets - cache first
      {
        match: request =>
          request.destination === 'script' ||
          request.destination === 'style' ||
          request.destination === 'font',
        strategy: 'cacheFirst',
        cacheName: this.caches.static
      },

      // Images - stale while revalidate
      {
        match: request => request.destination === 'image',
        strategy: 'staleWhileRevalidate',
        cacheName: this.caches.images,
        maxEntries: 100,
        maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
      },

      // API calls - network first with timeout
      {
        match: request => request.url.includes('/api/'),
        strategy: 'networkFirst',
        cacheName: this.caches.api,
        timeout: 3000, // 3 seconds
        maxEntries: 50
      },

      // HTML pages - network first
      {
        match: request =>
          request.destination === 'document' ||
          request.mode === 'navigate',
        strategy: 'networkFirst',
        cacheName: this.caches.dynamic
      },

      // External CDN - cache first
      {
        match: request =>
          request.url.includes('cdn.jsdelivr.net') ||
          request.url.includes('cdnjs.cloudflare.com'),
        strategy: 'cacheFirst',
        cacheName: this.caches.static
      }
    ];
  }

  async handleRequest(request) {
    const rule = this.findMatchingRule(request);

    if (!rule) {
      return fetch(request);
    }

    try {
      // Apply strategy with timeout if specified
      if (rule.timeout) {
        return await this.withTimeout(
          CacheStrategies[rule.strategy](request),
          rule.timeout,
          caches.match(request)
        );
      } else {
        const response = await CacheStrategies[rule.strategy](request);

        // Apply cache management rules
        if (response && response.ok && rule.cacheName) {
          await this.manageCache(rule.cacheName, request, rule);
        }

        return response;
      }
    } catch (error) {
      console.error('Cache strategy failed:', error);

      // Fallback to cache if available
      return caches.match(request) || new Response('Offline', {
        status: 503,
        statusText: 'Service Unavailable'
      });
    }
  }

  findMatchingRule(request) {
    return this.cacheRules.find(rule => rule.match(request));
  }

  async withTimeout(promise, timeout, fallback) {
    return Promise.race([
      promise,
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Timeout')), timeout)
      )
    ]).catch(error => {
      if (fallback) return fallback;
      throw error;
    });
  }

  async manageCache(cacheName, request, rule) {
    if (rule.maxEntries || rule.maxAge) {
      const cache = await caches.open(cacheName);
      const requests = await cache.keys();

      // Remove old entries if maxEntries exceeded
      if (rule.maxEntries && requests.length > rule.maxEntries) {
        const toDelete = requests.slice(0, requests.length - rule.maxEntries);
        await Promise.all(toDelete.map(req => cache.delete(req)));
      }

      // Remove old entries based on age
      if (rule.maxAge) {
        const now = Date.now();
        for (const cacheRequest of requests) {
          const response = await cache.match(cacheRequest);
          const dateHeader = response.headers.get('date');
          if (dateHeader && (now - new Date(dateHeader).getTime()) > rule.maxAge) {
            cache.delete(cacheRequest);
          }
        }
      }
    }
  }
}

// Initialize cache manager
const cacheManager = new CacheManager();

// Service worker event handlers
self.addEventListener('fetch', event => {
  event.respondWith(cacheManager.handleRequest(event.request));
});

// Install event - precache critical assets
self.addEventListener('install', event => {
  const staticAssets = [
    '/',
    '/index.html',
    '/offline.html',
    '/styles/main.css',
    '/scripts/app.js',
    '/images/logo.png'
  ];

  event.waitUntil(
    caches.open(cacheManager.caches.static)
      .then(cache => cache.addAll(staticAssets))
  );
});

// Activate event - clean up old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(name => !Object.values(cacheManager.caches).includes(name))
          .map(name => caches.delete(name))
      );
    })
  );
});

// Background sync for offline analytics
self.addEventListener('sync', event => {
  if (event.tag === 'analytics-sync') {
    event.waitUntil(syncAnalytics());
  }
});

async function syncAnalytics() {
  const offlineData = await getOfflineAnalytics();

  for (const data of offlineData) {
    try {
      await fetch('/api/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });

      await removeOfflineAnalytics(data.id);
    } catch (error) {
      console.error('Failed to sync analytics:', error);
      break;
    }
  }
}

// IndexedDB helpers for offline analytics
async function getOfflineAnalytics() {
  // Implementation for getting analytics from IndexedDB
  return [];
}

async function removeOfflineAnalytics(id) {
  // Implementation for removing analytics from IndexedDB
}

// main.js - Client-side analytics that works offline
class OfflineAnalytics {
  constructor() {
    this.db = null;
  }

  async init() {
    this.db = await this.openDB();
  }

  async openDB() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('analytics-db', 1);

      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);

      request.onupgradeneeded = () => {
        const db = request.result;
        db.createObjectStore('events', { keyPath: 'id', autoIncrement: true });
      };
    });
  }

  async track(eventName, properties = {}) {
    const event = {
      name: eventName,
      properties: {
        ...properties,
        timestamp: Date.now(),
        url: window.location.href,
        userAgent: navigator.userAgent
      }
    };

    try {
      // Try to send immediately
      const response = await fetch('/api/analytics', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(event)
      });

      if (!response.ok) throw new Error('Analytics request failed');

    } catch (error) {
      // Store offline
      await this.storeOffline(event);

      // Register background sync if available
      if ('serviceWorker' in navigator &&
          typeof ServiceWorkerRegistration !== 'undefined' &&
          'sync' in ServiceWorkerRegistration.prototype) {
        const registration = await navigator.serviceWorker.ready;
        registration.sync.register('analytics-sync');
      }
    }
  }

  async storeOffline(event) {
    const transaction = this.db.transaction(['events'], 'readwrite');
    const store = transaction.objectStore('events');
    await store.add(event);
  }
}

// Usage
const analytics = new OfflineAnalytics();
analytics.init();

// Track page view
analytics.track('page_view', {
  page: window.location.pathname,
  title: document.title
});

// Track user interaction
document.getElementById('cta-button').addEventListener('click', () => {
  analytics.track('cta_clicked', {
    button_text: 'Get Started',
    location: 'homepage'
  });
});

Herramientas

Herramientas que suelen usarse con este ejemplo

Relacionado

Colecciones de ejemplos relacionadas

Guía de uso de la herramienta

Contexto para entender esta colección de ejemplos

Título

Service Workers para Apps Web Offline

Descripción

Service Workers para funcionalidad offline, sincronización en fondo e interceptación de red

Resumen

Service Workers para funcionalidad offline, sincronización en fondo e interceptación de red

Cuándo usarlo

  • Use this sample set when you want reference inputs for Service Workers para Apps Web Offline.
  • Use it to preview output formats and compare related sample variants.

Cómo funciona

  1. 1Open a sample entry to inspect the prepared example payload or asset.
  2. 2Compare it with related tools to understand how the workflow connects.

Casos de uso

  • Configuración Básica de Service Worker
  • Sincronización en Fondo
  • Notificaciones Push
  • Estrategias de Caché Avanzadas

Preguntas frecuentes

What is included in Service Workers para Apps Web Offline?

This page includes curated sample entries, related tools, and neighboring sample collections for Service Workers para Apps Web Offline.

Inicio
Herramientas3197
Ejemplos593
Visualizaciones461
Visores de archivos103
Temas229
Etiquetas333
Imágenes, audio y video655

Herramientas de procesamiento, conversión y optimización de imágenes, audio y video

Matemáticas, fechas y finanzas644

Calculadoras, herramientas numéricas, lógica de fechas, estadística y finanzas

Diseño y color296

Herramientas de color, maquetación, gráficos, estilo visual y apoyo al diseño

Texto y redacción209

Herramientas de redacción, limpieza, formato, extracción y análisis de texto

Desarrollo y Web193

Utilidades para desarrolladores, red, depuración web y automatización

Conversión y codificación175

Herramientas de conversión de formatos, archivos, unidades, archivos comprimidos y codificación

Estructurar una captura de interfaz, revisarla y entregarla a desarrollo
Boceto de piano, revisión MIDI y entrega de una maqueta de escucha
Revisión de texturas retro y entrega de recursos
Revisión y entrega de guías de procedimiento con capturas
Revisión visual de un rediseño adaptable y entrega de evidencias

© 2026 Elysia Tools. Todos los derechos reservados.

DocumentaciónPreciosAcerca deTérminos de servicioPolítica de privacidadCookiesContacto

Media

Redimensionamiento de Capturas de Chrome Web Store

Redimensionar imágenes a las dimensiones de capturas de Chrome Web Store mientras se conserva el contenido completo y la proporción original

Security

Escaner ReDoS de regex

Escanea patrones regex por riesgo de backtracking catastrofico, simula entradas maliciosas y sugiere alternativas seguras

Security

Generador de códigos TOTP / HOTP sin conexión

Genera contraseñas de un solo uso TOTP RFC 6238 (6/8 dígitos, 30/60 s) y HOTP RFC 4226 (por contador) a partir de un secreto base32, sin conexión, con HMAC-SHA1/256/512, y exporta una URI otpauth://

Format Conversion

Codificador de Web Push: petición RFC 8030 + VAPID + cifrado RFC 8291

Codifica un envío completo de Web Push: cifrado aes128gcm del RFC 8291, JWT ES256 VAPID del RFC 8292, cabeceras RFC 8030, correspondencia con APNs/FCM y verificación por descifrado de ida y vuelta.

Web & Internet

Búsqueda web (JSON de SearXNG)

Busca en la web mediante la instancia SearXNG de s.stdlang.com y devuelve JSON para automatización o herramientas posteriores.

Design

Laboratorio de Emparejamiento de Fuentes Web

Previsualiza un emparejamiento de Google Fonts de titular + cuerpo con controles de peso e interlínea, y copia un snippet CSS @import + variables listo para pegar. Registro curado de 22 fuentes y 7 presets.

Tecnologías web

Aplicaciones Web Progresivas (PWA)

Construye aplicaciones web progresivas modernas, confiables y atractivas con capacidades offline

Tecnologías web

Muestras de Comunicación en Tiempo Real WebRTC

Muestras completas de WebRTC para comunicación de audio/video P2P, canales de datos, compartir pantalla y servidor de señalización