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

# Best Practices

> Optimization tips and best practices for webview authentication

## Security Best Practices

### API Key Management

<Warning>
  **Critical**: Never expose your API key in client-side code. Always call the Doshi API from your backend.
</Warning>

```typescript theme={null}
// ✅ Correct - Backend handles API key
// backend/api/auth.ts
app.post('/generate-doshi-token', async (req, res) => {
  const response = await fetch('https://api.doshi.app/client/auth/token', {
    headers: {
      'Authorization': `Bearer ${process.env.DOSHI_API_KEY}`,
      'Access-Type': 'client' 
    }
  });
  // ...
});

// ❌ Never do this - API key exposed
// frontend/app.tsx
const response = await fetch('https://api.doshi.app/client/auth/token', {
  headers: {
    'Authorization': 'Bearer sk_live_abc123...', // EXPOSED!
    'Access-Type': 'client' 
  }
});
```

### Environment Variables

Store your API key securely:

```bash theme={null}
# .env
DOSHI_API_KEY=your_api_key_here
DOSHI_API_URL=https://api.doshi.app
```

```typescript theme={null}
// config.ts
export const config = {
  doshiApiKey: process.env.DOSHI_API_KEY,
  doshiApiUrl: process.env.DOSHI_API_URL,
};

// Never commit .env files
// Add to .gitignore:
// .env
// .env.local
```

### Always Use HTTPS

```javascript theme={null}
// ✅ Correct
const webviewUrl = "https://embed.v2.doshi.app";
const apiUrl = "https://api.doshi.app";

// ❌ Never use HTTP in production
const insecureUrl = "http://embed.v2.doshi.app";
```

### Implement Proper Origin Checks

```javascript theme={null}
// For postMessage
window.addEventListener("message", (event) => {
  // ✅ Always verify origin in production
  if (event.origin !== 'https://embed.v2.doshi.app') {
    console.warn('Rejected message from:', event.origin);
    return;
  }
  
  // Process message
});
```

### Never Use Wildcards in Production

```javascript theme={null}
// ❌ Development/testing only
webview.contentWindow.postMessage(data, "*");

// ✅ Production - always specify exact origin
webview.contentWindow.postMessage(data, "https://embed.v2.doshi.app");
```

### Validate All Data

```typescript theme={null}
function validateAuthData(data: any): data is AuthData {
  if (!data || typeof data !== 'object') {
    throw new Error('Invalid data format');
  }

  if (!data.token || typeof data.token !== 'string') {
    throw new Error('Missing or invalid token');
  }

  if (data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
    throw new Error('Invalid email format');
  }

  return true;
}

// Use in your code
try {
  validateAuthData(authData);
  // Proceed with authentication
} catch (error) {
  console.error('Validation failed:', error);
  // Handle error
}
```

## Performance Best Practices

### Minimize API Calls

<Tip>
  Cache the custom token temporarily if you need to render multiple iframes for the same user session
</Tip>

```typescript theme={null}
// Token cache with expiration
class TokenCache {
  private cache = new Map<string, { token: string; expires: number }>();
  private readonly TTL = 5 * 60 * 1000; // 5 minutes

  async getToken(userId: string): Promise<string> {
    const cached = this.cache.get(userId);
    
    if (cached && cached.expires > Date.now()) {
      return cached.token;
    }

    // Fetch new token
    const token = await this.fetchNewToken(userId);
    
    this.cache.set(userId, {
      token,
      expires: Date.now() + this.TTL
    });

    return token;
  }

  private async fetchNewToken(userId: string): Promise<string> {
    // Call your backend
    const response = await fetch('/api/generate-doshi-token', {
      method: 'POST',
      body: JSON.stringify({ userId })
    });
    const data = await response.json();
    return data.token;
  }
}

const tokenCache = new TokenCache();
```

### Optimize Message Size

```javascript theme={null}
// ✅ Send all data in a single message
const authData = {
  token: user.token,
  email: user.email,
  segment: user.segment,
  branchId: user.branchId,
  type: "AUTH"
};
webview.contentWindow.postMessage(JSON.stringify(authData), origin);

// ❌ Avoid multiple sequential messages
webview.contentWindow.postMessage(JSON.stringify({ token }), origin);
webview.contentWindow.postMessage(JSON.stringify({ email }), origin);
webview.contentWindow.postMessage(JSON.stringify({ segment }), origin);
```

### Implement Proper Cleanup

```typescript theme={null}
useEffect(() => {
  const handleMessage = (event: MessageEvent) => {
    // Handle message
  };

  window.addEventListener("message", handleMessage);
  
  // ✅ Always cleanup
  return () => {
    window.removeEventListener("message", handleMessage);
  };
}, []);
```

### Lazy Load the Iframe

```tsx theme={null}
import React, { useState, useEffect } from 'react';

const LazyWebview: React.FC = () => {
  const [shouldLoad, setShouldLoad] = useState(false);

  useEffect(() => {
    // Load iframe only when needed
    const timer = setTimeout(() => {
      setShouldLoad(true);
    }, 1000);

    return () => clearTimeout(timer);
  }, []);

  if (!shouldLoad) {
    return <div>Preparing...</div>;
  }

  return <WebviewComponent />;
};
```

### Preconnect to Doshi Domain

```html theme={null}
<!-- Add to <head> for faster iframe loading -->
<link rel="preconnect" href="https://embed.v2.doshi.app">
<link rel="dns-prefetch" href="https://embed.v2.doshi.app">
```

## Mobile App Integration

When embedding Doshi Frontend in a mobile app (iOS/Android WebView), there are special considerations:

### Disable Zoom

<Warning>
  Zooming should be disabled on mobile to prevent layout issues and ensure consistent user experience.
</Warning>

**For Web (HTML Meta Tag):**

```html theme={null}
<!-- Add to your <head> tag -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
```

**For React:**

```tsx theme={null}
// Add to your main HTML file or use react-helmet
import { Helmet } from 'react-helmet';

function App() {
  return (
    <>
      <Helmet>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
      </Helmet>
      <DoshiEmbed />
    </>
  );
}
```

**For iOS (Swift/UIWebView):**

```swift theme={null}
// Disable pinch to zoom
webView.scrollView.isScrollEnabled = false
webView.scrollView.bounces = false

// For WKWebView
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.scrollView.isScrollEnabled = false

// Inject viewport meta tag
let viewportScript = """
var meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
document.getElementsByTagName('head')[0].appendChild(meta);
"""
let userScript = WKUserScript(source: viewportScript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(userScript)
```

**For Android (WebView):**

```kotlin theme={null}
// In your Activity or Fragment
webView.settings.apply {
    setSupportZoom(false)
    builtInZoomControls = false
    displayZoomControls = false
}

// Load HTML with viewport meta tag
val htmlContent = """
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
    <iframe src="https://embed.v2.doshi.app?token=YOUR_TOKEN" 
            style="width:100%; height:100vh; border:none;">
    </iframe>
</body>
</html>
"""
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null)
```

### Handle Keyboard in Mobile Apps

<Warning>
  When the keyboard opens in mobile apps, it can squeeze the iframe height and make content not visible. Handle this in your app environment.
</Warning>

The Doshi Frontend is designed to work within mobile WebViews, but your app needs to handle keyboard behavior correctly.

**iOS (Swift) - Adjust WebView when Keyboard Opens:**

```swift theme={null}
class ViewController: UIViewController {
    var webView: WKWebView!
    var webViewBottomConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Setup WebView with constraints
        webView = WKWebView()
        view.addSubview(webView)
        webView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
        
        webViewBottomConstraint = webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        webViewBottomConstraint.isActive = true
        
        // Observe keyboard notifications
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow),
            name: UIResponder.keyboardWillShowNotification,
            object: nil
        )
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillHide),
            name: UIResponder.keyboardWillHideNotification,
            object: nil
        )
    }
    
    @objc func keyboardWillShow(_ notification: Notification) {
        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
            return
        }
        
        // Adjust WebView bottom constraint
        webViewBottomConstraint.constant = -keyboardFrame.height
        
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded()
        }
    }
    
    @objc func keyboardWillHide(_ notification: Notification) {
        // Reset WebView bottom constraint
        webViewBottomConstraint.constant = 0
        
        UIView.animate(withDuration: 0.3) {
            self.view.layoutIfNeeded()
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}
```

**Android (Kotlin) - Adjust WebView when Keyboard Opens:**

```kotlin theme={null}
class WebViewActivity : AppCompatActivity() {
    private lateinit var webView: WebView
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Set window to adjust resize when keyboard appears
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
        
        setContentView(R.layout.activity_webview)
        
        webView = findViewById(R.id.webView)
        webView.settings.apply {
            javaScriptEnabled = true
            domStorageEnabled = true
        }
        
        // Optional: Listen to keyboard visibility
        val rootView = findViewById<View>(android.R.id.content)
        rootView.viewTreeObserver.addOnGlobalLayoutListener {
            val heightDiff = rootView.rootView.height - rootView.height
            if (heightDiff > 200) {
                // Keyboard is visible
                onKeyboardVisible(heightDiff)
            } else {
                // Keyboard is hidden
                onKeyboardHidden()
            }
        }
    }
    
    private fun onKeyboardVisible(keyboardHeight: Int) {
        // Adjust WebView or scroll to focused input
        webView.evaluateJavascript("""
            (function() {
                var activeElement = document.activeElement;
                if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
                    activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                }
            })();
        """, null)
    }
    
    private fun onKeyboardHidden() {
        // Reset any adjustments if needed
    }
}
```

**React Native (if applicable):**

```javascript theme={null}
import { KeyboardAvoidingView, Platform } from 'react-native';
import { WebView } from 'react-native-webview';

function DoshiWebView() {
  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      style={{ flex: 1 }}
    >
      <WebView
        source={{ uri: 'https://embed.v2.doshi.app?token=YOUR_TOKEN' }}
        style={{ flex: 1 }}
        scrollEnabled={false}
        scalesPageToFit={false}
      />
    </KeyboardAvoidingView>
  );
}
```

**In Manifest (Android):**

```xml theme={null}
<!-- In AndroidManifest.xml -->
<activity
    android:name=".WebViewActivity"
    android:windowSoftInputMode="adjustResize">
</activity>
```

### Best Practices for Mobile

<Check>
  Always disable zoom for consistent experience
</Check>

<Check>
  Handle keyboard visibility in your app wrapper
</Check>

<Check>
  Use `adjustResize` on Android to resize layout when keyboard appears
</Check>

<Check>
  Use constraint-based layout on iOS to adjust for keyboard
</Check>

<Check>
  Test on various screen sizes and keyboard types
</Check>

<Check>
  Ensure input fields remain visible when keyboard is open
</Check>

## Handling Link Clicks

<Warning>
  Doshi Frontend cannot handle external link clicks that open in new windows/popups due to security restrictions.
</Warning>

### Why Popups Are Blocked

For security reasons, Doshi Frontend does not support opening links in popups or new windows from within the iframe. This prevents:

* Phishing attacks
* Unauthorized redirects
* Cross-site scripting vulnerabilities

### postMessage Callback for Link Clicks

When users click on external links within Doshi Frontend, the iframe will send a postMessage to your parent application. You must handle these messages and open links in your environment.

**Listen for Link Click Messages:**

```typescript theme={null}
window.addEventListener('message', (event) => {
  // Verify origin
  if (event.origin !== 'https://embed.v2.doshi.app') return;
  
  try {
    const data = typeof event.data === 'string' 
      ? JSON.parse(event.data) 
      : event.data;
    
    // Handle external link clicks
    if (data.type === 'EXTERNAL_LINK') {
      const { url, title } = data;
      
      // Open in same tab
      window.location.href = url;
      
      // Or open in new tab
      // window.open(url, '_blank');
      
      // Or handle in your app's navigation
      // navigateToExternal(url);
    }
  } catch (error) {
    console.error('Error handling message:', error);
  }
});
```

**iOS (Swift) - Handle Link Callbacks:**

```swift theme={null}
// Add WKScriptMessageHandler
extension ViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, 
                              didReceive message: WKScriptMessage) {
        if message.name == "linkHandler" {
            if let body = message.body as? [String: Any],
               let url = body["url"] as? String {
                // Handle the link
                if let linkUrl = URL(string: url) {
                    // Option 1: Open in Safari
                    UIApplication.shared.open(linkUrl)
                    
                    // Option 2: Open in SFSafariViewController
                    let safariVC = SFSafariViewController(url: linkUrl)
                    present(safariVC, animated: true)
                }
            }
        }
    }
}

// Setup message handler
override func viewDidLoad() {
    super.viewDidLoad()
    
    let contentController = webView.configuration.userContentController
    contentController.add(self, name: "linkHandler")
    
    // Inject message handler script
    let script = """
    window.addEventListener('message', function(event) {
        if (event.data.type === 'EXTERNAL_LINK') {
            window.webkit.messageHandlers.linkHandler.postMessage({
                url: event.data.url,
                title: event.data.title
            });
        }
    });
    """
    
    let userScript = WKUserScript(
        source: script,
        injectionTime: .atDocumentEnd,
        forMainFrameOnly: false
    )
    contentController.addUserScript(userScript)
}
```

**Android (Kotlin) - Handle Link Callbacks:**

```kotlin theme={null}
webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
        val url = request?.url.toString()
        
        // If it's an external link, handle it in your app
        if (url.startsWith("http") && !url.contains("embed.v2.doshi.app")) {
            // Option 1: Open in external browser
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
            startActivity(intent)
            return true
            
            // Option 2: Open in Chrome Custom Tabs
            val builder = CustomTabsIntent.Builder()
            val customTabsIntent = builder.build()
            customTabsIntent.launchUrl(this@WebViewActivity, Uri.parse(url))
            return true
        }
        
        return false
    }
}

// Add JavaScript interface for postMessage
webView.addJavascriptInterface(object {
    @JavascriptInterface
    fun handleExternalLink(url: String, title: String) {
        runOnUiThread {
            // Handle the link
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
            startActivity(intent)
        }
    }
}, "AndroidInterface")

// Inject message listener
webView.evaluateJavascript("""
    window.addEventListener('message', function(event) {
        if (event.data.type === 'EXTERNAL_LINK') {
            AndroidInterface.handleExternalLink(event.data.url, event.data.title);
        }
    });
""", null)
```

**React - Handle Link Callbacks:**

```tsx theme={null}
import React, { useEffect } from 'react';

function DoshiEmbed() {
  useEffect(() => {
    const handleMessage = (event: MessageEvent) => {
      if (event.origin !== 'https://embed.v2.doshi.app') return;
      
      try {
        const data = typeof event.data === 'string'
          ? JSON.parse(event.data)
          : event.data;
        
        if (data.type === 'EXTERNAL_LINK') {
          // Option 1: Navigate in same window
          window.location.href = data.url;
          
          // Option 2: Open in new tab
          // window.open(data.url, '_blank', 'noopener,noreferrer');
          
          // Option 3: Use React Router (if applicable)
          // navigate(`/external?url=${encodeURIComponent(data.url)}`);
          
          // Option 4: Show confirmation dialog
          // if (confirm(`Open ${data.title || 'link'}?`)) {
          //   window.open(data.url, '_blank');
          // }
        }
      } catch (error) {
        console.error('Error handling link message:', error);
      }
    };
    
    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, []);
  
  return (
    <iframe
      src="https://embed.v2.doshi.app"
      className="w-full h-screen"
      frameBorder="0"
      allowFullScreen
    />
  );
}
```

### Message Format

When a user clicks an external link, Doshi Frontend sends:

```typescript theme={null}
{
  type: 'EXTERNAL_LINK',
  url: 'https://example.com/article',
  title: 'Article Title', // Optional
  timestamp: 1234567890
}
```

### Security Considerations

<Check>
  Always verify `event.origin` before processing messages
</Check>

<Check>
  Validate URLs before opening (check for malicious protocols)
</Check>

<Check>
  Use `noopener,noreferrer` when opening links in new tabs
</Check>

<Check>
  Consider showing user confirmation for external links
</Check>

<Check>
  Log external link clicks for security auditing
</Check>

### URL Validation Example

```typescript theme={null}
function isValidUrl(url: string): boolean {
  try {
    const urlObj = new URL(url);
    
    // Only allow http and https protocols
    if (!['http:', 'https:'].includes(urlObj.protocol)) {
      return false;
    }
    
    // Optional: Whitelist or blacklist domains
    const blockedDomains = ['malicious-site.com', 'phishing-site.com'];
    if (blockedDomains.some(domain => urlObj.hostname.includes(domain))) {
      return false;
    }
    
    return true;
  } catch {
    return false;
  }
}

// Use in message handler
if (data.type === 'EXTERNAL_LINK') {
  if (isValidUrl(data.url)) {
    window.open(data.url, '_blank', 'noopener,noreferrer');
  } else {
    console.warn('Invalid or blocked URL:', data.url);
  }
}
```

## Error Handling Best Practices

### Comprehensive Error Handling

```typescript theme={null}
const handleAuthentication = async () => {
  try {
    // Attempt to get token
    const token = await fetchToken();
    
    if (!token) {
      throw new Error('No token received');
    }

    // Attempt to send to iframe
    await sendAuthToIframe(token);
    
  } catch (error) {
    // Log error
    console.error('Authentication error:', error);
    
    // Send to monitoring service
    if (window.analytics) {
      window.analytics.track('Authentication Failed', {
        error: error.message,
        timestamp: new Date().toISOString()
      });
    }
    
    // Show user-friendly message
    setError('Unable to authenticate. Please try again.');
    
    // Optionally retry
    if (retryCount < MAX_RETRIES) {
      setTimeout(() => {
        setRetryCount(retryCount + 1);
        handleAuthentication();
      }, RETRY_DELAY);
    }
  }
};
```

### User Feedback

```tsx theme={null}
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [errorMessage, setErrorMessage] = useState<string | null>(null);

// Show appropriate UI based on status
return (
  <div>
    {status === 'loading' && (
      <div className="loading-state">
        <Spinner />
        <p>Authenticating...</p>
      </div>
    )}
    
    {status === 'error' && (
      <div className="error-state">
        <ErrorIcon />
        <p>{errorMessage}</p>
        <button onClick={retry}>Retry</button>
      </div>
    )}
    
    {status === 'success' && (
      <iframe src={webviewUrl} />
    )}
  </div>
);
```

### Timeout Handling

```typescript theme={null}
const AUTH_TIMEOUT = 10000; // 10 seconds

const authenticateWithTimeout = () => {
  return Promise.race([
    authenticate(),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Authentication timeout')), AUTH_TIMEOUT)
    )
  ]);
};

try {
  await authenticateWithTimeout();
} catch (error) {
  if (error.message === 'Authentication timeout') {
    setError('Authentication is taking longer than expected. Please check your connection.');
  }
}
```

## Method Selection Guide

### When to Use postMessage

<Check>
  Handling sensitive authentication data
</Check>

<Check>
  Need for real-time, bidirectional communication
</Check>

<Check>
  Production environments
</Check>

<Check>
  Multiple authentication steps
</Check>

<Check>
  Maximum security requirements
</Check>

<Check>
  Complex 2FA flows
</Check>

```typescript theme={null}
// Use postMessage for production
const ProductionWebview = ({ userData }) => {
  // postMessage implementation
  // More secure, no data in URLs
};
```

### When to Use Query Parameters

<Check>
  Simple, one-time authentication
</Check>

<Check>
  Rapid prototyping and development
</Check>

<Check>
  Need for easier debugging
</Check>

<Check>
  Simpler implementation requirements
</Check>

<Check>
  Quick demos or proof of concepts
</Check>

```typescript theme={null}
// Use query params for development/testing
const DevelopmentWebview = ({ userData }) => {
  const url = `https://embed.v2.doshi.app?token=${token}`;
  return <iframe src={url} />;
};
```

## Code Organization

### Separation of Concerns

```typescript theme={null}
// services/doshi-auth.service.ts
export class DoshiAuthService {
  private apiUrl: string;
  private embedUrl: string;

  constructor(config: Config) {
    this.apiUrl = config.apiUrl;
    this.embedUrl = config.embedUrl;
  }

  async getAuthToken(userData: UserData): Promise<string> {
    const response = await fetch(`${this.apiUrl}/generate-token`, {
      method: 'POST',
      body: JSON.stringify(userData)
    });
    const data = await response.json();
    return data.token;
  }

  buildEmbedUrl(token: string, params: AuthParams): string {
    const urlParams = new URLSearchParams({ token, ...params });
    return `${this.embedUrl}?${urlParams.toString()}`;
  }
}

// components/DoshiEmbed.tsx
export const DoshiEmbed: React.FC<Props> = ({ userData }) => {
  const authService = useDoshiAuthService();
  // Component logic
};
```

### Configuration Management

```typescript theme={null}
// config/doshi.config.ts
export const doshiConfig = {
  development: {
    apiUrl: 'https://sandbox.api.doshi.app',
    embedUrl: 'https://staging-embed.doshi.app',
    enableDebugLogs: true,
    strictOriginCheck: false
  },
  production: {
    apiUrl: 'https://api.doshi.app',
    embedUrl: 'https://embed.v2.doshi.app',
    enableDebugLogs: false,
    strictOriginCheck: true
  }
};

export const getConfig = () => {
  const env = process.env.NODE_ENV || 'development';
  return doshiConfig[env];
};
```

## Styling Best Practices

### Responsive Container

```css theme={null}
.webview-container {
  /* Full viewport height */
  width: 100%;
  height: 100vh;
  
  /* Prevent overflow */
  overflow: hidden;
  
  /* Optional: Add subtle border */
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}

/* Mobile responsive */
@media (max-width: 768px) {
  .webview-container {
    /* Adjust for mobile viewport */
    height: calc(100vh - 60px); /* Account for mobile browser UI */
  }
}

/* Tablet */
@media (min-width: 769px) and (max-width: 1024px) {
  .webview-container {
    height: calc(100vh - 80px);
  }
}
```

### Loading States

```tsx theme={null}
const LoadingOverlay: React.FC = () => (
  <div className="absolute inset-0 bg-white bg-opacity-90 flex items-center justify-center z-50">
    <div className="text-center">
      <div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-solid border-blue-600 border-r-transparent"></div>
      <p className="mt-4 text-gray-700">Loading Doshi...</p>
    </div>
  </div>
);

// Usage
<div className="relative webview-container">
  {isLoading && <LoadingOverlay />}
  <iframe src={url} onLoad={() => setIsLoading(false)} />
</div>
```

### Full-Screen Support

```css theme={null}
.webview-container iframe {
  width: 100%;
  height: 100%;
  border: none;
  display: block;
}

/* Allow fullscreen */
.webview-container iframe:fullscreen {
  width: 100vw;
  height: 100vh;
}
```

## Testing Best Practices

### Environment-Specific Testing

```typescript theme={null}
describe('Doshi Authentication', () => {
  beforeEach(() => {
    // Use sandbox environment for testing
    process.env.DOSHI_API_URL = 'https://sandbox.api.doshi.app';
  });

  it('should generate auth token', async () => {
    const token = await generateAuthToken({
      email: 'test@example.com'
    });
    
    expect(token).toBeDefined();
    expect(typeof token).toBe('string');
  });

  it('should handle authentication errors', async () => {
    // Test error handling
    await expect(
      generateAuthToken({ email: 'invalid' })
    ).rejects.toThrow();
  });
});
```

### Mock API Responses

```typescript theme={null}
// __mocks__/doshi-api.ts
export const mockDoshiApi = {
  generateToken: jest.fn().mockResolvedValue({
    token: 'mock_token_123',
    userId: 'user_123',
    isNewUser: false
  }),
  
  mockError: jest.fn().mockRejectedValue(
    new Error('API Error')
  )
};
```

### Integration Testing

```typescript theme={null}
import { render, screen, waitFor } from '@testing-library/react';

test('displays webview after successful authentication', async () => {
  render(<DoshiEmbed userEmail="test@example.com" />);
  
  // Should show loading initially
  expect(screen.getByText('Loading...')).toBeInTheDocument();
  
  // Should show iframe after auth
  await waitFor(() => {
    expect(screen.getByTitle('Doshi Embed')).toBeInTheDocument();
  });
});
```

## Monitoring and Logging

### Track Key Events

```typescript theme={null}
// Track authentication flow
const trackAuthEvent = (event: string, metadata?: any) => {
  if (window.analytics) {
    window.analytics.track(event, {
      ...metadata,
      timestamp: new Date().toISOString(),
      environment: process.env.NODE_ENV
    });
  }
};

// Usage
trackAuthEvent('Auth Token Requested', { userId: user.id });
trackAuthEvent('Auth Token Received', { userId: user.id });
trackAuthEvent('Iframe Loaded', { userId: user.id });
```

### Error Monitoring

```typescript theme={null}
// Send errors to monitoring service
const logError = (error: Error, context?: any) => {
  console.error('Doshi Auth Error:', error);
  
  // Send to error tracking service (e.g., Sentry)
  if (window.Sentry) {
    window.Sentry.captureException(error, {
      extra: context
    });
  }
};

// Usage
try {
  await authenticate();
} catch (error) {
  logError(error, {
    userId: user.id,
    action: 'authentication'
  });
}
```

### Performance Monitoring

```typescript theme={null}
// Measure authentication performance
const measureAuthPerformance = async () => {
  const startTime = performance.now();
  
  try {
    await authenticate();
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    // Log performance metric
    console.log(`Authentication took ${duration}ms`);
    
    // Send to analytics
    if (window.analytics) {
      window.analytics.track('Auth Performance', {
        duration,
        status: 'success'
      });
    }
  } catch (error) {
    const endTime = performance.now();
    const duration = endTime - startTime;
    
    if (window.analytics) {
      window.analytics.track('Auth Performance', {
        duration,
        status: 'error'
      });
    }
  }
};
```

## Production Checklist

<Check>
  API key stored securely in environment variables
</Check>

<Check>
  API calls made from backend only
</Check>

<Check>
  HTTPS used for all URLs
</Check>

<Check>
  Origin verification enabled for postMessage
</Check>

<Check>
  No "\*" wildcards in production
</Check>

<Check>
  Comprehensive error handling implemented
</Check>

<Check>
  User feedback for all states (loading, error, success)
</Check>

<Check>
  Timeout handling for slow connections
</Check>

<Check>
  Proper cleanup of event listeners
</Check>

<Check>
  Error logging and monitoring configured
</Check>

<Check>
  Performance tracking in place
</Check>

<Check>
  Tested across multiple browsers
</Check>

<Check>
  Tested on mobile devices
</Check>

<Check>
  Responsive design implemented
</Check>

<Check>
  Accessibility considerations addressed
</Check>

## Next Steps

<CardGroup cols={2}>
  <Card title="Troubleshooting" icon="wrench" href="/webview/troubleshooting">
    Common issues and solutions
  </Card>

  <Card title="Security Guide" icon="shield" href="/webview/security">
    Review security considerations
  </Card>
</CardGroup>
