@crowdedkingdoms/crowdyjs 8.0.0 → 8.1.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.
Files changed (59) hide show
  1. package/README.md +92 -15
  2. package/dist/client.d.ts +8 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +5 -0
  5. package/dist/crowdy-client.d.ts +22 -0
  6. package/dist/crowdy-client.d.ts.map +1 -1
  7. package/dist/crowdy-client.js +21 -0
  8. package/dist/domains/gameModel.d.ts +54 -3
  9. package/dist/domains/gameModel.d.ts.map +1 -1
  10. package/dist/domains/gameModel.js +67 -4
  11. package/dist/domains/host.d.ts +14 -3
  12. package/dist/domains/host.d.ts.map +1 -1
  13. package/dist/domains/host.js +17 -3
  14. package/dist/domains/organizations.d.ts +3 -3
  15. package/dist/domains/organizations.js +3 -3
  16. package/dist/domains/quotas.d.ts +1 -1
  17. package/dist/domains/quotas.js +1 -1
  18. package/dist/domains/udp.d.ts +28 -17
  19. package/dist/domains/udp.d.ts.map +1 -1
  20. package/dist/domains/udp.js +28 -17
  21. package/dist/generated/graphql.d.ts +366 -6
  22. package/dist/generated/graphql.d.ts.map +1 -1
  23. package/dist/generated/graphql.js +13 -8
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -1
  27. package/dist/kit/blueprints.d.ts +259 -0
  28. package/dist/kit/blueprints.d.ts.map +1 -0
  29. package/dist/kit/blueprints.js +480 -0
  30. package/dist/kit/index.d.ts +7 -0
  31. package/dist/kit/index.d.ts.map +1 -0
  32. package/dist/kit/index.js +6 -0
  33. package/dist/kit/inventory.d.ts +111 -0
  34. package/dist/kit/inventory.d.ts.map +1 -0
  35. package/dist/kit/inventory.js +158 -0
  36. package/dist/kit/kit.d.ts +96 -0
  37. package/dist/kit/kit.d.ts.map +1 -0
  38. package/dist/kit/kit.js +98 -0
  39. package/dist/kit/npcs.d.ts +182 -0
  40. package/dist/kit/npcs.d.ts.map +1 -0
  41. package/dist/kit/npcs.js +106 -0
  42. package/dist/kit/objects.d.ts +108 -0
  43. package/dist/kit/objects.d.ts.map +1 -0
  44. package/dist/kit/objects.js +110 -0
  45. package/dist/kit/shared.d.ts +32 -0
  46. package/dist/kit/shared.d.ts.map +1 -0
  47. package/dist/kit/shared.js +39 -0
  48. package/dist/lb-cookie-store.d.ts +20 -0
  49. package/dist/lb-cookie-store.d.ts.map +1 -0
  50. package/dist/lb-cookie-store.js +73 -0
  51. package/dist/realtime.d.ts +20 -5
  52. package/dist/realtime.d.ts.map +1 -1
  53. package/dist/realtime.js +38 -0
  54. package/dist/types.d.ts +22 -6
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/world.d.ts +13 -8
  57. package/dist/world.d.ts.map +1 -1
  58. package/dist/world.js +13 -8
  59. package/package.json +6 -4
@@ -0,0 +1,108 @@
1
+ import type { GameModelAPI } from '../domains/gameModel.js';
2
+ import type { Scalars, SeedPropertyInput } from '../generated/graphql.js';
3
+ import { type KitInvokeResult } from './shared.js';
4
+ /** Options for {@link ObjectsKit}. Must match the deployed lock blueprint. */
5
+ export interface ObjectsKitOptions {
6
+ /** The `objectTypeName` the lock blueprint was deployed with. Defaults to `'Lockable'`. */
7
+ objectTypeName?: string;
8
+ /** The `keyTypeName` the lock blueprint was deployed with. */
9
+ keyTypeName?: string;
10
+ }
11
+ /**
12
+ * Runtime helpers for the {@link lockBlueprint} conventions: instantiate
13
+ * lockable world objects, grant key items, and operate the objects through
14
+ * their authority-gated `open`/`close` functions. Authorization is decided
15
+ * entirely server-side; a denied attempt resolves with `success: false`.
16
+ *
17
+ * Obtained via `client.kit(appId).objects` (or `client.kit(appId)
18
+ * .objectsFor('Door')` for a non-default type name).
19
+ */
20
+ export declare class ObjectsKit {
21
+ private readonly appId;
22
+ private readonly gameModel;
23
+ private readonly names;
24
+ constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, options?: ObjectsKitOptions);
25
+ /**
26
+ * Instantiate a lockable object (admin/studio call — the type is
27
+ * admin-instantiable). For key-gated objects set `requiredKeyId` to the key
28
+ * id that opens it; for owner-gated objects set `ownerUserId`.
29
+ */
30
+ create(input: {
31
+ displayName: string;
32
+ requiredKeyId?: string;
33
+ ownerUserId?: Scalars['BigInt']['input'];
34
+ properties?: SeedPropertyInput[];
35
+ sessionId?: string;
36
+ }): Promise<{
37
+ __typename?: "GmContainer";
38
+ containerId: string;
39
+ appId: string;
40
+ sessionId: string | null;
41
+ typeName: string;
42
+ displayName: string;
43
+ description: string | null;
44
+ ownerUserId: string | null;
45
+ metadataJson: string;
46
+ }>;
47
+ /**
48
+ * Grant a player a key item (admin/studio call). Creates a key container
49
+ * owned by the player, with the owner mirrored into the `owner_user_id`
50
+ * property that the key condition policy reads.
51
+ */
52
+ grantKey(input: {
53
+ keyId: string;
54
+ toUserId: Scalars['BigInt']['input'];
55
+ displayName?: string;
56
+ }): Promise<{
57
+ __typename?: "GmContainer";
58
+ containerId: string;
59
+ appId: string;
60
+ sessionId: string | null;
61
+ typeName: string;
62
+ displayName: string;
63
+ description: string | null;
64
+ ownerUserId: string | null;
65
+ metadataJson: string;
66
+ }>;
67
+ /** List the key items a player holds. */
68
+ keysOf(userId: Scalars['BigInt']['input']): Promise<{
69
+ __typename?: "GmContainer";
70
+ containerId: string;
71
+ appId: string;
72
+ sessionId: string | null;
73
+ typeName: string;
74
+ displayName: string;
75
+ description: string | null;
76
+ ownerUserId: string | null;
77
+ metadataJson: string;
78
+ }[]>;
79
+ /**
80
+ * Try to open an object. Pass `keyId` (the **container id** of a key the
81
+ * caller holds) when the object is key-gated; owner/grid/group authorities
82
+ * need no params. A denial is not an exception — check `success`.
83
+ */
84
+ open(objectId: string, options?: {
85
+ keyId?: string;
86
+ }): Promise<KitInvokeResult<boolean>>;
87
+ /** Try to close an object; same authority as {@link open}. */
88
+ close(objectId: string, options?: {
89
+ keyId?: string;
90
+ }): Promise<KitInvokeResult<boolean>>;
91
+ /** Read whether an object is currently open. */
92
+ isOpen(objectId: string): Promise<boolean>;
93
+ /** List all objects of this lockable type. */
94
+ list(options?: {
95
+ sessionId?: string;
96
+ }): Promise<{
97
+ __typename?: "GmContainer";
98
+ containerId: string;
99
+ appId: string;
100
+ sessionId: string | null;
101
+ typeName: string;
102
+ displayName: string;
103
+ description: string | null;
104
+ ownerUserId: string | null;
105
+ metadataJson: string;
106
+ }[]>;
107
+ }
108
+ //# sourceMappingURL=objects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../../src/kit/objects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,qBAAa,UAAU;IAInB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAJ5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;gBAGf,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACxC,OAAO,GAAE,iBAAsB;IAKjC;;;;OAIG;IACG,MAAM,CAAC,KAAK,EAAE;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC;QACzC,UAAU,CAAC,EAAE,iBAAiB,EAAE,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;;;;;;IAwBD;;;;OAIG;IACG,QAAQ,CAAC,KAAK,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC;QACrC,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB;;;;;;;;;;;IAiBD,yCAAyC;IACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC;;;;;;;;;;;IAU/C;;;;OAIG;IACG,IAAI,CACR,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IASpC,8DAA8D;IACxD,KAAK,CACT,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IASpC,gDAAgD;IAC1C,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAShD,8CAA8C;IACxC,IAAI,CAAC,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;;;;;;;;;;;CAOhD"}
@@ -0,0 +1,110 @@
1
+ import { lockNames } from './blueprints.js';
2
+ import { kitContainerProperties, kitInvoke, } from './shared.js';
3
+ /**
4
+ * Runtime helpers for the {@link lockBlueprint} conventions: instantiate
5
+ * lockable world objects, grant key items, and operate the objects through
6
+ * their authority-gated `open`/`close` functions. Authorization is decided
7
+ * entirely server-side; a denied attempt resolves with `success: false`.
8
+ *
9
+ * Obtained via `client.kit(appId).objects` (or `client.kit(appId)
10
+ * .objectsFor('Door')` for a non-default type name).
11
+ */
12
+ export class ObjectsKit {
13
+ constructor(appId, gameModel, options = {}) {
14
+ this.appId = appId;
15
+ this.gameModel = gameModel;
16
+ this.names = lockNames(options.objectTypeName, options.keyTypeName);
17
+ }
18
+ /**
19
+ * Instantiate a lockable object (admin/studio call — the type is
20
+ * admin-instantiable). For key-gated objects set `requiredKeyId` to the key
21
+ * id that opens it; for owner-gated objects set `ownerUserId`.
22
+ */
23
+ async create(input) {
24
+ const properties = [
25
+ { key: 'is_open', valueType: 'bool', valueJson: 'false' },
26
+ ...(input.requiredKeyId !== undefined
27
+ ? [
28
+ {
29
+ key: 'required_key_id',
30
+ valueType: 'string',
31
+ valueJson: JSON.stringify(input.requiredKeyId),
32
+ },
33
+ ]
34
+ : []),
35
+ ...(input.properties ?? []),
36
+ ];
37
+ return this.gameModel.createContainer({
38
+ appId: this.appId,
39
+ typeName: this.names.objectType,
40
+ displayName: input.displayName,
41
+ ...(input.ownerUserId !== undefined ? { ownerUserId: input.ownerUserId } : {}),
42
+ ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}),
43
+ properties,
44
+ });
45
+ }
46
+ /**
47
+ * Grant a player a key item (admin/studio call). Creates a key container
48
+ * owned by the player, with the owner mirrored into the `owner_user_id`
49
+ * property that the key condition policy reads.
50
+ */
51
+ async grantKey(input) {
52
+ return this.gameModel.createContainer({
53
+ appId: this.appId,
54
+ typeName: this.names.keyType,
55
+ displayName: input.displayName ?? `Key ${input.keyId}`,
56
+ ownerUserId: input.toUserId,
57
+ properties: [
58
+ { key: 'key_id', valueType: 'string', valueJson: JSON.stringify(input.keyId) },
59
+ {
60
+ key: 'owner_user_id',
61
+ valueType: 'int',
62
+ valueJson: String(input.toUserId),
63
+ },
64
+ ],
65
+ });
66
+ }
67
+ /** List the key items a player holds. */
68
+ async keysOf(userId) {
69
+ const containers = await this.gameModel.containers({
70
+ appId: this.appId,
71
+ typeName: this.names.keyType,
72
+ });
73
+ return containers.filter((c) => c.ownerUserId != null && String(c.ownerUserId) === String(userId));
74
+ }
75
+ /**
76
+ * Try to open an object. Pass `keyId` (the **container id** of a key the
77
+ * caller holds) when the object is key-gated; owner/grid/group authorities
78
+ * need no params. A denial is not an exception — check `success`.
79
+ */
80
+ async open(objectId, options = {}) {
81
+ return kitInvoke(this.gameModel, {
82
+ appId: String(this.appId),
83
+ functionName: this.names.openFn,
84
+ selfContainerId: objectId,
85
+ params: options.keyId !== undefined ? { key_id: options.keyId } : {},
86
+ });
87
+ }
88
+ /** Try to close an object; same authority as {@link open}. */
89
+ async close(objectId, options = {}) {
90
+ return kitInvoke(this.gameModel, {
91
+ appId: String(this.appId),
92
+ functionName: this.names.closeFn,
93
+ selfContainerId: objectId,
94
+ params: options.keyId !== undefined ? { key_id: options.keyId } : {},
95
+ });
96
+ }
97
+ /** Read whether an object is currently open. */
98
+ async isOpen(objectId) {
99
+ const props = await kitContainerProperties(this.gameModel, String(this.appId), objectId);
100
+ return props.is_open === true;
101
+ }
102
+ /** List all objects of this lockable type. */
103
+ async list(options = {}) {
104
+ return this.gameModel.containers({
105
+ appId: this.appId,
106
+ typeName: this.names.objectType,
107
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
108
+ });
109
+ }
110
+ }
@@ -0,0 +1,32 @@
1
+ import type { GameModelAPI } from '../domains/gameModel.js';
2
+ import type { GameModelInvokeMutation } from '../generated/graphql.js';
3
+ /** The raw server result of a `gameModelInvoke` call. */
4
+ export type RawInvokeResult = GameModelInvokeMutation['gameModelInvoke'];
5
+ /**
6
+ * A kit invoke outcome: the server's authority/evaluation verdict plus the
7
+ * parsed return value. Authority denials and expression errors are **not**
8
+ * exceptions — check {@link success}.
9
+ */
10
+ export interface KitInvokeResult<T = unknown> {
11
+ /** `false` when the invoke policy denied the caller or the logic errored (rolled back). */
12
+ success: boolean;
13
+ /** The parsed `returnValueJson`, when present and `success` is true. */
14
+ returnValue?: T;
15
+ /** The server's error message when `success` is false. */
16
+ errorMessage?: string;
17
+ /** The full server result (event id, applied mutations, …). */
18
+ raw: RawInvokeResult;
19
+ }
20
+ /** Wrap a raw invoke result, parsing the JSON return value. */
21
+ export declare function toKitInvokeResult<T>(raw: RawInvokeResult): KitInvokeResult<T>;
22
+ /** Invoke a model function and wrap the result. */
23
+ export declare function kitInvoke<T = unknown>(gameModel: GameModelAPI, input: {
24
+ appId: string;
25
+ functionName: string;
26
+ selfContainerId: string;
27
+ params?: Record<string, unknown>;
28
+ sessionId?: string;
29
+ }): Promise<KitInvokeResult<T>>;
30
+ /** Read a container's visible properties as a parsed object. */
31
+ export declare function kitContainerProperties(gameModel: GameModelAPI, appId: string, containerId: string): Promise<Record<string, unknown>>;
32
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/kit/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAEvE,yDAAyD;AACzD,MAAM,MAAM,eAAe,GAAG,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,2FAA2F;IAC3F,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,WAAW,CAAC,EAAE,CAAC,CAAC;IAChB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,GAAG,EAAE,eAAe,CAAC;CACtB;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,GAAG,eAAe,CAAC,CAAC,CAAC,CAe7E;AAED,mDAAmD;AACnD,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EACzC,SAAS,EAAE,YAAY,EACvB,KAAK,EAAE;IACL,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAS7B;AAED,gEAAgE;AAChE,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,YAAY,EACvB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOlC"}
@@ -0,0 +1,39 @@
1
+ /** Wrap a raw invoke result, parsing the JSON return value. */
2
+ export function toKitInvokeResult(raw) {
3
+ let returnValue;
4
+ if (raw.success && raw.returnValueJson != null) {
5
+ try {
6
+ returnValue = JSON.parse(raw.returnValueJson);
7
+ }
8
+ catch {
9
+ returnValue = undefined;
10
+ }
11
+ }
12
+ return {
13
+ success: raw.success,
14
+ returnValue,
15
+ errorMessage: raw.errorMessage ?? undefined,
16
+ raw,
17
+ };
18
+ }
19
+ /** Invoke a model function and wrap the result. */
20
+ export async function kitInvoke(gameModel, input) {
21
+ const raw = await gameModel.invoke({
22
+ appId: input.appId,
23
+ functionName: input.functionName,
24
+ selfContainerId: input.selfContainerId,
25
+ paramsJson: JSON.stringify(input.params ?? {}),
26
+ ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}),
27
+ });
28
+ return toKitInvokeResult(raw);
29
+ }
30
+ /** Read a container's visible properties as a parsed object. */
31
+ export async function kitContainerProperties(gameModel, appId, containerId) {
32
+ const state = await gameModel.containerState({ appId, containerId });
33
+ try {
34
+ return JSON.parse(state.propertiesJson);
35
+ }
36
+ catch {
37
+ return {};
38
+ }
39
+ }
@@ -0,0 +1,20 @@
1
+ export declare class LbCookieStore {
2
+ private value;
3
+ /** Current `cks_ga=…` header value, or null when unset. */
4
+ headerValue(): string | null;
5
+ /** Parse `Set-Cookie` response headers and retain `cks_ga` when present. */
6
+ ingestSetCookie(headers: Headers): void;
7
+ /** @internal test helper */
8
+ getValue(): string | null;
9
+ /**
10
+ * Obtain `cks_ga` from the game-api before opening a WebSocket so the
11
+ * upgrade lands on the same upstream as subsequent HTTP mutations.
12
+ */
13
+ primeFromGraphql(args: {
14
+ endpoint: string;
15
+ token?: string | null;
16
+ timeoutMs?: number;
17
+ signal?: AbortSignal;
18
+ }): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=lb-cookie-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lb-cookie-store.d.ts","sourceRoot":"","sources":["../src/lb-cookie-store.ts"],"names":[],"mappings":"AAWA,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAuB;IAEpC,2DAA2D;IAC3D,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,4EAA4E;IAC5E,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAYvC,4BAA4B;IAC5B,QAAQ,IAAI,MAAM,GAAG,IAAI;IAIzB;;;OAGG;IACG,gBAAgB,CAAC,IAAI,EAAE;QAC3B,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;CAwBlB"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * In-memory store for the Caddy load-balancer sticky cookie (`cks_ga`).
3
+ *
4
+ * CKS shared game LBs set `cks_ga` on the first HTTP response so GraphQL
5
+ * mutations and the graphql-transport-ws upgrade for the same client land on
6
+ * one game-api. Browsers persist this automatically; Node `fetch` and `ws` do
7
+ * not — this store bridges that gap for bots, e2e tests, and other non-browser
8
+ * runtimes.
9
+ */
10
+ const CKS_GA_NAME = 'cks_ga';
11
+ export class LbCookieStore {
12
+ constructor() {
13
+ this.value = null;
14
+ }
15
+ /** Current `cks_ga=…` header value, or null when unset. */
16
+ headerValue() {
17
+ return this.value ? `${CKS_GA_NAME}=${this.value}` : null;
18
+ }
19
+ /** Parse `Set-Cookie` response headers and retain `cks_ga` when present. */
20
+ ingestSetCookie(headers) {
21
+ const setCookies = typeof headers.getSetCookie === 'function'
22
+ ? headers.getSetCookie()
23
+ : parseSetCookieFallback(headers.get('set-cookie'));
24
+ for (const raw of setCookies) {
25
+ const pair = raw.split(';', 1)[0]?.trim();
26
+ if (!pair?.startsWith(`${CKS_GA_NAME}=`))
27
+ continue;
28
+ this.value = pair.slice(CKS_GA_NAME.length + 1);
29
+ }
30
+ }
31
+ /** @internal test helper */
32
+ getValue() {
33
+ return this.value;
34
+ }
35
+ /**
36
+ * Obtain `cks_ga` from the game-api before opening a WebSocket so the
37
+ * upgrade lands on the same upstream as subsequent HTTP mutations.
38
+ */
39
+ async primeFromGraphql(args) {
40
+ if (this.value)
41
+ return;
42
+ const httpEndpoint = toHttpGraphqlEndpoint(args.endpoint);
43
+ const controller = new AbortController();
44
+ const timeoutMs = args.timeoutMs ?? 10000;
45
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
46
+ const signal = args.signal ?? controller.signal;
47
+ try {
48
+ const response = await fetch(httpEndpoint, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ ...(args.token ? { Authorization: `Bearer ${args.token}` } : {}),
53
+ },
54
+ body: JSON.stringify({ query: '{ __typename }' }),
55
+ credentials: 'include',
56
+ signal,
57
+ });
58
+ this.ingestSetCookie(response.headers);
59
+ await response.arrayBuffer().catch(() => undefined);
60
+ }
61
+ finally {
62
+ clearTimeout(timeoutId);
63
+ }
64
+ }
65
+ }
66
+ function parseSetCookieFallback(header) {
67
+ if (!header)
68
+ return [];
69
+ return [header];
70
+ }
71
+ function toHttpGraphqlEndpoint(endpoint) {
72
+ return endpoint.replace(/^wss:/i, 'https:').replace(/^ws:/i, 'http:');
73
+ }
@@ -1,6 +1,7 @@
1
1
  import type { SessionStore } from './session.js';
2
2
  import type { CrowdyLogger } from './logger.js';
3
3
  import { CrowdyRealtimeError } from './errors.js';
4
+ import type { LbCookieStore } from './lb-cookie-store.js';
4
5
  import { type UdpNotificationsSubscription } from './generated/graphql.js';
5
6
  /**
6
7
  * Lifecycle state of the realtime WebSocket connection, as reported by
@@ -62,9 +63,11 @@ export interface UdpNotificationHandlers {
62
63
  __typename?: 'ActorUpdateNotification';
63
64
  }>) => void;
64
65
  /**
65
- * Server acknowledgement echoing one of **your own** actor updates. This is
66
- * the notification a `sendActorUpdateAndWait` correlates to via
67
- * `sequenceNumber`.
66
+ * Legacy handler for `ActorUpdateResponse`. Current game servers never emit
67
+ * this type; the echo of your own actor update arrives as an
68
+ * `ActorUpdateNotification` (see {@link actorUpdate}), which is what
69
+ * `sendActorUpdateAndWait` correlates to via `sequenceNumber`. Retained for
70
+ * backward compatibility.
68
71
  */
69
72
  actorUpdateResponse?: (notification: Extract<UdpNotification, {
70
73
  __typename?: 'ActorUpdateResponse';
@@ -77,8 +80,11 @@ export interface UdpNotificationHandlers {
77
80
  __typename?: 'VoxelUpdateNotification';
78
81
  }>) => void;
79
82
  /**
80
- * Server acknowledgement echoing one of **your own** voxel updates (the
81
- * `sendVoxelUpdateAndWait` correlation target).
83
+ * Legacy handler for `VoxelUpdateResponse`. Current game servers never emit
84
+ * this type; the echo of your own voxel update arrives as a
85
+ * `VoxelUpdateNotification` (see {@link voxelUpdate}), which is what
86
+ * `sendVoxelUpdateAndWait` correlates to via `sequenceNumber`. Retained for
87
+ * backward compatibility.
82
88
  */
83
89
  voxelUpdateResponse?: (notification: Extract<UdpNotification, {
84
90
  __typename?: 'VoxelUpdateResponse';
@@ -191,6 +197,12 @@ export interface RealtimeConfig {
191
197
  waitTimeoutMs?: number;
192
198
  /** Optional logger for realtime diagnostics. Defaults to a silent logger. */
193
199
  logger?: CrowdyLogger;
200
+ /**
201
+ * Sticky-LB cookie jar shared with the game-api HTTP client. When set in
202
+ * Node, the WebSocket upgrade forwards `cks_ga` so HTTP mutations and the
203
+ * subscription land on the same game-api upstream.
204
+ */
205
+ lbCookieStore?: LbCookieStore;
194
206
  }
195
207
  /**
196
208
  * Manages the single WebSocket subscription to the game-api's
@@ -218,6 +230,7 @@ export declare class RealtimeClient {
218
230
  private readonly retryInitialDelayMs;
219
231
  private readonly retryMaxDelayMs;
220
232
  private readonly waitTimeoutMs;
233
+ private readonly lbCookieStore?;
221
234
  private client;
222
235
  private release;
223
236
  private desired;
@@ -227,6 +240,7 @@ export declare class RealtimeClient {
227
240
  private readonly pending;
228
241
  private nextSubscriberId;
229
242
  private subscribedAppId;
243
+ private opening;
230
244
  /**
231
245
  * @param config - Reconnect/timeout/endpoint tuning; see
232
246
  * {@link RealtimeConfig}.
@@ -308,6 +322,7 @@ export declare class RealtimeClient {
308
322
  */
309
323
  waitForSequence(sequenceNumber: number, timeoutMs?: number): Promise<SpatialNotification>;
310
324
  private ensureSubscription;
325
+ private openSubscription;
311
326
  private restart;
312
327
  private dispatch;
313
328
  private resolvePending;
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
package/dist/realtime.js CHANGED
@@ -45,12 +45,14 @@ export class RealtimeClient {
45
45
  // game-api only fans this app's spatial notifications to this subscription.
46
46
  // The game-api rejects subscriptions that arrive without it.
47
47
  this.subscribedAppId = null;
48
+ this.opening = null;
48
49
  this.wsUrl = config.wsUrl || config.wsEndpoint || 'ws://localhost:3000/graphql';
49
50
  this.logger = config.logger ?? silentLogger;
50
51
  this.retryAttempts = config.retryAttempts ?? 8;
51
52
  this.retryInitialDelayMs = config.retryInitialDelayMs ?? 250;
52
53
  this.retryMaxDelayMs = config.retryMaxDelayMs ?? 5000;
53
54
  this.waitTimeoutMs = config.waitTimeoutMs ?? 5000;
55
+ this.lbCookieStore = config.lbCookieStore;
54
56
  this.session.onChange((token) => {
55
57
  if (!this.desired)
56
58
  return;
@@ -185,6 +187,8 @@ export class RealtimeClient {
185
187
  ensureSubscription() {
186
188
  if (this.release)
187
189
  return;
190
+ if (this.opening)
191
+ return;
188
192
  const token = this.session.getToken();
189
193
  if (!token) {
190
194
  const error = new CrowdyRealtimeError('Must be authenticated to subscribe', {
@@ -195,11 +199,24 @@ export class RealtimeClient {
195
199
  this.dispatchError(error);
196
200
  throw error;
197
201
  }
202
+ this.opening = this.openSubscription(token).finally(() => {
203
+ this.opening = null;
204
+ });
205
+ }
206
+ async openSubscription(token) {
207
+ if (this.lbCookieStore) {
208
+ await this.lbCookieStore.primeFromGraphql({
209
+ endpoint: this.wsUrl,
210
+ token,
211
+ });
212
+ }
198
213
  this.setStatus('connecting');
214
+ const webSocketImpl = createStickyWebSocketImpl(this.lbCookieStore);
199
215
  this.client = createClient({
200
216
  url: this.wsUrl,
201
217
  lazy: true,
202
218
  retryAttempts: this.retryAttempts,
219
+ ...(webSocketImpl ? { webSocketImpl } : {}),
203
220
  connectionParams: () => {
204
221
  const currentToken = this.session.getToken();
205
222
  if (!currentToken)
@@ -388,3 +405,24 @@ export class RealtimeClient {
388
405
  }
389
406
  }
390
407
  }
408
+ function isNodeRuntime() {
409
+ return (typeof process !== 'undefined' &&
410
+ typeof process.versions?.node === 'string');
411
+ }
412
+ /**
413
+ * Node `ws` does not send browser cookies on upgrade. When a sticky cookie jar
414
+ * is configured, wrap `ws` so `cks_ga` rides on the handshake.
415
+ */
416
+ function createStickyWebSocketImpl(lbCookieStore) {
417
+ if (!lbCookieStore || !isNodeRuntime())
418
+ return undefined;
419
+ const cookieStore = lbCookieStore;
420
+ // Node `ws` accepts a third options arg with `headers`; DOM typings do not.
421
+ const NodeWebSocket = WebSocket;
422
+ return class StickyLbWebSocket extends NodeWebSocket {
423
+ constructor(url, protocols) {
424
+ const cookie = cookieStore.headerValue();
425
+ super(url, protocols, cookie ? { headers: { Cookie: cookie } } : undefined);
426
+ }
427
+ };
428
+ }
package/dist/types.d.ts CHANGED
@@ -379,8 +379,12 @@ export interface ActorUpdateNotification {
379
379
  epochMillis: BigInt;
380
380
  }
381
381
  /**
382
- * Server acknowledgement echoing one of **your own** actor updates — the
383
- * correlation target for `sendActorUpdateAndWait`. Has no `state` payload.
382
+ * Legacy type retained for backward compatibility. Current game servers do
383
+ * **not** emit an `ActorUpdateResponse` the applied echo of your own actor
384
+ * update arrives as an {@link ActorUpdateNotification} (the sender is included
385
+ * in the chunk fan-out), correlated by `sequenceNumber`. That notification, not
386
+ * this type, is what `sendActorUpdateAndWait` resolves with. Has no `state`
387
+ * payload.
384
388
  */
385
389
  export interface ActorUpdateResponse {
386
390
  /** Discriminator for the {@link UdpNotification} union. */
@@ -445,8 +449,12 @@ export interface VoxelUpdateNotification {
445
449
  epochMillis: BigInt;
446
450
  }
447
451
  /**
448
- * Server acknowledgement echoing one of **your own** voxel updates — the
449
- * correlation target for `sendVoxelUpdateAndWait`. Has no voxel payload.
452
+ * Legacy type retained for backward compatibility. Current game servers do
453
+ * **not** emit a `VoxelUpdateResponse` the applied echo of your own voxel
454
+ * update arrives as a {@link VoxelUpdateNotification} (the sender is included
455
+ * in the chunk fan-out), correlated by `sequenceNumber`. That notification, not
456
+ * this type, is what `sendVoxelUpdateAndWait` resolves with. Has no voxel
457
+ * payload.
450
458
  */
451
459
  export interface VoxelUpdateResponse {
452
460
  /** Discriminator for the {@link UdpNotification} union. */
@@ -634,11 +642,19 @@ export interface CrowdyClientConfig {
634
642
  }
635
643
  /** Callback for an {@link ActorUpdateNotification} (another actor moved/changed). */
636
644
  export type ActorUpdateHandler = (notification: ActorUpdateNotification) => void;
637
- /** Callback for an {@link ActorUpdateResponse} (echo of your own actor update). */
645
+ /**
646
+ * Callback for a legacy {@link ActorUpdateResponse}. Current servers never emit
647
+ * one; your own actor update echoes back as an {@link ActorUpdateNotification}.
648
+ * Retained for backward compatibility.
649
+ */
638
650
  export type ActorUpdateResponseHandler = (response: ActorUpdateResponse) => void;
639
651
  /** Callback for a {@link VoxelUpdateNotification} (a nearby voxel changed). */
640
652
  export type VoxelUpdateHandler = (notification: VoxelUpdateNotification) => void;
641
- /** Callback for a {@link VoxelUpdateResponse} (echo of your own voxel update). */
653
+ /**
654
+ * Callback for a legacy {@link VoxelUpdateResponse}. Current servers never emit
655
+ * one; your own voxel update echoes back as a {@link VoxelUpdateNotification}.
656
+ * Retained for backward compatibility.
657
+ */
642
658
  export type VoxelUpdateResponseHandler = (response: VoxelUpdateResponse) => void;
643
659
  /** Callback for a {@link ClientAudioNotification} (nearby voice/audio packet). */
644
660
  export type ClientAudioHandler = (notification: ClientAudioNotification) => void;