blob: b0d8f52400162254e257b18c41936e9e3df7c63d [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 = {};
17
Akrone8e2c952018-07-04 13:43:12 +020018 // This is a counter to limit acceptable incoming messages
19 // to a certain amount. For every message, this counter will
20 // be decreased (down to 0), for every second this will be
21 // increased (up to 100).
22 // Once a widget surpasses the limit, it will be killed
23 // and called suspicious.
24 var maxMessages = 100;
25 var limits = {};
26
27 // TODO:
28 // It may be useful to establish a watcher that pings
29 // all widgets every second to see if it is still alive.
30
Akron479994e2018-07-02 13:21:44 +020031 return {
32
33 /**
34 * Create new plugin management system
35 */
36 create : function () {
37 return Object.create(this)._init();
38 },
39
40 /*
Akron76dd8d32018-07-06 09:30:22 +020041 * Initialize the plugin manager
Akron479994e2018-07-02 13:21:44 +020042 */
43 _init : function () {
Akron479994e2018-07-02 13:21:44 +020044 return this;
45 },
46
47 /**
Akrone8e2c952018-07-04 13:43:12 +020048 * Open a new widget as a child to a certain element
Akron479994e2018-07-02 13:21:44 +020049 */
50 addWidget : function (element, src) {
51
Akron76dd8d32018-07-06 09:30:22 +020052 // Is it the first widget?
53 if (!this._listener) {
54
55 /*
56 * Establish the global 'message' hook.
57 */
58 this._listener = this._receiveMsg.bind(this);
59 window.addEventListener("message", this._listener);
60
61 // Every second increase the limits of all registered widgets
62 this._timer = window.setInterval(function () {
63 for (var i in limits) {
64 if (limits[i]++ >= maxMessages) {
65 limits[i] = maxMessages;
66 }
67 }
68 }, 1000);
69 };
70
Akrona6c32b92018-07-02 18:39:42 +020071 // Create a unique random ID per widget
72 var id = 'id-' + this._randomID();
73
74 // Create a new widget
75 var widget = widgetClass.create(src, id);
76
77 // Store the widget based on the identifier
78 widgets[id] = widget;
Akrona99315e2018-07-03 22:56:45 +020079 limits[id] = maxMessages;
Akrona6c32b92018-07-02 18:39:42 +020080
81 // Open widget in frontend
82 element.appendChild(
83 widget.element()
84 );
Akronb43c8c62018-07-04 18:27:28 +020085
86 return id;
Akron479994e2018-07-02 13:21:44 +020087 },
88
Akrone8e2c952018-07-04 13:43:12 +020089 // Receive a call from an embedded iframe.
90 // The handling needs to be very careful,
91 // as this can easily become a security nightmare.
Akron479994e2018-07-02 13:21:44 +020092 _receiveMsg : function (e) {
93 // Get event data
94 var d = e.data;
95
Akrona99315e2018-07-03 22:56:45 +020096 // If no data given - fail
97 // (probably check that it's an assoc array)
98 if (!d)
99 return;
100
101 // e.origin is probably set and okay - CHECK!
Akron479994e2018-07-02 13:21:44 +0200102
Akrona99315e2018-07-03 22:56:45 +0200103 // Get origin ID
104 var id = d["originID"];
105
106 // If no origin ID given - fail
107 if (!id)
108 return;
109
Akrona6c32b92018-07-02 18:39:42 +0200110 // Get the widget
Akrona99315e2018-07-03 22:56:45 +0200111 var widget = widgets[id];
Akrona6c32b92018-07-02 18:39:42 +0200112
113 // If the addressed widget does not exist - fail
114 if (!widget)
115 return;
116
Akrona99315e2018-07-03 22:56:45 +0200117 // Check for message limits
118 if (limits[id]-- < 0) {
Akrone8e2c952018-07-04 13:43:12 +0200119
120 // Kill widget
Akronc0a2da82018-07-04 15:27:37 +0200121 KorAP.log(0, 'Suspicious action by widget', widget.src);
Akron76dd8d32018-07-06 09:30:22 +0200122 this.closeWidget(widget);
Akrona99315e2018-07-03 22:56:45 +0200123 return;
124 };
Akrona6c32b92018-07-02 18:39:42 +0200125
Akron479994e2018-07-02 13:21:44 +0200126 // Resize the iframe
127 if (d.action === 'resize') {
Akrona6c32b92018-07-02 18:39:42 +0200128 widget.resize(d);
Akron479994e2018-07-02 13:21:44 +0200129 }
130
131 // Log message from iframe
132 else if (d.action === 'log') {
Akronc0a2da82018-07-04 15:27:37 +0200133 KorAP.log(d.code, d.msg, widget.src);
Akrona6c32b92018-07-02 18:39:42 +0200134 };
135
136 // TODO:
137 // Close
Akron479994e2018-07-02 13:21:44 +0200138 },
139
Akron76dd8d32018-07-06 09:30:22 +0200140 // Close the widget
141 closeWidget : function (widget) {
142 delete limits[widget.id];
143 delete widgets[widget.id];
144 widget.shutdown();
145
146 // Remove listeners in case no widget
147 // is available any longer
148 if (Object.keys(limits).length == 0)
149 this._removeListener();
150 },
151
Akrona6c32b92018-07-02 18:39:42 +0200152 // Get a random identifier
153 _randomID : function () {
154 return randomID(20);
Akronb43c8c62018-07-04 18:27:28 +0200155 },
156
Akron76dd8d32018-07-06 09:30:22 +0200157 // Remove the listener
158 _removeListener : function () {
159 window.clearInterval(this._timer);
160 this._timer = undefined;
161 window.removeEventListener("message", this._listener);
162 this._listener = undefined;
163 },
164
Akronb43c8c62018-07-04 18:27:28 +0200165 // Destructor, just for testing scenarios
166 destroy : function () {
167 limits = {};
168 widgets = {};
Akron76dd8d32018-07-06 09:30:22 +0200169 this._removeListener();
Akron479994e2018-07-02 13:21:44 +0200170 }
171 }
172});