| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 1 | /** | 
|  | 2 | * The plugin system is based | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 3 | * on registered services (iframes) from | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 4 | * foreign services. | 
|  | 5 | * The server component spawns new iframes and | 
|  | 6 | * listens to them. | 
|  | 7 | * | 
|  | 8 | * @author Nils Diewald | 
|  | 9 | */ | 
| Akron | e51eaa3 | 2020-11-10 09:35:53 +0100 | [diff] [blame] | 10 | "use strict"; | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 11 |  | 
| Akron | 26d57f2 | 2021-09-10 16:48:57 +0200 | [diff] [blame] | 12 | define(['plugin/widget', 'plugin/service', 'state', 'pageInfo', 'util'], function (widgetClass, serviceClass, stateClass, pageInfoClass) { | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 13 |  | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 14 | KorAP.Panel = KorAP.Panel || {}; | 
|  | 15 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 16 | // Contains all servicess to address with | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 17 | // messages to them | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 18 | var services = {}; | 
|  | 19 | var plugins  = {}; | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 20 |  | 
|  | 21 | // TODO: | 
|  | 22 | //   These should better be panels and every panel | 
|  | 23 | //   has a buttonGroup | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 24 |  | 
|  | 25 | // List of panels with dynamic buttons, i.e. | 
|  | 26 | // panels that may occur multiple times. | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 27 | var buttons = { | 
|  | 28 | match : [] | 
|  | 29 | }; | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 30 |  | 
|  | 31 | // List of panels with static buttons, i.e. | 
|  | 32 | // panels that occur only once. | 
|  | 33 | var buttonsSingle = { | 
| hebasta | 043e96f | 2019-11-28 12:33:00 +0100 | [diff] [blame] | 34 | query : [], | 
|  | 35 | result : [] | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 36 | } | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 37 |  | 
| Akron | e8e2c95 | 2018-07-04 13:43:12 +0200 | [diff] [blame] | 38 | // 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 | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 49 | //   all widgets every second to see if it is still alive, | 
|  | 50 | //   otherwise kill | 
| Akron | e8e2c95 | 2018-07-04 13:43:12 +0200 | [diff] [blame] | 51 |  | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 52 | // Load Plugin server | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 53 | return { | 
|  | 54 |  | 
|  | 55 | /** | 
|  | 56 | * Create new plugin management system | 
|  | 57 | */ | 
|  | 58 | create : function () { | 
|  | 59 | return Object.create(this)._init(); | 
|  | 60 | }, | 
|  | 61 |  | 
|  | 62 | /* | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 63 | * Initialize the plugin manager | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 64 | */ | 
|  | 65 | _init : function () { | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 66 | return this; | 
|  | 67 | }, | 
|  | 68 |  | 
|  | 69 | /** | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 70 | * Register a plugin described as a JSON object. | 
|  | 71 | * | 
|  | 72 | * This is work in progress. | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 73 | * | 
|  | 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 | *       } | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 88 | *     },{ | 
|  | 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 | *       } | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 99 | *     }] | 
|  | 100 | *   }); | 
|  | 101 | * | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 102 | */ | 
|  | 103 | register : function (obj) { | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 104 | // TODO: | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 105 | //   These fields need to be localized for display by a structure like | 
|  | 106 | //   { de : { name : '..' }, en : { .. } } | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 107 | var name = obj["name"]; | 
|  | 108 |  | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 109 | if (!name) | 
|  | 110 | throw new Error("Missing name of plugin"); | 
|  | 111 |  | 
| Akron | 3d01380 | 2020-10-07 15:03:38 +0200 | [diff] [blame] | 112 | var desc = obj["desc"]; | 
|  | 113 |  | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 114 | // Register plugin by name | 
|  | 115 | var plugin = plugins[name] = { | 
|  | 116 | name : name, | 
| Akron | 3d01380 | 2020-10-07 15:03:38 +0200 | [diff] [blame] | 117 | desc : desc, | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 118 | about : obj["about"], | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 119 | widgets : [], | 
|  | 120 | services : [] | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 121 | }; | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 122 |  | 
|  | 123 | if (typeof obj["embed"] !== 'object') | 
|  | 124 | throw new Error("Embedding of plugin is no list"); | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 125 |  | 
|  | 126 | // Embed all embeddings of the plugin | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 127 | var that = this; | 
| Akron | 678c26f | 2020-10-09 08:52:50 +0200 | [diff] [blame] | 128 | obj["embed"].forEach(function(embed) { | 
| Akron | 10a4796 | 2018-07-12 21:17:10 +0200 | [diff] [blame] | 129 |  | 
|  | 130 | if (typeof embed !== 'object') | 
|  | 131 | throw new Error("Embedding of plugin is no object"); | 
|  | 132 |  | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 133 | // Needs to be localized as well | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 134 | let title = embed["title"]; | 
|  | 135 | let panel = embed["panel"]; | 
|  | 136 | let onClick = embed["onClick"]; | 
| hebasta | 40a85cf | 2020-07-15 18:10:08 +0200 | [diff] [blame] | 137 | let icon = embed["icon"]; | 
|  | 138 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 139 | if (!panel || !(buttons[panel] || buttonsSingle[panel])) | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 140 | throw new Error("Panel for plugin is invalid"); | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 141 |  | 
|  | 142 | // The embedding will open a widget | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 143 | if (!onClick["action"] || | 
|  | 144 | onClick["action"] == "addWidget" || | 
|  | 145 | onClick["action"] == "setWidget") { | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 146 |  | 
|  | 147 | let cb = function (e) { | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 148 |  | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 149 | // "this" is bind to the panel | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 150 | // "this".button is the button | 
|  | 151 | // "that" is the server object | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 152 |  | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 153 | // 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 |  | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 157 | let s = this.button.state; | 
| Akron | fcf89db | 2020-10-01 17:40:20 +0200 | [diff] [blame] | 158 |  | 
|  | 159 | // The associated service is existent | 
|  | 160 | if (services[this.button['widgetID']]) { | 
| Akron | 237abc4 | 2020-10-07 14:14:52 +0200 | [diff] [blame] | 161 | s.roll(); | 
| Akron | fcf89db | 2020-10-01 17:40:20 +0200 | [diff] [blame] | 162 | return; | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | // The service is not existent | 
|  | 166 | else { | 
|  | 167 |  | 
|  | 168 | // Remove broken state associations | 
|  | 169 | s.clear(); | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 170 | s.set(true); | 
| Akron | fcf89db | 2020-10-01 17:40:20 +0200 | [diff] [blame] | 171 | } | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 172 | }; | 
|  | 173 |  | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 174 | // Add the widget to the panel | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 175 | let id = that.addWidget(this, { | 
|  | 176 | "name": name, | 
|  | 177 | "src": onClick["template"], // that._interpolateURI(onClick["template"], this.match); | 
| Akron | 3d01380 | 2020-10-07 15:03:38 +0200 | [diff] [blame] | 178 | "permissions": onClick["permissions"], | 
|  | 179 | "desc":desc | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 180 | }); | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 181 | plugin["widgets"].push(id); | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 182 |  | 
|  | 183 | // If a state exists, associate with a mediator object | 
|  | 184 | if ('state' in this.button) { | 
| Akron | fcf89db | 2020-10-01 17:40:20 +0200 | [diff] [blame] | 185 | this.button['widgetID'] = id; | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 186 | 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 | } | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 198 | }; | 
|  | 199 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 200 |  | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 201 | // 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 | 
| Akron | 237abc4 | 2020-10-07 14:14:52 +0200 | [diff] [blame] | 207 | obj['state'] = stateClass.create([true, false]); | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 208 | }; | 
|  | 209 |  | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 210 | // Add to dynamic button list (e.g. for matches) | 
|  | 211 | if (buttons[panel]) { | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 212 | buttons[panel].push([title, obj, cb]); | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 213 | } | 
|  | 214 |  | 
|  | 215 | // Add to static button list (e.g. for query) already loaded | 
|  | 216 | else if (KorAP.Panel[panel]) { | 
| Akron | 37ea119 | 2021-07-28 10:40:14 +0200 | [diff] [blame] | 217 | KorAP.Panel[panel].actions().add(title, obj, cb); | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 218 | } | 
|  | 219 |  | 
|  | 220 | // Add to static button list (e.g. for query) not yet loaded | 
|  | 221 | else { | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 222 | buttonsSingle[panel].push([title, obj, cb]); | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 223 | } | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 224 | } | 
| Akron | ba09ed2 | 2020-10-01 16:01:45 +0200 | [diff] [blame] | 225 |  | 
| Akron | ec4bbfa | 2021-09-15 15:00:59 +0200 | [diff] [blame] | 226 | // TODO There is no possibility to add icons to a plugin toggle button right now. | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 227 | else if (onClick["action"] == "toggle") { | 
|  | 228 |  | 
| Akron | 237abc4 | 2020-10-07 14:14:52 +0200 | [diff] [blame] | 229 | // 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]); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 235 |  | 
| Akron | a70b689 | 2021-11-04 14:23:24 +0100 | [diff] [blame] | 236 | if (onClick["default"] !== undefined) { | 
|  | 237 | state.setIfNotYet(onClick["default"]); | 
|  | 238 | }; | 
|  | 239 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 240 | // TODO: | 
|  | 241 | //   Lazy registration (see above!) | 
| Akron | 37ea119 | 2021-07-28 10:40:14 +0200 | [diff] [blame] | 242 | KorAP.Panel[panel].actions().addToggle(title, {'cls':["title"]}, state); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 243 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 244 | // Add the service | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 245 | let id = this.addService({ | 
|  | 246 | "name" : name, | 
|  | 247 | // TODO: | 
|  | 248 | //   Use the "service" keyword | 
|  | 249 | "src" : onClick["template"], | 
|  | 250 | "permissions" : onClick["permissions"] | 
|  | 251 | }); | 
| Akron | fb11a96 | 2020-10-05 12:12:55 +0200 | [diff] [blame] | 252 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 253 | // TODO: | 
|  | 254 | //   This is a bit stupid to get the service window | 
| Akron | c300364 | 2020-03-30 10:19:14 +0200 | [diff] [blame] | 255 | let service = services[id]; | 
|  | 256 | let iframe = service.load(); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 257 |  | 
|  | 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) { | 
| Akron | c300364 | 2020-03-30 10:19:14 +0200 | [diff] [blame] | 263 | service.sendMsg({ | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 264 | action: 'state', | 
|  | 265 | key : onClick['state'], | 
|  | 266 | value : val | 
| Akron | c300364 | 2020-03-30 10:19:14 +0200 | [diff] [blame] | 267 | }); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 268 | } | 
|  | 269 | }; | 
|  | 270 |  | 
|  | 271 | // Associate object with the state | 
|  | 272 | state.associate(sendToggle); | 
|  | 273 | }; | 
|  | 274 |  | 
|  | 275 | plugin["services"].push(id); | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 276 | }; | 
| Akron | 678c26f | 2020-10-09 08:52:50 +0200 | [diff] [blame] | 277 | }, this); | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 278 | }, | 
|  | 279 |  | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 280 | // 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 | /** | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 289 | * Get named button group - better rename to "action" | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 290 | */ | 
|  | 291 | buttonGroup : function (name) { | 
| Akron | 2d0d96d | 2019-11-18 19:49:50 +0100 | [diff] [blame] | 292 | 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 | } | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 309 | }, | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 310 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 311 | // Optionally initialize the service mechanism and get an ID | 
|  | 312 | _getServiceID : function () { | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 313 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 314 | // Is it the first service? | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 315 | 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 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 323 | // Every second increase the limits of all registered services | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 324 | this._timer = window.setInterval(function () { | 
| Akron | 678c26f | 2020-10-09 08:52:50 +0200 | [diff] [blame] | 325 | for (let i = 0; i < limits.length; i++) { | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 326 | if (limits[i]++ >= maxMessages) { | 
|  | 327 | limits[i] = maxMessages; | 
|  | 328 | } | 
|  | 329 | } | 
|  | 330 | }, 1000); | 
|  | 331 | }; | 
|  | 332 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 333 | // 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 | */ | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 340 | addService : function (data) { | 
|  | 341 | if (!data["src"]) | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 342 | return; | 
|  | 343 |  | 
|  | 344 | let id = this._getServiceID(); | 
|  | 345 |  | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 346 | data["id"] = id; | 
|  | 347 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 348 | // Create a new service | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 349 | let service = serviceClass.create(data); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 350 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 351 | services[id] = service; | 
|  | 352 | limits[id] = maxMessages; | 
|  | 353 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 354 | // 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 | */ | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 367 | addWidget : function (panel, data) { | 
|  | 368 | // panel, name, src, permissions | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 369 |  | 
|  | 370 | let id = this._getServiceID(); | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 371 |  | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 372 | data["id"] = id; | 
|  | 373 |  | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 374 | // Create a new widget | 
| Akron | bb89198 | 2020-10-05 16:07:18 +0200 | [diff] [blame] | 375 | var widget = widgetClass.create(data); | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 376 |  | 
|  | 377 | // Store the widget based on the identifier | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 378 | services[id] = widget; | 
| Akron | a99315e | 2018-07-03 22:56:45 +0200 | [diff] [blame] | 379 | limits[id] = maxMessages; | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 380 |  | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 381 | widget._mgr = this; | 
|  | 382 |  | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 383 | // Add widget to panel | 
|  | 384 | panel.add(widget); | 
| Akron | b43c8c6 | 2018-07-04 18:27:28 +0200 | [diff] [blame] | 385 |  | 
|  | 386 | return id; | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 387 | }, | 
|  | 388 |  | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 389 |  | 
|  | 390 | /** | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 391 | * Get service by identifier | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 392 | */ | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 393 | service : function (id) { | 
|  | 394 | return services[id]; | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 395 | }, | 
|  | 396 |  | 
|  | 397 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 398 | // Receive a call from an embedded service. | 
| Akron | e8e2c95 | 2018-07-04 13:43:12 +0200 | [diff] [blame] | 399 | // The handling needs to be very careful, | 
|  | 400 | // as this can easily become a security nightmare. | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 401 | _receiveMsg : function (e) { | 
| Akron | bc94a9c | 2021-04-15 00:07:35 +0200 | [diff] [blame] | 402 |  | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 403 | // Get event data | 
|  | 404 | var d = e.data; | 
|  | 405 |  | 
| Akron | a99315e | 2018-07-03 22:56:45 +0200 | [diff] [blame] | 406 | // 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! | 
| Akron | bc94a9c | 2021-04-15 00:07:35 +0200 | [diff] [blame] | 412 | // TODO: Check e.origin is in the list of registered participants | 
|  | 413 | // if (e.origin !== "http://example.com:8080") | 
|  | 414 | //   return; | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 415 |  | 
| Akron | bc94a9c | 2021-04-15 00:07:35 +0200 | [diff] [blame] | 416 |  | 
| Akron | a99315e | 2018-07-03 22:56:45 +0200 | [diff] [blame] | 417 | // Get origin ID | 
|  | 418 | var id = d["originID"]; | 
|  | 419 |  | 
|  | 420 | // If no origin ID given - fail | 
|  | 421 | if (!id) | 
|  | 422 | return; | 
|  | 423 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 424 | // Get the service | 
|  | 425 | let service = services[id]; | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 426 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 427 | // If the addressed service does not exist - fail | 
|  | 428 | if (!service) | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 429 | return; | 
|  | 430 |  | 
| Akron | a99315e | 2018-07-03 22:56:45 +0200 | [diff] [blame] | 431 | // Check for message limits | 
|  | 432 | if (limits[id]-- < 0) { | 
| Akron | e8e2c95 | 2018-07-04 13:43:12 +0200 | [diff] [blame] | 433 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 434 | // Kill service | 
|  | 435 | KorAP.log(0, 'Suspicious action by service', service.src); | 
| Akron | 7c6e05f | 2018-07-12 19:08:13 +0200 | [diff] [blame] | 436 |  | 
|  | 437 | // TODO: | 
|  | 438 | //   Potentially kill the whole plugin! | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 439 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 440 | // This removes all connections before closing the service | 
|  | 441 | this._closeService(service.id); | 
|  | 442 |  | 
|  | 443 | // if (service.isWidget) | 
|  | 444 | service.close(); | 
|  | 445 |  | 
| Akron | a99315e | 2018-07-03 22:56:45 +0200 | [diff] [blame] | 446 | return; | 
|  | 447 | }; | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 448 |  | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 449 | // Resize the iframe | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 450 | switch (d.action) { | 
|  | 451 | case 'resize': | 
|  | 452 | if (service.isWidget) | 
|  | 453 | service.resize(d); | 
|  | 454 | break; | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 455 |  | 
|  | 456 | // Log message from iframe | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 457 | case 'log': | 
|  | 458 | KorAP.log(d.code, d.msg,  service.src); | 
|  | 459 | break; | 
| Akron | 51ee623 | 2019-12-17 21:00:05 +0100 | [diff] [blame] | 460 |  | 
|  | 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; | 
| Akron | c300364 | 2020-03-30 10:19:14 +0200 | [diff] [blame] | 471 |  | 
|  | 472 | // Get information from the embedding platform | 
|  | 473 | case 'get': | 
| Akron | 45308ce | 2020-08-28 14:10:23 +0200 | [diff] [blame] | 474 |  | 
|  | 475 | // Get KoralQuery | 
| Akron | c300364 | 2020-03-30 10:19:14 +0200 | [diff] [blame] | 476 | if (d.key == 'KQ') { | 
|  | 477 | if (KorAP.koralQuery !== undefined) { | 
|  | 478 | d["value"] = KorAP.koralQuery; | 
|  | 479 | }; | 
| Akron | 45308ce | 2020-08-28 14:10:23 +0200 | [diff] [blame] | 480 | } | 
|  | 481 |  | 
| Akron | 26d57f2 | 2021-09-10 16:48:57 +0200 | [diff] [blame] | 482 | // Get Query information from form | 
| Akron | 45308ce | 2020-08-28 14:10:23 +0200 | [diff] [blame] | 483 | 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 | }; | 
| Akron | 432972b | 2020-09-18 17:05:53 +0200 | [diff] [blame] | 497 | } | 
|  | 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"] = {}; | 
| Akron | 4de759f | 2021-10-13 10:46:45 +0200 | [diff] [blame] | 505 | v["search"] = window.location.search; // readonly | 
| Akron | 432972b | 2020-09-18 17:05:53 +0200 | [diff] [blame] | 506 | v["q"] = p.get('q'); | 
|  | 507 | v["ql"] = p.get('ql'); | 
|  | 508 | v["cq"] = p.get('cq'); | 
| Akron | 26d57f2 | 2021-09-10 16:48:57 +0200 | [diff] [blame] | 509 | } | 
|  | 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(); | 
| Akron | ec4bbfa | 2021-09-15 15:00:59 +0200 | [diff] [blame] | 518 | }; | 
| Akron | c300364 | 2020-03-30 10:19:14 +0200 | [diff] [blame] | 519 |  | 
| Akron | ec4bbfa | 2021-09-15 15:00:59 +0200 | [diff] [blame] | 520 | // 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; | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 538 | }; | 
|  | 539 |  | 
|  | 540 | // TODO: | 
|  | 541 | //   Close | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 542 | }, | 
|  | 543 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 544 | // Close the service | 
|  | 545 | _closeService : function (id) { | 
| Akron | 4a70387 | 2018-07-26 10:59:41 +0200 | [diff] [blame] | 546 | delete limits[id]; | 
| Akron | fcf89db | 2020-10-01 17:40:20 +0200 | [diff] [blame] | 547 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 548 | // 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 |  | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 556 |  | 
|  | 557 | // Remove listeners in case no widget | 
|  | 558 | // is available any longer | 
|  | 559 | if (Object.keys(limits).length == 0) | 
|  | 560 | this._removeListener(); | 
|  | 561 | }, | 
|  | 562 |  | 
| Akron | a6c32b9 | 2018-07-02 18:39:42 +0200 | [diff] [blame] | 563 | // Get a random identifier | 
|  | 564 | _randomID : function () { | 
|  | 565 | return randomID(20); | 
| Akron | b43c8c6 | 2018-07-04 18:27:28 +0200 | [diff] [blame] | 566 | }, | 
|  | 567 |  | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 568 | // 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 |  | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 576 | /** | 
|  | 577 | * Return the service element. | 
|  | 578 | */ | 
|  | 579 | element : function () { | 
| Akron | 24aa005 | 2020-11-10 11:00:34 +0100 | [diff] [blame] | 580 | if (!this._el) { | 
|  | 581 | this._el = document.createElement('div'); | 
|  | 582 | this._el.setAttribute("id", "services"); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 583 | } | 
| Akron | 24aa005 | 2020-11-10 11:00:34 +0100 | [diff] [blame] | 584 | return this._el; | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 585 | }, | 
|  | 586 |  | 
| Akron | b43c8c6 | 2018-07-04 18:27:28 +0200 | [diff] [blame] | 587 | // Destructor, just for testing scenarios | 
|  | 588 | destroy : function () { | 
|  | 589 | limits = {}; | 
| Akron | 678c26f | 2020-10-09 08:52:50 +0200 | [diff] [blame] | 590 | Object.keys(services).forEach( | 
|  | 591 | s => services[s].close() | 
|  | 592 | ); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 593 | services = {}; | 
| Akron | 678c26f | 2020-10-09 08:52:50 +0200 | [diff] [blame] | 594 | Object.keys(buttons).forEach( | 
|  | 595 | b => buttons[b] = [] | 
|  | 596 | ); | 
|  | 597 | Object.keys(buttonsSingle).forEach( | 
|  | 598 | b => buttonsSingle[b] = [] | 
|  | 599 | ); | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 600 |  | 
| Akron | 24aa005 | 2020-11-10 11:00:34 +0100 | [diff] [blame] | 601 | if (this._el) { | 
|  | 602 | let e = this._el; | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 603 | if (e.parentNode) { | 
|  | 604 | e.parentNode.removeChild(e); | 
|  | 605 | }; | 
| Akron | 24aa005 | 2020-11-10 11:00:34 +0100 | [diff] [blame] | 606 | this._el = null; | 
| Akron | 22598cd | 2019-12-09 14:59:03 +0100 | [diff] [blame] | 607 | }; | 
|  | 608 |  | 
| Akron | 76dd8d3 | 2018-07-06 09:30:22 +0200 | [diff] [blame] | 609 | this._removeListener(); | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 610 | } | 
| Akron | e1c27f6 | 2018-07-20 11:42:59 +0200 | [diff] [blame] | 611 | }; | 
| Akron | 479994e | 2018-07-02 13:21:44 +0200 | [diff] [blame] | 612 | }); |