diff --git a/roles/srv-web-7-7-inj-compose/tasks/main.yml b/roles/srv-web-7-7-inj-compose/tasks/main.yml index 82a1a62b..a988c6b9 100644 --- a/roles/srv-web-7-7-inj-compose/tasks/main.yml +++ b/roles/srv-web-7-7-inj-compose/tasks/main.yml @@ -15,6 +15,12 @@ matomo: "{{ applications | get_app_conf(application_id, 'features.matomo', False) }}" port_ui: "{{ applications | get_app_conf(application_id, 'features.port-ui-desktop', False) }}" +- name: "Activate Portfolio iFrame notifier for {{ domain }}" + include_role: + name: srv-web-7-7-inj-port-ui-desktop + public: true # Expose variables so that they can be used in template + when: inj_enabled.port_ui + - name: "Load CDN for {{domain}}" include_role: name: web-svc-cdn @@ -56,11 +62,6 @@ name: srv-web-7-7-inj-matomo when: inj_enabled.matomo -- name: "Activate Portfolio iFrame notifier for {{ domain }}" - include_role: - name: srv-web-7-7-inj-port-ui-desktop - when: inj_enabled.port_ui - - name: "Activate Javascript for {{ domain }}" include_role: name: srv-web-7-7-inj-javascript diff --git a/roles/srv-web-7-7-inj-compose/templates/location.lua.j2 b/roles/srv-web-7-7-inj-compose/templates/location.lua.j2 index 9598d9a7..5707f28a 100644 --- a/roles/srv-web-7-7-inj-compose/templates/location.lua.j2 +++ b/roles/srv-web-7-7-inj-compose/templates/location.lua.j2 @@ -58,7 +58,7 @@ body_filter_by_lua_block { -- build a list of body-injection snippets local body_snippets = {} - {% for body_feature in ['matomo', 'logout' ] %} + {% for body_feature in ['matomo', 'logout', 'port-ui-desktop'] %} {% if applications | get_app_conf(application_id, 'features.' ~ body_feature, false) | bool %} body_snippets[#body_snippets + 1] = [=[ {%- include "roles/srv-web-7-7-inj-" ~ body_feature ~ "/templates/body_sub.j2" -%} diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/tasks/01_deploy.yml b/roles/srv-web-7-7-inj-port-ui-desktop/tasks/01_deploy.yml new file mode 100644 index 00000000..153be3b4 --- /dev/null +++ b/roles/srv-web-7-7-inj-port-ui-desktop/tasks/01_deploy.yml @@ -0,0 +1,16 @@ +- name: Deploy iframe-handler.js + template: + src: iframe-handler.js.j2 + dest: "{{ inj_port_ui_js_destination }}" + owner: "{{ nginx.user }}" + group: "{{ nginx.user }}" + mode: '0644' + +- name: Get stat for iframe-handler.js + stat: + path: "{{ inj_port_ui_js_destination }}" + register: inj_port_ui_js_stat + +- name: Set inj_port_ui_js_version + set_fact: + inj_port_ui_js_version: "{{ inj_port_ui_js_stat.stat.mtime }}" diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/tasks/main.yml b/roles/srv-web-7-7-inj-port-ui-desktop/tasks/main.yml index 35a375f2..8fb9db5b 100644 --- a/roles/srv-web-7-7-inj-port-ui-desktop/tasks/main.yml +++ b/roles/srv-web-7-7-inj-port-ui-desktop/tasks/main.yml @@ -3,18 +3,20 @@ include_role: name: srv-web-7-4-core when: run_once_srv_web_7_4_core is not defined + - include_tasks: 01_deploy.yml - include_tasks: utils/run_once.yml when: run_once_srv_web_7_7_inj_port_ui_desktop is not defined -- name: "Load iFrame handler JS template for '{{ application_id }}'" +# --- Build tiny inline initializer (CSP-hashed) --- +- name: "Load iFrame init code for '{{ application_id }}'" set_fact: - iframe_code: "{{ lookup('template','iframe-handler.js.j2') }}" + iframe_init_code: "{{ lookup('template','iframe-init_one_liner.js.j2') }}" -- name: "Collapse iFrame code into one-liner for '{{ application_id }}'" +- name: "Collapse iFrame init code into one-liner for '{{ application_id }}'" set_fact: - iframe_code_one_liner: "{{ iframe_code | to_one_liner }}" + iframe_init_code_one_liner: "{{ iframe_init_code | to_one_liner }}" -- name: "Append iFrame CSP hash for '{{ application_id }}'" +- name: "Append iFrame init CSP hash for '{{ application_id }}'" set_fact: - applications: "{{ applications | append_csp_hash(application_id, iframe_code_one_liner) }}" + applications: "{{ applications | append_csp_hash(application_id, iframe_init_code_one_liner) }}" changed_when: false diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/templates/body_sub.j2 b/roles/srv-web-7-7-inj-port-ui-desktop/templates/body_sub.j2 new file mode 100644 index 00000000..9ef139f9 --- /dev/null +++ b/roles/srv-web-7-7-inj-port-ui-desktop/templates/body_sub.j2 @@ -0,0 +1 @@ + diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/templates/head_sub.j2 b/roles/srv-web-7-7-inj-port-ui-desktop/templates/head_sub.j2 index 42f9bafd..4b0fcb83 100644 --- a/roles/srv-web-7-7-inj-port-ui-desktop/templates/head_sub.j2 +++ b/roles/srv-web-7-7-inj-port-ui-desktop/templates/head_sub.j2 @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-handler.js.j2 b/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-handler.js.j2 index 438eb815..fdcfbd19 100644 --- a/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-handler.js.j2 +++ b/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-handler.js.j2 @@ -1,48 +1,57 @@ -(function() { - var primary = "{{ PRIMARY_DOMAIN }}"; - var allowedOrigin = "https://{{ domains | get_domain('web-app-port-ui') }}"; - - function notifyParent() { - if (window.self !== window.top) { - try { - window.parent.postMessage({ - type: "iframeLocationChange", - href: window.location.href - }, allowedOrigin); - } catch (e) {} +(function (global) { + /** + * Initializes the iframe sync & external link forcing logic. + * @param {string} primary_domain + * @param {string} current_domain + * @param {string} allowedOrigin - Parent origin for postMessage + */ + function initIframeHandler(primary_domain, current_domain, allowedOrigin) { + function notifyParent() { + if (window.self !== window.top) { + try { + window.parent.postMessage( + { type: "iframeLocationChange", href: window.location.href }, + allowedOrigin + ); + } catch (e) {} + } } - } - function forceExternalLinks() { - Array.prototype.forEach.call(document.querySelectorAll("a[href]"), function(a) { - try { - var url = new URL(a.href, location); - if (!url.hostname.endsWith(primary)) { - a.target = "_blank"; - a.rel = "noopener"; - } - } catch (e) {} + function forceExternalLinks() { + Array.prototype.forEach.call(document.querySelectorAll("a[href]"), function (a) { + try { + var url = new URL(a.href, location); + // open new tab if link goes outside our primary OR current domain + if (!(url.hostname.endsWith(primary_domain) || url.hostname.endsWith(current_domain))) { + a.target = "_blank"; + a.rel = "noopener"; + } + } catch (e) {} + }); + } + + window.addEventListener("load", function () { + notifyParent(); + forceExternalLinks(); }); + window.addEventListener("popstate", function () { + notifyParent(); + forceExternalLinks(); + }); + + // SPA support + var _pushState = history.pushState; + history.pushState = function () { + _pushState.apply(history, arguments); + notifyParent(); + forceExternalLinks(); + }; + + {% if MODE_DEBUG | bool %} + try { console.log("[iframe-sync] initIframeHandler installed."); } catch (e) {} + {% endif %} } - window.addEventListener("load", function() { - notifyParent(); - forceExternalLinks(); - }); - window.addEventListener("popstate", function() { - notifyParent(); - forceExternalLinks(); - }); - - // SPA support - var _pushState = history.pushState; - history.pushState = function() { - _pushState.apply(history, arguments); - notifyParent(); - forceExternalLinks(); - }; -})(); - -{% if MODE_DEBUG | bool %} -console.log("[iframe-sync] Sender for iframe messages is active."); -{% endif %} \ No newline at end of file + // expose for inline bootstrap + global.initIframeHandler = initIframeHandler; +})(window); diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-init_one_liner.js.j2 b/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-init_one_liner.js.j2 new file mode 100644 index 00000000..c6a271db --- /dev/null +++ b/roles/srv-web-7-7-inj-port-ui-desktop/templates/iframe-init_one_liner.js.j2 @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function () { + initIframeHandler( + '{{ PRIMARY_DOMAIN }}', + '{{ domain }}', + '{{ domains | get_url("web-app-port-ui", WEB_PROTOCOL) }}' + ); +}); +{% if MODE_DEBUG | bool %} +try { console.log("[iframe-sync] Sender for iframe messages is active."); } catch(e) {} +{% endif %} diff --git a/roles/srv-web-7-7-inj-port-ui-desktop/vars/main.yml b/roles/srv-web-7-7-inj-port-ui-desktop/vars/main.yml new file mode 100644 index 00000000..f9e693f6 --- /dev/null +++ b/roles/srv-web-7-7-inj-port-ui-desktop/vars/main.yml @@ -0,0 +1,2 @@ +inj_port_ui_file_name: "iframe-handler.js" +inj_port_ui_js_destination: "{{ [ nginx.directories.data.cdn, inj_port_ui_file_name ] | path_join }}" diff --git a/roles/web-svc-cdn/config/main.yml b/roles/web-svc-cdn/config/main.yml index 0bde48f1..7f720bf3 100644 --- a/roles/web-svc-cdn/config/main.yml +++ b/roles/web-svc-cdn/config/main.yml @@ -1,7 +1,7 @@ features: matomo: true css: true - port-ui-desktop: true + port-ui-desktop: false server: domains: canonical: