- Kiosk mode: fullscreen, wake lock, auto-login, context menu block, PWA/Capacitor detection - AI vision: camera photos analyzed by Gemma 3 27B vision model via OpenRouter - AI part classification: auto-suggest name/brand/category when entering part number - Public catalog chatbot: /api/chat endpoint with rate limiting, chat widget on catalog page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
169 lines
4.6 KiB
JavaScript
169 lines
4.6 KiB
JavaScript
// /home/Autopartes/pos/static/js/kiosk.js
|
|
// Kiosk mode for Nexus POS — fullscreen, wake lock, auto-login, no right-click
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
var STORAGE_KEY = 'pos_kiosk_mode';
|
|
|
|
// ─── Detection ───
|
|
function isPWA() {
|
|
return window.matchMedia('(display-mode: standalone)').matches
|
|
|| window.navigator.standalone === true;
|
|
}
|
|
|
|
function isCapacitor() {
|
|
return typeof window.Capacitor !== 'undefined' && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform();
|
|
}
|
|
|
|
function isKioskEnabled() {
|
|
// Enabled if explicitly set in localStorage, or if running as PWA/Capacitor
|
|
var pref = localStorage.getItem(STORAGE_KEY);
|
|
if (pref === 'true') return true;
|
|
if (pref === 'false') return false;
|
|
// Auto-detect
|
|
return isPWA() || isCapacitor();
|
|
}
|
|
|
|
// ─── Fullscreen ───
|
|
var fullscreenRequested = false;
|
|
|
|
function requestFullscreen() {
|
|
if (fullscreenRequested) return;
|
|
var el = document.documentElement;
|
|
var fn = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen;
|
|
if (fn) {
|
|
fn.call(el).catch(function () { /* user may have denied */ });
|
|
fullscreenRequested = true;
|
|
}
|
|
}
|
|
|
|
// ─── Wake Lock ───
|
|
var wakeLock = null;
|
|
|
|
async function acquireWakeLock() {
|
|
if (!('wakeLock' in navigator)) return;
|
|
try {
|
|
wakeLock = await navigator.wakeLock.request('screen');
|
|
wakeLock.addEventListener('release', function () {
|
|
wakeLock = null;
|
|
});
|
|
} catch (e) {
|
|
// Wake lock may fail if tab not visible
|
|
}
|
|
}
|
|
|
|
function releaseWakeLock() {
|
|
if (wakeLock) {
|
|
wakeLock.release();
|
|
wakeLock = null;
|
|
}
|
|
}
|
|
|
|
// Re-acquire wake lock when page becomes visible again
|
|
document.addEventListener('visibilitychange', function () {
|
|
if (document.visibilityState === 'visible' && isKioskEnabled() && !wakeLock) {
|
|
acquireWakeLock();
|
|
}
|
|
});
|
|
|
|
// ─── Auto-login ───
|
|
function tryAutoLogin() {
|
|
var token = localStorage.getItem('pos_token');
|
|
if (!token) return;
|
|
|
|
// Check if we are on the login page
|
|
var isLoginPage = window.location.pathname.indexOf('/pos/login') !== -1;
|
|
if (!isLoginPage) return;
|
|
|
|
// Validate token by trying to decode expiry (JWT is base64)
|
|
try {
|
|
var parts = token.split('.');
|
|
if (parts.length !== 3) return;
|
|
var payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
var exp = payload.exp;
|
|
if (exp && (exp * 1000) > Date.now()) {
|
|
// Token still valid — skip login
|
|
window.location.href = '/pos/';
|
|
}
|
|
} catch (e) {
|
|
// Invalid token, stay on login
|
|
}
|
|
}
|
|
|
|
// ─── Activate kiosk mode ───
|
|
function activate() {
|
|
// Prevent navigation away
|
|
window.addEventListener('beforeunload', function (e) {
|
|
if (isKioskEnabled()) {
|
|
e.preventDefault();
|
|
e.returnValue = '';
|
|
}
|
|
});
|
|
|
|
// Disable context menu
|
|
document.addEventListener('contextmenu', function (e) {
|
|
if (isKioskEnabled()) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
// Request fullscreen on first user interaction
|
|
var interactionEvents = ['click', 'touchstart', 'keydown'];
|
|
function onFirstInteraction() {
|
|
if (isKioskEnabled()) {
|
|
requestFullscreen();
|
|
acquireWakeLock();
|
|
}
|
|
interactionEvents.forEach(function (evt) {
|
|
document.removeEventListener(evt, onFirstInteraction);
|
|
});
|
|
}
|
|
interactionEvents.forEach(function (evt) {
|
|
document.addEventListener(evt, onFirstInteraction, { once: false });
|
|
});
|
|
|
|
// Auto-login if on login page
|
|
tryAutoLogin();
|
|
}
|
|
|
|
// ─── Public API ───
|
|
window.NexusKiosk = {
|
|
isEnabled: isKioskEnabled,
|
|
isPWA: isPWA,
|
|
isCapacitor: isCapacitor,
|
|
enable: function () {
|
|
localStorage.setItem(STORAGE_KEY, 'true');
|
|
requestFullscreen();
|
|
acquireWakeLock();
|
|
},
|
|
disable: function () {
|
|
localStorage.setItem(STORAGE_KEY, 'false');
|
|
releaseWakeLock();
|
|
if (document.fullscreenElement) {
|
|
document.exitFullscreen().catch(function () {});
|
|
}
|
|
},
|
|
toggle: function () {
|
|
if (isKioskEnabled()) {
|
|
window.NexusKiosk.disable();
|
|
} else {
|
|
window.NexusKiosk.enable();
|
|
}
|
|
return isKioskEnabled();
|
|
}
|
|
};
|
|
|
|
// ─── Init ───
|
|
if (isKioskEnabled()) {
|
|
activate();
|
|
}
|
|
|
|
// Also activate if preference changes (e.g. toggled from config)
|
|
window.addEventListener('storage', function (e) {
|
|
if (e.key === STORAGE_KEY && e.newValue === 'true') {
|
|
activate();
|
|
}
|
|
});
|
|
})();
|