feat(shopfloor): tablet_session_manager OWL service

Tracks idle + ceiling timers for an unlocked tech session. Fires
/fp/tablet/lock_session when either trips, then reloads the page so
the browser re-bootstraps under the fresh kiosk session.

Defaults: 10min idle, 8hr ceiling, 5s tick interval. Listens for
click/touchstart/keydown/mousemove as activity signals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 13:15:28 -04:00
parent 10477a7c8f
commit b41d9629e1
2 changed files with 100 additions and 0 deletions

View File

@@ -89,6 +89,11 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
# ---- Phase 6.2 tablet PIN gate ---- # ---- Phase 6.2 tablet PIN gate ----
'fusion_plating_shopfloor/static/src/js/services/tech_store.js', 'fusion_plating_shopfloor/static/src/js/services/tech_store.js',
'fusion_plating_shopfloor/static/src/js/services/activity_tracker.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 # Phase 6.3 — fpRpc wrapper. MUST load before any consumer
# (job_workspace, shopfloor_landing, manager_dashboard, # (job_workspace, shopfloor_landing, manager_dashboard,
# hold_composer) so `import { fpRpc }` resolves. # hold_composer) so `import { fpRpc }` resolves.

View File

@@ -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);