Sending auth message to websocket doesn't behave as expected

I'm trying to authenticate a websocket connection to Octoprint 1.3.11 from my own web application. This doesn't behave as I expect and I can't get it to work.

I'm using the following Typescript code to connect to the Octoprint web socket:

export default class OctoprintConnection {
  // http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-37
  private url: string;
  private socket: WebSocket;
  private domain: string;
  private port: string;

  constructor(domain: string, port: string, apikey: string) {
    console.log("Connection", domain, port, apikey);
    const serverId = Math.floor(Math.random() * 999 + 1);
    const sessionId = this.generateSessionId();
    this.domain = domain;
    this.port = port;
    this.url = `ws://${domain}:${port}/sockjs/${serverId}/${sessionId}/websocket`;

    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      this.socket.send('{"auth":"user:key"}');
    };
  }

  private generateSessionId() {
    let sessionId = "";
    var chars =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    for (let i = 0; i < 16; i++)
      sessionId += chars.charAt(Math.floor(Math.random() * chars.length));

    return sessionId;
  }
}

I did have this working with the previous Octoprint version, but with authentication disabled entirely in Octoprint. So I know that establishing the Websocket connection and reading it actually works. But now I'm trying to get this to work on an Octoprint with access control enabled, and it doesn't work at all.

I get the following error message:

octoprint.server.util.sockjs - WARNING - Invalid JSON received from client ::1, ignoring: {u'auth': u'user:apikey'}

I added some more print statements to the on_message function in \octoprint\server\util\sockjs.py line 156 that throws this error. And I can see that instead of the expected string this function gets passed a dictionary, which of course blows up when it's trying to parse that as JSON.

I can also blow this up in a different way by sending an invalid JSON string as payload, and then I get the following error:

tornado.general - ERROR - WebSocket
Traceback (most recent call last):
File "c:\octoprint\venv\lib\site-packages\octoprint\vendor\sockjs\tornado\transports\websocket.py", line 58, in on_message
msg = proto.json_decode(bytes_to_str(message))
File "c:\octoprint\venv\lib\site-packages\octoprint\vendor\sockjs\tornado\proto.py", line 31, in
json_decode = lambda data: json.loads(data)
File "c:\python27\Lib\json_init_.py", line 339, in loads
return _default_decoder.decode(s)
File "c:\python27\Lib\json\decoder.py", line 367, in decode
raise ValueError(errmsg("Extra data", s, end, len(s)))
ValueError: Extra data: line 1 column 3 - line 1 column 33 (char 2 - 32)

With the logging I added I can also see that the web socket connection of the actual Octoprint interface is established and authenticated, and in that case on_message gets a string as expected. So for some reason I'm hitting an entirely different path in the web socket code.

Are there some simple Javascript examples on how to connect and authenticate to the Octoprint web socket? I'm obviously doing something different here than intended, but I can't see what is wrong with my code.

I looked a bit more into it, and after capturing the regular web socket traffic for the Octoprint UI I managed to figure out how the payload has to look for this to work:

this.socket.onopen = () => {
      this.socket.send(`["{\\"auth\\":\\"${user}:${apikey}\\"}"]`);
    };

The actual payload is a string with escaped quotes inside an array. So it seems that it's actually parsed as JSON in the first step, but the resulting array has to contain JSON as string as well, so it's kind of double-wrapped in JSON.

I'm attributing this to SockJS being weird, it's kind of confusing and unnecessarily complex.