gameglue 4.0.1 → 4.0.2

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.
Files changed (56) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +275 -275
  3. package/babel.config.cjs +5 -5
  4. package/coverage/auth.js.html +525 -525
  5. package/coverage/base.css +224 -224
  6. package/coverage/block-navigation.js +87 -87
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +175 -175
  9. package/coverage/index.js.html +309 -309
  10. package/coverage/lcov-report/auth.js.html +525 -525
  11. package/coverage/lcov-report/base.css +224 -224
  12. package/coverage/lcov-report/block-navigation.js +87 -87
  13. package/coverage/lcov-report/favicon.png +0 -0
  14. package/coverage/lcov-report/index.html +175 -175
  15. package/coverage/lcov-report/index.js.html +309 -309
  16. package/coverage/lcov-report/listener.js.html +528 -528
  17. package/coverage/lcov-report/prettify.css +1 -1
  18. package/coverage/lcov-report/prettify.js +2 -2
  19. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  20. package/coverage/lcov-report/sorter.js +210 -210
  21. package/coverage/lcov-report/user.js.html +117 -117
  22. package/coverage/lcov-report/utils.js.html +117 -117
  23. package/coverage/lcov.info +391 -391
  24. package/coverage/listener.js.html +528 -528
  25. package/coverage/prettify.css +1 -1
  26. package/coverage/prettify.js +2 -2
  27. package/coverage/sort-arrow-sprite.png +0 -0
  28. package/coverage/sorter.js +210 -210
  29. package/coverage/user.js.html +117 -117
  30. package/coverage/utils.js.html +117 -117
  31. package/dist/gg.cjs.js +1 -1
  32. package/dist/gg.cjs.js.map +1 -1
  33. package/dist/gg.esm.js +1 -1
  34. package/dist/gg.esm.js.map +1 -1
  35. package/dist/gg.umd.js +1 -1
  36. package/dist/gg.umd.js.map +1 -1
  37. package/examples/certs/cert.pem +19 -19
  38. package/examples/certs/key.pem +28 -28
  39. package/examples/flight-dashboard.html +431 -431
  40. package/examples/server.js +99 -99
  41. package/examples/telemetry-validator.html +1410 -1410
  42. package/jest.config.cjs +33 -33
  43. package/package.json +56 -56
  44. package/rollup.config.js +57 -57
  45. package/src/auth.js +255 -255
  46. package/src/auth.spec.js +481 -481
  47. package/src/index.js +168 -168
  48. package/src/listener.js +196 -196
  49. package/src/listener.spec.js +598 -598
  50. package/src/presence_listener.js +112 -112
  51. package/src/test/fixtures.js +106 -106
  52. package/src/test/setup.js +51 -51
  53. package/src/utils.js +63 -63
  54. package/src/utils.spec.js +78 -78
  55. package/types/index.d.ts +338 -338
  56. package/webpack.config.js +15 -15
@@ -1 +1 @@
1
- {"version":3,"file":"gg.cjs.js","sources":["../src/utils.js","../src/auth.js","../../schemas/dist/types.js","../../schemas/dist/categories/flight_sim.js","../../schemas/dist/categories/index.js","../../schemas/dist/games/xplane.js","../../schemas/dist/games/index.js","../../schemas/dist/games/msfs.js","../../schemas/dist/normalizer.js","../src/listener.js","../src/presence_listener.js","../src/index.js"],"sourcesContent":["const storageMap = {};\nexport const storage = {\n set: (key, value) => {\n return isBrowser() ? localStorage.setItem(key, value) : (storageMap[key] = value);\n },\n get: (key) => {\n return isBrowser() ? localStorage.getItem(key) : storageMap[key];\n },\n remove: (key) => {\n return isBrowser() ? localStorage.removeItem(key) : delete storageMap[key];\n }\n};\nexport const isBrowser = () => {\n return !(typeof process === 'object' && String(process) === '[object process]');\n}\n\n/**\n * Detect if an error is likely a CORS error.\n * CORS errors in browsers are intentionally vague for security, so we look for common patterns.\n */\nexport function isCorsError(error) {\n if (!error) return false;\n const msg = (error.message || error.toString() || '').toLowerCase();\n\n // Common CORS error patterns\n return (\n msg.includes('cors') ||\n msg.includes('cross-origin') ||\n msg.includes('network error') ||\n msg.includes('failed to fetch') ||\n msg.includes('networkerror') ||\n msg.includes('load failed') ||\n // Socket.io specific patterns for CORS failures\n msg.includes('xhr poll error') ||\n msg.includes('websocket error') ||\n (msg.includes('transport') && msg.includes('error'))\n );\n}\n\n/**\n * Log helpful CORS debugging guidance to the console.\n */\nexport function logCorsHelp(context, url) {\n console.error(`\n╔══════════════════════════════════════════════════════════════════════════════╗\n║ GameGlue SDK: Possible CORS Error Detected ║\n╠══════════════════════════════════════════════════════════════════════════════╣\n║ Context: ${context.padEnd(66)}║\n║ URL: ${(url || 'unknown').substring(0, 70).padEnd(70)}║\n╠══════════════════════════════════════════════════════════════════════════════╣\n║ This error typically means the server rejected the request due to CORS. ║\n║ ║\n║ To fix this, add your origin to your app's allowed Web Origins: ║\n║ ║\n║ 1. Go to the GameGlue Developer Portal (https://developer.gameglue.gg) ║\n║ 2. Select your app ║\n║ 3. Add your origin to \"Web Origins\" and save ║\n║ ║\n║ IMPORTANT: Protocol and port must match exactly. ║\n║ http://localhost:3000 and https://localhost:3000 are different origins. ║\n║ http://localhost:3000 and http://localhost:5000 are different origins. ║\n╚══════════════════════════════════════════════════════════════════════════════╝\n`);\n}","import { OidcClient } from 'oidc-client-ts';\nimport { storage, isCorsError, logCorsHelp } from './utils';\nimport jwt_decode from 'jwt-decode';\n\nconst DEFAULT_AUTH_URL = 'https://auth.gameglue.gg/realms/GameGlue';\n\n// Track if callback is being processed (prevents double-processing)\nlet _callbackPromise = null;\n\nexport class GameGlueAuth {\n constructor(cfg) {\n const authority = cfg.authUrl || DEFAULT_AUTH_URL;\n this._oidcSettings = {\n authority,\n client_id: cfg.clientId,\n redirect_uri: removeTrailingSlashes(cfg.redirect_uri || window.location.href),\n post_logout_redirect_uri: removeTrailingSlashes(window.location.href),\n response_type: \"code\",\n scope: `openid ${(cfg.scopes || []).join(' ')}`,\n response_mode: \"fragment\",\n filterProtocolClaims: true\n };\n this._oidcClient = new OidcClient(this._oidcSettings);\n this._refreshCallback = () => {};\n this._refreshTimeout = null;\n }\n\n /**\n * Check if user is authenticated.\n * If OAuth callback params are in URL, processes them first.\n * Safe to call multiple times - idempotent.\n * @returns {Promise<boolean>}\n */\n async isAuthenticated() {\n // If callback params present, process them first\n if (this._hasCallbackParams()) {\n await this._processCallback();\n }\n\n // Check for valid tokens\n return this._hasValidTokens();\n }\n\n /**\n * Redirect to OAuth login page.\n * Does not return - navigates away.\n */\n login() {\n this._oidcClient.createSigninRequest({ state: { bar: 15 } }).then((req) => {\n window.location = req.url;\n }).catch((err) => {\n if (isCorsError(err)) {\n logCorsHelp('Login Request', this._oidcSettings.authority);\n }\n console.error('Failed to create signin request:', err);\n });\n }\n\n /**\n * Log out the user.\n * Clears local tokens and optionally redirects to Keycloak logout.\n * @param {Object} options - { redirect?: boolean }\n */\n logout(options = {}) {\n // Clear local tokens\n storage.remove('gg-auth-token');\n storage.remove('gg-refresh-token');\n clearTimeout(this._refreshTimeout);\n\n // Optionally redirect to Keycloak logout\n if (options.redirect !== false) {\n const logoutUrl = `${this._oidcSettings.authority}/protocol/openid-connect/logout?post_logout_redirect_uri=${encodeURIComponent(this._oidcSettings.post_logout_redirect_uri)}`;\n window.location.href = logoutUrl;\n }\n }\n\n /**\n * Get the current user's ID.\n * @throws {Error} if not authenticated\n * @returns {string}\n */\n getUser() {\n const token = this._getAccessToken();\n if (!token) {\n throw new Error('Not authenticated');\n }\n const decoded = jwt_decode(token);\n return decoded.sub;\n }\n\n /**\n * Get the access token for API calls.\n * @returns {string|null}\n */\n getAccessToken() {\n return this._getAccessToken();\n }\n\n /**\n * Register callback for token refresh events.\n * @param {Function} callback\n */\n onTokenRefreshed(callback) {\n this._refreshCallback = callback;\n }\n\n // ============ Internal Methods ============\n\n _hasCallbackParams() {\n return location.hash.includes(\"state=\") &&\n (location.hash.includes(\"code=\") || location.hash.includes(\"error=\"));\n }\n\n _clearCallbackUrl() {\n window.history.replaceState(\"\", document.title, window.location.pathname + window.location.search);\n }\n\n async _processCallback() {\n // If already processing, wait for that to complete\n if (_callbackPromise) {\n await _callbackPromise;\n return;\n }\n\n // Start processing\n _callbackPromise = this._doProcessCallback();\n\n try {\n await _callbackPromise;\n } finally {\n _callbackPromise = null;\n }\n }\n\n async _doProcessCallback() {\n try {\n const response = await this._oidcClient.processSigninResponse(window.location.href);\n\n if (response.error) {\n this._clearCallbackUrl();\n throw new Error(response.error);\n }\n\n if (!response.access_token) {\n this._clearCallbackUrl();\n throw new Error('No access token received');\n }\n\n this._setAccessToken(response.access_token);\n this._setRefreshToken(response.refresh_token);\n this._clearCallbackUrl();\n } catch (err) {\n // If we failed but tokens exist (another call succeeded), that's fine\n if (this._hasValidTokens()) {\n this._clearCallbackUrl();\n return;\n }\n this._clearCallbackUrl();\n throw err;\n }\n }\n\n _hasValidTokens() {\n const token = this._getAccessToken();\n if (!token) {\n return false;\n }\n\n try {\n const decoded = jwt_decode(token);\n const expirationDate = new Date(decoded.exp * 1000);\n return expirationDate > new Date();\n } catch {\n return false;\n }\n }\n\n _getAccessToken() {\n const token = storage.get('gg-auth-token');\n if (token) {\n this._setTokenRefreshTimeout(token);\n }\n return token || undefined;\n }\n\n _setAccessToken(token) {\n this._setTokenRefreshTimeout(token);\n return storage.set('gg-auth-token', token);\n }\n\n _setRefreshToken(token) {\n return storage.set('gg-refresh-token', token);\n }\n\n _getRefreshToken() {\n return storage.get('gg-refresh-token');\n }\n\n _setTokenRefreshTimeout(token) {\n if (!token) return;\n\n clearTimeout(this._refreshTimeout);\n\n try {\n const timeUntilExp = (jwt_decode(token).exp * 1000) - Date.now() - 5000;\n if (timeUntilExp > 0) {\n this._refreshTimeout = setTimeout(() => {\n this._attemptRefresh();\n }, timeUntilExp);\n }\n } catch {\n // Invalid token, ignore\n }\n }\n\n async _attemptRefresh() {\n const url = `${this._oidcSettings.authority}/protocol/openid-connect/token`;\n const client_id = this._oidcSettings.client_id;\n const refresh_token = this._getRefreshToken();\n const grant_type = 'refresh_token';\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded'\n },\n body: new URLSearchParams({\n client_id,\n grant_type,\n refresh_token\n })\n });\n\n if (response.status === 200) {\n const resObj = await response.json();\n this._setAccessToken(resObj.access_token);\n this._setRefreshToken(resObj.refresh_token);\n this._refreshCallback(resObj.access_token);\n }\n } catch (e) {\n if (isCorsError(e)) {\n logCorsHelp('Token Refresh', url);\n }\n console.error('Token refresh failed:', e);\n }\n }\n}\n\nfunction removeTrailingSlashes(url) {\n if (url.endsWith('/')) {\n return url.replace(/\\/+$/, '');\n }\n return url;\n}\n","/**\n * Schema type definitions for GameGlue telemetry normalization\n */\n/**\n * Transform functions for converting values between game-specific and canonical formats\n */\nexport const transforms = {\n /** Convert radians to degrees */\n radiansToDegrees: (value) => value * (180 / Math.PI),\n /** Convert degrees to radians */\n degreesToRadians: (value) => value * (Math.PI / 180),\n /** Convert meters to feet */\n metersToFeet: (value) => value * 3.28084,\n /** Convert feet to meters */\n feetToMeters: (value) => value / 3.28084,\n /** Convert m/s to knots */\n msToKnots: (value) => value * 1.94384,\n /** Convert knots to m/s */\n knotsToMs: (value) => value / 1.94384,\n /** Convert meters per second to feet per minute */\n msToFpm: (value) => value * 196.85,\n /** Convert boolean-like (0/1) to boolean */\n toBoolean: (value) => Boolean(value),\n /** Divide by 100 (for values that come as percent * 100) */\n divideBy100: (value) => value / 100,\n};\n","/**\n * Canonical schema for flight simulators.\n * These are the normalized field names that developers code against.\n * Individual sim schemas map their game-specific fields to these canonical names.\n */\nexport const flightSimSchema = {\n category: 'flight_sim',\n name: 'Flight Simulator',\n description: 'Canonical telemetry fields for flight simulation games',\n recommendedUpdateRateHz: 10,\n requiredFields: [\n 'latitude',\n 'longitude',\n 'altitude',\n 'indicated_airspeed',\n 'heading',\n 'vertical_speed',\n 'on_ground',\n ],\n fields: {\n // ===========================================\n // POSITION\n // ===========================================\n latitude: {\n type: 'number',\n description: 'Aircraft latitude',\n unit: 'degrees',\n min: -90,\n max: 90,\n group: 'Position',\n },\n longitude: {\n type: 'number',\n description: 'Aircraft longitude',\n unit: 'degrees',\n min: -180,\n max: 180,\n group: 'Position',\n },\n altitude: {\n type: 'number',\n description: 'Aircraft altitude above sea level',\n unit: 'ft',\n min: -2000,\n max: 100000,\n group: 'Position',\n },\n altitude_agl: {\n type: 'number',\n description: 'Aircraft altitude above ground level',\n unit: 'ft',\n min: 0,\n max: 100000,\n group: 'Position',\n },\n on_ground: {\n type: 'boolean',\n description: 'Whether aircraft is on the ground',\n group: 'Position',\n },\n // ===========================================\n // AIRSPEED & VELOCITY\n // ===========================================\n indicated_airspeed: {\n type: 'number',\n description: 'Indicated airspeed',\n unit: 'kts',\n min: 0,\n max: 1000,\n group: 'Velocity',\n },\n true_airspeed: {\n type: 'number',\n description: 'True airspeed',\n unit: 'kts',\n min: 0,\n max: 1000,\n group: 'Velocity',\n },\n ground_speed: {\n type: 'number',\n description: 'Ground speed',\n unit: 'kts',\n min: 0,\n max: 1000,\n group: 'Velocity',\n },\n mach: {\n type: 'number',\n description: 'Mach number',\n min: 0,\n max: 5,\n group: 'Velocity',\n },\n vertical_speed: {\n type: 'number',\n description: 'Vertical speed',\n unit: 'ft/min',\n min: -20000,\n max: 20000,\n group: 'Velocity',\n },\n g_force: {\n type: 'number',\n description: 'Current G-force',\n unit: 'G',\n min: -10,\n max: 10,\n group: 'Velocity',\n },\n // ===========================================\n // ATTITUDE\n // ===========================================\n heading: {\n type: 'number',\n description: 'Aircraft magnetic heading',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Attitude',\n },\n true_heading: {\n type: 'number',\n description: 'Aircraft true heading',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Attitude',\n },\n pitch: {\n type: 'number',\n description: 'Aircraft pitch angle',\n unit: 'degrees',\n min: -90,\n max: 90,\n group: 'Attitude',\n },\n roll: {\n type: 'number',\n description: 'Aircraft roll/bank angle',\n unit: 'degrees',\n min: -180,\n max: 180,\n group: 'Attitude',\n },\n yaw: {\n type: 'number',\n description: 'Aircraft yaw angle',\n unit: 'degrees',\n min: -180,\n max: 180,\n group: 'Attitude',\n },\n // ===========================================\n // ENGINE (generic - up to 4 engines, 0-indexed)\n // ===========================================\n throttle_0: {\n type: 'number',\n description: 'Engine 0 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n throttle_1: {\n type: 'number',\n description: 'Engine 1 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n throttle_2: {\n type: 'number',\n description: 'Engine 2 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n throttle_3: {\n type: 'number',\n description: 'Engine 3 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n rpm_0: {\n type: 'number',\n description: 'Engine 0 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n rpm_1: {\n type: 'number',\n description: 'Engine 1 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n rpm_2: {\n type: 'number',\n description: 'Engine 2 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n rpm_3: {\n type: 'number',\n description: 'Engine 3 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n n1_0: {\n type: 'number',\n description: 'Engine 0 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n1_1: {\n type: 'number',\n description: 'Engine 1 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n1_2: {\n type: 'number',\n description: 'Engine 2 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n1_3: {\n type: 'number',\n description: 'Engine 3 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_0: {\n type: 'number',\n description: 'Engine 0 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_1: {\n type: 'number',\n description: 'Engine 1 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_2: {\n type: 'number',\n description: 'Engine 2 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_3: {\n type: 'number',\n description: 'Engine 3 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n egt_0: {\n type: 'number',\n description: 'Engine 0 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n egt_1: {\n type: 'number',\n description: 'Engine 1 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n egt_2: {\n type: 'number',\n description: 'Engine 2 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n egt_3: {\n type: 'number',\n description: 'Engine 3 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n // ===========================================\n // FUEL\n // ===========================================\n fuel_quantity: {\n type: 'number',\n description: 'Total fuel quantity',\n unit: 'lbs',\n min: 0,\n max: 600000,\n group: 'Fuel',\n },\n fuel_flow_0: {\n type: 'number',\n description: 'Engine 0 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n fuel_flow_1: {\n type: 'number',\n description: 'Engine 1 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n fuel_flow_2: {\n type: 'number',\n description: 'Engine 2 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n fuel_flow_3: {\n type: 'number',\n description: 'Engine 3 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n // ===========================================\n // AUTOPILOT\n // ===========================================\n autopilot_master: {\n type: 'boolean',\n description: 'Autopilot master engaged',\n group: 'Autopilot',\n },\n autopilot_altitude_hold: {\n type: 'boolean',\n description: 'Altitude hold engaged',\n group: 'Autopilot',\n },\n autopilot_heading_hold: {\n type: 'boolean',\n description: 'Heading hold engaged',\n group: 'Autopilot',\n },\n autopilot_vs_hold: {\n type: 'boolean',\n description: 'Vertical speed hold engaged',\n group: 'Autopilot',\n },\n autopilot_speed_hold: {\n type: 'boolean',\n description: 'Speed hold engaged',\n group: 'Autopilot',\n },\n autopilot_approach: {\n type: 'boolean',\n description: 'Approach mode engaged',\n group: 'Autopilot',\n },\n autopilot_nav: {\n type: 'boolean',\n description: 'NAV mode engaged',\n group: 'Autopilot',\n },\n flight_director: {\n type: 'boolean',\n description: 'Flight director active',\n group: 'Autopilot',\n },\n autopilot_flc: {\n type: 'boolean',\n description: 'Flight level change mode engaged',\n group: 'Autopilot',\n },\n autopilot_altitude_target: {\n type: 'number',\n description: 'Autopilot target altitude',\n unit: 'ft',\n min: 0,\n max: 60000,\n group: 'Autopilot',\n },\n autopilot_heading_target: {\n type: 'number',\n description: 'Autopilot target heading',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Autopilot',\n },\n autopilot_vs_target: {\n type: 'number',\n description: 'Autopilot target vertical speed',\n unit: 'ft/min',\n min: -10000,\n max: 10000,\n group: 'Autopilot',\n },\n autopilot_speed_target: {\n type: 'number',\n description: 'Autopilot target airspeed',\n unit: 'kts',\n min: 0,\n max: 600,\n group: 'Autopilot',\n },\n // ===========================================\n // FLIGHT CONTROLS\n // ===========================================\n flaps: {\n type: 'number',\n description: 'Flaps position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n spoilers: {\n type: 'number',\n description: 'Spoilers position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_down: {\n type: 'boolean',\n description: 'Landing gear extended',\n group: 'Controls',\n },\n parking_brake: {\n type: 'boolean',\n description: 'Parking brake engaged',\n group: 'Controls',\n },\n brake_left: {\n type: 'number',\n description: 'Left brake position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n brake_right: {\n type: 'number',\n description: 'Right brake position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n // ===========================================\n // LIGHTS\n // ===========================================\n light_landing: {\n type: 'boolean',\n description: 'Landing lights on',\n group: 'Lights',\n },\n light_taxi: {\n type: 'boolean',\n description: 'Taxi lights on',\n group: 'Lights',\n },\n light_beacon: {\n type: 'boolean',\n description: 'Beacon lights on',\n group: 'Lights',\n },\n light_nav: {\n type: 'boolean',\n description: 'Navigation lights on',\n group: 'Lights',\n },\n light_strobe: {\n type: 'boolean',\n description: 'Strobe lights on',\n group: 'Lights',\n },\n // ===========================================\n // ENVIRONMENT\n // ===========================================\n outside_air_temp: {\n type: 'number',\n description: 'Outside air temperature',\n unit: '°C',\n min: -80,\n max: 60,\n group: 'Environment',\n },\n barometric_pressure: {\n type: 'number',\n description: 'Barometric pressure setting',\n unit: 'inHg',\n min: 27,\n max: 32,\n group: 'Environment',\n },\n wind_direction: {\n type: 'number',\n description: 'Wind direction',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Environment',\n },\n wind_speed: {\n type: 'number',\n description: 'Wind speed',\n unit: 'kts',\n min: 0,\n max: 200,\n group: 'Environment',\n },\n // ===========================================\n // WARNINGS\n // ===========================================\n stall_warning: {\n type: 'boolean',\n description: 'Stall warning active',\n group: 'Warnings',\n },\n overspeed_warning: {\n type: 'boolean',\n description: 'Overspeed warning active',\n group: 'Warnings',\n },\n // ===========================================\n // SIM STATE\n // ===========================================\n sim_paused: {\n type: 'boolean',\n description: 'Simulation is paused',\n group: 'Simulation',\n },\n // ===========================================\n // GPS / NAVIGATION\n // ===========================================\n gps_wp_distance: {\n type: 'number',\n description: 'Distance to next GPS waypoint',\n unit: 'nm',\n min: 0,\n max: 10000,\n group: 'Navigation',\n },\n gps_wp_ete: {\n type: 'number',\n description: 'Estimated time enroute to next GPS waypoint',\n unit: 'seconds',\n min: 0,\n max: 86400,\n group: 'Navigation',\n },\n },\n commands: {\n // ===========================================\n // AUTOPILOT COMMANDS\n // ===========================================\n autopilot_on: {\n description: 'Engage autopilot master',\n group: 'Autopilot',\n },\n autopilot_off: {\n description: 'Disengage autopilot master',\n group: 'Autopilot',\n },\n autopilot_toggle: {\n description: 'Toggle autopilot master',\n group: 'Autopilot',\n },\n autopilot_altitude_hold_on: {\n description: 'Engage altitude hold',\n group: 'Autopilot',\n },\n autopilot_altitude_hold_off: {\n description: 'Disengage altitude hold',\n group: 'Autopilot',\n },\n autopilot_heading_hold_on: {\n description: 'Engage heading hold',\n group: 'Autopilot',\n },\n autopilot_heading_hold_off: {\n description: 'Disengage heading hold',\n group: 'Autopilot',\n },\n autopilot_vs_hold_on: {\n description: 'Engage vertical speed hold',\n group: 'Autopilot',\n },\n autopilot_vs_hold_off: {\n description: 'Disengage vertical speed hold',\n group: 'Autopilot',\n },\n autopilot_nav_on: {\n description: 'Engage NAV mode',\n group: 'Autopilot',\n },\n autopilot_nav_off: {\n description: 'Disengage NAV mode',\n group: 'Autopilot',\n },\n autopilot_approach_on: {\n description: 'Engage approach mode',\n group: 'Autopilot',\n },\n autopilot_approach_off: {\n description: 'Disengage approach mode',\n group: 'Autopilot',\n },\n flight_director_on: {\n description: 'Engage flight director',\n group: 'Autopilot',\n },\n flight_director_off: {\n description: 'Disengage flight director',\n group: 'Autopilot',\n },\n autopilot_flc_on: {\n description: 'Engage flight level change mode',\n group: 'Autopilot',\n },\n autopilot_flc_off: {\n description: 'Disengage flight level change mode',\n group: 'Autopilot',\n },\n autopilot_flc_toggle: {\n description: 'Toggle flight level change mode',\n group: 'Autopilot',\n },\n set_autopilot_altitude: {\n description: 'Set autopilot target altitude',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target altitude in feet',\n paramMin: 0,\n paramMax: 60000,\n },\n set_autopilot_heading: {\n description: 'Set autopilot target heading',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target heading in degrees',\n paramMin: 0,\n paramMax: 360,\n },\n set_autopilot_vs: {\n description: 'Set autopilot target vertical speed',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target vertical speed in ft/min',\n paramMin: -10000,\n paramMax: 10000,\n },\n set_autopilot_speed: {\n description: 'Set autopilot target airspeed',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target airspeed in knots',\n paramMin: 0,\n paramMax: 600,\n },\n // ===========================================\n // GEAR COMMANDS\n // ===========================================\n gear_up: {\n description: 'Retract landing gear',\n group: 'Controls',\n },\n gear_down: {\n description: 'Extend landing gear',\n group: 'Controls',\n },\n gear_toggle: {\n description: 'Toggle landing gear',\n group: 'Controls',\n },\n // ===========================================\n // FLAPS COMMANDS\n // ===========================================\n flaps_up: {\n description: 'Retract flaps one notch',\n group: 'Controls',\n },\n flaps_down: {\n description: 'Extend flaps one notch',\n group: 'Controls',\n },\n flaps_full: {\n description: 'Extend flaps fully',\n group: 'Controls',\n },\n flaps_retract: {\n description: 'Fully retract flaps',\n group: 'Controls',\n },\n set_flaps: {\n description: 'Set flaps to specific position',\n group: 'Controls',\n paramType: 'number',\n paramDescription: 'Flaps position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n // ===========================================\n // SPOILERS COMMANDS\n // ===========================================\n spoilers_arm: {\n description: 'Arm spoilers',\n group: 'Controls',\n },\n spoilers_deploy: {\n description: 'Deploy spoilers',\n group: 'Controls',\n },\n spoilers_retract: {\n description: 'Retract spoilers',\n group: 'Controls',\n },\n spoilers_toggle: {\n description: 'Toggle spoilers',\n group: 'Controls',\n },\n // ===========================================\n // BRAKES COMMANDS\n // ===========================================\n parking_brake_toggle: {\n description: 'Toggle parking brake',\n group: 'Controls',\n },\n parking_brake_on: {\n description: 'Engage parking brake',\n group: 'Controls',\n },\n parking_brake_off: {\n description: 'Release parking brake',\n group: 'Controls',\n },\n // ===========================================\n // THROTTLE COMMANDS\n // ===========================================\n set_throttle: {\n description: 'Set all throttles',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_0: {\n description: 'Set engine 0 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_1: {\n description: 'Set engine 1 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_2: {\n description: 'Set engine 2 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_3: {\n description: 'Set engine 3 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n throttle_full: {\n description: 'Set throttles to full',\n group: 'Engine',\n },\n throttle_idle: {\n description: 'Set throttles to idle',\n group: 'Engine',\n },\n throttle_cutoff: {\n description: 'Cut throttles completely',\n group: 'Engine',\n },\n // ===========================================\n // LIGHTS COMMANDS\n // ===========================================\n landing_lights_on: {\n description: 'Turn on landing lights',\n group: 'Lights',\n },\n landing_lights_off: {\n description: 'Turn off landing lights',\n group: 'Lights',\n },\n landing_lights_toggle: {\n description: 'Toggle landing lights',\n group: 'Lights',\n },\n taxi_lights_on: {\n description: 'Turn on taxi lights',\n group: 'Lights',\n },\n taxi_lights_off: {\n description: 'Turn off taxi lights',\n group: 'Lights',\n },\n taxi_lights_toggle: {\n description: 'Toggle taxi lights',\n group: 'Lights',\n },\n beacon_lights_on: {\n description: 'Turn on beacon lights',\n group: 'Lights',\n },\n beacon_lights_off: {\n description: 'Turn off beacon lights',\n group: 'Lights',\n },\n beacon_lights_toggle: {\n description: 'Toggle beacon lights',\n group: 'Lights',\n },\n nav_lights_on: {\n description: 'Turn on navigation lights',\n group: 'Lights',\n },\n nav_lights_off: {\n description: 'Turn off navigation lights',\n group: 'Lights',\n },\n nav_lights_toggle: {\n description: 'Toggle navigation lights',\n group: 'Lights',\n },\n strobe_lights_on: {\n description: 'Turn on strobe lights',\n group: 'Lights',\n },\n strobe_lights_off: {\n description: 'Turn off strobe lights',\n group: 'Lights',\n },\n strobe_lights_toggle: {\n description: 'Toggle strobe lights',\n group: 'Lights',\n },\n // ===========================================\n // SIM COMMANDS\n // ===========================================\n pause: {\n description: 'Pause simulation',\n group: 'Simulation',\n },\n unpause: {\n description: 'Unpause simulation',\n group: 'Simulation',\n },\n pause_toggle: {\n description: 'Toggle simulation pause',\n group: 'Simulation',\n },\n },\n};\n","import { flightSimSchema } from './flight_sim';\nexport { flightSimSchema } from './flight_sim';\n/** Registry of all category schemas */\nexport const categorySchemas = {\n flight_sim: flightSimSchema,\n racing_sim: flightSimSchema, // TODO: Create racing_sim schema\n};\n/** Get a category schema by ID */\nexport function getCategorySchema(category) {\n return categorySchemas[category];\n}\n","import xplaneMappings from '../mappings/xplane.json';\n/**\n * X-Plane schema\n * Maps X-Plane-specific field names to canonical flight_sim fields\n *\n * Note: Field and command mappings are loaded from xplane.json\n * This allows the same mappings to be used by both TypeScript and Rust.\n */\nexport const xplaneSchema = {\n gameId: xplaneMappings.gameId,\n name: xplaneMappings.name,\n category: xplaneMappings.category,\n fieldMappings: xplaneMappings.fieldMappings,\n commandMappings: xplaneMappings.commandMappings,\n // X-Plane specific fields not in canonical schema\n extraFields: {\n replay_mode: {\n type: 'boolean',\n description: 'Replay mode active',\n group: 'Simulation',\n },\n time_local: {\n type: 'number',\n description: 'Local time in seconds since midnight',\n unit: 'sec',\n min: 0,\n max: 86400,\n group: 'Simulation',\n },\n time_zulu: {\n type: 'number',\n description: 'Zulu time in seconds since midnight',\n unit: 'sec',\n min: 0,\n max: 86400,\n group: 'Simulation',\n },\n },\n};\n","import { msfsSchema } from './msfs';\nimport { xplaneSchema } from './xplane';\nexport { msfsSchema } from './msfs';\nexport { xplaneSchema } from './xplane';\n/** Registry of all game schemas */\nexport const gameSchemas = {\n msfs: msfsSchema,\n xplane: xplaneSchema,\n};\n/** Get a game schema by ID */\nexport function getGameSchema(gameId) {\n return gameSchemas[gameId];\n}\n/** Get all available games */\nexport function getAvailableGames() {\n return Object.values(gameSchemas).map((schema) => ({\n id: schema.gameId,\n name: schema.name,\n category: schema.category,\n }));\n}\n","import msfsMappings from '../mappings/msfs.json';\n/**\n * Microsoft Flight Simulator (MSFS) schema\n * Maps MSFS-specific field names to canonical flight_sim fields\n *\n * Note: Field and command mappings are loaded from msfs.json\n * This allows the same mappings to be used by both TypeScript and Rust.\n */\nexport const msfsSchema = {\n gameId: msfsMappings.gameId,\n name: msfsMappings.name,\n category: msfsMappings.category,\n fieldMappings: msfsMappings.fieldMappings,\n commandMappings: msfsMappings.commandMappings,\n // MSFS-specific fields not in canonical schema\n extraFields: {\n pitot_heat: {\n type: 'boolean',\n description: 'Pitot heat active',\n group: 'Systems',\n },\n fuel_quantity_gallons: {\n type: 'number',\n description: 'Total fuel in gallons',\n unit: 'gal',\n min: 0,\n max: 100000,\n group: 'Fuel',\n },\n fuel_flow_gph_1: {\n type: 'number',\n description: 'Engine 1 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n fuel_flow_gph_2: {\n type: 'number',\n description: 'Engine 2 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n fuel_flow_gph_3: {\n type: 'number',\n description: 'Engine 3 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n fuel_flow_gph_4: {\n type: 'number',\n description: 'Engine 4 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n flaps_left_percent: {\n type: 'number',\n description: 'Left flaps position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n flaps_right_percent: {\n type: 'number',\n description: 'Right flaps position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n spoilers_left_percent: {\n type: 'number',\n description: 'Left spoilers position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n spoilers_right_percent: {\n type: 'number',\n description: 'Right spoilers position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_left_position: {\n type: 'number',\n description: 'Left gear position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_center_position: {\n type: 'number',\n description: 'Center gear position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_right_position: {\n type: 'number',\n description: 'Right gear position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n ambient_pressure: {\n type: 'number',\n description: 'Ambient pressure',\n unit: 'inHg',\n min: 20,\n max: 35,\n group: 'Environment',\n },\n sea_level_pressure: {\n type: 'number',\n description: 'Sea level pressure',\n unit: 'inHg',\n min: 27,\n max: 32,\n group: 'Environment',\n },\n aircraft_wind_x: {\n type: 'number',\n description: 'Wind component X-axis',\n unit: 'kts',\n min: -200,\n max: 200,\n group: 'Environment',\n },\n aircraft_wind_y: {\n type: 'number',\n description: 'Wind component Y-axis',\n unit: 'kts',\n min: -200,\n max: 200,\n group: 'Environment',\n },\n aircraft_wind_z: {\n type: 'number',\n description: 'Wind component Z-axis',\n unit: 'kts',\n min: -200,\n max: 200,\n group: 'Environment',\n },\n },\n};\n","import { transforms } from './types';\n/**\n * Denormalizes a canonical command to a game-specific command.\n * This is used when the SDK sends commands to the game.\n */\nexport function denormalizeCommand(canonicalCommand, gameSchema, value) {\n const mapping = gameSchema.commandMappings[canonicalCommand];\n if (!mapping) {\n return undefined;\n }\n let transformedValue = value;\n if (mapping.paramTransform && mapping.paramTransform in transforms && value !== undefined) {\n const transformFn = transforms[mapping.paramTransform];\n transformedValue = transformFn(value);\n }\n return {\n command: mapping.gameCommand,\n value: transformedValue,\n };\n}\n/**\n * Gets a list of available canonical commands for a game\n */\nexport function getAvailableCommands(gameSchema) {\n return Object.keys(gameSchema.commandMappings);\n}\n/**\n * Normalizes raw game telemetry to canonical field names.\n * This is the core function the SDK uses to provide a consistent API across sims.\n */\nexport function normalizeTelemetry(raw, gameSchema) {\n const normalized = {};\n for (const [rawField, value] of Object.entries(raw)) {\n const mapping = gameSchema.fieldMappings[rawField];\n if (mapping) {\n // Apply transform if specified\n let normalizedValue = value;\n if (mapping.transform && mapping.transform in transforms) {\n const transformFn = transforms[mapping.transform];\n normalizedValue = transformFn(value);\n }\n normalized[mapping.canonical] = normalizedValue;\n }\n else if (gameSchema.extraFields?.[rawField]) {\n // Keep extra fields with their original names (prefixed with game ID)\n normalized[`${gameSchema.gameId}:${rawField}`] = value;\n }\n // Unknown fields are dropped (or could optionally be preserved)\n }\n return normalized;\n}\n/**\n * Denormalizes canonical field names back to game-specific names.\n * Used for sending commands back to the game.\n */\nexport function denormalizeTelemetry(normalized, gameSchema) {\n const raw = {};\n // Build reverse mapping\n const reverseMapping = {};\n for (const [rawField, mapping] of Object.entries(gameSchema.fieldMappings)) {\n // Only keep first mapping for each canonical field\n if (!reverseMapping[mapping.canonical]) {\n reverseMapping[mapping.canonical] = { rawField, transform: mapping.transform };\n }\n }\n for (const [canonicalField, value] of Object.entries(normalized)) {\n const mapping = reverseMapping[canonicalField];\n if (mapping) {\n // Note: we don't reverse transforms here - commands typically don't need it\n raw[mapping.rawField] = value;\n }\n }\n return raw;\n}\nexport function validateTelemetry(normalized, categorySchema) {\n const results = [];\n for (const [fieldName, fieldDef] of Object.entries(categorySchema.fields)) {\n const value = normalized[fieldName];\n if (value === undefined) {\n results.push({ field: fieldName, status: 'missing' });\n continue;\n }\n if (value === null) {\n results.push({ field: fieldName, status: 'null', value });\n continue;\n }\n if (fieldDef.type === 'number') {\n if (typeof value !== 'number') {\n results.push({\n field: fieldName,\n status: 'wrong_type',\n value,\n message: `Expected number, got ${typeof value}`,\n });\n continue;\n }\n if (fieldDef.min !== undefined && value < fieldDef.min) {\n results.push({\n field: fieldName,\n status: 'out_of_range',\n value,\n message: `Value ${value} below min ${fieldDef.min}`,\n });\n continue;\n }\n if (fieldDef.max !== undefined && value > fieldDef.max) {\n results.push({\n field: fieldName,\n status: 'out_of_range',\n value,\n message: `Value ${value} above max ${fieldDef.max}`,\n });\n continue;\n }\n }\n if (fieldDef.type === 'boolean' && typeof value !== 'boolean') {\n results.push({\n field: fieldName,\n status: 'wrong_type',\n value,\n message: `Expected boolean, got ${typeof value}`,\n });\n continue;\n }\n results.push({ field: fieldName, status: 'ok', value });\n }\n return results;\n}\nexport function getCoverageStats(validationResults, categorySchema) {\n const receivedFields = validationResults.filter((r) => r.status !== 'missing').length;\n const totalFields = Object.keys(categorySchema.fields).length;\n const receivedSet = new Set(validationResults.filter((r) => r.status !== 'missing').map((r) => r.field));\n const requiredReceived = categorySchema.requiredFields.filter((f) => receivedSet.has(f)).length;\n const missingRequired = categorySchema.requiredFields.filter((f) => !receivedSet.has(f));\n const fieldsWithIssues = validationResults.filter((r) => r.status === 'null' || r.status === 'out_of_range' || r.status === 'wrong_type').length;\n return {\n totalFields,\n receivedFields,\n requiredFields: categorySchema.requiredFields.length,\n requiredReceived,\n missingRequired,\n fieldsWithIssues,\n coverage: totalFields > 0 ? Math.round((receivedFields / totalFields) * 100) : 0,\n };\n}\n","import EventEmitter from 'event-emitter';\nimport { getGameSchema, normalizeTelemetry } from '@gameglue/schemas';\n\nexport class Listener {\n constructor(socket, config) {\n this._config = config;\n this._socket = socket;\n this._callbacks = [];\n this._fields = config.fields ? [...config.fields] : null;\n this._gameSchema = getGameSchema(config.gameId);\n }\n\n async establishConnection() {\n if (!this._socket || !this._config.userId || !this._config.gameId) {\n throw new Error('Missing arguments in establishConnection');\n }\n return new Promise((resolve) => {\n // Use object format if fields are specified, otherwise use legacy string format\n let listenPayload;\n if (this._fields) {\n listenPayload = {\n userId: this._config.userId,\n gameId: this._config.gameId,\n fields: this._fields\n };\n } else {\n listenPayload = `${this._config.userId}:${this._config.gameId}`;\n }\n\n this._socket.timeout(5000).emit('listen', listenPayload, (error, response) => {\n if (error) {\n return resolve({status: 'failed', reason: 'Listen request timed out.'});\n }\n if (response.status === 'success') {\n return resolve({status: 'success'});\n } else {\n return resolve({status: 'failed', reason: response.reason});\n }\n });\n });\n }\n\n setupEventListener() {\n // Listen for telemetry updates\n this._socket.on('update', (payload) => {\n // Filter events by gameId when present (multiple listeners share one socket)\n if (payload?.gameId && payload.gameId !== this._config.gameId) return;\n\n const rawData = payload?.data;\n\n // Normalize telemetry to canonical field names\n const normalizedData = rawData && this._gameSchema\n ? normalizeTelemetry(rawData, this._gameSchema)\n : rawData;\n\n // Apply client-side field filtering on normalized data\n let filteredData = normalizedData;\n if (this._fields && this._fields.length > 0 && normalizedData) {\n filteredData = {};\n for (const field of this._fields) {\n if (field in normalizedData) {\n filteredData[field] = normalizedData[field];\n }\n }\n }\n\n // Emit with both raw and normalized data\n this.emit('update', {\n ...payload,\n raw: rawData,\n data: filteredData\n });\n });\n\n // Listen for derived events (landing, takeoff, flight_phase, etc.)\n this._socket.on('key-events', (payload) => {\n const { gameId, eventType, data } = payload || {};\n\n // Only emit events for our game\n if (gameId !== this._config.gameId) {\n return;\n }\n\n // Emit specific event type (landing, takeoff, flight_phase)\n if (eventType && data) {\n this.emit(eventType, data);\n }\n });\n\n return this;\n }\n\n /**\n * Subscribe to additional fields dynamically\n * @param {string[]} fields - Array of field names to add\n * @returns {Promise<{status: string, reason?: string}>}\n */\n async subscribe(fields) {\n if (!Array.isArray(fields) || fields.length === 0) {\n throw new Error('fields must be a non-empty array');\n }\n\n // Add new fields to existing list\n if (!this._fields) {\n this._fields = [...fields];\n } else {\n for (const field of fields) {\n if (!this._fields.includes(field)) {\n this._fields.push(field);\n }\n }\n }\n\n return this._updateSubscription();\n }\n\n /**\n * Unsubscribe from specific fields\n * @param {string[]} fields - Array of field names to remove\n * @returns {Promise<{status: string, reason?: string}>}\n */\n async unsubscribe(fields) {\n if (!Array.isArray(fields) || fields.length === 0) {\n throw new Error('fields must be a non-empty array');\n }\n\n if (!this._fields) {\n // Currently receiving all fields, create explicit list without these fields\n throw new Error('Cannot unsubscribe when receiving all fields. Use subscribe() first to set explicit field list.');\n }\n\n this._fields = this._fields.filter(f => !fields.includes(f));\n\n return this._updateSubscription();\n }\n\n /**\n * Get the current list of subscribed fields\n * @returns {string[]|null} - Array of field names, or null if receiving all fields\n */\n getFields() {\n return this._fields ? [...this._fields] : null;\n }\n\n /**\n * Send a command to the broadcaster (game client)\n * @param {string} command - The canonical command name (e.g., 'gear_up', 'set_autopilot_heading')\n * @param {any} value - The value to set (optional for toggle commands)\n * @returns {Promise<{status: string, reason?: string}>}\n */\n async sendCommand(command, value) {\n if (!command || typeof command !== 'string') {\n throw new Error('command must be a non-empty string');\n }\n\n return new Promise((resolve) => {\n const payload = {\n userId: this._config.userId,\n gameId: this._config.gameId,\n data: {\n fieldName: command,\n value\n }\n };\n\n this._socket.timeout(5000).emit('set', payload, (error, response) => {\n if (error) {\n return resolve({ status: 'failed', reason: 'Command request timed out.' });\n }\n return resolve(response);\n });\n });\n }\n\n /**\n * Internal method to send subscription update to server\n */\n async _updateSubscription() {\n return new Promise((resolve) => {\n const payload = {\n userId: this._config.userId,\n gameId: this._config.gameId,\n fields: this._fields\n };\n\n this._socket.timeout(5000).emit('listen-update', payload, (error, response) => {\n if (error) {\n return resolve({status: 'failed', reason: 'Update request timed out.'});\n }\n return resolve(response);\n });\n });\n }\n}\n\nEventEmitter(Listener.prototype);\n","import EventEmitter from 'event-emitter';\nimport { getGameSchema, normalizeTelemetry } from '@gameglue/schemas';\n\nexport class PresenceListener {\n constructor(socket, config) {\n this._config = config;\n this._socket = socket;\n this._gameSchema = getGameSchema(config.gameId);\n this._broadcasters = [];\n }\n\n async establishConnection() {\n if (!this._socket || !this._config.clientId || !this._config.gameId) {\n throw new Error('Missing arguments in establishConnection');\n }\n\n return new Promise((resolve) => {\n const payload = {\n clientId: this._config.clientId,\n gameId: this._config.gameId\n };\n\n this._socket.timeout(5000).emit('presenceSubscribe', payload, (error, response) => {\n if (error) {\n return resolve({ status: 'failed', reason: 'Presence request timed out.' });\n }\n\n if (response?.status === 'success') {\n const broadcasters = Array.isArray(response.broadcasters)\n ? response.broadcasters.map((snapshot) => this._normalizeSnapshot(snapshot))\n : [];\n this._broadcasters = broadcasters;\n return resolve({ status: 'success', broadcasters });\n }\n\n return resolve({\n status: 'failed',\n reason: response?.reason || 'Presence subscription failed.'\n });\n });\n });\n }\n\n setupEventListener() {\n this._socket.on('presence-update', (snapshot) => {\n const normalized = this._normalizeSnapshot(snapshot);\n this._upsertBroadcaster(normalized);\n this.emit('update', normalized);\n });\n\n this._socket.on('presence-remove', ({ userId }) => {\n if (!userId) return;\n this._broadcasters = this._broadcasters.filter((b) => b.userId !== userId);\n this.emit('remove', { userId });\n });\n\n this._socket.on('connect', () => {\n this.emit('connect');\n });\n\n this._socket.on('disconnect', (reason) => {\n this.emit('disconnect', reason);\n });\n\n return this;\n }\n\n getBroadcasters() {\n return [...this._broadcasters];\n }\n\n disconnect() {\n if (this._socket) {\n this._socket.disconnect();\n }\n }\n\n _normalizeSnapshot(snapshot) {\n if (!snapshot || typeof snapshot !== 'object') {\n return snapshot;\n }\n\n const rawData = snapshot.data;\n const normalizedData = rawData && this._gameSchema\n ? normalizeTelemetry(rawData, this._gameSchema)\n : rawData;\n\n return {\n ...snapshot,\n data: normalizedData,\n raw: rawData\n };\n }\n\n _upsertBroadcaster(snapshot) {\n if (!snapshot || !snapshot.userId) {\n return;\n }\n\n const idx = this._broadcasters.findIndex((b) => b.userId === snapshot.userId);\n if (idx >= 0) {\n const next = [...this._broadcasters];\n next[idx] = snapshot;\n this._broadcasters = next;\n return;\n }\n\n this._broadcasters = [...this._broadcasters, snapshot];\n }\n}\n\nEventEmitter(PresenceListener.prototype);\n","import { GameGlueAuth } from './auth';\nimport { io } from \"socket.io-client\";\nimport { Listener } from \"./listener\";\nimport { PresenceListener } from \"./presence_listener\";\nimport { isCorsError, logCorsHelp } from './utils';\nimport { getGameSchema, getCategorySchema, normalizeTelemetry } from '@gameglue/schemas';\n\nconst GAME_IDS = {\n 'msfs': true,\n 'xplane': true,\n};\n\nconst DEFAULT_SOCKET_URL = 'https://socks.gameglue.gg';\n\nclass GameGlue extends GameGlueAuth {\n constructor(cfg) {\n super(cfg);\n this._socket = null;\n this._socketUrl = cfg.socketUrl || DEFAULT_SOCKET_URL;\n this._connectPromise = null;\n }\n\n /**\n * Create a listener for game telemetry.\n * Connects to socket server lazily on first call.\n * @param {Object} config - { userId, gameId, fields? }\n * @returns {Promise<Listener>}\n */\n async createListener(config) {\n if (!config) throw new Error('Not a valid listener config');\n if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');\n if (!config.userId) throw new Error('User ID not supplied');\n if (config.fields && !Array.isArray(config.fields)) throw new Error('fields must be an array');\n\n // Ensure socket is connected (lazy initialization)\n await this._ensureConnected();\n\n const listener = new Listener(this._socket, config);\n const establishConnectionResponse = await listener.establishConnection();\n\n // Handle reconnection\n this._socket.io.on('reconnect_attempt', () => {\n this._updateSocketAuth(this.getAccessToken());\n });\n this._socket.io.on('reconnect', () => {\n listener.establishConnection();\n });\n\n if (establishConnectionResponse.status !== 'success') {\n throw new Error(`There was a problem setting up the listener. Reason: ${establishConnectionResponse.reason}`);\n }\n\n return listener.setupEventListener();\n }\n\n /**\n * Create a presence listener for public broadcaster updates.\n * This does not require authentication.\n * @param {Object} config - { clientId, gameId }\n * @returns {Promise<PresenceListener>}\n */\n async createPresenceListener(config) {\n if (!config) throw new Error('Not a valid presence listener config');\n if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');\n if (!config.clientId) throw new Error('Client ID not supplied');\n\n const socket = io(this._socketUrl, {\n transports: ['websocket'],\n });\n\n await new Promise((resolve, reject) => {\n socket.on('connect', resolve);\n socket.on('connect_error', (err) => {\n if (isCorsError(err)) {\n logCorsHelp('WebSocket Connection', this._socketUrl);\n }\n reject(new Error(`Socket connection failed: ${err.message}`));\n });\n });\n\n const listener = new PresenceListener(socket, config);\n const establishConnectionResponse = await listener.establishConnection();\n\n // Handle reconnection\n socket.io.on('reconnect', () => {\n listener.establishConnection();\n });\n\n if (establishConnectionResponse.status !== 'success') {\n throw new Error(\n `There was a problem setting up the presence listener. Reason: ${establishConnectionResponse.reason}`\n );\n }\n\n return listener.setupEventListener();\n }\n\n // ============ Internal Methods ============\n\n async _ensureConnected() {\n // Already connected\n if (this._socket?.connected) {\n return;\n }\n\n // Connection in progress - wait for it\n if (this._connectPromise) {\n await this._connectPromise;\n return;\n }\n\n // Start new connection\n this._connectPromise = this._connect();\n\n try {\n await this._connectPromise;\n } finally {\n this._connectPromise = null;\n }\n }\n\n _connect() {\n return new Promise((resolve, reject) => {\n const token = this.getAccessToken();\n\n if (!token) {\n reject(new Error('Not authenticated - call isAuthenticated() first'));\n return;\n }\n\n this._socket = io(this._socketUrl, {\n transports: ['websocket'],\n auth: { token }\n });\n\n this._socket.on('connect', () => {\n resolve();\n });\n\n this._socket.on('connect_error', (err) => {\n if (isCorsError(err)) {\n logCorsHelp('WebSocket Connection', this._socketUrl);\n }\n reject(new Error(`Socket connection failed: ${err.message}`));\n });\n\n // Update socket auth when token refreshes\n this.onTokenRefreshed((newToken) => {\n this._updateSocketAuth(newToken);\n });\n });\n }\n\n _updateSocketAuth(authToken) {\n if (this._socket) {\n this._socket.auth.token = authToken;\n }\n }\n}\n\nif (typeof window !== 'undefined') {\n window.GameGlue = GameGlue;\n // Expose schema utilities for validation tools\n window.GameGlueSchemas = { getGameSchema, getCategorySchema, normalizeTelemetry };\n}\n\nexport default GameGlue;\nexport { GameGlue, getGameSchema, getCategorySchema, normalizeTelemetry };\n"],"names":["storageMap","storage","key","value","isBrowser","localStorage","setItem","getItem","removeItem","process","String","isCorsError","error","msg","message","toString","toLowerCase","includes","logCorsHelp","context","url","console","padEnd","substring","_callbackPromise","GameGlueAuth","constructor","cfg","authority","authUrl","this","_oidcSettings","client_id","clientId","redirect_uri","removeTrailingSlashes","window","location","href","post_logout_redirect_uri","response_type","scope","scopes","join","response_mode","filterProtocolClaims","_oidcClient","OidcClient","_refreshCallback","_refreshTimeout","isAuthenticated","_hasCallbackParams","_processCallback","_hasValidTokens","login","createSigninRequest","state","bar","then","req","catch","err","logout","options","clearTimeout","redirect","logoutUrl","encodeURIComponent","getUser","token","_getAccessToken","Error","jwt_decode","sub","getAccessToken","onTokenRefreshed","callback","hash","_clearCallbackUrl","history","replaceState","document","title","pathname","search","_doProcessCallback","response","processSigninResponse","access_token","_setAccessToken","_setRefreshToken","refresh_token","decoded","Date","exp","_setTokenRefreshTimeout","undefined","_getRefreshToken","timeUntilExp","now","setTimeout","_attemptRefresh","fetch","method","headers","body","URLSearchParams","grant_type","status","resObj","json","e","endsWith","replace","transforms","radiansToDegrees","Math","PI","degreesToRadians","metersToFeet","feetToMeters","msToKnots","knotsToMs","msToFpm","toBoolean","Boolean","divideBy100","flightSimSchema","category","name","description","recommendedUpdateRateHz","requiredFields","fields","latitude","type","unit","min","max","group","longitude","altitude","altitude_agl","on_ground","indicated_airspeed","true_airspeed","ground_speed","mach","vertical_speed","g_force","heading","true_heading","pitch","roll","yaw","throttle_0","throttle_1","throttle_2","throttle_3","rpm_0","rpm_1","rpm_2","rpm_3","n1_0","n1_1","n1_2","n1_3","n2_0","n2_1","n2_2","n2_3","egt_0","egt_1","egt_2","egt_3","fuel_quantity","fuel_flow_0","fuel_flow_1","fuel_flow_2","fuel_flow_3","autopilot_master","autopilot_altitude_hold","autopilot_heading_hold","autopilot_vs_hold","autopilot_speed_hold","autopilot_approach","autopilot_nav","flight_director","autopilot_flc","autopilot_altitude_target","autopilot_heading_target","autopilot_vs_target","autopilot_speed_target","flaps","spoilers","gear_down","parking_brake","brake_left","brake_right","light_landing","light_taxi","light_beacon","light_nav","light_strobe","outside_air_temp","barometric_pressure","wind_direction","wind_speed","stall_warning","overspeed_warning","sim_paused","gps_wp_distance","gps_wp_ete","commands","autopilot_on","autopilot_off","autopilot_toggle","autopilot_altitude_hold_on","autopilot_altitude_hold_off","autopilot_heading_hold_on","autopilot_heading_hold_off","autopilot_vs_hold_on","autopilot_vs_hold_off","autopilot_nav_on","autopilot_nav_off","autopilot_approach_on","autopilot_approach_off","flight_director_on","flight_director_off","autopilot_flc_on","autopilot_flc_off","autopilot_flc_toggle","set_autopilot_altitude","paramType","paramDescription","paramMin","paramMax","set_autopilot_heading","set_autopilot_vs","set_autopilot_speed","gear_up","gear_toggle","flaps_up","flaps_down","flaps_full","flaps_retract","set_flaps","spoilers_arm","spoilers_deploy","spoilers_retract","spoilers_toggle","parking_brake_toggle","parking_brake_on","parking_brake_off","set_throttle","set_throttle_0","set_throttle_1","set_throttle_2","set_throttle_3","throttle_full","throttle_idle","throttle_cutoff","landing_lights_on","landing_lights_off","landing_lights_toggle","taxi_lights_on","taxi_lights_off","taxi_lights_toggle","beacon_lights_on","beacon_lights_off","beacon_lights_toggle","nav_lights_on","nav_lights_off","nav_lights_toggle","strobe_lights_on","strobe_lights_off","strobe_lights_toggle","pause","unpause","pause_toggle","categorySchemas","flight_sim","racing_sim","getCategorySchema","gameSchemas","msfs","gameId","msfsMappings","fieldMappings","commandMappings","extraFields","pitot_heat","fuel_quantity_gallons","fuel_flow_gph_1","fuel_flow_gph_2","fuel_flow_gph_3","fuel_flow_gph_4","flaps_left_percent","flaps_right_percent","spoilers_left_percent","spoilers_right_percent","gear_left_position","gear_center_position","gear_right_position","ambient_pressure","sea_level_pressure","aircraft_wind_x","aircraft_wind_y","aircraft_wind_z","xplane","xplaneMappings","replay_mode","time_local","time_zulu","getGameSchema","normalizeTelemetry","raw","gameSchema","normalized","rawField","Object","entries","mapping","normalizedValue","transform","transformFn","canonical","Listener","socket","config","_config","_socket","_callbacks","_fields","_gameSchema","establishConnection","userId","Promise","resolve","listenPayload","timeout","emit","reason","setupEventListener","on","payload","rawData","data","normalizedData","filteredData","length","field","eventType","subscribe","Array","isArray","push","_updateSubscription","unsubscribe","filter","f","getFields","sendCommand","command","fieldName","EventEmitter","prototype","PresenceListener","_broadcasters","broadcasters","map","snapshot","_normalizeSnapshot","_upsertBroadcaster","b","getBroadcasters","disconnect","idx","findIndex","next","GAME_IDS","GameGlue","super","_socketUrl","socketUrl","_connectPromise","createListener","_ensureConnected","listener","establishConnectionResponse","io","_updateSocketAuth","createPresenceListener","transports","reject","connected","_connect","auth","newToken","authToken","GameGlueSchemas"],"mappings":"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"}
1
+ {"version":3,"file":"gg.cjs.js","sources":["../src/utils.js","../src/auth.js","../../schemas/dist/types.js","../../schemas/dist/categories/flight_sim.js","../../schemas/dist/categories/index.js","../../schemas/dist/games/xplane.js","../../schemas/dist/games/index.js","../../schemas/dist/games/msfs.js","../../schemas/dist/normalizer.js","../src/listener.js","../src/presence_listener.js","../src/index.js"],"sourcesContent":["const storageMap = {};\r\nexport const storage = {\r\n set: (key, value) => {\r\n return isBrowser() ? localStorage.setItem(key, value) : (storageMap[key] = value);\r\n },\r\n get: (key) => {\r\n return isBrowser() ? localStorage.getItem(key) : storageMap[key];\r\n },\r\n remove: (key) => {\r\n return isBrowser() ? localStorage.removeItem(key) : delete storageMap[key];\r\n }\r\n};\r\nexport const isBrowser = () => {\r\n return !(typeof process === 'object' && String(process) === '[object process]');\r\n}\r\n\r\n/**\r\n * Detect if an error is likely a CORS error.\r\n * CORS errors in browsers are intentionally vague for security, so we look for common patterns.\r\n */\r\nexport function isCorsError(error) {\r\n if (!error) return false;\r\n const msg = (error.message || error.toString() || '').toLowerCase();\r\n\r\n // Common CORS error patterns\r\n return (\r\n msg.includes('cors') ||\r\n msg.includes('cross-origin') ||\r\n msg.includes('network error') ||\r\n msg.includes('failed to fetch') ||\r\n msg.includes('networkerror') ||\r\n msg.includes('load failed') ||\r\n // Socket.io specific patterns for CORS failures\r\n msg.includes('xhr poll error') ||\r\n msg.includes('websocket error') ||\r\n (msg.includes('transport') && msg.includes('error'))\r\n );\r\n}\r\n\r\n/**\r\n * Log helpful CORS debugging guidance to the console.\r\n */\r\nexport function logCorsHelp(context, url) {\r\n console.error(`\r\n╔══════════════════════════════════════════════════════════════════════════════╗\r\n║ GameGlue SDK: Possible CORS Error Detected ║\r\n╠══════════════════════════════════════════════════════════════════════════════╣\r\n║ Context: ${context.padEnd(66)}║\r\n║ URL: ${(url || 'unknown').substring(0, 70).padEnd(70)}║\r\n╠══════════════════════════════════════════════════════════════════════════════╣\r\n║ This error typically means the server rejected the request due to CORS. ║\r\n║ ║\r\n║ To fix this, add your origin to your app's allowed Web Origins: ║\r\n║ ║\r\n║ 1. Go to the GameGlue Developer Portal (https://developer.gameglue.gg) ║\r\n║ 2. Select your app ║\r\n║ 3. Add your origin to \"Web Origins\" and save ║\r\n║ ║\r\n║ IMPORTANT: Protocol and port must match exactly. ║\r\n║ http://localhost:3000 and https://localhost:3000 are different origins. ║\r\n║ http://localhost:3000 and http://localhost:5000 are different origins. ║\r\n╚══════════════════════════════════════════════════════════════════════════════╝\r\n`);\r\n}","import { OidcClient } from 'oidc-client-ts';\r\nimport { storage, isCorsError, logCorsHelp } from './utils';\r\nimport jwt_decode from 'jwt-decode';\r\n\r\nconst DEFAULT_AUTH_URL = 'https://auth.gameglue.gg/realms/GameGlue';\r\n\r\n// Track if callback is being processed (prevents double-processing)\r\nlet _callbackPromise = null;\r\n\r\nexport class GameGlueAuth {\r\n constructor(cfg) {\r\n const authority = cfg.authUrl || DEFAULT_AUTH_URL;\r\n this._oidcSettings = {\r\n authority,\r\n client_id: cfg.clientId,\r\n redirect_uri: removeTrailingSlashes(cfg.redirect_uri || window.location.href),\r\n post_logout_redirect_uri: removeTrailingSlashes(window.location.href),\r\n response_type: \"code\",\r\n scope: `openid ${(cfg.scopes || []).join(' ')}`,\r\n response_mode: \"fragment\",\r\n filterProtocolClaims: true\r\n };\r\n this._oidcClient = new OidcClient(this._oidcSettings);\r\n this._refreshCallback = () => {};\r\n this._refreshTimeout = null;\r\n }\r\n\r\n /**\r\n * Check if user is authenticated.\r\n * If OAuth callback params are in URL, processes them first.\r\n * Safe to call multiple times - idempotent.\r\n * @returns {Promise<boolean>}\r\n */\r\n async isAuthenticated() {\r\n // If callback params present, process them first\r\n if (this._hasCallbackParams()) {\r\n await this._processCallback();\r\n }\r\n\r\n // Check for valid tokens\r\n return this._hasValidTokens();\r\n }\r\n\r\n /**\r\n * Redirect to OAuth login page.\r\n * Does not return - navigates away.\r\n */\r\n login() {\r\n this._oidcClient.createSigninRequest({ state: { bar: 15 } }).then((req) => {\r\n window.location = req.url;\r\n }).catch((err) => {\r\n if (isCorsError(err)) {\r\n logCorsHelp('Login Request', this._oidcSettings.authority);\r\n }\r\n console.error('Failed to create signin request:', err);\r\n });\r\n }\r\n\r\n /**\r\n * Log out the user.\r\n * Clears local tokens and optionally redirects to Keycloak logout.\r\n * @param {Object} options - { redirect?: boolean }\r\n */\r\n logout(options = {}) {\r\n // Clear local tokens\r\n storage.remove('gg-auth-token');\r\n storage.remove('gg-refresh-token');\r\n clearTimeout(this._refreshTimeout);\r\n\r\n // Optionally redirect to Keycloak logout\r\n if (options.redirect !== false) {\r\n const logoutUrl = `${this._oidcSettings.authority}/protocol/openid-connect/logout?post_logout_redirect_uri=${encodeURIComponent(this._oidcSettings.post_logout_redirect_uri)}`;\r\n window.location.href = logoutUrl;\r\n }\r\n }\r\n\r\n /**\r\n * Get the current user's ID.\r\n * @throws {Error} if not authenticated\r\n * @returns {string}\r\n */\r\n getUser() {\r\n const token = this._getAccessToken();\r\n if (!token) {\r\n throw new Error('Not authenticated');\r\n }\r\n const decoded = jwt_decode(token);\r\n return decoded.sub;\r\n }\r\n\r\n /**\r\n * Get the access token for API calls.\r\n * @returns {string|null}\r\n */\r\n getAccessToken() {\r\n return this._getAccessToken();\r\n }\r\n\r\n /**\r\n * Register callback for token refresh events.\r\n * @param {Function} callback\r\n */\r\n onTokenRefreshed(callback) {\r\n this._refreshCallback = callback;\r\n }\r\n\r\n // ============ Internal Methods ============\r\n\r\n _hasCallbackParams() {\r\n return location.hash.includes(\"state=\") &&\r\n (location.hash.includes(\"code=\") || location.hash.includes(\"error=\"));\r\n }\r\n\r\n _clearCallbackUrl() {\r\n window.history.replaceState(\"\", document.title, window.location.pathname + window.location.search);\r\n }\r\n\r\n async _processCallback() {\r\n // If already processing, wait for that to complete\r\n if (_callbackPromise) {\r\n await _callbackPromise;\r\n return;\r\n }\r\n\r\n // Start processing\r\n _callbackPromise = this._doProcessCallback();\r\n\r\n try {\r\n await _callbackPromise;\r\n } finally {\r\n _callbackPromise = null;\r\n }\r\n }\r\n\r\n async _doProcessCallback() {\r\n try {\r\n const response = await this._oidcClient.processSigninResponse(window.location.href);\r\n\r\n if (response.error) {\r\n this._clearCallbackUrl();\r\n throw new Error(response.error);\r\n }\r\n\r\n if (!response.access_token) {\r\n this._clearCallbackUrl();\r\n throw new Error('No access token received');\r\n }\r\n\r\n this._setAccessToken(response.access_token);\r\n this._setRefreshToken(response.refresh_token);\r\n this._clearCallbackUrl();\r\n } catch (err) {\r\n // If we failed but tokens exist (another call succeeded), that's fine\r\n if (this._hasValidTokens()) {\r\n this._clearCallbackUrl();\r\n return;\r\n }\r\n this._clearCallbackUrl();\r\n throw err;\r\n }\r\n }\r\n\r\n _hasValidTokens() {\r\n const token = this._getAccessToken();\r\n if (!token) {\r\n return false;\r\n }\r\n\r\n try {\r\n const decoded = jwt_decode(token);\r\n const expirationDate = new Date(decoded.exp * 1000);\r\n return expirationDate > new Date();\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n _getAccessToken() {\r\n const token = storage.get('gg-auth-token');\r\n if (token) {\r\n this._setTokenRefreshTimeout(token);\r\n }\r\n return token || undefined;\r\n }\r\n\r\n _setAccessToken(token) {\r\n this._setTokenRefreshTimeout(token);\r\n return storage.set('gg-auth-token', token);\r\n }\r\n\r\n _setRefreshToken(token) {\r\n return storage.set('gg-refresh-token', token);\r\n }\r\n\r\n _getRefreshToken() {\r\n return storage.get('gg-refresh-token');\r\n }\r\n\r\n _setTokenRefreshTimeout(token) {\r\n if (!token) return;\r\n\r\n clearTimeout(this._refreshTimeout);\r\n\r\n try {\r\n const timeUntilExp = (jwt_decode(token).exp * 1000) - Date.now() - 5000;\r\n if (timeUntilExp > 0) {\r\n this._refreshTimeout = setTimeout(() => {\r\n this._attemptRefresh();\r\n }, timeUntilExp);\r\n }\r\n } catch {\r\n // Invalid token, ignore\r\n }\r\n }\r\n\r\n async _attemptRefresh() {\r\n const url = `${this._oidcSettings.authority}/protocol/openid-connect/token`;\r\n const client_id = this._oidcSettings.client_id;\r\n const refresh_token = this._getRefreshToken();\r\n const grant_type = 'refresh_token';\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded'\r\n },\r\n body: new URLSearchParams({\r\n client_id,\r\n grant_type,\r\n refresh_token\r\n })\r\n });\r\n\r\n if (response.status === 200) {\r\n const resObj = await response.json();\r\n this._setAccessToken(resObj.access_token);\r\n this._setRefreshToken(resObj.refresh_token);\r\n this._refreshCallback(resObj.access_token);\r\n }\r\n } catch (e) {\r\n if (isCorsError(e)) {\r\n logCorsHelp('Token Refresh', url);\r\n }\r\n console.error('Token refresh failed:', e);\r\n }\r\n }\r\n}\r\n\r\nfunction removeTrailingSlashes(url) {\r\n if (url.endsWith('/')) {\r\n return url.replace(/\\/+$/, '');\r\n }\r\n return url;\r\n}\r\n","/**\n * Schema type definitions for GameGlue telemetry normalization\n */\n/**\n * Transform functions for converting values between game-specific and canonical formats\n */\nexport const transforms = {\n /** Convert radians to degrees */\n radiansToDegrees: (value) => value * (180 / Math.PI),\n /** Convert degrees to radians */\n degreesToRadians: (value) => value * (Math.PI / 180),\n /** Convert meters to feet */\n metersToFeet: (value) => value * 3.28084,\n /** Convert feet to meters */\n feetToMeters: (value) => value / 3.28084,\n /** Convert m/s to knots */\n msToKnots: (value) => value * 1.94384,\n /** Convert knots to m/s */\n knotsToMs: (value) => value / 1.94384,\n /** Convert meters per second to feet per minute */\n msToFpm: (value) => value * 196.85,\n /** Convert boolean-like (0/1) to boolean */\n toBoolean: (value) => Boolean(value),\n /** Divide by 100 (for values that come as percent * 100) */\n divideBy100: (value) => value / 100,\n};\n","/**\n * Canonical schema for flight simulators.\n * These are the normalized field names that developers code against.\n * Individual sim schemas map their game-specific fields to these canonical names.\n */\nexport const flightSimSchema = {\n category: 'flight_sim',\n name: 'Flight Simulator',\n description: 'Canonical telemetry fields for flight simulation games',\n recommendedUpdateRateHz: 10,\n requiredFields: [\n 'latitude',\n 'longitude',\n 'altitude',\n 'indicated_airspeed',\n 'heading',\n 'vertical_speed',\n 'on_ground',\n ],\n fields: {\n // ===========================================\n // POSITION\n // ===========================================\n latitude: {\n type: 'number',\n description: 'Aircraft latitude',\n unit: 'degrees',\n min: -90,\n max: 90,\n group: 'Position',\n },\n longitude: {\n type: 'number',\n description: 'Aircraft longitude',\n unit: 'degrees',\n min: -180,\n max: 180,\n group: 'Position',\n },\n altitude: {\n type: 'number',\n description: 'Aircraft altitude above sea level',\n unit: 'ft',\n min: -2000,\n max: 100000,\n group: 'Position',\n },\n altitude_agl: {\n type: 'number',\n description: 'Aircraft altitude above ground level',\n unit: 'ft',\n min: 0,\n max: 100000,\n group: 'Position',\n },\n on_ground: {\n type: 'boolean',\n description: 'Whether aircraft is on the ground',\n group: 'Position',\n },\n // ===========================================\n // AIRSPEED & VELOCITY\n // ===========================================\n indicated_airspeed: {\n type: 'number',\n description: 'Indicated airspeed',\n unit: 'kts',\n min: 0,\n max: 1000,\n group: 'Velocity',\n },\n true_airspeed: {\n type: 'number',\n description: 'True airspeed',\n unit: 'kts',\n min: 0,\n max: 1000,\n group: 'Velocity',\n },\n ground_speed: {\n type: 'number',\n description: 'Ground speed',\n unit: 'kts',\n min: 0,\n max: 1000,\n group: 'Velocity',\n },\n mach: {\n type: 'number',\n description: 'Mach number',\n min: 0,\n max: 5,\n group: 'Velocity',\n },\n vertical_speed: {\n type: 'number',\n description: 'Vertical speed',\n unit: 'ft/min',\n min: -20000,\n max: 20000,\n group: 'Velocity',\n },\n g_force: {\n type: 'number',\n description: 'Current G-force',\n unit: 'G',\n min: -10,\n max: 10,\n group: 'Velocity',\n },\n // ===========================================\n // ATTITUDE\n // ===========================================\n heading: {\n type: 'number',\n description: 'Aircraft magnetic heading',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Attitude',\n },\n true_heading: {\n type: 'number',\n description: 'Aircraft true heading',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Attitude',\n },\n pitch: {\n type: 'number',\n description: 'Aircraft pitch angle',\n unit: 'degrees',\n min: -90,\n max: 90,\n group: 'Attitude',\n },\n roll: {\n type: 'number',\n description: 'Aircraft roll/bank angle',\n unit: 'degrees',\n min: -180,\n max: 180,\n group: 'Attitude',\n },\n yaw: {\n type: 'number',\n description: 'Aircraft yaw angle',\n unit: 'degrees',\n min: -180,\n max: 180,\n group: 'Attitude',\n },\n // ===========================================\n // ENGINE (generic - up to 4 engines, 0-indexed)\n // ===========================================\n throttle_0: {\n type: 'number',\n description: 'Engine 0 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n throttle_1: {\n type: 'number',\n description: 'Engine 1 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n throttle_2: {\n type: 'number',\n description: 'Engine 2 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n throttle_3: {\n type: 'number',\n description: 'Engine 3 throttle position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Engine',\n },\n rpm_0: {\n type: 'number',\n description: 'Engine 0 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n rpm_1: {\n type: 'number',\n description: 'Engine 1 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n rpm_2: {\n type: 'number',\n description: 'Engine 2 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n rpm_3: {\n type: 'number',\n description: 'Engine 3 RPM',\n unit: 'RPM',\n min: 0,\n max: 10000,\n group: 'Engine',\n },\n n1_0: {\n type: 'number',\n description: 'Engine 0 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n1_1: {\n type: 'number',\n description: 'Engine 1 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n1_2: {\n type: 'number',\n description: 'Engine 2 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n1_3: {\n type: 'number',\n description: 'Engine 3 N1 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_0: {\n type: 'number',\n description: 'Engine 0 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_1: {\n type: 'number',\n description: 'Engine 1 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_2: {\n type: 'number',\n description: 'Engine 2 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n n2_3: {\n type: 'number',\n description: 'Engine 3 N2 (turbine)',\n unit: '%',\n min: 0,\n max: 120,\n group: 'Engine',\n },\n egt_0: {\n type: 'number',\n description: 'Engine 0 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n egt_1: {\n type: 'number',\n description: 'Engine 1 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n egt_2: {\n type: 'number',\n description: 'Engine 2 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n egt_3: {\n type: 'number',\n description: 'Engine 3 exhaust gas temperature',\n unit: '°C',\n min: 0,\n max: 1200,\n group: 'Engine',\n },\n // ===========================================\n // FUEL\n // ===========================================\n fuel_quantity: {\n type: 'number',\n description: 'Total fuel quantity',\n unit: 'lbs',\n min: 0,\n max: 600000,\n group: 'Fuel',\n },\n fuel_flow_0: {\n type: 'number',\n description: 'Engine 0 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n fuel_flow_1: {\n type: 'number',\n description: 'Engine 1 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n fuel_flow_2: {\n type: 'number',\n description: 'Engine 2 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n fuel_flow_3: {\n type: 'number',\n description: 'Engine 3 fuel flow',\n unit: 'lbs/hr',\n min: 0,\n max: 30000,\n group: 'Fuel',\n },\n // ===========================================\n // AUTOPILOT\n // ===========================================\n autopilot_master: {\n type: 'boolean',\n description: 'Autopilot master engaged',\n group: 'Autopilot',\n },\n autopilot_altitude_hold: {\n type: 'boolean',\n description: 'Altitude hold engaged',\n group: 'Autopilot',\n },\n autopilot_heading_hold: {\n type: 'boolean',\n description: 'Heading hold engaged',\n group: 'Autopilot',\n },\n autopilot_vs_hold: {\n type: 'boolean',\n description: 'Vertical speed hold engaged',\n group: 'Autopilot',\n },\n autopilot_speed_hold: {\n type: 'boolean',\n description: 'Speed hold engaged',\n group: 'Autopilot',\n },\n autopilot_approach: {\n type: 'boolean',\n description: 'Approach mode engaged',\n group: 'Autopilot',\n },\n autopilot_nav: {\n type: 'boolean',\n description: 'NAV mode engaged',\n group: 'Autopilot',\n },\n flight_director: {\n type: 'boolean',\n description: 'Flight director active',\n group: 'Autopilot',\n },\n autopilot_flc: {\n type: 'boolean',\n description: 'Flight level change mode engaged',\n group: 'Autopilot',\n },\n autopilot_altitude_target: {\n type: 'number',\n description: 'Autopilot target altitude',\n unit: 'ft',\n min: 0,\n max: 60000,\n group: 'Autopilot',\n },\n autopilot_heading_target: {\n type: 'number',\n description: 'Autopilot target heading',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Autopilot',\n },\n autopilot_vs_target: {\n type: 'number',\n description: 'Autopilot target vertical speed',\n unit: 'ft/min',\n min: -10000,\n max: 10000,\n group: 'Autopilot',\n },\n autopilot_speed_target: {\n type: 'number',\n description: 'Autopilot target airspeed',\n unit: 'kts',\n min: 0,\n max: 600,\n group: 'Autopilot',\n },\n // ===========================================\n // FLIGHT CONTROLS\n // ===========================================\n flaps: {\n type: 'number',\n description: 'Flaps position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n spoilers: {\n type: 'number',\n description: 'Spoilers position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_down: {\n type: 'boolean',\n description: 'Landing gear extended',\n group: 'Controls',\n },\n parking_brake: {\n type: 'boolean',\n description: 'Parking brake engaged',\n group: 'Controls',\n },\n brake_left: {\n type: 'number',\n description: 'Left brake position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n brake_right: {\n type: 'number',\n description: 'Right brake position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n // ===========================================\n // LIGHTS\n // ===========================================\n light_landing: {\n type: 'boolean',\n description: 'Landing lights on',\n group: 'Lights',\n },\n light_taxi: {\n type: 'boolean',\n description: 'Taxi lights on',\n group: 'Lights',\n },\n light_beacon: {\n type: 'boolean',\n description: 'Beacon lights on',\n group: 'Lights',\n },\n light_nav: {\n type: 'boolean',\n description: 'Navigation lights on',\n group: 'Lights',\n },\n light_strobe: {\n type: 'boolean',\n description: 'Strobe lights on',\n group: 'Lights',\n },\n // ===========================================\n // ENVIRONMENT\n // ===========================================\n outside_air_temp: {\n type: 'number',\n description: 'Outside air temperature',\n unit: '°C',\n min: -80,\n max: 60,\n group: 'Environment',\n },\n barometric_pressure: {\n type: 'number',\n description: 'Barometric pressure setting',\n unit: 'inHg',\n min: 27,\n max: 32,\n group: 'Environment',\n },\n wind_direction: {\n type: 'number',\n description: 'Wind direction',\n unit: 'degrees',\n min: 0,\n max: 360,\n group: 'Environment',\n },\n wind_speed: {\n type: 'number',\n description: 'Wind speed',\n unit: 'kts',\n min: 0,\n max: 200,\n group: 'Environment',\n },\n // ===========================================\n // WARNINGS\n // ===========================================\n stall_warning: {\n type: 'boolean',\n description: 'Stall warning active',\n group: 'Warnings',\n },\n overspeed_warning: {\n type: 'boolean',\n description: 'Overspeed warning active',\n group: 'Warnings',\n },\n // ===========================================\n // SIM STATE\n // ===========================================\n sim_paused: {\n type: 'boolean',\n description: 'Simulation is paused',\n group: 'Simulation',\n },\n sim_rate: {\n type: 'number',\n description: 'Simulation rate multiplier (1.0 = real time)',\n group: 'Simulation',\n },\n // ===========================================\n // GPS / NAVIGATION\n // ===========================================\n gps_wp_distance: {\n type: 'number',\n description: 'Distance to next GPS waypoint',\n unit: 'nm',\n min: 0,\n max: 10000,\n group: 'Navigation',\n },\n gps_wp_ete: {\n type: 'number',\n description: 'Estimated time enroute to next GPS waypoint',\n unit: 'seconds',\n min: 0,\n max: 86400,\n group: 'Navigation',\n },\n },\n commands: {\n // ===========================================\n // AUTOPILOT COMMANDS\n // ===========================================\n autopilot_on: {\n description: 'Engage autopilot master',\n group: 'Autopilot',\n },\n autopilot_off: {\n description: 'Disengage autopilot master',\n group: 'Autopilot',\n },\n autopilot_toggle: {\n description: 'Toggle autopilot master',\n group: 'Autopilot',\n },\n autopilot_altitude_hold_on: {\n description: 'Engage altitude hold',\n group: 'Autopilot',\n },\n autopilot_altitude_hold_off: {\n description: 'Disengage altitude hold',\n group: 'Autopilot',\n },\n autopilot_heading_hold_on: {\n description: 'Engage heading hold',\n group: 'Autopilot',\n },\n autopilot_heading_hold_off: {\n description: 'Disengage heading hold',\n group: 'Autopilot',\n },\n autopilot_vs_hold_on: {\n description: 'Engage vertical speed hold',\n group: 'Autopilot',\n },\n autopilot_vs_hold_off: {\n description: 'Disengage vertical speed hold',\n group: 'Autopilot',\n },\n autopilot_nav_on: {\n description: 'Engage NAV mode',\n group: 'Autopilot',\n },\n autopilot_nav_off: {\n description: 'Disengage NAV mode',\n group: 'Autopilot',\n },\n autopilot_approach_on: {\n description: 'Engage approach mode',\n group: 'Autopilot',\n },\n autopilot_approach_off: {\n description: 'Disengage approach mode',\n group: 'Autopilot',\n },\n flight_director_on: {\n description: 'Engage flight director',\n group: 'Autopilot',\n },\n flight_director_off: {\n description: 'Disengage flight director',\n group: 'Autopilot',\n },\n autopilot_flc_on: {\n description: 'Engage flight level change mode',\n group: 'Autopilot',\n },\n autopilot_flc_off: {\n description: 'Disengage flight level change mode',\n group: 'Autopilot',\n },\n autopilot_flc_toggle: {\n description: 'Toggle flight level change mode',\n group: 'Autopilot',\n },\n set_autopilot_altitude: {\n description: 'Set autopilot target altitude',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target altitude in feet',\n paramMin: 0,\n paramMax: 60000,\n },\n set_autopilot_heading: {\n description: 'Set autopilot target heading',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target heading in degrees',\n paramMin: 0,\n paramMax: 360,\n },\n set_autopilot_vs: {\n description: 'Set autopilot target vertical speed',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target vertical speed in ft/min',\n paramMin: -10000,\n paramMax: 10000,\n },\n set_autopilot_speed: {\n description: 'Set autopilot target airspeed',\n group: 'Autopilot',\n paramType: 'number',\n paramDescription: 'Target airspeed in knots',\n paramMin: 0,\n paramMax: 600,\n },\n // ===========================================\n // GEAR COMMANDS\n // ===========================================\n gear_up: {\n description: 'Retract landing gear',\n group: 'Controls',\n },\n gear_down: {\n description: 'Extend landing gear',\n group: 'Controls',\n },\n gear_toggle: {\n description: 'Toggle landing gear',\n group: 'Controls',\n },\n // ===========================================\n // FLAPS COMMANDS\n // ===========================================\n flaps_up: {\n description: 'Retract flaps one notch',\n group: 'Controls',\n },\n flaps_down: {\n description: 'Extend flaps one notch',\n group: 'Controls',\n },\n flaps_full: {\n description: 'Extend flaps fully',\n group: 'Controls',\n },\n flaps_retract: {\n description: 'Fully retract flaps',\n group: 'Controls',\n },\n set_flaps: {\n description: 'Set flaps to specific position',\n group: 'Controls',\n paramType: 'number',\n paramDescription: 'Flaps position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n // ===========================================\n // SPOILERS COMMANDS\n // ===========================================\n spoilers_arm: {\n description: 'Arm spoilers',\n group: 'Controls',\n },\n spoilers_deploy: {\n description: 'Deploy spoilers',\n group: 'Controls',\n },\n spoilers_retract: {\n description: 'Retract spoilers',\n group: 'Controls',\n },\n spoilers_toggle: {\n description: 'Toggle spoilers',\n group: 'Controls',\n },\n // ===========================================\n // BRAKES COMMANDS\n // ===========================================\n parking_brake_toggle: {\n description: 'Toggle parking brake',\n group: 'Controls',\n },\n parking_brake_on: {\n description: 'Engage parking brake',\n group: 'Controls',\n },\n parking_brake_off: {\n description: 'Release parking brake',\n group: 'Controls',\n },\n // ===========================================\n // THROTTLE COMMANDS\n // ===========================================\n set_throttle: {\n description: 'Set all throttles',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_0: {\n description: 'Set engine 0 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_1: {\n description: 'Set engine 1 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_2: {\n description: 'Set engine 2 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n set_throttle_3: {\n description: 'Set engine 3 throttle',\n group: 'Engine',\n paramType: 'number',\n paramDescription: 'Throttle position (0-100%)',\n paramMin: 0,\n paramMax: 100,\n },\n throttle_full: {\n description: 'Set throttles to full',\n group: 'Engine',\n },\n throttle_idle: {\n description: 'Set throttles to idle',\n group: 'Engine',\n },\n throttle_cutoff: {\n description: 'Cut throttles completely',\n group: 'Engine',\n },\n // ===========================================\n // LIGHTS COMMANDS\n // ===========================================\n landing_lights_on: {\n description: 'Turn on landing lights',\n group: 'Lights',\n },\n landing_lights_off: {\n description: 'Turn off landing lights',\n group: 'Lights',\n },\n landing_lights_toggle: {\n description: 'Toggle landing lights',\n group: 'Lights',\n },\n taxi_lights_on: {\n description: 'Turn on taxi lights',\n group: 'Lights',\n },\n taxi_lights_off: {\n description: 'Turn off taxi lights',\n group: 'Lights',\n },\n taxi_lights_toggle: {\n description: 'Toggle taxi lights',\n group: 'Lights',\n },\n beacon_lights_on: {\n description: 'Turn on beacon lights',\n group: 'Lights',\n },\n beacon_lights_off: {\n description: 'Turn off beacon lights',\n group: 'Lights',\n },\n beacon_lights_toggle: {\n description: 'Toggle beacon lights',\n group: 'Lights',\n },\n nav_lights_on: {\n description: 'Turn on navigation lights',\n group: 'Lights',\n },\n nav_lights_off: {\n description: 'Turn off navigation lights',\n group: 'Lights',\n },\n nav_lights_toggle: {\n description: 'Toggle navigation lights',\n group: 'Lights',\n },\n strobe_lights_on: {\n description: 'Turn on strobe lights',\n group: 'Lights',\n },\n strobe_lights_off: {\n description: 'Turn off strobe lights',\n group: 'Lights',\n },\n strobe_lights_toggle: {\n description: 'Toggle strobe lights',\n group: 'Lights',\n },\n // ===========================================\n // SIM COMMANDS\n // ===========================================\n pause: {\n description: 'Pause simulation',\n group: 'Simulation',\n },\n unpause: {\n description: 'Unpause simulation',\n group: 'Simulation',\n },\n pause_toggle: {\n description: 'Toggle simulation pause',\n group: 'Simulation',\n },\n set_pause: {\n description: 'Set pause state with parameter (1 = pause, 0 = unpause). Preferred over pause/unpause for MSFS where PAUSE_ON/PAUSE_OFF are unreliable.',\n group: 'Simulation',\n },\n },\n};\n","import { flightSimSchema } from './flight_sim';\nexport { flightSimSchema } from './flight_sim';\n/** Registry of all category schemas */\nexport const categorySchemas = {\n flight_sim: flightSimSchema,\n racing_sim: flightSimSchema, // TODO: Create racing_sim schema\n};\n/** Get a category schema by ID */\nexport function getCategorySchema(category) {\n return categorySchemas[category];\n}\n","import xplaneMappings from '../mappings/xplane.json';\n/**\n * X-Plane schema\n * Maps X-Plane-specific field names to canonical flight_sim fields\n *\n * Note: Field and command mappings are loaded from xplane.json\n * This allows the same mappings to be used by both TypeScript and Rust.\n */\nexport const xplaneSchema = {\n gameId: xplaneMappings.gameId,\n name: xplaneMappings.name,\n category: xplaneMappings.category,\n fieldMappings: xplaneMappings.fieldMappings,\n commandMappings: xplaneMappings.commandMappings,\n // X-Plane specific fields not in canonical schema\n extraFields: {\n replay_mode: {\n type: 'boolean',\n description: 'Replay mode active',\n group: 'Simulation',\n },\n time_local: {\n type: 'number',\n description: 'Local time in seconds since midnight',\n unit: 'sec',\n min: 0,\n max: 86400,\n group: 'Simulation',\n },\n time_zulu: {\n type: 'number',\n description: 'Zulu time in seconds since midnight',\n unit: 'sec',\n min: 0,\n max: 86400,\n group: 'Simulation',\n },\n },\n};\n","import { msfsSchema } from './msfs';\nimport { xplaneSchema } from './xplane';\nexport { msfsSchema } from './msfs';\nexport { xplaneSchema } from './xplane';\n/** Registry of all game schemas */\nexport const gameSchemas = {\n msfs: msfsSchema,\n xplane: xplaneSchema,\n};\n/** Get a game schema by ID */\nexport function getGameSchema(gameId) {\n return gameSchemas[gameId];\n}\n/** Get all available games */\nexport function getAvailableGames() {\n return Object.values(gameSchemas).map((schema) => ({\n id: schema.gameId,\n name: schema.name,\n category: schema.category,\n }));\n}\n","import msfsMappings from '../mappings/msfs.json';\n/**\n * Microsoft Flight Simulator (MSFS) schema\n * Maps MSFS-specific field names to canonical flight_sim fields\n *\n * Note: Field and command mappings are loaded from msfs.json\n * This allows the same mappings to be used by both TypeScript and Rust.\n */\nexport const msfsSchema = {\n gameId: msfsMappings.gameId,\n name: msfsMappings.name,\n category: msfsMappings.category,\n fieldMappings: msfsMappings.fieldMappings,\n commandMappings: msfsMappings.commandMappings,\n // MSFS-specific fields not in canonical schema\n extraFields: {\n pitot_heat: {\n type: 'boolean',\n description: 'Pitot heat active',\n group: 'Systems',\n },\n fuel_quantity_gallons: {\n type: 'number',\n description: 'Total fuel in gallons',\n unit: 'gal',\n min: 0,\n max: 100000,\n group: 'Fuel',\n },\n fuel_flow_gph_1: {\n type: 'number',\n description: 'Engine 1 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n fuel_flow_gph_2: {\n type: 'number',\n description: 'Engine 2 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n fuel_flow_gph_3: {\n type: 'number',\n description: 'Engine 3 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n fuel_flow_gph_4: {\n type: 'number',\n description: 'Engine 4 fuel flow in GPH',\n unit: 'gal/hr',\n min: 0,\n max: 5000,\n group: 'Fuel',\n },\n flaps_left_percent: {\n type: 'number',\n description: 'Left flaps position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n flaps_right_percent: {\n type: 'number',\n description: 'Right flaps position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n spoilers_left_percent: {\n type: 'number',\n description: 'Left spoilers position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n spoilers_right_percent: {\n type: 'number',\n description: 'Right spoilers position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_left_position: {\n type: 'number',\n description: 'Left gear position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_center_position: {\n type: 'number',\n description: 'Center gear position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n gear_right_position: {\n type: 'number',\n description: 'Right gear position',\n unit: '%',\n min: 0,\n max: 100,\n group: 'Controls',\n },\n ambient_pressure: {\n type: 'number',\n description: 'Ambient pressure',\n unit: 'inHg',\n min: 20,\n max: 35,\n group: 'Environment',\n },\n sea_level_pressure: {\n type: 'number',\n description: 'Sea level pressure',\n unit: 'inHg',\n min: 27,\n max: 32,\n group: 'Environment',\n },\n aircraft_wind_x: {\n type: 'number',\n description: 'Wind component X-axis',\n unit: 'kts',\n min: -200,\n max: 200,\n group: 'Environment',\n },\n aircraft_wind_y: {\n type: 'number',\n description: 'Wind component Y-axis',\n unit: 'kts',\n min: -200,\n max: 200,\n group: 'Environment',\n },\n aircraft_wind_z: {\n type: 'number',\n description: 'Wind component Z-axis',\n unit: 'kts',\n min: -200,\n max: 200,\n group: 'Environment',\n },\n },\n};\n","import { transforms } from './types';\n/**\n * Denormalizes a canonical command to a game-specific command.\n * This is used when the SDK sends commands to the game.\n */\nexport function denormalizeCommand(canonicalCommand, gameSchema, value) {\n const mapping = gameSchema.commandMappings[canonicalCommand];\n if (!mapping) {\n return undefined;\n }\n let transformedValue = value;\n if (mapping.paramTransform && mapping.paramTransform in transforms && value !== undefined) {\n const transformFn = transforms[mapping.paramTransform];\n transformedValue = transformFn(value);\n }\n return {\n command: mapping.gameCommand,\n value: transformedValue,\n };\n}\n/**\n * Gets a list of available canonical commands for a game\n */\nexport function getAvailableCommands(gameSchema) {\n return Object.keys(gameSchema.commandMappings);\n}\n/**\n * Normalizes raw game telemetry to canonical field names.\n * This is the core function the SDK uses to provide a consistent API across sims.\n */\nexport function normalizeTelemetry(raw, gameSchema) {\n const normalized = {};\n for (const [rawField, value] of Object.entries(raw)) {\n const mapping = gameSchema.fieldMappings[rawField];\n if (mapping) {\n // Apply transform if specified\n let normalizedValue = value;\n if (mapping.transform && mapping.transform in transforms) {\n const transformFn = transforms[mapping.transform];\n normalizedValue = transformFn(value);\n }\n normalized[mapping.canonical] = normalizedValue;\n }\n else if (gameSchema.extraFields?.[rawField]) {\n // Keep extra fields with their original names (prefixed with game ID)\n normalized[`${gameSchema.gameId}:${rawField}`] = value;\n }\n // Unknown fields are dropped (or could optionally be preserved)\n }\n return normalized;\n}\n/**\n * Denormalizes canonical field names back to game-specific names.\n * Used for sending commands back to the game.\n */\nexport function denormalizeTelemetry(normalized, gameSchema) {\n const raw = {};\n // Build reverse mapping\n const reverseMapping = {};\n for (const [rawField, mapping] of Object.entries(gameSchema.fieldMappings)) {\n // Only keep first mapping for each canonical field\n if (!reverseMapping[mapping.canonical]) {\n reverseMapping[mapping.canonical] = { rawField, transform: mapping.transform };\n }\n }\n for (const [canonicalField, value] of Object.entries(normalized)) {\n const mapping = reverseMapping[canonicalField];\n if (mapping) {\n // Note: we don't reverse transforms here - commands typically don't need it\n raw[mapping.rawField] = value;\n }\n }\n return raw;\n}\nexport function validateTelemetry(normalized, categorySchema) {\n const results = [];\n for (const [fieldName, fieldDef] of Object.entries(categorySchema.fields)) {\n const value = normalized[fieldName];\n if (value === undefined) {\n results.push({ field: fieldName, status: 'missing' });\n continue;\n }\n if (value === null) {\n results.push({ field: fieldName, status: 'null', value });\n continue;\n }\n if (fieldDef.type === 'number') {\n if (typeof value !== 'number') {\n results.push({\n field: fieldName,\n status: 'wrong_type',\n value,\n message: `Expected number, got ${typeof value}`,\n });\n continue;\n }\n if (fieldDef.min !== undefined && value < fieldDef.min) {\n results.push({\n field: fieldName,\n status: 'out_of_range',\n value,\n message: `Value ${value} below min ${fieldDef.min}`,\n });\n continue;\n }\n if (fieldDef.max !== undefined && value > fieldDef.max) {\n results.push({\n field: fieldName,\n status: 'out_of_range',\n value,\n message: `Value ${value} above max ${fieldDef.max}`,\n });\n continue;\n }\n }\n if (fieldDef.type === 'boolean' && typeof value !== 'boolean') {\n results.push({\n field: fieldName,\n status: 'wrong_type',\n value,\n message: `Expected boolean, got ${typeof value}`,\n });\n continue;\n }\n results.push({ field: fieldName, status: 'ok', value });\n }\n return results;\n}\nexport function getCoverageStats(validationResults, categorySchema) {\n const receivedFields = validationResults.filter((r) => r.status !== 'missing').length;\n const totalFields = Object.keys(categorySchema.fields).length;\n const receivedSet = new Set(validationResults.filter((r) => r.status !== 'missing').map((r) => r.field));\n const requiredReceived = categorySchema.requiredFields.filter((f) => receivedSet.has(f)).length;\n const missingRequired = categorySchema.requiredFields.filter((f) => !receivedSet.has(f));\n const fieldsWithIssues = validationResults.filter((r) => r.status === 'null' || r.status === 'out_of_range' || r.status === 'wrong_type').length;\n return {\n totalFields,\n receivedFields,\n requiredFields: categorySchema.requiredFields.length,\n requiredReceived,\n missingRequired,\n fieldsWithIssues,\n coverage: totalFields > 0 ? Math.round((receivedFields / totalFields) * 100) : 0,\n };\n}\n","import EventEmitter from 'event-emitter';\r\nimport { getGameSchema, normalizeTelemetry } from '@gameglue/schemas';\r\n\r\nexport class Listener {\r\n constructor(socket, config) {\r\n this._config = config;\r\n this._socket = socket;\r\n this._callbacks = [];\r\n this._fields = config.fields ? [...config.fields] : null;\r\n this._gameSchema = getGameSchema(config.gameId);\r\n }\r\n\r\n async establishConnection() {\r\n if (!this._socket || !this._config.userId || !this._config.gameId) {\r\n throw new Error('Missing arguments in establishConnection');\r\n }\r\n return new Promise((resolve) => {\r\n // Use object format if fields are specified, otherwise use legacy string format\r\n let listenPayload;\r\n if (this._fields) {\r\n listenPayload = {\r\n userId: this._config.userId,\r\n gameId: this._config.gameId,\r\n fields: this._fields\r\n };\r\n } else {\r\n listenPayload = `${this._config.userId}:${this._config.gameId}`;\r\n }\r\n\r\n this._socket.timeout(5000).emit('listen', listenPayload, (error, response) => {\r\n if (error) {\r\n return resolve({status: 'failed', reason: 'Listen request timed out.'});\r\n }\r\n if (response.status === 'success') {\r\n return resolve({status: 'success'});\r\n } else {\r\n return resolve({status: 'failed', reason: response.reason});\r\n }\r\n });\r\n });\r\n }\r\n\r\n setupEventListener() {\r\n // Listen for telemetry updates\r\n this._socket.on('update', (payload) => {\r\n // Filter events by gameId when present (multiple listeners share one socket)\r\n if (payload?.gameId && payload.gameId !== this._config.gameId) return;\r\n\r\n const rawData = payload?.data;\r\n\r\n // Normalize telemetry to canonical field names\r\n const normalizedData = rawData && this._gameSchema\r\n ? normalizeTelemetry(rawData, this._gameSchema)\r\n : rawData;\r\n\r\n // Apply client-side field filtering on normalized data\r\n let filteredData = normalizedData;\r\n if (this._fields && this._fields.length > 0 && normalizedData) {\r\n filteredData = {};\r\n for (const field of this._fields) {\r\n if (field in normalizedData) {\r\n filteredData[field] = normalizedData[field];\r\n }\r\n }\r\n }\r\n\r\n // Emit with both raw and normalized data\r\n this.emit('update', {\r\n ...payload,\r\n raw: rawData,\r\n data: filteredData\r\n });\r\n });\r\n\r\n // Listen for derived events (landing, takeoff, flight_phase, etc.)\r\n this._socket.on('key-events', (payload) => {\r\n const { gameId, eventType, data } = payload || {};\r\n\r\n // Only emit events for our game\r\n if (gameId !== this._config.gameId) {\r\n return;\r\n }\r\n\r\n // Emit specific event type (landing, takeoff, flight_phase)\r\n if (eventType && data) {\r\n this.emit(eventType, data);\r\n }\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Subscribe to additional fields dynamically\r\n * @param {string[]} fields - Array of field names to add\r\n * @returns {Promise<{status: string, reason?: string}>}\r\n */\r\n async subscribe(fields) {\r\n if (!Array.isArray(fields) || fields.length === 0) {\r\n throw new Error('fields must be a non-empty array');\r\n }\r\n\r\n // Add new fields to existing list\r\n if (!this._fields) {\r\n this._fields = [...fields];\r\n } else {\r\n for (const field of fields) {\r\n if (!this._fields.includes(field)) {\r\n this._fields.push(field);\r\n }\r\n }\r\n }\r\n\r\n return this._updateSubscription();\r\n }\r\n\r\n /**\r\n * Unsubscribe from specific fields\r\n * @param {string[]} fields - Array of field names to remove\r\n * @returns {Promise<{status: string, reason?: string}>}\r\n */\r\n async unsubscribe(fields) {\r\n if (!Array.isArray(fields) || fields.length === 0) {\r\n throw new Error('fields must be a non-empty array');\r\n }\r\n\r\n if (!this._fields) {\r\n // Currently receiving all fields, create explicit list without these fields\r\n throw new Error('Cannot unsubscribe when receiving all fields. Use subscribe() first to set explicit field list.');\r\n }\r\n\r\n this._fields = this._fields.filter(f => !fields.includes(f));\r\n\r\n return this._updateSubscription();\r\n }\r\n\r\n /**\r\n * Get the current list of subscribed fields\r\n * @returns {string[]|null} - Array of field names, or null if receiving all fields\r\n */\r\n getFields() {\r\n return this._fields ? [...this._fields] : null;\r\n }\r\n\r\n /**\r\n * Send a command to the broadcaster (game client)\r\n * @param {string} command - The canonical command name (e.g., 'gear_up', 'set_autopilot_heading')\r\n * @param {any} value - The value to set (optional for toggle commands)\r\n * @returns {Promise<{status: string, reason?: string}>}\r\n */\r\n async sendCommand(command, value) {\r\n if (!command || typeof command !== 'string') {\r\n throw new Error('command must be a non-empty string');\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const payload = {\r\n userId: this._config.userId,\r\n gameId: this._config.gameId,\r\n data: {\r\n fieldName: command,\r\n value\r\n }\r\n };\r\n\r\n this._socket.timeout(5000).emit('set', payload, (error, response) => {\r\n if (error) {\r\n return resolve({ status: 'failed', reason: 'Command request timed out.' });\r\n }\r\n return resolve(response);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Internal method to send subscription update to server\r\n */\r\n async _updateSubscription() {\r\n return new Promise((resolve) => {\r\n const payload = {\r\n userId: this._config.userId,\r\n gameId: this._config.gameId,\r\n fields: this._fields\r\n };\r\n\r\n this._socket.timeout(5000).emit('listen-update', payload, (error, response) => {\r\n if (error) {\r\n return resolve({status: 'failed', reason: 'Update request timed out.'});\r\n }\r\n return resolve(response);\r\n });\r\n });\r\n }\r\n}\r\n\r\nEventEmitter(Listener.prototype);\r\n","import EventEmitter from 'event-emitter';\r\nimport { getGameSchema, normalizeTelemetry } from '@gameglue/schemas';\r\n\r\nexport class PresenceListener {\r\n constructor(socket, config) {\r\n this._config = config;\r\n this._socket = socket;\r\n this._gameSchema = getGameSchema(config.gameId);\r\n this._broadcasters = [];\r\n }\r\n\r\n async establishConnection() {\r\n if (!this._socket || !this._config.clientId || !this._config.gameId) {\r\n throw new Error('Missing arguments in establishConnection');\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const payload = {\r\n clientId: this._config.clientId,\r\n gameId: this._config.gameId\r\n };\r\n\r\n this._socket.timeout(5000).emit('presenceSubscribe', payload, (error, response) => {\r\n if (error) {\r\n return resolve({ status: 'failed', reason: 'Presence request timed out.' });\r\n }\r\n\r\n if (response?.status === 'success') {\r\n const broadcasters = Array.isArray(response.broadcasters)\r\n ? response.broadcasters.map((snapshot) => this._normalizeSnapshot(snapshot))\r\n : [];\r\n this._broadcasters = broadcasters;\r\n return resolve({ status: 'success', broadcasters });\r\n }\r\n\r\n return resolve({\r\n status: 'failed',\r\n reason: response?.reason || 'Presence subscription failed.'\r\n });\r\n });\r\n });\r\n }\r\n\r\n setupEventListener() {\r\n this._socket.on('presence-update', (snapshot) => {\r\n const normalized = this._normalizeSnapshot(snapshot);\r\n this._upsertBroadcaster(normalized);\r\n this.emit('update', normalized);\r\n });\r\n\r\n this._socket.on('presence-remove', ({ userId }) => {\r\n if (!userId) return;\r\n this._broadcasters = this._broadcasters.filter((b) => b.userId !== userId);\r\n this.emit('remove', { userId });\r\n });\r\n\r\n this._socket.on('connect', () => {\r\n this.emit('connect');\r\n });\r\n\r\n this._socket.on('disconnect', (reason) => {\r\n this.emit('disconnect', reason);\r\n });\r\n\r\n return this;\r\n }\r\n\r\n getBroadcasters() {\r\n return [...this._broadcasters];\r\n }\r\n\r\n disconnect() {\r\n if (this._socket) {\r\n this._socket.disconnect();\r\n }\r\n }\r\n\r\n _normalizeSnapshot(snapshot) {\r\n if (!snapshot || typeof snapshot !== 'object') {\r\n return snapshot;\r\n }\r\n\r\n const rawData = snapshot.data;\r\n const normalizedData = rawData && this._gameSchema\r\n ? normalizeTelemetry(rawData, this._gameSchema)\r\n : rawData;\r\n\r\n return {\r\n ...snapshot,\r\n data: normalizedData,\r\n raw: rawData\r\n };\r\n }\r\n\r\n _upsertBroadcaster(snapshot) {\r\n if (!snapshot || !snapshot.userId) {\r\n return;\r\n }\r\n\r\n const idx = this._broadcasters.findIndex((b) => b.userId === snapshot.userId);\r\n if (idx >= 0) {\r\n const next = [...this._broadcasters];\r\n next[idx] = snapshot;\r\n this._broadcasters = next;\r\n return;\r\n }\r\n\r\n this._broadcasters = [...this._broadcasters, snapshot];\r\n }\r\n}\r\n\r\nEventEmitter(PresenceListener.prototype);\r\n","import { GameGlueAuth } from './auth';\r\nimport { io } from \"socket.io-client\";\r\nimport { Listener } from \"./listener\";\r\nimport { PresenceListener } from \"./presence_listener\";\r\nimport { isCorsError, logCorsHelp } from './utils';\r\nimport { getGameSchema, getCategorySchema, normalizeTelemetry } from '@gameglue/schemas';\r\n\r\nconst GAME_IDS = {\r\n 'msfs': true,\r\n 'xplane': true,\r\n};\r\n\r\nconst DEFAULT_SOCKET_URL = 'https://socks.gameglue.gg';\r\n\r\nclass GameGlue extends GameGlueAuth {\r\n constructor(cfg) {\r\n super(cfg);\r\n this._socket = null;\r\n this._socketUrl = cfg.socketUrl || DEFAULT_SOCKET_URL;\r\n this._connectPromise = null;\r\n }\r\n\r\n /**\r\n * Create a listener for game telemetry.\r\n * Connects to socket server lazily on first call.\r\n * @param {Object} config - { userId, gameId, fields? }\r\n * @returns {Promise<Listener>}\r\n */\r\n async createListener(config) {\r\n if (!config) throw new Error('Not a valid listener config');\r\n if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');\r\n if (!config.userId) throw new Error('User ID not supplied');\r\n if (config.fields && !Array.isArray(config.fields)) throw new Error('fields must be an array');\r\n\r\n // Ensure socket is connected (lazy initialization)\r\n await this._ensureConnected();\r\n\r\n const listener = new Listener(this._socket, config);\r\n const establishConnectionResponse = await listener.establishConnection();\r\n\r\n // Handle reconnection\r\n this._socket.io.on('reconnect_attempt', () => {\r\n this._updateSocketAuth(this.getAccessToken());\r\n });\r\n this._socket.io.on('reconnect', () => {\r\n listener.establishConnection();\r\n });\r\n\r\n if (establishConnectionResponse.status !== 'success') {\r\n throw new Error(`There was a problem setting up the listener. Reason: ${establishConnectionResponse.reason}`);\r\n }\r\n\r\n return listener.setupEventListener();\r\n }\r\n\r\n /**\r\n * Create a presence listener for public broadcaster updates.\r\n * This does not require authentication.\r\n * @param {Object} config - { clientId, gameId }\r\n * @returns {Promise<PresenceListener>}\r\n */\r\n async createPresenceListener(config) {\r\n if (!config) throw new Error('Not a valid presence listener config');\r\n if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');\r\n if (!config.clientId) throw new Error('Client ID not supplied');\r\n\r\n const socket = io(this._socketUrl, {\r\n transports: ['websocket'],\r\n });\r\n\r\n await new Promise((resolve, reject) => {\r\n socket.on('connect', resolve);\r\n socket.on('connect_error', (err) => {\r\n if (isCorsError(err)) {\r\n logCorsHelp('WebSocket Connection', this._socketUrl);\r\n }\r\n reject(new Error(`Socket connection failed: ${err.message}`));\r\n });\r\n });\r\n\r\n const listener = new PresenceListener(socket, config);\r\n const establishConnectionResponse = await listener.establishConnection();\r\n\r\n // Handle reconnection\r\n socket.io.on('reconnect', () => {\r\n listener.establishConnection();\r\n });\r\n\r\n if (establishConnectionResponse.status !== 'success') {\r\n throw new Error(\r\n `There was a problem setting up the presence listener. Reason: ${establishConnectionResponse.reason}`\r\n );\r\n }\r\n\r\n return listener.setupEventListener();\r\n }\r\n\r\n // ============ Internal Methods ============\r\n\r\n async _ensureConnected() {\r\n // Already connected\r\n if (this._socket?.connected) {\r\n return;\r\n }\r\n\r\n // Connection in progress - wait for it\r\n if (this._connectPromise) {\r\n await this._connectPromise;\r\n return;\r\n }\r\n\r\n // Start new connection\r\n this._connectPromise = this._connect();\r\n\r\n try {\r\n await this._connectPromise;\r\n } finally {\r\n this._connectPromise = null;\r\n }\r\n }\r\n\r\n _connect() {\r\n return new Promise((resolve, reject) => {\r\n const token = this.getAccessToken();\r\n\r\n if (!token) {\r\n reject(new Error('Not authenticated - call isAuthenticated() first'));\r\n return;\r\n }\r\n\r\n this._socket = io(this._socketUrl, {\r\n transports: ['websocket'],\r\n auth: { token }\r\n });\r\n\r\n this._socket.on('connect', () => {\r\n resolve();\r\n });\r\n\r\n this._socket.on('connect_error', (err) => {\r\n if (isCorsError(err)) {\r\n logCorsHelp('WebSocket Connection', this._socketUrl);\r\n }\r\n reject(new Error(`Socket connection failed: ${err.message}`));\r\n });\r\n\r\n // Update socket auth when token refreshes\r\n this.onTokenRefreshed((newToken) => {\r\n this._updateSocketAuth(newToken);\r\n });\r\n });\r\n }\r\n\r\n _updateSocketAuth(authToken) {\r\n if (this._socket) {\r\n this._socket.auth.token = authToken;\r\n }\r\n }\r\n}\r\n\r\nif (typeof window !== 'undefined') {\r\n window.GameGlue = GameGlue;\r\n // Expose schema utilities for validation tools\r\n window.GameGlueSchemas = { getGameSchema, getCategorySchema, normalizeTelemetry };\r\n}\r\n\r\nexport default GameGlue;\r\nexport { GameGlue, getGameSchema, getCategorySchema, normalizeTelemetry };\r\n"],"names":["storageMap","storage","key","value","isBrowser","localStorage","setItem","getItem","removeItem","process","String","isCorsError","error","msg","message","toString","toLowerCase","includes","logCorsHelp","context","url","console","padEnd","substring","_callbackPromise","GameGlueAuth","constructor","cfg","authority","authUrl","this","_oidcSettings","client_id","clientId","redirect_uri","removeTrailingSlashes","window","location","href","post_logout_redirect_uri","response_type","scope","scopes","join","response_mode","filterProtocolClaims","_oidcClient","OidcClient","_refreshCallback","_refreshTimeout","isAuthenticated","_hasCallbackParams","_processCallback","_hasValidTokens","login","createSigninRequest","state","bar","then","req","catch","err","logout","options","clearTimeout","redirect","logoutUrl","encodeURIComponent","getUser","token","_getAccessToken","Error","jwt_decode","sub","getAccessToken","onTokenRefreshed","callback","hash","_clearCallbackUrl","history","replaceState","document","title","pathname","search","_doProcessCallback","response","processSigninResponse","access_token","_setAccessToken","_setRefreshToken","refresh_token","decoded","Date","exp","_setTokenRefreshTimeout","undefined","_getRefreshToken","timeUntilExp","now","setTimeout","_attemptRefresh","fetch","method","headers","body","URLSearchParams","grant_type","status","resObj","json","e","endsWith","replace","transforms","radiansToDegrees","Math","PI","degreesToRadians","metersToFeet","feetToMeters","msToKnots","knotsToMs","msToFpm","toBoolean","Boolean","divideBy100","flightSimSchema","category","name","description","recommendedUpdateRateHz","requiredFields","fields","latitude","type","unit","min","max","group","longitude","altitude","altitude_agl","on_ground","indicated_airspeed","true_airspeed","ground_speed","mach","vertical_speed","g_force","heading","true_heading","pitch","roll","yaw","throttle_0","throttle_1","throttle_2","throttle_3","rpm_0","rpm_1","rpm_2","rpm_3","n1_0","n1_1","n1_2","n1_3","n2_0","n2_1","n2_2","n2_3","egt_0","egt_1","egt_2","egt_3","fuel_quantity","fuel_flow_0","fuel_flow_1","fuel_flow_2","fuel_flow_3","autopilot_master","autopilot_altitude_hold","autopilot_heading_hold","autopilot_vs_hold","autopilot_speed_hold","autopilot_approach","autopilot_nav","flight_director","autopilot_flc","autopilot_altitude_target","autopilot_heading_target","autopilot_vs_target","autopilot_speed_target","flaps","spoilers","gear_down","parking_brake","brake_left","brake_right","light_landing","light_taxi","light_beacon","light_nav","light_strobe","outside_air_temp","barometric_pressure","wind_direction","wind_speed","stall_warning","overspeed_warning","sim_paused","sim_rate","gps_wp_distance","gps_wp_ete","commands","autopilot_on","autopilot_off","autopilot_toggle","autopilot_altitude_hold_on","autopilot_altitude_hold_off","autopilot_heading_hold_on","autopilot_heading_hold_off","autopilot_vs_hold_on","autopilot_vs_hold_off","autopilot_nav_on","autopilot_nav_off","autopilot_approach_on","autopilot_approach_off","flight_director_on","flight_director_off","autopilot_flc_on","autopilot_flc_off","autopilot_flc_toggle","set_autopilot_altitude","paramType","paramDescription","paramMin","paramMax","set_autopilot_heading","set_autopilot_vs","set_autopilot_speed","gear_up","gear_toggle","flaps_up","flaps_down","flaps_full","flaps_retract","set_flaps","spoilers_arm","spoilers_deploy","spoilers_retract","spoilers_toggle","parking_brake_toggle","parking_brake_on","parking_brake_off","set_throttle","set_throttle_0","set_throttle_1","set_throttle_2","set_throttle_3","throttle_full","throttle_idle","throttle_cutoff","landing_lights_on","landing_lights_off","landing_lights_toggle","taxi_lights_on","taxi_lights_off","taxi_lights_toggle","beacon_lights_on","beacon_lights_off","beacon_lights_toggle","nav_lights_on","nav_lights_off","nav_lights_toggle","strobe_lights_on","strobe_lights_off","strobe_lights_toggle","pause","unpause","pause_toggle","set_pause","categorySchemas","flight_sim","racing_sim","getCategorySchema","gameSchemas","msfs","gameId","msfsMappings","fieldMappings","commandMappings","extraFields","pitot_heat","fuel_quantity_gallons","fuel_flow_gph_1","fuel_flow_gph_2","fuel_flow_gph_3","fuel_flow_gph_4","flaps_left_percent","flaps_right_percent","spoilers_left_percent","spoilers_right_percent","gear_left_position","gear_center_position","gear_right_position","ambient_pressure","sea_level_pressure","aircraft_wind_x","aircraft_wind_y","aircraft_wind_z","xplane","xplaneMappings","replay_mode","time_local","time_zulu","getGameSchema","normalizeTelemetry","raw","gameSchema","normalized","rawField","Object","entries","mapping","normalizedValue","transform","transformFn","canonical","Listener","socket","config","_config","_socket","_callbacks","_fields","_gameSchema","establishConnection","userId","Promise","resolve","listenPayload","timeout","emit","reason","setupEventListener","on","payload","rawData","data","normalizedData","filteredData","length","field","eventType","subscribe","Array","isArray","push","_updateSubscription","unsubscribe","filter","f","getFields","sendCommand","command","fieldName","EventEmitter","prototype","PresenceListener","_broadcasters","broadcasters","map","snapshot","_normalizeSnapshot","_upsertBroadcaster","b","getBroadcasters","disconnect","idx","findIndex","next","GAME_IDS","GameGlue","super","_socketUrl","socketUrl","_connectPromise","createListener","_ensureConnected","listener","establishConnectionResponse","io","_updateSocketAuth","createPresenceListener","transports","reject","connected","_connect","auth","newToken","authToken","GameGlueSchemas"],"mappings":"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"}