npm package: @oncade/sdk
Description: SDK for game developers to integrate with the Oncade platform
Author: Oncade
Latest version: 0.1.15

# Oncade SDK

A JavaScript/TypeScript SDK for game developers to integrate with the Oncade purchase platform. This SDK enables games to offer in-game purchases through a PWA webstore and retrieve purchase information.

## Installation

```bash
npm install @oncade/sdk
# or
yarn add @oncade/sdk
```

## CDN Usage

For direct inclusion in HTML files without using npm/yarn, you can use one of these CDN options:

### jsDelivr

```html
<!-- Include axios first (required dependency) -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<!-- Include the Oncade SDK (specific version) -->
<script src="https://cdn.jsdelivr.net/npm/@oncade/sdk@0.1.12/dist/browser/oncade-sdk.min.js"></script>

<!-- Or use the latest version (recommended for development only) -->
<script src="https://cdn.jsdelivr.net/npm/@oncade/sdk/dist/browser/oncade-sdk.min.js"></script>
```

### UNPKG

```html
<!-- Include axios first (required dependency) -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<!-- Include the Oncade SDK (specific version) -->
<script src="https://unpkg.com/@oncade/sdk@0.1.12/dist/browser/oncade-sdk.min.js"></script>

<!-- Or use the latest version (recommended for development only) -->
<script src="https://unpkg.com/@oncade/sdk/dist/browser/oncade-sdk.min.js"></script>
```

> **Note about versioning**: 
> - For production environments, it's recommended to pin to a specific version (e.g., `@0.1.12`) to avoid unexpected changes.
> - For development or testing, you can use the latest version by omitting the version number.
> - When a new version is released, you should test it thoroughly before updating your production code.

## Features

- Game server initialization and authentication
- Session management with persistent tokens
- Purchase catalog management
- Purchase initiation and URL generation
- Transaction history retrieval
- Deep link parameter parsing
- Developer tip URL generation

## New Features: Storage, Affiliates, and Leaderboards (WIP)

### Storage API

```typescript
const storage = sdk.storage({ gameId: 'your-game-id' });

// Read a value
const { value, version } = await storage.get<string>('myKey');

// Write a value
await storage.set('myKey', 'newValue');

// Subscribe to changes
const unsubscribe = storage.onChange('myKey', ({ value, version }) => {
  console.log('Value changed:', value, version);
});
```

### Affiliates API

```typescript
const link = sdk.createAffiliateLink({
  gameId: 'your-game-id',
  sessionId: 'user-session-id',
  itemId: 'optional-item-id'
});
console.log('Affiliate link:', link);
```

### Leaderboards API *(not yet implemented)*

```typescript
// Submit a score
await sdk.leaderboards.submit({
  gameId: 'your-game-id',
  boardId: 'daily',
  sessionId: 'user-session-id',
  score: 123
});

// Fetch leaderboard rows
const rows = await sdk.leaderboards.fetch({
  gameId: 'your-game-id',
  boardId: 'daily',
  limit: 10
});
console.log('Leaderboard:', rows);
```

> **Reminder:** When testing new features, please update your tests and verify the new API functionality.

### Telemetry

```typescript
// Track custom gameplay events
sdk.track('level_complete', { level: 3, time: 142 });

// Manually flush queued events (optional)
await sdk.flushTelemetry();
```

### Remote Configuration

```typescript
// Fetch a single config value with optional default
const difficulty = await sdk.getConfig<number>('enemy.difficulty', 1);

// Subscribe to changes
const unsubscribe = sdk.onConfigChange<number>('enemy.difficulty', (val) => {
  console.log('Difficulty updated:', val);
});

// Get the entire config object
const allConfig = await sdk.getAllConfig();
```

## Usage

### Initializing the SDK

```typescript
import OncadeSDK from '@oncade/sdk';

const sdk = new OncadeSDK({
  apiKey: 'your-api-key',
  gameId: 'your-game-id',
  environment: 'production' // Optional: 'production' (default), 'development', or 'staging'
});

// Initialize the SDK
// This will also create or validate a session token
await sdk.initialize();
```

### Session Management

The SDK automatically manages session tokens for users. When `initialize()` is called, the SDK will:

1. Check for an existing session token in localStorage (in browser environments)
2. Send the token to the server for validation
3. If the token is valid, continue using it
4. If the token is invalid or missing, create a new session token

The session token is automatically included in all API requests and purchase URLs. This allows the server to identify the user and maintain their session across requests.

You can check the session status and handle authentication as needed:

```typescript
// Check session status and authenticate if needed
async function ensureAuthenticated() {
  const sessionInfo = await sdk.getSessionInfo();
  
  if (!sessionInfo.isValid) {
    console.log('No valid session. Please initialize the SDK first.');
    return false;
  }
  
  if (!sessionInfo.hasUserId) {
    console.log('Session exists but no user associated. Redirecting to login...');
    const loginUrl = sdk.getLoginURL({
      gameId: sdk.config.gameId,
      redirectUrl: window.location.href
    });
    window.location.href = loginUrl; // or window.open(loginUrl, '_blank');
    return false;
  }
  
  return true;
}

// Use this before operations that require authentication
async function getPurchaseHistory() {
  if (!(await ensureAuthenticated())) {
    return;
  }
  
  const purchases = await sdk.getPurchasedItems();
  console.log('Purchases:', purchases);
}
```

### Purchase Flow

The purchase flow involves the following steps:

1. Initialize the SDK to create a session
2. Generate a purchase URL with the session token
3. Redirect the user to the purchase URL
4. If the user is not logged in, they will be prompted to log in
5. After login, their session will be associated with their user ID
6. The user can then complete the purchase

### Initiating a Purchase

```typescript
const itemId = 'item-456';
const affiliateCode = 'affiliate-789'; // Optional

const purchaseUrl = await sdk.initiatePurchase(itemId, affiliateCode);

if (purchaseUrl) {
  // Redirect the player to this URL to complete the purchase
  console.log('Purchase URL:', purchaseUrl);
}
```

### Generating a Purchase URL Directly

You can also generate a purchase URL without initiating a purchase:

```typescript
const purchaseUrl = sdk.generatePurchaseURL({
  itemId: 'item-456',
  affiliateCode: 'affiliate-789',
  redirectUrl: 'https://yourgame.com/success'
});

if (purchaseUrl) {
  // Redirect the player to this URL to complete the purchase
  console.log('Purchase URL:', purchaseUrl);
}
```

### Retrieving Purchase History

```typescript
const purchasedItems = await sdk.getPurchasedItems();

console.log('Purchased items:', purchasedItems);
```

### Retrieving Transaction History

```typescript
const transactions = await sdk.getTransactionHistory();

console.log('Transaction history:', transactions);
```

### Handling Store Links

```typescript
const url = 'https://store.oncade.com?affiliateCode=affiliate-789';
const params = sdk.handleStoreLink(url);

console.log('Affiliate Code:', params.affiliateCode);
```

### User Authentication and Session Association

You can generate a login URL to allow users to authenticate and associate their session with their user account:

```javascript
// Get the current session token
const { sessionToken } = sdk.getSessionInfo();

// Generate a login URL
const loginUrl = sdk.generateLoginURL({
  sessionToken: sessionToken,
  gameId: 'your-game-id',
  redirectUrl: 'https://yourgame.com/after-login'
});
console.log('Login URL:', loginUrl);

// Redirect the user to the login URL
window.location.href = loginUrl;
```

When a user completes the login process, their user ID will be associated with their session, allowing you to track their purchases and other activities.

### Generating a Tip URL

You can generate a tip URL to allow users to give tips to the developer:

```javascript
// Generate a tip URL
const tipUrl = sdk.getTipURL({
  gameId: 'your-game-id',
  redirectUrl: 'https://yourgame.com/after-tip',
  amount: 500 // Optional: amount in cents (e.g., 500 = $5.00)
});
console.log('Tip URL:', tipUrl);

// Redirect the user to the tip URL or open in a new window
window.location.href = tipUrl;
// or
window.open(tipUrl, '_blank');
```

This allows players to support the game directly with monetary tips of any amount.

## Browser Usage

The SDK can be used directly in a browser environment in several ways:

### Option 1: Using a CDN

```html
<!-- Include axios first (required dependency) -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<!-- Include the Oncade SDK from jsDelivr (specific version) -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@oncade/sdk@0.1.12/dist/browser/oncade-sdk.min.js"></script> -->

<!-- Include the Oncade SDK from jsDelivr (latest version) -->
<script src="https://cdn.jsdelivr.net/npm/@oncade/sdk/dist/browser/oncade-sdk.min.js"></script>

<!-- Or include from UNPKG (specific version) -->
<!-- <script src="https://unpkg.com/@oncade/sdk@0.1.12/dist/browser/oncade-sdk.min.js"></script> -->

<!-- Or include from UNPKG (latest version) -->
<!-- <script src="https://unpkg.com/@oncade/sdk/dist/browser/oncade-sdk.min.js"></script> -->

<script>
  // The SDK is available as a global variable
  const sdk = new OncadeSDK({
    apiKey: 'your-api-key',
    gameId: 'your-game-id',
    environment: 'development' // Optional: 'production' (default), 'development', or 'staging'
  });

  // Initialize the SDK
  sdk.initialize().then(initialized => {
    if (initialized) {
      console.log('SDK initialized successfully');
    }
  });
</script>
```

### Option 2: Using a Module Bundler (Webpack, Rollup, etc.)

```javascript
import axios from 'axios';
import OncadeSDK from '@oncade/sdk/browser';

const sdk = new OncadeSDK({
  apiKey: 'your-api-key',
  gameId: 'your-game-id'
});

// Initialize the SDK
sdk.initialize().then(initialized => {
  if (initialized) {
    console.log('SDK initialized successfully');
  }
});
```

### Option 3: Using ES Modules Directly in the Browser

```html
<script type="module">
  // Import axios from CDN
  import axios from 'https://cdn.jsdelivr.net/npm/axios/dist/esm/axios.min.js';
  
  // Import the SDK from a specific version (if available as ESM)
  // import OncadeSDK from 'https://cdn.jsdelivr.net/npm/@oncade/sdk@0.1.12/dist/browser/oncade-sdk.esm.js';
  
  // Or import the latest version (if available as ESM)
  import OncadeSDK from 'https://cdn.jsdelivr.net/npm/@oncade/sdk/dist/browser/oncade-sdk.esm.js';

  const sdk = new OncadeSDK({
    apiKey: 'your-api-key',
    gameId: 'your-game-id'
  });

  // Initialize the SDK
  sdk.initialize().then(initialized => {
    if (initialized) {
      console.log('SDK initialized successfully');
    }
  });
</script>
```

> **Note**: The ESM version may not be available yet. If you need ESM support, consider adding an ESM build to your webpack configuration.

## API Reference

### Configuration

- `apiKey`: Your API key for authentication
- `gameId`: Your game's unique identifier
- `environment`: The environment for the SDK (optional: 'production' (default), 'development', or 'staging')

### Methods

- `initialize()`: Initialize the SDK, verify credentials, and manage session tokens
- `getSessionInfo()`: Get the current session information including session token
- `clearSession()`: Clear the current session data
- `setPurchaseCatalog(items)`: Set or update the purchase catalog
- `getStoreCatalog()`: Get the current store catalog
- `getStoreItem(itemId)`: Get a specific store item by ID
- `initiatePurchase(itemId, affiliateCode?, redirectUrl?)`: Initiate a purchase and get a URL
- `generatePurchaseURL(options)`: Generate a purchase URL without initiating a purchase
- `generateLoginURL(options)`: Generate a login URL for user authentication and session association
- `getTipURL(options)`: Generate a tip URL for users to give a tip to the developer
- `getPurchasedItems()`: Get a list of items purchased by the current user
- `getTransactionHistory()`: Get transaction history for the current user
- `syncTransactionHistory()`: Sync transaction history from the server
- `handleStoreLink(url)`: Extract parameters from a store link
- `parseDeepLinkParams(url)`: Parse parameters from a deep link URL

## Testing

```bash
npm test
# or
yarn test
```

## License

MIT

## LLM Integration Guide

If you're using an AI assistant to help integrate this SDK into your game, here are specific instructions to guide the process:

### For AI Assistants: How to Integrate @oncade/sdk into a JavaScript Game

1. **Install the package**:
   ```bash
   npm install @oncade/sdk
   # or
   yarn add @oncade/sdk
   ```

2. **Import and initialize the SDK**:
   ```javascript
   import OncadeSDK from '@oncade/sdk';
   
   // Create an instance with required configuration
   const sdk = new OncadeSDK({
     apiKey: 'GAME_API_KEY', // You can get a free API key from https://oncade-devportal.vercel.app/
     gameId: 'GAME_ID',      // You can get your game ID from the https://oncade-devportal.vercel.app/
     environment: 'production' // Or 'development' for testing
   });
   
   // Initialize the SDK (typically in your game's startup sequence)
   async function initializeStore() {
     try {
       await sdk.initialize();
       console.log('Store SDK initialized successfully');
       return true;
     } catch (error) {
       console.error('Failed to initialize store:', error);
       return false;
     }
   }
   ```

3. **Set up the purchase catalog**:
   ```javascript
   async function setupStoreCatalog() {
     // Define your in-game purchasable items
     const catalog = [
       {
         id: 'currency_small',
         type: 'currency',
         name: '100 Coins',
         description: 'A small pack of in-game coins',
         price: 99, // in cents (0.99 USD)
         imageUrl: 'https://yourgame.com/assets/coins_small.png'
       },
       {
         id: 'premium_skin',
         type: 'item',
         name: 'Premium Character Skin',
         description: 'Exclusive character appearance',
         price: 499, // in cents (4.99 USD)
         imageUrl: 'https://yourgame.com/assets/premium_skin.png'
       }
     ];
     
     try {
       const success = await sdk.setPurchaseCatalog(catalog);
       return success;
     } catch (error) {
       console.error('Failed to set up store catalog:', error);
       return false;
     }
   }
   ```
   
   > **Note**: Store entries can also be created and edited directly in the developer portal at https://oncade-devportal.vercel.app/ instead of programmatically setting them through the SDK.

4. **Implement the purchase flow**:
   ```javascript
   // Call this function when player clicks "Buy" on an item
   async function purchaseItem(itemId) {
     try {
       // Get the purchase URL
       const purchaseUrl = await sdk.initiatePurchase(itemId);
       
       if (purchaseUrl) {
         // For web games: redirect to the purchase URL
         window.open(purchaseUrl, '_blank');
         
         // For mobile games using a WebView: use the appropriate native bridge
         // Example: window.webkit.messageHandlers.openUrl.postMessage(purchaseUrl);
         
         return true;
       }
       return false;
     } catch (error) {
       console.error('Failed to initiate purchase:', error);
       return false;
     }
   }
   ```

5. **Check for completed purchases**:
   ```javascript
   // Call this when your game starts or after returning from the store
   async function checkPurchases() {
     try {
       const purchases = await sdk.getPurchasedItems();
       
       // Process each purchased item
       for (const purchase of purchases) {
         // Grant the item to the player based on the purchase type
         if (purchase.type === 'currency') {
           // Add currency to player's account
           addCurrencyToPlayer(purchase.amount);
         } else if (purchase.type === 'item') {
           // Add item to player's inventory
           addItemToInventory(purchase.id);
         }
         
         // Mark the purchase as processed in your game
         markPurchaseAsProcessed(purchase.transactionId);
       }
       
       return purchases;
     } catch (error) {
       console.error('Failed to check purchases:', error);
       return [];
     }
   }
   ```
   
   > **Important**: For consumable items like virtual currency, you will need to implement your own tracking system to manage consumption of that currency after it's been added to the player's account.

6. **Handle returning from purchase**:
   ```javascript
   // Call this when your game is loaded or resumed
   function handleReturnFromPurchase() {
     // Check if we're returning from a purchase (URL parameters or app state)
     const currentUrl = window.location.href;
     const params = sdk.parseDeepLinkParams(currentUrl);
     
     if (params.purchaseComplete) {
       // Show a success message
       showPurchaseSuccessMessage();
       
       // Check for new purchases
       checkPurchases();
     }
   }
   ```

7. **Complete integration example**:
   ```javascript
   // Game initialization
   async function initGame() {
     // Initialize the store
     const storeInitialized = await initializeStore();
     
     if (storeInitialized) {
       // Set up the catalog
       await setupStoreCatalog();
       
       // Check for existing purchases
       await checkPurchases();
       
       // Handle return from purchase if applicable
       handleReturnFromPurchase();
     }
     
     // Continue with normal game initialization
     // ...
   }
   
   // Call this when player clicks the store button
   function openStore() {
     // Show your in-game store UI
     // When player selects an item to buy, call:
     // purchaseItem(selectedItemId);
   }
   
   // Start the game
   initGame();
   ```

8. **Error handling best practices**:
   ```javascript
   // Implement proper error handling
   async function safeStoreFunctionCall(asyncFunction) {
     try {
       return await asyncFunction();
     } catch (error) {
       if (error.response && error.response.status === 401) {
         // Handle authentication errors
         console.error('Authentication error, trying to reinitialize');
         await sdk.initialize();
         return await asyncFunction();
       } else {
         // Handle other errors
         console.error('Store operation failed:', error);
         showErrorToUser('Store operation failed. Please try again later.');
         return null;
       }
     }
   }
   ```

9. **Testing your integration**:
   ```javascript
   // Test function to verify SDK integration
   async function testSDKIntegration() {
     try {
       // Test initialization
       const initialized = await sdk.initialize();
       console.log('SDK initialization:', initialized ? 'SUCCESS' : 'FAILED');
       
       // Test catalog retrieval
       const catalog = await sdk.getStoreCatalog();
       console.log('Catalog items:', catalog.length);
       
       // Test session info
       const sessionInfo = sdk.getSessionInfo();
       console.log('Session token exists:', !!sessionInfo.sessionToken);
       
       return true;
     } catch (error) {
       console.error('Integration test failed:', error);
       return false;
     }
   }
   
   // Run this in development to verify your integration
   // testSDKIntegration();
   ```

Remember to:
- Replace placeholder values with actual game-specific information
- Register your game in the Oncade developer portal (https://oncade-devportal.vercel.app/) to get your API key and game ID
- Implement proper error handling and transaction verification
- Test thoroughly in development environment before going to production
- Adapt the code to fit your game's architecture and design patterns


## Wagering Integration

The Oncade SDK supports on-chain wagering between players. Follow these steps to integrate wagering into your client game:

1. Install the Oncade SDK:
  ```bash
  yarn add @oncade/sdk
  ```
2. Initialize the SDK in your game:
  ```typescript
  import OncadeSDK from '@oncade/sdk';

  const sdk = new OncadeSDK({
    apiKey: 'YOUR_API_KEY',      // Provided after signup
    gameId: 'YOUR_GAME_ID',      // Create or retrieve from portal
    environment: 'production'    // or 'development'
  });
  await sdk.initialize();
  ```
3. List existing wager sessions (optional):
  ```typescript
  const sessions = await sdk.listWagerSessions();
  console.log('Wager Sessions:', sessions);
  ```
4. Create a new wager session:
  ```typescript
  const { sessionId } = await sdk.createWagerSession({});
  // Generate a URL to view and place bets
  const url = sdk.getWagerSessionURL(sessionId);
  window.open(url, '_blank');
  ```
5. After gameplay, submit a score to resolve the wager:
  ```typescript
  await sdk.submitWagerScore(sessionId, {
    score: playerScore,        // Numeric result of the game
    timeUsed: elapsedMilliseconds // Time taken to complete
  });
  ```

The DevPortal UI at `/wager-sessions/{sessionId}` provides a rich React component for placing bets, sharing via QR code, and handling on-chain deposits, withdrawals, and resolution. Your game only needs to navigate to that URL and submit scores after gameplay.