@crowdedkingdomstudios/crowdyjs 3.0.0 → 4.0.0
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 +125 -57
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/crowdy-client.d.ts +41 -13
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +41 -16
- package/dist/domains/apps.d.ts +48 -20
- package/dist/domains/apps.d.ts.map +1 -1
- package/dist/domains/apps.js +58 -35
- package/dist/domains/auth.d.ts +33 -22
- package/dist/domains/auth.d.ts.map +1 -1
- package/dist/domains/auth.js +51 -33
- package/dist/domains/udp.d.ts +9 -1
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +14 -1
- package/dist/domains/users.d.ts +19 -16
- package/dist/domains/users.d.ts.map +1 -1
- package/dist/domains/users.js +21 -39
- package/dist/generated/graphql.d.ts +1077 -7
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +16 -9
- package/dist/index.d.ts +29 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -15
- package/dist/realtime.d.ts +3 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +3 -0
- package/dist/types.d.ts +2 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -33
- package/dist/world.d.ts +9 -0
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +17 -0
- package/package.json +2 -1
- package/dist/domains/appAccess.d.ts +0 -23
- package/dist/domains/appAccess.d.ts.map +0 -1
- package/dist/domains/appAccess.js +0 -42
- package/dist/domains/billing.d.ts +0 -17
- package/dist/domains/billing.d.ts.map +0 -1
- package/dist/domains/billing.js +0 -31
- package/dist/domains/organizations.d.ts +0 -33
- package/dist/domains/organizations.d.ts.map +0 -1
- package/dist/domains/organizations.js +0 -90
- package/dist/domains/payments.d.ts +0 -20
- package/dist/domains/payments.d.ts.map +0 -1
- package/dist/domains/payments.js +0 -28
- package/dist/domains/quotas.d.ts +0 -20
- package/dist/domains/quotas.d.ts.map +0 -1
- package/dist/domains/quotas.js +0 -34
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
# CrowdyJS
|
|
1
|
+
# CrowdyJS
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
API, the UDP proxy subscription, auth/session state, and spatial send helpers in
|
|
5
|
-
one typed client.
|
|
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.
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
@@ -10,11 +8,9 @@ one typed client.
|
|
|
10
8
|
npm install @crowdedkingdomstudios/crowdyjs
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
CrowdyJS
|
|
14
|
-
`crypto`, `btoa`, and `atob`. Node tools can still use the SDK, but must provide
|
|
15
|
-
browser-compatible globals when they open realtime connections.
|
|
11
|
+
CrowdyJS v4 targets browsers by default and uses native `fetch`, `WebSocket`, `crypto`, `btoa`, and `atob`. Node tools can still use the SDK, but must provide browser-compatible globals when opening realtime connections.
|
|
16
12
|
|
|
17
|
-
## Quick
|
|
13
|
+
## Quick start
|
|
18
14
|
|
|
19
15
|
```ts
|
|
20
16
|
import {
|
|
@@ -23,8 +19,11 @@ import {
|
|
|
23
19
|
} from '@crowdedkingdomstudios/crowdyjs';
|
|
24
20
|
|
|
25
21
|
const client = createCrowdyClient({
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
// Game API (world data + UDP proxy)
|
|
23
|
+
httpUrl: 'https://game.example.com',
|
|
24
|
+
wsUrl: 'wss://game.example.com',
|
|
25
|
+
// Management API (login, register, profile)
|
|
26
|
+
managementUrl: 'https://management.example.com',
|
|
28
27
|
tokenStore: new BrowserLocalStorageTokenStore(),
|
|
29
28
|
realtime: {
|
|
30
29
|
retryAttempts: 8,
|
|
@@ -32,35 +31,78 @@ const client = createCrowdyClient({
|
|
|
32
31
|
},
|
|
33
32
|
});
|
|
34
33
|
|
|
34
|
+
// Restore a previous session if there is one, otherwise log in.
|
|
35
35
|
await client.session.restore();
|
|
36
|
-
|
|
37
36
|
if (!client.session.getToken()) {
|
|
38
37
|
await client.auth.login({ email: 'player@example.com', password: 'secret' });
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
// Fetch the per-app bootstrap (version requirements, UDP availability, spatial limits).
|
|
41
41
|
const bootstrap = await client.serverStatus.gameClientBootstrap('1');
|
|
42
42
|
console.log(bootstrap.versionInfo.minimumClientVersion);
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Both endpoints share a single `AuthState`, so once `client.auth.login()` returns, every subsequent SDK call (against either endpoint) carries the bearer token automatically.
|
|
46
|
+
|
|
47
|
+
If `managementUrl` is omitted, the SDK falls back to `httpUrl` for backwards-compat with the single-endpoint deployment.
|
|
48
|
+
|
|
49
|
+
## Sub-clients at a glance
|
|
50
|
+
|
|
51
|
+
| Sub-client | What it does |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `client.auth` | Register, log in, log out, password reset, email confirmation. |
|
|
54
|
+
| `client.users` | `me`, `updateGamertag`, profile reads. |
|
|
55
|
+
| `client.session` | Token store, `restore()`, `getToken()`, manual `setToken()`. |
|
|
56
|
+
| `client.serverStatus` | `gameClientBootstrap(appId)` — per-app version info, UDP status, spatial limits. |
|
|
57
|
+
| `client.chunks`, `client.voxels`, `client.actors`, `client.avatars`, `client.state` | World data reads + writes. |
|
|
58
|
+
| `client.teleport` | Teleport requests. |
|
|
59
|
+
| `client.udp` | UDP proxy subscriptions + spatial mutations (`sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`, `sendTextPacket`, `sendClientEvent`). |
|
|
60
|
+
| `client.realtime` | Connection status, manual `connect()` / `disconnect()`, `onStatus()` listener. |
|
|
61
|
+
| `client.world(appId)` | Higher-level helpers for browser games (`actor.join`, `actor.sendState`, `actor.sendText`). |
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
Auth and user reads always target `managementUrl`. Everything else targets `httpUrl` / `wsUrl`.
|
|
64
|
+
|
|
65
|
+
## Game-loop lifecycle
|
|
66
|
+
|
|
67
|
+
1. Authenticate with `client.auth.login()` or restore a previous token through `client.session.restore()`.
|
|
68
|
+
2. Subscribe to UDP proxy notifications with `client.udp.subscribe()` (the SDK will open the realtime socket on demand).
|
|
51
69
|
3. Join a chunk by sending an initial actor update.
|
|
52
|
-
4. Send actor, voxel, text, audio, and client-event updates through `client.udp`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
when disposing the SDK instance.
|
|
70
|
+
4. Send actor, voxel, text, audio, and client-event updates through `client.udp` or the higher-level `client.world(appId)` helpers.
|
|
71
|
+
5. Call `client.udp.disconnect()` when leaving the world.
|
|
72
|
+
6. Call `client.close()` when disposing the SDK instance.
|
|
56
73
|
|
|
57
|
-
##
|
|
74
|
+
## Per-app routing
|
|
75
|
+
|
|
76
|
+
When a player is about to join an app, query its routing fields on the management API first:
|
|
77
|
+
|
|
78
|
+
```graphql
|
|
79
|
+
query AppForRouting($id: BigInt!) {
|
|
80
|
+
app(id: $id) {
|
|
81
|
+
appId
|
|
82
|
+
splitMode
|
|
83
|
+
gameApiUrl
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If `splitMode && gameApiUrl`, the app lives behind its own Game API deployment. Build a **second** `CrowdyClient` with `httpUrl: gameApiUrl` (and the matching `wsUrl`) **sharing the same `tokenStore` as the first client**, then drive gameplay through that client. Apps without `splitMode` keep working against the default `httpUrl` you configured.
|
|
89
|
+
|
|
90
|
+
## Realtime notifications
|
|
58
91
|
|
|
59
92
|
```ts
|
|
60
93
|
const unsubscribe = client.udp.subscribe({
|
|
61
94
|
actorUpdate: (event) => {
|
|
62
95
|
console.log(event.uuid, event.state);
|
|
63
96
|
},
|
|
97
|
+
voxelUpdate: (event) => { /* ... */ },
|
|
98
|
+
text: (event) => { /* ... */ },
|
|
99
|
+
audio: (event) => { /* ... */ },
|
|
100
|
+
clientEvent: (event) => { /* ... */ },
|
|
101
|
+
serverEvent: (event) => { /* ... */ },
|
|
102
|
+
singleActorMessage: (event) => {
|
|
103
|
+
// A direct actor-to-actor message addressed to you.
|
|
104
|
+
console.log(event.uuid, event.payload); // payload is base64
|
|
105
|
+
},
|
|
64
106
|
genericError: (event) => {
|
|
65
107
|
console.warn(event.sequenceNumber, event.errorCode);
|
|
66
108
|
},
|
|
@@ -75,20 +117,21 @@ const unsubscribe = client.udp.subscribe({
|
|
|
75
117
|
client.realtime.onStatus((status) => {
|
|
76
118
|
console.log('realtime:', status);
|
|
77
119
|
});
|
|
120
|
+
|
|
121
|
+
// Later:
|
|
122
|
+
unsubscribe();
|
|
78
123
|
```
|
|
79
124
|
|
|
80
|
-
The SDK uses the `graphql-transport-ws` protocol through `graphql-ws`, reconnects
|
|
81
|
-
with backoff, re-reads the current token before reconnecting, and resubscribes to
|
|
82
|
-
the generated `UdpNotifications` document.
|
|
125
|
+
The SDK uses the `graphql-transport-ws` protocol through `graphql-ws`, reconnects with backoff, re-reads the current token before reconnecting, and resubscribes automatically.
|
|
83
126
|
|
|
84
|
-
##
|
|
127
|
+
## Spatial sends
|
|
85
128
|
|
|
86
129
|
```ts
|
|
87
130
|
const response = await client.udp.sendActorUpdateAndWait({
|
|
88
131
|
appId: '1',
|
|
89
132
|
chunk: { x: '0', y: '0', z: '0' },
|
|
90
133
|
uuid: '0123456789abcdef0123456789abcdef',
|
|
91
|
-
state: 'AA==',
|
|
134
|
+
state: 'AA==', // base64-encoded payload
|
|
92
135
|
distance: 8,
|
|
93
136
|
decayRate: 1,
|
|
94
137
|
});
|
|
@@ -96,12 +139,24 @@ const response = await client.udp.sendActorUpdateAndWait({
|
|
|
96
139
|
console.log(response.__typename, response.sequenceNumber);
|
|
97
140
|
```
|
|
98
141
|
|
|
99
|
-
The plain `sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`,
|
|
100
|
-
`sendTextPacket`, and `sendClientEvent` methods return the GraphQL mutation
|
|
101
|
-
result immediately. The `AndWait` variants allocate a `sequenceNumber` when one
|
|
102
|
-
is missing and wait for either a matching notification or `GenericErrorResponse`.
|
|
142
|
+
The plain `sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`, `sendTextPacket`, and `sendClientEvent` methods return the GraphQL mutation result immediately. The `AndWait` variants allocate a `sequenceNumber` when one is missing and wait for either a matching notification or `GenericErrorResponse`.
|
|
103
143
|
|
|
104
|
-
|
|
144
|
+
### Actor-to-actor messages
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// Delivered only to the actor whose UUID matches `targetUuid`; you must know
|
|
148
|
+
// that actor's current chunk. Fire-and-forget — the sender gets no echo, so
|
|
149
|
+
// there is no `AndWait` variant. The target receives a
|
|
150
|
+
// `SingleActorMessageNotification` on its subscription.
|
|
151
|
+
await client.udp.sendSingleActorMessage({
|
|
152
|
+
appId: '1',
|
|
153
|
+
chunk: { x: '7', y: '1', z: '2' }, // the TARGET actor's chunk
|
|
154
|
+
targetUuid: '0123456789abcdef0123456789abcdef',
|
|
155
|
+
payload: 'aGVsbG8=', // base64; embed sender identity here if you need it
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## World helpers
|
|
105
160
|
|
|
106
161
|
```ts
|
|
107
162
|
const world = client.world('1');
|
|
@@ -110,29 +165,48 @@ const actor = world.actor();
|
|
|
110
165
|
await actor.join({ x: '0', y: '0', z: '0' });
|
|
111
166
|
await actor.sendState('AA==');
|
|
112
167
|
await actor.sendText('hello nearby players');
|
|
168
|
+
|
|
169
|
+
// Direct message to one other actor (you supply its UUID + current chunk):
|
|
170
|
+
await actor.sendToActor(
|
|
171
|
+
'0123456789abcdef0123456789abcdef',
|
|
172
|
+
'aGVsbG8=', // base64 payload
|
|
173
|
+
{ x: '7', y: '1', z: '2' },
|
|
174
|
+
);
|
|
113
175
|
```
|
|
114
176
|
|
|
115
|
-
The world helpers are
|
|
116
|
-
can always use `client.udp.*` with generated GraphQL input types.
|
|
177
|
+
The world helpers are thin wrappers over `client.udp.*` with the appId pre-bound — convenient for browser games. Advanced callers can always use `client.udp.*` with the generated GraphQL input types directly.
|
|
117
178
|
|
|
118
179
|
## Errors
|
|
119
180
|
|
|
120
|
-
Transport and protocol failures
|
|
181
|
+
Transport and protocol failures throw structured error classes:
|
|
182
|
+
|
|
183
|
+
- `CrowdyHttpError` — non-2xx response from a GraphQL endpoint.
|
|
184
|
+
- `CrowdyGraphQLError` — preserves every GraphQL error including `path` and `extensions.code`.
|
|
185
|
+
- `CrowdyNetworkError` — network-level failure (DNS, TLS, connection refused).
|
|
186
|
+
- `CrowdyTimeoutError` — request or `AndWait` timed out.
|
|
187
|
+
- `CrowdyRealtimeError` — realtime subscription couldn't be established or was dropped.
|
|
188
|
+
- `CrowdyProtocolError` — server response failed schema validation.
|
|
121
189
|
|
|
122
|
-
|
|
123
|
-
- `CrowdyGraphQLError`
|
|
124
|
-
- `CrowdyNetworkError`
|
|
125
|
-
- `CrowdyTimeoutError`
|
|
126
|
-
- `CrowdyRealtimeError`
|
|
127
|
-
- `CrowdyProtocolError`
|
|
190
|
+
## Auth notes
|
|
128
191
|
|
|
129
|
-
`
|
|
130
|
-
`
|
|
192
|
+
- Use `client.auth.setToken(token)` if you need to seed a token externally (e.g. when restoring auth from a non-default storage).
|
|
193
|
+
- `client.session.restore()` reads from the configured `tokenStore`. `BrowserLocalStorageTokenStore` is provided; bring your own for SSR or Node usage.
|
|
194
|
+
- A single `AuthState` is observed by both the HTTP client and the realtime socket, so HTTP and WebSocket auth can never drift.
|
|
131
195
|
|
|
132
|
-
##
|
|
196
|
+
## What's NOT in CrowdyJS
|
|
133
197
|
|
|
134
|
-
|
|
135
|
-
|
|
198
|
+
CrowdyJS focuses on the **game-client surface**: auth, world data, UDP proxy, profile reads. The following operations are **not** exposed by the SDK and should be called against the management GraphQL API directly (with a server-side token, typically from a studio backend):
|
|
199
|
+
|
|
200
|
+
- Org / app / billing / payments / quotas operations
|
|
201
|
+
- Access-tier and runtime-permission administration
|
|
202
|
+
- Game-token issuance / revocation
|
|
203
|
+
- Marketplace and catalog management
|
|
204
|
+
|
|
205
|
+
The SDK is intentionally scoped to client-side, end-user-facing flows.
|
|
206
|
+
|
|
207
|
+
## Low-level GraphQL access
|
|
208
|
+
|
|
209
|
+
Game-client methods are first-class, but generated operation documents are also available through a transport escape hatch:
|
|
136
210
|
|
|
137
211
|
```ts
|
|
138
212
|
import { VersionInfoDocument } from '@crowdedkingdomstudios/crowdyjs/generated';
|
|
@@ -140,18 +214,12 @@ import { VersionInfoDocument } from '@crowdedkingdomstudios/crowdyjs/generated';
|
|
|
140
214
|
const data = await client.graphql.request(VersionInfoDocument);
|
|
141
215
|
```
|
|
142
216
|
|
|
143
|
-
Most consumers should prefer the methods on `client.auth`, `client.udp`,
|
|
144
|
-
`client.serverStatus`, `client.users`, `client.apps`, and `client.world()`.
|
|
217
|
+
Most consumers should prefer the typed methods on `client.auth`, `client.users`, `client.udp`, `client.serverStatus`, and `client.world()`.
|
|
145
218
|
|
|
146
|
-
##
|
|
219
|
+
## Migration
|
|
147
220
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
npm run build
|
|
152
|
-
npm test
|
|
153
|
-
```
|
|
221
|
+
See [MIGRATION.md](MIGRATION.md) for breaking changes between SDK majors.
|
|
222
|
+
|
|
223
|
+
## License
|
|
154
224
|
|
|
155
|
-
|
|
156
|
-
checked out beside this SDK. `npm run check:schema` fails if the committed SDL or
|
|
157
|
-
generated types drift from the API schema.
|
|
225
|
+
MIT
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,mBAAmB,YAAK,EAAE,OAAO,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,EAAE,mBAAmB,YAAK,EAAE,OAAO,EAAE,YAAY;IAUnE,WAAW,IAAI,MAAM;IAIrB;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,UAAU,EAC/B,QAAQ,EAAE,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC,EAChD,SAAS,CAAC,EAAE,UAAU,EACtB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,OAAO,CAAC,OAAO,CAAC;IASnB;;;;OAIG;IACG,KAAK,CAAC,CAAC,GAAG,GAAG,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACvC,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACrC,OAAO,CAAC,CAAC,CAAC;CA+Cd;AAED,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -12,7 +12,9 @@ import { CrowdyError, CrowdyGraphQLError, CrowdyHttpError, CrowdyNetworkError, C
|
|
|
12
12
|
export class GraphQLClient {
|
|
13
13
|
constructor(config = {}, session) {
|
|
14
14
|
this.graphqlEndpoint =
|
|
15
|
-
config.
|
|
15
|
+
config.graphqlEndpoint ||
|
|
16
|
+
config.httpUrl ||
|
|
17
|
+
'http://localhost:3000/graphql';
|
|
16
18
|
this.timeout = config.timeout || 60000;
|
|
17
19
|
this.session = session;
|
|
18
20
|
this.logger = config.logger ?? silentLogger;
|
package/dist/crowdy-client.d.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public surface of the SDK. Construct one `CrowdyClient` per session and
|
|
3
3
|
* access everything via the typed sub-clients (`client.auth`, `client.udp`,
|
|
4
|
-
* `client.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `client.chunks`, ...).
|
|
5
|
+
*
|
|
6
|
+
* The management/game-api split means CrowdyJS now talks to **two** GraphQL
|
|
7
|
+
* endpoints behind the scenes:
|
|
8
|
+
*
|
|
9
|
+
* - `managementUrl` / `managementGraphqlEndpoint` -> `cks-management-api`
|
|
10
|
+
* used by `auth` (login, register, logout, password / email flows) and
|
|
11
|
+
* `users` (me, updateGamertag, deleteMyAccount). This is also where
|
|
12
|
+
* `game_tokens` are minted.
|
|
13
|
+
*
|
|
14
|
+
* - `httpUrl` / `graphqlEndpoint` -> `cks-game-api`
|
|
15
|
+
* used by every game / world / replication sub-client
|
|
16
|
+
* (`chunks`, `voxels`, `actors`, `teleport`, `state`, `serverStatus`,
|
|
17
|
+
* `udp`). WebSocket subscriptions (`wsUrl`) also target this endpoint.
|
|
18
|
+
*
|
|
19
|
+
* A single `AuthState` is shared across both clients, so once
|
|
20
|
+
* `client.auth.login()` returns, every subsequent SDK call (against either
|
|
21
|
+
* endpoint) carries the Bearer token automatically.
|
|
7
22
|
*/
|
|
8
23
|
import { AuthState } from './auth-state.js';
|
|
9
24
|
import { GraphQLClient } from './client.js';
|
|
@@ -13,12 +28,7 @@ import type { TokenStore } from './session.js';
|
|
|
13
28
|
import { WorldClient } from './world.js';
|
|
14
29
|
import { AuthAPI } from './domains/auth.js';
|
|
15
30
|
import { UsersAPI } from './domains/users.js';
|
|
16
|
-
import { OrganizationsAPI } from './domains/organizations.js';
|
|
17
31
|
import { AppsAPI } from './domains/apps.js';
|
|
18
|
-
import { AppAccessAPI } from './domains/appAccess.js';
|
|
19
|
-
import { BillingAPI } from './domains/billing.js';
|
|
20
|
-
import { QuotasAPI } from './domains/quotas.js';
|
|
21
|
-
import { PaymentsAPI } from './domains/payments.js';
|
|
22
32
|
import { ChunksAPI } from './domains/chunks.js';
|
|
23
33
|
import { VoxelsAPI } from './domains/voxels.js';
|
|
24
34
|
import { ActorsAPI } from './domains/actors.js';
|
|
@@ -27,10 +37,24 @@ import { StateAPI } from './domains/state.js';
|
|
|
27
37
|
import { ServerStatusAPI } from './domains/serverStatus.js';
|
|
28
38
|
import { UdpAPI } from './domains/udp.js';
|
|
29
39
|
export interface CrowdyClientConfig {
|
|
40
|
+
/** game-api HTTP root (e.g. `https://dev-game-api.crowdedkingdoms.com`). */
|
|
30
41
|
httpUrl?: string;
|
|
42
|
+
/** game-api WS root. */
|
|
31
43
|
wsUrl?: string;
|
|
44
|
+
/** game-api GraphQL endpoint. Defaults to `${httpUrl}/graphql`. */
|
|
32
45
|
graphqlEndpoint?: string;
|
|
46
|
+
/** game-api WS endpoint. Defaults to `${wsUrl}/graphql`. */
|
|
33
47
|
wsEndpoint?: string;
|
|
48
|
+
/**
|
|
49
|
+
* management-api HTTP root (e.g.
|
|
50
|
+
* `https://dev-management-api.crowdedkingdoms.com`). When set,
|
|
51
|
+
* `client.auth` and `client.users` route here. If left empty the SDK
|
|
52
|
+
* falls back to `httpUrl` for backwards-compatibility with the legacy
|
|
53
|
+
* single-endpoint deployment, but new code should set this explicitly.
|
|
54
|
+
*/
|
|
55
|
+
managementUrl?: string;
|
|
56
|
+
/** management-api GraphQL endpoint. Defaults to `${managementUrl}/graphql`. */
|
|
57
|
+
managementGraphqlEndpoint?: string;
|
|
34
58
|
timeout?: number;
|
|
35
59
|
tokenStore?: TokenStore;
|
|
36
60
|
logger?: CrowdyLogger;
|
|
@@ -42,17 +66,17 @@ export interface CrowdyClientConfig {
|
|
|
42
66
|
};
|
|
43
67
|
}
|
|
44
68
|
export declare class CrowdyClient {
|
|
69
|
+
/** Shared token state for both game-api and management-api requests. */
|
|
45
70
|
readonly session: AuthState;
|
|
71
|
+
/** game-api HTTP client. */
|
|
46
72
|
readonly graphql: GraphQLClient;
|
|
73
|
+
/** game-api WebSocket subscription manager. */
|
|
47
74
|
readonly realtime: SubscriptionManager;
|
|
75
|
+
/** management-api HTTP client. Same `AuthState` as `graphql`. */
|
|
76
|
+
readonly management: GraphQLClient;
|
|
48
77
|
readonly auth: AuthAPI;
|
|
49
78
|
readonly users: UsersAPI;
|
|
50
|
-
readonly orgs: OrganizationsAPI;
|
|
51
79
|
readonly apps: AppsAPI;
|
|
52
|
-
readonly appAccess: AppAccessAPI;
|
|
53
|
-
readonly billing: BillingAPI;
|
|
54
|
-
readonly quotas: QuotasAPI;
|
|
55
|
-
readonly payments: PaymentsAPI;
|
|
56
80
|
readonly chunks: ChunksAPI;
|
|
57
81
|
readonly voxels: VoxelsAPI;
|
|
58
82
|
readonly actors: ActorsAPI;
|
|
@@ -61,6 +85,10 @@ export declare class CrowdyClient {
|
|
|
61
85
|
readonly serverStatus: ServerStatusAPI;
|
|
62
86
|
readonly udp: UdpAPI;
|
|
63
87
|
constructor(config?: CrowdyClientConfig);
|
|
88
|
+
/** Imperatively set the Bearer token (useful for SSO / token rehydrate). */
|
|
89
|
+
setToken(token: string | null): void;
|
|
90
|
+
/** Read the current Bearer token (null if no session). */
|
|
91
|
+
getToken(): string | null;
|
|
64
92
|
world(appId: string): WorldClient;
|
|
65
93
|
/** Closes the WebSocket and clears the in-memory auth token. */
|
|
66
94
|
close(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crowdy-client.d.ts","sourceRoot":"","sources":["../src/crowdy-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"crowdy-client.d.ts","sourceRoot":"","sources":["../src/crowdy-client.ts"],"names":[],"mappings":"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"}
|
package/dist/crowdy-client.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public surface of the SDK. Construct one `CrowdyClient` per session and
|
|
3
3
|
* access everything via the typed sub-clients (`client.auth`, `client.udp`,
|
|
4
|
-
* `client.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `client.chunks`, ...).
|
|
5
|
+
*
|
|
6
|
+
* The management/game-api split means CrowdyJS now talks to **two** GraphQL
|
|
7
|
+
* endpoints behind the scenes:
|
|
8
|
+
*
|
|
9
|
+
* - `managementUrl` / `managementGraphqlEndpoint` -> `cks-management-api`
|
|
10
|
+
* used by `auth` (login, register, logout, password / email flows) and
|
|
11
|
+
* `users` (me, updateGamertag, deleteMyAccount). This is also where
|
|
12
|
+
* `game_tokens` are minted.
|
|
13
|
+
*
|
|
14
|
+
* - `httpUrl` / `graphqlEndpoint` -> `cks-game-api`
|
|
15
|
+
* used by every game / world / replication sub-client
|
|
16
|
+
* (`chunks`, `voxels`, `actors`, `teleport`, `state`, `serverStatus`,
|
|
17
|
+
* `udp`). WebSocket subscriptions (`wsUrl`) also target this endpoint.
|
|
18
|
+
*
|
|
19
|
+
* A single `AuthState` is shared across both clients, so once
|
|
20
|
+
* `client.auth.login()` returns, every subsequent SDK call (against either
|
|
21
|
+
* endpoint) carries the Bearer token automatically.
|
|
7
22
|
*/
|
|
8
23
|
import { AuthState } from './auth-state.js';
|
|
9
24
|
import { GraphQLClient } from './client.js';
|
|
@@ -11,12 +26,7 @@ import { SubscriptionManager } from './subscriptions.js';
|
|
|
11
26
|
import { WorldClient } from './world.js';
|
|
12
27
|
import { AuthAPI } from './domains/auth.js';
|
|
13
28
|
import { UsersAPI } from './domains/users.js';
|
|
14
|
-
import { OrganizationsAPI } from './domains/organizations.js';
|
|
15
29
|
import { AppsAPI } from './domains/apps.js';
|
|
16
|
-
import { AppAccessAPI } from './domains/appAccess.js';
|
|
17
|
-
import { BillingAPI } from './domains/billing.js';
|
|
18
|
-
import { QuotasAPI } from './domains/quotas.js';
|
|
19
|
-
import { PaymentsAPI } from './domains/payments.js';
|
|
20
30
|
import { ChunksAPI } from './domains/chunks.js';
|
|
21
31
|
import { VoxelsAPI } from './domains/voxels.js';
|
|
22
32
|
import { ActorsAPI } from './domains/actors.js';
|
|
@@ -39,14 +49,21 @@ export class CrowdyClient {
|
|
|
39
49
|
logger: config.logger,
|
|
40
50
|
...config.realtime,
|
|
41
51
|
}, this.session);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
|
|
52
|
+
const managementGraphqlEndpoint = config.managementGraphqlEndpoint ??
|
|
53
|
+
(config.managementUrl
|
|
54
|
+
? `${config.managementUrl.replace(/\/$/, '')}/graphql`
|
|
55
|
+
: config.graphqlEndpoint);
|
|
56
|
+
// Management-api client. Falls back to game-api endpoint if the caller
|
|
57
|
+
// hasn't configured `managementUrl` yet (single-endpoint legacy mode).
|
|
58
|
+
this.management = new GraphQLClient({
|
|
59
|
+
httpUrl: config.managementUrl ?? config.httpUrl,
|
|
60
|
+
graphqlEndpoint: managementGraphqlEndpoint,
|
|
61
|
+
timeout: config.timeout,
|
|
62
|
+
logger: config.logger,
|
|
63
|
+
}, this.session);
|
|
64
|
+
this.auth = new AuthAPI(this.management, this.session);
|
|
65
|
+
this.users = new UsersAPI(this.management);
|
|
66
|
+
this.apps = new AppsAPI(this.management);
|
|
50
67
|
this.chunks = new ChunksAPI(this.graphql);
|
|
51
68
|
this.voxels = new VoxelsAPI(this.graphql);
|
|
52
69
|
this.actors = new ActorsAPI(this.graphql);
|
|
@@ -55,6 +72,14 @@ export class CrowdyClient {
|
|
|
55
72
|
this.serverStatus = new ServerStatusAPI(this.graphql);
|
|
56
73
|
this.udp = new UdpAPI(this.graphql, this.realtime);
|
|
57
74
|
}
|
|
75
|
+
/** Imperatively set the Bearer token (useful for SSO / token rehydrate). */
|
|
76
|
+
setToken(token) {
|
|
77
|
+
this.session.setToken(token);
|
|
78
|
+
}
|
|
79
|
+
/** Read the current Bearer token (null if no session). */
|
|
80
|
+
getToken() {
|
|
81
|
+
return this.session.getToken();
|
|
82
|
+
}
|
|
58
83
|
world(appId) {
|
|
59
84
|
return new WorldClient(appId, this.udp);
|
|
60
85
|
}
|
package/dist/domains/apps.d.ts
CHANGED
|
@@ -1,27 +1,55 @@
|
|
|
1
|
-
import type { GraphQLClient } from '../client.js';
|
|
2
|
-
import { type AppQuery, type AppQueryVariables, type AppBySlugQuery, type AppBySlugQueryVariables, type MyAppsQuery, type AppsForOrgQuery, type AppsForOrgQueryVariables, type MarketplaceAppsQuery, type MarketplaceAppsQueryVariables, type CreateAppMutation, type CreateAppMutationVariables, type UpdateAppMutation, type UpdateAppMutationVariables, type ArchiveAppMutationVariables, type SetAppVisibilityMutation, type SetAppVisibilityMutationVariables } from '../generated/graphql.js';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Apps sub-client. Targets `cks-management-api` (where the apps catalog
|
|
3
|
+
* lives). After the DB split each app may be served by its own per-tenant
|
|
4
|
+
* cks-game-api; the marketplace returns `gameApiUrl` for those rows so the
|
|
5
|
+
* caller can build a per-app `CrowdyClient` against the correct endpoint.
|
|
6
|
+
*
|
|
7
|
+
* Typical pattern:
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* const baseClient = createCrowdyClient({
|
|
10
|
+
* managementUrl: 'https://api.example.com',
|
|
11
|
+
* httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
|
|
12
|
+
* });
|
|
13
|
+
* await baseClient.auth.login({ email, password });
|
|
14
|
+
*
|
|
15
|
+
* const route = await baseClient.apps.routeFor(appId);
|
|
16
|
+
* if (route.splitMode && route.gameApiUrl) {
|
|
17
|
+
* const perAppClient = createCrowdyClient({
|
|
18
|
+
* managementUrl: 'https://api.example.com',
|
|
19
|
+
* httpUrl: route.gameApiUrl,
|
|
20
|
+
* wsUrl: route.gameApiUrl.replace(/^https?/, 'wss'),
|
|
21
|
+
* tokenStore: baseClient.session.tokenStore,
|
|
22
|
+
* });
|
|
23
|
+
* // drive gameplay through perAppClient
|
|
24
|
+
* }
|
|
9
25
|
*/
|
|
26
|
+
import type { GraphQLClient } from '../client.js';
|
|
27
|
+
import { type AppQuery, type AppBySlugQuery, type MyAppsQuery } from '../generated/graphql.js';
|
|
28
|
+
/**
|
|
29
|
+
* Subset of an `App` row that the SDK exposes for routing decisions. The
|
|
30
|
+
* fields are typed loosely as `unknown` because the generated types lag
|
|
31
|
+
* behind the `splitMode` / `gameApiUrl` selection until codegen runs.
|
|
32
|
+
*/
|
|
33
|
+
export interface AppRoute {
|
|
34
|
+
appId: string;
|
|
35
|
+
splitMode: boolean;
|
|
36
|
+
gameApiUrl: string | null;
|
|
37
|
+
}
|
|
10
38
|
export declare class AppsAPI {
|
|
11
|
-
private
|
|
12
|
-
constructor(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
39
|
+
private readonly management;
|
|
40
|
+
constructor(management: GraphQLClient);
|
|
41
|
+
/** Fetch a single app by id. */
|
|
42
|
+
app(appId: string): Promise<AppQuery['app']>;
|
|
43
|
+
/** Fetch by org slug + app slug (marketplace links). */
|
|
44
|
+
appBySlug(orgSlug: string, appSlug: string): Promise<AppBySlugQuery['appBySlug']>;
|
|
45
|
+
/** Apps the caller can play (org membership OR active access). */
|
|
16
46
|
myApps(): Promise<MyAppsQuery['myApps']>;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}>;
|
|
25
|
-
setVisibility(args: SetAppVisibilityMutationVariables): Promise<SetAppVisibilityMutation['setAppVisibility']>;
|
|
47
|
+
/**
|
|
48
|
+
* Convenience: returns just the routing tuple for a given app. If the
|
|
49
|
+
* app row is missing or the API does not expose split-mode fields yet,
|
|
50
|
+
* returns `{ appId, splitMode: false, gameApiUrl: null }` so the caller
|
|
51
|
+
* keeps using the legacy single-endpoint deployment.
|
|
52
|
+
*/
|
|
53
|
+
routeFor(appId: string): Promise<AppRoute>;
|
|
26
54
|
}
|
|
27
55
|
//# sourceMappingURL=apps.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/domains/apps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/domains/apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAIL,KAAK,QAAQ,EAEb,KAAK,cAAc,EAEnB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAEjC;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAcD,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,aAAa;IAEtD,gCAAgC;IAC1B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAQlD,wDAAwD;IAClD,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAQvC,kEAAkE;IAC5D,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAQ9C;;;;;OAKG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAUjD"}
|