Integration Guide: Receiving Messages from the Sit-to-Stand Iframe
When you embed the GOFA Sit-to-Stand experience as an <iframe>, you can listen for important events and results using the window.postMessage API.
1. Embed the Iframe
<iframe
id="sitToStandIframe"
src="https://your-gofa-app-url/fall-risk/[fallRiskResultId]/sit-to-stand"
width="100%"
height="600"
frameborder="0"
allow="camera; microphone"
></iframe>
2. Listen for Messages
Add a message event listener in your parent page's JavaScript:
window.addEventListener("message", function (event) {
// Optionally, check event.origin for security
// if (event.origin !== 'https://your-gofa-app-url') return;
const { source, type, payload } = event.data || {};
// Only handle messages from the GOFA sit-to-stand iframe
if (source !== "gofa-sit-to-stand") return;
switch (type) {
case "STATE_TRANSITION":
// payload: { state, metrics }
console.log("State changed:", payload.state, payload.metrics);
break;
case "ASSESSMENT_STARTED":
// payload: { metrics }
console.log("Assessment started:", payload.metrics);
break;
case "ASSESSMENT_ENDED":
// payload: { metrics }
console.log("Assessment ended:", payload.metrics);
break;
case "REP_COUNT_UPDATED":
// payload: { count, fiveRepsTimeTaken, shoulderTilts }
console.log(
"Rep count updated:",
payload.count,
payload.fiveRepsTimeTaken,
);
break;
case "RESULTS_SET":
// payload: { count, fiveRepsTimeTaken, shoulderTilts }
console.log("Final results:", payload);
break;
case "TIMER_COMPLETE":
console.log("Timer completed");
break;
case "TIMER_ERROR":
// payload: { error }
console.error("Timer error:", payload.error);
break;
default:
// Handle other message types if needed
break;
}
});
3. Message Types and Payloads
STATE_TRANSITION
Fired when the test state changes (e.g., from calibration to introduction to assessment).
Payload:
{
state: "calibration" | "introduction_step1" | "introduction_step2" | "introduction_step3" | "introduction_step4" | "demo" | "ready_showStartPose" | "ready_holdPose" | "ready_holdPose_voiceAssist" | "ready_sequence" | "assessment" | "assessment_complete" | "result_showCheck" | "result_showMetrics" | "paused" | "quited",
metrics: {
count?: number, // Current repetition count
fiveRepsTimeTaken?: number, // Time taken to complete 5 repetitions (if achieved)
shoulderTilts?: number[] // Array of shoulder tilt measurements collected every second during assessment
}
}
ASSESSMENT_STARTED
Fired when the assessment phase begins.
Payload:
{
metrics: {
count?: number, // Current repetition count (starts at 0)
fiveRepsTimeTaken?: number, // Time taken to complete 5 repetitions (if achieved)
shoulderTilts?: number[] // Array of shoulder tilt measurements collected every second during assessment
}
}
ASSESSMENT_ENDED
Fired when the assessment phase ends (30 seconds elapsed or early exit). This is especially important as it contains the final assessment results that the parent window needs to capture.
Payload:
{
metrics: {
count: number, // Final number of completed sit-to-stand repetitions
fiveRepsTimeTaken?: number, // Time taken to complete 5 repetitions (null if less than 5 reps completed)
shoulderTilts: number[] // Array of shoulder tilt measurements in degrees, collected every second during the assessment
}
}
REP_COUNT_UPDATED
Fired each time the user completes a sit-to-stand repetition.
Payload:
{
count: number, // Updated repetition count
fiveRepsTimeTaken?: number, // Time taken to complete 5 repetitions (only set when count reaches 5)
shoulderTilts: number[] // Array of shoulder tilt measurements in degrees, collected every second during the assessment
}
RESULTS_SET
Fired when the final assessment results are calculated and stored.
Payload:
{
count: number, // Final number of completed sit-to-stand repetitions
fiveRepsTimeTaken: number | null, // Time in seconds to complete 5 repetitions (null if less than 5 completed)
shoulderTilts: number[] // Array of shoulder tilt measurements in degrees, collected every second during the assessment
}
TIMER_COMPLETE
Fired when the assessment timer completes (30 seconds elapsed).
Payload: None
TIMER_ERROR
Fired when there's an error with the assessment timer.
Payload:
{
error: any; // Error details
}
EARLY_EXIT
Fired when the user exits the assessment early (e.g., via quit button).
Payload: None
4. Example Implementation
// Track sit-to-stand test progress
let testResults = null;
let currentRepCount = 0;
let isTestActive = false;
window.addEventListener("message", function (event) {
const { source, type, payload } = event.data || {};
if (source !== "gofa-sit-to-stand") return;
switch (type) {
case "STATE_TRANSITION":
console.log("State changed to:", payload.state);
if (payload.state === "assessment") {
isTestActive = true;
updateUI("Sit-to-stand assessment started");
} else if (payload.state === "assessment_complete") {
isTestActive = false;
updateUI("Assessment completed");
}
break;
case "ASSESSMENT_STARTED":
isTestActive = true;
currentRepCount = 0;
updateUI("Assessment phase started");
break;
case "ASSESSMENT_ENDED":
isTestActive = false;
testResults = payload.metrics;
updateUI("Assessment phase ended");
console.log("Final assessment data:", testResults);
// This is the most important event - contains complete results
break;
case "REP_COUNT_UPDATED":
currentRepCount = payload.count;
updateUI(`Repetitions completed: ${currentRepCount}`);
if (payload.fiveRepsTimeTaken) {
updateUI(
`5 reps completed in ${payload.fiveRepsTimeTaken.toFixed(1)} seconds`,
);
}
break;
case "RESULTS_SET":
testResults = payload;
showResults(payload);
break;
case "TIMER_COMPLETE":
updateUI("Timer completed");
break;
case "TIMER_ERROR":
console.error("Timer error:", payload.error);
updateUI("Timer error occurred");
break;
case "EARLY_EXIT":
isTestActive = false;
updateUI("User exited assessment early");
break;
}
});
function updateUI(message) {
document.getElementById("status").textContent = message;
if (currentRepCount > 0) {
document.getElementById("repCount").textContent =
`Reps: ${currentRepCount}`;
}
}
function showResults(results) {
const avgShoulderTilt =
results.shoulderTilts.length > 0
? (
results.shoulderTilts.reduce((a, b) => a + b, 0) /
results.shoulderTilts.length
).toFixed(1)
: "N/A";
const timeMessage = results.fiveRepsTimeTaken
? `Time for 5 reps: ${results.fiveRepsTimeTaken.toFixed(1)} seconds`
: "Did not complete 5 repetitions";
document.getElementById("results").innerHTML = `
<h3>Sit-to-Stand Test Results</h3>
<p>Total Repetitions: ${results.count}</p>
<p>${timeMessage}</p>
<p>Shoulder Tilt Measurements: ${results.shoulderTilts.length}</p>
<p>Average Shoulder Tilt: ${avgShoulderTilt}°</p>
`;
}
5. Security Considerations
For production environments, always verify the event.origin to ensure messages are coming from your trusted GOFA application:
window.addEventListener("message", function (event) {
// Replace with your actual GOFA app domain
if (event.origin !== "https://your-gofa-app-url") {
console.warn("Received message from untrusted origin:", event.origin);
return;
}
// Process the message...
});