import { h, Component, render } from '/js/preact.module.js'; import htm from '/js/htm.module.js'; // Initialize htm with Preact const html = htm.bind(h); window.mitty = mitt() const SVG_BATTERY_LEVEL = html`` const BATTERY_LEVEL = { 4: 'full', 3: 'medium', 2: 'low', 1: 'critical', 0: 'critical', } const PairingMethod = { DEFAULT: 'default', FAST: 'fast', STADIA: 'stadia', OLD: 'old', } const WsCommand = { GET_JOYCON_LIST: 'get_joycon_list', CONNECT_JOYCON: 'connect_joycon', DISCONNECT_JOYCON: 'disconnect_joycon', UPDATE_JOYCON_STATE: 'update_joycon_state', } const PairingState = { IDLE: 0, GETTING_TOKEN: 1, PAIRING: 2, CONNECTING: 3, CONNECTED: 4, DISCONNECTING: 5, DISCONNECTED: 10, ERROR_JOYCON: 101, ERROR_CONNECTION: 102, ERROR_INVALID_PAIRING_CODE: 103, ERROR_PUNCH_PAIRING: 104, ERROR_HOLE_PUNCHING: 105, ERROR_CONSOLE_CONNECTION: 106, } const PairingStateMessage = { [PairingState.IDLE]: 'Idle', [PairingState.GETTING_TOKEN]: 'Getting auth token...', [PairingState.PAIRING]: 'Sending pairing code...', [PairingState.CONNECTING]: 'Connecting with console...', [PairingState.CONNECTED]: 'Connected!', [PairingState.DISCONNECTED]: 'Disconnected', [PairingState.ERROR_JOYCON]: 'Joy-Con problem!', [PairingState.ERROR_CONNECTION]: 'Couldn\'t get auth token!', [PairingState.ERROR_INVALID_PAIRING_CODE]: 'Invalid pairing code!', [PairingState.ERROR_PUNCH_PAIRING]: 'Couldn\'t punch pairing!', [PairingState.ERROR_HOLE_PUNCHING]: 'Couldn\'t connect with console!', [PairingState.ERROR_CONSOLE_CONNECTION]: 'Couldn\'t connect with console!', } class PairingMethodPicker extends Component { constructor(props) { super() this.state = { pairing_method: props.pairing_method, } this.onChange = this.onChange.bind(this) } onChange(e) { const pairing_method = e.target.value this.setState({ pairing_method: pairing_method, }) window.mitty.emit('update_method', pairing_method) } render(props) { return html` ` } } class PrivateIpAddress extends Component { constructor(props) { super(props) let lock_host = false let host_ip_addr = props.host_ip_addr let console_ip_addr = props.console_ip_addr let hostname = window.location.hostname if (hostname.startsWith('192.168.') || hostname.startsWith('10.')) { host_ip_addr = hostname lock_host = true } this.state = { host_ip_addr: host_ip_addr, console_ip_addr: console_ip_addr, lock_host: lock_host, } this.onKeyPress = this.onKeyPress.bind(this) this.onChange = this.onChange.bind(this) } onChange(e) { const key = this.props.pairing_method == PairingMethod.DEFAULT ? 'host_ip_addr' : 'console_ip_addr' const value = e.target.value this.setState({ [key]: value, }) window.mitty.emit('update_addr', value) } onKeyPress(e) { if (!/[0-9\.]/.test(e.key)) { e.preventDefault() return } } componentDidMount() { let addr = this.props.pairing_method == PairingMethod.DEFAULT ? this.state.host_ip_addr : this.state.console_ip_addr window.mitty.emit('update_addr', addr) } render(props, state) { const pairing_method = props.pairing_method const addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr return html` ${(pairing_method == PairingMethod.STADIA) && html` `} ${(pairing_method == PairingMethod.DEFAULT && state.lock_host) && html` `} ${([PairingMethod.FAST, PairingMethod.OLD].indexOf(pairing_method) > -1 || (pairing_method == PairingMethod.DEFAULT && !state.lock_host)) && html` `} ` } } class PairingCode extends Component { constructor(props) { super(props) this.state = { pairing_code: props.pairing_code, } this.onChange = this.onChange.bind(this) } onChange(e) { const value = e.target.value this.setState({ pairing_code: value, }) window.mitty.emit('update_code', value) } render(props, state) { const pairing_method = props.pairing_method return html` ${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) > -1 && html` !/[0-9]/.test(e.key) && e.preventDefault()} onChange=${this.onChange} /> `} ${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) == -1 && html` `} ` } } class JoyCon extends Component { constructor(props) { super(props) this.connect = this.connect.bind(this) this.disconnect = this.disconnect.bind(this) this.onStateUpdated = this.onStateUpdated.bind(this) this.state = { ...props.joycon, } window.mitty.on('resp_' + WsCommand.DISCONNECT_JOYCON, this.onDisconnected) window.mitty.on('resp_' + WsCommand.UPDATE_JOYCON_STATE, this.onStateUpdated) } connect() { window.mitty.emit('req_' + WsCommand.CONNECT_JOYCON, this.props.joycon.serial) } disconnect() { window.mitty.emit('req_' + WsCommand.DISCONNECT_JOYCON, this.props.joycon.serial) } onStateUpdated(data) { if (data['serial'] != this.props.joycon.serial) { return } const state = data['state'] if (PairingStateMessage.hasOwnProperty(state)) { this.setState({ ...data, }) } } render(props, { name, state, pairing_code, is_left, color, battery_level }) { const joyconState = state const stateMessage = PairingStateMessage[joyconState] let showButton = true if ([PairingState.GETTING_TOKEN, PairingState.PAIRING, PairingState.CONNECTING].indexOf(joyconState) > -1) { showButton = false } let joyconSvg if (is_left) { joyconSvg = html`` } else { joyconSvg = html`` } const batteryLevel = BATTERY_LEVEL[battery_level] return html`
  • ${joyconSvg}
    ${name} ${SVG_BATTERY_LEVEL}
    ${stateMessage}
    ${pairing_code && html` ${pairing_code} `}
    ${showButton && joyconState == PairingState.CONNECTED && html` `} ${showButton && joyconState != PairingState.CONNECTED && html` `}
  • ` } } class JoyCons extends Component { constructor() { super() this.state = { isRefreshing: false, } this.refreshJoyconList = this.refreshJoyconList.bind(this) } refreshJoyconList() { this.setState({ isRefreshing: false, }) window.mitty.emit('req_' + WsCommand.GET_JOYCON_LIST) } componentDidMount() { } render(props, state) { return html`

    Joy-Cons

    ${state.isRefreshing && html` `}
    ${props.joycons.length == 0 && html`

    No Joy-Cons found!

    `} ${props.joycons.length > 0 && html` `}
    ` } } class App extends Component { constructor(props) { super() this.state = { pairing_method: window.CONFIG.pairing_method, host_ip_addr: window.CONFIG.host_ip_addr, console_ip_addr: window.CONFIG.console_ip_addr, pairing_code: window.CONFIG.pairing_code, joycons: [], } this.connectWs = this.connectWs.bind(this) this.sendRequest = this.sendRequest.bind(this) this.requestGetJoyconList = this.requestGetJoyconList.bind(this) this.requestConnectJoycon = this.requestConnectJoycon.bind(this) this.requestDisconnectJoycon = this.requestDisconnectJoycon.bind(this) this.handleMethodChange = this.handleMethodChange.bind(this) this.handleAddrChange = this.handleAddrChange.bind(this) this.handleCodeChange = this.handleCodeChange.bind(this) window.mitty.on('req_' + WsCommand.GET_JOYCON_LIST, this.requestGetJoyconList) window.mitty.on('req_' + WsCommand.CONNECT_JOYCON, this.requestConnectJoycon) window.mitty.on('req_' + WsCommand.DISCONNECT_JOYCON, this.requestDisconnectJoycon) window.mitty.on('update_method', this.handleMethodChange) window.mitty.on('update_addr', this.handleAddrChange) window.mitty.on('update_code', this.handleCodeChange) } sendRequest(cmd, data) { if (!data) { data = {} } const msg = { cmd: cmd, data: data, } console.log('send', msg) this.socket.send(JSON.stringify(msg)) } requestGetJoyconList() { this.sendRequest(WsCommand.GET_JOYCON_LIST); } requestConnectJoycon(serial) { const state = this.state const pairing_method = state.pairing_method let addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr if (!addr.match(/^(192\.168|10.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5]))\.((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.)(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/)) { alert('ERROR: Invalid IP address!') document.getElementById('ipAddr').focus() return } if (pairing_method == PairingMethod.DEFAULT) { const pairing_code = state.pairing_code if (!pairing_code.match(/^\d{6}$/)) { alert('ERROR: Invalid pairing code!') document.getElementById('pairingCode').focus() return } } this.sendRequest(WsCommand.CONNECT_JOYCON, { pairing_method: state.pairing_method, host_ip_addr: state.host_ip_addr, console_ip_addr: state.console_ip_addr, pairing_code: state.pairing_code, joycon_serial: serial, }) } requestDisconnectJoycon(serial) { this.sendRequest(WsCommand.DISCONNECT_JOYCON, { joycon_serial: serial, }) } connectWs() { const that = this this.socket = new WebSocket('ws://' + window.location.host + '/ws') this.socket.onopen = function(e) { console.log('[open] Connection established') that.requestGetJoyconList() } this.socket.onmessage = function(event) { const msg = JSON.parse(event.data) console.log(msg) const cmd = msg['cmd'] const shortCmd = msg['cmd'].slice(5) // Remove "resp_" prefix switch (shortCmd) { case WsCommand.GET_JOYCON_LIST: that.setState({ joycons: msg['data'], }) break default: window.mitty.emit(cmd, msg['data']) } } this.socket.onclose = function(event) { if (event.wasClean) { console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); } else { console.log('[close] Connection died'); } } this.socket.onerror = function(error) { console.log(`[error] ${error.message}`); } } handleMethodChange(pairing_method) { this.setState({ pairing_method: pairing_method, }) } handleAddrChange(addr) { const key = this.state.pairing_method == PairingMethod.DEFAULT ? 'host_ip_addr' : 'console_ip_addr' this.setState({ [key]: addr, }) } handleCodeChange(pairing_code) { this.setState({ pairing_code: pairing_code, }) } componentDidMount() { this.connectWs() } render(props, state) { return html`
         ░░  ░░░░░░  ░░    ░░ ░░░░░░   ░░░░░  ░░░    ░░  ░░░░░░ ░░░░░░░
         ▒▒ ▒▒    ▒▒  ▒▒  ▒▒  ▒▒   ▒▒ ▒▒   ▒▒ ▒▒▒▒   ▒▒ ▒▒      ▒▒
         ▒▒ ▒▒    ▒▒   ▒▒▒▒   ▒▒   ▒▒ ▒▒▒▒▒▒▒ ▒▒ ▒▒  ▒▒ ▒▒      ▒▒▒▒▒
    ▓▓   ▓▓ ▓▓    ▓▓    ▓▓    ▓▓   ▓▓ ▓▓   ▓▓ ▓▓  ▓▓ ▓▓ ▓▓      ▓▓
     █████   ██████     ██    ██████  ██   ██ ██   ████  ██████ ███████
                        

    Config

    <${PairingMethodPicker} pairing_method=${state.pairing_method}/>
    <${PrivateIpAddress} pairing_method=${state.pairing_method} host_ip_addr=${state.host_ip_addr} console_ip_addr=${state.console_ip_addr} />
    <${PairingCode} pairing_method=${state.pairing_method} pairing_code=${state.pairing_code} />
    <${JoyCons} pairing_method=${state.pairing_method} joycons=${state.joycons} />
    ` } } render(html`<${App} />`, document.body)