@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.
- package/README.md +92 -15
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -0
- package/dist/crowdy-client.d.ts +22 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +21 -0
- package/dist/domains/gameModel.d.ts +54 -3
- package/dist/domains/gameModel.d.ts.map +1 -1
- package/dist/domains/gameModel.js +67 -4
- package/dist/domains/host.d.ts +14 -3
- package/dist/domains/host.d.ts.map +1 -1
- package/dist/domains/host.js +17 -3
- package/dist/domains/organizations.d.ts +3 -3
- package/dist/domains/organizations.js +3 -3
- package/dist/domains/quotas.d.ts +1 -1
- package/dist/domains/quotas.js +1 -1
- package/dist/domains/udp.d.ts +28 -17
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +28 -17
- package/dist/generated/graphql.d.ts +366 -6
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +13 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/kit/blueprints.d.ts +259 -0
- package/dist/kit/blueprints.d.ts.map +1 -0
- package/dist/kit/blueprints.js +480 -0
- package/dist/kit/index.d.ts +7 -0
- package/dist/kit/index.d.ts.map +1 -0
- package/dist/kit/index.js +6 -0
- package/dist/kit/inventory.d.ts +111 -0
- package/dist/kit/inventory.d.ts.map +1 -0
- package/dist/kit/inventory.js +158 -0
- package/dist/kit/kit.d.ts +96 -0
- package/dist/kit/kit.d.ts.map +1 -0
- package/dist/kit/kit.js +98 -0
- package/dist/kit/npcs.d.ts +182 -0
- package/dist/kit/npcs.d.ts.map +1 -0
- package/dist/kit/npcs.js +106 -0
- package/dist/kit/objects.d.ts +108 -0
- package/dist/kit/objects.d.ts.map +1 -0
- package/dist/kit/objects.js +110 -0
- package/dist/kit/shared.d.ts +32 -0
- package/dist/kit/shared.d.ts.map +1 -0
- package/dist/kit/shared.js +39 -0
- package/dist/lb-cookie-store.d.ts +20 -0
- package/dist/lb-cookie-store.d.ts.map +1 -0
- package/dist/lb-cookie-store.js +73 -0
- package/dist/realtime.d.ts +20 -5
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +38 -0
- package/dist/types.d.ts +22 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/world.d.ts +13 -8
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +13 -8
- 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
|
+
}
|
package/dist/realtime.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
66
|
-
* the
|
|
67
|
-
* `
|
|
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
|
-
*
|
|
81
|
-
*
|
|
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;
|
package/dist/realtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,YAAY,GACZ,WAAW,GACX,cAAc,GACd,cAAc,GACd,QAAQ,CAAC;AAEb;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CACvC,4BAA4B,CAAC,kBAAkB,CAAC,CACjD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,eAAe,EACf;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAC3B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,eAAe,EAAE;QAAE,UAAU,CAAC,EAAE,yBAAyB,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;IAC3G
|
|
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
|
-
*
|
|
383
|
-
*
|
|
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
|
-
*
|
|
449
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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;
|