`y-webrtc` : WebSocket connection failed : 'wss://y-webrtcsignaling-eu.herokuapp.com'

With this code:

import { WebrtcProvider } from 'y-webrtc'
import * as Y from "yjs";

  const [sharedType, provider] = useMemo(() => {
    const doc = new Y.Doc();
    const sharedType = doc.getArray<SyncElement>("content");
    const provider = new WebrtcProvider('ws://localhost:5555', doc)
    return [sharedType, provider];
  }, [id]);

  const editor = useMemo(() => {
    const editor = withCursor(
      withYjs(withLinks(withReact(withHistory(createEditor()))), sharedType),
      provider.awareness
    );
    return editor;
  }, [sharedType, provider]);


  useEffect(() => {
    provider.on("status", ({ status }: { status: string }) => {
      setOnlineState(status === "connected");
    });

    provider.awareness.setLocalState({
      alphaColor: color.slice(0, -2) + "0.2)",
      color,
      name,
    });

    provider.on("sync", (isSynced: boolean) => {
      if (isSynced && sharedType.length === 0) {
        toSharedType(sharedType, [
          { type: "paragraph", children: [{ text: "Hello world!" }] },
        ]);
      }
    });
    provider.connect();

    return () => {
      provider.disconnect();
    };
  }, [provider]);

I get these error messages :

Why and how is herokuapp involved if the server executed is the following one :

node y-webrtc-server.js 
Signaling server running on localhost: 5555

y-webrtc-server.js :

#!/usr/bin/env node

import ws from 'ws'
import http from 'http'
import * as map from 'lib0/map'

const wsReadyStateConnecting = 0
const wsReadyStateOpen = 1
const wsReadyStateClosing = 2 // eslint-disable-line
const wsReadyStateClosed = 3 // eslint-disable-line

const pingTimeout = 30000

const port = process.env.PORT || 5555
// @ts-ignore
const wss = new ws.Server({ noServer: true })

const server = http.createServer((request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' })
  response.end('okay')
})

/**
 * Map froms topic-name to set of subscribed clients.
 * @type {Map<string, Set<any>>}
 */
const topics = new Map()

/**
 * @param {any} conn
 * @param {object} message
 */
const send = (conn, message) => {
  if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) {
    conn.close()
  }
  try {
    conn.send(JSON.stringify(message))
  } catch (e) {
    conn.close()
  }
}

/**
 * Setup a new client
 * @param {any} conn
 */
const onconnection = conn => {
  /**
   * @type {Set<string>}
   */
  const subscribedTopics = new Set()
  let closed = false
  // Check if connection is still alive
  let pongReceived = true
  const pingInterval = setInterval(() => {
    if (!pongReceived) {
      conn.close()
      clearInterval(pingInterval)
    } else {
      pongReceived = false
      try {
        conn.ping()
      } catch (e) {
        conn.close()
      }
    }
  }, pingTimeout)
  conn.on('pong', () => {
    pongReceived = true
  })
  conn.on('close', () => {
    subscribedTopics.forEach(topicName => {
      const subs = topics.get(topicName) || new Set()
      subs.delete(conn)
      if (subs.size === 0) {
        topics.delete(topicName)
      }
    })
    subscribedTopics.clear()
    closed = true
  })
  conn.on('message', /** @param {object} message */ message => {
    if (typeof message === 'string') {
      message = JSON.parse(message)
    }
    if (message && message.type && !closed) {
      switch (message.type) {
        case 'subscribe':
          /** @type {Array<string>} */ (message.topics || []).forEach(topicName => {
            if (typeof topicName === 'string') {
              // add conn to topic
              const topic = map.setIfUndefined(topics, topicName, () => new Set())
              topic.add(conn)
              // add topic to conn
              subscribedTopics.add(topicName)
            }
          })
          break
        case 'unsubscribe':
          /** @type {Array<string>} */ (message.topics || []).forEach(topicName => {
            const subs = topics.get(topicName)
            if (subs) {
              subs.delete(conn)
            }
          })
          break
        case 'publish':
          if (message.topic) {
            const receivers = topics.get(message.topic)
            if (receivers) {
              receivers.forEach(receiver =>
                send(receiver, message)
              )
            }
          }
          break
        case 'ping':
          send(conn, { type: 'pong' })
      }
    }
  })
}
wss.on('connection', onconnection)

server.on('upgrade', (request, socket, head) => {
  // You may check auth of request here..
  /**
   * @param {any} ws
   */
  const handleAuth = ws => {
    wss.emit('connection', ws, request)
  }
  wss.handleUpgrade(request, socket, head, handleAuth)
})

server.listen(port)

console.log('Signaling server running on localhost:', port)

I actually found that in /node_modules/y-webrtc/src$y-webrtc.js the heroku signaling server is hard-wired within the WebRTCProvider class :

xport class WebrtcProvider extends Observable {
/**
* @param {string} roomName
* @param {Y.Doc} doc
* @param {Object} [opts]
* @param {Array} [opts.signaling]
* @param {string?} [opts.password]
* @param {awarenessProtocol.Awareness} [opts.awareness]
* @param {number} [opts.maxConns]
* @param {boolean} [opts.filterBcConns]
* @param {any} [opts.peerOpts]
*/
constructor (
roomName,
doc,
{
signaling = [‘wss://signaling.yjs.dev’, ‘wss://y-webrtc-signaling-eu.herokuapp.com’, ‘wss://y-webrtc-signaling-us.herokuapp.com’],

But… how to modify it? I shouldn’t touch this repo…
And in client.js I’ve already specified:

const provider = new WebrtcProvider('ws://localhost:5555', doc)

How to solve the problem?

@raphael10-collab I have faced a similar problem with my deployment. The NodeJS server by default listens to localhost. But when you have the server deployed behind a hostname or some other kind of proxy servers, the request will be served with the hostname or the proxy server IP. NodeJS simply refuses to process this. What I did to fix is to specify the HOST environment variable with value “0.0.0.0”. This way, the server listens to everything. I’m not entirely convinced about the security aspects of it. I will let you know if I find anything more.

You need to specify the signaling servers in the optional arguments, not as the room name:

const provider = new WebrtcProvider(
  'your-room-name',
  ydoc,
  { signaling: ['ws://localhost:5555'] }
)

See the y-webrtc readme.

I checked but it’s already in this form:

const provider = new WebrtcProvider('webrtc-test', ydoc, { signaling: ['ws://localhost:5555']})

client.js :

import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'

const ydoc = new Y.Doc()
const provider = new WebrtcProvider('webrtc-test', ydoc, { signaling: ['ws://localhost:5555']})
const yarray = ydoc.getArray()

provider.on('synced', synced => {
  // NOTE: This is only called when a different browser connects to this client
  // Windows of the same browser communicate directly with each other
  // Although this behavior might be subject to change.
  // It is better not to expect a synced event when using y-webrtc
  console.log('synced!', synced)
})

yarray.observeDeep(() => {
  console.log('yarray updated: ', yarray.toJSON())
})

// @ts-ignore
window.example = { provider, ydoc, yarray }

Hi!!!

(base) raphy@pc:~/y-webrtc-playing$ node
Welcome to Node.js v14.15.5.
Type “.help” for more information.
> console.log(process.env)
{
SHELL: ‘/bin/bash’,
SESSION_MANAGER: ‘local/pc:@/tmp/.ICE-unix/4145,unix/pc:/tmp/.ICE-unix/4145’,
QT_ACCESSIBILITY: ‘1’,
COLORTERM: ‘truecolor’,
XDG_CONFIG_DIRS: ‘/etc/xdg/xdg-ubuntu:/etc/xdg’,
NVM_INC: ‘/home/raphy/.nvm/versions/node/v14.17.0/include/node’,
XDG_MENU_PREFIX: ‘gnome-’,
GNOME_DESKTOP_SESSION_ID: ‘this-is-deprecated’,
CONDA_EXE: ‘/home/raphy/anaconda3/bin/conda’,
_CE_M: ‘’,
MANDATORY_PATH: ‘/usr/share/gconf/ubuntu.mandatory.path’,
LC_ADDRESS: ‘it_IT.UTF-8’,
GNOME_SHELL_SESSION_MODE: ‘ubuntu’,
LC_NAME: ‘it_IT.UTF-8’,
SSH_AUTH_SOCK: ‘/run/user/1000/keyring/ssh’,
XMODIFIERS: ‘@im=ibus’,
DESKTOP_SESSION: ‘ubuntu’,
LC_MONETARY: ‘it_IT.UTF-8’,
SSH_AGENT_PID: ‘2671’,
GOBIN: ‘/home/raphy/go/bin’,
GTK_MODULES: ‘gail:atk-bridge’,
PWD: ‘/home/raphy/y-webrtc-playing’,
LOGNAME: ‘raphy’,
XDG_SESSION_DESKTOP: ‘ubuntu’,
XDG_SESSION_TYPE: ‘x11’,
CONDA_PREFIX: ‘/home/raphy/anaconda3’,
GPG_AGENT_INFO: ‘/run/user/1000/gnupg/S.gpg-agent:0:1’,
XAUTHORITY: ‘/run/user/1000/gdm/Xauthority’,
IM_CONFIG_CHECK_ENV: ‘1’,
GJS_DEBUG_TOPICS: ‘JS ERROR;JS LOG’,
WINDOWPATH: ‘2’,
HOME: ‘/home/raphy’,
USERNAME: ‘raphy’,
IM_CONFIG_PHASE: ‘1’,
LC_PAPER: ‘it_IT.UTF-8’,
LANG: ‘en_US.UTF-8’,
LS_COLORS: ‘rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:.tar=01;31:.tgz=01;31:.arc=01;31:.arj=01;31:.taz=01;31:.lha=01;31:.lz4=01;31:.lzh=01;31:.lzma=01;31:.tlz=01;31:.txz=01;31:.tzo=01;31:.t7z=01;31:.zip=01;31:.z=01;31:.dz=01;31:.gz=01;31:.lrz=01;31:.lz=01;31:.lzo=01;31:.xz=01;31:.zst=01;31:.tzst=01;31:.bz2=01;31:.bz=01;31:.tbz=01;31:.tbz2=01;31:.tz=01;31:.deb=01;31:.rpm=01;31:.jar=01;31:.war=01;31:.ear=01;31:.sar=01;31:.rar=01;31:.alz=01;31:.ace=01;31:.zoo=01;31:.cpio=01;31:.7z=01;31:.rz=01;31:.cab=01;31:.wim=01;31:.swm=01;31:.dwm=01;31:.esd=01;31:.jpg=01;35:.jpeg=01;35:.mjpg=01;35:.mjpeg=01;35:.gif=01;35:.bmp=01;35:.pbm=01;35:.pgm=01;35:.ppm=01;35:.tga=01;35:.xbm=01;35:.xpm=01;35:.tif=01;35:.tiff=01;35:.png=01;35:.svg=01;35:.svgz=01;35:.mng=01;35:.pcx=01;35:.mov=01;35:.mpg=01;35:.mpeg=01;35:.m2v=01;35:.mkv=01;35:.webm=01;35:.ogm=01;35:.mp4=01;35:.m4v=01;35:.mp4v=01;35:.vob=01;35:.qt=01;35:.nuv=01;35:.wmv=01;35:.asf=01;35:.rm=01;35:.rmvb=01;35:.flc=01;35:.avi=01;35:.fli=01;35:.flv=01;35:.gl=01;35:.dl=01;35:.xcf=01;35:.xwd=01;35:.yuv=01;35:.cgm=01;35:.emf=01;35:.ogv=01;35:.ogx=01;35:.aac=00;36:.au=00;36:.flac=00;36:.m4a=00;36:.mid=00;36:.midi=00;36:.mka=00;36:.mp3=00;36:.mpc=00;36:.ogg=00;36:.ra=00;36:.wav=00;36:.oga=00;36:.opus=00;36:.spx=00;36:*.xspf=00;36:’,
XDG_CURRENT_DESKTOP: ‘ubuntu:GNOME’,
VTE_VERSION: ‘6003’,
CONDA_PROMPT_MODIFIER: '(base) ',
GNOME_TERMINAL_SCREEN: ‘/org/gnome/Terminal/screen/2bb5eb2b_94d0_400d_9bc0_7027fbb00b7b’,
INVOCATION_ID: ‘dbac713464e34c7888b1abc690258f57’,
MANAGERPID: ‘2432’,
GOROOT: ‘/usr/local/go’,
GJS_DEBUG_OUTPUT: ‘stderr’,
NVM_DIR: ‘/home/raphy/.nvm’,
LESSCLOSE: ‘/usr/bin/lesspipe %s %s’,
XDG_SESSION_CLASS: ‘user’,
TERM: ‘xterm-256color’,
LC_IDENTIFICATION: ‘it_IT.UTF-8’,
_CE_CONDA: ‘’,
DEFAULTS_PATH: ‘/usr/share/gconf/ubuntu.default.path’,
LESSOPEN: ‘| /usr/bin/lesspipe %s’,
USER: ‘raphy’,
GNOME_TERMINAL_SERVICE: ‘:1.93’,
CONDA_SHLVL: ‘1’,
DISPLAY: ‘:0’,
SHLVL: ‘1’,
NVM_CD_FLAGS: ‘’,
LC_TELEPHONE: ‘it_IT.UTF-8’,
QT_IM_MODULE: ‘ibus’,
LC_MEASUREMENT: ‘it_IT.UTF-8’,
CONDA_PYTHON_EXE: ‘/home/raphy/anaconda3/bin/python’,
XDG_RUNTIME_DIR: ‘/run/user/1000’,
CONDA_DEFAULT_ENV: ‘base’,
PYENV_ROOT: ‘/home/raphy/.pyenv’,
LC_TIME: ‘it_IT.UTF-8’,
JOURNAL_STREAM: ‘8:64285’,
XDG_DATA_DIRS: ‘/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop’,
PATH: ‘/home/raphy/emsdk/upstream/emscripten:/home/raphy/emsdk/node/14.15.5_64bit/bin:/home/raphy/emsdk:/home/raphy/anaconda3/bin:/home/raphy/anaconda3/condabin:/home/raphy/.pyenv/shims:/home/raphy/.pyenv/bin:/home/raphy/.nvm/versions/node/v14.17.0/bin:/home/raphy/.local/bin:/home/raphy/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/local/go/bin:/usr/local/go:/home/raphy/go:/home/raphy/go/bin’,
GDMSESSION: ‘ubuntu’,
DBUS_SESSION_BUS_ADDRESS: ‘unix:path=/run/user/1000/bus’,
NVM_BIN: ‘/home/raphy/.nvm/versions/node/v14.17.0/bin’,
LC_NUMERIC: ‘it_IT.UTF-8’,
GOPATH: ‘/home/raphy/go’,
OLDPWD: ‘/home/raphy’,
_: ‘/home/raphy/emsdk/node/14.15.5_64bit/bin/node’
}
undefined

I gave a try with :

const provider = new WebrtcProvider('webrtc-test', ydoc, { signaling: ['ws://0.0.0.0:5555']})

but the problem persists.

Which of environment variables shown by process.env command should I set to “0.0.0.0” ?

The environment variable name is HOST. If it is not already defined, you will have to define it. And restart the server if it’s already running.

Thank you for helping.

Since I didn’t know how to set environment variables in node.js, I searched and found this explanation: https://www.thirdrocktechkno.com/blog/how-to-set-environment-variable-in-node-js/

Following the indications found there, I’ve put the HOST environment variable value into .env file.

I added in client.js this console.log("process.env.HOST: ", process.env.HOST)

client.js :

import * as Y from ‘yjs’
import { WebrtcProvider } from ‘y-webrtc’
import dotenv from ‘dotenv’

console.log("process.env.HOST: ", process.env.HOST)

const ydoc = new Y.Doc()
const provider = new WebrtcProvider('webrtc-test', ydoc, { signaling: ['ws://localhost:5555']})
const yarray = ydoc.getArray()

provider.on('synced', synced => {
  console.log('synced!', synced)
})

yarray.observeDeep(() => {
  console.log('yarray updated: ', yarray.toJSON())
})

// @ts-ignore
window.example = { provider, ydoc, yarray }

Based on what is stated here: dotenv - npm I preloaded dotenv :

(base) raphy@pc:~/y-webrtc-playing$ node -r dotenv/config client.js 
process.env.HOST:  0.0.0.0
file:///home/raphy/y-webrtc-playing/node_modules/lib0/websocket.js:25
    const websocket = new WebSocket(wsclient.url)
                      ^

ReferenceError: WebSocket is not defined
    at setupWS (file:///home/raphy/y-webrtc-playing/node_modules/lib0/websocket.js:25:23)
    at new WebsocketClient (file:///home/raphy/y-webrtc-playing/node_modules/lib0/websocket.js:122:5)
    at new SignalingConn (file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:461:5)
    at file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:596:75
    at Module.setIfUndefined (file:///home/raphy/y-webrtc-playing/node_modules/lib0/map.js:49:24)
    at file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:596:33
    at Array.forEach (<anonymous>)
    at WebrtcProvider.connect (file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:595:24)
    at new WebrtcProvider (file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:581:10)
    at file:///home/raphy/y-webrtc-playing/client.js:12:18

As you can see, the HOST environment variable is now set to “0.0.0.0” but the problem persists.
What am I missing or doing wrongly? How to make it right?

@raphael10-collab The environment variable needs to be set at the server side. Apologies for the confusion. I see that your server is running on heroku. Here’s a documentation on how to set environment variables on heroku. Hope this helps.

Actually this is my server :

(base) raphy@pc:~/y-webrtc-playing$ node ./bin/server.js 
Signaling server running on localhost: 5555

server.js :

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            #!/usr/bin/env node

import ws from 'ws'
import http from 'http'
import * as map from 'lib0/map'

const wsReadyStateConnecting = 0
const wsReadyStateOpen = 1
const wsReadyStateClosing = 2 // eslint-disable-line
const wsReadyStateClosed = 3 // eslint-disable-line

const pingTimeout = 30000

const port = process.env.PORT || 5555
// @ts-ignore
const wss = new ws.Server({ noServer: true })

const server = http.createServer((request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' })
  response.end('okay')
})

/**
 * Map froms topic-name to set of subscribed clients.
 * @type {Map<string, Set<any>>}
 */
const topics = new Map()

/**
 * @param {any} conn
 * @param {object} message
 */
const send = (conn, message) => {
  if (conn.readyState !== wsReadyStateConnecting && conn.readyState !== wsReadyStateOpen) {
    conn.close()
  }
  try {
    conn.send(JSON.stringify(message))
  } catch (e) {
    conn.close()
  }
}

/**
 * Setup a new client
 * @param {any} conn
 */
const onconnection = conn => {
  /**
   * @type {Set<string>}
   */
  const subscribedTopics = new Set()
  let closed = false
  // Check if connection is still alive
  let pongReceived = true
  const pingInterval = setInterval(() => {
    if (!pongReceived) {
      conn.close()
      clearInterval(pingInterval)
    } else {
      pongReceived = false
      try {
        conn.ping()
      } catch (e) {
        conn.close()
      }
    }
  }, pingTimeout)
  conn.on('pong', () => {
    pongReceived = true
  })
  conn.on('close', () => {
    subscribedTopics.forEach(topicName => {
      const subs = topics.get(topicName) || new Set()
      subs.delete(conn)
      if (subs.size === 0) {
        topics.delete(topicName)
      }
    })
    subscribedTopics.clear()
    closed = true
  })
  conn.on('message', /** @param {object} message */ message => {
    if (typeof message === 'string') {
      message = JSON.parse(message)
    }
    if (message && message.type && !closed) {
      switch (message.type) {
        case 'subscribe':
          /** @type {Array<string>} */ (message.topics || []).forEach(topicName => {
            if (typeof topicName === 'string') {
              // add conn to topic
              const topic = map.setIfUndefined(topics, topicName, () => new Set())
              topic.add(conn)
              // add topic to conn
              subscribedTopics.add(topicName)
            }
          })
          break
        case 'unsubscribe':
          /** @type {Array<string>} */ (message.topics || []).forEach(topicName => {
            const subs = topics.get(topicName)
            if (subs) {
              subs.delete(conn)
            }
          })
          break
        case 'publish':
          if (message.topic) {
            const receivers = topics.get(message.topic)
            if (receivers) {
              receivers.forEach(receiver =>
                send(receiver, message)
              )
            }
          }
          break
        case 'ping':
          send(conn, { type: 'pong' })
      }
    }
  })
}
wss.on('connection', onconnection)

server.on('upgrade', (request, socket, head) => {
  // You may check auth of request here..
  /**
   * @param {any} ws
   */
  const handleAuth = ws => {
    wss.emit('connection', ws, request)
  }
  wss.handleUpgrade(request, socket, head, handleAuth)
})

server.listen(port)

console.log('Signaling server running on localhost:', port)

I’ve done the same with ./bin/server.js :

(base) raphy@pc:~/y-webrtc-playing$ node -r dotenv/config bin/server.js 
process.env.HOST:  0.0.0.0
Signaling server running on localhost: 5555

But still the problem persists:
(base) raphy@pc:~/y-webrtc-playing$ node -r dotenv/config client.js
process.env.HOST: 0.0.0.0
file:///home/raphy/y-webrtc-playing/node_modules/lib0/websocket.js:25
const websocket = new WebSocket(wsclient.url)
^

ReferenceError: WebSocket is not defined
    at setupWS (file:///home/raphy/y-webrtc-playing/node_modules/lib0/websocket.js:25:23)
    at new WebsocketClient (file:///home/raphy/y-webrtc-playing/node_modules/lib0/websocket.js:122:5)
    at new SignalingConn (file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:461:5)
    at file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:596:75
    at Module.setIfUndefined (file:///home/raphy/y-webrtc-playing/node_modules/lib0/map.js:49:24)
    at file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:596:33
    at Array.forEach (<anonymous>)
    at WebrtcProvider.connect (file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:595:24)
    at new WebrtcProvider (file:///home/raphy/y-webrtc-playing/node_modules/y-webrtc/src/y-webrtc.js:581:10)
    at file:///home/raphy/y-webrtc-playing/client.js:12:18

Your server side looks good :+1: . Looks like you’re trying to run the client-side via NodeJS. As far as I know, that’s not going to work. In my client code (I use y-monaco and y-websocket), I have a similar js file. But I’m bundling it using webpack and add the bundle to an HTML page. I’m then serving the HTML page using a NodeJS server. You can checkout the demo client code for y-monaco here. This one does not have a web server to run it. But you can use the included webpack dev server to run it. Also, the client code is in monaco.js.

This demo uses y-websocket