From ded9ee70605279cebb208caf57cd6c1203403d17 Mon Sep 17 00:00:00 2001 From: Joe Date: Tue, 7 Feb 2017 12:48:17 -0600 Subject: [PATCH] initial checkin --- EventSource.js | 186 +++++++++++++++++++++++++++++++++++++++++++ display.html | 212 +++++++++++++++++++++++++++++++++++++++++++++++++ install.txt | 37 +++++++++ package.json | 19 +++++ server.js | 180 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 634 insertions(+) create mode 100644 EventSource.js create mode 100644 display.html create mode 100644 install.txt create mode 100644 package.json create mode 100644 server.js diff --git a/EventSource.js b/EventSource.js new file mode 100644 index 0000000..92ca621 --- /dev/null +++ b/EventSource.js @@ -0,0 +1,186 @@ +;(function (global) { + +if ("EventSource" in global) return; + +var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g; + +var EventSource = function (url) { + var eventsource = this, + interval = 500, // polling interval + lastEventId = null, + cache = ''; + + if (!url || typeof url != 'string') { + throw new SyntaxError('Not enough arguments'); + } + + this.URL = url; + this.readyState = this.CONNECTING; + this._pollTimer = null; + this._xhr = null; + + function pollAgain(interval) { + eventsource._pollTimer = setTimeout(function () { + poll.call(eventsource); + }, interval); + } + + function poll() { + try { // force hiding of the error message... insane? + if (eventsource.readyState == eventsource.CLOSED) return; + + // NOTE: IE7 and upwards support + var xhr = new XMLHttpRequest(); + xhr.open('GET', eventsource.URL, true); + xhr.setRequestHeader('Accept', 'text/event-stream'); + xhr.setRequestHeader('Cache-Control', 'no-cache'); + // we must make use of this on the server side if we're working with Android - because they don't trigger + // readychange until the server connection is closed + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + if (lastEventId != null) xhr.setRequestHeader('Last-Event-ID', lastEventId); + cache = ''; + + xhr.timeout = 50000; + xhr.onreadystatechange = function () { + if (this.readyState == 3 || (this.readyState == 4 && this.status == 200)) { + // on success + if (eventsource.readyState == eventsource.CONNECTING) { + eventsource.readyState = eventsource.OPEN; + eventsource.dispatchEvent('open', { type: 'open' }); + } + + var responseText = ''; + try { + responseText = this.responseText || ''; + } catch (e) {} + + // process this.responseText + var parts = responseText.substr(cache.length).split("\n"), + eventType = 'message', + data = [], + i = 0, + line = ''; + + cache = responseText; + + // TODO handle 'event' (for buffer name), retry + for (; i < parts.length; i++) { + line = parts[i].replace(reTrim, ''); + if (line.indexOf('event') == 0) { + eventType = line.replace(/event:?\s*/, ''); + } else if (line.indexOf('retry') == 0) { + retry = parseInt(line.replace(/retry:?\s*/, '')); + if(!isNaN(retry)) { interval = retry; } + } else if (line.indexOf('data') == 0) { + data.push(line.replace(/data:?\s*/, '')); + } else if (line.indexOf('id:') == 0) { + lastEventId = line.replace(/id:?\s*/, ''); + } else if (line.indexOf('id') == 0) { // this resets the id + lastEventId = null; + } else if (line == '') { + if (data.length) { + var event = new MessageEvent(data.join('\n'), eventsource.url, lastEventId); + eventsource.dispatchEvent(eventType, event); + data = []; + eventType = 'message'; + } + } + } + + if (this.readyState == 4) pollAgain(interval); + // don't need to poll again, because we're long-loading + } else if (eventsource.readyState !== eventsource.CLOSED) { + if (this.readyState == 4) { // and some other status + // dispatch error + eventsource.readyState = eventsource.CONNECTING; + eventsource.dispatchEvent('error', { type: 'error' }); + pollAgain(interval); + } else if (this.readyState == 0) { // likely aborted + pollAgain(interval); + } else { + } + } + }; + + xhr.send(); + + setTimeout(function () { + if (true || xhr.readyState == 3) xhr.abort(); + }, xhr.timeout); + + eventsource._xhr = xhr; + + } catch (e) { // in an attempt to silence the errors + eventsource.dispatchEvent('error', { type: 'error', data: e.message }); // ??? + } + }; + + poll(); // init now +}; + +EventSource.prototype = { + close: function () { + // closes the connection - disabling the polling + this.readyState = this.CLOSED; + clearInterval(this._pollTimer); + this._xhr.abort(); + }, + CONNECTING: 0, + OPEN: 1, + CLOSED: 2, + dispatchEvent: function (type, event) { + var handlers = this['_' + type + 'Handlers']; + if (handlers) { + for (var i = 0; i < handlers.length; i++) { + handlers[i].call(this, event); + } + } + + if (this['on' + type]) { + this['on' + type].call(this, event); + } + }, + addEventListener: function (type, handler) { + if (!this['_' + type + 'Handlers']) { + this['_' + type + 'Handlers'] = []; + } + + this['_' + type + 'Handlers'].push(handler); + }, + removeEventListener: function (type, handler) { + var handlers = this['_' + type + 'Handlers']; + if (!handlers) { + return; + } + for (var i = handlers.length - 1; i >= 0; --i) { + if (handlers[i] === handler) { + handlers.splice(i, 1); + break; + } + } + }, + onerror: null, + onmessage: null, + onopen: null, + readyState: 0, + URL: '' +}; + +var MessageEvent = function (data, origin, lastEventId) { + this.data = data; + this.origin = origin; + this.lastEventId = lastEventId || ''; +}; + +MessageEvent.prototype = { + data: null, + type: 'message', + lastEventId: '', + origin: '' +}; + +if ('module' in global) module.exports = EventSource; +global.EventSource = EventSource; + +})(this); diff --git a/display.html b/display.html new file mode 100644 index 0000000..8cb605f --- /dev/null +++ b/display.html @@ -0,0 +1,212 @@ + + + + + Joe's Web-Clipboard + + + + + + + +Web-Clipboard by joe@netsquat.com + + Connection +
+ + +
+
+ + + + + +
+ +
+ +
+$Revision: 15 $ -- $Date: 2016-02-07 12:09:18 -0600 (Sun, 07 Feb 2016) $ + + diff --git a/install.txt b/install.txt new file mode 100644 index 0000000..12640b5 --- /dev/null +++ b/install.txt @@ -0,0 +1,37 @@ +put files into webserver-directory. + +install nodejs package +install nodejs-legacy package +install npm package +[sudo] npm -g install forever + +npm install autosize +ln -s node_modules/autosize/dist/autosize.min.js + + + +copy files to some directory (/var/local/nodejs/wc/) and make sure permissions are good +in the directory get the Q-library for promises: +cd /var/loca/nodejs/wc && npm install q + +configure a process to keep running node.js with the server.js: +--------------------------------------------------------------- +[edit] /etc/rd.local: +su - -c "cd /var/local/nodejs/wc; /usr/local/bin/forever start server.js" + + +Configure Lighttpd: +------------------- +cd /etc/lighttps/conf-enabled +ln -s ../conf-available/10-proxy.conf + +add to 10-proxy.conf: + +$HTTP["url"] =~ "^/wc/.*$" { + proxy.server = ( "" => + (( "host" => "127.0.0.1", "port" => 8000 )) + ) +} + + +re-start lighttpd diff --git a/package.json b/package.json new file mode 100644 index 0000000..20919dc --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "webclip", + "version": "1.0.0", + "description": "Web-Clipboard", + "main": "EventSource.js", + "directories": { + "doc": "docs" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "j.tretter@gmail.com", + "license": "ISC", + "dependencies": { + "q": "^1.4.1", + "querystring": "^0.2.0" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..8f40676 --- /dev/null +++ b/server.js @@ -0,0 +1,180 @@ +// $Revision: 16 $ -- $Date: 2016-02-08 15:14:12 -0600 (Mon, 08 Feb 2016) $ + +var http = require('http'); +var sys = require('sys'); +var fs = require('fs'); +var moniFilename = 'abc'; +var querystring = require('querystring'); +var q = require('q'); + +var deferredAllDone; + +http.createServer(function(req, res) { + //debugHeaders(req); + + deferredAllDone = q.defer(); + deferredAllDone.promise.done(function(pos) { + res.end(); + console.log("Response END. [" + pos + "]"); + }); + + if (req.headers.accept && req.headers.accept === 'text/event-stream') { + var arrMatches; + arrMatches = req.url.match(new RegExp(/\/events\/(.*)$/)); + if (arrMatches) { + moniFilename = arrMatches[1]; + console.log("Monitoring:" + moniFilename); + sendSSE(req, res); + } else { + res.writeHead(404); + deferredAllDone.resolve("404"); + } + } else { + console.log("RCV-HTML"); + res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' }); + arrMatches = req.url.match(new RegExp(/\/(save|raw)\/(.*)$/)); + console.log("Matches", arrMatches); + if ((arrMatches) && (arrMatches.length === 3)) { + var func = arrMatches[1]; + moniFilename = arrMatches[2]; + if ("save" === func) { + console.log("SAVE-Data"); + //sys.puts(sys.inspect(req)); + var fullBody = ''; + + req.on('data', function(chunk) { + // append the current chunk of data to the fullBody variable + fullBody += chunk.toString(); + }); + + //req.on('end', (function(res) { + req.on('end', function() { + var decodedBody = querystring.parse(fullBody); + //sys.puts(sys.inspect(decodedBody)); + + fs.writeFile(__dirname + "/docs/" + moniFilename, decodedBody.data, function(err) { + if (err) { + res.write('ERROR : "' + err + '"'); + deferredAllDone.resolve("OnEndERR"); + console.log(err); + } else { + fs.stat(__dirname + "/docs/" + moniFilename, function(err, stats) { + if (stats) { + console.log('deb' + stats.mtime); + res.write(constructJSON("Saved", stats.mtime)); + } + deferredAllDone.resolve("OnEndSaved"); + }); + } + }); + }); + //}(res))); + } else if ("raw" === func) { + console.log("RAW"); + try { + res.write(fs.readFileSync(__dirname + '/docs/' + moniFilename)); + } catch (e) { + res.writeHead(404, { 'Content-Type': 'text/html' }); + console.log("RAW - FAILED"); + } + deferredAllDone.resolve("OnRaw"); + } + } else { + var reqFile=req.url.replace("/wc/",""); + + console.log("Orig Requested File " + reqFile ); + if ((reqFile!=='EventSource.js') && (reqFile!=='autosize.min.js') ) { + reqFile='display.html' + } + console.log("Requested File " + reqFile ); + var contentType="text/html"; + if (reqFile.match(/\.css/gi)) { + contentType="text/css"; + } + if (reqFile.match(/\.js/gi)) { + contentType="application/javascript"; + } + + + var filePath = __dirname + '/' + reqFile; + console.log("Full path " + filePath + " [" + contentType + "]" ); + + try { + var stat = fs.statSync(filePath); + + var readStream = fs.createReadStream(filePath); + res.writeHead(200, {"Content-Type":contentType}); + + readStream.pipe(res); // this will do a res.end() by its own. + + } catch (ex) { + res.writeHead(404,{"Content-Type":"text/html"}); + res.write("Not found: " + reqFile ); + deferredAllDone.resolve("OnDisplay"); + console.log ("Not found: " + reqFile, ex); + } + } + } +}).listen(8001,'127.0.0.1'); + +// Send the server side event - consider that this response never ends, so no res.end() is called whatsowever! +function sendSSE(req, res) { + var theClient=req.socket.remoteAddress + ":" + req.socket.remotePort; + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive' + }); + + var id = (new Date()).toLocaleTimeString(); + + fs.stat(__dirname + "/docs/" + moniFilename, function(err, stats) { + if (stats) { + console.log('deb' + stats.mtime); + res.write(constructSSE("Modified", stats.mtime)); + } + }); + + // Watching directory for changes + fs.watch(__dirname + '/docs/', function(event, filename) { + console.log('event is: ' + event); + if (filename) { + console.log('filename provided: ' + filename); + console.log("Monitoring:" + moniFilename); + + if (moniFilename === filename) { + fs.stat(__dirname + "/docs/" + filename, function(err, stats) { + console.log('deb 2 ' +theClient + " - " + stats.mtime); + res.write(constructSSE("Modified", stats.mtime)); + }); + } + } else { + console.log('filename not provided'); + } + }); + +} + + + +// the SSE requires a "data" element +function constructSSE(type, timeStamp) { + var retVal = "data: " + constructJSON(type, timeStamp) + "\n\n"; + console.log('MESSAGE-DEBUG-L: ' + type + " _ " + timeStamp); + return (retVal); +} + +// just the JSON of the data +function constructJSON(type, timeStamp) { + var retVal = '{"type": "' + type + '",'; + retVal += ('"timeStamp": "' + timeStamp + '"}'); + return (retVal); +} + +function debugHeaders(req) { + sys.puts('URL: ' + req.url); + for (var key in req.headers) { + sys.puts(key + ': ' + req.headers[key]); + } + sys.puts('\n\n'); +}