Integration Guide: Receiving Messages from the Tandem Walk Iframe
When you embed the GOFA Tandem Walk experience as an <iframe>, you can listen for important events and results using the window.postMessage API.
1. Embed the Iframe
<iframe
id="tandemWalkIframe"
src="https://your-gofa-app-url/fall-risk/[fallRiskResultId]/tandem-walk"
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 tandem-walk iframe
if (source !== "gofa-tandem-walk") 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 "RESULTS_SET":
// payload: { stepCount, stepsCompleted, walkingPath, 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;
case "EARLY_EXIT":
console.log("User exited assessment early");
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: {
stepCount?: number, // Current number of tandem steps taken
stepsCompleted?: boolean, // Whether the required steps were completed
walkingPath?: Array<{x: number, y: number, timestamp: number}>, // Array of foot position coordinates over time
shoulderTilts?: number[] // Array of shoulder tilt measurements collected every second during assessment
}
}
ASSESSMENT_STARTED
Fired when the assessment phase begins.
Payload:
{
metrics: {
stepCount?: number, // Current number of tandem steps taken (starts at 0)
stepsCompleted?: boolean, // Whether the required steps were completed
walkingPath?: Array<{x: number, y: number, timestamp: number}>, // Array of foot position coordinates over time
shoulderTilts?: number[] // Array of shoulder tilt measurements collected every second during assessment
}
}
ASSESSMENT_ENDED
Fired when the assessment phase ends (time limit reached or early exit). This is especially important as it contains the final assessment results that the parent window needs to capture.
Payload:
{
metrics: {
stepCount: number, // Final number of tandem steps completed
stepsCompleted: boolean, // Whether the user completed the required number of steps
walkingPath: Array<{x: number, y: number, timestamp: number}>, // Complete array of foot position coordinates tracked during the assessment
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:
{
stepCount: number, // Final number of tandem steps completed
stepsCompleted: boolean, // Whether the user completed the required number of steps
walkingPath: Array<{x: number, y: number, timestamp: number}>, // Complete array of foot position coordinates tracked during the assessment
shoulderTilts: number[] // Array of shoulder tilt measurements in degrees, collected every second during the assessment
}
TIMER_COMPLETE
Fired when the assessment timer completes.
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 tandem walk test progress
let testResults = null;
let currentStepCount = 0;
let isTestActive = false;
window.addEventListener("message", function (event) {
const { source, type, payload } = event.data || {};
if (source !== "gofa-tandem-walk") return;
switch (type) {
case "STATE_TRANSITION":
console.log("State changed to:", payload.state);
if (payload.state === "assessment") {
isTestActive = true;
updateUI("Tandem walk assessment started");
} else if (payload.state === "assessment_complete") {
isTestActive = false;
updateUI("Assessment completed");
}
if (payload.metrics && payload.metrics.stepCount !== undefined) {
currentStepCount = payload.metrics.stepCount;
}
break;
case "ASSESSMENT_STARTED":
isTestActive = true;
currentStepCount = 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 "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 (currentStepCount > 0) {
document.getElementById("stepCount").textContent =
`Steps: ${currentStepCount}`;
}
}
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 completionStatus = results.stepsCompleted
? "Successfully completed required steps"
: "Did not complete all required steps";
const pathLength = results.walkingPath ? results.walkingPath.length : 0;
document.getElementById("results").innerHTML = `
<h3>Tandem Walk Test Results</h3>
<p>Steps Taken: ${results.stepCount}</p>
<p>Status: ${completionStatus}</p>
<p>Walking Path Points: ${pathLength}</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...
});