diff --git a/src/util/scratch-link-websocket.js b/src/util/scratch-link-websocket.js index 035cea566..753baca6a 100644 --- a/src/util/scratch-link-websocket.js +++ b/src/util/scratch-link-websocket.js @@ -23,26 +23,75 @@ class ScratchLinkWebSocket { } open () { + if (!(this._onOpen && this._onClose && this._onError && this._handleMessage)) { + throw new Error('Must set open, close, message and error handlers before calling open on the socket'); + } + + let pathname; switch (this._type) { case 'BLE': - this._ws = new WebSocket('wss://device-manager.scratch.mit.edu:20110/scratch/ble'); + pathname = 'scratch/ble'; break; case 'BT': - this._ws = new WebSocket('wss://device-manager.scratch.mit.edu:20110/scratch/bt'); + pathname = 'scratch/bt'; break; default: throw new Error(`Unknown ScratchLink socket Type: ${this._type}`); } - if (this._onOpen && this._onClose && this._onError && this._handleMessage) { + // Try ws:// (the new way) and wss:// (the old way) simultaneously. If either connects, close the other. If we + // were to try one and fall back to the other on failure, that could mean a delay of 30 seconds or more for + // those who need the fallback. + // If both connections fail we should report only one error. + + const setSocket = (socketToUse, socketToClose) => { + socketToClose.onopen = socketToClose.onerror = null; + socketToClose.close(); + + this._ws = socketToUse; this._ws.onopen = this._onOpen; this._ws.onclose = this._onClose; this._ws.onerror = this._onError; - } else { - throw new Error('Must set open, close, message and error handlers before calling open on the socket'); - } + this._ws.onmessage = this._onMessage.bind(this); + }; - this._ws.onmessage = this._onMessage.bind(this); + const ws = new WebSocket(`ws://127.0.0.1:20111/${pathname}`); + const wss = new WebSocket(`wss://device-manager.scratch.mit.edu:20110/${pathname}`); + + const connectTimeout = setTimeout(() => { + // neither socket succeeded before the timeout + setSocket(ws, wss); + this._ws.onerror(new Event('timeout')); + }, 15 * 1000); + ws.onopen = openEvent => { + clearTimeout(connectTimeout); + setSocket(ws, wss); + this._ws.onopen(openEvent); + }; + wss.onopen = openEvent => { + clearTimeout(connectTimeout); + setSocket(wss, ws); + this._ws.onopen(openEvent); + }; + + let wsError; + let wssError; + const errorHandler = () => { + // if only one has received an error, we haven't overall failed yet + if (wsError && wssError) { + clearTimeout(connectTimeout); + setSocket(ws, wss); + this._ws.onerror(wsError); + } + }; + ws.onerror = errorEvent => { + wsError = errorEvent; + errorHandler(); + }; + wss.onerror = errorEvent => { + wssError = errorEvent; + errorHandler(); + }; } close () {