npm package: @sky-mavis/1more-sdk
Description: SDK for game partners to integrate with the OneMore platform
Latest version: 0.0.3

# @sky-mavis/1more-sdk

SDK for game partners to integrate their games with the OneMore platform. This SDK handles the communication between partner games and the main platform using [Penpal](https://github.com/Aaronius/penpal) for secure, promise-based RPC. Works in both **iframe** (web) and **React Native WebView** environments.

## Installation

```bash
npm install @sky-mavis/1more-sdk
# or
yarn add @sky-mavis/1more-sdk
```

## Quick Start

```typescript
import { init, endRound } from '@sky-mavis/1more-sdk';

// Initialize SDK when your game loads
const { parent, destroy } = await init({
  // Optional: add extra allowed origins for local development
  allowedOrigins: ['http://localhost:3000'],
  onRoundStart: (data) => {
    // Called when parent starts a new round
    console.log('Round started!');
    console.log('Token:', data.token);
    console.log('Session ID:', data.sessionId);
    console.log('Round ID:', data.roundId);
    console.log('Target Score:', data.targetScore);

    // Store token for API calls
    window.arcadeToken = data.token;
    sessionStorage.setItem('arcadeToken', data.token);
  },
});

// When game round ends, call parent.endRound()
await parent.endRound();

// Or use the standalone function:
await endRound();
```

## API

### `init(config)`

Initializes the SDK and establishes a connection with the parent platform.

```typescript
const { parent, destroy } = await init({
  allowedOrigins: ['http://localhost:3000'], // optional
  timeout: 10000, // optional, default 10 seconds
  onRoundStart: (data) => {
    // Handle round start
    console.log(data.token, data.sessionId, data.roundId, data.targetScore);
  },
});
```

**Parameters:**
- `config.allowedOrigins` - Additional allowed origins (default origin is always included)
- `config.timeout` - Connection timeout in milliseconds (default: 10000)
- `config.onRoundStart` - Callback fired when the parent starts a new round

**Returns:** `Promise<IInitResult>`
- `parent` - Proxy object to call methods on the parent platform
- `destroy()` - Function to clean up the connection

### `parent.endRound()`

Notifies the parent platform that the game round has ended.

```typescript
await parent.endRound();
```

### `endRound()` (standalone)

Convenience function that can be called from anywhere after `init()`.

```typescript
import { endRound } from '@sky-mavis/1more-sdk';

// Anywhere in your game code:
await endRound();
```

## Types

### Game (Child) Types

```typescript
interface IInitConfig {
  allowedOrigins?: string[];
  timeout?: number;
  onRoundStart: (data: IRoundStartData) => void;
}

interface IInitResult {
  parent: IParentMethods;
  destroy: () => void;
}
```

### Shared Types

```typescript
interface IRoundStartData {
  token: string;
  sessionId: string;
  roundId: string;
  targetScore: number;
  [key: string]: unknown;
}

interface IGameMethods {
  startRound: (data: IRoundStartData) => void | Promise<void>;
}

interface IParentMethods {
  endRound: () => void | Promise<void>;
}
```

## Example: Phaser Integration

```typescript
import Phaser from 'phaser';
import { init, endRound } from '@sky-mavis/1more-sdk';

declare global {
  interface Window {
    arcadeToken?: string;
  }
}

async function main() {
  // Initialize SDK before starting game
  await init({
    onRoundStart: (data) => {
      window.arcadeToken = data.token;
      sessionStorage.setItem('arcadeToken', data.token);
      console.log('Round started:', data.roundId);
    },
  });

  // Start Phaser game
  new Phaser.Game(gameConfig);
}

// Call in your game over scene
export async function notifyGameFinished() {
  await endRound();
}

main();
```

## Communication Flow

```
┌─────────────────┐                    ┌─────────────────┐
│ OneMore Platform│                    │   Partner Game  │
│    (Parent)     │                    │ (Iframe/WebView)│
└────────┬────────┘                    └────────┬────────┘
         │                                      │
         │     1. Load iframe or WebView        │
         │ ─────────────────────────────────────>
         │                                      │
         │     2. Penpal connection established │
         │ <═══════════════════════════════════>
         │                                      │
         │  3. parent calls game.startRound()   │
         │       (token, sessionId, roundId,    │
         │        targetScore)                  │
         │ ─────────────────────────────────────>
         │                                      │
         │         ... game plays ...           │
         │                                      │
         │   4. game calls parent.endRound()    │
         │ <─────────────────────────────────────
         │                                      │
```

### Transport Auto-Detection (Game Side)

| Priority | Environment | Detected by | Transport |
|---|---|---|---|
| 1 | Custom | `config.messenger` provided | User-provided messenger |
| 2 | React Native WebView | `window.ReactNativeWebView` exists | `ReactNativeWebViewChildMessenger` |
| 3 | Iframe (default) | Fallback | `WindowMessenger` via `window.parent` |

## React Native WebView

The SDK works in React Native WebView environments in addition to iframes.

### Game side (web page in WebView)

No code changes needed — `init()` auto-detects `window.ReactNativeWebView` and uses the appropriate transport.

```typescript
import { init, endRound } from '@sky-mavis/1more-sdk';

// Works identically in iframe or React Native WebView
const { parent, destroy } = await init({
  onRoundStart: (data) => {
    console.log('Round started:', data.roundId);
    window.arcadeToken = data.token;
  },
});

await parent.endRound();
```

### Platform side (React Native app)

Use `ReactNativeWebViewParentMessenger` to bridge the WebView's message channel.

```tsx
import { useRef, useMemo, useEffect } from 'react';
import WebView from 'react-native-webview';
import {
  ReactNativeWebViewParentMessenger,
  initParent,
} from '@sky-mavis/1more-sdk';

function GameScreen() {
  const webViewRef = useRef<WebView>(null);

  const messenger = useMemo(
    () =>
      new ReactNativeWebViewParentMessenger({
        postMessage: (data) => webViewRef.current?.postMessage(data),
      }),
    [],
  );

  useEffect(() => {
    let destroy: (() => void) | undefined;

    initParent({
      messenger,
      onRoundEnd: () => {
        console.log('Game ended the round');
      },
    }).then((result) => {
      destroy = result.destroy;
      // Start a round
      result.game.startRound({
        token: 'abc123',
        sessionId: 'session-456',
        roundId: 'round-789',
        targetScore: 1000,
      });
    });

    return () => destroy?.();
  }, [messenger]);

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: 'https://game.example.com' }}
      onMessage={(e) => messenger.handleMessageFromWebView(e.nativeEvent.data)}
    />
  );
}
```

## Security

**Iframe mode:**
- **Origin** — Only accepts connections from configured `allowedOrigins` (Penpal + `WindowMessenger`)
- **Source** — Only communicates with `window.parent`
- Default allowed origin: `https://main.new-arcade.axieinfinity.services`

**React Native WebView mode:**
- **Native bridge isolation** — Communication goes through the native WebView bridge (`window.ReactNativeWebView.postMessage`), which is sandboxed to the WebView instance
- **Protocol validation** — Penpal's `validateReceivedMessage` filters out any non-Penpal messages

## License

MIT