From 8c5826972ba0bda236de5d40eeb15f30dec9ff3f Mon Sep 17 00:00:00 2001 From: Ondrej Zara Date: Tue, 19 Mar 2019 11:13:15 +0100 Subject: [PATCH] initial --- .gitignore | 2 ++ commands.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ demo/index.html | 6 +++++ demo/index.js | 4 +++ index.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 +++++++++++ 6 files changed, 167 insertions(+) create mode 100644 .gitignore create mode 100644 commands.js create mode 100644 demo/index.html create mode 100644 demo/index.js create mode 100644 index.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93f1361 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/commands.js b/commands.js new file mode 100644 index 0000000..77fe84e --- /dev/null +++ b/commands.js @@ -0,0 +1,69 @@ +const EventEmitter = require("events"); + +function log(...args) { + console.log(Date.now(), ...args); +} + +class Command extends EventEmitter { + constructor(mpd) { + super(); + this._mpd = mpd; + this._buffer = Buffer.alloc(0); + this._dataListener = data => this._onData(data); + mpd.on("data", this._dataListener); + } + + _onData(data) { + log("<-- mpd", data); + this._buffer = Buffer.concat([this._buffer, data]); + this._processBuffer(); + } + + _processBuffer() {} // abstract + + _done(data) { + this._mpd.off("data", this._dataListener); + this.emit("done", data); + } + + _getLine() { + let index = this._buffer.indexOf(0x0a); + if (index == -1) { return null; } + let str = this._buffer.slice(0, index).toString("utf8"); + this._buffer = this._buffer.slice(index+1); + return str; + } +} + +class Normal extends Command { + constructor(mpd, command) { + super(mpd); + this._lines = []; + log("--> mpd", command); + mpd.write(command + "\n"); + } + + _processBuffer() { + while (1) { + let line = this._getLine(); + if (!line) { break; } + this._lines.push(line); + if (line.startsWith("OK") || line.startsWith("ACK")) { return this._done(this._lines); } + } + } +} + +class Welcome extends Command { + _processBuffer() { + let line = this._getLine(); + if (line) { this._done(line); } + } +} + +exports.create = function(mpd, command) { + return new Normal(mpd, command); +} + +exports.welcome = function(mpd) { + return new Welcome(mpd); +} diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..4a9679b --- /dev/null +++ b/demo/index.html @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/demo/index.js b/demo/index.js new file mode 100644 index 0000000..c9ae2c4 --- /dev/null +++ b/demo/index.js @@ -0,0 +1,4 @@ +let httpServer = require("http").createServer(); +httpServer.listen(8080); + +require("..").ws2mpd(httpServer); diff --git a/index.js b/index.js new file mode 100644 index 0000000..31c36bb --- /dev/null +++ b/index.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +const commands = require("./commands"); + +function log(...args) { + console.log(Date.now(), ...args); +} + +function initConnection(ws, server, port) { + log(`ws connection accepted, connecting to ${server}:${port}`); + + let mpd = new (require("net").Socket)(); + mpd.setTimeout(0); + mpd.connect(port, server); + + let commandQueue = []; + let command = null; + + function waitForCommand(command) { + command.on("done", data => { + log("ws <--", data); + ws.send(data); + command = null; + processQueue(); + }); + } + + function processQueue() { + if (command || !commandQueue.length) { return; } + command = commands.create(mpd, commandQueue.shift()); + waitForCommand(command); + } + + ws.on("message", message => { + log("ws -->", message.utf8Data); + commandQueue.push(message.utf8Data); + processQueue(); + }); + + ws.on("close", (reasonCode, description) => { + log(`ws ${ws.remoteAddress} disconnected`); + mpd.end(); + }); + + mpd.on("close", () => { + log("mpd disconnected"); + ws.close(); + }); + + waitForCommand(commands.welcome(mpd)); +} + +function onRequest(request) { + let s = request.resourceURL.query.server || ""; + let r = s.match(/^([^:]+)(:([0-9]+))?$/); + if (!r) { return request.reject(); } + let connection = request.accept(null, request.origin); + + initConnection(connection, r[1], r[3] || 6600); +} + +exports.ws2mpd = function(httpServer) { + function ready() { log("ws2mpd attached to a http server", httpServer.address()); } + (httpServer.listening ? ready() : httpServer.on("listening", ready)); + + let wsServer = new (require("websocket").server)({ + httpServer, + autoAcceptConnections: false + }); + wsServer.on("request", onRequest); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2685bac --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "ws2mpd", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node demo" + }, + "author": "", + "license": "ISC", + "dependencies": { + "websocket": "^1.0.28" + } +}