designdebt.club
Menu
Design Debt Club

Can’t access my WebXR from Meta Quest

When I first tried building a basic WebXR app for my Quest 3 headset, I got caught by a few gotchas. I’ll describe them below.

I used BabylonJS & Vite for my setup, but while the code examples will be tailored toward that in some instances, the concepts will hopefully be universally relevant.

Access the page from your headset

You’re probably developing on your laptop or desktop, in which case, at many points along the way you will want to open the page in your headset.

Vite’s defaults will give you an address that will only work on the device you’re running the process on though. ie…

http://localhost:3000

You turn that into a link accessible from any device online by adding the following host value to your vite.config.js file. It’s not enough though, so keep reading.

import { defineConfig } from 'vite';

///////////////////

export default defineConfig({
  root: 'src',
  build: {
    outDir: '../dist',
    emptyOutDir: true,
  },
  server: {
    host: '0.0.0.0', // This makes the server accessible publicly
    port: 3000
  }
}); 

Now, when you restart the server you’ll get both a local URL and a network URL. Use the network URL to access the page from another device while the server is running.

Recognise the Quest as an XR supported device

The next stumbling block is that you cannot enter VR or AR mode unless the device is being served through an SSL connection. If it’s not, opening the page on the Quest headset will look just like on your laptop — It won’t report as XR capable and therefore won’t give you a button to enter immersive mode (Or if you manually created the button, it won’t work).

You can fix this during development with a temporary certificate.

First install the @vitejs/plugin-basic-ssl package.

npm i @vitejs/plugin-basic-ssl

Then update your vite.config.js file like this:

import { defineConfig } from 'vite';
import basicSsl from '@vitejs/plugin-basic-ssl'; // Don't forget the import

///////////////////

export default defineConfig({
  root: 'src',
  build: {
    outDir: '../dist',
    emptyOutDir: true,
  },
  server: {
    host: '0.0.0.0',
    port: 3000
  },

  // This gives you a basic SSL certificate
  plugins: [
    basicSsl({
      name: 'test', // name of certification
      domains: ['*.custom.com'], // custom trust domains
      certDir: '/Users/.../.devServer/cert' // custom certification directory
    })
  ]

}); 

Now when you restart he server you’ll get a public address that you can use in your Headset. The address might look the same, but it’s not, it’s https instead of http.

When you access it, it will give you a warning about an unknown certificate, but you can click advanced and proceed anyway. This is safe, since you made the page.

Side-note: AI told me to do the below, but I found it doesn’t do the job. On it’s own it will create an https link, but it won’t create a certificate. If you do the above instead, you don’t need the below because you’ll get both automatically.

Not needed:

server: {
    https: true,
    port: 3000
},