- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
96 lines
3.2 KiB
JavaScript
96 lines
3.2 KiB
JavaScript
/**
|
|
* push.js — Web Push notification setup for Nexus POS
|
|
*
|
|
* Registers a service worker and subscribes to push notifications.
|
|
* Only activates for owner/admin roles.
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Only set up push for owner/admin
|
|
var employee = {};
|
|
try { employee = JSON.parse(localStorage.getItem('pos_employee') || '{}'); } catch(e) {}
|
|
var role = employee.role || '';
|
|
if (role !== 'owner' && role !== 'admin') return;
|
|
|
|
// Check browser support
|
|
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
|
console.log('[Push] Browser does not support push notifications');
|
|
return;
|
|
}
|
|
|
|
var token = localStorage.getItem('pos_token');
|
|
if (!token) return;
|
|
|
|
function urlBase64ToUint8Array(base64String) {
|
|
var padding = '='.repeat((4 - base64String.length % 4) % 4);
|
|
var base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
var rawData = window.atob(base64);
|
|
var outputArray = new Uint8Array(rawData.length);
|
|
for (var i = 0; i < rawData.length; ++i) {
|
|
outputArray[i] = rawData.charCodeAt(i);
|
|
}
|
|
return outputArray;
|
|
}
|
|
|
|
async function setupPush() {
|
|
try {
|
|
// Register service worker
|
|
var registration = await navigator.serviceWorker.register('/pos/sw-push.js', {
|
|
scope: '/pos/'
|
|
});
|
|
console.log('[Push] Service worker registered');
|
|
|
|
// Get VAPID key from server
|
|
var resp = await fetch('/pos/api/push/vapid-key', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
});
|
|
if (!resp.ok) {
|
|
console.log('[Push] VAPID key not available:', resp.status);
|
|
return;
|
|
}
|
|
var data = await resp.json();
|
|
var vapidKey = data.public_key;
|
|
if (!vapidKey) return;
|
|
|
|
// Request permission
|
|
var permission = await Notification.requestPermission();
|
|
if (permission !== 'granted') {
|
|
console.log('[Push] Permission denied');
|
|
return;
|
|
}
|
|
|
|
// Subscribe
|
|
var subscription = await registration.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: urlBase64ToUint8Array(vapidKey)
|
|
});
|
|
|
|
// Send subscription to server
|
|
var subResp = await fetch('/pos/api/push/subscribe', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token
|
|
},
|
|
body: JSON.stringify({ subscription: subscription.toJSON() })
|
|
});
|
|
|
|
if (subResp.ok) {
|
|
console.log('[Push] Subscribed successfully');
|
|
}
|
|
} catch(err) {
|
|
console.log('[Push] Setup error:', err);
|
|
}
|
|
}
|
|
|
|
// Delay push setup to not block page load
|
|
if (document.readyState === 'complete') {
|
|
setTimeout(setupPush, 2000);
|
|
} else {
|
|
window.addEventListener('load', function() {
|
|
setTimeout(setupPush, 2000);
|
|
});
|
|
}
|
|
})();
|