blob: 1fa0002b3b3601ffefe7f930ac321efc49cab799 [file] [log] [blame]
Akron479994e2018-07-02 13:21:44 +02001/**
2 * The plugin system is based
Akron22598cd2019-12-09 14:59:03 +01003 * on registered services (iframes) from
Akron479994e2018-07-02 13:21:44 +02004 * foreign services.
5 * The server component spawns new iframes and
6 * listens to them.
7 *
8 * @author Nils Diewald
9 */
Akrone51eaa32020-11-10 09:35:53 +010010"use strict";
Akron479994e2018-07-02 13:21:44 +020011
Akron26d57f22021-09-10 16:48:57 +020012define(['plugin/widget', 'plugin/service', 'state', 'pageInfo', 'util'], function (widgetClass, serviceClass, stateClass, pageInfoClass) {
Akron479994e2018-07-02 13:21:44 +020013
Akron2d0d96d2019-11-18 19:49:50 +010014 KorAP.Panel = KorAP.Panel || {};
15
Akron22598cd2019-12-09 14:59:03 +010016 // Contains all servicess to address with
Akrona6c32b92018-07-02 18:39:42 +020017 // messages to them
Akron22598cd2019-12-09 14:59:03 +010018 var services = {};
19 var plugins = {};
Akron10a47962018-07-12 21:17:10 +020020
21 // TODO:
22 // These should better be panels and every panel
23 // has a buttonGroup
Akron2d0d96d2019-11-18 19:49:50 +010024
25 // List of panels with dynamic buttons, i.e.
26 // panels that may occur multiple times.
Akron7c6e05f2018-07-12 19:08:13 +020027 var buttons = {
28 match : []
29 };
Akron2d0d96d2019-11-18 19:49:50 +010030
31 // List of panels with static buttons, i.e.
32 // panels that occur only once.
33 var buttonsSingle = {
hebasta043e96f2019-11-28 12:33:00 +010034 query : [],
35 result : []
Akron22598cd2019-12-09 14:59:03 +010036 }
Akron10a47962018-07-12 21:17:10 +020037
Akrone8e2c952018-07-04 13:43:12 +020038 // This is a counter to limit acceptable incoming messages
39 // to a certain amount. For every message, this counter will
40 // be decreased (down to 0), for every second this will be
41 // increased (up to 100).
42 // Once a widget surpasses the limit, it will be killed
43 // and called suspicious.
44 var maxMessages = 100;
45 var limits = {};
46
47 // TODO:
48 // It may be useful to establish a watcher that pings
Akrone1c27f62018-07-20 11:42:59 +020049 // all widgets every second to see if it is still alive,
50 // otherwise kill
Akrone8e2c952018-07-04 13:43:12 +020051
Akrone1c27f62018-07-20 11:42:59 +020052 // Load Plugin server
Akron479994e2018-07-02 13:21:44 +020053 return {
54
55 /**
56 * Create new plugin management system
57 */
58 create : function () {
59 return Object.create(this)._init();
60 },
61
62 /*
Akron76dd8d32018-07-06 09:30:22 +020063 * Initialize the plugin manager
Akron479994e2018-07-02 13:21:44 +020064 */
65 _init : function () {
Akron479994e2018-07-02 13:21:44 +020066 return this;
67 },
68
69 /**
Akron7c6e05f2018-07-12 19:08:13 +020070 * Register a plugin described as a JSON object.
71 *
72 * This is work in progress.
Akron10a47962018-07-12 21:17:10 +020073 *
74 * Example:
75 *
76 * KorAP.Plugin.register({
77 * 'name' : 'CatContent',
78 * 'desc' : 'Some content about cats',
79 * 'about' : 'https://localhost:5678/',
80 * 'embed' : [{
81 * 'panel' : 'match',
82 * 'title' : loc.TRANSLATE,
83 * 'classes' : ['translate']
84 * 'onClick' : {
85 * 'action' : 'addWidget',
86 * 'template' : 'https://localhost:5678/?match={matchid}',
87 * }
Akron22598cd2019-12-09 14:59:03 +010088 * },{
89 * 'title' : 'glemm',
90 * 'panel' : 'query',
91 * 'onClick' : {
92 * 'action' : 'toggle',
93 * 'state' : 'glemm',
94 * 'service' : {
95 * 'id' : 'glemm',
96 * 'template' : 'https://localhost:5678/'
97 * }
98 * }
Akron10a47962018-07-12 21:17:10 +020099 * }]
100 * });
101 *
Akron7c6e05f2018-07-12 19:08:13 +0200102 */
103 register : function (obj) {
Akron7c6e05f2018-07-12 19:08:13 +0200104 // TODO:
Akron10a47962018-07-12 21:17:10 +0200105 // These fields need to be localized for display by a structure like
106 // { de : { name : '..' }, en : { .. } }
Akron7c6e05f2018-07-12 19:08:13 +0200107 var name = obj["name"];
108
Akron10a47962018-07-12 21:17:10 +0200109 if (!name)
110 throw new Error("Missing name of plugin");
111
Akron3d013802020-10-07 15:03:38 +0200112 var desc = obj["desc"];
113
Akron7c6e05f2018-07-12 19:08:13 +0200114 // Register plugin by name
115 var plugin = plugins[name] = {
116 name : name,
Akron3d013802020-10-07 15:03:38 +0200117 desc : desc,
Akron7c6e05f2018-07-12 19:08:13 +0200118 about : obj["about"],
Akron22598cd2019-12-09 14:59:03 +0100119 widgets : [],
120 services : []
Akron7c6e05f2018-07-12 19:08:13 +0200121 };
Akron10a47962018-07-12 21:17:10 +0200122
123 if (typeof obj["embed"] !== 'object')
124 throw new Error("Embedding of plugin is no list");
Akron7c6e05f2018-07-12 19:08:13 +0200125
126 // Embed all embeddings of the plugin
Akrone1c27f62018-07-20 11:42:59 +0200127 var that = this;
Akron678c26f2020-10-09 08:52:50 +0200128 obj["embed"].forEach(function(embed) {
Akron10a47962018-07-12 21:17:10 +0200129
130 if (typeof embed !== 'object')
131 throw new Error("Embedding of plugin is no object");
132
Akron7c6e05f2018-07-12 19:08:13 +0200133 // Needs to be localized as well
Akron22598cd2019-12-09 14:59:03 +0100134 let title = embed["title"];
135 let panel = embed["panel"];
136 let onClick = embed["onClick"];
hebasta40a85cf2020-07-15 18:10:08 +0200137 let icon = embed["icon"];
138
Akron22598cd2019-12-09 14:59:03 +0100139 if (!panel || !(buttons[panel] || buttonsSingle[panel]))
Akronba09ed22020-10-01 16:01:45 +0200140 throw new Error("Panel for plugin is invalid");
Akron7c6e05f2018-07-12 19:08:13 +0200141
142 // The embedding will open a widget
Akronba09ed22020-10-01 16:01:45 +0200143 if (!onClick["action"] ||
144 onClick["action"] == "addWidget" ||
145 onClick["action"] == "setWidget") {
Akron22598cd2019-12-09 14:59:03 +0100146
147 let cb = function (e) {
Akron7c6e05f2018-07-12 19:08:13 +0200148
Akrone1c27f62018-07-20 11:42:59 +0200149 // "this" is bind to the panel
Akronba09ed22020-10-01 16:01:45 +0200150 // "this".button is the button
151 // "that" is the server object
Akrone1c27f62018-07-20 11:42:59 +0200152
Akronba09ed22020-10-01 16:01:45 +0200153 // The button has a state and the state is associated to the
154 // a intermediate object to toggle the view
155 if ('state' in this.button && this.button.state.associates() > 0) {
156
Akronba09ed22020-10-01 16:01:45 +0200157 let s = this.button.state;
Akronfcf89db2020-10-01 17:40:20 +0200158
159 // The associated service is existent
160 if (services[this.button['widgetID']]) {
Akron237abc42020-10-07 14:14:52 +0200161 s.roll();
Akronfcf89db2020-10-01 17:40:20 +0200162 return;
163 }
164
165 // The service is not existent
166 else {
167
168 // Remove broken state associations
169 s.clear();
Akronba09ed22020-10-01 16:01:45 +0200170 s.set(true);
Akronfcf89db2020-10-01 17:40:20 +0200171 }
Akronba09ed22020-10-01 16:01:45 +0200172 };
173
Akron7c6e05f2018-07-12 19:08:13 +0200174 // Add the widget to the panel
Akronbb891982020-10-05 16:07:18 +0200175 let id = that.addWidget(this, {
176 "name": name,
177 "src": onClick["template"], // that._interpolateURI(onClick["template"], this.match);
Akron3d013802020-10-07 15:03:38 +0200178 "permissions": onClick["permissions"],
179 "desc":desc
Akronbb891982020-10-05 16:07:18 +0200180 });
Akron7c6e05f2018-07-12 19:08:13 +0200181 plugin["widgets"].push(id);
Akronba09ed22020-10-01 16:01:45 +0200182
183 // If a state exists, associate with a mediator object
184 if ('state' in this.button) {
Akronfcf89db2020-10-01 17:40:20 +0200185 this.button['widgetID'] = id;
Akronba09ed22020-10-01 16:01:45 +0200186 this.button.state.associate({
187 setState : function (value) {
188 // Minimize the widget
189 if (value == false) {
190 services[id].minimize();
191 }
192 else {
193 services[id].show();
194 };
195 }
196 });
197 }
Akron7c6e05f2018-07-12 19:08:13 +0200198 };
199
Akron22598cd2019-12-09 14:59:03 +0100200
Akronba09ed22020-10-01 16:01:45 +0200201 // Button object
202 let obj = {'cls':embed["classes"], 'icon': icon }
203
204 if (onClick["action"] && onClick["action"] == "setWidget") {
205
206 // Create a boolean state value, that initializes to true == opened
Akron237abc42020-10-07 14:14:52 +0200207 obj['state'] = stateClass.create([true, false]);
Akronba09ed22020-10-01 16:01:45 +0200208 };
209
Akron2d0d96d2019-11-18 19:49:50 +0100210 // Add to dynamic button list (e.g. for matches)
211 if (buttons[panel]) {
Akronba09ed22020-10-01 16:01:45 +0200212 buttons[panel].push([title, obj, cb]);
Akron2d0d96d2019-11-18 19:49:50 +0100213 }
214
215 // Add to static button list (e.g. for query) already loaded
216 else if (KorAP.Panel[panel]) {
Akron37ea1192021-07-28 10:40:14 +0200217 KorAP.Panel[panel].actions().add(title, obj, cb);
Akron2d0d96d2019-11-18 19:49:50 +0100218 }
219
220 // Add to static button list (e.g. for query) not yet loaded
221 else {
Akronba09ed22020-10-01 16:01:45 +0200222 buttonsSingle[panel].push([title, obj, cb]);
Akron2d0d96d2019-11-18 19:49:50 +0100223 }
Akron22598cd2019-12-09 14:59:03 +0100224 }
Akronba09ed22020-10-01 16:01:45 +0200225
Akronec4bbfa2021-09-15 15:00:59 +0200226 // TODO There is no possibility to add icons to a plugin toggle button right now.
Akron22598cd2019-12-09 14:59:03 +0100227 else if (onClick["action"] == "toggle") {
228
Akron237abc42020-10-07 14:14:52 +0200229 // TODO:
230 // Accept a "value" list here for toggling, which should
231 // also allow for "rolling" through states via CSS classes
232 // as 'toggle-true', 'toggle-false' etc.
233
234 let state = stateClass.create([true, false]);
Akron22598cd2019-12-09 14:59:03 +0100235
Akrona70b6892021-11-04 14:23:24 +0100236 if (onClick["default"] !== undefined) {
237 state.setIfNotYet(onClick["default"]);
238 };
239
Akron22598cd2019-12-09 14:59:03 +0100240 // TODO:
241 // Lazy registration (see above!)
Akron37ea1192021-07-28 10:40:14 +0200242 KorAP.Panel[panel].actions().addToggle(title, {'cls':["title"]}, state);
Akron22598cd2019-12-09 14:59:03 +0100243
Akron22598cd2019-12-09 14:59:03 +0100244 // Add the service
Akronbb891982020-10-05 16:07:18 +0200245 let id = this.addService({
246 "name" : name,
247 // TODO:
248 // Use the "service" keyword
249 "src" : onClick["template"],
250 "permissions" : onClick["permissions"]
251 });
Akronfb11a962020-10-05 12:12:55 +0200252
Akron22598cd2019-12-09 14:59:03 +0100253 // TODO:
254 // This is a bit stupid to get the service window
Akronc3003642020-03-30 10:19:14 +0200255 let service = services[id];
256 let iframe = service.load();
Akron22598cd2019-12-09 14:59:03 +0100257
258 // Create object to communicate the toggle state
259 // once the iframe is loaded.
260 iframe.onload = function () {
261 let sendToggle = {
262 setState : function (val) {
Akronc3003642020-03-30 10:19:14 +0200263 service.sendMsg({
Akron22598cd2019-12-09 14:59:03 +0100264 action: 'state',
265 key : onClick['state'],
266 value : val
Akronc3003642020-03-30 10:19:14 +0200267 });
Akron22598cd2019-12-09 14:59:03 +0100268 }
269 };
270
271 // Associate object with the state
272 state.associate(sendToggle);
273 };
274
275 plugin["services"].push(id);
Akron7c6e05f2018-07-12 19:08:13 +0200276 };
Akron678c26f2020-10-09 08:52:50 +0200277 }, this);
Akron7c6e05f2018-07-12 19:08:13 +0200278 },
279
Akron7c6e05f2018-07-12 19:08:13 +0200280 // TODO:
281 // Interpolate URIs similar to https://tools.ietf.org/html/rfc6570
282 // but as simple as possible
283 _interpolateURI : function (uri, obj) {
284 // ...
285 },
286
287
288 /**
Akron4a703872018-07-26 10:59:41 +0200289 * Get named button group - better rename to "action"
Akron7c6e05f2018-07-12 19:08:13 +0200290 */
291 buttonGroup : function (name) {
Akron2d0d96d2019-11-18 19:49:50 +0100292 if (buttons[name] != undefined) {
293 return buttons[name];
294 } else if (buttonsSingle[name] != undefined) {
295 return buttonsSingle[name];
296 };
297 return [];
298 },
299
300 /**
301 * Clear named button group - better rename to "action"
302 */
303 clearButtonGroup : function (name) {
304 if (buttons[name] != undefined) {
305 buttons[name] = [];
306 } else if (buttonsSingle[name] != undefined) {
307 buttonsSingle[name] = [];
308 }
Akron7c6e05f2018-07-12 19:08:13 +0200309 },
Akron479994e2018-07-02 13:21:44 +0200310
Akron22598cd2019-12-09 14:59:03 +0100311 // Optionally initialize the service mechanism and get an ID
312 _getServiceID : function () {
Akron4a703872018-07-26 10:59:41 +0200313
Akron22598cd2019-12-09 14:59:03 +0100314 // Is it the first service?
Akron76dd8d32018-07-06 09:30:22 +0200315 if (!this._listener) {
316
317 /*
318 * Establish the global 'message' hook.
319 */
320 this._listener = this._receiveMsg.bind(this);
321 window.addEventListener("message", this._listener);
322
Akron22598cd2019-12-09 14:59:03 +0100323 // Every second increase the limits of all registered services
Akron76dd8d32018-07-06 09:30:22 +0200324 this._timer = window.setInterval(function () {
Akron678c26f2020-10-09 08:52:50 +0200325 for (let i = 0; i < limits.length; i++) {
Akron76dd8d32018-07-06 09:30:22 +0200326 if (limits[i]++ >= maxMessages) {
327 limits[i] = maxMessages;
328 }
329 }
330 }, 1000);
331 };
332
Akron22598cd2019-12-09 14:59:03 +0100333 // Create a unique random ID per service
334 return 'id-' + this._randomID();
335 },
336
337 /**
338 * Add a service in a certain panel and return the id.
339 */
Akronbb891982020-10-05 16:07:18 +0200340 addService : function (data) {
341 if (!data["src"])
Akron22598cd2019-12-09 14:59:03 +0100342 return;
343
344 let id = this._getServiceID();
345
Akronbb891982020-10-05 16:07:18 +0200346 data["id"] = id;
347
Akron22598cd2019-12-09 14:59:03 +0100348 // Create a new service
Akronbb891982020-10-05 16:07:18 +0200349 let service = serviceClass.create(data);
Akron22598cd2019-12-09 14:59:03 +0100350
Akron22598cd2019-12-09 14:59:03 +0100351 services[id] = service;
352 limits[id] = maxMessages;
353
Akron22598cd2019-12-09 14:59:03 +0100354 // Add service to panel
355 this.element().appendChild(
356 service.load()
357 );
358
359 return id;
360 },
361
362
363 /**
364 * Open a new widget view in a certain panel and return
365 * the id.
366 */
Akronbb891982020-10-05 16:07:18 +0200367 addWidget : function (panel, data) {
368 // panel, name, src, permissions
Akron22598cd2019-12-09 14:59:03 +0100369
370 let id = this._getServiceID();
Akrona6c32b92018-07-02 18:39:42 +0200371
Akronbb891982020-10-05 16:07:18 +0200372 data["id"] = id;
373
Akrona6c32b92018-07-02 18:39:42 +0200374 // Create a new widget
Akronbb891982020-10-05 16:07:18 +0200375 var widget = widgetClass.create(data);
Akrona6c32b92018-07-02 18:39:42 +0200376
377 // Store the widget based on the identifier
Akron22598cd2019-12-09 14:59:03 +0100378 services[id] = widget;
Akrona99315e2018-07-03 22:56:45 +0200379 limits[id] = maxMessages;
Akrona6c32b92018-07-02 18:39:42 +0200380
Akron4a703872018-07-26 10:59:41 +0200381 widget._mgr = this;
382
Akrone1c27f62018-07-20 11:42:59 +0200383 // Add widget to panel
384 panel.add(widget);
Akronb43c8c62018-07-04 18:27:28 +0200385
386 return id;
Akron479994e2018-07-02 13:21:44 +0200387 },
388
Akron4a703872018-07-26 10:59:41 +0200389
390 /**
Akron22598cd2019-12-09 14:59:03 +0100391 * Get service by identifier
Akron4a703872018-07-26 10:59:41 +0200392 */
Akron22598cd2019-12-09 14:59:03 +0100393 service : function (id) {
394 return services[id];
Akron4a703872018-07-26 10:59:41 +0200395 },
396
397
Akron22598cd2019-12-09 14:59:03 +0100398 // Receive a call from an embedded service.
Akrone8e2c952018-07-04 13:43:12 +0200399 // The handling needs to be very careful,
400 // as this can easily become a security nightmare.
Akron479994e2018-07-02 13:21:44 +0200401 _receiveMsg : function (e) {
Akronbc94a9c2021-04-15 00:07:35 +0200402
Akron479994e2018-07-02 13:21:44 +0200403 // Get event data
404 var d = e.data;
405
Akrona99315e2018-07-03 22:56:45 +0200406 // If no data given - fail
407 // (probably check that it's an assoc array)
408 if (!d)
409 return;
410
411 // e.origin is probably set and okay - CHECK!
Akronbc94a9c2021-04-15 00:07:35 +0200412 // TODO: Check e.origin is in the list of registered participants
413 // if (e.origin !== "http://example.com:8080")
414 // return;
Akron479994e2018-07-02 13:21:44 +0200415
Akronbc94a9c2021-04-15 00:07:35 +0200416
Akrona99315e2018-07-03 22:56:45 +0200417 // Get origin ID
418 var id = d["originID"];
419
420 // If no origin ID given - fail
421 if (!id)
422 return;
423
Akron22598cd2019-12-09 14:59:03 +0100424 // Get the service
425 let service = services[id];
Akrona6c32b92018-07-02 18:39:42 +0200426
Akron22598cd2019-12-09 14:59:03 +0100427 // If the addressed service does not exist - fail
428 if (!service)
Akrona6c32b92018-07-02 18:39:42 +0200429 return;
430
Akrona99315e2018-07-03 22:56:45 +0200431 // Check for message limits
432 if (limits[id]-- < 0) {
Akrone8e2c952018-07-04 13:43:12 +0200433
Akron22598cd2019-12-09 14:59:03 +0100434 // Kill service
435 KorAP.log(0, 'Suspicious action by service', service.src);
Akron7c6e05f2018-07-12 19:08:13 +0200436
437 // TODO:
438 // Potentially kill the whole plugin!
Akron4a703872018-07-26 10:59:41 +0200439
Akron22598cd2019-12-09 14:59:03 +0100440 // This removes all connections before closing the service
441 this._closeService(service.id);
442
443 // if (service.isWidget)
444 service.close();
445
Akrona99315e2018-07-03 22:56:45 +0200446 return;
447 };
Akrona6c32b92018-07-02 18:39:42 +0200448
Akron479994e2018-07-02 13:21:44 +0200449 // Resize the iframe
Akron22598cd2019-12-09 14:59:03 +0100450 switch (d.action) {
451 case 'resize':
452 if (service.isWidget)
453 service.resize(d);
454 break;
Akron479994e2018-07-02 13:21:44 +0200455
456 // Log message from iframe
Akron22598cd2019-12-09 14:59:03 +0100457 case 'log':
458 KorAP.log(d.code, d.msg, service.src);
459 break;
Akron51ee6232019-12-17 21:00:05 +0100460
461 // Modify pipes
462 case 'pipe':
463 if (KorAP.Pipe != undefined) {
464 if (d.job == 'del') {
465 KorAP.Pipe.remove(d.service);
466 } else {
467 KorAP.Pipe.append(d.service);
468 };
469 };
470 break;
Akronc3003642020-03-30 10:19:14 +0200471
472 // Get information from the embedding platform
473 case 'get':
Akron45308ce2020-08-28 14:10:23 +0200474
475 // Get KoralQuery
Akronc3003642020-03-30 10:19:14 +0200476 if (d.key == 'KQ') {
477 if (KorAP.koralQuery !== undefined) {
478 d["value"] = KorAP.koralQuery;
479 };
Akron45308ce2020-08-28 14:10:23 +0200480 }
481
Akron26d57f22021-09-10 16:48:57 +0200482 // Get Query information from form
Akron45308ce2020-08-28 14:10:23 +0200483 else if (d.key == 'QueryForm') {
484 let doc = document;
485 let v = d["value"] = {};
486
487 var el;
488 if (el = doc.getElementById('q-field')) {
489 v["q"] = el.value;
490 };
491 if (el = doc.getElementById('ql-field')) {
492 v["ql"] = el.value;
493 };
494 if (el = KorAP.vc) {
495 v["cq"] = el.toQuery();
496 };
Akron432972b2020-09-18 17:05:53 +0200497 }
498
499 // Get Query information from parameters
500 else if (d.key == 'QueryParam') {
501
502 // Supported in all modern browsers
503 var p = new URLSearchParams(window.location.search);
504 let v = d["value"] = {};
Akron4de759f2021-10-13 10:46:45 +0200505 v["search"] = window.location.search; // readonly
Akron432972b2020-09-18 17:05:53 +0200506 v["q"] = p.get('q');
507 v["ql"] = p.get('ql');
508 v["cq"] = p.get('cq');
Akron26d57f22021-09-10 16:48:57 +0200509 }
510
511 // Get pagination information
512 else if (d.key == 'Pagination') {
513 const pi = pageInfoClass.create();
514 let v = d["value"] = {};
515 v["page"] = pi.page();
516 v["total"] = pi.total();
517 v["count"] = pi.count();
Akronec4bbfa2021-09-15 15:00:59 +0200518 };
Akronc3003642020-03-30 10:19:14 +0200519
Akronec4bbfa2021-09-15 15:00:59 +0200520 // data needs to be mirrored
521 if (d._id) {
522 service.sendMsg(d);
523 };
524
525 break;
526
527 // Redirect to a different page relative to the current
528 case 'redirect':
529 const url = new URL(window.location);
530
531 // Currently this only accepts search parameters
532 if (d["queryParam"]) {
533 url.search = new URLSearchParams(d["queryParam"]);
534 };
535
536 window.location = url.toString();
537 break;
Akrona6c32b92018-07-02 18:39:42 +0200538 };
539
540 // TODO:
541 // Close
Akron479994e2018-07-02 13:21:44 +0200542 },
543
Akron22598cd2019-12-09 14:59:03 +0100544 // Close the service
545 _closeService : function (id) {
Akron4a703872018-07-26 10:59:41 +0200546 delete limits[id];
Akronfcf89db2020-10-01 17:40:20 +0200547
Akron22598cd2019-12-09 14:59:03 +0100548 // Close the iframe
549 if (services[id] && services[id]._closeIframe) {
550 services[id]._closeIframe();
551
552 // Remove from list
553 delete services[id];
554 };
555
Akron76dd8d32018-07-06 09:30:22 +0200556
557 // Remove listeners in case no widget
558 // is available any longer
559 if (Object.keys(limits).length == 0)
560 this._removeListener();
561 },
562
Akrona6c32b92018-07-02 18:39:42 +0200563 // Get a random identifier
564 _randomID : function () {
565 return randomID(20);
Akronb43c8c62018-07-04 18:27:28 +0200566 },
567
Akron76dd8d32018-07-06 09:30:22 +0200568 // Remove the listener
569 _removeListener : function () {
570 window.clearInterval(this._timer);
571 this._timer = undefined;
572 window.removeEventListener("message", this._listener);
573 this._listener = undefined;
574 },
575
Akron22598cd2019-12-09 14:59:03 +0100576 /**
577 * Return the service element.
578 */
579 element : function () {
Akron24aa0052020-11-10 11:00:34 +0100580 if (!this._el) {
581 this._el = document.createElement('div');
582 this._el.setAttribute("id", "services");
Akron22598cd2019-12-09 14:59:03 +0100583 }
Akron24aa0052020-11-10 11:00:34 +0100584 return this._el;
Akron22598cd2019-12-09 14:59:03 +0100585 },
586
Akronb43c8c62018-07-04 18:27:28 +0200587 // Destructor, just for testing scenarios
588 destroy : function () {
589 limits = {};
Akron678c26f2020-10-09 08:52:50 +0200590 Object.keys(services).forEach(
591 s => services[s].close()
592 );
Akron22598cd2019-12-09 14:59:03 +0100593 services = {};
Akron678c26f2020-10-09 08:52:50 +0200594 Object.keys(buttons).forEach(
595 b => buttons[b] = []
596 );
597 Object.keys(buttonsSingle).forEach(
598 b => buttonsSingle[b] = []
599 );
Akron22598cd2019-12-09 14:59:03 +0100600
Akron24aa0052020-11-10 11:00:34 +0100601 if (this._el) {
602 let e = this._el;
Akron22598cd2019-12-09 14:59:03 +0100603 if (e.parentNode) {
604 e.parentNode.removeChild(e);
605 };
Akron24aa0052020-11-10 11:00:34 +0100606 this._el = null;
Akron22598cd2019-12-09 14:59:03 +0100607 };
608
Akron76dd8d32018-07-06 09:30:22 +0200609 this._removeListener();
Akron479994e2018-07-02 13:21:44 +0200610 }
Akrone1c27f62018-07-20 11:42:59 +0200611 };
Akron479994e2018-07-02 13:21:44 +0200612});