Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions deps/cloudxr/webxr_client/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
node_modules
build
dist
**/*.tgz
package-lock.json
9 changes: 9 additions & 0 deletions deps/cloudxr/webxr_client/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid",
"endOfLine": "auto"
}
Comment on lines +1 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add REUSE metadata for .prettierrc.

This new file has no SPDX metadata. For JSON config, add a REUSE sidecar license file (e.g., .prettierrc.license) or switch to a JS-based Prettier config that can carry the SPDX header block.

As per coding guidelines **/*: Files require SPDX-FileCopyrightText and SPDX-License-Identifier headers in the form the repo already uses (e.g., HTML comment block format as in README.md), as enforced by the REUSE hook.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deps/cloudxr/webxr_client/.prettierrc` around lines 1 - 9, The new JSON
config (.prettierrc) lacks REUSE SPDX metadata; either add a sidecar license
file named .prettierrc.license next to .prettierrc containing the required
SPDX-FileCopyrightText and SPDX-License-Identifier headers in the repo’s
HTML-style comment format, or convert .prettierrc to a JS config (e.g.,
.prettierrc.js) and prepend the same SPDX header block as a comment at the top;
ensure the headers match the repo’s existing format so the REUSE hook accepts
it.

85 changes: 85 additions & 0 deletions deps/cloudxr/webxr_client/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// Minimal ESLint flat config derived from /gitlab/cloudxr-js (examples / React client rules).
// Use CommonJS so Node can load without ESM; paths are relative to this package root.
const typescriptEslint = require('@typescript-eslint/eslint-plugin');
const typescriptParser = require('@typescript-eslint/parser');
const reactPlugin = require('eslint-plugin-react');
const reactHooksPlugin = require('eslint-plugin-react-hooks');
const simpleImportSort = require('eslint-plugin-simple-import-sort');

module.exports = [
{
linterOptions: {
reportUnusedDisableDirectives: true,
},
},
{
ignores: ['node_modules/**', 'build/**', 'dist/**'],
},
{
files: ['src/**/*.{ts,tsx}', 'helpers/**/*.ts'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescriptEslint,
react: reactPlugin,
'react-hooks': reactHooksPlugin,
'simple-import-sort': simpleImportSort,
},
rules: {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'react/jsx-uses-react': 'off',
'react/jsx-uses-vars': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'no-console': ['warn', { allow: ['info', 'warn', 'error', 'debug'] }],
// `import/order` from eslint-plugin-import does not reliably --fix
// "empty line within import group" (see App.tsx + @helpers + TeleopProjects).
// simple-import-sort rewrites the block and fixes that on `eslint --fix`.
'simple-import-sort/imports': [
'error',
{
groups: [
// Side effect imports
['^\\u0000'],
// `node:`
['^node:'],
// External: not `../` and not the `@helpers/` alias
['^(?!\\.|@helpers/)'],
// Internal alias
['^@helpers/'],
// Relative
['^\\.'],
],
},
],
'simple-import-sort/exports': 'error',
},
settings: {
react: {
version: 'detect',
},
},
},
];
46 changes: 23 additions & 23 deletions deps/cloudxr/webxr_client/helpers/WebGLStateApply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,38 @@
*/

import {
WebGLState,
GL_MAX_COLOR_ATTACHMENTS,
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
GL_MAX_UNIFORM_BUFFER_BINDINGS,
GL_MAX_VERTEX_ATTRIBS,
GLUndefined,
isDefined,
WebGLBlendState,
WebGLBufferState,
WebGLTextureState,
WebGLTextureUnitState,
WebGLProgramState,
WebGLFramebufferState,
WebGLVertexArrayState,
WebGLVertexAttribState,
WebGLCapabilityState,
WebGLViewportState,
WebGLClearState,
WebGLBlendState,
WebGLDepthState,
WebGLStencilState,
WebGLColorState,
WebGLCullingState,
WebGLDepthState,
WebGLFramebufferState,
WebGLIndexedBufferBinding,
WebGLLineState,
WebGLPolygonOffsetState,
WebGLSampleState,
WebGLPixelStoreState,
WebGLTransformFeedbackState,
WebGLPolygonOffsetState,
WebGLProgramState,
WebGLQueryState,
WebGLRenderbufferState,
WebGLSamplerState,
WebGLQueryState,
WebGLIndexedBufferBinding,
GL_MAX_VERTEX_ATTRIBS,
GL_MAX_UNIFORM_BUFFER_BINDINGS,
GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
GL_MAX_COLOR_ATTACHMENTS,
GLUndefined,
isDefined,
WebGLSampleState,
WebGLState,
WebGLStencilState,
WebGLTextureState,
WebGLTextureUnitState,
WebGLTransformFeedbackState,
WebGLVertexArrayState,
WebGLVertexAttribState,
WebGLViewportState,
} from './WebGLState';

/**
Expand Down
2 changes: 1 addition & 1 deletion deps/cloudxr/webxr_client/helpers/WebGLStateBinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

import { WebGLStateTracker, WebGLState } from './WebGLState';
import { WebGLState, WebGLStateTracker } from './WebGLState';
import { apply } from './WebGLStateApply';

/**
Expand Down
18 changes: 4 additions & 14 deletions deps/cloudxr/webxr_client/helpers/controlChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,7 @@ export class HeadsetControlChannel {
ws = new WebSocket(this.opts.url);
} catch (err) {
if (this.disposed) return;
console.warn(
'[ControlChannel] WebSocket constructor failed for',
this.opts.url,
err
);
console.warn('[ControlChannel] WebSocket constructor failed for', this.opts.url, err);
this.ws = null;
this._afterSocketClosed();
return;
Expand All @@ -129,7 +125,7 @@ export class HeadsetControlChannel {
this._startMetricsTimer();
};

ws.onmessage = (ev) => {
ws.onmessage = ev => {
if (typeof ev.data !== 'string') return;
let msg: { type?: string; payload?: unknown };
try {
Expand Down Expand Up @@ -169,17 +165,11 @@ export class HeadsetControlChannel {

if (type === 'hello') {
// hello to headset includes initial config
if (
payload.config != null &&
typeof payload.configVersion === 'number'
) {
if (payload.config != null && typeof payload.configVersion === 'number') {
this.opts.onConfig(payload.config as StreamConfig, payload.configVersion as number);
}
} else if (type === 'config') {
if (
payload.config != null &&
typeof payload.configVersion === 'number'
) {
if (payload.config != null && typeof payload.configVersion === 'number') {
this.opts.onConfig(payload.config as StreamConfig, payload.configVersion as number);
}
} else if (type === 'error') {
Expand Down
15 changes: 14 additions & 1 deletion deps/cloudxr/webxr_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
"dev": "webpack --config ./webpack.dev.js",
"dev-server": "webpack serve --config ./webpack.dev.js --no-open",
"dev-server:https": "cross-env HTTPS=true webpack serve --config ./webpack.dev.js --no-open",
"clean": "rimraf dist"
"clean": "rimraf dist",
"lint:check": "eslint \"src/**/*.{ts,tsx}\" \"helpers/**/*.ts\" --ignore-pattern \"**/build/**\" --ignore-pattern \"**/node_modules/**\" --ignore-pattern \"**/dist/**\" --quiet",
"lint": "eslint \"src/**/*.{ts,tsx}\" \"helpers/**/*.ts\" --ignore-pattern \"**/build/**\" --ignore-pattern \"**/node_modules/**\" --ignore-pattern \"**/dist/**\" --quiet --fix",
"prettier:check": "prettier --check \"src/**/*.{ts,tsx,html,css,json,md}\" \"helpers/**/*.ts\" \"webpack.*.js\" \"eslint.config.js\" \"*.{js,json,md}\"",
"prettier": "prettier --write \"src/**/*.{ts,tsx,html,css,json,md}\" \"helpers/**/*.ts\" \"webpack.*.js\" \"eslint.config.js\" \"*.{js,json,md}\"",
"format:check": "npm run lint:check && npm run prettier:check",
"format": "npm run lint && npm run prettier"
},
"dependencies": {
"@nvidia/cloudxr": "file:../nvidia-cloudxr-6.1.0.tgz",
Expand All @@ -37,12 +43,19 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/three": "^0.172.0",
"@typescript-eslint/eslint-plugin": "^8.32.1",
"@typescript-eslint/parser": "^8.32.1",
"@webxr-input-profiles/assets": "1.0.19",
"copy-webpack-plugin": "^13.0.0",
"cross-env": "^10.1.0",
"css-loader": "^6.8.1",
"eslint": "9.27.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"html-webpack-plugin": "^5.6.3",
"iwer": "^2.2.1",
"prettier": "3.5.3",
"rimraf": "^5.0.5",
"style-loader": "^3.3.3",
"ts-loader": "^9.5.1",
Expand Down
39 changes: 17 additions & 22 deletions deps/cloudxr/webxr_client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,28 @@
* and disconnect when in XR mode.
*/

import * as CloudXR from '@nvidia/cloudxr';
import { getResolutionValidationError } from '@nvidia/cloudxr';
import { computed, signal } from '@preact/signals-react';
import { Canvas } from '@react-three/fiber';
import { setPreferredColorScheme } from '@react-three/uikit';
import { createXRStore, noEvents, PointerEvents, useXR, XR, XROrigin } from '@react-three/xr';
import type { XRDevice } from 'iwer';
import { useEffect, useMemo, useRef, useState } from 'react';
import { v5 } from 'uuid';

import { checkCapabilities } from '@helpers/BrowserCapabilities';
import { HeadsetControlChannel } from '@helpers/controlChannel';
import { getDeviceProfile, resolveDeviceProfileId } from '@helpers/DeviceProfiles';
import { loadIWERIfNeeded } from '@helpers/LoadIWER';
import { overridePressureObserver } from '@helpers/overridePressureObserver';
import { kPerformanceOptions } from '@helpers/PerformanceProfiles';
import CloudXRComponent from '@helpers/react/CloudXRComponent';
import { SimpleEnvironment } from '@helpers/react/SimpleEnvironment';
import { getControlPanelPositionVector } from '@helpers/react/utils';
import * as CloudXR from '@nvidia/cloudxr';
import { getResolutionValidationError } from '@nvidia/cloudxr';
import { signal, computed } from '@preact/signals-react';
import { Canvas } from '@react-three/fiber';
import { setPreferredColorScheme } from '@react-three/uikit';
import { XR, createXRStore, noEvents, PointerEvents, XROrigin, useXR } from '@react-three/xr';
import type { XRDevice } from 'iwer';
import { useState, useMemo, useEffect, useRef } from 'react';

import { v5 } from 'uuid';
import { CloudXR2DUI } from './CloudXR2DUI';
import CloudXR3DUI from './CloudXRUI';
import { HeadsetControlChannel } from '@helpers/controlChannel';

// Performance metrics signals - raw numeric data, one per callback cadence.
// Signals update their value without triggering React re-renders.
Expand Down Expand Up @@ -83,7 +84,6 @@ overridePressureObserver();

setPreferredColorScheme('dark');


const TELEOP_CHANNEL_UUID: Uint8Array = v5('teleop_command', v5.DNS, new Uint8Array(16));

type AvailableChannel = CloudXR.Session['availableMessageChannels'][number];
Expand Down Expand Up @@ -118,8 +118,7 @@ function buildOobHubWsUrlFromQuery(searchParams: URLSearchParams): string | null
const portStr = searchParams.get('port')?.trim();
if (!serverIP || portStr === undefined || portStr === '') return null;
if (!/^\d{1,5}$/.test(portStr)) return null;
const host =
serverIP.includes(':') && !serverIP.startsWith('[') ? `[${serverIP}]` : serverIP;
const host = serverIP.includes(':') && !serverIP.startsWith('[') ? `[${serverIP}]` : serverIP;
return `wss://${host}:${portStr}/oob/v1/ws`;
}

Expand Down Expand Up @@ -337,10 +336,7 @@ function App() {
ui.initialize(Object.keys(urlSeeds).length > 0 ? urlSeeds : undefined);
const doConnect = async () => {
const config = ui.getConfiguration();
const resolutionError = getResolutionValidationError(
config.perEyeWidth,
config.perEyeHeight
);
const resolutionError = getResolutionValidationError(config.perEyeWidth, config.perEyeHeight);
if (resolutionError) {
ui.updateConnectButtonState();
return;
Expand Down Expand Up @@ -560,7 +556,6 @@ function App() {
}, 1000);
};


const handleResetTeleop = async () => {
console.info('Reset Teleop pressed');

Expand Down Expand Up @@ -674,7 +669,9 @@ function App() {
});
channel.connect();

return () => { channel.dispose(); };
return () => {
channel.dispose();
};
}, [cloudXR2DUI]);

// Countdown configuration handlers (0-5 seconds)
Expand Down Expand Up @@ -735,9 +732,7 @@ function App() {
const uuidHex = Array.from(ch.uuid as Uint8Array)
.map((b: number) => b.toString(16).padStart(2, '0'))
.join('');
console.info(
` [${i}] uuid=${uuidHex} status=${ch.status}`
);
console.info(` [${i}] uuid=${uuidHex} status=${ch.status}`);
});

const channel = findChannelByUuid(channels, TELEOP_CHANNEL_UUID);
Expand Down
Loading
Loading