@crowdedkingdomstudios/crowdyjs 5.1.0 → 5.2.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/MIGRATION.md +64 -0
- package/README.md +19 -0
- package/dist/client.d.ts +98 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +74 -5
- package/dist/crowdy-client.d.ts +31 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +8 -0
- package/dist/domains/actors.d.ts +88 -5
- package/dist/domains/actors.d.ts.map +1 -1
- package/dist/domains/actors.js +89 -6
- package/dist/domains/apps.d.ts +95 -41
- package/dist/domains/apps.d.ts.map +1 -1
- package/dist/domains/apps.js +80 -33
- package/dist/domains/auth.d.ts +139 -19
- package/dist/domains/auth.d.ts.map +1 -1
- package/dist/domains/auth.js +137 -17
- package/dist/domains/channels.d.ts +264 -5
- package/dist/domains/channels.d.ts.map +1 -1
- package/dist/domains/channels.js +264 -5
- package/dist/domains/chunks.d.ts +116 -3
- package/dist/domains/chunks.d.ts.map +1 -1
- package/dist/domains/chunks.js +116 -3
- package/dist/domains/gameModel.d.ts +412 -6
- package/dist/domains/gameModel.d.ts.map +1 -1
- package/dist/domains/gameModel.js +412 -6
- package/dist/domains/platform.d.ts +36 -20
- package/dist/domains/platform.d.ts.map +1 -1
- package/dist/domains/platform.js +29 -18
- package/dist/domains/serverStatus.d.ts +74 -6
- package/dist/domains/serverStatus.d.ts.map +1 -1
- package/dist/domains/serverStatus.js +74 -6
- package/dist/domains/state.d.ts +50 -2
- package/dist/domains/state.d.ts.map +1 -1
- package/dist/domains/state.js +50 -2
- package/dist/domains/teams.d.ts +265 -7
- package/dist/domains/teams.d.ts.map +1 -1
- package/dist/domains/teams.js +267 -9
- package/dist/domains/teleport.d.ts +30 -2
- package/dist/domains/teleport.d.ts.map +1 -1
- package/dist/domains/teleport.js +30 -2
- package/dist/domains/udp.d.ts +341 -5
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +341 -5
- package/dist/domains/users.d.ts +42 -11
- package/dist/domains/users.d.ts.map +1 -1
- package/dist/domains/users.js +41 -10
- package/dist/domains/voxels.d.ts +107 -2
- package/dist/domains/voxels.d.ts.map +1 -1
- package/dist/domains/voxels.js +107 -2
- package/dist/errors.d.ts +116 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +100 -0
- package/dist/generated/graphql.d.ts +1787 -110
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +75 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/realtime.d.ts +226 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +90 -0
- package/dist/session.d.ts +46 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -0
- package/dist/types.d.ts +429 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +53 -0
- package/dist/utils.d.ts +86 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +86 -0
- package/dist/world.d.ts +192 -0
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +170 -0
- package/package.json +1 -1
package/dist/domains/actors.js
CHANGED
|
@@ -1,40 +1,123 @@
|
|
|
1
1
|
import { ActorDocument, ActorsDocument, BatchLookupActorsDocument, CreateActorDocument, UpdateActorDocument, DeleteActorDocument, UpdateActorStateDocument, } from '../generated/graphql.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Persisted-actor (player / NPC) CRUD and filtering on the **game-api**.
|
|
4
|
+
* Exposed as `client.actors`.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* An "actor" here is the durable, server-stored record of a participant
|
|
7
|
+
* (identity, owning app, optional avatar, last-known chunk, and a state blob) —
|
|
8
|
+
* not the high-frequency spatial replication stream. For real-time position
|
|
9
|
+
* and state fan-out use the UDP path instead (`client.udp.sendActorUpdate(...)`
|
|
10
|
+
* or the ergonomic `client.world(appId).actor()` helper), which is unchanged
|
|
11
|
+
* and far cheaper per update.
|
|
7
12
|
*
|
|
8
|
-
*
|
|
13
|
+
* Actor ids are exactly **32 ASCII characters** (the UDP-wire actor id), **not**
|
|
14
|
+
* a hyphenated RFC-4122 UUID. Use {@link generateCrowdyUuid} to mint one.
|
|
15
|
+
* `BigInt` values (`appId`, `avatarId`, `userId`) are sent and received as
|
|
16
|
+
* decimal strings.
|
|
17
|
+
*
|
|
18
|
+
* Every method requires an authenticated session (a Bearer token set via
|
|
19
|
+
* `client.auth.login()` or `client.setToken()`) and that the caller is
|
|
20
|
+
* entitled to the target app, or it throws {@link CrowdyGraphQLError}
|
|
21
|
+
* (`UNAUTHENTICATED` / `FORBIDDEN`).
|
|
9
22
|
*/
|
|
10
23
|
export class ActorsAPI {
|
|
11
24
|
constructor(gql) {
|
|
12
25
|
this.gql = gql;
|
|
13
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetch a single persisted actor by its 32-character actor id.
|
|
29
|
+
*
|
|
30
|
+
* @param uuid - The actor's 32-ASCII-character id.
|
|
31
|
+
* @returns The {@link Actor}, or `null` if no actor with that id exists.
|
|
32
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` / `FORBIDDEN` if the caller
|
|
33
|
+
* isn't entitled to the actor's app.
|
|
34
|
+
*/
|
|
14
35
|
async get(uuid) {
|
|
15
36
|
const data = await this.gql.request(ActorDocument, { uuid });
|
|
16
37
|
return data.actor;
|
|
17
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* List persisted actors, optionally narrowed by an {@link ActorFilterInput}
|
|
41
|
+
* (e.g. by app, owning user, or avatar). Omit the filter to use its defaults.
|
|
42
|
+
*
|
|
43
|
+
* @param filter - Optional filter; fields are ANDed together.
|
|
44
|
+
* @returns The matching actors.
|
|
45
|
+
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
46
|
+
*/
|
|
18
47
|
async list(filter) {
|
|
19
48
|
const data = await this.gql.request(ActorsDocument, { filter });
|
|
20
49
|
return data.actors;
|
|
21
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve many actors in one round-trip by id (and/or the other keys the
|
|
53
|
+
* {@link BatchActorLookupInput} accepts). Prefer this over calling
|
|
54
|
+
* {@link get} in a loop — it avoids N requests and N auth checks.
|
|
55
|
+
*
|
|
56
|
+
* @param input - The batch lookup keys.
|
|
57
|
+
* @returns The actors that were found (missing ids are simply omitted).
|
|
58
|
+
*/
|
|
22
59
|
async batchLookup(input) {
|
|
23
60
|
const data = await this.gql.request(BatchLookupActorsDocument, { input });
|
|
24
61
|
return data.batchLookupActors;
|
|
25
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Create a persisted actor. `input.uuid` must be a unique 32-ASCII-character
|
|
65
|
+
* id and `input.appId` the owning app; `chunk` is the initial grid position.
|
|
66
|
+
* `avatarId`, `privateState`, and `publicState` are optional (state blobs are
|
|
67
|
+
* base64-encoded binary).
|
|
68
|
+
*
|
|
69
|
+
* @param input - {@link CreateActorInput}.
|
|
70
|
+
* @returns The newly created {@link Actor}.
|
|
71
|
+
* @throws {CrowdyGraphQLError} `BAD_USER_INPUT` (e.g. malformed/duplicate
|
|
72
|
+
* uuid), `FORBIDDEN` if not entitled to the app, or `UNAUTHENTICATED`.
|
|
73
|
+
*/
|
|
26
74
|
async create(input) {
|
|
27
75
|
const data = await this.gql.request(CreateActorDocument, { input });
|
|
28
76
|
return data.createActor;
|
|
29
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Patch an existing actor. Only the fields present on `input` change; omitted
|
|
80
|
+
* fields are left untouched.
|
|
81
|
+
*
|
|
82
|
+
* @param uuid - The actor's 32-character id.
|
|
83
|
+
* @param input - Fields to change ({@link UpdateActorInput}).
|
|
84
|
+
* @returns The updated {@link Actor}.
|
|
85
|
+
* @throws {CrowdyGraphQLError} if the actor doesn't exist or the caller lacks
|
|
86
|
+
* access.
|
|
87
|
+
*/
|
|
30
88
|
async update(uuid, input) {
|
|
31
89
|
const data = await this.gql.request(UpdateActorDocument, { uuid, input });
|
|
32
90
|
return data.updateActor;
|
|
33
91
|
}
|
|
34
|
-
|
|
35
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Delete a persisted actor.
|
|
94
|
+
*
|
|
95
|
+
* Pass an `idempotencyKey` to make retries safe: replaying with the same key
|
|
96
|
+
* returns the first result instead of re-applying, while the same key with a
|
|
97
|
+
* **different** `uuid` throws {@link CrowdyGraphQLError} with
|
|
98
|
+
* `code === 'IDEMPOTENCY_CONFLICT'`. Keys expire server-side after 24h.
|
|
99
|
+
* Requires game-api ≥ v0.10.3.
|
|
100
|
+
*
|
|
101
|
+
* @param uuid - The actor's 32-character id.
|
|
102
|
+
* @param idempotencyKey - Optional client-supplied key for safe retries.
|
|
103
|
+
* @returns The deleted {@link Actor} (its identifying fields).
|
|
104
|
+
* @throws {CrowdyGraphQLError} `IDEMPOTENCY_CONFLICT`, `FORBIDDEN`, or
|
|
105
|
+
* `UNAUTHENTICATED`.
|
|
106
|
+
*/
|
|
107
|
+
async delete(uuid, idempotencyKey) {
|
|
108
|
+
const data = await this.gql.request(DeleteActorDocument, { uuid, idempotencyKey });
|
|
36
109
|
return data.deleteActor;
|
|
37
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Replace just an actor's state blob(s) without touching its other fields —
|
|
113
|
+
* a lighter write than {@link update} when only `privateState`/`publicState`
|
|
114
|
+
* change. State is base64-encoded binary.
|
|
115
|
+
*
|
|
116
|
+
* @param uuid - The actor's 32-character id.
|
|
117
|
+
* @param input - {@link UpdateActorStateInput}.
|
|
118
|
+
* @returns The updated {@link Actor}.
|
|
119
|
+
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
120
|
+
*/
|
|
38
121
|
async updateState(uuid, input) {
|
|
39
122
|
const data = await this.gql.request(UpdateActorStateDocument, { uuid, input });
|
|
40
123
|
return data.updateActorState;
|
package/dist/domains/apps.d.ts
CHANGED
|
@@ -1,65 +1,119 @@
|
|
|
1
|
-
/**
|
|
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:
|
|
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.gameApiUrl) {
|
|
17
|
-
* // Set for both dedicated (split-mode) AND shared-environment apps.
|
|
18
|
-
* const perAppClient = createCrowdyClient({
|
|
19
|
-
* managementUrl: 'https://api.example.com',
|
|
20
|
-
* httpUrl: route.gameApiUrl,
|
|
21
|
-
* wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
|
|
22
|
-
* tokenStore: baseClient.session.tokenStore,
|
|
23
|
-
* });
|
|
24
|
-
* // drive gameplay through perAppClient
|
|
25
|
-
* }
|
|
26
|
-
*/
|
|
27
1
|
import type { GraphQLClient } from '../client.js';
|
|
28
2
|
import { type AppQuery, type AppBySlugQuery, type MyAppsQuery } from '../generated/graphql.js';
|
|
29
3
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
4
|
+
* The minimal routing tuple the SDK derives from an `App` row: just enough to
|
|
5
|
+
* decide which game-api endpoint should serve a given app. Returned by
|
|
6
|
+
* {@link AppsAPI.routeFor}.
|
|
33
7
|
*/
|
|
34
8
|
export interface AppRoute {
|
|
9
|
+
/** Numeric id of the app (`BigInt` as a decimal string). */
|
|
35
10
|
appId: string;
|
|
11
|
+
/**
|
|
12
|
+
* `true` when the app's runtime data lives in a dedicated per-tenant game-api
|
|
13
|
+
* database rather than the shared game-api. Used together with `gameApiUrl` to
|
|
14
|
+
* route gameplay calls.
|
|
15
|
+
*/
|
|
36
16
|
splitMode: boolean;
|
|
37
17
|
/**
|
|
38
|
-
* 'none' (draft), 'shared' (the shared
|
|
39
|
-
*
|
|
18
|
+
* Where the app runs: `'none'` (draft / not deployed), `'shared'` (the shared
|
|
19
|
+
* game-api), or `'dedicated'` (a provisioned per-tenant environment). `null`
|
|
20
|
+
* until the schema/codegen expose it.
|
|
40
21
|
*/
|
|
41
22
|
deploymentTarget: string | null;
|
|
42
23
|
/**
|
|
43
|
-
* The game-api URL to route gameplay to. Set for BOTH dedicated
|
|
44
|
-
* and shared-environment apps. When non-null, build a game-api
|
|
45
|
-
* against it; when null
|
|
24
|
+
* The game-api base URL to route gameplay to. Set for BOTH dedicated
|
|
25
|
+
* (split-mode) and shared-environment apps. When non-null, build a game-api
|
|
26
|
+
* client against it; when `null`, fall back to the constructor `httpUrl`.
|
|
46
27
|
*/
|
|
47
28
|
gameApiUrl: string | null;
|
|
48
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* App discovery & game-api routing — exposed as `client.apps`.
|
|
32
|
+
*
|
|
33
|
+
* Targets the **management-api** (every call routes to `managementUrl`), where
|
|
34
|
+
* the apps catalog lives. After the database split an app may be served by its
|
|
35
|
+
* own per-tenant cks-game-api; the catalog returns each app's `gameApiUrl` so
|
|
36
|
+
* you can build a per-app `CrowdyClient` against the correct endpoint (see
|
|
37
|
+
* {@link routeFor} / {@link AppRoute}).
|
|
38
|
+
*
|
|
39
|
+
* Auth: {@link appBySlug} is **public** (no session; resolves unlisted or draft
|
|
40
|
+
* apps when the exact slugs are known). {@link app}, {@link myApps}, and
|
|
41
|
+
* {@link routeFor} require authentication (any signed-in user) and otherwise
|
|
42
|
+
* throw {@link CrowdyGraphQLError} with `UNAUTHENTICATED`; note {@link app} does
|
|
43
|
+
* not enforce org/app permissions. `BigInt` ids such as `appId` and `orgId` are
|
|
44
|
+
* decimal strings.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const base = createCrowdyClient({
|
|
49
|
+
* managementUrl: 'https://api.example.com',
|
|
50
|
+
* httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
|
|
51
|
+
* });
|
|
52
|
+
* await base.auth.login({ email, password });
|
|
53
|
+
*
|
|
54
|
+
* const route = await base.apps.routeFor(appId);
|
|
55
|
+
* if (route.gameApiUrl) {
|
|
56
|
+
* // route gameplay to the app's resolved game-api endpoint
|
|
57
|
+
* const perAppClient = createCrowdyClient({
|
|
58
|
+
* managementUrl: 'https://api.example.com',
|
|
59
|
+
* httpUrl: route.gameApiUrl,
|
|
60
|
+
* wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
|
|
61
|
+
* tokenStore: base.session.tokenStore,
|
|
62
|
+
* });
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
49
66
|
export declare class AppsAPI {
|
|
50
67
|
private readonly management;
|
|
51
68
|
constructor(management: GraphQLClient);
|
|
52
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Fetch a single app by its numeric id. Requires authentication (any signed-in
|
|
71
|
+
* user); does **not** enforce org/app permissions, so it can read apps the
|
|
72
|
+
* caller does not own, of any visibility/status. Prefer {@link appBySlug} for
|
|
73
|
+
* slug-based marketplace lookups.
|
|
74
|
+
*
|
|
75
|
+
* @param appId - Numeric id of the app (`BigInt` as a decimal string).
|
|
76
|
+
* @returns The {@link App}, or `null` if the id does not exist.
|
|
77
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
|
|
78
|
+
*/
|
|
53
79
|
app(appId: string): Promise<AppQuery['app']>;
|
|
54
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Look up a single app by its org slug + app slug (the marketplace URL path).
|
|
82
|
+
* **Public**: no authentication required, and not filtered by visibility or
|
|
83
|
+
* status — it can resolve unlisted or draft apps when the exact slugs are
|
|
84
|
+
* known.
|
|
85
|
+
*
|
|
86
|
+
* @param orgSlug - URL slug of the owning organization (e.g. `"acme"` in the
|
|
87
|
+
* path `/acme/my-game`).
|
|
88
|
+
* @param appSlug - URL slug of the app within the org (e.g. `"my-game"`);
|
|
89
|
+
* unique per org.
|
|
90
|
+
* @returns The {@link App}, or `null` if no matching app exists.
|
|
91
|
+
* @throws {CrowdyGraphQLError} on transport/validation failures.
|
|
92
|
+
*/
|
|
55
93
|
appBySlug(orgSlug: string, appSlug: string): Promise<AppBySlugQuery['appBySlug']>;
|
|
56
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* List the apps the authenticated caller can see in their account: those owned
|
|
96
|
+
* by an org they are an active member of, OR those where they hold an active
|
|
97
|
+
* access grant. Includes apps of any visibility/status (e.g. accessible
|
|
98
|
+
* drafts), ordered newest-first. Requires authentication.
|
|
99
|
+
*
|
|
100
|
+
* @returns The caller's accessible {@link App}s (an empty array if none).
|
|
101
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
|
|
102
|
+
*/
|
|
57
103
|
myApps(): Promise<MyAppsQuery['myApps']>;
|
|
58
104
|
/**
|
|
59
|
-
* Convenience
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
105
|
+
* Convenience wrapper over {@link app} that returns just the {@link AppRoute}
|
|
106
|
+
* routing tuple for an app — i.e. which game-api endpoint should serve it. If
|
|
107
|
+
* the app row is missing or the API does not expose the split-mode fields yet,
|
|
108
|
+
* returns a safe default (`{ appId, splitMode: false, deploymentTarget: null,
|
|
109
|
+
* gameApiUrl: null }`) so the caller keeps using the legacy single-endpoint
|
|
110
|
+
* deployment.
|
|
111
|
+
*
|
|
112
|
+
* @param appId - Numeric id of the app (`BigInt` as a decimal string).
|
|
113
|
+
* @returns The {@link AppRoute}; route gameplay to `gameApiUrl` when non-null,
|
|
114
|
+
* otherwise fall back to the constructor `httpUrl`.
|
|
115
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` (it calls {@link app} under
|
|
116
|
+
* the hood).
|
|
63
117
|
*/
|
|
64
118
|
routeFor(appId: string): Promise<AppRoute>;
|
|
65
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/domains/apps.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/domains/apps.ts"],"names":[],"mappings":"AAAA,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,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,OAAO;IACN,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,aAAa;IAEtD;;;;;;;;;OASG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAQlD;;;;;;;;;;;;OAYG;IACG,SAAS,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAQvC;;;;;;;;OAQG;IACG,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAQ9C;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAWjD"}
|
package/dist/domains/apps.js
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
/**
|
|
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:
|
|
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.gameApiUrl) {
|
|
17
|
-
* // Set for both dedicated (split-mode) AND shared-environment apps.
|
|
18
|
-
* const perAppClient = createCrowdyClient({
|
|
19
|
-
* managementUrl: 'https://api.example.com',
|
|
20
|
-
* httpUrl: route.gameApiUrl,
|
|
21
|
-
* wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
|
|
22
|
-
* tokenStore: baseClient.session.tokenStore,
|
|
23
|
-
* });
|
|
24
|
-
* // drive gameplay through perAppClient
|
|
25
|
-
* }
|
|
26
|
-
*/
|
|
27
1
|
import { AppDocument, AppBySlugDocument, MyAppsDocument, } from '../generated/graphql.js';
|
|
28
2
|
function appRouteFromAppRow(row) {
|
|
29
3
|
if (!row || typeof row !== 'object')
|
|
@@ -38,30 +12,103 @@ function appRouteFromAppRow(row) {
|
|
|
38
12
|
gameApiUrl: typeof r.gameApiUrl === 'string' && r.gameApiUrl ? r.gameApiUrl : null,
|
|
39
13
|
};
|
|
40
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* App discovery & game-api routing — exposed as `client.apps`.
|
|
17
|
+
*
|
|
18
|
+
* Targets the **management-api** (every call routes to `managementUrl`), where
|
|
19
|
+
* the apps catalog lives. After the database split an app may be served by its
|
|
20
|
+
* own per-tenant cks-game-api; the catalog returns each app's `gameApiUrl` so
|
|
21
|
+
* you can build a per-app `CrowdyClient` against the correct endpoint (see
|
|
22
|
+
* {@link routeFor} / {@link AppRoute}).
|
|
23
|
+
*
|
|
24
|
+
* Auth: {@link appBySlug} is **public** (no session; resolves unlisted or draft
|
|
25
|
+
* apps when the exact slugs are known). {@link app}, {@link myApps}, and
|
|
26
|
+
* {@link routeFor} require authentication (any signed-in user) and otherwise
|
|
27
|
+
* throw {@link CrowdyGraphQLError} with `UNAUTHENTICATED`; note {@link app} does
|
|
28
|
+
* not enforce org/app permissions. `BigInt` ids such as `appId` and `orgId` are
|
|
29
|
+
* decimal strings.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const base = createCrowdyClient({
|
|
34
|
+
* managementUrl: 'https://api.example.com',
|
|
35
|
+
* httpUrl: 'https://legacy-game-api.example.com', // pre-split fallback
|
|
36
|
+
* });
|
|
37
|
+
* await base.auth.login({ email, password });
|
|
38
|
+
*
|
|
39
|
+
* const route = await base.apps.routeFor(appId);
|
|
40
|
+
* if (route.gameApiUrl) {
|
|
41
|
+
* // route gameplay to the app's resolved game-api endpoint
|
|
42
|
+
* const perAppClient = createCrowdyClient({
|
|
43
|
+
* managementUrl: 'https://api.example.com',
|
|
44
|
+
* httpUrl: route.gameApiUrl,
|
|
45
|
+
* wsUrl: route.gameApiUrl.replace(/^http/, 'ws'),
|
|
46
|
+
* tokenStore: base.session.tokenStore,
|
|
47
|
+
* });
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
41
51
|
export class AppsAPI {
|
|
42
52
|
constructor(management) {
|
|
43
53
|
this.management = management;
|
|
44
54
|
}
|
|
45
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Fetch a single app by its numeric id. Requires authentication (any signed-in
|
|
57
|
+
* user); does **not** enforce org/app permissions, so it can read apps the
|
|
58
|
+
* caller does not own, of any visibility/status. Prefer {@link appBySlug} for
|
|
59
|
+
* slug-based marketplace lookups.
|
|
60
|
+
*
|
|
61
|
+
* @param appId - Numeric id of the app (`BigInt` as a decimal string).
|
|
62
|
+
* @returns The {@link App}, or `null` if the id does not exist.
|
|
63
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
|
|
64
|
+
*/
|
|
46
65
|
async app(appId) {
|
|
47
66
|
const data = await this.management.request(AppDocument, { appId });
|
|
48
67
|
return data.app;
|
|
49
68
|
}
|
|
50
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Look up a single app by its org slug + app slug (the marketplace URL path).
|
|
71
|
+
* **Public**: no authentication required, and not filtered by visibility or
|
|
72
|
+
* status — it can resolve unlisted or draft apps when the exact slugs are
|
|
73
|
+
* known.
|
|
74
|
+
*
|
|
75
|
+
* @param orgSlug - URL slug of the owning organization (e.g. `"acme"` in the
|
|
76
|
+
* path `/acme/my-game`).
|
|
77
|
+
* @param appSlug - URL slug of the app within the org (e.g. `"my-game"`);
|
|
78
|
+
* unique per org.
|
|
79
|
+
* @returns The {@link App}, or `null` if no matching app exists.
|
|
80
|
+
* @throws {CrowdyGraphQLError} on transport/validation failures.
|
|
81
|
+
*/
|
|
51
82
|
async appBySlug(orgSlug, appSlug) {
|
|
52
83
|
const data = await this.management.request(AppBySlugDocument, { orgSlug, appSlug });
|
|
53
84
|
return data.appBySlug;
|
|
54
85
|
}
|
|
55
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* List the apps the authenticated caller can see in their account: those owned
|
|
88
|
+
* by an org they are an active member of, OR those where they hold an active
|
|
89
|
+
* access grant. Includes apps of any visibility/status (e.g. accessible
|
|
90
|
+
* drafts), ordered newest-first. Requires authentication.
|
|
91
|
+
*
|
|
92
|
+
* @returns The caller's accessible {@link App}s (an empty array if none).
|
|
93
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the caller is not signed in.
|
|
94
|
+
*/
|
|
56
95
|
async myApps() {
|
|
57
96
|
const data = await this.management.request(MyAppsDocument, {});
|
|
58
97
|
return data.myApps;
|
|
59
98
|
}
|
|
60
99
|
/**
|
|
61
|
-
* Convenience
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
100
|
+
* Convenience wrapper over {@link app} that returns just the {@link AppRoute}
|
|
101
|
+
* routing tuple for an app — i.e. which game-api endpoint should serve it. If
|
|
102
|
+
* the app row is missing or the API does not expose the split-mode fields yet,
|
|
103
|
+
* returns a safe default (`{ appId, splitMode: false, deploymentTarget: null,
|
|
104
|
+
* gameApiUrl: null }`) so the caller keeps using the legacy single-endpoint
|
|
105
|
+
* deployment.
|
|
106
|
+
*
|
|
107
|
+
* @param appId - Numeric id of the app (`BigInt` as a decimal string).
|
|
108
|
+
* @returns The {@link AppRoute}; route gameplay to `gameApiUrl` when non-null,
|
|
109
|
+
* otherwise fall back to the constructor `httpUrl`.
|
|
110
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` (it calls {@link app} under
|
|
111
|
+
* the hood).
|
|
65
112
|
*/
|
|
66
113
|
async routeFor(appId) {
|
|
67
114
|
const row = await this.app(appId);
|
package/dist/domains/auth.d.ts
CHANGED
|
@@ -1,43 +1,163 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth sub-client. Talks to `cks-management-api` (NOT the game API) because
|
|
3
|
-
* the management API owns the `game_tokens` table that backs every login /
|
|
4
|
-
* register / password / email-confirm flow.
|
|
5
|
-
*
|
|
6
|
-
* The returned `token` is a `game_tokens` row that game-api will validate
|
|
7
|
-
* against the same shared Postgres. Once the SDK has a token, the rest of
|
|
8
|
-
* the sub-clients (chunks, voxels, actors, udp, ...) use it transparently
|
|
9
|
-
* via the shared `AuthState`.
|
|
10
|
-
*/
|
|
11
1
|
import type { GraphQLClient } from '../client.js';
|
|
12
2
|
import type { AuthState } from '../auth-state.js';
|
|
13
3
|
import { type LoginMutation, type LoginUserInput, type RegisterMutation, type RegisterUserInput, type ResetPasswordInput } from '../generated/graphql.js';
|
|
4
|
+
/**
|
|
5
|
+
* Authentication and account-lifecycle flows — exposed as `client.auth`.
|
|
6
|
+
*
|
|
7
|
+
* Targets the **management-api**: every call routes to `managementUrl` (falling
|
|
8
|
+
* back to the game-api endpoint only in legacy single-endpoint mode). The
|
|
9
|
+
* management API owns the `game_tokens` table that backs every login / register
|
|
10
|
+
* / password / email-confirmation flow; the `token` it returns is a
|
|
11
|
+
* `game_tokens` row that game-api validates against the same shared Postgres.
|
|
12
|
+
*
|
|
13
|
+
* {@link login} and {@link register} mint that session token **and** store it on
|
|
14
|
+
* the shared session state automatically, so every later call on *either*
|
|
15
|
+
* endpoint (auth, users, apps, actors, chunks, udp, ...) is authenticated
|
|
16
|
+
* without you threading the token through by hand. Use {@link setToken} to
|
|
17
|
+
* rehydrate a saved token and {@link getToken} to read the current one. `BigInt`
|
|
18
|
+
* ids on the returned user (e.g. `userId`, `orgId`) are decimal strings.
|
|
19
|
+
*
|
|
20
|
+
* **Public — no session required:** {@link login}, {@link register},
|
|
21
|
+
* {@link confirmEmail}, {@link requestPasswordReset}, {@link resetPassword}, and
|
|
22
|
+
* {@link resendConfirmationEmail}. **Require a valid session:** {@link logout},
|
|
23
|
+
* {@link logoutAllDevices}, and {@link changePassword}, which otherwise throw
|
|
24
|
+
* {@link CrowdyGraphQLError} with `UNAUTHENTICATED` when the bearer token is
|
|
25
|
+
* missing, expired, or revoked.
|
|
26
|
+
*/
|
|
14
27
|
export declare class AuthAPI {
|
|
15
28
|
private readonly graphql;
|
|
16
29
|
private readonly session;
|
|
17
30
|
constructor(graphql: GraphQLClient, session: AuthState);
|
|
18
31
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
32
|
+
* Authenticate with email + password and start a new session. **Public** — no
|
|
33
|
+
* existing session required.
|
|
34
|
+
*
|
|
35
|
+
* On success the returned `token` is minted **and** stored on the shared
|
|
36
|
+
* session state, so subsequent calls on any sub-client (management-api or
|
|
37
|
+
* game-api) carry it automatically — no need to call {@link setToken}.
|
|
38
|
+
*
|
|
39
|
+
* @param input - Credentials ({@link LoginUserInput}): `email` and `password`
|
|
40
|
+
* (min 8 characters).
|
|
41
|
+
* @returns An {@link AuthResponse}: the opaque session `token` (sent as
|
|
42
|
+
* `Authorization: Bearer <token>`), `gameTokenId` (the session row id, a
|
|
43
|
+
* string), and the authenticated `user`.
|
|
44
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` on invalid credentials, or
|
|
45
|
+
* `BAD_USER_INPUT` on malformed input.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const { user } = await client.auth.login({ email, password });
|
|
49
|
+
* // the session token is now stored; later calls are authenticated for you
|
|
50
|
+
* await client.users.me();
|
|
51
|
+
* ```
|
|
22
52
|
*/
|
|
23
53
|
login(input: LoginUserInput): Promise<LoginMutation['login']>;
|
|
24
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Create a new (initially unconfirmed) account, send a confirmation email, and
|
|
56
|
+
* return a session for immediate login. **Public** — no existing session
|
|
57
|
+
* required. Same token-persistence behaviour as {@link login}: the new `token`
|
|
58
|
+
* is stored on the shared session state automatically.
|
|
59
|
+
*
|
|
60
|
+
* @param input - New-account details ({@link RegisterUserInput}): `email`
|
|
61
|
+
* (where the confirmation email is sent), `password` (min 8 characters), and
|
|
62
|
+
* an optional initial `gamertag` (min 3 characters; can also be set later via
|
|
63
|
+
* `client.users.updateGamertag`).
|
|
64
|
+
* @returns An {@link AuthResponse} (session `token`, `gameTokenId`, and the new
|
|
65
|
+
* `user`).
|
|
66
|
+
* @throws {CrowdyGraphQLError} `BAD_USER_INPUT` if the email already exists or
|
|
67
|
+
* the input is invalid.
|
|
68
|
+
*/
|
|
25
69
|
register(input: RegisterUserInput): Promise<RegisterMutation['register']>;
|
|
26
70
|
/**
|
|
27
|
-
* Single-device logout
|
|
28
|
-
*
|
|
71
|
+
* Single-device logout: revoke the `game_tokens` row that authenticated this
|
|
72
|
+
* request; other devices/tokens are unaffected. After a successful server-side
|
|
73
|
+
* revoke the in-memory token is cleared from the shared session state so the
|
|
74
|
+
* other sub-clients stop using it. Requires a valid session.
|
|
75
|
+
*
|
|
76
|
+
* @returns `true` if a token was revoked, or `false` if the request carried no
|
|
77
|
+
* game token.
|
|
78
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the session is invalid.
|
|
29
79
|
*/
|
|
30
80
|
logout(): Promise<boolean>;
|
|
31
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Revoke **every** active session for the authenticated user (deletes all
|
|
83
|
+
* their `game_tokens` rows and records revocations). Requires a valid session;
|
|
84
|
+
* use {@link logout} to end only the current one.
|
|
85
|
+
*
|
|
86
|
+
* Note: unlike {@link logout}, this does not clear the SDK's in-memory token —
|
|
87
|
+
* call {@link setToken}`(null)` afterwards if you also want to drop it locally.
|
|
88
|
+
*
|
|
89
|
+
* @returns `true` on success.
|
|
90
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` if the session is invalid.
|
|
91
|
+
*/
|
|
32
92
|
logoutAllDevices(): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Confirm a user's email address using the token from the confirmation email.
|
|
95
|
+
* **Public** — the token itself authorizes the call.
|
|
96
|
+
*
|
|
97
|
+
* @param token - The confirmation token from the emailed link.
|
|
98
|
+
* @returns `true` on success, or `false` if the token is invalid or expired.
|
|
99
|
+
* @throws {CrowdyGraphQLError} on transport/validation failures (invalid or
|
|
100
|
+
* expired tokens resolve to `false` rather than throwing).
|
|
101
|
+
*/
|
|
33
102
|
confirmEmail(token: string): Promise<boolean>;
|
|
103
|
+
/**
|
|
104
|
+
* Start the password-reset flow by emailing a reset link to the address.
|
|
105
|
+
* **Public**. Always returns `true` regardless of whether the email exists or
|
|
106
|
+
* is confirmed, to prevent account enumeration.
|
|
107
|
+
*
|
|
108
|
+
* @param email - Email address to send the password-reset link to.
|
|
109
|
+
* @returns `true` (always, even when no such account exists).
|
|
110
|
+
* @throws {CrowdyGraphQLError} on transport/validation failures.
|
|
111
|
+
*/
|
|
34
112
|
requestPasswordReset(email: string): Promise<boolean>;
|
|
113
|
+
/**
|
|
114
|
+
* Complete a password reset using the reset token and a new password.
|
|
115
|
+
* **Public** — the reset token authorizes the call. Existing sessions are
|
|
116
|
+
* **not** revoked.
|
|
117
|
+
*
|
|
118
|
+
* @param input - {@link ResetPasswordInput}: the `token` from the emailed reset
|
|
119
|
+
* link and the `newPassword` to set (min 8 characters).
|
|
120
|
+
* @returns `true` on success.
|
|
121
|
+
* @throws {CrowdyGraphQLError} `BAD_USER_INPUT` if the token is invalid or
|
|
122
|
+
* expired.
|
|
123
|
+
*/
|
|
35
124
|
resetPassword(input: ResetPasswordInput): Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Re-send the email-confirmation link. **Public**. Always returns `true`
|
|
127
|
+
* regardless of whether the account exists or is already confirmed (prevents
|
|
128
|
+
* enumeration); the email is only actually sent for existing unconfirmed
|
|
129
|
+
* accounts.
|
|
130
|
+
*
|
|
131
|
+
* @param email - Email address of the account to re-send confirmation to.
|
|
132
|
+
* @returns `true` (always).
|
|
133
|
+
* @throws {CrowdyGraphQLError} on transport/validation failures.
|
|
134
|
+
*/
|
|
36
135
|
resendConfirmationEmail(email: string): Promise<boolean>;
|
|
136
|
+
/**
|
|
137
|
+
* Change the authenticated user's password after verifying the current one.
|
|
138
|
+
* Requires a valid session. Existing sessions are **not** revoked.
|
|
139
|
+
*
|
|
140
|
+
* @param currentPassword - The user's current password, for verification.
|
|
141
|
+
* @param newPassword - The new password to set (min 8 characters).
|
|
142
|
+
* @returns `true` on success.
|
|
143
|
+
* @throws {CrowdyGraphQLError} `UNAUTHENTICATED` without a valid session, or
|
|
144
|
+
* `BAD_USER_INPUT` if the current password is wrong.
|
|
145
|
+
*/
|
|
37
146
|
changePassword(currentPassword: string, newPassword: string): Promise<boolean>;
|
|
38
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Imperatively replace the in-memory bearer token on the shared session state
|
|
149
|
+
* (e.g. to rehydrate a token persisted to disk). Affects every sub-client.
|
|
150
|
+
* Local only — performs no network call. Pass `null` to clear it.
|
|
151
|
+
*
|
|
152
|
+
* @param token - The bearer token to use, or `null` to clear the session.
|
|
153
|
+
*/
|
|
39
154
|
setToken(token: string | null): void;
|
|
40
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Read the current in-memory bearer token from the shared session state. Local
|
|
157
|
+
* only — performs no network call.
|
|
158
|
+
*
|
|
159
|
+
* @returns The current bearer token, or `null` if none is set.
|
|
160
|
+
*/
|
|
41
161
|
getToken(): string | null;
|
|
42
162
|
}
|
|
43
163
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/domains/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/domains/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAUL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,OAAO;IAEhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,SAAS;IAGrC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAQnE;;;;;;;;;;;;;;OAcG;IACG,QAAQ,CACZ,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAQxC;;;;;;;;;OASG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAMhC;;;;;;;;;;OAUG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1C;;;;;;;;OAQG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnD;;;;;;;;OAQG;IACG,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO3D;;;;;;;;;;OAUG;IACG,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhE;;;;;;;;;OASG;IACG,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO9D;;;;;;;;;OASG;IACG,cAAc,CAClB,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC;IAQnB;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIpC;;;;;OAKG;IACH,QAAQ,IAAI,MAAM,GAAG,IAAI;CAG1B"}
|