cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/drop-hero-notarization-note
goq / portal / pointer-feedback.mjs
pointer-feedback.mjs 3.3K
1import { validatePointerPositionFeedback } from './input-state.mjs';23const TERMINAL_REASONS = new Set(['eof', 'malformed']);45export function newPointerSession() {6 return {7 received: false,8 latest: null,9 failed: false,10 failureDetail: null,11 closing: false,12 channel: null,13 surfaceDimensions: null,14 remotePosition: null,15 remoteVisible: false,16 };17}1819export function parsePointerFeedbackMessage(value, surface = null) {20 if (value?.type === 'position') {21 return {22 type: 'position',23 feedback: validatePointerPositionFeedback(value, surface),24 };25 }26 if (value?.type === 'terminal' && TERMINAL_REASONS.has(value.reason)) {27 return { type: 'terminal', reason: value.reason };28 }29 throw new TypeError('invalid pointer feedback message');30}3132export function createPointerFeedbackRuntime({33 getActiveSession,34 getCapabilities,35 getSurface,36 render,37 disconnect,38 logger = console,39} = {}) {40 if (typeof getActiveSession !== 'function') {41 throw new TypeError('getActiveSession must be a function');42 }43 if (typeof getCapabilities !== 'function') {44 throw new TypeError('getCapabilities must be a function');45 }46 if (typeof getSurface !== 'function') throw new TypeError('getSurface must be a function');47 if (typeof render !== 'function') throw new TypeError('render must be a function');48 if (typeof disconnect !== 'function') throw new TypeError('disconnect must be a function');49 if (typeof logger?.error !== 'function') throw new TypeError('logger must provide error');5051 function capabilities() {52 return getCapabilities() ?? {};53 }5455 function disconnectIfConnected(snapshot) {56 if (snapshot.connected === true) void disconnect();57 }5859 function applyPosition(message, session) {60 const snapshot = capabilities();61 if (session !== getActiveSession() || snapshot.pointerPositionFeedback !== true) return;62 const surface = getSurface();63 if (surface === null) return;64 const feedback = validatePointerPositionFeedback(message, surface);65 session.remotePosition = feedback.position;66 session.remoteVisible = feedback.pointer_visible;67 render();68 }6970 function handleMessage(message, session) {71 if (session !== getActiveSession() || session.failed || session.closing) return;72 try {73 const envelope = parsePointerFeedbackMessage(message);74 if (envelope.type === 'terminal') {75 session.failed = true;76 session.failureDetail = envelope.reason === 'eof'77 ? 'Pointer feedback ended'78 : 'Pointer feedback was malformed';79 session.remotePosition = null;80 session.remoteVisible = false;81 render();82 logger.error(session.failureDetail);83 disconnectIfConnected(capabilities());84 return;85 }86 const feedback = envelope.feedback;87 session.received = true;88 session.latest = feedback;89 const snapshot = capabilities();90 if (snapshot.connected === true) applyPosition(feedback, session);91 } catch (error) {92 session.failed = true;93 session.failureDetail = `Pointer feedback failed: ${error}`;94 session.remotePosition = null;95 session.remoteVisible = false;96 render();97 logger.error('invalid pointer-position feedback:', error);98 disconnectIfConnected(capabilities());99 }100 }101102 return Object.freeze({103 applyPosition,104 handleMessage,105 });106}