mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 03:39:55 +00:00
Add server (web/socket), create socket system, better helper loader.
- The Magic Mirror is now hosted via a express server, allowing you to load it from an external client (for debugging.) - It now includes a socket system to communicate between the node_helper and the client module. - node_helpers are now only loaded if the module is configured in the config.
This commit is contained in:
parent
15856574d7
commit
899d05bc32
@ -16,9 +16,7 @@ Things that still have to be implemented or changed.
|
|||||||
####Loader
|
####Loader
|
||||||
- Loading of module uses `eval()`. We might want to look into a better solution. [loader.js#L112](https://github.com/MichMich/MagicMirror/blob/v2-beta/js/loader.js#L112).
|
- Loading of module uses `eval()`. We might want to look into a better solution. [loader.js#L112](https://github.com/MichMich/MagicMirror/blob/v2-beta/js/loader.js#L112).
|
||||||
|
|
||||||
####Helper scripts
|
|
||||||
- Only start helper scripts of modules that are actually loaded in the UI (config.js)
|
|
||||||
- Notification system, so that not every helper scripts needs it's own socket to the UI.
|
|
||||||
- `modules/newsfeed/node_helper.js` now spawns it's own epxress webserver on port 8080. We need to create a solution for every module that needs a server side url.
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
|
port: 80,
|
||||||
|
|
||||||
language: 'en',
|
language: 'en',
|
||||||
timeFormat: 24,
|
timeFormat: 24,
|
||||||
@ -47,4 +48,8 @@ var config = {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== 'undefined') {module.exports = config;}
|
@ -27,6 +27,7 @@
|
|||||||
<div class="region bottom right"><div class="container"></div></div>
|
<div class="region bottom right"><div class="container"></div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
|
||||||
<script type="text/javascript" src="js/defaults.js"></script>
|
<script type="text/javascript" src="js/defaults.js"></script>
|
||||||
<script type="text/javascript" src="config/config.js"></script>
|
<script type="text/javascript" src="config/config.js"></script>
|
||||||
<script type="text/javascript" src="vendor/vendor.js"></script>
|
<script type="text/javascript" src="vendor/vendor.js"></script>
|
||||||
@ -34,6 +35,7 @@
|
|||||||
<script type="text/javascript" src="js/class.js"></script>
|
<script type="text/javascript" src="js/class.js"></script>
|
||||||
<script type="text/javascript" src="js/module.js"></script>
|
<script type="text/javascript" src="js/module.js"></script>
|
||||||
<script type="text/javascript" src="js/loader.js"></script>
|
<script type="text/javascript" src="js/loader.js"></script>
|
||||||
|
<script type="text/javascript" src="js/socketclient.js"></script>
|
||||||
<script type="text/javascript" src="js/main.js"></script>
|
<script type="text/javascript" src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var defaults = {
|
var defaults = {
|
||||||
|
port: 80,
|
||||||
|
|
||||||
language: 'en',
|
language: 'en',
|
||||||
timeFormat: 24,
|
timeFormat: 24,
|
||||||
@ -50,4 +51,8 @@ var defaults = {
|
|||||||
modules: 'modules',
|
modules: 'modules',
|
||||||
vendor: 'vendor'
|
vendor: 'vendor'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||||
|
if (typeof module !== 'undefined') {module.exports = defaults;}
|
153
js/electron.js
153
js/electron.js
@ -1,10 +1,15 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
//for searching modules
|
//load modules
|
||||||
const walk = require('walk');
|
const walk = require('walk');
|
||||||
|
const fs = require('fs');
|
||||||
|
const Server = require(__dirname + '/server.js');
|
||||||
const spawn = require('child_process').spawn;
|
const spawn = require('child_process').spawn;
|
||||||
|
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
|
|
||||||
|
|
||||||
|
// Config
|
||||||
|
var config = {};
|
||||||
// Module to control application life.
|
// Module to control application life.
|
||||||
const app = electron.app;
|
const app = electron.app;
|
||||||
// Module to create native browser window.
|
// Module to create native browser window.
|
||||||
@ -15,79 +20,115 @@ const BrowserWindow = electron.BrowserWindow;
|
|||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
|
||||||
function createWindow () {
|
function createWindow () {
|
||||||
// Create the browser window.
|
// Create the browser window.
|
||||||
mainWindow = new BrowserWindow({width: 800, height: 600, fullscreen: true, "auto-hide-menu-bar": true, "node-integration": false});
|
mainWindow = new BrowserWindow({width: 800, height: 600, fullscreen: true, "auto-hide-menu-bar": true, "node-integration": false});
|
||||||
|
|
||||||
// and load the index.html of the app.
|
// and load the index.html of the app.
|
||||||
mainWindow.loadURL('file://' + __dirname + '../../index.html');
|
//mainWindow.loadURL('file://' + __dirname + '../../index.html');
|
||||||
|
mainWindow.loadURL('http://localhost:' + config.port);
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
//mainWindow.webContents.openDevTools();
|
//mainWindow.webContents.openDevTools();
|
||||||
|
|
||||||
// Emitted when the window is closed.
|
// Emitted when the window is closed.
|
||||||
mainWindow.on('closed', function() {
|
mainWindow.on('closed', function() {
|
||||||
// Dereference the window object, usually you would store windows
|
// Dereference the window object, usually you would store windows
|
||||||
// in an array if your app supports multi windows, this is the time
|
// in an array if your app supports multi windows, this is the time
|
||||||
// when you should delete the corresponding element.
|
// when you should delete the corresponding element.
|
||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Walk module folder and get file names
|
function loadConfig (callback) {
|
||||||
var module_loader = walk.walk(__dirname + '/../modules', { followLinks: false });
|
console.log("Loading config ...");
|
||||||
|
var defaults = require(__dirname + '/defaults.js');
|
||||||
|
var configFilename = __dirname + '/../config/config.js';
|
||||||
|
|
||||||
//for each file in modules
|
try {
|
||||||
module_loader.on('file', function(root, stat, next) {
|
fs.accessSync(configFilename, fs.R_OK);
|
||||||
//if file is called node_helper.js load it
|
var c = require(configFilename);
|
||||||
if (stat.name == "node_helper.js"){
|
var config = Object.assign(defaults, c);
|
||||||
var module = (root + '/' + stat.name).split("/");
|
callback(config);
|
||||||
var moduleName = module[module.length-2];
|
} catch (e) {
|
||||||
|
callback(defaults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//start module as child
|
function loadModule(moduleName) {
|
||||||
var child = spawn('node', [root + '/' + stat.name])
|
var helperPath = __dirname + '/../modules/' + moduleName + '/node_helper.js';
|
||||||
|
|
||||||
// Make sure the output is logged.
|
try {
|
||||||
child.stdout.on('data', function(data) {
|
fs.accessSync(helperPath, fs.R_OK);
|
||||||
process.stdout.write(moduleName + ': ' + data);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.stderr.on('data', function(data) {
|
var child = spawn('node', [helperPath]);
|
||||||
process.stdout.write(moduleName + ': ' + data);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('close', function(code) {
|
|
||||||
console.log(moduleName + ' closing code: ' + code);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Make sure the output is logged.
|
||||||
|
child.stdout.on('data', function(data) {
|
||||||
|
process.stdout.write('[' + moduleName + '] ' + data);
|
||||||
|
});
|
||||||
|
|
||||||
//Log module name
|
child.stderr.on('data', function(data) {
|
||||||
|
process.stdout.write('[' + moduleName + '] ' + data);
|
||||||
console.log("Started helper script for module " + moduleName + ".");
|
});
|
||||||
}
|
|
||||||
next();
|
child.on('close', function(code) {
|
||||||
|
console.log(moduleName + ' closing code: ' + code);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Log module name
|
||||||
|
console.log("Started helper script for module: " + moduleName + ".");
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("No helper found for module: " + moduleName + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadModules(modules) {
|
||||||
|
console.log("Loading module helpers ...");
|
||||||
|
|
||||||
|
for (var m in modules) {
|
||||||
|
loadModule(modules[m]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("All module helpers loaded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig(function(c) {
|
||||||
|
config = c;
|
||||||
|
|
||||||
|
var modules = [];
|
||||||
|
|
||||||
|
for (var m in config.modules) {
|
||||||
|
var module = config.modules[m];
|
||||||
|
if (modules.indexOf(module.module) === -1) {
|
||||||
|
modules.push(module.module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadModules(modules);
|
||||||
});
|
});
|
||||||
|
|
||||||
module_loader.on('end', function() {
|
|
||||||
console.log("All helpers started.");
|
|
||||||
});
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
app.on('ready', createWindow);
|
app.on('ready', function() {
|
||||||
|
var server = new Server(config, function() {
|
||||||
|
createWindow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Quit when all windows are closed.
|
// Quit when all windows are closed.
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
// On OS X it is common for applications and their menu bar
|
// On OS X it is common for applications and their menu bar
|
||||||
// to stay active until the user quits explicitly with Cmd + Q
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
// On OS X it's common to re-create a window in the app when the
|
// On OS X it's common to re-create a window in the app when the
|
||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
if (mainWindow === null) {
|
if (mainWindow === null) {
|
||||||
createWindow();
|
createWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
37
js/module.js
37
js/module.js
@ -88,7 +88,15 @@ var Module = Class.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* socketNotificationReceived(notification, payload)
|
||||||
|
* This method is called when a socket notification arrives.
|
||||||
|
*
|
||||||
|
* argument notification string - The identifier of the noitication.
|
||||||
|
* argument payload mixed - The payload of the notification.
|
||||||
|
*/
|
||||||
|
socketNotificationReceived: function(notification, payload) {
|
||||||
|
Log.log(this.name + ' received a socket notification: ' + notification + ' - Payload: ' + payload);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -118,6 +126,23 @@ var Module = Class.extend({
|
|||||||
this.config = Object.assign(this.defaults, config);
|
this.config = Object.assign(this.defaults, config);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/* socket()
|
||||||
|
* Returns a socket object. If it doesn't exsist, it's created.
|
||||||
|
* It also registers the notification callback.
|
||||||
|
*/
|
||||||
|
socket: function() {
|
||||||
|
if (typeof this._socket === 'undefined') {
|
||||||
|
this._socket = this._socket = new MMSocket(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this._socket.setNotificationCallback(function(notification, payload) {
|
||||||
|
self.socketNotificationReceived(notification, payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._socket;
|
||||||
|
},
|
||||||
|
|
||||||
/* file(file)
|
/* file(file)
|
||||||
* Retrieve the path to a module fike.
|
* Retrieve the path to a module fike.
|
||||||
*
|
*
|
||||||
@ -170,6 +195,16 @@ var Module = Class.extend({
|
|||||||
*/
|
*/
|
||||||
sendNotification: function(notification, payload) {
|
sendNotification: function(notification, payload) {
|
||||||
MM.sendNotification(notification, payload, this);
|
MM.sendNotification(notification, payload, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* sendSocketNotification(notification, payload)
|
||||||
|
* Send a socket notification to the node helper.
|
||||||
|
*
|
||||||
|
* argument notification string - The identifier of the noitication.
|
||||||
|
* argument payload mixed - The payload of the notification.
|
||||||
|
*/
|
||||||
|
sendSocketNotification: function(notification, payload) {
|
||||||
|
this.socket().sendNotification(notification, payload);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
83
js/server.js
Normal file
83
js/server.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/* Magic Mirror
|
||||||
|
* Server
|
||||||
|
*
|
||||||
|
* By Michael Teeuw http://michaelteeuw.nl
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var express = require('express');
|
||||||
|
var app = require('express')();
|
||||||
|
var server = require('http').Server(app);
|
||||||
|
var io = require('socket.io')(server);
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var Server = function(config, callback) {
|
||||||
|
|
||||||
|
/* createNamespace(namespace)
|
||||||
|
* Creates a namespace with a wildcard event.
|
||||||
|
*
|
||||||
|
* argument namespace string - The name of the namespace.
|
||||||
|
*/
|
||||||
|
var createNamespace = function(namespace) {
|
||||||
|
console.log('Creating socket namespace: ' + namespace);
|
||||||
|
|
||||||
|
io.of(namespace).on('connection', function (socket) {
|
||||||
|
console.log("New socket connection on namespace: " + namespace);
|
||||||
|
|
||||||
|
// add a catch all event.
|
||||||
|
var onevent = socket.onevent;
|
||||||
|
socket.onevent = function (packet) {
|
||||||
|
var args = packet.data || [];
|
||||||
|
onevent.call (this, packet); // original call
|
||||||
|
packet.data = ["*"].concat(args);
|
||||||
|
onevent.call(this, packet); // additional call to catch-all
|
||||||
|
};
|
||||||
|
|
||||||
|
// register catch all.
|
||||||
|
socket.on('*', function (event, data) {
|
||||||
|
io.of(namespace).emit(event, data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* createNamespaces()
|
||||||
|
* Creates a namespace for all modules in the config.
|
||||||
|
*/
|
||||||
|
var createNamespaces = function() {
|
||||||
|
var modules = [];
|
||||||
|
var m;
|
||||||
|
|
||||||
|
for (m in config.modules) {
|
||||||
|
var module = config.modules[m];
|
||||||
|
if (modules.indexOf(module.module) === -1) {
|
||||||
|
modules.push(module.module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (m in modules) {
|
||||||
|
createNamespace(modules[m]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Starting server op port " + config.port + " ... ");
|
||||||
|
|
||||||
|
server.listen(config.port);
|
||||||
|
app.use('/js', express.static(__dirname));
|
||||||
|
app.use('/config', express.static(path.resolve(__dirname + '/../config')));
|
||||||
|
app.use('/css', express.static(path.resolve(__dirname + '/../css')));
|
||||||
|
app.use('/fonts', express.static(path.resolve(__dirname + '/../fonts')));
|
||||||
|
app.use('/modules', express.static(path.resolve(__dirname + '/../modules')));
|
||||||
|
app.use('/vendor', express.static(path.resolve(__dirname + '/../vendor')));
|
||||||
|
|
||||||
|
app.get('/', function (req, res) {
|
||||||
|
res.sendFile(path.resolve(__dirname + '/../index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
createNamespaces();
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Server;
|
40
js/socket.js
Normal file
40
js/socket.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/* exported Log */
|
||||||
|
|
||||||
|
/* Magic Mirror
|
||||||
|
* Socket Connection
|
||||||
|
*
|
||||||
|
* By Michael Teeuw http://michaelteeuw.nl
|
||||||
|
* MIT Licensed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var MMSocket = function(moduleName) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (typeof moduleName !== 'string') {
|
||||||
|
throw new Error('Please set the module name for the MMSocket.');
|
||||||
|
}
|
||||||
|
|
||||||
|
self.moduleName = moduleName;
|
||||||
|
|
||||||
|
|
||||||
|
self.socket = io('http://localhost:8080');
|
||||||
|
self.socket.on('notification', function (data) {
|
||||||
|
MM.sendNotification(data.notification, data.payload, Socket);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendMessage: function(notification, payload, sender) {
|
||||||
|
Log.log('Send socket message: ' + notification);
|
||||||
|
self.socket.emit('notification', {
|
||||||
|
notification: notification,
|
||||||
|
sender: sender,
|
||||||
|
payload: payload
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
83
js/socketclient.js
Normal file
83
js/socketclient.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
if (typeof window === 'undefined') {
|
||||||
|
// Only perfom this part if is isn't running in the browser.
|
||||||
|
|
||||||
|
// Load socket client
|
||||||
|
var io = require('socket.io-client');
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var config = {};
|
||||||
|
|
||||||
|
var defaults = require(__dirname + '/defaults.js');
|
||||||
|
var configFilename = __dirname + '/../config/config.js';
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.accessSync(configFilename, fs.R_OK);
|
||||||
|
var c = require(configFilename);
|
||||||
|
config = Object.assign(defaults, c);
|
||||||
|
} catch (e) {
|
||||||
|
config = defaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var MMSocket = function(moduleName) {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (typeof moduleName !== 'string') {
|
||||||
|
throw new Error('Please set the module name for the MMSocket.');
|
||||||
|
}
|
||||||
|
|
||||||
|
self.moduleName = moduleName;
|
||||||
|
|
||||||
|
// Private Methods
|
||||||
|
var socketBase = (typeof window === 'undefined') ? 'http://localhost:'+config.port : '';
|
||||||
|
socket = io(socketBase + '/' + self.moduleName);
|
||||||
|
|
||||||
|
var notificationCallback = function() {};
|
||||||
|
|
||||||
|
socket.on('connect', function(s) {
|
||||||
|
|
||||||
|
// add a catch all event.
|
||||||
|
var onevent = socket.onevent;
|
||||||
|
socket.onevent = function (packet) {
|
||||||
|
var args = packet.data || [];
|
||||||
|
onevent.call (this, packet); // original call
|
||||||
|
packet.data = ["*"].concat(args);
|
||||||
|
onevent.call(this, packet); // additional call to catch-all
|
||||||
|
};
|
||||||
|
|
||||||
|
// register catch all.
|
||||||
|
socket.on('*', function (notification, payload) {
|
||||||
|
if (notification !== '*') {
|
||||||
|
//console.log('Received notification: ' + notification +', payload: ' + payload);
|
||||||
|
notificationCallback(notification, payload);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var sendNotification = function(notification, payload) {
|
||||||
|
//console.log('Send notification: ' + notification +', payload: ' + payload);
|
||||||
|
socket.emit(notification, payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Public Methods
|
||||||
|
this.setNotificationCallback = function(callback) {
|
||||||
|
notificationCallback = callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sendNotification = function(notification, payload) {
|
||||||
|
if (typeof payload === 'undefined') {
|
||||||
|
payload = {};
|
||||||
|
}
|
||||||
|
sendNotification(notification, payload);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
module.exports = MMSocket;
|
||||||
|
}
|
@ -16,11 +16,6 @@ Module.create({
|
|||||||
reloadInterval: 10 * 60 * 1000, // every 10 minutes
|
reloadInterval: 10 * 60 * 1000, // every 10 minutes
|
||||||
updateInterval: 7.5 * 1000,
|
updateInterval: 7.5 * 1000,
|
||||||
animationSpeed: 2.5 * 1000,
|
animationSpeed: 2.5 * 1000,
|
||||||
|
|
||||||
|
|
||||||
proxyUrl: 'http://localhost:8080/?url=',
|
|
||||||
initialLoadDelay: 0, // 5 seconds delay. This delay is used to keep the OpenWeather API happy.
|
|
||||||
retryDelay: 2500,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Define required scripts.
|
// Define required scripts.
|
||||||
@ -37,14 +32,28 @@ Module.create({
|
|||||||
|
|
||||||
this.newsItems = [];
|
this.newsItems = [];
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.scheduleFetch(this.config.initialLoadDelay);
|
|
||||||
|
|
||||||
this.fetchTimer = null;
|
|
||||||
this.activeItem = 0;
|
this.activeItem = 0;
|
||||||
|
|
||||||
|
this.fetchNews();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Override socket notification handler.
|
||||||
|
socketNotificationReceived: function(notification, payload) {
|
||||||
|
if (notification === 'NEWS_ITEMS') {
|
||||||
|
if (payload.url === this.config.feedUrl) {
|
||||||
|
this.newsItems = payload.items;
|
||||||
|
if (!this.loaded) {
|
||||||
|
this.scheduleUpdateInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Override dom generator.
|
// Override dom generator.
|
||||||
getDom: function() {
|
getDom: function() {
|
||||||
|
|
||||||
var wrapper = document.createElement("div");
|
var wrapper = document.createElement("div");
|
||||||
|
|
||||||
if (this.activeItem >= this.newsItems.length) {
|
if (this.activeItem >= this.newsItems.length) {
|
||||||
@ -57,6 +66,7 @@ Module.create({
|
|||||||
var timestamp = document.createElement("div");
|
var timestamp = document.createElement("div");
|
||||||
timestamp.className = "light small dimmed";
|
timestamp.className = "light small dimmed";
|
||||||
timestamp.innerHTML = this.capitalizeFirstLetter(moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow() + ':');
|
timestamp.innerHTML = this.capitalizeFirstLetter(moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow() + ':');
|
||||||
|
//timestamp.innerHTML = this.config.feedUrl;
|
||||||
wrapper.appendChild(timestamp);
|
wrapper.appendChild(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,30 +87,11 @@ Module.create({
|
|||||||
* Requests new data from news proxy.
|
* Requests new data from news proxy.
|
||||||
*/
|
*/
|
||||||
fetchNews: function() {
|
fetchNews: function() {
|
||||||
var url = this.config.proxyUrl + encodeURIComponent(this.config.feedUrl);
|
Log.log('Add news feed to fetcher: ' + this.config.feedUrl);
|
||||||
var self = this;
|
this.sendSocketNotification('ADD_FEED', {
|
||||||
|
url: this.config.feedUrl,
|
||||||
var newsRequest = new XMLHttpRequest();
|
reloadInterval: this.config.reloadInterval
|
||||||
newsRequest.open("GET", url, true);
|
});
|
||||||
newsRequest.onreadystatechange = function() {
|
|
||||||
if(this.readyState === 4) {
|
|
||||||
if(this.status === 200) {
|
|
||||||
self.newsItems = JSON.parse(this.response);
|
|
||||||
|
|
||||||
if (!self.loaded) {
|
|
||||||
self.scheduleUpdateInterval();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.loaded = true;
|
|
||||||
} else {
|
|
||||||
Log.error(self.name + ": Could not load news.");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scheduleFetch((self.loaded) ? -1 : self.config.retryDelay);
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
newsRequest.send();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* scheduleUpdateInterval()
|
/* scheduleUpdateInterval()
|
||||||
@ -117,24 +108,6 @@ Module.create({
|
|||||||
}, this.config.updateInterval);
|
}, this.config.updateInterval);
|
||||||
},
|
},
|
||||||
|
|
||||||
/* scheduleFetch()
|
|
||||||
* Schedule next news fetch.
|
|
||||||
*
|
|
||||||
* argument delay number - Milliseconds before next update. If empty, this.config.reloadInterval is used.
|
|
||||||
*/
|
|
||||||
scheduleFetch: function(delay) {
|
|
||||||
var nextLoad = this.config.reloadInterval;
|
|
||||||
if (typeof delay !== 'undefined' && delay >= 0) {
|
|
||||||
nextLoad = delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
clearTimeout(this.fetchTimer);
|
|
||||||
this.fetchTimer = setTimeout(function() {
|
|
||||||
self.fetchNews();
|
|
||||||
}, nextLoad);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* capitalizeFirstLetter(string)
|
/* capitalizeFirstLetter(string)
|
||||||
* Capitalizes the first character of a string.
|
* Capitalizes the first character of a string.
|
||||||
*
|
*
|
||||||
|
@ -1,17 +1,142 @@
|
|||||||
// Configuration.
|
|
||||||
var config = {
|
|
||||||
port: 8080
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load modules.
|
// Load modules.
|
||||||
var express = require('express');
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var FeedMe = require('feedme');
|
var FeedMe = require('feedme');
|
||||||
var validUrl = require('valid-url');
|
var validUrl = require('valid-url');
|
||||||
var app = express();
|
var MMSocket = require('../../js/socketclient.js');
|
||||||
|
var socket = new MMSocket('newsfeed');
|
||||||
|
|
||||||
// Create NewsFetcher.
|
var fetchers = {};
|
||||||
var NewsFetcher = (function() {
|
|
||||||
|
// Register the notification callback.
|
||||||
|
socket.setNotificationCallback(function(notification, payload) {
|
||||||
|
if(notification === 'ADD_FEED') {
|
||||||
|
createFetcher(payload.url, payload.reloadInterval);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* createFetcher(url, reloadInterval)
|
||||||
|
* Creates a fetcher for a new url if it doesn't exsist yet.
|
||||||
|
* Otherwise it reoses the exsisting one.
|
||||||
|
*
|
||||||
|
* attribute url string - URL of the news feed.
|
||||||
|
* attribute reloadInterval number - Reload interval in milliseconds.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var createFetcher = function(url, reloadInterval) {
|
||||||
|
if (!validUrl.isUri(url)){
|
||||||
|
socket.sendNotification('INCORRECT_URL', url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fetcher;
|
||||||
|
if (typeof fetchers[url] === 'undefined') {
|
||||||
|
console.log('Create new news fetcher for url: ' + url + ' - Interval: ' + reloadInterval);
|
||||||
|
fetcher = new Fetcher(url, reloadInterval);
|
||||||
|
fetchers[url] = fetcher;
|
||||||
|
} else {
|
||||||
|
console.log('Use exsisting news fetcher for url: ' + url);
|
||||||
|
fetcher = fetchers[url];
|
||||||
|
fetcher.setReloadInterval(reloadInterval);
|
||||||
|
fetcher.broadcastItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher.startFetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Fetcher
|
||||||
|
* Responsible for requesting an update on the set interval and broadcasting the data.
|
||||||
|
*
|
||||||
|
* attribute url string - URL of the news feed.
|
||||||
|
* attribute reloadInterval number - Reload interval in milliseconds.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Fetcher = function(url, reloadInterval) {
|
||||||
|
var self = this;
|
||||||
|
var newsFetcher = new NewsFetcher();
|
||||||
|
if (reloadInterval < 1000) {
|
||||||
|
reloadInterval = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reloadTimer = null;
|
||||||
|
var items = [];
|
||||||
|
|
||||||
|
/* private methods */
|
||||||
|
|
||||||
|
/* fetchNews()
|
||||||
|
* Request the new items from the newsFetcher.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fetchNews = function() {
|
||||||
|
//console.log('Fetch news.');
|
||||||
|
clearTimeout(reloadTimer);
|
||||||
|
reloadTimer = null;
|
||||||
|
newsFetcher.fetchNews(url, function(fetchedItems) {
|
||||||
|
//console.log(fetchedItems.length + ' items received.');
|
||||||
|
items = fetchedItems;
|
||||||
|
self.broadcastItems();
|
||||||
|
scheduleTimer();
|
||||||
|
}, function(error) {
|
||||||
|
//console.log('Unable to load news: ' + error);
|
||||||
|
socket.sendNotification('UNABLE_TO_LOAD_NEWS', {url:url, error:error});
|
||||||
|
scheduleTimer();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* scheduleTimer()
|
||||||
|
* Schedule the timer for the next update.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var scheduleTimer = function() {
|
||||||
|
//console.log('Schedule update timer.');
|
||||||
|
clearTimeout(reloadTimer);
|
||||||
|
reloadTimer = setTimeout(function() {
|
||||||
|
fetchNews();
|
||||||
|
}, reloadInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* public methods */
|
||||||
|
|
||||||
|
/* setReloadInterval()
|
||||||
|
* Update the reload interval, but only if we need to increase the speed.
|
||||||
|
*
|
||||||
|
* attribute interval number - Interval for the update in milliseconds.
|
||||||
|
*/
|
||||||
|
this.setReloadInterval = function(interval) {
|
||||||
|
if (interval > 1000 && interval < reloadInterval) {
|
||||||
|
reloadInterval = interval;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* startFetch()
|
||||||
|
* Initiate fetchNews();
|
||||||
|
*/
|
||||||
|
this.startFetch = function() {
|
||||||
|
fetchNews();
|
||||||
|
};
|
||||||
|
|
||||||
|
/* broadcastItems()
|
||||||
|
* Broadcast the exsisting items.
|
||||||
|
*/
|
||||||
|
this.broadcastItems = function() {
|
||||||
|
if (items.length <= 0) {
|
||||||
|
//console.log('No items to broadcast yet.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//console.log('Broadcasting ' + items.length + ' items.');
|
||||||
|
socket.sendNotification('NEWS_ITEMS', {
|
||||||
|
url: url,
|
||||||
|
items: items
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* NewsFetcher
|
||||||
|
* Responsible for requesting retrieving the data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var NewsFetcher = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.successCallback = function(){};
|
self.successCallback = function(){};
|
||||||
@ -33,41 +158,27 @@ var NewsFetcher = (function() {
|
|||||||
self.successCallback(self.items);
|
self.successCallback(self.items);
|
||||||
});
|
});
|
||||||
|
|
||||||
parser.on('error', function(item) {
|
parser.on('error', function(error) {
|
||||||
self.errorCallback();
|
self.errorCallback(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
/* public methods */
|
||||||
fetchNews: function(url, success, error) {
|
|
||||||
self.successCallback = success;
|
/* fetchNews()
|
||||||
self.errorCallback = error;
|
* Fetch the new news items.
|
||||||
request(url).pipe(parser);
|
*
|
||||||
}
|
* attribute url string - The url to fetch.
|
||||||
|
* attribute success function(items) - Callback on succes.
|
||||||
|
* attribute error function(error) - Callback on error.
|
||||||
|
*/
|
||||||
|
self.fetchNews = function(url, success, error) {
|
||||||
|
self.successCallback = success;
|
||||||
|
self.errorCallback = error;
|
||||||
|
request(url).pipe(parser);
|
||||||
};
|
};
|
||||||
})();
|
};
|
||||||
|
|
||||||
// Create route for fetcher.
|
|
||||||
app.get('/', function (req, res) {
|
|
||||||
|
|
||||||
if (!validUrl.isUri(req.query.url)){
|
|
||||||
res.status(404).send('No valid feed URL.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NewsFetcher.fetchNews(req.query.url, function(items) {
|
|
||||||
res.send(items);
|
|
||||||
}, function() {
|
|
||||||
res.status(400).send('Could not parse feed.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen on port.
|
|
||||||
app.listen(config.port, function () {
|
|
||||||
console.log('Feed proxy is running on port: ' + config.port);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Starting feed proxy on port: ' + config.port);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,10 +27,13 @@
|
|||||||
"electron-prebuilt": "latest"
|
"electron-prebuilt": "latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"socket.io":"latest",
|
||||||
"express":"latest",
|
"express":"latest",
|
||||||
"request":"latest",
|
"request":"latest",
|
||||||
"walk": "latest",
|
"walk": "latest",
|
||||||
"feedme": "latest",
|
"feedme": "latest",
|
||||||
"valid-url": "latest"
|
"valid-url": "latest",
|
||||||
|
"ical": "latest",
|
||||||
|
"moment": "latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user