// Mobile-connector "Pair a device" console (page_id `pairing`). // // Opens a pairing window on the plugin (`POST /pairing`), shows the QR the phone // scans, and counts down to expiry. A device that pairs in this window is // auto-bound to the admin who opened it (server-side, on `ClientPaired`) — so it // is usable on the phone immediately and can be reassigned later from the // Devices page. Default-exports the element class; the host registers it. import { html, nothing } from 'lit'; import { MobileBase, jf, t } from './common.js'; const P = 'plugin.mobile-connector'; export default class MobilePairingPage extends MobileBase { static get properties() { return { _session: { state: true }, // { url, code, expires_at } | null _remain: { state: true }, // seconds until expiry _busy: { state: true }, _error: { state: true }, }; } constructor() { super(); this._session = null; this._remain = 0; this._busy = false; this._error = null; this._timer = null; } disconnectedCallback() { super.disconnectedCallback(); this._stopTimer(); // Best-effort close so a forgotten window does not linger. if (this._session) jf(`${this.api}/pairing`, { method: 'DELETE' }).catch(() => {}); } _stopTimer() { if (this._timer) { clearInterval(this._timer); this._timer = null; } } _startTimer() { this._stopTimer(); const tick = () => { const remain = Math.max(0, Math.round((this._session.expires_at - Date.now()) / 1000)); this._remain = remain; if (remain <= 0) { this._stopTimer(); } }; tick(); this._timer = setInterval(tick, 1000); } async _open() { this._busy = true; this._error = null; try { this._session = await jf(`${this.api}/pairing`, { method: 'POST', body: JSON.stringify({}) }); this._startTimer(); } catch (e) { this._error = e.message; this._session = null; } finally { this._busy = false; } } async _stop() { this._stopTimer(); const had = this._session; this._session = null; if (had) { try { await jf(`${this.api}/pairing`, { method: 'DELETE' }); } catch { /* ignore */ } } } render() { const expired = this._session && this._remain <= 0; return html`

${t(`${P}.pairing.title`)}

${this._error ? html`
${this._error}
` : nothing} ${!this._session ? html`

${t(`${P}.pairing.intro`)}

` : html`
${t(`${P}.pairing.qr_alt`)} ${expired ? html`
${t(`${P}.pairing.expired`)}
` : html`
${t(`${P}.pairing.scan_within`, { n: this._remain })}
`}
${expired ? html`` : html``}
`}
`; } }