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: