npm package: @onyx-engine/client
Description: Onyx Engine SDK — auth, multiplayer, economy, social, and more
Latest version: 1.6.2

# @onyx-engine/client

TypeScript SDK for the Onyx game platform. One client for everything: authentication, multiplayer, economy, social, leaderboards, achievements, and more.

Works with React Native, Expo, and browsers. No native dependencies.

## Versioning

SDK major version = API version. This ensures backwards compatibility:

- **SDK 1.x** → always uses API v1. Safe to update within 1.x.
- When API v2 is released → **SDK 2.0.0** will be published.
- `^1.0.0` in your package.json will never auto-update to 2.x.

## Installation

```bash
npm install @onyx-engine/client
```

## Quick Start

```typescript
import { OnyxClient } from "@onyx-engine/client";

const client = new OnyxClient({
  apiKey: "YOUR_API_KEY", // Get from https://onyx.blessupgames.com/dashboard/api-keys
});

// Login
const session = await client.auth.guestLogin("unique-device-id", "ios");
console.log(session.playerId);

// Use any feature
const profile = await client.players.getProfile();
const balance = await client.economy.getBalance();
const friends = await client.friends.list();
const leaderboard = await client.leaderboard.get("daily", 10);

// Real-time multiplayer
await client.realtime.connect("default");
client.realtime.raiseEvent(1, { action: "move", x: 10 }, "others");
client.realtime.on("customEvent", (evt) => {
  console.log(`Player ${evt.senderRef} did:`, evt.data);
});
```

## Authentication

All login methods return `AuthResponse` and automatically store the JWT for subsequent API calls.

```typescript
// Guest (anonymous)
await client.auth.guestLogin("device-id", "ios");

// Google
await client.auth.googleLogin(googleIdToken, "device-id", "android");

// Apple
await client.auth.appleLogin(appleIdentityToken, "user@icloud.com", "Alice", "device-id", "ios");

// Email OTP
await client.auth.sendOtp("user@example.com");
await client.auth.verifyOtp("user@example.com", "123456");

// Link provider to guest account
await client.auth.linkAccount("google", { token: googleIdToken });

// Logout / delete
await client.auth.logout();
await client.auth.deleteAccount();
```

### Token Persistence

JWT tokens expire after 1 hour. The SDK refreshes them automatically on 401. Persist tokens so users stay logged in:

```typescript
client.onTokenRefresh((jwt, refreshToken) => {
  // Save to AsyncStorage, SecureStore, localStorage, etc.
  storage.set("jwt", jwt);
  storage.set("refreshToken", refreshToken);
});

// Restore session on app restart
const restored = await client.restoreSession(savedJwt, savedRefreshToken);
if (!restored) {
  // Token expired, show login screen
}
```

## Player Profile

```typescript
const profile = await client.players.getProfile();
// { playerId, username, avatar, stats: { totalScore, gamesPlayed, coins } }

await client.players.setUsername("NewName");     // 30-day cooldown
await client.players.setAvatar("preset", "emoji:🎮");
```

## Friends & Social

```typescript
const friends = await client.friends.list();
const results = await client.friends.search("ali");
await client.friends.sendRequest("player_xyz...");

const requests = await client.friends.getRequests();
await client.friends.respond(requestId, "accept");

await client.friends.remove(friendshipId);
```

## Chat

```typescript
const convos = await client.chat.getConversations();
const history = await client.chat.getHistory(friendId, { limit: 50 });
await client.chat.send(friendId, "Hello!");
await client.chat.markAsRead(friendId);
const unread = await client.chat.getUnreadCount();
const ban = await client.chat.getBanStatus();
```

## Blocks & Moderation

```typescript
await client.blocks.block(playerId);
await client.blocks.unblock(playerId);
const list = await client.blocks.list();
const status = await client.blocks.isBlocked(playerId);

await client.moderation.report({
  reportedPlayerId: 123,
  category: "chat_abuse",
  reason: "Abusive messages",
  evidence: "msg:1234,msg:1235",
});
```

## Economy & IAP

```typescript
const { coins } = await client.economy.getBalance();
const products = await client.economy.getProducts("chess");
const inventory = await client.economy.getInventory();

// Buy with coins
await client.economy.purchase("hint_pack", 50);

// Verify real-money IAP receipt
await client.economy.verifyPurchase({
  productId: "coins_500",
  platform: "ios",
  transactionId: "apple-tx-id",
  receipt: "base64-receipt",
});

const txns = await client.economy.getTransactions(20, 0);
```

## Leaderboard

```typescript
const lb = await client.leaderboard.get("weekly", 50);
// { period, entries: [{ rank, playerId, username, score }], myRank, total }
```

Periods: `daily`, `weekly`, `monthly`, `alltime`.

## Achievements

```typescript
const { achievements } = await client.achievements.list();
// [{ id, nameKey, progress, unlocked, rewardAmount, rewardClaimed }]

const reward = await client.achievements.claimReward("first_win");
// { coinsAwarded: 50, newBalance: 390 }
```

## Push Notifications

```typescript
await client.notifications.register(fcmToken, "ios", "device-id");
await client.notifications.updateToken(oldToken, newToken);

const prefs = await client.notifications.getPreferences();
await client.notifications.setPreferences({ challenges: false });
```

## Sessions (Open Lobby)

Browse and create game sessions. Players join freely, leave anytime.

```typescript
// List open sessions
const { sessions } = await client.sessions.list({ gameType: "chess", limit: 20 });
sessions.forEach(s => console.log(s.sessionRef, s.playerCount, "/", s.maxPlayers));

// Create a session
const session = await client.sessions.create({
  sessionName: "Friday Game Night",
  maxPlayers: 20,
  isOpen: true,
  properties: { gameMode: "casual", language: "tr" },
});

// Join the session
await client.realtime.connect(session.sessionRef);
```

## Matchmaking

Matchmaking runs over WebSocket. Connect to realtime first.

```typescript
await client.realtime.connect("default");

// Join queue
client.matchmaking.join("chess", "en", 1200);

// Listen for match
client.realtime.onEvent("EvMatchFound", (evt) => {
  console.log("Match found:", evt.sessionRef, evt.opponent);
});

// Cancel
client.matchmaking.cancel();

// Query queue stats
const stats = await client.matchmaking.getQueueStats("word_puzzle", "tr");
console.log(`${stats.playersQueued} players queued, avg wait ${stats.averageWaitSeconds}s`);

// Challenge a friend
client.matchmaking.challenge("player_abc", "chess", "en");
client.realtime.onEvent("EvChallengeResult", (evt) => {
  if (evt.accepted) console.log("Game session:", evt.sessionRef);
});
```

## Real-time Multiplayer

```typescript
await client.realtime.connect("session-name");

// Send events to other players
client.realtime.raiseEvent(1, { x: 10, y: 20 }, "others");
client.realtime.raiseEvent(2, { card: 42 }, "all");
client.realtime.raiseEvent(3, { msg: "hi" }, ["Player(2)"]);

// Shared room state
client.realtime.setRoomProperties({ phase: "playing", round: 1 });
client.realtime.setRoomProperties(
  { turn: "Player(3)" },
  { turn: "Player(2)" }  // CAS: only if current value matches
);

// Your player state
client.realtime.setPlayerProperties({ score: 42, ready: true });

// Listen for events
client.realtime.on("customEvent", (evt) => console.log(evt.senderRef, evt.data));
client.realtime.on("propertiesChanged", (evt) => console.log(evt.properties));
client.realtime.on("playerJoined", (evt) => console.log(evt.displayName, "joined"));
client.realtime.on("playerLeft", (evt) => console.log(evt.playerRef, "left"));
client.realtime.on("snapshot", (snap) => console.log("Players:", snap.playerCount));
client.realtime.on("disconnected", () => console.log("Lost connection"));

// Time helpers
client.realtime.tickRate;                    // 20
client.realtime.serverTick;                  // current tick
client.realtime.serverTimeSeconds;           // elapsed seconds
client.realtime.remainingSeconds(endTick);   // countdown

// Use power-up
client.realtime.usePowerUp("freeze");

// Disconnect
client.realtime.disconnect();
```

## Live Video (WebRTC)

```typescript
const creds = await client.video.getTurnCredentials();
// { urls, username, credential, ttl }

const pc = new RTCPeerConnection({
  iceServers: [{ urls: creds.urls, username: creds.username, credential: creds.credential }],
});

const ban = await client.video.getBanStatus();
```

## Error Handling

```typescript
import { OnyxApiError } from "@onyx-engine/client";

try {
  await client.economy.purchase("item", 100);
} catch (err) {
  if (err instanceof OnyxApiError) {
    console.log(err.status); // 400
    console.log(err.code);   // "insufficient_coins"
    console.log(err.message);
  }
}
```

The SDK automatically refreshes expired JWTs on 401 responses.

## Configuration

```typescript
const client = new OnyxClient({
  // baseUrl defaults to "https://api.blessupgames.com"
  // wsUrl defaults to "wss://api.blessupgames.com/ws"
  ws: {
    clientVersion: "1.0.0",
    autoReconnect: true,
    reconnectBaseDelayMs: 500,
    reconnectMaxDelayMs: 30000,
    reconnectMaxAttempts: 10,
    debug: false,
  },
});
```

## Low-level GameClient

For advanced use cases, you can use the low-level `GameClient` directly:

```typescript
import { GameClient, registerCommand, registerEvent } from "@onyx-engine/client";

const gc = new GameClient("wss://server/ws", { autoReconnect: true });
await gc.connect();
await gc.join("session", "Alice");

gc.raiseEvent(1, { data: "test" }, "others");
gc.setRoomProperties({ key: "value" });
gc.sendCommand("CmdCustom", { field: 1 });
```

## Exports

```typescript
// High-level client
export { OnyxClient, OnyxApiError };
export type {
  OnyxClientOptions, AuthResponse, PlayerProfile, Friend,
  ChatMessage, Product, Achievement, LeaderboardEntry,
  MatchmakingStatus, BanStatus, BlockedPlayer, Transaction,
  NotificationPreferences, TurnCredentials,
};

// Low-level client
export { GameClient };
export type { GameClientOptions };

// Protocol types
export { MessageType, ConnectionState, DisconnectCode, EventTarget, PropertyScope };

// Serialization
export { registerCommand, registerEvent };
```

## React Native

```typescript
import { useEffect, useRef, useState } from "react";
import { OnyxClient } from "@onyx-engine/client";
import AsyncStorage from "@react-native-async-storage/async-storage";

const client = new OnyxClient({ apiKey: process.env.ONYX_API_KEY! });

client.onTokenRefresh(async (jwt, rt) => {
  await AsyncStorage.multiSet([["@jwt", jwt], ["@rt", rt]]);
});

function useOnyx() {
  const [loggedIn, setLoggedIn] = useState(false);

  useEffect(() => {
    AsyncStorage.multiGet(["@jwt", "@rt"]).then(async ([[, jwt], [, rt]]) => {
      if (jwt && rt) {
        const ok = await client.restoreSession(jwt, rt);
        setLoggedIn(ok);
      }
    });
  }, []);

  return { client, loggedIn, setLoggedIn };
}
```