Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TypeScript SDK

TypeScript SDK’sı Open Play’e tarayıcıdan, Node.js’ten ve oyun motorlarının JavaScript katmanından bağlanmanın tipli yoludur: oyuncu girişi, eklenti komutları, gerçek zamanlı bağlantı ve sunucu anahtarıyla yönetim çağrıları. Bu sayfa kurulumu, giriş noktalarını, temel istemcileri ve hangi modüllerin tipli istemcisi olduğunu anlatır.

Not

Bu belge @ferdiunal/open-play-ts 0.1.15 sürümüyle doğrulanmıştır. Paket GitHub Packages’ta yayımlıdır; depo bugün özeldir, bu yüzden kurulum için yetkili bir GitHub belirteci gerekir.

API başvurusu: tüm tür ve işlevlerin üretilmiş başvurusu https://open-play.b4klabs.dev/docs/sdk/typescript/ adresindedir (@open-play/sdk ve @open-play/panel-kit).

Kurulum

Gereksinimler: Node.js 22 ya da üstü; paket yalnız ESM’dir (require ile yüklenmez). Tarayıcıda Fetch, AbortController ve BigInt destekli modern bir tarayıcı gerekir.

  1. Kayıt defterini tanıtın. Projenizin kökündeki .npmrc dosyasına kapsamı (scope) ve belirteç değişkenini yazın. Belirteç read:packages yetkili bir GitHub klasik belirtecidir (ince ayarlı belirteç denenmedi); dosyaya değer yazmayın, ortam değişkeni kullanın:

    @ferdiunal:registry=https://npm.pkg.github.com
    //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
    
  2. Paketi kurun:

    export GITHUB_TOKEN="<read:packages yetkili belirteç>"
    npm install @ferdiunal/[email protected]
    

    pnpm aynı .npmrc ile çalışır. Belirteci depoya, imaja ya da CI günlüğüne koymayın.

  3. İçe aktarın:

    import { CrxClient, createPlayerClient } from "@ferdiunal/open-play-ts";
    

Uyarı

Paket adı @ferdiunal/open-play-ts’tir. SDK deposundaki bazı dosyalarda ve örneklerde geçen @open-play/sdk adı yalnızca kaynak deposundaki iç addır; yayımlanan pakette kullanmayın.

Giriş noktaları

İçe aktarmaİçerikOrtam
@ferdiunal/open-play-tsCrxClient, createPlayerClient, createGameClient, hata ve sayı yardımcıları, katalog/mağaza/sıralama tipleriTarayıcı ve Node
.../playerOyuncu istemcisi ve eklenti bağlama oluşturma tipleriTarayıcı ve Node
.../gameGameClientTarayıcı ve Node
.../realtimeGerçek zamanlı istemci tipleriTarayıcı ve Node
.../browserIndexedDB günlüğü ve sekme koordinasyonu (createBrowserAdapter)Tarayıcı
.../codegenBağlama ve tip üreticisiYalnız Node

CLI: crx (npx crx --help). Kod üretimi bölümüne bakın.

İstemciler

CrxClient: sunucu anahtarıyla REST

CrxClient, API anahtarıyla çalışan tipli REST istemcisidir. Sunucu anahtarını yalnız güvenilen arka uçta kullanın; tarayıcıya ya da oyun paketine koymayın.

const client = new CrxClient({ baseUrl: "https://oyun.example.com/open-play", apiKey: serverKey });

ClientOptions: baseUrl, apiKey, isteğe bağlı fetch, timeoutMs (1–120 000 ms, varsayılan 15 sn), maxRetries (0–5, varsayılan 2), maxResponseBytes (varsayılan 2 MiB, en çok 16 MiB). Her çağrı { path, query, body, headers } girdisi ve isteğe bağlı { signal, timeoutMs, maxRetries } alır.

import { CrxClient, CrxError, ErrorKind } from "@ferdiunal/open-play-ts";

/**
 * Sunucu anahtarıyla bir karaktere XP verir. Aynı `requestKey` ile yapılan tekrar aynı sonucu döndürür;
 * `If-Match`, okuduğunuz sürümle yazdığınız sürümün çakışmasını yakalar.
 */
export async function grantXp(
  baseUrl: string,
  serverKey: string,
  characterId: string,
  amount: bigint,
  requestKey: string,
): Promise<bigint> {
  const client = new CrxClient({ baseUrl, apiKey: serverKey });
  const current = await client.getCharacter({ path: { id: characterId } });
  try {
    const result = await client.executeCommand({
      path: { id: characterId },
      body: { type: "add_xp", amount },
      headers: { "Idempotency-Key": requestKey, "If-Match": current.version.toString() },
    });
    return result.version;
  } catch (error) {
    if (error instanceof CrxError && error.kind === ErrorKind.Http && (error.status === 409 || error.status === 422)) {
      // 409: sürüm çakışması; yeniden okuyup karar verin. 422: aynı anahtar başka bir gövdeyle kullanılmış.
      // Yeni bir anahtarla kör yeniden deneme yapmayın.
      throw error;
    }
    throw error;
  }
}

/** Sayfalamayı SDK yürütür: imleç (cursor) opaktır, `break` yeni sayfa istemez. */
export async function countCharacters(baseUrl: string, serverKey: string, definitionKey: string): Promise<number> {
  const client = new CrxClient({ baseUrl, apiKey: serverKey });
  let total = 0;
  for await (const _character of client.iterateListCharacters({ query: { definition: definitionKey, limit: 100 } })) {
    total += 1;
  }
  return total;
}

Çağrı kuralları

  • baseUrl HTTPS olmak zorundadır; yalnızca loopback (localhost, 127.0.0.1, [::1]) düz HTTP kabul eder. Adres kullanıcı bilgisi, sorgu ya da parça içeremez. Yönlendirmeler (redirect) izlenmez.
  • Anahtar yalnız Authorization başlığındadır; hata nesnelerinde ve hata ayıklama çıktısında görünmez.
  • Yeniden deneme yalnızca okuma GET’lerinde ve gerçekten verilmiş aynı Idempotency-Key taşıyan komutlarda yapılır. Geçici 408, 429, 500, 502, 503, 504 ve güvenli işlemde ağ hatası, sınırlı üstel geri çekilmeyle denenir; Retry-After toplam süre sınırı içindedir. 409 ve 422 yeniden denenmez. Oluşturma ve silme çağrıları otomatik tekrarlanmaz.
  • Komutlarda If-Match (kaynağın okuduğunuz sürümü) ve Idempotency-Key başlıklarını siz verirsiniz (Idempotency ve eşzamanlılık).
  • Sayfalama imleçleri opaktır; iterate… yöntemleri (iterateListCharacters, iterateListCharacterEvents, iterateGetCharacterTimeline, iterateListRules, iterateListWebhooks) sayfaları sizin yerinize yürütür.

PlayerClient: oyuncu oturumu

createPlayerClient({ baseUrl, environmentId }) oyuncu tarafı istemcisidir (yalnız çerez kipi; sunucu anahtarı bu istemciye girmez). İçerdikleri:

ÜyeNe yapar?
sessionGiriş, oturum durumu, yenileme, çıkış: Kimlik doğrulama
plugin(binding) · publicPlugin(binding)Eklenti komut/sorgu/modülleri: Eklenti bağlamaları
realtimeGerçek zamanlı soket (aşağıda)
catalogPanelden yönetilen eşya/veri/model/içerik grubu okuması (koşullu GET, watch… yoklayıcıları)
leaderboard(key, { limit })Çekirdek skor tablosu okuması (limit 1–50)
store.catalog() · store.buy()Mağaza vitrini ve satın alma
data.list() · data.get() · data.create() · data.update() · data.delete()Oyuncu verisi (bulut kaydı): sürümlü yazım, çatışmada PlayerDataConflict; bkz. Oyuncu verisi
notificationsCihaz kaydı, gelen kutusu, web push
dispose()Yerel kaynakları bırakır (sunucu oturumunu kapatmaz)

İlk kullanım için Hızlı başlangıç. Oturum durumları için Oturumlar ve CSRF.

createPlayerClient ağ isteğini kurucuda başlatmaz; baseUrl oyun sayfanızla aynı origin’dir (ters vekil) ya da oyunun alan adı ortama bağlıysa Open Play adresidir (CORS; aynı site gerekir) (Mimari ve barındırma).

GameClient: oyuncu + eklenti + realtime tek yaşam döngüsü

createGameClient, bir oyuncu istemcisini, eklenti bağlamalarını ve tek gerçek zamanlı soketi aynı yaşam döngüsünde tutar. dispose() çocuk bağlamaları ve kendi yarattığı oyuncu istemcisini kapatır; sunucu çıkışı için ayrıca game.session.logout() çağırın. Dışarıdan verdiğiniz PlayerClient GameClient tarafından sahiplenilmez.

import { createGameClient, type GameClient } from "@ferdiunal/open-play-ts";
import { createBrowserAdapter } from "@ferdiunal/open-play-ts/browser";

/**
 * Bir oyunun oyuncu istemcisi, eklenti bağlamaları ve tek realtime soketi aynı yaşam döngüsünde tutulur.
 * IndexedDB günlüğü (journal) yarım kalan eklenti komutlarının sayfa yenilendikten sonra kurtarılmasını sağlar.
 */
export function startGame(environmentId: string): { game: GameClient; stop: () => Promise<void> } {
  const browser = createBrowserAdapter({ journal: "indexeddb", allowMemoryFallback: false });
  const game = createGameClient({
    playerOptions: { baseUrl: window.location.origin, environmentId, browser },
  });
  return {
    game,
    stop: async () => {
      await game.dispose();
      await browser.dispose();
    },
  };
}

journal: "indexeddb" yarım kalan eklenti komutlarının sayfa yenilendikten sonra kurtarılmasını sağlar. SSR/Node’da journal: "memory" seçin ve istemciyi istekler arasında paylaşmayın.

Gerçek zamanlı soket

client.realtime.connect() tek kullanımlık bilet alır, soketi açar ve kimlik doğrular; kopmada bileti yenileyip imleç/epoch ile kaldığı yerden sürdürür. Sunucu etkinlik göstermeyen soketi 4100 koduyla kapatır; onIdleClose verirseniz SDK otomatik yeniden bağlanmaz ve “Devam et” kararı sizindir. Protokol ayrıntıları Gerçek zamanlı bölümündedir.

import { createPlayerClient, isIdleClose, type PlayerClient } from "@ferdiunal/open-play-ts";

/**
 * Realtime soketini açar. Sunucu, etkinlik göstermeyen soketi 4100 koduyla kapatır; `onIdleClose` verirseniz
 * SDK otomatik yeniden bağlanmaz ve "Devam et" kararını size bırakır.
 */
export function createRealtimeClient(environmentId: string, showContinue: (resume: () => Promise<unknown>) => void): PlayerClient {
  const client: PlayerClient = createPlayerClient({
    baseUrl: window.location.origin,
    environmentId,
    realtime: {
      onIdleClose: () => showContinue(() => client.realtime.connect()),
    },
  });
  return client;
}

/** Durumu yoklayan uygulama aynı bilgiyi `lastClose` üzerinden okuyabilir. */
export function wasIdleClosed(client: PlayerClient): boolean {
  return isIdleClose(client.realtime.lastClose);
}

/** Oturum açıldıktan sonra soketi kurar; bilet yenileme ve yeniden bağlanma SDK'dadır. */
export async function connect(client: PlayerClient): Promise<void> {
  const connection = await client.realtime.connect();
  console.log("realtime durumu:", connection.state);
}

Kapsam: hangi modülün tipli istemcisi var?

SDK, REST yüzeyinin tamamını sarmaz. Aşağıdaki tablo bugünkü durumu gösterir; “ham REST” olan modüller için uçları uç nokta başvurusunda bulur, HTTP çağrısını kendiniz yaparsınız (anahtar ve başlık kuralları aynıdır).

Modül / alanTipli istemciNerede
Karakter tanımları, özellikler (trait), seviye eğrileri, karakterler, komutlar, geçmiş, istatistikVarCrxClient (sunucu anahtarı)
KurallarVarCrxClient (createRule, listRules, dryRunRule, …)
Webhook uç kaydı (oluştur/listele/oku/sil)VarCrxClient; imza doğrulaması sizin kodunuzdur (Webhook’lar)
KlanlarVarCrxClient (20 işlem)
Katalog (eşya/veri/model) ve içerik gruplarıVarPlayerClient.catalog (oyuncu), CrxClient.catalog (sunucu)
Ödül tablosu kaydı ve vermeVarCrxClient.createRewardTable, getRewardTable, grantRewardTable
Mağaza vitrini ve satın almaVarPlayerClient.store
Çekirdek skor tablosu okumaVarPlayerClient.leaderboard
Bildirimler: cihaz kaydı, gelen kutusu, web pushVarPlayerClient.notifications
Eklentiler ve gerçek zamanlıVar (şemadan üretilen bağlamayla)PlayerClient.plugin, GameClient
Eşyalar ve envanterYokHam REST
Para birimleri, cüzdanlar, transferler, defterYokHam REST
Takas (trading), tüccarlarYokHam REST
Mağaza yönetimi, IAP doğrulamaYokHam REST
Görev, VIP, günlük giriş ve NPC ödülleriYokHam REST
PartilerYokHam REST
Sunucu tarafı skor tabloları, turnuvalarYokHam REST
SohbetYokHam REST/WebSocket
Sahneler, varlıklarYokHam REST
Magic link, hesap bağını çözme, yerel platform girişleriYokHam REST (Kimlik doğrulama)

CrxClient’ta tipli işlem sayısı 45’tir (25 çekirdek + 20 klan) ve katalog ile ödül tablosu işlemleri bunlara eklenir. Tipli istemcisi olmayan bir modülü kullanmak sorun değildir; yalnızca tipleri elle yazarsınız.

Hatalar

Tüm hatalar CrxError olarak gelir: kind, status, attempts ve güvenli metadata (code, operation, deliveryState, reference, reason). HTTP gövdesi, istek URL’si ve anahtar hata nesnesine girmez.

ErrorKindAnlamı
HttpSunucu hata durumu döndürdü (status dolu)
NetworkBağlantı koptu; mutasyon uygulanmış olabilir
TimeoutToplam süre sınırı doldu
Cancelledsignal ile iptal
ContractGirdi ya da yanıt sözleşmeye uymuyor
ConfigurationGüvensiz ya da bozuk ayar
AuthOyuncu oturumu yeniden doğrulama istiyor
UnknownOutcomeİstek gönderildi ama sonuç bilinmiyor; kör tekrar yapmayın, sonucu sorgulayın
StorageGünlük, tarayıcı koordinasyonu ya da OAuth bağ deposu kullanılamıyor
Realtime · Backpressure · UpgradeRequired · ScopeChanged · DisposedGerçek zamanlı yaşam döngüsü, kuyruk dolu, eklenti sürüm uyuşmazlığı, oturum değişti, istemci kapatıldı

Banlı hesap error.code === ACCOUNT_BANNED_CODE ile ayırt edilir (Kimlik doğrulamaya genel bakış).

Sayılar ve JSON

  • i64/u64 alanlar bigint’tir; 32 bit alanlar sınır denetimli number’dır. 9007199254740993n kayıpsız taşınır.
  • SDK yükünü JSON.parse/JSON.stringify ile yeniden dolaştırmayın; parseJson, stringifyJson, encodeModel, decodeModel kullanın. Ondalık sayılar LosslessNumber olur.
  • Varsayılan sınırlar: JSON 2 MiB, derinlik 96, düğüm sayısı 200 000.
  • Eklenti protokolünde i64 değerler ondalık dizgedir (plugin-v1-decimal-int64). Bkz. Sayılar ve kodlama.

Kod üretimi (crx)

Eklenti şemasından tipli bağlama ve karakter tanımından tip üretmek için CLI kullanılır (yalnız Node; ağ erişimi yoktur, girdi dosyadır, çıktı yalnız yeni dosyaya yazılır, --check bayt karşılaştırır):

# Eklenti bağlaması: şema özeti (sha256) sabitlenir; değişirse üretim reddedilir
npx crx plugins generate --input plugin.schema.json --schema-sha256 "$SCHEMA_SHA256" --output binding.ts
npx crx plugins generate --input plugin.schema.json --schema-sha256 "$SCHEMA_SHA256" --output binding.ts --check

# Karakter tanımı tipleri
npx crx types generate --input definition.json --version 1 --schema-sha256 "$SCHEMA_SHA256" --output hero.ts

Eklenti şemasını nereden alacağınızı ve schemaSha256 sabitlemenin anlamını Eklenti bağlamaları sayfası anlatır.

Bilinen sınırlar

  • Yalnız çerez kipi vardır; oyuncu istemcisinde Bearer ya da sunucu anahtarı kipi yoktur.
  • Erişim JWT’si uygulamaya açılmaz (Sunucuda doğrulama).
  • Magic link, unlink ve yerel platform girişleri için yardımcı yoktur.
  • Görünen ad değiştirme (POST /v1/auth/session/display-name) için yöntem yoktur.
  • Sunucu CORS’u yalnız ortam alan adı listesindeki origin’lere verir; oturum çerezi SameSite=Lax olduğundan oyun ile Open Play aynı sitede olmalıdır, değilse ters vekil kullanın.