blob: 6689529f05ff16f0642a6d570c2cd1b85d5bff87 [file] [log] [blame]
Akron479994e2018-07-02 13:21:44 +02001/**
2 * The plugin system is based
3 * on registered widgets (iframes) from
4 * foreign services.
5 * The server component spawns new iframes and
6 * listens to them.
7 *
8 * @author Nils Diewald
9 */
10
Akrona6c32b92018-07-02 18:39:42 +020011define(["plugin/widget", "util"], function (widgetClass) {
Akron479994e2018-07-02 13:21:44 +020012 "use strict";
13
Akrona6c32b92018-07-02 18:39:42 +020014 // Contains all widgets to address with
15 // messages to them
16 var widgets = {};
Akron7c6e05f2018-07-12 19:08:13 +020017 var plugins = {};
18 var buttons = {
19 match : []
20 };
Akrona6c32b92018-07-02 18:39:42 +020021
Akrone8e2c952018-07-04 13:43:12 +020022 // This is a counter to limit acceptable incoming messages
23 // to a certain amount. For every message, this counter will
24 // be decreased (down to 0), for every second this will be
25 // increased (up to 100).
26 // Once a widget surpasses the limit, it will be killed
27 // and called suspicious.
28 var maxMessages = 100;
29 var limits = {};
30
31 // TODO:
32 // It may be useful to establish a watcher that pings
33 // all widgets every second to see if it is still alive.
34
Akron479994e2018-07-02 13:21:44 +020035 return {
36
37 /**
38 * Create new plugin management system
39 */
40 create : function () {
41 return Object.create(this)._init();
42 },
43
44 /*
Akron76dd8d32018-07-06 09:30:22 +020045 * Initialize the plugin manager
Akron479994e2018-07-02 13:21:44 +020046 */
47 _init : function () {
Akron479994e2018-07-02 13:21:44 +020048 return this;
49 },
50
51 /**
Akron7c6e05f2018-07-12 19:08:13 +020052 * Register a plugin described as a JSON object.
53 *
54 * This is work in progress.
55 */
56 register : function (obj) {
57
58 /* Example:
59
60 KorAP.Plugin.register({
61 'name' : 'CatContent',
62 'desc' : 'Some content about cats',
63 'about' : 'https://localhost:5678/',
64 'embed' : [{
65 'buttonGroup' : 'match',
66 'title' : loc.TRANSLATE,
67 'classes' : ['translate']
68 'onClick' : {
69 'action' : 'addWidget',
70 'panel' : 'match',
71 'template' : 'https://localhost:5678/?match={matchid}',
72 }
73 }]
74 });
75 */
76
77 // TODO:
78 // These fields need to be localized by a structure like
79 // {
80 // de : {
81 // name : '..'
82 // }
83 // en : ...
84 // }
85 // for display
86 var name = obj["name"];
87
88 // Register plugin by name
89 var plugin = plugins[name] = {
90 name : name,
91 desc : obj["desc"],
92 about : obj["about"],
93 widgets : []
94 };
95
96 // Embed all embeddings of the plugin
97 for (var i in obj["embed"]) {
98 var embed = obj["embed"][i];
99 var addTo = embed["buttonGroup"];
100 var onClick = embed["onClick"];
101
102 // Needs to be localized as well
103 var title = embed["title"];
104
105 // The embedding will open a widget
106 if (onClick["action"] == "addWidget") {
107
108 var panel = onClick["panel"];
109 var that = this;
110 var cb = function (e) {
111
112 // Get the URL of the widget
113 var url = onClick["template"]; // that._interpolateURI(onClick["template"], this.match);
114
115 // Add the widget to the panel
116 var id = that.addWidget(document.getElementById(panel), name, url);
117 plugin["widgets"].push(id);
118 };
119
120 buttons["match"].push([title, embed["classes"], cb]);
121 };
122 };
123 },
124
125
126 // TODO:
127 // Interpolate URIs similar to https://tools.ietf.org/html/rfc6570
128 // but as simple as possible
129 _interpolateURI : function (uri, obj) {
130 // ...
131 },
132
133
134 /**
135 * Get named button group
136 */
137 buttonGroup : function (name) {
138 return buttons[name];
139 },
140
141 /**
Akrone8e2c952018-07-04 13:43:12 +0200142 * Open a new widget as a child to a certain element
Akron479994e2018-07-02 13:21:44 +0200143 */
Akron7991b192018-07-09 17:28:43 +0200144 addWidget : function (element, name, src) {
Akron479994e2018-07-02 13:21:44 +0200145
Akron76dd8d32018-07-06 09:30:22 +0200146 // Is it the first widget?
147 if (!this._listener) {
148
149 /*
150 * Establish the global 'message' hook.
151 */
152 this._listener = this._receiveMsg.bind(this);
153 window.addEventListener("message", this._listener);
154
155 // Every second increase the limits of all registered widgets
156 this._timer = window.setInterval(function () {
157 for (var i in limits) {
158 if (limits[i]++ >= maxMessages) {
159 limits[i] = maxMessages;
160 }
161 }
162 }, 1000);
163 };
164
Akrona6c32b92018-07-02 18:39:42 +0200165 // Create a unique random ID per widget
166 var id = 'id-' + this._randomID();
167
168 // Create a new widget
Akron7991b192018-07-09 17:28:43 +0200169 var widget = widgetClass.create(name, src, id);
Akrona6c32b92018-07-02 18:39:42 +0200170
171 // Store the widget based on the identifier
172 widgets[id] = widget;
Akrona99315e2018-07-03 22:56:45 +0200173 limits[id] = maxMessages;
Akrona6c32b92018-07-02 18:39:42 +0200174
175 // Open widget in frontend
Akron7c6e05f2018-07-12 19:08:13 +0200176 // TODO:
177 // Instead of an "element" this should probably be a 'panel' object!
Akrona6c32b92018-07-02 18:39:42 +0200178 element.appendChild(
179 widget.element()
180 );
Akronb43c8c62018-07-04 18:27:28 +0200181
182 return id;
Akron479994e2018-07-02 13:21:44 +0200183 },
184
Akrone8e2c952018-07-04 13:43:12 +0200185 // Receive a call from an embedded iframe.
186 // The handling needs to be very careful,
187 // as this can easily become a security nightmare.
Akron479994e2018-07-02 13:21:44 +0200188 _receiveMsg : function (e) {
189 // Get event data
190 var d = e.data;
191
Akrona99315e2018-07-03 22:56:45 +0200192 // If no data given - fail
193 // (probably check that it's an assoc array)
194 if (!d)
195 return;
196
197 // e.origin is probably set and okay - CHECK!
Akron479994e2018-07-02 13:21:44 +0200198
Akrona99315e2018-07-03 22:56:45 +0200199 // Get origin ID
200 var id = d["originID"];
201
202 // If no origin ID given - fail
203 if (!id)
204 return;
205
Akrona6c32b92018-07-02 18:39:42 +0200206 // Get the widget
Akrona99315e2018-07-03 22:56:45 +0200207 var widget = widgets[id];
Akrona6c32b92018-07-02 18:39:42 +0200208
209 // If the addressed widget does not exist - fail
210 if (!widget)
211 return;
212
Akrona99315e2018-07-03 22:56:45 +0200213 // Check for message limits
214 if (limits[id]-- < 0) {
Akrone8e2c952018-07-04 13:43:12 +0200215
216 // Kill widget
Akronc0a2da82018-07-04 15:27:37 +0200217 KorAP.log(0, 'Suspicious action by widget', widget.src);
Akron7c6e05f2018-07-12 19:08:13 +0200218
219 // TODO:
220 // Potentially kill the whole plugin!
Akron76dd8d32018-07-06 09:30:22 +0200221 this.closeWidget(widget);
Akrona99315e2018-07-03 22:56:45 +0200222 return;
223 };
Akrona6c32b92018-07-02 18:39:42 +0200224
Akron479994e2018-07-02 13:21:44 +0200225 // Resize the iframe
226 if (d.action === 'resize') {
Akrona6c32b92018-07-02 18:39:42 +0200227 widget.resize(d);
Akron479994e2018-07-02 13:21:44 +0200228 }
229
230 // Log message from iframe
231 else if (d.action === 'log') {
Akronc0a2da82018-07-04 15:27:37 +0200232 KorAP.log(d.code, d.msg, widget.src);
Akrona6c32b92018-07-02 18:39:42 +0200233 };
234
235 // TODO:
236 // Close
Akron479994e2018-07-02 13:21:44 +0200237 },
238
Akron76dd8d32018-07-06 09:30:22 +0200239 // Close the widget
240 closeWidget : function (widget) {
241 delete limits[widget.id];
242 delete widgets[widget.id];
243 widget.shutdown();
244
245 // Remove listeners in case no widget
246 // is available any longer
247 if (Object.keys(limits).length == 0)
248 this._removeListener();
249 },
250
Akrona6c32b92018-07-02 18:39:42 +0200251 // Get a random identifier
252 _randomID : function () {
253 return randomID(20);
Akronb43c8c62018-07-04 18:27:28 +0200254 },
255
Akron76dd8d32018-07-06 09:30:22 +0200256 // Remove the listener
257 _removeListener : function () {
258 window.clearInterval(this._timer);
259 this._timer = undefined;
260 window.removeEventListener("message", this._listener);
261 this._listener = undefined;
262 },
263
Akronb43c8c62018-07-04 18:27:28 +0200264 // Destructor, just for testing scenarios
265 destroy : function () {
266 limits = {};
267 widgets = {};
Akron76dd8d32018-07-06 09:30:22 +0200268 this._removeListener();
Akron479994e2018-07-02 13:21:44 +0200269 }
270 }
271});