fix(idaa): ensure breakout links preserve site access key and uuid

Proactively re-injects 'key' (site access key) and 'uuid' (Novi token)
into 'Open Externally' and 'Copy Link' URLs on the Video Conferences
page. This prevents authentication failures when members open meetings
in a new browser tab after SvelteKit internal navigation has dropped
the bootstrap parameters.

Updated CLIENT__IDAA_and_customized_mods.md to document the requirement
for these keys in breakout URLs.
This commit is contained in:
Scott Idem
2026-05-23 11:31:10 -04:00
parent 194c89f6d1
commit a90572bcb8
2 changed files with 56 additions and 5 deletions

View File

@@ -31,6 +31,17 @@ IDAA is a private membership organization for physicians in recovery. They use t
IDAA's Aether instance is embedded as an **iframe inside their existing Novi-powered website** (`idaa.org`). Novi is their external Association Management System (AMS) — it handles membership records and authentication. Aether receives the member context via URL parameters on iframe load.
### Breakout Links and Iframe Persistence
Members often need to open Jitsi meetings outside the Novi iframe (e.g., for full-screen features or on mobile). These are referred to as **Breakout Links**.
- **The Problem:** SvelteKit client-side navigation within the iframe often drops "bootstrap" query parameters like `?key=...` (site access key) and `?uuid=...` (Novi identity token).
- **The Requirement:** When a member breaks out of the iframe into a new browser tab, these keys **must** be present in the URL. Without them, the member will hit the site-domain gate or the IDAA auth gate and see "Access Denied."
- **The Solution:** The Video Conferences page uses a derived `breakout_url` that proactively re-injects the missing `key` (from `$ae_loc.allow_access`) and `uuid` (from `$idaa_loc.novi_uuid`) before generating the external link.
**Example Breakout URL:**
`https://client.oneskyit.com/idaa/video_conferences?uuid=...&key=...&room=...`
---
## Architecture: Composite Module

View File

@@ -57,17 +57,57 @@ let duration_timer_id: any = $state(null);
let show_breakout_modal: boolean = $state(false);
let breakout_link_copied: boolean = $state(false);
const breakout_url = $derived.by(() => {
const url = new URL($page.url.href);
// Proactively inject site access key if missing or empty.
// WHY: SvelteKit internal navigation drops bootstrap params (key, uuid). When a member
// "breaks out" of the Novi iframe, they need these keys in the URL to satisfy the
// site-domain gate and the IDAA Novi auth gate in a fresh tab.
if (!url.searchParams.get('key')) {
const access_key = $ae_loc.allow_access;
if (typeof access_key === 'string' && access_key) {
url.searchParams.set('key', access_key);
} else if ($ae_loc.site_access_key) {
url.searchParams.set('key', $ae_loc.site_access_key);
}
}
// Proactively inject Novi UUID if missing or empty (ensures auth persistence).
if (!url.searchParams.get('uuid') && $idaa_loc.novi_uuid) {
url.searchParams.set('uuid', $idaa_loc.novi_uuid);
}
return url.href;
});
function copy_meeting_link() {
navigator.clipboard.writeText($page.url.href).then(() => {
navigator.clipboard.writeText(breakout_url).then(() => {
breakout_link_copied = true;
setTimeout(() => (breakout_link_copied = false), 2000);
});
}
function build_jitsi_reports_href() {
const url = new URL($page.url);
const url = new URL($page.url.href);
url.pathname = '/idaa/jitsi_reports';
url.searchParams.delete('room');
// Proactively inject site access key if missing or empty
if (!url.searchParams.get('key')) {
const access_key = $ae_loc.allow_access;
if (typeof access_key === 'string' && access_key) {
url.searchParams.set('key', access_key);
} else if ($ae_loc.site_access_key) {
url.searchParams.set('key', $ae_loc.site_access_key);
}
}
// Proactively inject Novi UUID if missing or empty
if (!url.searchParams.get('uuid') && $idaa_loc.novi_uuid) {
url.searchParams.set('uuid', $idaa_loc.novi_uuid);
}
return `${url.pathname}${url.search}${url.hash}`;
}
@@ -1172,7 +1212,7 @@ async function init_jitsi() {
mt-2 flex w-full flex-col flex-wrap items-center justify-center gap-1
border-t-2 border-dashed border-gray-400 pt-2 sm:flex-row">
<MyClipboard
value={encodeURI($page.url.href)}
value={encodeURI(breakout_url)}
btn_text="Copy Break-out Link"
btn_title="Copy a link to this meeting that can be opened outside of the Novi iframe."
btn_class="mt-2 px-2 py-1 bg-indigo-400 text-white rounded hover:bg-indigo-500"
@@ -1312,7 +1352,7 @@ async function init_jitsi() {
{breakout_link_copied ? 'Copied!' : 'Copy Link'}
</button>
<a
href={$page.url.href}
href={breakout_url}
target="_blank"
rel="noopener noreferrer"
onclick={() => (show_breakout_modal = false)}
@@ -1327,7 +1367,7 @@ async function init_jitsi() {
<textarea
readonly
rows="3"
value={$page.url.href}
value={breakout_url}
onclick={(e) => (e.target as HTMLTextAreaElement).select()}
class="w-full cursor-text resize-none rounded border border-gray-200 bg-gray-50 px-2 py-1.5 font-mono text-xs text-gray-700 focus:outline-none"
title="Click to select all, then copy (ctrl/cmd + C)"