@jspsych/extension-tobii 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  var jsPsychExtensionTobii = (function (jspsych) {
2
2
  'use strict';
3
3
 
4
- var version = "0.1.1";
4
+ var version = "0.2.1";
5
5
 
6
6
  class WebSocketClient {
7
7
  constructor(config = {}) {
@@ -654,9 +654,7 @@ var jsPsychExtensionTobii = (function (jspsych) {
654
654
  };
655
655
  this.on_start = async (_params = {}) => {
656
656
  this.dataManager.startTrial();
657
- if (!this.tracking) {
658
- await this.startTracking();
659
- }
657
+ await this.startTracking();
660
658
  };
661
659
  this.on_load = async () => {
662
660
  };
@@ -719,6 +717,9 @@ var jsPsychExtensionTobii = (function (jspsych) {
719
717
  * Start eye tracking data collection
720
718
  */
721
719
  async startTracking() {
720
+ if (this.tracking) {
721
+ return;
722
+ }
722
723
  if (!this.isConnected()) {
723
724
  throw new Error("Not connected to server. Call connect() first.");
724
725
  }
@@ -1002,4 +1003,4 @@ var jsPsychExtensionTobii = (function (jspsych) {
1002
1003
  return TobiiExtension;
1003
1004
 
1004
1005
  })(jsPsychModule);
1005
- //# sourceMappingURL=https://unpkg.com/@jspsych/extension-tobii@0.1.1/dist/index.browser.js.map
1006
+ //# sourceMappingURL=https://unpkg.com/@jspsych/extension-tobii@0.2.1/dist/index.browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","sources":["../package.json","../src/websocket-client.js","../src/data-manager.js","../src/time-sync.js","../src/device-time-sync.js","../src/coordinate-utils.js","../src/data-export.js","../src/validation.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/extension-tobii\",\n \"version\": \"0.1.1\",\n \"description\": \"jsPsych extension for Tobii eye tracker integration via WebSocket\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/extension-tobii\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"websocket\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/extension-tobii#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * WebSocket client for Tobii server communication\n */\nexport class WebSocketClient {\n constructor(config = {}) {\n this.ws = null;\n this.reconnectTimeout = null;\n this.currentReconnectAttempt = 0;\n this.nextRequestId = 0;\n this.config = {\n url: config.url || 'ws://localhost:8080',\n autoConnect: config.autoConnect ?? true,\n reconnectAttempts: config.reconnectAttempts ?? 5,\n reconnectDelay: config.reconnectDelay ?? 1000,\n };\n this.status = {\n connected: false,\n tracking: false,\n };\n this.messageHandlers = new Map();\n }\n /**\n * Connect to WebSocket server\n */\n async connect() {\n if (this.ws?.readyState === WebSocket.OPEN) {\n return;\n }\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(this.config.url);\n // Timeout for connection — cleared on success\n const timeoutId = setTimeout(() => {\n if (this.ws?.readyState !== WebSocket.OPEN) {\n reject(new Error(`Connection timeout (${this.config.url})`));\n }\n }, 5000);\n this.ws.onopen = () => {\n clearTimeout(timeoutId);\n this.status.connected = true;\n this.status.connectedAt = Date.now();\n this.currentReconnectAttempt = 0;\n resolve();\n };\n this.ws.onmessage = (event) => {\n this.handleMessage(event);\n };\n this.ws.onerror = (error) => {\n this.status.lastError = 'WebSocket error';\n console.error('WebSocket error:', error);\n };\n this.ws.onclose = () => {\n this.status.connected = false;\n this.status.tracking = false;\n this.handleDisconnect();\n };\n }\n catch (error) {\n reject(error);\n }\n });\n }\n /**\n * Disconnect from WebSocket server\n */\n async disconnect() {\n if (this.reconnectTimeout !== null) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.status.connected = false;\n this.status.tracking = false;\n }\n /**\n * Check if connected\n */\n isConnected() {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n /**\n * Get connection status\n */\n getStatus() {\n return { ...this.status };\n }\n /**\n * Send message to server\n */\n async send(message) {\n if (!this.isConnected()) {\n throw new Error('Not connected to server');\n }\n this.ws.send(JSON.stringify(message));\n }\n /**\n * Send message and wait for response\n */\n async sendAndWait(message, timeout = 5000) {\n if (!this.isConnected()) {\n throw new Error('Not connected to server');\n }\n return new Promise((resolve, reject) => {\n // Generate unique ID for this request\n const requestId = `req_${this.nextRequestId++}`;\n const messageWithId = { ...message, requestId };\n // Set up response handler\n const timeoutId = setTimeout(() => {\n this.messageHandlers.delete(requestId);\n reject(new Error('Request timeout'));\n }, timeout);\n this.messageHandlers.set(requestId, (data) => {\n clearTimeout(timeoutId);\n this.messageHandlers.delete(requestId);\n resolve(data);\n });\n // Send message\n this.ws.send(JSON.stringify(messageWithId));\n });\n }\n /**\n * Register message handler\n */\n on(messageType, handler) {\n if (this.messageHandlers.has(messageType)) {\n console.warn(`Tobii WebSocket: Overwriting existing handler for message type \"${messageType}\"`);\n }\n this.messageHandlers.set(messageType, handler);\n }\n /**\n * Unregister message handler\n */\n off(messageType) {\n this.messageHandlers.delete(messageType);\n }\n /**\n * Handle incoming message\n */\n handleMessage(event) {\n try {\n const receiveTime = performance.now();\n const data = JSON.parse(event.data);\n data._clientReceiveTime = receiveTime;\n // Check for request ID (response to sendAndWait)\n if (data.requestId && this.messageHandlers.has(data.requestId)) {\n const handler = this.messageHandlers.get(data.requestId);\n handler(data);\n return;\n }\n // Handle by message type\n if (data.type && this.messageHandlers.has(data.type)) {\n const handler = this.messageHandlers.get(data.type);\n handler(data);\n }\n }\n catch (error) {\n console.error('Error handling message:', error);\n }\n }\n /**\n * Handle disconnection and attempt reconnect\n */\n handleDisconnect() {\n if (this.currentReconnectAttempt < this.config.reconnectAttempts) {\n this.currentReconnectAttempt++;\n const delay = this.config.reconnectDelay * this.currentReconnectAttempt;\n this.reconnectTimeout = window.setTimeout(async () => {\n try {\n await this.connect();\n // Emit reconnected event so listeners can re-sync time\n const reconnectedHandler = this.messageHandlers.get('reconnected');\n if (reconnectedHandler) {\n reconnectedHandler({ type: 'reconnected' });\n }\n }\n catch (error) {\n console.warn(`Tobii: Reconnection attempt ${this.currentReconnectAttempt}/${this.config.reconnectAttempts} failed:`, error);\n }\n }, delay);\n }\n else {\n this.status.lastError = 'Max reconnection attempts reached';\n }\n }\n}\n//# sourceMappingURL=websocket-client.js.map","/**\n * Data manager for storing and retrieving gaze data\n */\nexport class DataManager {\n /**\n * @param maxBufferSize Maximum number of samples to retain. Oldest samples\n * are dropped when the buffer exceeds this size. Default is 7200\n * (~60 seconds at 120 Hz).\n */\n constructor(maxBufferSize = 7200) {\n this.gazeBuffer = [];\n this.trialStartTime = null;\n this.trialEndTime = null;\n this.maxBufferSize = maxBufferSize;\n }\n /**\n * Add gaze data point to the buffer\n */\n addGazeData(data) {\n this.gazeBuffer.push(data);\n if (this.gazeBuffer.length > this.maxBufferSize) {\n this.gazeBuffer = this.gazeBuffer.slice(-this.maxBufferSize);\n }\n }\n /**\n * Mark trial start\n */\n startTrial() {\n this.trialStartTime = performance.now();\n }\n /**\n * Mark trial end\n */\n endTrial() {\n this.trialEndTime = performance.now();\n }\n /**\n * Get all gaze data for current trial\n */\n getTrialData() {\n if (this.trialStartTime === null) {\n return [];\n }\n const endTime = this.trialEndTime || performance.now();\n return this.gazeBuffer.filter((data) => {\n // Use browserTimestamp for filtering (in performance.now() domain, same as startTrial/endTrial)\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= this.trialStartTime && ts <= endTime;\n });\n }\n /**\n * Get gaze data for specific time range (using browserTimestamp if available)\n */\n getDataRange(startTime, endTime) {\n return this.gazeBuffer.filter((data) => {\n // Use browserTimestamp for filtering (in performance.now() domain)\n // Fall back to timestamp if browserTimestamp not available\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= startTime && ts <= endTime;\n });\n }\n /**\n * Get most recent gaze data point\n */\n getCurrentGaze() {\n if (this.gazeBuffer.length === 0) {\n return null;\n }\n return this.gazeBuffer[this.gazeBuffer.length - 1];\n }\n /**\n * Clear all gaze data\n */\n clear() {\n this.gazeBuffer = [];\n this.trialStartTime = null;\n this.trialEndTime = null;\n }\n /**\n * Clear old data (keep only recent data)\n */\n clearOldData(keepDuration = 60000) {\n const cutoffTime = performance.now() - keepDuration;\n this.gazeBuffer = this.gazeBuffer.filter((data) => {\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= cutoffTime;\n });\n }\n /**\n * Get buffer size\n */\n getBufferSize() {\n return this.gazeBuffer.length;\n }\n /**\n * Get recent gaze data from the last N milliseconds\n */\n getRecentData(durationMs) {\n const now = performance.now();\n const startTime = now - durationMs;\n return this.gazeBuffer.filter((data) => {\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= startTime;\n });\n }\n}\n//# sourceMappingURL=data-manager.js.map","/**\n * Time synchronization between browser and server\n */\nexport class TimeSync {\n constructor(ws) {\n this.ws = ws;\n this.offset = 0;\n this.synced = false;\n }\n /**\n * Synchronize time with server\n */\n async synchronize() {\n const measurements = [];\n const numSamples = 10;\n for (let i = 0; i < numSamples; i++) {\n const t0 = performance.now();\n const response = await this.ws.sendAndWait({\n type: 'time_sync',\n clientTime: t0,\n });\n const t1 = performance.now();\n const roundTripTime = t1 - t0;\n const serverTime = response.serverTime;\n // Estimate one-way latency\n const latency = roundTripTime / 2;\n // Calculate offset\n const offset = serverTime - (t0 + latency);\n measurements.push(offset);\n // Small delay between measurements\n await this.delay(100);\n }\n // Use median offset to reduce noise\n this.offset = this.median(measurements);\n this.synced = true;\n }\n /**\n * Convert local timestamp to server timestamp\n */\n toServerTime(localTime) {\n return localTime + this.offset;\n }\n /**\n * Convert server timestamp to local timestamp\n */\n toLocalTime(serverTime) {\n return serverTime - this.offset;\n }\n /**\n * Check if time is synchronized\n */\n isSynced() {\n return this.synced;\n }\n /**\n * Get current offset\n */\n getOffset() {\n return this.offset;\n }\n /**\n * Calculate median of array\n */\n median(values) {\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];\n }\n /**\n * Delay helper\n */\n delay(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n//# sourceMappingURL=time-sync.js.map","/**\n * Device time synchronization - maps browser timestamps to Tobii device timestamps\n *\n * Three clocks exist:\n * A = performance.now() (browser)\n * B = time.time() * 1000 (Python server)\n * C = Tobii device clock\n *\n * TimeSync establishes A↔B. The server computes B↔C from gaze samples\n * (server_timestamp and device timestamp are set in the same Python callback).\n * This module chains them: A↔C = A↔B + B↔C.\n */\nexport class DeviceTimeSync {\n constructor(ws, timeSync) {\n this.ws = ws;\n this.timeSync = timeSync;\n this.offsetBC = null;\n this.bcSampleCount = 0;\n this.bcStdDev = null;\n this.bcMin = null;\n this.bcMax = null;\n }\n /**\n * Request the B-C offset from the server and compute the A-C chain.\n * Requires that TimeSync (A-B) is already synchronized and that\n * gaze samples have been collected on the server.\n */\n async synchronizeDeviceClock() {\n if (!this.timeSync.isSynced()) {\n return false;\n }\n try {\n const response = await this.ws.sendAndWait({\n type: 'get_device_clock_offset',\n });\n if (!response.success) {\n return false;\n }\n this.offsetBC = response.offset;\n this.bcSampleCount = response.sample_count;\n this.bcStdDev = response.std_dev;\n this.bcMin = response.min;\n this.bcMax = response.max;\n return true;\n }\n catch {\n return false;\n }\n }\n /**\n * Whether the full A↔C chain is established\n */\n isSynced() {\n return this.timeSync.isSynced() && this.offsetBC !== null;\n }\n /**\n * Convert a performance.now() timestamp to device clock time.\n * offset_AB: B = A + offset_AB\n * offset_BC: B = C + offset_BC → C = B - offset_BC\n * So: C = (A + offset_AB) - offset_BC = A + (offset_AB - offset_BC)\n */\n toDeviceTime(performanceNow) {\n if (!this.isSynced()) {\n throw new Error('Device time sync not established. Call synchronizeDeviceClock() first.');\n }\n const offsetAB = this.timeSync.getOffset();\n return performanceNow + offsetAB - this.offsetBC;\n }\n /**\n * Convert a device clock timestamp to performance.now() domain.\n * A = C - offset_AB + offset_BC = C - (offset_AB - offset_BC)\n */\n toLocalTime(deviceTime) {\n if (!this.isSynced()) {\n throw new Error('Device time sync not established. Call synchronizeDeviceClock() first.');\n }\n const offsetAB = this.timeSync.getOffset();\n return deviceTime - offsetAB + this.offsetBC;\n }\n /**\n * Get full synchronization status with all offsets and diagnostics\n */\n getStatus() {\n const offsetAB = this.timeSync.getOffset();\n const offsetAC = this.offsetBC !== null ? offsetAB - this.offsetBC : null;\n return {\n synced: this.isSynced(),\n offsetAB,\n offsetBC: this.offsetBC,\n offsetAC,\n bcSampleCount: this.bcSampleCount,\n bcStdDev: this.bcStdDev,\n bcMin: this.bcMin,\n bcMax: this.bcMax,\n };\n }\n /**\n * Validate timestamp alignment across a set of gaze samples.\n *\n * For each sample, computes: residual = (_receiveTime + offset_AC) - timestamp\n * using the internal _receiveTime property (raw WebSocket receive time) as an\n * independent measurement to cross-validate the sync offset.\n * If clocks are well-aligned, residuals should cluster tightly around the\n * one-way WebSocket latency (server→client).\n *\n * @param samples - Array of gaze samples (must have internal _receiveTime set)\n * @returns Alignment statistics, or null if sync is not established or no valid samples\n */\n validateTimestampAlignment(samples) {\n if (!this.isSynced()) {\n return null;\n }\n const offsetAB = this.timeSync.getOffset();\n const offsetAC = offsetAB - this.offsetBC;\n const residuals = [];\n for (const sample of samples) {\n const receiveTime = sample._receiveTime;\n if (receiveTime != null && sample.timestamp != null) {\n residuals.push(receiveTime + offsetAC - sample.timestamp);\n }\n }\n if (residuals.length === 0) {\n return null;\n }\n const n = residuals.length;\n const mean = residuals.reduce((a, b) => a + b, 0) / n;\n const variance = residuals.reduce((a, b) => a + (b - mean) ** 2, 0) / n;\n const stdDev = Math.sqrt(variance);\n const min = Math.min(...residuals);\n const max = Math.max(...residuals);\n return {\n sampleCount: n,\n meanResidual: mean,\n stdDev,\n min,\n max,\n };\n }\n /**\n * Reset sync state (e.g., after reconnection)\n */\n reset() {\n this.offsetBC = null;\n this.bcSampleCount = 0;\n this.bcStdDev = null;\n this.bcMin = null;\n this.bcMax = null;\n }\n}\n//# sourceMappingURL=device-time-sync.js.map","/**\n * Coordinate conversion utilities\n */\n/**\n * Convert normalized coordinates (0-1) to pixels\n */\nexport function normalizedToPixels(x, y) {\n const width = window.innerWidth;\n const height = window.innerHeight;\n return {\n x: Math.round(x * width),\n y: Math.round(y * height),\n };\n}\n/**\n * Convert pixel coordinates to normalized (0-1)\n */\nexport function pixelsToNormalized(x, y) {\n const width = window.innerWidth;\n const height = window.innerHeight;\n return {\n x: x / width,\n y: y / height,\n };\n}\n/**\n * Get screen dimensions\n */\nexport function getScreenDimensions() {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n}\n/**\n * Calculate distance between two points\n */\nexport function distance(p1, p2) {\n const dx = p2.x - p1.x;\n const dy = p2.y - p1.y;\n return Math.sqrt(dx * dx + dy * dy);\n}\n/**\n * Convert window pixel coordinates to container-relative coordinates\n */\nexport function windowToContainer(x, y, container) {\n const rect = container.getBoundingClientRect();\n return {\n x: Math.round(x - rect.left),\n y: Math.round(y - rect.top),\n };\n}\n/**\n * Get container dimensions\n */\nexport function getContainerDimensions(container) {\n const rect = container.getBoundingClientRect();\n return {\n width: rect.width,\n height: rect.height,\n };\n}\n/**\n * Check if window coordinates fall within a container\n */\nexport function isWithinContainer(x, y, container) {\n const rect = container.getBoundingClientRect();\n return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;\n}\n//# sourceMappingURL=coordinate-utils.js.map","/**\n * Data export utilities\n */\n/**\n * Convert data to CSV format and download\n */\nexport function toCSV(data, filename) {\n if (data.length === 0) {\n console.warn('No data to export');\n return;\n }\n // Get all unique keys\n const keys = Array.from(new Set(data.flatMap((item) => Object.keys(flattenObject(item)))));\n // Create CSV header\n const header = keys.join(',');\n // Create CSV rows\n const rows = data.map((item) => {\n const flattened = flattenObject(item);\n return keys\n .map((key) => {\n const value = flattened[key];\n // Handle values that might contain commas\n if (typeof value === 'string' && value.includes(',')) {\n return `\"${value}\"`;\n }\n return value ?? '';\n })\n .join(',');\n });\n // Combine header and rows\n const csv = [header, ...rows].join('\\n');\n // Download\n downloadFile(csv, filename, 'text/csv');\n}\n/**\n * Convert data to JSON format and download\n */\nexport function toJSON(data, filename) {\n const json = JSON.stringify(data, null, 2);\n downloadFile(json, filename, 'application/json');\n}\n/**\n * Flatten nested object for CSV export\n */\nfunction flattenObject(obj, prefix = '') {\n const flattened = {};\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n Object.assign(flattened, flattenObject(value, newKey));\n }\n else if (Array.isArray(value)) {\n flattened[newKey] = JSON.stringify(value);\n }\n else {\n flattened[newKey] = value;\n }\n }\n return flattened;\n}\n/**\n * Download file helper\n */\nfunction downloadFile(content, filename, mimeType) {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n setTimeout(() => URL.revokeObjectURL(url), 60000);\n}\n//# sourceMappingURL=data-export.js.map","/**\n * Data validation utilities\n */\n/**\n * Validate gaze data point\n */\nexport function validateGazeData(data) {\n if (typeof data !== 'object' || data === null)\n return false;\n const d = data;\n return (typeof d.x === 'number' &&\n typeof d.y === 'number' &&\n typeof d.timestamp === 'number' &&\n !isNaN(d.x) &&\n !isNaN(d.y) &&\n !isNaN(d.timestamp));\n}\n/**\n * Validate calibration point\n */\nexport function validateCalibrationPoint(point) {\n if (typeof point !== 'object' || point === null)\n return false;\n const p = point;\n return (typeof p.x === 'number' &&\n typeof p.y === 'number' &&\n p.x >= 0 &&\n p.x <= 1 &&\n p.y >= 0 &&\n p.y <= 1);\n}\n/**\n * Filter invalid gaze data\n */\nexport function filterValidGaze(data) {\n return data.filter(validateGazeData);\n}\n/**\n * Validate that a server response conforms to CalibrationResult\n */\nexport function validateCalibrationResult(data) {\n if (typeof data !== 'object' || data === null)\n return false;\n return typeof data.success === 'boolean';\n}\n/**\n * Validate that a server response conforms to ValidationResult\n */\nexport function validateValidationResult(data) {\n if (typeof data !== 'object' || data === null)\n return false;\n return typeof data.success === 'boolean';\n}\n//# sourceMappingURL=validation.js.map","/**\n * @title Tobii Extension\n * @description jsPsych extension for Tobii eye tracker integration via WebSocket.\n * Provides real-time gaze data streaming, calibration control, time synchronization,\n * and coordinate utilities for eye tracking experiments.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/extension-tobii#readme Documentation}\n */\n\nimport { JsPsych, JsPsychExtension, JsPsychExtensionInfo, ParameterType } from 'jspsych';\nimport { version } from '../package.json';\n\nimport { WebSocketClient } from './websocket-client';\nimport { DataManager } from './data-manager';\nimport { TimeSync } from './time-sync';\nimport { DeviceTimeSync } from './device-time-sync';\nimport * as CoordinateUtils from './coordinate-utils';\nimport * as DataExport from './data-export';\nimport * as Validation from './validation';\n\nimport type {\n InitializeParameters,\n OnStartParameters,\n OnFinishParameters,\n GazeData,\n CalibrationPoint,\n CalibrationResult,\n ValidationResult,\n UserPositionData,\n ScreenDimensions,\n Coordinates,\n ConnectionStatus,\n DeviceTimeSyncStatus,\n TimestampAlignmentResult,\n} from './types';\n\nclass TobiiExtension implements JsPsychExtension {\n static info: JsPsychExtensionInfo = {\n name: 'tobii',\n version: version,\n data: {\n /** Eye tracking gaze data collected during the trial */\n tobii_data: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n },\n };\n\n private jsPsych: JsPsych;\n private ws!: WebSocketClient;\n private dataManager!: DataManager;\n private timeSync!: TimeSync;\n private deviceTimeSync!: DeviceTimeSync;\n private tracking: boolean = false;\n private config: InitializeParameters = {};\n private gazeSampleCount: number = 0;\n private deviceTimeSyncTriggered: boolean = false;\n\n constructor(jsPsych: JsPsych) {\n this.jsPsych = jsPsych;\n }\n\n initialize = async (params: InitializeParameters = {}): Promise<void> => {\n this.config = params;\n\n // Initialize WebSocket client\n this.ws = new WebSocketClient(params.connection);\n\n // Initialize data manager\n this.dataManager = new DataManager();\n\n // Initialize time synchronization\n this.timeSync = new TimeSync(this.ws);\n\n // Initialize device time synchronization (browser ↔ device clock chain)\n this.deviceTimeSync = new DeviceTimeSync(this.ws, this.timeSync);\n\n // Set up gaze data handler\n this.ws.on('gaze_data', (data) => {\n const rawGaze = data.gaze;\n if (rawGaze && Validation.validateGazeData(rawGaze)) {\n const receiveTime = (data._clientReceiveTime as number) ?? performance.now();\n const gazeWithTimestamps: GazeData = {\n ...rawGaze,\n browserTimestamp: this.deviceTimeSync.isSynced()\n ? this.deviceTimeSync.toLocalTime(rawGaze.timestamp as number)\n : receiveTime,\n };\n // Store raw receive time for validateTimestampAlignment cross-check\n (gazeWithTimestamps as unknown as Record<string, unknown>)._receiveTime = receiveTime;\n this.dataManager.addGazeData(gazeWithTimestamps);\n\n // Auto-trigger device time sync after first 50 gaze samples\n this.gazeSampleCount++;\n if (!this.deviceTimeSyncTriggered && this.gazeSampleCount >= 50) {\n this.deviceTimeSyncTriggered = true;\n this.deviceTimeSync.synchronizeDeviceClock().catch((e) => {\n console.warn('Tobii: Device time sync failed, can be retried manually:', e);\n });\n }\n }\n });\n\n // Set up reconnection handler to re-sync time\n this.ws.on('reconnected', async () => {\n try {\n await this.timeSync.synchronize();\n } catch (e) {\n console.warn('Tobii: Time sync failed after reconnection:', e);\n }\n // Reset device time sync so it re-triggers once new samples arrive\n this.deviceTimeSync.reset();\n this.gazeSampleCount = 0;\n this.deviceTimeSyncTriggered = false;\n });\n\n // Auto-connect if configured\n if (params.connection?.autoConnect) {\n await this.connect();\n }\n };\n\n on_start = async (_params: OnStartParameters = {}): Promise<void> => {\n // Mark trial start\n this.dataManager.startTrial();\n\n // Start tracking if not already tracking\n if (!this.tracking) {\n await this.startTracking();\n }\n };\n\n on_load = async (): Promise<void> => {\n // Optional: additional setup when trial loads\n };\n\n on_finish = async (_params: OnFinishParameters = {}): Promise<{ tobii_data: GazeData[] }> => {\n // Mark trial end\n this.dataManager.endTrial();\n\n // Get trial data\n const trialData = this.dataManager.getTrialData();\n\n // Clear old data to prevent memory buildup\n this.dataManager.clearOldData();\n\n return {\n tobii_data: trialData,\n };\n };\n\n // ==========================================\n // PUBLIC API METHODS\n // These are accessible via jsPsych.extensions.tobii.*\n // ==========================================\n\n /**\n * Connect to the WebSocket server\n */\n async connect(): Promise<void> {\n await this.ws.connect();\n await this.timeSync.synchronize();\n }\n\n /**\n * Disconnect from the WebSocket server\n */\n async disconnect(): Promise<void> {\n if (this.tracking) {\n await this.stopTracking();\n }\n await this.ws.disconnect();\n }\n\n /**\n * Check if connected to server\n */\n isConnected(): boolean {\n return this.ws.isConnected();\n }\n\n /**\n * Get connection status details\n */\n getConnectionStatus(): ConnectionStatus {\n return this.ws.getStatus();\n }\n\n /**\n * Start eye tracking data collection\n */\n async startTracking(): Promise<void> {\n if (!this.isConnected()) {\n throw new Error('Not connected to server. Call connect() first.');\n }\n\n // Wait for server confirmation before setting state\n const response = await this.ws.sendAndWait({ type: 'start_tracking' });\n if (response.success) {\n this.tracking = true;\n } else {\n throw new Error(`Server failed to start tracking: ${response.error || 'unknown error'}`);\n }\n }\n\n /**\n * Stop eye tracking data collection\n */\n async stopTracking(): Promise<void> {\n try {\n await this.ws.sendAndWait({ type: 'stop_tracking' });\n } finally {\n this.tracking = false;\n }\n }\n\n /**\n * Check if currently tracking\n */\n isTracking(): boolean {\n return this.tracking;\n }\n\n /**\n * Start calibration procedure\n */\n async startCalibration(): Promise<void> {\n await this.ws.send({ type: 'calibration_start' });\n }\n\n /**\n * Collect calibration data for a specific point\n * @returns Promise resolving to success status when SDK finishes collecting\n */\n async collectCalibrationPoint(x: number, y: number): Promise<{ success: boolean }> {\n if (!Validation.validateCalibrationPoint({ x, y })) {\n throw new Error(\n `Invalid calibration point (${x}, ${y}). Coordinates must be in range [0, 1].`\n );\n }\n\n const response = await this.ws.sendAndWait({\n type: 'calibration_point',\n point: { x, y },\n timestamp: performance.now(),\n });\n\n return { success: response.success === true };\n }\n\n /**\n * Compute calibration from collected points\n */\n async computeCalibration(): Promise<CalibrationResult> {\n const response = await this.ws.sendAndWait({\n type: 'calibration_compute',\n });\n if (!Validation.validateCalibrationResult(response)) {\n return { success: false, error: 'Invalid server response' };\n }\n return response;\n }\n\n /**\n * Get calibration data/quality metrics\n */\n async getCalibrationData(): Promise<CalibrationResult> {\n const response = await this.ws.sendAndWait({\n type: 'get_calibration_data',\n });\n if (!Validation.validateCalibrationResult(response)) {\n return { success: false, error: 'Invalid server response' };\n }\n return response;\n }\n\n /**\n * Start validation procedure\n */\n async startValidation(): Promise<void> {\n await this.ws.send({ type: 'validation_start' });\n }\n\n /**\n * Collect validation data for a specific point\n * @param x - Normalized x coordinate (0-1)\n * @param y - Normalized y coordinate (0-1)\n * @param gazeSamples - Optional array of gaze samples collected at this point\n */\n async collectValidationPoint(x: number, y: number, gazeSamples?: GazeData[]): Promise<void> {\n if (!Validation.validateCalibrationPoint({ x, y })) {\n throw new Error(\n `Invalid validation point (${x}, ${y}). Coordinates must be in range [0, 1].`\n );\n }\n\n await this.ws.send({\n type: 'validation_point',\n point: { x, y },\n timestamp: performance.now(),\n gaze_samples: gazeSamples || [],\n });\n }\n\n /**\n * Get recent gaze data from the data manager buffer\n * @param durationMs - How many milliseconds of recent data to retrieve\n */\n getRecentGazeData(durationMs: number): GazeData[] {\n return this.dataManager.getRecentData(durationMs);\n }\n\n /**\n * Compute validation from collected points\n */\n async computeValidation(): Promise<ValidationResult> {\n const response = await this.ws.sendAndWait({\n type: 'validation_compute',\n });\n if (!Validation.validateValidationResult(response)) {\n return { success: false, error: 'Invalid server response' };\n }\n return response;\n }\n\n /**\n * Get current gaze position\n */\n async getCurrentGaze(): Promise<GazeData | null> {\n // Try to get from local buffer first\n const localGaze = this.dataManager.getCurrentGaze();\n if (localGaze) {\n return localGaze;\n }\n\n // Otherwise request from server\n const response = await this.ws.sendAndWait({\n type: 'get_current_gaze',\n });\n return (response.gaze as GazeData) || null;\n }\n\n /**\n * Get current user position (head position)\n */\n async getUserPosition(): Promise<UserPositionData | null> {\n if (!this.isConnected()) {\n return null;\n }\n const response = await this.ws.sendAndWait({\n type: 'get_user_position',\n });\n return (response.position as UserPositionData) || null;\n }\n\n /**\n * Get gaze data for a specific time range\n */\n async getGazeData(startTime: number, endTime: number): Promise<GazeData[]> {\n // Get from local buffer\n const localData = this.dataManager.getDataRange(startTime, endTime);\n\n // Filter valid gaze points\n return Validation.filterValidGaze(localData);\n }\n\n /**\n * Clear stored gaze data\n */\n clearGazeData(): void {\n this.dataManager.clear();\n }\n\n /**\n * Convert normalized coordinates (0-1) to pixels\n */\n normalizedToPixels(x: number, y: number): Coordinates {\n return CoordinateUtils.normalizedToPixels(x, y);\n }\n\n /**\n * Convert pixel coordinates to normalized (0-1)\n */\n pixelsToNormalized(x: number, y: number): Coordinates {\n return CoordinateUtils.pixelsToNormalized(x, y);\n }\n\n /**\n * Get screen dimensions\n */\n getScreenDimensions(): ScreenDimensions {\n return CoordinateUtils.getScreenDimensions();\n }\n\n /**\n * Calculate distance between two points\n */\n calculateDistance(p1: Coordinates, p2: Coordinates): number {\n return CoordinateUtils.distance(p1, p2);\n }\n\n /**\n * Convert window pixel coordinates to container-relative coordinates\n * @param x - X coordinate in window pixels\n * @param y - Y coordinate in window pixels\n * @param container - Optional container element (defaults to jsPsych display element)\n */\n windowToContainer(x: number, y: number, container?: HTMLElement): Coordinates {\n const el = container || this.jsPsych.getDisplayElement();\n return CoordinateUtils.windowToContainer(x, y, el);\n }\n\n /**\n * Get container dimensions\n * @param container - Optional container element (defaults to jsPsych display element)\n */\n getContainerDimensions(container?: HTMLElement): ScreenDimensions {\n const el = container || this.jsPsych.getDisplayElement();\n return CoordinateUtils.getContainerDimensions(el);\n }\n\n /**\n * Check if window coordinates fall within a container\n * @param x - X coordinate in window pixels\n * @param y - Y coordinate in window pixels\n * @param container - Optional container element (defaults to jsPsych display element)\n */\n isWithinContainer(x: number, y: number, container?: HTMLElement): boolean {\n const el = container || this.jsPsych.getDisplayElement();\n return CoordinateUtils.isWithinContainer(x, y, el);\n }\n\n /**\n * Export gaze data to CSV\n */\n exportToCSV(data: Record<string, unknown>[], filename: string): void {\n DataExport.toCSV(data, filename);\n }\n\n /**\n * Export gaze data to JSON\n */\n exportToJSON(data: Record<string, unknown>[], filename: string): void {\n DataExport.toJSON(data, filename);\n }\n\n /**\n * Set extension configuration\n */\n setConfig(config: Partial<InitializeParameters>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Get current configuration\n */\n getConfig(): InitializeParameters {\n return { ...this.config };\n }\n\n /**\n * Get time synchronization offset\n */\n getTimeOffset(): number {\n return this.timeSync.getOffset();\n }\n\n /**\n * Check if time is synchronized\n */\n isTimeSynced(): boolean {\n return this.timeSync.isSynced();\n }\n\n /**\n * Convert a performance.now() timestamp to Tobii device clock time.\n * Requires that device time sync is established.\n */\n toDeviceTime(performanceNow: number): number {\n return this.deviceTimeSync.toDeviceTime(performanceNow);\n }\n\n /**\n * Convert a Tobii device clock timestamp to performance.now() domain.\n * Requires that device time sync is established.\n */\n toLocalTime(deviceTime: number): number {\n return this.deviceTimeSync.toLocalTime(deviceTime);\n }\n\n /**\n * Check if the browser-to-device time sync chain is established\n */\n isDeviceTimeSynced(): boolean {\n return this.deviceTimeSync.isSynced();\n }\n\n /**\n * Get full device time synchronization status with all offsets and diagnostics\n */\n getTimeSyncStatus(): DeviceTimeSyncStatus {\n return this.deviceTimeSync.getStatus();\n }\n\n /**\n * Validate timestamp alignment across a set of gaze samples.\n * Computes per-sample residuals to verify the A↔C offset is consistent.\n * Low stdDev indicates well-aligned timestamps.\n * @param samples - Gaze samples to validate (uses internal _receiveTime for cross-check)\n */\n validateTimestampAlignment(samples: GazeData[]): TimestampAlignmentResult | null {\n return this.deviceTimeSync.validateTimestampAlignment(samples);\n }\n}\n\nexport default TobiiExtension;\n\n// Export types for use by plugins and experiments\nexport type {\n InitializeParameters,\n OnStartParameters,\n OnFinishParameters,\n GazeData,\n CalibrationPoint,\n CalibrationResult,\n ValidationResult,\n UserPositionData,\n ScreenDimensions,\n Coordinates,\n ConnectionStatus,\n DeviceTimeSyncStatus,\n TimestampAlignmentResult,\n};\n"],"names":["Validation.validateGazeData","ParameterType","Validation.validateCalibrationPoint","Validation.validateCalibrationResult","Validation.validateValidationResult","Validation.filterValidGaze","CoordinateUtils.normalizedToPixels","CoordinateUtils.pixelsToNormalized","CoordinateUtils.getScreenDimensions","CoordinateUtils.distance","CoordinateUtils.windowToContainer","CoordinateUtils.getContainerDimensions","CoordinateUtils.isWithinContainer","DataExport.toCSV","DataExport.toJSON"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.js","sources":["../package.json","../src/websocket-client.js","../src/data-manager.js","../src/time-sync.js","../src/device-time-sync.js","../src/coordinate-utils.js","../src/data-export.js","../src/validation.js","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/extension-tobii\",\n \"version\": \"0.2.1\",\n \"description\": \"jsPsych extension for Tobii eye tracker integration via WebSocket\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-tobii.git\",\n \"directory\": \"packages/extension-tobii\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"eye-tracking\",\n \"tobii\",\n \"websocket\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-tobii/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-tobii/tree/main/packages/extension-tobii#readme\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","/**\n * WebSocket client for Tobii server communication\n */\nexport class WebSocketClient {\n constructor(config = {}) {\n this.ws = null;\n this.reconnectTimeout = null;\n this.currentReconnectAttempt = 0;\n this.nextRequestId = 0;\n this.config = {\n url: config.url || 'ws://localhost:8080',\n autoConnect: config.autoConnect ?? true,\n reconnectAttempts: config.reconnectAttempts ?? 5,\n reconnectDelay: config.reconnectDelay ?? 1000,\n };\n this.status = {\n connected: false,\n tracking: false,\n };\n this.messageHandlers = new Map();\n }\n /**\n * Connect to WebSocket server\n */\n async connect() {\n if (this.ws?.readyState === WebSocket.OPEN) {\n return;\n }\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(this.config.url);\n // Timeout for connection — cleared on success\n const timeoutId = setTimeout(() => {\n if (this.ws?.readyState !== WebSocket.OPEN) {\n reject(new Error(`Connection timeout (${this.config.url})`));\n }\n }, 5000);\n this.ws.onopen = () => {\n clearTimeout(timeoutId);\n this.status.connected = true;\n this.status.connectedAt = Date.now();\n this.currentReconnectAttempt = 0;\n resolve();\n };\n this.ws.onmessage = (event) => {\n this.handleMessage(event);\n };\n this.ws.onerror = (error) => {\n this.status.lastError = 'WebSocket error';\n console.error('WebSocket error:', error);\n };\n this.ws.onclose = () => {\n this.status.connected = false;\n this.status.tracking = false;\n this.handleDisconnect();\n };\n }\n catch (error) {\n reject(error);\n }\n });\n }\n /**\n * Disconnect from WebSocket server\n */\n async disconnect() {\n if (this.reconnectTimeout !== null) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.status.connected = false;\n this.status.tracking = false;\n }\n /**\n * Check if connected\n */\n isConnected() {\n return this.ws?.readyState === WebSocket.OPEN;\n }\n /**\n * Get connection status\n */\n getStatus() {\n return { ...this.status };\n }\n /**\n * Send message to server\n */\n async send(message) {\n if (!this.isConnected()) {\n throw new Error('Not connected to server');\n }\n this.ws.send(JSON.stringify(message));\n }\n /**\n * Send message and wait for response\n */\n async sendAndWait(message, timeout = 5000) {\n if (!this.isConnected()) {\n throw new Error('Not connected to server');\n }\n return new Promise((resolve, reject) => {\n // Generate unique ID for this request\n const requestId = `req_${this.nextRequestId++}`;\n const messageWithId = { ...message, requestId };\n // Set up response handler\n const timeoutId = setTimeout(() => {\n this.messageHandlers.delete(requestId);\n reject(new Error('Request timeout'));\n }, timeout);\n this.messageHandlers.set(requestId, (data) => {\n clearTimeout(timeoutId);\n this.messageHandlers.delete(requestId);\n resolve(data);\n });\n // Send message\n this.ws.send(JSON.stringify(messageWithId));\n });\n }\n /**\n * Register message handler\n */\n on(messageType, handler) {\n if (this.messageHandlers.has(messageType)) {\n console.warn(`Tobii WebSocket: Overwriting existing handler for message type \"${messageType}\"`);\n }\n this.messageHandlers.set(messageType, handler);\n }\n /**\n * Unregister message handler\n */\n off(messageType) {\n this.messageHandlers.delete(messageType);\n }\n /**\n * Handle incoming message\n */\n handleMessage(event) {\n try {\n const receiveTime = performance.now();\n const data = JSON.parse(event.data);\n data._clientReceiveTime = receiveTime;\n // Check for request ID (response to sendAndWait)\n if (data.requestId && this.messageHandlers.has(data.requestId)) {\n const handler = this.messageHandlers.get(data.requestId);\n handler(data);\n return;\n }\n // Handle by message type\n if (data.type && this.messageHandlers.has(data.type)) {\n const handler = this.messageHandlers.get(data.type);\n handler(data);\n }\n }\n catch (error) {\n console.error('Error handling message:', error);\n }\n }\n /**\n * Handle disconnection and attempt reconnect\n */\n handleDisconnect() {\n if (this.currentReconnectAttempt < this.config.reconnectAttempts) {\n this.currentReconnectAttempt++;\n const delay = this.config.reconnectDelay * this.currentReconnectAttempt;\n this.reconnectTimeout = window.setTimeout(async () => {\n try {\n await this.connect();\n // Emit reconnected event so listeners can re-sync time\n const reconnectedHandler = this.messageHandlers.get('reconnected');\n if (reconnectedHandler) {\n reconnectedHandler({ type: 'reconnected' });\n }\n }\n catch (error) {\n console.warn(`Tobii: Reconnection attempt ${this.currentReconnectAttempt}/${this.config.reconnectAttempts} failed:`, error);\n }\n }, delay);\n }\n else {\n this.status.lastError = 'Max reconnection attempts reached';\n }\n }\n}\n//# sourceMappingURL=websocket-client.js.map","/**\n * Data manager for storing and retrieving gaze data\n */\nexport class DataManager {\n /**\n * @param maxBufferSize Maximum number of samples to retain. Oldest samples\n * are dropped when the buffer exceeds this size. Default is 7200\n * (~60 seconds at 120 Hz).\n */\n constructor(maxBufferSize = 7200) {\n this.gazeBuffer = [];\n this.trialStartTime = null;\n this.trialEndTime = null;\n this.maxBufferSize = maxBufferSize;\n }\n /**\n * Add gaze data point to the buffer\n */\n addGazeData(data) {\n this.gazeBuffer.push(data);\n if (this.gazeBuffer.length > this.maxBufferSize) {\n this.gazeBuffer = this.gazeBuffer.slice(-this.maxBufferSize);\n }\n }\n /**\n * Mark trial start\n */\n startTrial() {\n this.trialStartTime = performance.now();\n }\n /**\n * Mark trial end\n */\n endTrial() {\n this.trialEndTime = performance.now();\n }\n /**\n * Get all gaze data for current trial\n */\n getTrialData() {\n if (this.trialStartTime === null) {\n return [];\n }\n const endTime = this.trialEndTime || performance.now();\n return this.gazeBuffer.filter((data) => {\n // Use browserTimestamp for filtering (in performance.now() domain, same as startTrial/endTrial)\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= this.trialStartTime && ts <= endTime;\n });\n }\n /**\n * Get gaze data for specific time range (using browserTimestamp if available)\n */\n getDataRange(startTime, endTime) {\n return this.gazeBuffer.filter((data) => {\n // Use browserTimestamp for filtering (in performance.now() domain)\n // Fall back to timestamp if browserTimestamp not available\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= startTime && ts <= endTime;\n });\n }\n /**\n * Get most recent gaze data point\n */\n getCurrentGaze() {\n if (this.gazeBuffer.length === 0) {\n return null;\n }\n return this.gazeBuffer[this.gazeBuffer.length - 1];\n }\n /**\n * Clear all gaze data\n */\n clear() {\n this.gazeBuffer = [];\n this.trialStartTime = null;\n this.trialEndTime = null;\n }\n /**\n * Clear old data (keep only recent data)\n */\n clearOldData(keepDuration = 60000) {\n const cutoffTime = performance.now() - keepDuration;\n this.gazeBuffer = this.gazeBuffer.filter((data) => {\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= cutoffTime;\n });\n }\n /**\n * Get buffer size\n */\n getBufferSize() {\n return this.gazeBuffer.length;\n }\n /**\n * Get recent gaze data from the last N milliseconds\n */\n getRecentData(durationMs) {\n const now = performance.now();\n const startTime = now - durationMs;\n return this.gazeBuffer.filter((data) => {\n const ts = data.browserTimestamp ?? data.timestamp;\n return ts >= startTime;\n });\n }\n}\n//# sourceMappingURL=data-manager.js.map","/**\n * Time synchronization between browser and server\n */\nexport class TimeSync {\n constructor(ws) {\n this.ws = ws;\n this.offset = 0;\n this.synced = false;\n }\n /**\n * Synchronize time with server\n */\n async synchronize() {\n const measurements = [];\n const numSamples = 10;\n for (let i = 0; i < numSamples; i++) {\n const t0 = performance.now();\n const response = await this.ws.sendAndWait({\n type: 'time_sync',\n clientTime: t0,\n });\n const t1 = performance.now();\n const roundTripTime = t1 - t0;\n const serverTime = response.serverTime;\n // Estimate one-way latency\n const latency = roundTripTime / 2;\n // Calculate offset\n const offset = serverTime - (t0 + latency);\n measurements.push(offset);\n // Small delay between measurements\n await this.delay(100);\n }\n // Use median offset to reduce noise\n this.offset = this.median(measurements);\n this.synced = true;\n }\n /**\n * Convert local timestamp to server timestamp\n */\n toServerTime(localTime) {\n return localTime + this.offset;\n }\n /**\n * Convert server timestamp to local timestamp\n */\n toLocalTime(serverTime) {\n return serverTime - this.offset;\n }\n /**\n * Check if time is synchronized\n */\n isSynced() {\n return this.synced;\n }\n /**\n * Get current offset\n */\n getOffset() {\n return this.offset;\n }\n /**\n * Calculate median of array\n */\n median(values) {\n const sorted = [...values].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];\n }\n /**\n * Delay helper\n */\n delay(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n//# sourceMappingURL=time-sync.js.map","/**\n * Device time synchronization - maps browser timestamps to Tobii device timestamps\n *\n * Three clocks exist:\n * A = performance.now() (browser)\n * B = time.time() * 1000 (Python server)\n * C = Tobii device clock\n *\n * TimeSync establishes A↔B. The server computes B↔C from gaze samples\n * (server_timestamp and device timestamp are set in the same Python callback).\n * This module chains them: A↔C = A↔B + B↔C.\n */\nexport class DeviceTimeSync {\n constructor(ws, timeSync) {\n this.ws = ws;\n this.timeSync = timeSync;\n this.offsetBC = null;\n this.bcSampleCount = 0;\n this.bcStdDev = null;\n this.bcMin = null;\n this.bcMax = null;\n }\n /**\n * Request the B-C offset from the server and compute the A-C chain.\n * Requires that TimeSync (A-B) is already synchronized and that\n * gaze samples have been collected on the server.\n */\n async synchronizeDeviceClock() {\n if (!this.timeSync.isSynced()) {\n return false;\n }\n try {\n const response = await this.ws.sendAndWait({\n type: 'get_device_clock_offset',\n });\n if (!response.success) {\n return false;\n }\n this.offsetBC = response.offset;\n this.bcSampleCount = response.sample_count;\n this.bcStdDev = response.std_dev;\n this.bcMin = response.min;\n this.bcMax = response.max;\n return true;\n }\n catch {\n return false;\n }\n }\n /**\n * Whether the full A↔C chain is established\n */\n isSynced() {\n return this.timeSync.isSynced() && this.offsetBC !== null;\n }\n /**\n * Convert a performance.now() timestamp to device clock time.\n * offset_AB: B = A + offset_AB\n * offset_BC: B = C + offset_BC → C = B - offset_BC\n * So: C = (A + offset_AB) - offset_BC = A + (offset_AB - offset_BC)\n */\n toDeviceTime(performanceNow) {\n if (!this.isSynced()) {\n throw new Error('Device time sync not established. Call synchronizeDeviceClock() first.');\n }\n const offsetAB = this.timeSync.getOffset();\n return performanceNow + offsetAB - this.offsetBC;\n }\n /**\n * Convert a device clock timestamp to performance.now() domain.\n * A = C - offset_AB + offset_BC = C - (offset_AB - offset_BC)\n */\n toLocalTime(deviceTime) {\n if (!this.isSynced()) {\n throw new Error('Device time sync not established. Call synchronizeDeviceClock() first.');\n }\n const offsetAB = this.timeSync.getOffset();\n return deviceTime - offsetAB + this.offsetBC;\n }\n /**\n * Get full synchronization status with all offsets and diagnostics\n */\n getStatus() {\n const offsetAB = this.timeSync.getOffset();\n const offsetAC = this.offsetBC !== null ? offsetAB - this.offsetBC : null;\n return {\n synced: this.isSynced(),\n offsetAB,\n offsetBC: this.offsetBC,\n offsetAC,\n bcSampleCount: this.bcSampleCount,\n bcStdDev: this.bcStdDev,\n bcMin: this.bcMin,\n bcMax: this.bcMax,\n };\n }\n /**\n * Validate timestamp alignment across a set of gaze samples.\n *\n * For each sample, computes: residual = (_receiveTime + offset_AC) - timestamp\n * using the internal _receiveTime property (raw WebSocket receive time) as an\n * independent measurement to cross-validate the sync offset.\n * If clocks are well-aligned, residuals should cluster tightly around the\n * one-way WebSocket latency (server→client).\n *\n * @param samples - Array of gaze samples (must have internal _receiveTime set)\n * @returns Alignment statistics, or null if sync is not established or no valid samples\n */\n validateTimestampAlignment(samples) {\n if (!this.isSynced()) {\n return null;\n }\n const offsetAB = this.timeSync.getOffset();\n const offsetAC = offsetAB - this.offsetBC;\n const residuals = [];\n for (const sample of samples) {\n const receiveTime = sample._receiveTime;\n if (receiveTime != null && sample.timestamp != null) {\n residuals.push(receiveTime + offsetAC - sample.timestamp);\n }\n }\n if (residuals.length === 0) {\n return null;\n }\n const n = residuals.length;\n const mean = residuals.reduce((a, b) => a + b, 0) / n;\n const variance = residuals.reduce((a, b) => a + (b - mean) ** 2, 0) / n;\n const stdDev = Math.sqrt(variance);\n const min = Math.min(...residuals);\n const max = Math.max(...residuals);\n return {\n sampleCount: n,\n meanResidual: mean,\n stdDev,\n min,\n max,\n };\n }\n /**\n * Reset sync state (e.g., after reconnection)\n */\n reset() {\n this.offsetBC = null;\n this.bcSampleCount = 0;\n this.bcStdDev = null;\n this.bcMin = null;\n this.bcMax = null;\n }\n}\n//# sourceMappingURL=device-time-sync.js.map","/**\n * Coordinate conversion utilities\n */\n/**\n * Convert normalized coordinates (0-1) to pixels\n */\nexport function normalizedToPixels(x, y) {\n const width = window.innerWidth;\n const height = window.innerHeight;\n return {\n x: Math.round(x * width),\n y: Math.round(y * height),\n };\n}\n/**\n * Convert pixel coordinates to normalized (0-1)\n */\nexport function pixelsToNormalized(x, y) {\n const width = window.innerWidth;\n const height = window.innerHeight;\n return {\n x: x / width,\n y: y / height,\n };\n}\n/**\n * Get screen dimensions\n */\nexport function getScreenDimensions() {\n return {\n width: window.innerWidth,\n height: window.innerHeight,\n };\n}\n/**\n * Calculate distance between two points\n */\nexport function distance(p1, p2) {\n const dx = p2.x - p1.x;\n const dy = p2.y - p1.y;\n return Math.sqrt(dx * dx + dy * dy);\n}\n/**\n * Convert window pixel coordinates to container-relative coordinates\n */\nexport function windowToContainer(x, y, container) {\n const rect = container.getBoundingClientRect();\n return {\n x: Math.round(x - rect.left),\n y: Math.round(y - rect.top),\n };\n}\n/**\n * Get container dimensions\n */\nexport function getContainerDimensions(container) {\n const rect = container.getBoundingClientRect();\n return {\n width: rect.width,\n height: rect.height,\n };\n}\n/**\n * Check if window coordinates fall within a container\n */\nexport function isWithinContainer(x, y, container) {\n const rect = container.getBoundingClientRect();\n return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;\n}\n//# sourceMappingURL=coordinate-utils.js.map","/**\n * Data export utilities\n */\n/**\n * Convert data to CSV format and download\n */\nexport function toCSV(data, filename) {\n if (data.length === 0) {\n console.warn('No data to export');\n return;\n }\n // Get all unique keys\n const keys = Array.from(new Set(data.flatMap((item) => Object.keys(flattenObject(item)))));\n // Create CSV header\n const header = keys.join(',');\n // Create CSV rows\n const rows = data.map((item) => {\n const flattened = flattenObject(item);\n return keys\n .map((key) => {\n const value = flattened[key];\n // Handle values that might contain commas\n if (typeof value === 'string' && value.includes(',')) {\n return `\"${value}\"`;\n }\n return value ?? '';\n })\n .join(',');\n });\n // Combine header and rows\n const csv = [header, ...rows].join('\\n');\n // Download\n downloadFile(csv, filename, 'text/csv');\n}\n/**\n * Convert data to JSON format and download\n */\nexport function toJSON(data, filename) {\n const json = JSON.stringify(data, null, 2);\n downloadFile(json, filename, 'application/json');\n}\n/**\n * Flatten nested object for CSV export\n */\nfunction flattenObject(obj, prefix = '') {\n const flattened = {};\n for (const [key, value] of Object.entries(obj)) {\n const newKey = prefix ? `${prefix}.${key}` : key;\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n Object.assign(flattened, flattenObject(value, newKey));\n }\n else if (Array.isArray(value)) {\n flattened[newKey] = JSON.stringify(value);\n }\n else {\n flattened[newKey] = value;\n }\n }\n return flattened;\n}\n/**\n * Download file helper\n */\nfunction downloadFile(content, filename, mimeType) {\n const blob = new Blob([content], { type: mimeType });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n setTimeout(() => URL.revokeObjectURL(url), 60000);\n}\n//# sourceMappingURL=data-export.js.map","/**\n * Data validation utilities\n */\n/**\n * Validate gaze data point\n */\nexport function validateGazeData(data) {\n if (typeof data !== 'object' || data === null)\n return false;\n const d = data;\n return (typeof d.x === 'number' &&\n typeof d.y === 'number' &&\n typeof d.timestamp === 'number' &&\n !isNaN(d.x) &&\n !isNaN(d.y) &&\n !isNaN(d.timestamp));\n}\n/**\n * Validate calibration point\n */\nexport function validateCalibrationPoint(point) {\n if (typeof point !== 'object' || point === null)\n return false;\n const p = point;\n return (typeof p.x === 'number' &&\n typeof p.y === 'number' &&\n p.x >= 0 &&\n p.x <= 1 &&\n p.y >= 0 &&\n p.y <= 1);\n}\n/**\n * Filter invalid gaze data\n */\nexport function filterValidGaze(data) {\n return data.filter(validateGazeData);\n}\n/**\n * Validate that a server response conforms to CalibrationResult\n */\nexport function validateCalibrationResult(data) {\n if (typeof data !== 'object' || data === null)\n return false;\n return typeof data.success === 'boolean';\n}\n/**\n * Validate that a server response conforms to ValidationResult\n */\nexport function validateValidationResult(data) {\n if (typeof data !== 'object' || data === null)\n return false;\n return typeof data.success === 'boolean';\n}\n//# sourceMappingURL=validation.js.map","/**\n * @title Tobii Extension\n * @description jsPsych extension for Tobii eye tracker integration via WebSocket.\n * Provides real-time gaze data streaming, calibration control, time synchronization,\n * and coordinate utilities for eye tracking experiments.\n * @version 1.0.0\n * @author jsPsych Team\n * @see {@link https://github.com/jspsych/jspsych-tobii/tree/main/packages/extension-tobii#readme Documentation}\n */\n\nimport { JsPsych, JsPsychExtension, JsPsychExtensionInfo, ParameterType } from 'jspsych';\nimport { version } from '../package.json';\n\nimport { WebSocketClient } from './websocket-client';\nimport { DataManager } from './data-manager';\nimport { TimeSync } from './time-sync';\nimport { DeviceTimeSync } from './device-time-sync';\nimport * as CoordinateUtils from './coordinate-utils';\nimport * as DataExport from './data-export';\nimport * as Validation from './validation';\n\nimport type {\n InitializeParameters,\n OnStartParameters,\n OnFinishParameters,\n GazeData,\n CalibrationPoint,\n CalibrationResult,\n ValidationResult,\n UserPositionData,\n ScreenDimensions,\n Coordinates,\n ConnectionStatus,\n DeviceTimeSyncStatus,\n TimestampAlignmentResult,\n} from './types';\n\nclass TobiiExtension implements JsPsychExtension {\n static info: JsPsychExtensionInfo = {\n name: 'tobii',\n version: version,\n data: {\n /** Eye tracking gaze data collected during the trial */\n tobii_data: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n },\n };\n\n private jsPsych: JsPsych;\n private ws!: WebSocketClient;\n private dataManager!: DataManager;\n private timeSync!: TimeSync;\n private deviceTimeSync!: DeviceTimeSync;\n private tracking: boolean = false;\n private config: InitializeParameters = {};\n private gazeSampleCount: number = 0;\n private deviceTimeSyncTriggered: boolean = false;\n\n constructor(jsPsych: JsPsych) {\n this.jsPsych = jsPsych;\n }\n\n initialize = async (params: InitializeParameters = {}): Promise<void> => {\n this.config = params;\n\n // Initialize WebSocket client\n this.ws = new WebSocketClient(params.connection);\n\n // Initialize data manager\n this.dataManager = new DataManager();\n\n // Initialize time synchronization\n this.timeSync = new TimeSync(this.ws);\n\n // Initialize device time synchronization (browser ↔ device clock chain)\n this.deviceTimeSync = new DeviceTimeSync(this.ws, this.timeSync);\n\n // Set up gaze data handler\n this.ws.on('gaze_data', (data) => {\n const rawGaze = data.gaze;\n if (rawGaze && Validation.validateGazeData(rawGaze)) {\n const receiveTime = (data._clientReceiveTime as number) ?? performance.now();\n const gazeWithTimestamps: GazeData = {\n ...rawGaze,\n browserTimestamp: this.deviceTimeSync.isSynced()\n ? this.deviceTimeSync.toLocalTime(rawGaze.timestamp as number)\n : receiveTime,\n };\n // Store raw receive time for validateTimestampAlignment cross-check\n (gazeWithTimestamps as unknown as Record<string, unknown>)._receiveTime = receiveTime;\n this.dataManager.addGazeData(gazeWithTimestamps);\n\n // Auto-trigger device time sync after first 50 gaze samples\n this.gazeSampleCount++;\n if (!this.deviceTimeSyncTriggered && this.gazeSampleCount >= 50) {\n this.deviceTimeSyncTriggered = true;\n this.deviceTimeSync.synchronizeDeviceClock().catch((e) => {\n console.warn('Tobii: Device time sync failed, can be retried manually:', e);\n });\n }\n }\n });\n\n // Set up reconnection handler to re-sync time\n this.ws.on('reconnected', async () => {\n try {\n await this.timeSync.synchronize();\n } catch (e) {\n console.warn('Tobii: Time sync failed after reconnection:', e);\n }\n // Reset device time sync so it re-triggers once new samples arrive\n this.deviceTimeSync.reset();\n this.gazeSampleCount = 0;\n this.deviceTimeSyncTriggered = false;\n });\n\n // Auto-connect if configured\n if (params.connection?.autoConnect) {\n await this.connect();\n }\n };\n\n on_start = async (_params: OnStartParameters = {}): Promise<void> => {\n // Mark trial start\n this.dataManager.startTrial();\n\n await this.startTracking();\n };\n\n on_load = async (): Promise<void> => {\n // Optional: additional setup when trial loads\n };\n\n on_finish = async (_params: OnFinishParameters = {}): Promise<{ tobii_data: GazeData[] }> => {\n // Mark trial end\n this.dataManager.endTrial();\n\n // Get trial data\n const trialData = this.dataManager.getTrialData();\n\n // Clear old data to prevent memory buildup\n this.dataManager.clearOldData();\n\n return {\n tobii_data: trialData,\n };\n };\n\n // ==========================================\n // PUBLIC API METHODS\n // These are accessible via jsPsych.extensions.tobii.*\n // ==========================================\n\n /**\n * Connect to the WebSocket server\n */\n async connect(): Promise<void> {\n await this.ws.connect();\n await this.timeSync.synchronize();\n }\n\n /**\n * Disconnect from the WebSocket server\n */\n async disconnect(): Promise<void> {\n if (this.tracking) {\n await this.stopTracking();\n }\n await this.ws.disconnect();\n }\n\n /**\n * Check if connected to server\n */\n isConnected(): boolean {\n return this.ws.isConnected();\n }\n\n /**\n * Get connection status details\n */\n getConnectionStatus(): ConnectionStatus {\n return this.ws.getStatus();\n }\n\n /**\n * Start eye tracking data collection\n */\n async startTracking(): Promise<void> {\n if (this.tracking) {\n return;\n }\n\n if (!this.isConnected()) {\n throw new Error('Not connected to server. Call connect() first.');\n }\n\n // Wait for server confirmation before setting state\n const response = await this.ws.sendAndWait({ type: 'start_tracking' });\n if (response.success) {\n this.tracking = true;\n } else {\n throw new Error(`Server failed to start tracking: ${response.error || 'unknown error'}`);\n }\n }\n\n /**\n * Stop eye tracking data collection\n */\n async stopTracking(): Promise<void> {\n try {\n await this.ws.sendAndWait({ type: 'stop_tracking' });\n } finally {\n this.tracking = false;\n }\n }\n\n /**\n * Check if currently tracking\n */\n isTracking(): boolean {\n return this.tracking;\n }\n\n /**\n * Start calibration procedure\n */\n async startCalibration(): Promise<void> {\n await this.ws.send({ type: 'calibration_start' });\n }\n\n /**\n * Collect calibration data for a specific point\n * @returns Promise resolving to success status when SDK finishes collecting\n */\n async collectCalibrationPoint(x: number, y: number): Promise<{ success: boolean }> {\n if (!Validation.validateCalibrationPoint({ x, y })) {\n throw new Error(\n `Invalid calibration point (${x}, ${y}). Coordinates must be in range [0, 1].`\n );\n }\n\n const response = await this.ws.sendAndWait({\n type: 'calibration_point',\n point: { x, y },\n timestamp: performance.now(),\n });\n\n return { success: response.success === true };\n }\n\n /**\n * Compute calibration from collected points\n */\n async computeCalibration(): Promise<CalibrationResult> {\n const response = await this.ws.sendAndWait({\n type: 'calibration_compute',\n });\n if (!Validation.validateCalibrationResult(response)) {\n return { success: false, error: 'Invalid server response' };\n }\n return response;\n }\n\n /**\n * Get calibration data/quality metrics\n */\n async getCalibrationData(): Promise<CalibrationResult> {\n const response = await this.ws.sendAndWait({\n type: 'get_calibration_data',\n });\n if (!Validation.validateCalibrationResult(response)) {\n return { success: false, error: 'Invalid server response' };\n }\n return response;\n }\n\n /**\n * Start validation procedure\n */\n async startValidation(): Promise<void> {\n await this.ws.send({ type: 'validation_start' });\n }\n\n /**\n * Collect validation data for a specific point\n * @param x - Normalized x coordinate (0-1)\n * @param y - Normalized y coordinate (0-1)\n * @param gazeSamples - Optional array of gaze samples collected at this point\n */\n async collectValidationPoint(x: number, y: number, gazeSamples?: GazeData[]): Promise<void> {\n if (!Validation.validateCalibrationPoint({ x, y })) {\n throw new Error(\n `Invalid validation point (${x}, ${y}). Coordinates must be in range [0, 1].`\n );\n }\n\n await this.ws.send({\n type: 'validation_point',\n point: { x, y },\n timestamp: performance.now(),\n gaze_samples: gazeSamples || [],\n });\n }\n\n /**\n * Get recent gaze data from the data manager buffer\n * @param durationMs - How many milliseconds of recent data to retrieve\n */\n getRecentGazeData(durationMs: number): GazeData[] {\n return this.dataManager.getRecentData(durationMs);\n }\n\n /**\n * Compute validation from collected points\n */\n async computeValidation(): Promise<ValidationResult> {\n const response = await this.ws.sendAndWait({\n type: 'validation_compute',\n });\n if (!Validation.validateValidationResult(response)) {\n return { success: false, error: 'Invalid server response' };\n }\n return response;\n }\n\n /**\n * Get current gaze position\n */\n async getCurrentGaze(): Promise<GazeData | null> {\n // Try to get from local buffer first\n const localGaze = this.dataManager.getCurrentGaze();\n if (localGaze) {\n return localGaze;\n }\n\n // Otherwise request from server\n const response = await this.ws.sendAndWait({\n type: 'get_current_gaze',\n });\n return (response.gaze as GazeData) || null;\n }\n\n /**\n * Get current user position (head position)\n */\n async getUserPosition(): Promise<UserPositionData | null> {\n if (!this.isConnected()) {\n return null;\n }\n const response = await this.ws.sendAndWait({\n type: 'get_user_position',\n });\n return (response.position as UserPositionData) || null;\n }\n\n /**\n * Get gaze data for a specific time range\n */\n async getGazeData(startTime: number, endTime: number): Promise<GazeData[]> {\n // Get from local buffer\n const localData = this.dataManager.getDataRange(startTime, endTime);\n\n // Filter valid gaze points\n return Validation.filterValidGaze(localData);\n }\n\n /**\n * Clear stored gaze data\n */\n clearGazeData(): void {\n this.dataManager.clear();\n }\n\n /**\n * Convert normalized coordinates (0-1) to pixels\n */\n normalizedToPixels(x: number, y: number): Coordinates {\n return CoordinateUtils.normalizedToPixels(x, y);\n }\n\n /**\n * Convert pixel coordinates to normalized (0-1)\n */\n pixelsToNormalized(x: number, y: number): Coordinates {\n return CoordinateUtils.pixelsToNormalized(x, y);\n }\n\n /**\n * Get screen dimensions\n */\n getScreenDimensions(): ScreenDimensions {\n return CoordinateUtils.getScreenDimensions();\n }\n\n /**\n * Calculate distance between two points\n */\n calculateDistance(p1: Coordinates, p2: Coordinates): number {\n return CoordinateUtils.distance(p1, p2);\n }\n\n /**\n * Convert window pixel coordinates to container-relative coordinates\n * @param x - X coordinate in window pixels\n * @param y - Y coordinate in window pixels\n * @param container - Optional container element (defaults to jsPsych display element)\n */\n windowToContainer(x: number, y: number, container?: HTMLElement): Coordinates {\n const el = container || this.jsPsych.getDisplayElement();\n return CoordinateUtils.windowToContainer(x, y, el);\n }\n\n /**\n * Get container dimensions\n * @param container - Optional container element (defaults to jsPsych display element)\n */\n getContainerDimensions(container?: HTMLElement): ScreenDimensions {\n const el = container || this.jsPsych.getDisplayElement();\n return CoordinateUtils.getContainerDimensions(el);\n }\n\n /**\n * Check if window coordinates fall within a container\n * @param x - X coordinate in window pixels\n * @param y - Y coordinate in window pixels\n * @param container - Optional container element (defaults to jsPsych display element)\n */\n isWithinContainer(x: number, y: number, container?: HTMLElement): boolean {\n const el = container || this.jsPsych.getDisplayElement();\n return CoordinateUtils.isWithinContainer(x, y, el);\n }\n\n /**\n * Export gaze data to CSV\n */\n exportToCSV(data: Record<string, unknown>[], filename: string): void {\n DataExport.toCSV(data, filename);\n }\n\n /**\n * Export gaze data to JSON\n */\n exportToJSON(data: Record<string, unknown>[], filename: string): void {\n DataExport.toJSON(data, filename);\n }\n\n /**\n * Set extension configuration\n */\n setConfig(config: Partial<InitializeParameters>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Get current configuration\n */\n getConfig(): InitializeParameters {\n return { ...this.config };\n }\n\n /**\n * Get time synchronization offset\n */\n getTimeOffset(): number {\n return this.timeSync.getOffset();\n }\n\n /**\n * Check if time is synchronized\n */\n isTimeSynced(): boolean {\n return this.timeSync.isSynced();\n }\n\n /**\n * Convert a performance.now() timestamp to Tobii device clock time.\n * Requires that device time sync is established.\n */\n toDeviceTime(performanceNow: number): number {\n return this.deviceTimeSync.toDeviceTime(performanceNow);\n }\n\n /**\n * Convert a Tobii device clock timestamp to performance.now() domain.\n * Requires that device time sync is established.\n */\n toLocalTime(deviceTime: number): number {\n return this.deviceTimeSync.toLocalTime(deviceTime);\n }\n\n /**\n * Check if the browser-to-device time sync chain is established\n */\n isDeviceTimeSynced(): boolean {\n return this.deviceTimeSync.isSynced();\n }\n\n /**\n * Get full device time synchronization status with all offsets and diagnostics\n */\n getTimeSyncStatus(): DeviceTimeSyncStatus {\n return this.deviceTimeSync.getStatus();\n }\n\n /**\n * Validate timestamp alignment across a set of gaze samples.\n * Computes per-sample residuals to verify the A↔C offset is consistent.\n * Low stdDev indicates well-aligned timestamps.\n * @param samples - Gaze samples to validate (uses internal _receiveTime for cross-check)\n */\n validateTimestampAlignment(samples: GazeData[]): TimestampAlignmentResult | null {\n return this.deviceTimeSync.validateTimestampAlignment(samples);\n }\n}\n\nexport default TobiiExtension;\n\n// Export types for use by plugins and experiments\nexport type {\n InitializeParameters,\n OnStartParameters,\n OnFinishParameters,\n GazeData,\n CalibrationPoint,\n CalibrationResult,\n ValidationResult,\n UserPositionData,\n ScreenDimensions,\n Coordinates,\n ConnectionStatus,\n DeviceTimeSyncStatus,\n TimestampAlignmentResult,\n};\n"],"names":["Validation.validateGazeData","ParameterType","Validation.validateCalibrationPoint","Validation.validateCalibrationResult","Validation.validateValidationResult","Validation.filterValidGaze","CoordinateUtils.normalizedToPixels","CoordinateUtils.pixelsToNormalized","CoordinateUtils.getScreenDimensions","CoordinateUtils.distance","CoordinateUtils.windowToContainer","CoordinateUtils.getContainerDimensions","CoordinateUtils.isWithinContainer","DataExport.toCSV","DataExport.toJSON"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- var jsPsychExtensionTobii=function(x){"use strict";var O="0.1.1",P=Object.defineProperty,M=Object.defineProperties,B=Object.getOwnPropertyDescriptors,g=Object.getOwnPropertySymbols,k=Object.prototype.hasOwnProperty,j=Object.prototype.propertyIsEnumerable,p=(i,t,e)=>t in i?P(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,w=(i,t)=>{for(var e in t||(t={}))k.call(t,e)&&p(i,e,t[e]);if(g)for(var e of g(t))j.call(t,e)&&p(i,e,t[e]);return i},A=(i,t)=>M(i,B(t)),f=(i,t,e)=>new Promise((n,s)=>{var r=a=>{try{o(e.next(a))}catch(c){s(c)}},l=a=>{try{o(e.throw(a))}catch(c){s(c)}},o=a=>a.done?n(a.value):Promise.resolve(a.value).then(r,l);o((e=e.apply(i,t)).next())});class _{constructor(t={}){var e,n,s;this.ws=null,this.reconnectTimeout=null,this.currentReconnectAttempt=0,this.nextRequestId=0,this.config={url:t.url||"ws://localhost:8080",autoConnect:(e=t.autoConnect)!=null?e:!0,reconnectAttempts:(n=t.reconnectAttempts)!=null?n:5,reconnectDelay:(s=t.reconnectDelay)!=null?s:1e3},this.status={connected:!1,tracking:!1},this.messageHandlers=new Map}connect(){return f(this,null,function*(){var t;if(((t=this.ws)==null?void 0:t.readyState)!==WebSocket.OPEN)return new Promise((e,n)=>{try{this.ws=new WebSocket(this.config.url);const s=setTimeout(()=>{var r;((r=this.ws)==null?void 0:r.readyState)!==WebSocket.OPEN&&n(new Error(`Connection timeout (${this.config.url})`))},5e3);this.ws.onopen=()=>{clearTimeout(s),this.status.connected=!0,this.status.connectedAt=Date.now(),this.currentReconnectAttempt=0,e()},this.ws.onmessage=r=>{this.handleMessage(r)},this.ws.onerror=r=>{this.status.lastError="WebSocket error",console.error("WebSocket error:",r)},this.ws.onclose=()=>{this.status.connected=!1,this.status.tracking=!1,this.handleDisconnect()}}catch(s){n(s)}})})}disconnect(){return f(this,null,function*(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(),this.ws=null),this.status.connected=!1,this.status.tracking=!1})}isConnected(){var t;return((t=this.ws)==null?void 0:t.readyState)===WebSocket.OPEN}getStatus(){return w({},this.status)}send(t){return f(this,null,function*(){if(!this.isConnected())throw new Error("Not connected to server");this.ws.send(JSON.stringify(t))})}sendAndWait(t,e=5e3){return f(this,null,function*(){if(!this.isConnected())throw new Error("Not connected to server");return new Promise((n,s)=>{const r=`req_${this.nextRequestId++}`,l=A(w({},t),{requestId:r}),o=setTimeout(()=>{this.messageHandlers.delete(r),s(new Error("Request timeout"))},e);this.messageHandlers.set(r,a=>{clearTimeout(o),this.messageHandlers.delete(r),n(a)}),this.ws.send(JSON.stringify(l))})})}on(t,e){this.messageHandlers.has(t)&&console.warn(`Tobii WebSocket: Overwriting existing handler for message type "${t}"`),this.messageHandlers.set(t,e)}off(t){this.messageHandlers.delete(t)}handleMessage(t){try{const e=performance.now(),n=JSON.parse(t.data);if(n._clientReceiveTime=e,n.requestId&&this.messageHandlers.has(n.requestId)){this.messageHandlers.get(n.requestId)(n);return}n.type&&this.messageHandlers.has(n.type)&&this.messageHandlers.get(n.type)(n)}catch(e){console.error("Error handling message:",e)}}handleDisconnect(){if(this.currentReconnectAttempt<this.config.reconnectAttempts){this.currentReconnectAttempt++;const t=this.config.reconnectDelay*this.currentReconnectAttempt;this.reconnectTimeout=window.setTimeout(()=>f(this,null,function*(){try{yield this.connect();const e=this.messageHandlers.get("reconnected");e&&e({type:"reconnected"})}catch(e){console.warn(`Tobii: Reconnection attempt ${this.currentReconnectAttempt}/${this.config.reconnectAttempts} failed:`,e)}}),t)}else this.status.lastError="Max reconnection attempts reached"}}class E{constructor(t=7200){this.gazeBuffer=[],this.trialStartTime=null,this.trialEndTime=null,this.maxBufferSize=t}addGazeData(t){this.gazeBuffer.push(t),this.gazeBuffer.length>this.maxBufferSize&&(this.gazeBuffer=this.gazeBuffer.slice(-this.maxBufferSize))}startTrial(){this.trialStartTime=performance.now()}endTrial(){this.trialEndTime=performance.now()}getTrialData(){if(this.trialStartTime===null)return[];const t=this.trialEndTime||performance.now();return this.gazeBuffer.filter(e=>{var n;const s=(n=e.browserTimestamp)!=null?n:e.timestamp;return s>=this.trialStartTime&&s<=t})}getDataRange(t,e){return this.gazeBuffer.filter(n=>{var s;const r=(s=n.browserTimestamp)!=null?s:n.timestamp;return r>=t&&r<=e})}getCurrentGaze(){return this.gazeBuffer.length===0?null:this.gazeBuffer[this.gazeBuffer.length-1]}clear(){this.gazeBuffer=[],this.trialStartTime=null,this.trialEndTime=null}clearOldData(t=6e4){const e=performance.now()-t;this.gazeBuffer=this.gazeBuffer.filter(n=>{var s;return((s=n.browserTimestamp)!=null?s:n.timestamp)>=e})}getBufferSize(){return this.gazeBuffer.length}getRecentData(t){const e=performance.now()-t;return this.gazeBuffer.filter(n=>{var s;return((s=n.browserTimestamp)!=null?s:n.timestamp)>=e})}}var R=(i,t,e)=>new Promise((n,s)=>{var r=a=>{try{o(e.next(a))}catch(c){s(c)}},l=a=>{try{o(e.throw(a))}catch(c){s(c)}},o=a=>a.done?n(a.value):Promise.resolve(a.value).then(r,l);o((e=e.apply(i,t)).next())});class W{constructor(t){this.ws=t,this.offset=0,this.synced=!1}synchronize(){return R(this,null,function*(){const t=[];for(let e=0;e<10;e++){const n=performance.now(),s=yield this.ws.sendAndWait({type:"time_sync",clientTime:n}),r=performance.now()-n,l=s.serverTime,o=r/2,a=l-(n+o);t.push(a),yield this.delay(100)}this.offset=this.median(t),this.synced=!0})}toServerTime(t){return t+this.offset}toLocalTime(t){return t-this.offset}isSynced(){return this.synced}getOffset(){return this.offset}median(t){const e=[...t].sort((s,r)=>s-r),n=Math.floor(e.length/2);return e.length%2===0?(e[n-1]+e[n])/2:e[n]}delay(t){return new Promise(e=>setTimeout(e,t))}}var $=Math.pow,N=(i,t,e)=>new Promise((n,s)=>{var r=a=>{try{o(e.next(a))}catch(c){s(c)}},l=a=>{try{o(e.throw(a))}catch(c){s(c)}},o=a=>a.done?n(a.value):Promise.resolve(a.value).then(r,l);o((e=e.apply(i,t)).next())});class H{constructor(t,e){this.ws=t,this.timeSync=e,this.offsetBC=null,this.bcSampleCount=0,this.bcStdDev=null,this.bcMin=null,this.bcMax=null}synchronizeDeviceClock(){return N(this,null,function*(){if(!this.timeSync.isSynced())return!1;try{const t=yield this.ws.sendAndWait({type:"get_device_clock_offset"});return t.success?(this.offsetBC=t.offset,this.bcSampleCount=t.sample_count,this.bcStdDev=t.std_dev,this.bcMin=t.min,this.bcMax=t.max,!0):!1}catch(t){return!1}})}isSynced(){return this.timeSync.isSynced()&&this.offsetBC!==null}toDeviceTime(t){if(!this.isSynced())throw new Error("Device time sync not established. Call synchronizeDeviceClock() first.");const e=this.timeSync.getOffset();return t+e-this.offsetBC}toLocalTime(t){if(!this.isSynced())throw new Error("Device time sync not established. Call synchronizeDeviceClock() first.");const e=this.timeSync.getOffset();return t-e+this.offsetBC}getStatus(){const t=this.timeSync.getOffset(),e=this.offsetBC!==null?t-this.offsetBC:null;return{synced:this.isSynced(),offsetAB:t,offsetBC:this.offsetBC,offsetAC:e,bcSampleCount:this.bcSampleCount,bcStdDev:this.bcStdDev,bcMin:this.bcMin,bcMax:this.bcMax}}validateTimestampAlignment(t){if(!this.isSynced())return null;const e=this.timeSync.getOffset()-this.offsetBC,n=[];for(const h of t){const d=h._receiveTime;d!=null&&h.timestamp!=null&&n.push(d+e-h.timestamp)}if(n.length===0)return null;const s=n.length,r=n.reduce((h,d)=>h+d,0)/s,l=n.reduce((h,d)=>h+$(d-r,2),0)/s,o=Math.sqrt(l),a=Math.min(...n),c=Math.max(...n);return{sampleCount:s,meanResidual:r,stdDev:o,min:a,max:c}}reset(){this.offsetBC=null,this.bcSampleCount=0,this.bcStdDev=null,this.bcMin=null,this.bcMax=null}}function I(i,t){const e=window.innerWidth,n=window.innerHeight;return{x:Math.round(i*e),y:Math.round(t*n)}}function q(i,t){const e=window.innerWidth,n=window.innerHeight;return{x:i/e,y:t/n}}function G(){return{width:window.innerWidth,height:window.innerHeight}}function L(i,t){const e=t.x-i.x,n=t.y-i.y;return Math.sqrt(e*e+n*n)}function J(i,t,e){const n=e.getBoundingClientRect();return{x:Math.round(i-n.left),y:Math.round(t-n.top)}}function V(i){const t=i.getBoundingClientRect();return{width:t.width,height:t.height}}function U(i,t,e){const n=e.getBoundingClientRect();return i>=n.left&&i<=n.right&&t>=n.top&&t<=n.bottom}function X(i,t){if(i.length===0){console.warn("No data to export");return}const e=Array.from(new Set(i.flatMap(l=>Object.keys(y(l))))),n=e.join(","),s=i.map(l=>{const o=y(l);return e.map(a=>{const c=o[a];return typeof c=="string"&&c.includes(",")?`"${c}"`:c!=null?c:""}).join(",")}),r=[n,...s].join(`
2
- `);v(r,t,"text/csv")}function F(i,t){const e=JSON.stringify(i,null,2);v(e,t,"application/json")}function y(i,t=""){const e={};for(const[n,s]of Object.entries(i)){const r=t?`${t}.${n}`:n;s!==null&&typeof s=="object"&&!Array.isArray(s)?Object.assign(e,y(s,r)):Array.isArray(s)?e[r]=JSON.stringify(s):e[r]=s}return e}function v(i,t,e){const n=new Blob([i],{type:e}),s=URL.createObjectURL(n),r=document.createElement("a");r.href=s,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),setTimeout(()=>URL.revokeObjectURL(s),6e4)}function b(i){if(typeof i!="object"||i===null)return!1;const t=i;return typeof t.x=="number"&&typeof t.y=="number"&&typeof t.timestamp=="number"&&!isNaN(t.x)&&!isNaN(t.y)&&!isNaN(t.timestamp)}function S(i){if(typeof i!="object"||i===null)return!1;const t=i;return typeof t.x=="number"&&typeof t.y=="number"&&t.x>=0&&t.x<=1&&t.y>=0&&t.y<=1}function K(i){return i.filter(b)}function T(i){return typeof i!="object"||i===null?!1:typeof i.success=="boolean"}function Q(i){return typeof i!="object"||i===null?!1:typeof i.success=="boolean"}var Y=Object.defineProperty,Z=Object.defineProperties,tt=Object.getOwnPropertyDescriptors,C=Object.getOwnPropertySymbols,et=Object.prototype.hasOwnProperty,nt=Object.prototype.propertyIsEnumerable,z=(i,t,e)=>t in i?Y(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,m=(i,t)=>{for(var e in t||(t={}))et.call(t,e)&&z(i,e,t[e]);if(C)for(var e of C(t))nt.call(t,e)&&z(i,e,t[e]);return i},it=(i,t)=>Z(i,tt(t)),u=(i,t,e)=>new Promise((n,s)=>{var r=a=>{try{o(e.next(a))}catch(c){s(c)}},l=a=>{try{o(e.throw(a))}catch(c){s(c)}},o=a=>a.done?n(a.value):Promise.resolve(a.value).then(r,l);o((e=e.apply(i,t)).next())});class D{constructor(t){this.tracking=!1,this.config={},this.gazeSampleCount=0,this.deviceTimeSyncTriggered=!1,this.initialize=(...e)=>u(this,[...e],function*(n={}){var s;this.config=n,this.ws=new _(n.connection),this.dataManager=new E,this.timeSync=new W(this.ws),this.deviceTimeSync=new H(this.ws,this.timeSync),this.ws.on("gaze_data",r=>{var l;const o=r.gaze;if(o&&b(o)){const a=(l=r._clientReceiveTime)!=null?l:performance.now(),c=it(m({},o),{browserTimestamp:this.deviceTimeSync.isSynced()?this.deviceTimeSync.toLocalTime(o.timestamp):a});c._receiveTime=a,this.dataManager.addGazeData(c),this.gazeSampleCount++,!this.deviceTimeSyncTriggered&&this.gazeSampleCount>=50&&(this.deviceTimeSyncTriggered=!0,this.deviceTimeSync.synchronizeDeviceClock().catch(h=>{console.warn("Tobii: Device time sync failed, can be retried manually:",h)}))}}),this.ws.on("reconnected",()=>u(this,null,function*(){try{yield this.timeSync.synchronize()}catch(r){console.warn("Tobii: Time sync failed after reconnection:",r)}this.deviceTimeSync.reset(),this.gazeSampleCount=0,this.deviceTimeSyncTriggered=!1})),(s=n.connection)!=null&&s.autoConnect&&(yield this.connect())}),this.on_start=(...e)=>u(this,[...e],function*(n={}){this.dataManager.startTrial(),this.tracking||(yield this.startTracking())}),this.on_load=()=>u(this,null,function*(){}),this.on_finish=(...e)=>u(this,[...e],function*(n={}){this.dataManager.endTrial();const s=this.dataManager.getTrialData();return this.dataManager.clearOldData(),{tobii_data:s}}),this.jsPsych=t}connect(){return u(this,null,function*(){yield this.ws.connect(),yield this.timeSync.synchronize()})}disconnect(){return u(this,null,function*(){this.tracking&&(yield this.stopTracking()),yield this.ws.disconnect()})}isConnected(){return this.ws.isConnected()}getConnectionStatus(){return this.ws.getStatus()}startTracking(){return u(this,null,function*(){if(!this.isConnected())throw new Error("Not connected to server. Call connect() first.");const t=yield this.ws.sendAndWait({type:"start_tracking"});if(t.success)this.tracking=!0;else throw new Error(`Server failed to start tracking: ${t.error||"unknown error"}`)})}stopTracking(){return u(this,null,function*(){try{yield this.ws.sendAndWait({type:"stop_tracking"})}finally{this.tracking=!1}})}isTracking(){return this.tracking}startCalibration(){return u(this,null,function*(){yield this.ws.send({type:"calibration_start"})})}collectCalibrationPoint(t,e){return u(this,null,function*(){if(!S({x:t,y:e}))throw new Error(`Invalid calibration point (${t}, ${e}). Coordinates must be in range [0, 1].`);return{success:(yield this.ws.sendAndWait({type:"calibration_point",point:{x:t,y:e},timestamp:performance.now()})).success===!0}})}computeCalibration(){return u(this,null,function*(){const t=yield this.ws.sendAndWait({type:"calibration_compute"});return T(t)?t:{success:!1,error:"Invalid server response"}})}getCalibrationData(){return u(this,null,function*(){const t=yield this.ws.sendAndWait({type:"get_calibration_data"});return T(t)?t:{success:!1,error:"Invalid server response"}})}startValidation(){return u(this,null,function*(){yield this.ws.send({type:"validation_start"})})}collectValidationPoint(t,e,n){return u(this,null,function*(){if(!S({x:t,y:e}))throw new Error(`Invalid validation point (${t}, ${e}). Coordinates must be in range [0, 1].`);yield this.ws.send({type:"validation_point",point:{x:t,y:e},timestamp:performance.now(),gaze_samples:n||[]})})}getRecentGazeData(t){return this.dataManager.getRecentData(t)}computeValidation(){return u(this,null,function*(){const t=yield this.ws.sendAndWait({type:"validation_compute"});return Q(t)?t:{success:!1,error:"Invalid server response"}})}getCurrentGaze(){return u(this,null,function*(){return this.dataManager.getCurrentGaze()||(yield this.ws.sendAndWait({type:"get_current_gaze"})).gaze||null})}getUserPosition(){return u(this,null,function*(){return this.isConnected()&&(yield this.ws.sendAndWait({type:"get_user_position"})).position||null})}getGazeData(t,e){return u(this,null,function*(){const n=this.dataManager.getDataRange(t,e);return K(n)})}clearGazeData(){this.dataManager.clear()}normalizedToPixels(t,e){return I(t,e)}pixelsToNormalized(t,e){return q(t,e)}getScreenDimensions(){return G()}calculateDistance(t,e){return L(t,e)}windowToContainer(t,e,n){const s=n||this.jsPsych.getDisplayElement();return J(t,e,s)}getContainerDimensions(t){const e=t||this.jsPsych.getDisplayElement();return V(e)}isWithinContainer(t,e,n){const s=n||this.jsPsych.getDisplayElement();return U(t,e,s)}exportToCSV(t,e){X(t,e)}exportToJSON(t,e){F(t,e)}setConfig(t){this.config=m(m({},this.config),t)}getConfig(){return m({},this.config)}getTimeOffset(){return this.timeSync.getOffset()}isTimeSynced(){return this.timeSync.isSynced()}toDeviceTime(t){return this.deviceTimeSync.toDeviceTime(t)}toLocalTime(t){return this.deviceTimeSync.toLocalTime(t)}isDeviceTimeSynced(){return this.deviceTimeSync.isSynced()}getTimeSyncStatus(){return this.deviceTimeSync.getStatus()}validateTimestampAlignment(t){return this.deviceTimeSync.validateTimestampAlignment(t)}}return D.info={name:"tobii",version:O,data:{tobii_data:{type:x.ParameterType.COMPLEX,array:!0}}},D}(jsPsychModule);
3
- //# sourceMappingURL=https://unpkg.com/@jspsych/extension-tobii@0.1.1/dist/index.browser.min.js.map
1
+ var jsPsychExtensionTobii=function(x){"use strict";var O="0.2.1",P=Object.defineProperty,M=Object.defineProperties,B=Object.getOwnPropertyDescriptors,g=Object.getOwnPropertySymbols,k=Object.prototype.hasOwnProperty,j=Object.prototype.propertyIsEnumerable,p=(i,t,e)=>t in i?P(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,w=(i,t)=>{for(var e in t||(t={}))k.call(t,e)&&p(i,e,t[e]);if(g)for(var e of g(t))j.call(t,e)&&p(i,e,t[e]);return i},A=(i,t)=>M(i,B(t)),f=(i,t,e)=>new Promise((n,s)=>{var r=o=>{try{a(e.next(o))}catch(c){s(c)}},l=o=>{try{a(e.throw(o))}catch(c){s(c)}},a=o=>o.done?n(o.value):Promise.resolve(o.value).then(r,l);a((e=e.apply(i,t)).next())});class _{constructor(t={}){var e,n,s;this.ws=null,this.reconnectTimeout=null,this.currentReconnectAttempt=0,this.nextRequestId=0,this.config={url:t.url||"ws://localhost:8080",autoConnect:(e=t.autoConnect)!=null?e:!0,reconnectAttempts:(n=t.reconnectAttempts)!=null?n:5,reconnectDelay:(s=t.reconnectDelay)!=null?s:1e3},this.status={connected:!1,tracking:!1},this.messageHandlers=new Map}connect(){return f(this,null,function*(){var t;if(((t=this.ws)==null?void 0:t.readyState)!==WebSocket.OPEN)return new Promise((e,n)=>{try{this.ws=new WebSocket(this.config.url);const s=setTimeout(()=>{var r;((r=this.ws)==null?void 0:r.readyState)!==WebSocket.OPEN&&n(new Error(`Connection timeout (${this.config.url})`))},5e3);this.ws.onopen=()=>{clearTimeout(s),this.status.connected=!0,this.status.connectedAt=Date.now(),this.currentReconnectAttempt=0,e()},this.ws.onmessage=r=>{this.handleMessage(r)},this.ws.onerror=r=>{this.status.lastError="WebSocket error",console.error("WebSocket error:",r)},this.ws.onclose=()=>{this.status.connected=!1,this.status.tracking=!1,this.handleDisconnect()}}catch(s){n(s)}})})}disconnect(){return f(this,null,function*(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.ws&&(this.ws.close(),this.ws=null),this.status.connected=!1,this.status.tracking=!1})}isConnected(){var t;return((t=this.ws)==null?void 0:t.readyState)===WebSocket.OPEN}getStatus(){return w({},this.status)}send(t){return f(this,null,function*(){if(!this.isConnected())throw new Error("Not connected to server");this.ws.send(JSON.stringify(t))})}sendAndWait(t,e=5e3){return f(this,null,function*(){if(!this.isConnected())throw new Error("Not connected to server");return new Promise((n,s)=>{const r=`req_${this.nextRequestId++}`,l=A(w({},t),{requestId:r}),a=setTimeout(()=>{this.messageHandlers.delete(r),s(new Error("Request timeout"))},e);this.messageHandlers.set(r,o=>{clearTimeout(a),this.messageHandlers.delete(r),n(o)}),this.ws.send(JSON.stringify(l))})})}on(t,e){this.messageHandlers.has(t)&&console.warn(`Tobii WebSocket: Overwriting existing handler for message type "${t}"`),this.messageHandlers.set(t,e)}off(t){this.messageHandlers.delete(t)}handleMessage(t){try{const e=performance.now(),n=JSON.parse(t.data);if(n._clientReceiveTime=e,n.requestId&&this.messageHandlers.has(n.requestId)){this.messageHandlers.get(n.requestId)(n);return}n.type&&this.messageHandlers.has(n.type)&&this.messageHandlers.get(n.type)(n)}catch(e){console.error("Error handling message:",e)}}handleDisconnect(){if(this.currentReconnectAttempt<this.config.reconnectAttempts){this.currentReconnectAttempt++;const t=this.config.reconnectDelay*this.currentReconnectAttempt;this.reconnectTimeout=window.setTimeout(()=>f(this,null,function*(){try{yield this.connect();const e=this.messageHandlers.get("reconnected");e&&e({type:"reconnected"})}catch(e){console.warn(`Tobii: Reconnection attempt ${this.currentReconnectAttempt}/${this.config.reconnectAttempts} failed:`,e)}}),t)}else this.status.lastError="Max reconnection attempts reached"}}class E{constructor(t=7200){this.gazeBuffer=[],this.trialStartTime=null,this.trialEndTime=null,this.maxBufferSize=t}addGazeData(t){this.gazeBuffer.push(t),this.gazeBuffer.length>this.maxBufferSize&&(this.gazeBuffer=this.gazeBuffer.slice(-this.maxBufferSize))}startTrial(){this.trialStartTime=performance.now()}endTrial(){this.trialEndTime=performance.now()}getTrialData(){if(this.trialStartTime===null)return[];const t=this.trialEndTime||performance.now();return this.gazeBuffer.filter(e=>{var n;const s=(n=e.browserTimestamp)!=null?n:e.timestamp;return s>=this.trialStartTime&&s<=t})}getDataRange(t,e){return this.gazeBuffer.filter(n=>{var s;const r=(s=n.browserTimestamp)!=null?s:n.timestamp;return r>=t&&r<=e})}getCurrentGaze(){return this.gazeBuffer.length===0?null:this.gazeBuffer[this.gazeBuffer.length-1]}clear(){this.gazeBuffer=[],this.trialStartTime=null,this.trialEndTime=null}clearOldData(t=6e4){const e=performance.now()-t;this.gazeBuffer=this.gazeBuffer.filter(n=>{var s;return((s=n.browserTimestamp)!=null?s:n.timestamp)>=e})}getBufferSize(){return this.gazeBuffer.length}getRecentData(t){const e=performance.now()-t;return this.gazeBuffer.filter(n=>{var s;return((s=n.browserTimestamp)!=null?s:n.timestamp)>=e})}}var R=(i,t,e)=>new Promise((n,s)=>{var r=o=>{try{a(e.next(o))}catch(c){s(c)}},l=o=>{try{a(e.throw(o))}catch(c){s(c)}},a=o=>o.done?n(o.value):Promise.resolve(o.value).then(r,l);a((e=e.apply(i,t)).next())});class W{constructor(t){this.ws=t,this.offset=0,this.synced=!1}synchronize(){return R(this,null,function*(){const t=[];for(let e=0;e<10;e++){const n=performance.now(),s=yield this.ws.sendAndWait({type:"time_sync",clientTime:n}),r=performance.now()-n,l=s.serverTime,a=r/2,o=l-(n+a);t.push(o),yield this.delay(100)}this.offset=this.median(t),this.synced=!0})}toServerTime(t){return t+this.offset}toLocalTime(t){return t-this.offset}isSynced(){return this.synced}getOffset(){return this.offset}median(t){const e=[...t].sort((s,r)=>s-r),n=Math.floor(e.length/2);return e.length%2===0?(e[n-1]+e[n])/2:e[n]}delay(t){return new Promise(e=>setTimeout(e,t))}}var $=Math.pow,N=(i,t,e)=>new Promise((n,s)=>{var r=o=>{try{a(e.next(o))}catch(c){s(c)}},l=o=>{try{a(e.throw(o))}catch(c){s(c)}},a=o=>o.done?n(o.value):Promise.resolve(o.value).then(r,l);a((e=e.apply(i,t)).next())});class H{constructor(t,e){this.ws=t,this.timeSync=e,this.offsetBC=null,this.bcSampleCount=0,this.bcStdDev=null,this.bcMin=null,this.bcMax=null}synchronizeDeviceClock(){return N(this,null,function*(){if(!this.timeSync.isSynced())return!1;try{const t=yield this.ws.sendAndWait({type:"get_device_clock_offset"});return t.success?(this.offsetBC=t.offset,this.bcSampleCount=t.sample_count,this.bcStdDev=t.std_dev,this.bcMin=t.min,this.bcMax=t.max,!0):!1}catch(t){return!1}})}isSynced(){return this.timeSync.isSynced()&&this.offsetBC!==null}toDeviceTime(t){if(!this.isSynced())throw new Error("Device time sync not established. Call synchronizeDeviceClock() first.");const e=this.timeSync.getOffset();return t+e-this.offsetBC}toLocalTime(t){if(!this.isSynced())throw new Error("Device time sync not established. Call synchronizeDeviceClock() first.");const e=this.timeSync.getOffset();return t-e+this.offsetBC}getStatus(){const t=this.timeSync.getOffset(),e=this.offsetBC!==null?t-this.offsetBC:null;return{synced:this.isSynced(),offsetAB:t,offsetBC:this.offsetBC,offsetAC:e,bcSampleCount:this.bcSampleCount,bcStdDev:this.bcStdDev,bcMin:this.bcMin,bcMax:this.bcMax}}validateTimestampAlignment(t){if(!this.isSynced())return null;const e=this.timeSync.getOffset()-this.offsetBC,n=[];for(const h of t){const d=h._receiveTime;d!=null&&h.timestamp!=null&&n.push(d+e-h.timestamp)}if(n.length===0)return null;const s=n.length,r=n.reduce((h,d)=>h+d,0)/s,l=n.reduce((h,d)=>h+$(d-r,2),0)/s,a=Math.sqrt(l),o=Math.min(...n),c=Math.max(...n);return{sampleCount:s,meanResidual:r,stdDev:a,min:o,max:c}}reset(){this.offsetBC=null,this.bcSampleCount=0,this.bcStdDev=null,this.bcMin=null,this.bcMax=null}}function I(i,t){const e=window.innerWidth,n=window.innerHeight;return{x:Math.round(i*e),y:Math.round(t*n)}}function q(i,t){const e=window.innerWidth,n=window.innerHeight;return{x:i/e,y:t/n}}function G(){return{width:window.innerWidth,height:window.innerHeight}}function L(i,t){const e=t.x-i.x,n=t.y-i.y;return Math.sqrt(e*e+n*n)}function J(i,t,e){const n=e.getBoundingClientRect();return{x:Math.round(i-n.left),y:Math.round(t-n.top)}}function V(i){const t=i.getBoundingClientRect();return{width:t.width,height:t.height}}function U(i,t,e){const n=e.getBoundingClientRect();return i>=n.left&&i<=n.right&&t>=n.top&&t<=n.bottom}function X(i,t){if(i.length===0){console.warn("No data to export");return}const e=Array.from(new Set(i.flatMap(l=>Object.keys(y(l))))),n=e.join(","),s=i.map(l=>{const a=y(l);return e.map(o=>{const c=a[o];return typeof c=="string"&&c.includes(",")?`"${c}"`:c!=null?c:""}).join(",")}),r=[n,...s].join(`
2
+ `);v(r,t,"text/csv")}function F(i,t){const e=JSON.stringify(i,null,2);v(e,t,"application/json")}function y(i,t=""){const e={};for(const[n,s]of Object.entries(i)){const r=t?`${t}.${n}`:n;s!==null&&typeof s=="object"&&!Array.isArray(s)?Object.assign(e,y(s,r)):Array.isArray(s)?e[r]=JSON.stringify(s):e[r]=s}return e}function v(i,t,e){const n=new Blob([i],{type:e}),s=URL.createObjectURL(n),r=document.createElement("a");r.href=s,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),setTimeout(()=>URL.revokeObjectURL(s),6e4)}function b(i){if(typeof i!="object"||i===null)return!1;const t=i;return typeof t.x=="number"&&typeof t.y=="number"&&typeof t.timestamp=="number"&&!isNaN(t.x)&&!isNaN(t.y)&&!isNaN(t.timestamp)}function S(i){if(typeof i!="object"||i===null)return!1;const t=i;return typeof t.x=="number"&&typeof t.y=="number"&&t.x>=0&&t.x<=1&&t.y>=0&&t.y<=1}function K(i){return i.filter(b)}function T(i){return typeof i!="object"||i===null?!1:typeof i.success=="boolean"}function Q(i){return typeof i!="object"||i===null?!1:typeof i.success=="boolean"}var Y=Object.defineProperty,Z=Object.defineProperties,tt=Object.getOwnPropertyDescriptors,C=Object.getOwnPropertySymbols,et=Object.prototype.hasOwnProperty,nt=Object.prototype.propertyIsEnumerable,z=(i,t,e)=>t in i?Y(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,m=(i,t)=>{for(var e in t||(t={}))et.call(t,e)&&z(i,e,t[e]);if(C)for(var e of C(t))nt.call(t,e)&&z(i,e,t[e]);return i},it=(i,t)=>Z(i,tt(t)),u=(i,t,e)=>new Promise((n,s)=>{var r=o=>{try{a(e.next(o))}catch(c){s(c)}},l=o=>{try{a(e.throw(o))}catch(c){s(c)}},a=o=>o.done?n(o.value):Promise.resolve(o.value).then(r,l);a((e=e.apply(i,t)).next())});class D{constructor(t){this.tracking=!1,this.config={},this.gazeSampleCount=0,this.deviceTimeSyncTriggered=!1,this.initialize=(...e)=>u(this,[...e],function*(n={}){var s;this.config=n,this.ws=new _(n.connection),this.dataManager=new E,this.timeSync=new W(this.ws),this.deviceTimeSync=new H(this.ws,this.timeSync),this.ws.on("gaze_data",r=>{var l;const a=r.gaze;if(a&&b(a)){const o=(l=r._clientReceiveTime)!=null?l:performance.now(),c=it(m({},a),{browserTimestamp:this.deviceTimeSync.isSynced()?this.deviceTimeSync.toLocalTime(a.timestamp):o});c._receiveTime=o,this.dataManager.addGazeData(c),this.gazeSampleCount++,!this.deviceTimeSyncTriggered&&this.gazeSampleCount>=50&&(this.deviceTimeSyncTriggered=!0,this.deviceTimeSync.synchronizeDeviceClock().catch(h=>{console.warn("Tobii: Device time sync failed, can be retried manually:",h)}))}}),this.ws.on("reconnected",()=>u(this,null,function*(){try{yield this.timeSync.synchronize()}catch(r){console.warn("Tobii: Time sync failed after reconnection:",r)}this.deviceTimeSync.reset(),this.gazeSampleCount=0,this.deviceTimeSyncTriggered=!1})),(s=n.connection)!=null&&s.autoConnect&&(yield this.connect())}),this.on_start=(...e)=>u(this,[...e],function*(n={}){this.dataManager.startTrial(),yield this.startTracking()}),this.on_load=()=>u(this,null,function*(){}),this.on_finish=(...e)=>u(this,[...e],function*(n={}){this.dataManager.endTrial();const s=this.dataManager.getTrialData();return this.dataManager.clearOldData(),{tobii_data:s}}),this.jsPsych=t}connect(){return u(this,null,function*(){yield this.ws.connect(),yield this.timeSync.synchronize()})}disconnect(){return u(this,null,function*(){this.tracking&&(yield this.stopTracking()),yield this.ws.disconnect()})}isConnected(){return this.ws.isConnected()}getConnectionStatus(){return this.ws.getStatus()}startTracking(){return u(this,null,function*(){if(this.tracking)return;if(!this.isConnected())throw new Error("Not connected to server. Call connect() first.");const t=yield this.ws.sendAndWait({type:"start_tracking"});if(t.success)this.tracking=!0;else throw new Error(`Server failed to start tracking: ${t.error||"unknown error"}`)})}stopTracking(){return u(this,null,function*(){try{yield this.ws.sendAndWait({type:"stop_tracking"})}finally{this.tracking=!1}})}isTracking(){return this.tracking}startCalibration(){return u(this,null,function*(){yield this.ws.send({type:"calibration_start"})})}collectCalibrationPoint(t,e){return u(this,null,function*(){if(!S({x:t,y:e}))throw new Error(`Invalid calibration point (${t}, ${e}). Coordinates must be in range [0, 1].`);return{success:(yield this.ws.sendAndWait({type:"calibration_point",point:{x:t,y:e},timestamp:performance.now()})).success===!0}})}computeCalibration(){return u(this,null,function*(){const t=yield this.ws.sendAndWait({type:"calibration_compute"});return T(t)?t:{success:!1,error:"Invalid server response"}})}getCalibrationData(){return u(this,null,function*(){const t=yield this.ws.sendAndWait({type:"get_calibration_data"});return T(t)?t:{success:!1,error:"Invalid server response"}})}startValidation(){return u(this,null,function*(){yield this.ws.send({type:"validation_start"})})}collectValidationPoint(t,e,n){return u(this,null,function*(){if(!S({x:t,y:e}))throw new Error(`Invalid validation point (${t}, ${e}). Coordinates must be in range [0, 1].`);yield this.ws.send({type:"validation_point",point:{x:t,y:e},timestamp:performance.now(),gaze_samples:n||[]})})}getRecentGazeData(t){return this.dataManager.getRecentData(t)}computeValidation(){return u(this,null,function*(){const t=yield this.ws.sendAndWait({type:"validation_compute"});return Q(t)?t:{success:!1,error:"Invalid server response"}})}getCurrentGaze(){return u(this,null,function*(){return this.dataManager.getCurrentGaze()||(yield this.ws.sendAndWait({type:"get_current_gaze"})).gaze||null})}getUserPosition(){return u(this,null,function*(){return this.isConnected()&&(yield this.ws.sendAndWait({type:"get_user_position"})).position||null})}getGazeData(t,e){return u(this,null,function*(){const n=this.dataManager.getDataRange(t,e);return K(n)})}clearGazeData(){this.dataManager.clear()}normalizedToPixels(t,e){return I(t,e)}pixelsToNormalized(t,e){return q(t,e)}getScreenDimensions(){return G()}calculateDistance(t,e){return L(t,e)}windowToContainer(t,e,n){const s=n||this.jsPsych.getDisplayElement();return J(t,e,s)}getContainerDimensions(t){const e=t||this.jsPsych.getDisplayElement();return V(e)}isWithinContainer(t,e,n){const s=n||this.jsPsych.getDisplayElement();return U(t,e,s)}exportToCSV(t,e){X(t,e)}exportToJSON(t,e){F(t,e)}setConfig(t){this.config=m(m({},this.config),t)}getConfig(){return m({},this.config)}getTimeOffset(){return this.timeSync.getOffset()}isTimeSynced(){return this.timeSync.isSynced()}toDeviceTime(t){return this.deviceTimeSync.toDeviceTime(t)}toLocalTime(t){return this.deviceTimeSync.toLocalTime(t)}isDeviceTimeSynced(){return this.deviceTimeSync.isSynced()}getTimeSyncStatus(){return this.deviceTimeSync.getStatus()}validateTimestampAlignment(t){return this.deviceTimeSync.validateTimestampAlignment(t)}}return D.info={name:"tobii",version:O,data:{tobii_data:{type:x.ParameterType.COMPLEX,array:!0}}},D}(jsPsychModule);
3
+ //# sourceMappingURL=https://unpkg.com/@jspsych/extension-tobii@0.2.1/dist/index.browser.min.js.map