@crowdedkingdoms/crowdyjs 7.1.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CrowdyJS
2
2
 
3
- The official browser-first TypeScript SDK for **Crowded Kingdoms**. CrowdyJS gives you one typed client that handles auth, the world/replication GraphQL API, and the UDP proxy subscription stream behind a single shared session.
3
+ The official browser-first TypeScript SDK for **Crowded Kingdoms**. CrowdyJS gives you typed clients for auth, the world/replication GraphQL API, and the UDP proxy subscription stream. As of **v7** it follows the Overworld two-token model: an identity **session token** for the Management API, and short-lived **app-scoped tokens** for gameplay via `client.portal` (see [Overworld portals & app-scoped tokens (v7)](#overworld-portals--app-scoped-tokens-v7)).
4
4
 
5
5
  ## Install
6
6
 
@@ -41,12 +41,15 @@ if (!client.session.getToken()) {
41
41
  await client.auth.login({ email: 'player@example.com', password: 'secret' });
42
42
  }
43
43
 
44
- // Fetch the per-app bootstrap (version requirements, UDP availability, spatial limits).
45
- const bootstrap = await client.serverStatus.gameClientBootstrap('1');
46
- console.log(bootstrap.versionInfo.minimumClientVersion);
44
+ // `client.auth.login()` returns an identity SESSION token (Management API only).
45
+ // Identity reads run on it:
46
+ const me = await client.users.me();
47
+ console.log(me.email);
47
48
  ```
48
49
 
49
- Both endpoints share a single `AuthState`, so once `client.auth.login()` returns, every subsequent SDK call (against either endpoint) carries the bearer token automatically.
50
+ **Gameplay needs an app-scoped token, not the login token.** Mint one per app and
51
+ drive the Game API world/UDP surface (including `gameClientBootstrap`) from a
52
+ per-game client — see [Overworld portals & app-scoped tokens (v7)](#overworld-portals--app-scoped-tokens-v7).
50
53
 
51
54
  If `managementUrl` is omitted, the SDK falls back to `httpUrl` for backwards-compat with the single-endpoint deployment.
52
55
 
@@ -90,16 +93,17 @@ If `managementUrl` is omitted, the SDK falls back to `httpUrl` for backwards-com
90
93
  |---|---|
91
94
  | `client.operator` | Control plane: cross-org environments, change orders, secrets, release management, audit. |
92
95
 
93
- Auth, user reads, and the studio-admin / operator surfaces target `managementUrl`; the game-client world/UDP surfaces target `httpUrl` / `wsUrl`. A single shared `AuthState` carries the bearer token to whichever endpoint serves each call.
96
+ Auth, user reads, and the studio-admin / operator surfaces target `managementUrl` and use the **identity session token**; the game-client world/UDP surfaces target `httpUrl` / `wsUrl` and require an **app-scoped token** for that app. Use one identity client plus a per-game client (see [Overworld portals & app-scoped tokens (v7)](#overworld-portals--app-scoped-tokens-v7)); each client's `AuthState` carries its token to its own endpoints, so HTTP and WebSocket auth never drift within a client.
94
97
 
95
98
  ## Game-loop lifecycle
96
99
 
97
- 1. Authenticate with `client.auth.login()` or restore a previous token through `client.session.restore()`.
98
- 2. Subscribe to UDP proxy notifications with `client.udp.subscribe(handlers, appId)` `appId` is **required** (the SDK opens the realtime socket on demand and scopes it to that app).
99
- 3. Join a chunk by sending an initial actor update.
100
- 4. Send actor, voxel, text, audio, and client-event updates through `client.udp` or the higher-level `client.world(appId)` helpers.
101
- 5. Call `client.udp.disconnect()` when leaving the world.
102
- 6. Call `client.close()` when disposing the SDK instance.
100
+ 1. Authenticate on the identity client with `client.auth.login()` (or `client.session.restore()`) — this yields the **session token**.
101
+ 2. Mint an **app-scoped token** for the app (`identity.portal.mintAppToken(appId)`, or the PKCE portal flow across origins) and build a per-game client holding it (`game.setToken(token)`). The gameplay steps below run on that **game** client.
102
+ 3. Subscribe to UDP proxy notifications with `game.udp.subscribe(handlers, appId)` — `appId` is **required** (the SDK opens the realtime socket on demand and scopes it to that app).
103
+ 4. Join a chunk by sending an initial actor update.
104
+ 5. Send actor, voxel, text, audio, and client-event updates through `game.udp` or the higher-level `game.world(appId)` helpers.
105
+ 6. Call `game.udp.disconnect()` when leaving the world; `game.portal.refresh()` before the token expires to keep playing.
106
+ 7. Call `client.close()` (and `game.close()`) when disposing the SDK instances.
103
107
 
104
108
  ## Per-app routing
105
109
 
@@ -118,18 +122,21 @@ query AppForRouting($appId: BigInt!) {
118
122
 
119
123
  `gameApiUrl` is populated for **both** dedicated (`splitMode`) and shared
120
124
  (`deploymentTarget: "shared"`) apps. When it's set, build a **second**
121
- `CrowdyClient` with `httpUrl: gameApiUrl` (and the matching `wsUrl`) **sharing the
122
- same `tokenStore` as the first client**, then drive gameplay through that client.
123
- Apps with no `gameApiUrl` keep working against the default `httpUrl` you
124
- configured.
125
+ `CrowdyClient` with `httpUrl: gameApiUrl` (and the matching `wsUrl`) holding that
126
+ app's **app-scoped token** (`identity.portal.mintAppToken(appId)` do **not**
127
+ reuse the identity client's session token store), then drive gameplay through that
128
+ client. In practice `mintAppToken` already returns `gameApiUrl` / `gameApiWsUrl`,
129
+ so you rarely need this separate routing query. Apps with no `gameApiUrl` keep
130
+ working against the default `httpUrl` you configured.
125
131
 
126
132
  ## Realtime notifications
127
133
 
128
134
  `subscribe` takes the handlers **and a required `appId`** (second argument). The
129
135
  Game API scopes the realtime session to that app and rejects an app-agnostic
130
- subscription with a `RealtimeConnectionEvent` (`code: 'APP_ID_REQUIRED'`). Run
131
- one client per app (sharing the same `tokenStore`) when a player is in multiple
132
- apps at once.
136
+ subscription with a `RealtimeConnectionEvent` (`code: 'APP_ID_REQUIRED'`). It also
137
+ rejects an identity session token (`APP_TOKEN_REQUIRED`) or a token scoped to a
138
+ different app (`APP_SCOPE_MISMATCH`). Run one client per app (each holding that
139
+ app's app-scoped token) when a player is in multiple apps at once.
133
140
 
134
141
  ```ts
135
142
  const appId = '1';
@@ -258,7 +265,7 @@ The key parameter is optional and trailing, so it's safe to omit. Requires a ser
258
265
 
259
266
  - Use `client.auth.setToken(token)` if you need to seed a token externally (e.g. when restoring auth from a non-default storage).
260
267
  - `client.session.restore()` reads from the configured `tokenStore`. `BrowserLocalStorageTokenStore` is provided; bring your own for SSR or Node usage.
261
- - A single `AuthState` is observed by both the HTTP client and the realtime socket, so HTTP and WebSocket auth can never drift.
268
+ - Each client's `AuthState` is observed by both its HTTP client and its realtime socket, so HTTP and WebSocket auth never drift within a client. Hold the **identity session token** on the management/identity client and an **app-scoped token** on each per-game client (`client.portal` — see the [v7 section](#overworld-portals--app-scoped-tokens-v7)).
262
269
 
263
270
  ## Overworld portals & app-scoped tokens (v7)
264
271
 
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@
41
41
  * or internal tooling, never an untrusted browser.
42
42
  */
43
43
  /** The published package version. Mirrors `package.json`. */
44
- export declare const VERSION = "7.1.0";
44
+ export declare const VERSION = "7.1.1";
45
45
  export { CrowdyClient, createCrowdyClient, type CrowdyClientConfig, } from './crowdy-client.js';
46
46
  export { BrowserLocalStorageTokenStore, SessionStore, type SessionListener, type TokenStore, } from './session.js';
47
47
  export { GraphQLClient, GraphQLTransport, type GraphQLClientConfig, } from './client.js';
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@
41
41
  * or internal tooling, never an untrusted browser.
42
42
  */
43
43
  /** The published package version. Mirrors `package.json`. */
44
- export const VERSION = '7.1.0';
44
+ export const VERSION = '7.1.1';
45
45
  export { CrowdyClient, createCrowdyClient, } from './crowdy-client.js';
46
46
  export { BrowserLocalStorageTokenStore, SessionStore, } from './session.js';
47
47
  export { GraphQLClient, GraphQLTransport, } from './client.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdedkingdoms/crowdyjs",
3
- "version": "7.1.0",
3
+ "version": "7.1.1",
4
4
  "description": "Client SDK for Crowded Kingdoms GraphQL API with UDP proxy support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",