Skip to main content

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...
});