Skip to content

Home > Documentation > Examples > Basic Parent-Child Communication

Basic Parent-Child Communication Example

A complete working example showing ParleyJS communication between a parent window and an iframe.

Table of Contents

  1. Overview
  2. Prerequisites
  3. Project Structure
  4. Parent Window Implementation
  5. Child Window Implementation
  6. Understanding the Code
  7. Running the Example
  8. Common Issues
  9. Next Steps

Overview

This example demonstrates the fundamental pattern for ParleyJS communication between a parent window and an iframe. The parent sends a request to the child, and the child responds with data.

What you will learn:

  • Creating a ParleyJS instance with origin validation
  • Connecting to an iframe
  • Sending messages and receiving responses
  • Handling errors and timeouts
  • Using TypeScript for type safety

Prerequisites

Before starting, ensure you have:

  • ParleyJS installed in your project
  • Basic understanding of iframes and the postMessage API
  • A local development server (for HTTPS in production)

For installation instructions, see Installation Guide.


Project Structure

Create the following files in your project:

project/
├── parent.html       # Parent window
├── child.html        # Child iframe
├── parent.ts         # Parent logic
└── child.ts          # Child logic

Parent Window Implementation

parent.html

html
<!DOCTYPE html>
<html>
    <head>
        <title>ParleyJS Parent Example</title>
    </head>
    <body>
        <h1>Parent Window</h1>
        <button id="send-message">Send Message to Child</button>
        <div id="response"></div>

        <iframe
            id="child-frame"
            src="child.html"
            width="600"
            height="400"
        ></iframe>

        <script type="module" src="parent.ts"></script>
    </body>
</html>

parent.ts

typescript
import { Parley, SYSTEM_EVENTS } from 'parley-js';
import type { TimeoutError, ValidationError } from 'parley-js';

// Define message types for type safety
interface DataRequest {
    userId: number;
}

interface DataResponse {
    success: boolean;
    user: {
        id: number;
        name: string;
        email: string;
    };
}

// Create ParleyJS instance with origin validation
const parley = Parley.create({
    allowedOrigins: [window.location.origin],
    timeout: 5000,
    debug: true,
});

// Get DOM elements
const iframe = document.getElementById('child-frame') as HTMLIFrameElement;
const sendButton = document.getElementById('send-message') as HTMLButtonElement;
const responseDiv = document.getElementById('response') as HTMLDivElement;

// Wait for iframe to load before connecting
iframe.addEventListener('load', async () => {
    try {
        // Connect to the iframe
        await parley.connect(iframe.contentWindow!, 'child-iframe');
        console.log('Connected to child iframe');

        // Enable the send button
        sendButton.disabled = false;
    } catch (error) {
        console.error('Failed to connect to iframe:', error);
        responseDiv.textContent = 'Connection failed';
    }
});

// Listen for connection events
parley.onSystem(SYSTEM_EVENTS.CONNECTED, (event) => {
    console.log('Child connected:', event.targetId);
    responseDiv.textContent = 'Ready to send messages';
});

parley.onSystem(SYSTEM_EVENTS.DISCONNECTED, (event) => {
    console.log('Child disconnected:', event.targetId, event.reason);
    responseDiv.textContent = 'Child disconnected';
    sendButton.disabled = true;
});

// Handle send button click
sendButton.addEventListener('click', async () => {
    try {
        // Show loading state
        responseDiv.textContent = 'Sending request...';
        sendButton.disabled = true;

        // Send request to child and wait for response
        const response = await parley.send<DataRequest, DataResponse>(
            'get-user-data',
            { userId: 123 },
            {
                targetId: 'child-iframe',
                timeout: 5000,
            }
        );

        // Handle successful response
        if (response.success) {
            responseDiv.innerHTML = `
                <h3>User Data Received:</h3>
                <p>ID: ${response.user.id}</p>
                <p>Name: ${response.user.name}</p>
                <p>Email: ${response.user.email}</p>
            `;
        } else {
            responseDiv.textContent = 'Failed to retrieve user data';
        }
    } catch (error) {
        // Handle different error types
        if (error instanceof TimeoutError) {
            responseDiv.textContent =
                'Request timed out. Child may be unresponsive.';
            console.error('Timeout error:', error);
        } else if (error instanceof ValidationError) {
            responseDiv.textContent = 'Invalid response from child.';
            console.error('Validation error:', error);
        } else {
            responseDiv.textContent =
                'Request failed. See console for details.';
            console.error('Request error:', error);
        }
    } finally {
        // Re-enable button
        sendButton.disabled = false;
    }
});

// Clean up when page unloads
window.addEventListener('beforeunload', () => {
    parley.disconnect('child-iframe');
});

Child Window Implementation

child.html

html
<!DOCTYPE html>
<html>
    <head>
        <title>ParleyJS Child Example</title>
    </head>
    <body>
        <h1>Child Iframe</h1>
        <p>Waiting for requests from parent...</p>
        <div id="status">Not connected</div>

        <script type="module" src="child.ts"></script>
    </body>
</html>

child.ts

typescript
import { Parley, SYSTEM_EVENTS } from 'parley-js';
import type { MessageMetadata, ResponseFunction } from 'parley-js';

// Define message types
interface DataRequest {
    userId: number;
}

interface DataResponse {
    success: boolean;
    user: {
        id: number;
        name: string;
        email: string;
    };
}

// Create ParleyJS instance
const parley = Parley.create({
    allowedOrigins: [window.location.origin],
    debug: true,
});

const statusDiv = document.getElementById('status') as HTMLDivElement;

// Register message handler for 'get-user-data' requests
parley.on<DataRequest>(
    'get-user-data',
    async (
        payload,
        respond: ResponseFunction<DataResponse>,
        metadata: MessageMetadata
    ) => {
        console.log('Received request from parent:', payload);
        statusDiv.textContent = `Processing request for user ${payload.userId}...`;

        try {
            // Simulate data fetch (in real app, this would be an API call)
            const userData = await fetchUserData(payload.userId);

            // Send success response
            respond({
                success: true,
                user: userData,
            });

            statusDiv.textContent = `Sent user data for ID ${payload.userId}`;
        } catch (error) {
            // Send error response
            respond({
                success: false,
                user: null as any,
            });

            statusDiv.textContent = `Failed to fetch user ${payload.userId}`;
            console.error('Error fetching user data:', error);
        }
    }
);

// Simulate fetching user data
async function fetchUserData(userId: number) {
    // Simulate network delay
    await new Promise((resolve) => setTimeout(resolve, 500));

    // Return mock data
    return {
        id: userId,
        name: 'Alice Johnson',
        email: 'alice@example.com',
    };
}

// Connect to parent window
(async () => {
    try {
        await parley.connect(window.parent, 'parent-window');
        console.log('Connected to parent');
    } catch (error) {
        console.error('Failed to connect to parent:', error);
        statusDiv.textContent = 'Connection failed';
    }
})();

// Listen for connection events
parley.onSystem(SYSTEM_EVENTS.CONNECTED, (event) => {
    console.log('Connected to parent:', event.targetId);
    statusDiv.textContent = 'Connected to parent - Ready';
});

parley.onSystem(SYSTEM_EVENTS.MESSAGE_RECEIVED, (event) => {
    console.log('Message received:', event.messageType);
});

Understanding the Code

Origin Validation

Both parent and child use strict origin validation:

typescript
allowedOrigins: [window.location.origin];

This ensures only messages from the same origin are accepted. In production, specify the exact origins you trust.

For more on origin validation, see Origin Validation Guide.

Connection Establishment

The parent waits for the iframe to load before connecting:

typescript
iframe.addEventListener('load', async () => {
    await parley.connect(iframe.contentWindow!, 'child-iframe');
});

The child connects to the parent immediately:

typescript
await parley.connect(window.parent, 'parent-window');

For connection patterns, see iFrame Communication Guide.

Type Safety

TypeScript generics provide type safety for messages:

typescript
await parley.send<DataRequest, DataResponse>(
    'get-user-data',
    { userId: 123 },
    { targetId: 'child-iframe' }
);

The compiler ensures request and response types match your interfaces.

Error Handling

The example handles three error types:

  • TimeoutError: Request took too long
  • ValidationError: Response failed schema validation
  • Generic errors: Other communication failures

For comprehensive error handling patterns, see Error Handling Pattern.

System Events

System events provide visibility into the communication lifecycle:

typescript
parley.onSystem(SYSTEM_EVENTS.CONNECTED, (event) => {
    console.log('Connected:', event.targetId);
});

For all available system events, see System Events Reference.


Running the Example

Development Server

Start a local development server:

bash
# Using Python
python -m http.server 8000

# Using Node.js (http-server)
npx http-server -p 8000

# Using VS Code Live Server
# Install Live Server extension and click "Go Live"

Open in Browser

Navigate to http://localhost:8000/parent.html

Expected Behavior

  1. Parent page loads with iframe
  2. Connection establishes automatically
  3. Click "Send Message to Child"
  4. User data appears in response div
  5. Console shows all communication events

Common Issues

Issue: Connection Timeout

Symptom: "Failed to connect to iframe" error

Solution: Ensure the iframe has fully loaded before connecting. Use the load event listener as shown in the example.

For more timeout troubleshooting, see Timeout Errors.

Issue: Origin Mismatch

Symptom: Messages not received, console shows origin errors

Solution: Verify allowedOrigins matches the actual origin. Use window.location.origin for same-origin communication.

For origin troubleshooting, see Origin Mismatch Errors.

Issue: TypeScript Errors

Symptom: Type errors when using send() or on()

Solution: Define proper interfaces for request and response types. See TypeScript examples in this guide.


Next Steps

Now that you understand the basics:

  1. Add Schema Validation - Validate message payloads with JSON Schema

  2. Implement Error Recovery - Add retry logic and fallback handling

  3. Monitor Connection Health - Use heartbeat monitoring

  4. Scale to Multiple Windows - Communicate with multiple iframes

  5. Learn Advanced Patterns - Request-response, state sync, broadcasting


API Reference:

Guides:

Patterns:


Previous: Examples Overview Next: Advanced Examples Back to: Examples

Released under the MIT License.