npm package: @myo-devs/react
Description: Official React SDK for the Myo Platform
Author: Myo
Latest version: 0.1.1

# Myo React SDK

Official React SDK for the [Myo Platform](https://myo-app.co) - Connect your users' personal data through a seamless widget experience.

## Installation

```bash
npm install @myo-devs/react
```

## Quick Start

The simplest way to add Myo to your app:

```tsx
import { MyoConnectButton, useMyoConnection } from '@myo-devs/react';

function App() {
  const { connection, setConnection, clearConnection } = useMyoConnection({
    storage: 'session',
  });

  if (connection) {
    return (
      <div>
        <p>Connected as {connection.userId}</p>
        <button onClick={clearConnection}>Disconnect</button>
      </div>
    );
  }

  return (
    <MyoConnectButton
      fetchToken={async () => {
        const res = await fetch('/api/link-token', { method: 'POST' });
        return (await res.json()).linkToken;
      }}
      onSuccess={(metadata) => setConnection(metadata.userId, metadata.provider)}
    >
      Connect Your Data
    </MyoConnectButton>
  );
}
```

## Features

- **One-click connection** - `MyoConnectButton` handles token fetching and widget opening
- **Custom button support** - `useMyoConnect` hook for using your own button UI
- **Connection persistence** - `useMyoConnection` hook for storing connection state
- **Low-level control** - `useMyoLink` hook for full customization
- **Modal & redirect modes** - Modal overlay (default) or popup window
- **Full TypeScript support** - Excellent IDE autocompletion
- **Secure PostMessage** - Origin-validated communication with widget

## Components & Hooks

### MyoConnectButton

The easiest way to add Myo connection to your app. Handles fetching the link token and opening the widget automatically.

```tsx
import { MyoConnectButton, useMyoConnection } from '@myo-devs/react';

function ConnectPage() {
  const { connection, setConnection, clearConnection } = useMyoConnection({
    storage: 'session',
  });

  if (connection) {
    return (
      <div>
        <p>Connected: {connection.userId} via {connection.provider}</p>
        <button onClick={clearConnection}>Disconnect</button>
      </div>
    );
  }

  return (
    <MyoConnectButton
      fetchToken={async () => {
        const res = await fetch('/api/link-token', { method: 'POST' });
        const data = await res.json();
        if (!res.ok) throw new Error(data.error);
        return data.linkToken;
      }}
      widgetUrl={process.env.NEXT_PUBLIC_MYO_WIDGET_URL}
      onSuccess={(metadata) => {
        setConnection(metadata.userId, metadata.provider);
      }}
      onError={(error) => {
        console.error('Connection failed:', error.error);
      }}
      className="connect-button"
    >
      Connect Your Data
    </MyoConnectButton>
  );
}
```

#### MyoConnectButton Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `fetchToken` | `() => Promise<string>` | Yes | Async function to fetch link token from your backend |
| `children` | `ReactNode` | Yes | Button content |
| `widgetUrl` | `string` | No | Custom widget URL (default: `https://link.myo-app.co`) |
| `onSuccess` | `(metadata) => void` | No | Called on successful connection |
| `onError` | `(error) => void` | No | Called when an error occurs |
| `onExit` | `(error, metadata) => void` | No | Called when user exits |
| `connectingText` | `string` | No | Text while connecting (default: `"Connecting..."`) |
| `className` | `string` | No | CSS classes for the button |
| `style` | `CSSProperties` | No | Inline styles for the button |
| `disabled` | `boolean` | No | Disable the button |

### useMyoConnect

Use this hook when you want to trigger the Myo modal from your own custom button or UI element. It handles token fetching and widget opening, giving you full control over the trigger UI.

```tsx
import { useMyoConnect, useMyoConnection } from '@myo-devs/react';

function CustomConnectButton() {
  const { setConnection } = useMyoConnection({ storage: 'session' });

  const { connect, isConnecting, isOpen, error } = useMyoConnect({
    fetchToken: async () => {
      const res = await fetch('/api/link-token', { method: 'POST' });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error);
      return data.linkToken;
    },
    widgetUrl: process.env.NEXT_PUBLIC_MYO_WIDGET_URL,
    onSuccess: (metadata) => {
      setConnection(metadata.userId, metadata.provider);
    },
    onError: (error) => {
      console.error('Connection failed:', error.error);
    },
  });

  return (
    <div>
      <MyFancyButton onClick={connect} disabled={isConnecting || isOpen}>
        {isConnecting ? 'Connecting...' : 'Link Your Data'}
      </MyFancyButton>
      {error && <p className="error">{error}</p>}
    </div>
  );
}
```

#### useMyoConnect Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `fetchToken` | `() => Promise<string>` | Yes | Async function to fetch link token from your backend |
| `widgetUrl` | `string` | No | Custom widget URL (default: `https://link.myo-app.co`) |
| `displayMode` | `'modal' \| 'redirect'` | No | Widget display mode (default: `'modal'`) |
| `onSuccess` | `(metadata) => void` | No | Called on successful connection |
| `onError` | `(error) => void` | No | Called when an error occurs |
| `onExit` | `(error, metadata) => void` | No | Called when user exits |
| `modalStyle` | `CSSProperties` | No | Override modal container styles |
| `iframeStyle` | `CSSProperties` | No | Override iframe styles |
| `backdropStyle` | `CSSProperties` | No | Override backdrop styles |

#### useMyoConnect Return Value

| Property | Type | Description |
|----------|------|-------------|
| `connect` | `() => Promise<void>` | Trigger the connection flow (fetch token + open widget) |
| `isConnecting` | `boolean` | Whether a token is being fetched |
| `isOpen` | `boolean` | Whether the widget is currently open |
| `ready` | `boolean` | Whether the widget is ready (token has been fetched) |
| `error` | `string \| null` | Latest error message (if any) |

### useMyoConnection

Hook for managing persisted connection state. Handles storing/retrieving connection from browser storage.

```tsx
import { useMyoConnection } from '@myo-devs/react';

function MyComponent() {
  const { connection, isLoaded, setConnection, clearConnection } = useMyoConnection({
    storage: 'session', // or 'local'
    storageKey: 'myo_connection', // optional custom key
  });

  if (!isLoaded) return <div>Loading...</div>;

  if (connection) {
    return (
      <div>
        <p>User: {connection.userId}</p>
        <p>Provider: {connection.provider}</p>
        <p>Connected at: {new Date(connection.connectedAt).toLocaleString()}</p>
        <button onClick={clearConnection}>Disconnect</button>
      </div>
    );
  }

  return <div>Not connected</div>;
}
```

#### useMyoConnection Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `storage` | `'session' \| 'local'` | `'session'` | Storage type |
| `storageKey` | `string` | `'myo_connection'` | Custom storage key |

#### useMyoConnection Return Value

| Property | Type | Description |
|----------|------|-------------|
| `connection` | `MyoConnection \| null` | Current connection or null |
| `isLoaded` | `boolean` | Whether storage has been checked (SSR-safe) |
| `setConnection` | `(userId, provider) => void` | Save a connection |
| `clearConnection` | `() => void` | Clear the connection |

### useMyoLink (Low-Level)

For advanced use cases where you already have a link token and need direct control over the widget. Most developers should use `useMyoConnect` instead.

```tsx
import { useMyoLink } from '@myo-devs/react';

function CustomConnect({ linkToken }) {
  const { open, close, ready, isOpen, error } = useMyoLink({
    token: linkToken,
    onSuccess: (metadata) => {
      console.log('User ID:', metadata.userId);
      console.log('Provider:', metadata.provider); // 'google' | 'meta' | 'upload'
    },
    onError: (error) => {
      console.error('Error:', error.error);
    },
    onExit: (error, metadata) => {
      if (metadata.cancelled) console.log('User cancelled');
    },
    displayMode: 'modal', // or 'redirect'
  });

  return (
    <button onClick={open} disabled={!ready || isOpen}>
      {isOpen ? 'Connecting...' : 'Connect Your Data'}
    </button>
  );
}
```

#### useMyoLink Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `token` | `string` | Yes | Link token from your backend |
| `onSuccess` | `(metadata) => void` | No | Called on successful connection |
| `onError` | `(error) => void` | No | Called when an error occurs |
| `onExit` | `(error, metadata) => void` | No | Called when user exits |
| `displayMode` | `'modal' \| 'redirect'` | No | Widget display mode (default: `'modal'`) |
| `widgetUrl` | `string` | No | Custom widget URL |
| `modalStyle` | `CSSProperties` | No | Override modal container styles |
| `iframeStyle` | `CSSProperties` | No | Override iframe styles |
| `backdropStyle` | `CSSProperties` | No | Override backdrop styles |

#### useMyoLink Return Value

| Property | Type | Description |
|----------|------|-------------|
| `open` | `() => void` | Open the link widget |
| `close` | `() => void` | Close the link widget programmatically |
| `ready` | `boolean` | Whether the widget is ready to open |
| `isOpen` | `boolean` | Whether the widget is currently open |
| `error` | `LinkErrorMetadata \| null` | Latest error (if any) |

## Backend Setup

Link tokens must be created on your backend. Example using Next.js API routes:

```typescript
// app/api/link-token/route.ts
import { NextResponse } from 'next/server';
import { MyoClient } from '@myo-devs/node';

const client = new MyoClient({ apiKey: process.env.MYO_API_KEY });

export async function POST() {
  const token = await client.link.tokens.create({
    redirectUri: process.env.NEXT_PUBLIC_APP_URL,
  });

  return NextResponse.json({
    linkToken: token.linkToken,
  });
}
```

## Callback Metadata Types

**LinkSuccessMetadata:**
```typescript
{
  userId: string;
  provider: 'google' | 'meta' | 'upload';
  timestamp: number;
}
```

**LinkErrorMetadata:**
```typescript
{
  error: string;
  provider?: 'google' | 'meta' | 'upload';
  timestamp: number;
}
```

**LinkExitMetadata:**
```typescript
{
  cancelled: boolean;
  error?: LinkErrorMetadata;
  timestamp: number;
}
```

**MyoConnection:**
```typescript
{
  userId: string;
  provider: 'google' | 'meta' | 'upload';
  connectedAt: number;
}
```

## Security Note

The `onSuccess` callback is useful for updating your UI immediately, but you should **always rely on backend webhooks** to confirm the connection was successful. The frontend callback could potentially be spoofed.

## Requirements

- React 18+
- React DOM 18+

## License

MIT