@rydr/game-sdk 1.8.0 → 1.8.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 +25 -24
- package/dist/client/adminContent.d.ts +17 -16
- package/dist/client/adminContent.d.ts.map +1 -1
- package/dist/client/adminContent.js +17 -16
- package/dist/client/adminContent.js.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/protocol/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ await session.saveContent("songs", "track-1", { title: "…", audioUrl: url });
|
|
|
162
162
|
|
|
163
163
|
### Build an in-game editor
|
|
164
164
|
|
|
165
|
-
Any game can ship an **editor** for its own `shared` content (levels, tracks, charts, worlds, …). The game reads it back through the session (`listContent`/`getContent`) — **one shared backend, no per-game server.** Authoring is **admin-gated**: a write succeeds only when the
|
|
165
|
+
Any game can ship an **editor** for its own `shared` content (levels, tracks, charts, worlds, …). The game reads it back through the session (`listContent`/`getContent`) — **one shared backend, no per-game server.** Authoring is **admin-gated**: a write succeeds only when the user is in the shell's admin mode (the shell holds the secret — the game never does).
|
|
166
166
|
|
|
167
167
|
**The crystal-clear rule:** your game **never handles the secret**. You only:
|
|
168
168
|
|
|
@@ -184,36 +184,37 @@ const tracks = await session.listContent("songs");
|
|
|
184
184
|
|
|
185
185
|
That's the whole contract. **No author allowlist, no secret in your game, no per-game server.** To *become* admin, a user enters the secret once via the shell's `?admin` flow (stored in the shell's localStorage) — your game just reads `isAdmin`.
|
|
186
186
|
|
|
187
|
-
####
|
|
187
|
+
#### There is exactly one way: open the editor in the shell and gate on `isAdmin`
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
**An editor is not a standalone app — it is itself a guest the shell loads.** It is *always* opened inside the platform shell, and it authors through the session, gated on `session.identity.isAdmin`. There is **no** "outside the shell" editor, and a game **never** prompts for, stores, or sends the `ADMIN_SECRET`. It doesn't have the secret and doesn't need it — the shell holds it and relays the authenticated write. If you're pasting a Bearer into a page, that page has stopped being a guest and you've broken the security boundary. Don't.
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
import { createAdminContentBackend } from "@rydr/game-sdk";
|
|
191
|
+
How it works end to end:
|
|
193
192
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return s;
|
|
199
|
-
}
|
|
193
|
+
1. Your editor lives at a path on your game's origin — a route or a static page (e.g. `run-editor.html`).
|
|
194
|
+
2. The shell opens it as a guest via a deep link: `/game/<your-game>/run-editor` mounts `https://<your-game-origin>/run-editor` in the guest iframe; your own host/router resolves the path.
|
|
195
|
+
3. That page calls `connectToPlatform()` exactly like the game does and receives a session with `identity.isAdmin` stamped by the shell.
|
|
196
|
+
4. To *become* admin, the user enters the secret once in the shell's `?admin` flow (stored in the shell's localStorage). The editor only ever reads `isAdmin`.
|
|
200
197
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
await
|
|
210
|
-
await
|
|
211
|
-
|
|
198
|
+
```ts
|
|
199
|
+
// run-editor.html — a guest page, opened in the shell at /game/<your-game>/run-editor
|
|
200
|
+
const session = await connectToPlatform({ gameId: "my-game", capabilities: ["identity"] });
|
|
201
|
+
if (!session.identity.isAdmin) {
|
|
202
|
+
// Not in admin mode — show "unlock admin mode in the shell", author nothing.
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Admin: author through the session. The shell attaches the secret; this iframe never sees it.
|
|
206
|
+
const { uploadUrl, url } = await session.getUploadUrl({ collection: "levels", filename: "bg.png" });
|
|
207
|
+
await fetch(uploadUrl, { method: "PUT", body: file });
|
|
208
|
+
await session.saveContent("levels", "level-1", { name: "…", bgUrl: url, draft: false });
|
|
209
|
+
const levels = await session.listContent("levels"); // includes drafts for an admin; players see only published
|
|
210
|
+
await session.deleteContent("levels", "old");
|
|
212
211
|
```
|
|
213
212
|
|
|
214
|
-
|
|
213
|
+
> **Drafts are your data, not a platform flag.** `saveContent` takes no `draft` argument — a doc's draft/published state is just a field in the content you store (`{ draft: true, … }`). Your editor writes it; your game and lobby read it and hide drafts from non-admins (admins keep seeing them via the same `isAdmin`).
|
|
214
|
+
|
|
215
|
+
> **Security boundary.** `ADMIN_SECRET` is the platform owner's key — full write to **any** game's shared content. It lives only in the shell, is **never shipped to a game or guest**, and is never entered into editor code. Player-generated content uses the `public` owner-write scope (`saveData(..., { scope: "public" })`), not admin auth.
|
|
215
216
|
|
|
216
|
-
>
|
|
217
|
+
> **`createAdminContentBackend` is not for games.** The SDK exports a low-level Bearer client (`createAdminContentBackend`) for the **platform owner's own** out-of-band tooling — a first-party admin console on the shell's origin, a seed/migration script — code that legitimately holds the secret. It is **never** bundled into a game or a game's editor. If you're in a game, use the session.
|
|
217
218
|
|
|
218
219
|
### Shared worlds
|
|
219
220
|
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin content backend —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Admin content backend — a low-level Bearer client for `shared` gamedata,
|
|
3
|
+
* intended for the **platform owner's own** out-of-band tooling: a first-party
|
|
4
|
+
* admin console on the shell's origin, a seed/migration script, CI. Code that
|
|
5
|
+
* legitimately holds the `ADMIN_SECRET`.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
7
|
+
* NOT for games or their editors. A game — and a game's editor, which is just
|
|
8
|
+
* another guest the shell loads — never holds the secret. An editor is opened
|
|
9
|
+
* inside the shell, gates on `session.identity.isAdmin`, and authors through
|
|
10
|
+
* the session (`saveContent` / `deleteContent` / `getUploadUrl`); the shell
|
|
11
|
+
* relays the authenticated write. See the "Build an in-game editor" guide in
|
|
12
|
+
* the README. If you reach for this from inside a game you've broken the
|
|
13
|
+
* security boundary — use the session instead.
|
|
14
|
+
*
|
|
15
|
+
* This factory wraps the generic gamedata party's HTTP contract for one
|
|
16
|
+
* `gameId` so owner tooling doesn't hand-roll `fetch` + headers:
|
|
11
17
|
*
|
|
12
18
|
* - read (public): `GET {host}/parties/gamedata/{gameId}/shared/{collection}[/{id}]`
|
|
13
19
|
* - write (Bearer): `PUT …/shared/{collection}/{id}` body `{ data, draft? }`
|
|
14
20
|
* - delete(Bearer): `DELETE …/shared/{collection}/{id}`
|
|
15
21
|
* - asset (Bearer): `POST …/{gameId}/asset/upload-url` → presigned R2 PUT
|
|
16
22
|
*
|
|
17
|
-
* The game reads the SAME content back through the SDK session
|
|
18
|
-
* (`session.listContent` / `getContent`) — one shared backend, no per-game
|
|
19
|
-
* server. See the "Build an in-game editor" guide in the README.
|
|
20
|
-
*
|
|
21
23
|
* SECURITY: `ADMIN_SECRET` is the platform owner's key (full write to any
|
|
22
|
-
* game's shared content).
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* instead, not this backend.
|
|
24
|
+
* game's shared content). Authoring-time only, entered at runtime, never
|
|
25
|
+
* shipped to players and never bundled into a game or guest. Player-generated
|
|
26
|
+
* content uses the SDK's public owner-write scope instead, not this backend.
|
|
26
27
|
*/
|
|
27
28
|
import type { GameDoc } from "../protocol/gamedata.js";
|
|
28
29
|
/** Bytes accepted by {@link AdminContentBackend.uploadAsset}. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adminContent.d.ts","sourceRoot":"","sources":["../../src/client/adminContent.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"adminContent.d.ts","sourceRoot":"","sources":["../../src/client/adminContent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvD,iEAAiE;AACjE,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,WAAW,GAAG,eAAe,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,2DAA2D;IAC3D,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC7D,2GAA2G;IAC3G,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChG,2BAA2B;IAC3B,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,kGAAkG;IAClG,WAAW,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9H;AAED,MAAM,WAAW,mBAAmB;IAClC,2GAA2G;IAC3G,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,kIAAkI;IAClI,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,CA+CxF"}
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin content backend —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Admin content backend — a low-level Bearer client for `shared` gamedata,
|
|
3
|
+
* intended for the **platform owner's own** out-of-band tooling: a first-party
|
|
4
|
+
* admin console on the shell's origin, a seed/migration script, CI. Code that
|
|
5
|
+
* legitimately holds the `ADMIN_SECRET`.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
7
|
+
* NOT for games or their editors. A game — and a game's editor, which is just
|
|
8
|
+
* another guest the shell loads — never holds the secret. An editor is opened
|
|
9
|
+
* inside the shell, gates on `session.identity.isAdmin`, and authors through
|
|
10
|
+
* the session (`saveContent` / `deleteContent` / `getUploadUrl`); the shell
|
|
11
|
+
* relays the authenticated write. See the "Build an in-game editor" guide in
|
|
12
|
+
* the README. If you reach for this from inside a game you've broken the
|
|
13
|
+
* security boundary — use the session instead.
|
|
14
|
+
*
|
|
15
|
+
* This factory wraps the generic gamedata party's HTTP contract for one
|
|
16
|
+
* `gameId` so owner tooling doesn't hand-roll `fetch` + headers:
|
|
11
17
|
*
|
|
12
18
|
* - read (public): `GET {host}/parties/gamedata/{gameId}/shared/{collection}[/{id}]`
|
|
13
19
|
* - write (Bearer): `PUT …/shared/{collection}/{id}` body `{ data, draft? }`
|
|
14
20
|
* - delete(Bearer): `DELETE …/shared/{collection}/{id}`
|
|
15
21
|
* - asset (Bearer): `POST …/{gameId}/asset/upload-url` → presigned R2 PUT
|
|
16
22
|
*
|
|
17
|
-
* The game reads the SAME content back through the SDK session
|
|
18
|
-
* (`session.listContent` / `getContent`) — one shared backend, no per-game
|
|
19
|
-
* server. See the "Build an in-game editor" guide in the README.
|
|
20
|
-
*
|
|
21
23
|
* SECURITY: `ADMIN_SECRET` is the platform owner's key (full write to any
|
|
22
|
-
* game's shared content).
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* instead, not this backend.
|
|
24
|
+
* game's shared content). Authoring-time only, entered at runtime, never
|
|
25
|
+
* shipped to players and never bundled into a game or guest. Player-generated
|
|
26
|
+
* content uses the SDK's public owner-write scope instead, not this backend.
|
|
26
27
|
*/
|
|
27
28
|
/**
|
|
28
29
|
* Build an {@link AdminContentBackend} for one game. Pure factory — no network
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adminContent.js","sourceRoot":"","sources":["../../src/client/adminContent.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"adminContent.js","sourceRoot":"","sources":["../../src/client/adminContent.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const RYDR_PROTOCOL_VERSION: 3;
|
|
10
10
|
/** Semver of this SDK build. Sent in the handshake for telemetry/debugging. */
|
|
11
|
-
export declare const RYDR_SDK_VERSION = "1.8.
|
|
11
|
+
export declare const RYDR_SDK_VERSION = "1.8.1";
|
|
12
12
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/protocol/version.js
CHANGED