> ## Documentation Index
> Fetch the complete documentation index at: https://docs.praxis-ai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Digital Twin Loading Sequence

> Step-by-step guide to the SDK initialization lifecycle — from script injection to conversation start.

## Overview

Loading a Digital Twin follows a strict sequence: inject the SDK script, initialize with credentials, poll for readiness, subscribe to events, then start the conversation. Skipping or reordering these steps causes silent failures.

This guide walks through each phase using the [sdk-sample.js](https://pria.praxislxp.com/integration-samples/sdk-sample.html) reference implementation.

```
DOM Ready
  └─ init()
       └─ Inject <script src="pria-sdk.js">
            └─ onload → priasdk(url, user, publicId, '', display)
                 └─ waitForReady() [poll 100ms]
                      └─ isReady() === true
                           └─ setupPria()
                                ├─ subscribe(handleResponse)
                                └─ button.click →
                                     ├─ setVisible(false)
                                     ├─ showLoading()
                                     ├─ setTimeout(5s fallback)
                                     └─ send({ command: 'convo.start' })
                                          │
                                    ┌─────┴──────┐
                                    ▼              ▼
                              pria-response    pria-error
                              ├─ hideLoading   ├─ reset timer
                              └─ setVisible    └─ retry convo.start
                                   (true)          (loops until success
                                                    or timeout)
```

***

## Phase 1: Script Injection

Wait for the DOM, then dynamically load the SDK script from your Pria server.

```javascript theme={null}
function init() {
    // Guard against double-initialization
    if (typeof window.priasdk === 'function') return;

    const script = document.createElement('script');
    script.src = 'https://pria.praxislxp.com/pria-sdk.js';
    script.async = true;

    script.onload = () => {
        // Phase 2 starts here
    };

    document.body.appendChild(script);
}

// Trigger on DOM ready
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
} else {
    init();
}
```

<Tip>
  You can inject optional CSS **before** loading the script to constrain the Pria UI dimensions:

  ```javascript theme={null}
  const style = document.createElement('style');
  style.textContent = '#pria_ui { max-width: 380px!important; max-height: 480px!important; }';
  document.head.appendChild(style);
  ```
</Tip>

***

## Phase 2: SDK Initialization

Inside the `script.onload` callback, call `window.priasdk()` with five arguments. This creates the iframe, establishes the Socket.io connection, and performs the REGISTER handshake.

```javascript theme={null}
script.onload = () => {
    // 1. User identity (persistent via cookies)
    const userConfig = {
        email: 'user123@example.com',
        profilename: 'User 123',
        lticontextid: document.URL
    };

    // 2. Display options
    const displayOptions = {
        buttonWidth: '320px',
        buttonPosition: 'initial',
        buttonContainerId: 'my-button-container',
        openOnLoad: false   // Keep hidden until conversation starts
    };

    // 3. Initialize — PUBLIC_ID is your Digital Twin's public instance ID
    window.priasdk(
        'https://pria.praxislxp.com',  // Base URL
        userConfig,                      // User identity
        PUBLIC_ID,                       // Digital Twin public ID
        '',                              // Reserved (pass empty string)
        displayOptions                   // Display configuration
    );

    // 4. Begin polling for readiness
    waitForReady(setupPria);
};
```

<Warning>
  **`openOnLoad: false` is critical.** Without it, the user sees a blank chat panel while the backend is still connecting. The UI should only appear after `convo.start` succeeds.
</Warning>

### Persistent User Identity

The reference implementation uses cookies to maintain a stable user identity across visits, which preserves conversation history:

```javascript theme={null}
function getUserIdentity() {
    const now = Math.floor(Date.now() / 1000);
    let userId = getCookie('pria-web-user');
    if (!userId) {
        setCookie('pria-web-user', now, 365);
        userId = now;
    }
    return {
        email: `${userId}@your-domain.com`,
        profilename: `${userId} WebUser`,
        lticontextid: document.URL
    };
}
```

***

## Phase 3: Poll for Readiness

The SDK needs time to load the iframe, connect via Socket.io, and complete the REGISTER handshake. Poll `isReady()` until it returns `true`.

```javascript theme={null}
function waitForReady(callback) {
    const timer = setInterval(() => {
        if (window.pria && window.pria.isReady && window.pria.isReady()) {
            clearInterval(timer);
            callback();
        }
    }, 100);  // Check every 100ms
}
```

<Note>
  `isReady()` returns `true` only after the Socket.io connection is established and the Digital Twin configuration is loaded. **Never send commands before this returns `true`.**
</Note>

***

## Phase 4: Subscribe and Setup

Once the SDK is ready, subscribe to response events and wire up your UI trigger.

```javascript theme={null}
function setupPria() {
    // 1. Subscribe to all Pria responses
    window.pria.subscribe(handlePriaResponse);

    // 2. Clean up on page unload
    window.addEventListener('beforeunload', () => {
        if (window.pria && typeof window.pria.destroy === 'function') {
            window.pria.destroy();
        }
    });

    // 3. Wire the launch button
    const button = document.getElementById('my-launch-button');
    if (button) {
        button.addEventListener('click', () => {
            window.pria.setVisible(false);    // Hide UI during connection
            showLoading();                     // Show spinner overlay
            startResponseTimer();              // Safety timeout
            window.pria.send({ command: 'convo.start' });
        });
    }
}
```

The button click triggers four actions in sequence:

1. **Hide the Pria UI** — prevents showing an empty chat window
2. **Show a loading overlay** — gives the user visual feedback
3. **Send `convo.start`** — tells the backend to initialize the AI session

***

## Phase 5: Response Handling with Retry

The response handler manages two scenarios: successful start and transient connection errors.

```javascript theme={null}
const RESPONSE_TIMEOUT = 5000; // 5 seconds
let responseTimer = null;

function startResponseTimer() {
    clearResponseTimer();
    responseTimer = setTimeout(() => {
        hideLoading();
        window.pria.setVisible(true);  // Show UI as fallback
    }, RESPONSE_TIMEOUT);
}

function clearResponseTimer() {
    if (responseTimer) {
        clearTimeout(responseTimer);
        responseTimer = null;
    }
}

function handlePriaResponse(response) {
    const command = response?.response?.command;
    const type = response?.type;
    const content = response?.response?.content;
    const isError = response?.response?.isError;

    // SUCCESS — conversation started
    if (type === 'pria-response' && command === 'convo.start') {
        clearResponseTimer();
        hideLoading();
        window.pria.setVisible(true);
        return;
    }

    // TRANSIENT ERROR — backend not ready yet, retry
    if (type === 'pria-error' && command === 'convo.start' && isError) {
        if (content && (content.includes('NO-SESSION') || content.includes('not connected'))) {
            // Reset the timeout to give more time
            startResponseTimer();
            // Retry immediately
            window.pria.send({ command: 'convo.start' });
            return;
        }
    }
}
```

### How the Retry Loop Works

| Event                              | Action                                          |
| ---------------------------------- | ----------------------------------------------- |
| `convo.start` sent                 | 5-second timeout starts                         |
| `pria-response` with `convo.start` | Timer cleared, loading hidden, UI shown         |
| `pria-error` with `NO-SESSION`     | Timer reset (another 5s), `convo.start` retried |
| Timeout fires (no response)        | Loading hidden, UI shown as fallback            |

<Tip>
  The `NO-SESSION` error means the Socket.io connection is established but the AI backend session hasn't initialized yet. This is a normal race condition — retrying resolves it within 1-2 attempts.
</Tip>

***

## Loading Overlay

Provide visual feedback while the Digital Twin connects. The reference implementation uses a full-screen overlay with a spinner:

```javascript theme={null}
function createLoadingOverlay() {
    const overlay = document.createElement('div');
    overlay.id = 'pria-loading-overlay';
    overlay.innerHTML = `
        <div class="pria-loading-content">
            <div class="pria-loading-spinner"></div>
            <span>Connecting to your Digital Twin...</span>
        </div>
    `;
    overlay.style.cssText = `
        position: fixed; top: 0; left: 0; right: 0; bottom: 0;
        background: rgba(0, 0, 0, 0.5);
        display: flex; align-items: center; justify-content: center;
        z-index: 99999;
    `;
    return overlay;
}
```

```css theme={null}
.pria-loading-content {
    background: white;
    padding: 24px 32px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    gap: 16px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.pria-loading-spinner {
    width: 24px; height: 24px;
    border: 3px solid #e0e0e0;
    border-top-color: #3B82F6;
    border-radius: 50%;
    animation: pria-spin 1s linear infinite;
}
@keyframes pria-spin { to { transform: rotate(360deg); } }
```

***

## Complete Minimal Example

Putting it all together — a minimal working integration:

```html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Digital Twin</title>
</head>
<body>
    <button id="start-btn">Talk to Digital Twin</button>
    <div id="pria-btn-container"></div>

    <script>
    (function() {
        const BASE_URL = 'https://pria.praxislxp.com';
        const PUBLIC_ID = 'your-public-id-here';
        const TIMEOUT = 5000;
        let timer = null;

        function startTimer() {
            clearTimer();
            timer = setTimeout(() => {
                window.pria.setVisible(true);
            }, TIMEOUT);
        }
        function clearTimer() {
            if (timer) { clearTimeout(timer); timer = null; }
        }

        function handleResponse(res) {
            if (res.type === 'pria-response' && res.response?.command === 'convo.start') {
                clearTimer();
                window.pria.setVisible(true);
            }
            if (res.type === 'pria-error' && res.response?.command === 'convo.start') {
                if (res.response?.content?.includes('NO-SESSION') ||
                    res.response?.content?.includes('not connected')) {
                    startTimer();
                    window.pria.send({ command: 'convo.start' });
                }
            }
        }

        function setup() {
            window.pria.subscribe(handleResponse);
            document.getElementById('start-btn').addEventListener('click', () => {
                window.pria.setVisible(false);
                startTimer();
                window.pria.send({ command: 'convo.start' });
            });
        }

        function waitReady(cb) {
            const t = setInterval(() => {
                if (window.pria?.isReady?.()) { clearInterval(t); cb(); }
            }, 100);
        }

        // Boot
        const s = document.createElement('script');
        s.src = BASE_URL + '/pria-sdk.js';
        s.onload = () => {
            window.priasdk(BASE_URL, {
                email: 'guest@example.com',
                profilename: 'Guest'
            }, PUBLIC_ID, '', {
                buttonContainerId: 'pria-btn-container',
                openOnLoad: false
            });
            waitReady(setup);
        };
        document.body.appendChild(s);
    })();
    </script>
</body>
</html>
```

***

## Common Pitfalls

<Warning>
  **Sending commands before `isReady()`**

  If you call `pria.send()` before the SDK is ready, the command is silently dropped. Always gate on `isReady()`.
</Warning>

<Warning>
  **Missing `subscribe()` before `convo.start`**

  If you send `convo.start` without subscribing first, you won't receive the success response and your UI will never transition from the loading state.
</Warning>

<Warning>
  **Not handling page unload**

  Call `pria.destroy()` on `beforeunload` to cleanly disconnect the Socket.io session. Without this, the server may hold stale sessions.
</Warning>
