Koppelmij Implementation Guide
0.1.0 - ci-build
Koppelmij Implementation Guide - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
Bij het lanceren van modules vanuit een PGO is het essentieel om een goede gebruikerservaring te bieden bij het afsluiten van de module. De gebruiker moet op een intuïtieve manier terug kunnen keren naar de PGO context waar de module vandaan werd gestart. Dit document beschrijft de verschillende opties voor module integratie en de bijbehorende terugkeer mechanismen.
Modules kunnen op drie verschillende manieren worden geïntegreerd in de PGO gebruikersinterface:
De module wordt geladen binnen een iframe in de PGO interface.
De module vervangt de huidige PGO pagina in hetzelfde browser window.
De module opent in een nieuw browser tabblad of window.
Kenmerken:
Terugkeer mechanisme:
Voorbeeld PostMessage implementatie:
// Module stuurt bericht bij afsluiten
window.parent.postMessage({
type: 'module-close',
status: 'completed',
data: {
taskId: '123',
outcome: 'success'
}
}, '*'); // In productie: gebruik specifieke origin
// PGO luistert naar berichten
window.addEventListener('message', (event) => {
if (event.data.type === 'module-close') {
// Verwijder iframe of verberg module container
closeModuleIframe();
}
});
Voordelen:
Nadelen:
Kenmerken:
⚠️ BELANGRIJKE SECURITY WAARSCHUWING: De token response met het access_token wordt ALLEEN verwerkt door de module backend server en mag NOOIT gedeeld worden met de browser/frontend. De module backend extraheert veilige context informatie (zoals return_url) en stuurt alleen deze naar de frontend.
Terugkeer mechanisme:
De return_url
parameter wordt meegegeven als onderdeel van de FHIR context tijdens de module launch. De module backend verwerkt deze informatie en maakt deze beschikbaar voor de frontend zonder het access_token te exposeren:
{
"access_token": "[GEHEIM - alleen backend]",
"patient": "123",
"resource": "Task/789",
"return_url": "https://pgo.example.com/patient/123/modules?task=789&status=active"
}
Let op: Het access_token
wordt NOOIT naar de frontend/browser gestuurd!
{
"access_token": "[GEHEIM - alleen backend]",
"authorization_details": [{
"type": "fhir_context",
"locations": ["https://fhir.example.org"],
"patient": "123",
"resource": "Task/789",
"return_url": "https://pgo.example.com/patient/123/modules?task=789"
}]
}
Let op: Het access_token
wordt NOOIT naar de frontend/browser gestuurd!
Module backend implementatie:
# Python voorbeeld - backend verwerkt token response
def handle_token_response(token_response):
"""
Deze functie draait op de module backend server,
NIET in de browser/frontend
"""
# Extract veilige context informatie voor frontend
safe_context = {
'patient_id': token_response.get('patient'),
'task_id': token_response.get('resource'), # bijv. "Task/789"
'return_url': token_response.get('return_url')
}
# Sla access_token veilig op in server sessie
session['access_token'] = token_response['access_token']
# Stuur alleen veilige context naar frontend
# NOOIT het access_token
return safe_context
Module frontend implementatie:
// Frontend krijgt alleen veilige context info van backend
let moduleContext = null;
// Haal context op van module backend (niet direct van DVA)
async function initializeModule() {
const response = await fetch('/api/module/context', {
credentials: 'include' // Gebruik sessie cookies
});
moduleContext = await response.json();
// moduleContext bevat return_url maar GEEN access_token
}
// Bij afsluiten module
function closeModule(status = 'completed') {
if (moduleContext && moduleContext.return_url) {
// Voeg optioneel status parameter toe
const url = new URL(moduleContext.return_url);
url.searchParams.append('module_status', status);
url.searchParams.append('timestamp', new Date().toISOString());
// Navigeer terug naar PGO
window.location.href = url.toString();
} else {
// Fallback: toon melding aan gebruiker
alert('Sluit dit venster om terug te keren naar uw PGO');
}
}
// FHIR calls gaan via module backend, nooit direct vanuit browser
async function getFHIRResource(resourceType, id) {
// Backend gebruikt opgeslagen access_token
const response = await fetch(`/api/fhir/${resourceType}/${id}`, {
credentials: 'include'
});
return response.json();
}
URL constructie door PGO:
// PGO genereert return_url tijdens Token Exchange
function generateReturnUrl(taskId, patientId) {
const baseUrl = 'https://pgo.example.com';
const returnPath = `/patient/${patientId}/modules`;
const params = new URLSearchParams({
task: taskId,
session: generateSessionToken(),
return_timestamp: new Date().toISOString()
});
return `${baseUrl}${returnPath}?${params.toString()}`;
}
Voordelen:
Nadelen:
Kenmerken:
Terugkeer mechanisme:
Voorbeeld implementatie met BroadcastChannel:
// PGO luistert naar module status updates
const channel = new BroadcastChannel('module-communication');
channel.addEventListener('message', (event) => {
if (event.data.type === 'module-closed') {
// Update UI om module status te tonen
updateModuleStatus(event.data.taskId, event.data.status);
// Focus PGO tabblad (optioneel)
window.focus();
}
});
// Module stuurt bericht bij afsluiten
function closeModuleTab() {
const channel = new BroadcastChannel('module-communication');
channel.postMessage({
type: 'module-closed',
taskId: getCurrentTaskId(),
status: 'completed',
timestamp: new Date().toISOString()
});
// Sluit het tabblad
window.close();
}
Window.opener alternatief:
// Module communiceert met opener window
function notifyAndClose() {
if (window.opener && !window.opener.closed) {
// Roep functie aan in PGO window
window.opener.postMessage({
type: 'module-complete',
taskId: '789'
}, 'https://pgo.example.com');
}
// Sluit module tabblad
setTimeout(() => window.close(), 100);
}
Voordelen:
Nadelen:
De DVA moet return_url's valideren om open redirect kwetsbaarheden te voorkomen:
// DVA valideert return_url tijdens Token Exchange
function validateReturnUrl(returnUrl, allowedDomains) {
try {
const url = new URL(returnUrl);
// Controleer protocol
if (!['https:', 'http:'].includes(url.protocol)) {
return false;
}
// Controleer tegen whitelist van toegestane domeinen
const isAllowed = allowedDomains.some(domain => {
return url.hostname === domain ||
url.hostname.endsWith(`.${domain}`);
});
if (!isAllowed) {
throw new Error(`Return URL domain niet toegestaan: ${url.hostname}`);
}
return true;
} catch (error) {
console.error('Return URL validatie fout:', error);
return false;
}
}
Voor iframe en cross-window communicatie:
// Strikte origin controle voor postMessage
window.addEventListener('message', (event) => {
// Valideer origin
const trustedOrigins = ['https://module1.example.com', 'https://module2.example.com'];
if (!trustedOrigins.includes(event.origin)) {
console.warn('Bericht van niet-vertrouwde origin:', event.origin);
return;
}
// Verwerk bericht
handleModuleMessage(event.data);
});
Scenario | Aanbevolen Integratie | Terugkeer Mechanisme |
---|---|---|
Eenvoudige module, korte interactie | Iframe | PostMessage + close button |
Complexe module, volledige UI nodig | Zelfde window | return_url in FHIR context |
Module naast PGO workflow | Nieuw tabblad | BroadcastChannel of window.opener |
Gevoelige data, strikte security | Zelfde window | return_url met sessie validatie |
De keuze voor een terugkeer mechanisme hangt af van de specifieke integratie methode en use case requirements. Door return_url op te nemen in de FHIR context bieden we een gestandaardiseerde oplossing voor het "zelfde window" scenario, terwijl andere integratie types hun eigen optimale terugkeer strategieën hebben.
Het is essentieel dat zowel PGO's als modules flexibel zijn in hun implementatie en meerdere terugkeer mechanismen ondersteunen om de beste gebruikerservaring te kunnen bieden in verschillende contexten.