diff --git a/fusion_plating/fusion_plating_shopfloor/__manifest__.py b/fusion_plating/fusion_plating_shopfloor/__manifest__.py index d97fee30..56200b65 100644 --- a/fusion_plating/fusion_plating_shopfloor/__manifest__.py +++ b/fusion_plating/fusion_plating_shopfloor/__manifest__.py @@ -89,6 +89,11 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved. # ---- Phase 6.2 tablet PIN gate ---- 'fusion_plating_shopfloor/static/src/js/services/tech_store.js', 'fusion_plating_shopfloor/static/src/js/services/activity_tracker.js', + # Phase D — tablet session manager (idle + ceiling timer). + # Used by tablet_lock when fp.shopfloor.tablet_session_mode + # ='session_swap' to call /fp/tablet/lock_session and reload + # the page so the browser re-bootstraps under the kiosk. + 'fusion_plating_shopfloor/static/src/js/services/tablet_session_manager.js', # Phase 6.3 — fpRpc wrapper. MUST load before any consumer # (job_workspace, shopfloor_landing, manager_dashboard, # hold_composer) so `import { fpRpc }` resolves. diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/services/tablet_session_manager.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/services/tablet_session_manager.js new file mode 100644 index 00000000..a1aef82e --- /dev/null +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/services/tablet_session_manager.js @@ -0,0 +1,95 @@ +/** @odoo-module **/ +// ============================================================================= +// Tablet Session Manager (Phase D of tablet PIN session redesign) +// +// OWL service that tracks idle time + hard ceiling for an unlocked tech +// session and fires /fp/tablet/lock_session when either threshold trips. +// +// Activity events (click / touchstart / keydown / mousemove) reset the idle +// timer. setInterval polls every 5 seconds. +// +// Spec: docs/superpowers/specs/2026-05-24-tablet-pin-session-redesign-design.md +// ============================================================================= +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; + +const DEFAULT_IDLE_MS = 10 * 60 * 1000; // 10 minutes +const DEFAULT_CEILING_MS = 8 * 60 * 60 * 1000; // 8 hours +const TICK_MS = 5000; // check every 5 seconds + +export const tabletSessionManager = { + dependencies: [], + + start(env) { + const service = { + idleMs: DEFAULT_IDLE_MS, + ceilingMs: DEFAULT_CEILING_MS, + lastActivity: Date.now(), + sessionStartedAt: null, + _tickHandle: null, + _running: false, + _touchHandler: null, + + beginSession(sessionStartedAtMs) { + this.sessionStartedAt = sessionStartedAtMs || Date.now(); + this.lastActivity = Date.now(); + this._installListeners(); + this._tickHandle = setInterval(() => this._tick(), TICK_MS); + this._running = true; + }, + + endSession() { + if (this._tickHandle) clearInterval(this._tickHandle); + this._tickHandle = null; + this._removeListeners(); + this._running = false; + this.sessionStartedAt = null; + }, + + _installListeners() { + this._touchHandler = () => { this.lastActivity = Date.now(); }; + ["click", "touchstart", "keydown", "mousemove"].forEach(ev => + document.addEventListener(ev, this._touchHandler, { passive: true }) + ); + }, + + _removeListeners() { + if (!this._touchHandler) return; + ["click", "touchstart", "keydown", "mousemove"].forEach(ev => + document.removeEventListener(ev, this._touchHandler) + ); + this._touchHandler = null; + }, + + async _tick() { + if (!this._running) return; + const now = Date.now(); + const idleFor = now - this.lastActivity; + const sessionAgeMs = this.sessionStartedAt ? now - this.sessionStartedAt : 0; + let reason = null; + if (sessionAgeMs > this.ceilingMs) { + reason = "ceiling"; + } else if (idleFor > this.idleMs) { + reason = "idle"; + } + if (!reason) return; + this.endSession(); // stop ticking before the RPC + await this.lockBack(reason); + }, + + async lockBack(reason) { + try { + await rpc("/fp/tablet/lock_session", { reason }); + } catch (e) { + // Even if the RPC fails, force a reload to drop the + // current session state — the cron will clean up. + console.warn("lock_session RPC failed; reloading anyway", e); + } + window.location.reload(); + }, + }; + return service; + }, +}; + +registry.category("services").add("fp_tablet_session_manager", tabletSessionManager);