blob: 6689529f05ff16f0642a6d570c2cd1b85d5bff87 [file] [log] [blame]
/**
* The plugin system is based
* on registered widgets (iframes) from
* foreign services.
* The server component spawns new iframes and
* listens to them.
*
* @author Nils Diewald
*/
define(["plugin/widget", "util"], function (widgetClass) {
"use strict";
// Contains all widgets to address with
// messages to them
var widgets = {};
var plugins = {};
var buttons = {
match : []
};
// This is a counter to limit acceptable incoming messages
// to a certain amount. For every message, this counter will
// be decreased (down to 0), for every second this will be
// increased (up to 100).
// Once a widget surpasses the limit, it will be killed
// and called suspicious.
var maxMessages = 100;
var limits = {};
// TODO:
// It may be useful to establish a watcher that pings
// all widgets every second to see if it is still alive.
return {
/**
* Create new plugin management system
*/
create : function () {
return Object.create(this)._init();
},
/*
* Initialize the plugin manager
*/
_init : function () {
return this;
},
/**
* Register a plugin described as a JSON object.
*
* This is work in progress.
*/
register : function (obj) {
/* Example:
KorAP.Plugin.register({
'name' : 'CatContent',
'desc' : 'Some content about cats',
'about' : 'https://localhost:5678/',
'embed' : [{
'buttonGroup' : 'match',
'title' : loc.TRANSLATE,
'classes' : ['translate']
'onClick' : {
'action' : 'addWidget',
'panel' : 'match',
'template' : 'https://localhost:5678/?match={matchid}',
}
}]
});
*/
// TODO:
// These fields need to be localized by a structure like
// {
// de : {
// name : '..'
// }
// en : ...
// }
// for display
var name = obj["name"];
// Register plugin by name
var plugin = plugins[name] = {
name : name,
desc : obj["desc"],
about : obj["about"],
widgets : []
};
// Embed all embeddings of the plugin
for (var i in obj["embed"]) {
var embed = obj["embed"][i];
var addTo = embed["buttonGroup"];
var onClick = embed["onClick"];
// Needs to be localized as well
var title = embed["title"];
// The embedding will open a widget
if (onClick["action"] == "addWidget") {
var panel = onClick["panel"];
var that = this;
var cb = function (e) {
// Get the URL of the widget
var url = onClick["template"]; // that._interpolateURI(onClick["template"], this.match);
// Add the widget to the panel
var id = that.addWidget(document.getElementById(panel), name, url);
plugin["widgets"].push(id);
};
buttons["match"].push([title, embed["classes"], cb]);
};
};
},
// TODO:
// Interpolate URIs similar to https://tools.ietf.org/html/rfc6570
// but as simple as possible
_interpolateURI : function (uri, obj) {
// ...
},
/**
* Get named button group
*/
buttonGroup : function (name) {
return buttons[name];
},
/**
* Open a new widget as a child to a certain element
*/
addWidget : function (element, name, src) {
// Is it the first widget?
if (!this._listener) {
/*
* Establish the global 'message' hook.
*/
this._listener = this._receiveMsg.bind(this);
window.addEventListener("message", this._listener);
// Every second increase the limits of all registered widgets
this._timer = window.setInterval(function () {
for (var i in limits) {
if (limits[i]++ >= maxMessages) {
limits[i] = maxMessages;
}
}
}, 1000);
};
// Create a unique random ID per widget
var id = 'id-' + this._randomID();
// Create a new widget
var widget = widgetClass.create(name, src, id);
// Store the widget based on the identifier
widgets[id] = widget;
limits[id] = maxMessages;
// Open widget in frontend
// TODO:
// Instead of an "element" this should probably be a 'panel' object!
element.appendChild(
widget.element()
);
return id;
},
// Receive a call from an embedded iframe.
// The handling needs to be very careful,
// as this can easily become a security nightmare.
_receiveMsg : function (e) {
// Get event data
var d = e.data;
// If no data given - fail
// (probably check that it's an assoc array)
if (!d)
return;
// e.origin is probably set and okay - CHECK!
// Get origin ID
var id = d["originID"];
// If no origin ID given - fail
if (!id)
return;
// Get the widget
var widget = widgets[id];
// If the addressed widget does not exist - fail
if (!widget)
return;
// Check for message limits
if (limits[id]-- < 0) {
// Kill widget
KorAP.log(0, 'Suspicious action by widget', widget.src);
// TODO:
// Potentially kill the whole plugin!
this.closeWidget(widget);
return;
};
// Resize the iframe
if (d.action === 'resize') {
widget.resize(d);
}
// Log message from iframe
else if (d.action === 'log') {
KorAP.log(d.code, d.msg, widget.src);
};
// TODO:
// Close
},
// Close the widget
closeWidget : function (widget) {
delete limits[widget.id];
delete widgets[widget.id];
widget.shutdown();
// Remove listeners in case no widget
// is available any longer
if (Object.keys(limits).length == 0)
this._removeListener();
},
// Get a random identifier
_randomID : function () {
return randomID(20);
},
// Remove the listener
_removeListener : function () {
window.clearInterval(this._timer);
this._timer = undefined;
window.removeEventListener("message", this._listener);
this._listener = undefined;
},
// Destructor, just for testing scenarios
destroy : function () {
limits = {};
widgets = {};
this._removeListener();
}
}
});