@aethervtt/shard-protocol 0.0.1 → 0.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/dist/preview.js +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +84 -9
- package/package.json +12 -12
package/dist/preview.js
CHANGED
package/dist/server.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface ProtocolServerOptions {
|
|
|
43
43
|
platform?: Platform;
|
|
44
44
|
/** Extra methods from the host (e.g. `project.reload`), added to the built-in ones. */
|
|
45
45
|
methods?: readonly MethodDef[];
|
|
46
|
+
/**
|
|
47
|
+
* The only methods this server answers (0061): everything else, `subscribe` included, fails with
|
|
48
|
+
* `protocol/method-not-allowed`. A host that forwards requests from hosted mods passes the few
|
|
49
|
+
* it allows; omit it for agents and tools, which get every method.
|
|
50
|
+
*/
|
|
51
|
+
allow?: readonly string[];
|
|
46
52
|
}
|
|
47
53
|
export declare const METHODS: MethodDef[];
|
|
48
54
|
export type Topic = 'log' | 'error' | 'frame' | 'project';
|
package/dist/server.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
describeInput,
|
|
17
17
|
injectInput,
|
|
18
|
+
simulateGestures,
|
|
18
19
|
startRecording,
|
|
19
20
|
startReplay,
|
|
20
21
|
stopRecording
|
|
@@ -87,7 +88,7 @@ function resolveEntity(world, ref) {
|
|
|
87
88
|
hint: 'Pass an entity id from world.query, or a scene path like "ship/camera".'
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
+
function knownComponent(name) {
|
|
91
92
|
const def = findComponent(name);
|
|
92
93
|
if (!def) {
|
|
93
94
|
throw new ShardError("protocol/unknown-component", `Unknown component "${name}"`, {
|
|
@@ -96,6 +97,28 @@ function requireComponent(name) {
|
|
|
96
97
|
}
|
|
97
98
|
return def;
|
|
98
99
|
}
|
|
100
|
+
function requireOwner(world, name) {
|
|
101
|
+
const owner = world.owners.find(name);
|
|
102
|
+
if (!owner) {
|
|
103
|
+
throw new ShardError("protocol/unknown-owner", `No live owner "${name}"`, {
|
|
104
|
+
hint: "owners.describe with no name lists every live owner."
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return owner;
|
|
108
|
+
}
|
|
109
|
+
function requireComponent(name) {
|
|
110
|
+
const def = knownComponent(name);
|
|
111
|
+
if (def.hostOnly) {
|
|
112
|
+
throw new ShardError(
|
|
113
|
+
"core/owner-not-authorable",
|
|
114
|
+
`"${name}" can't be written through the protocol`,
|
|
115
|
+
{
|
|
116
|
+
hint: "Ownership comes from host code holding an Owner (world.owners)."
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return def;
|
|
121
|
+
}
|
|
99
122
|
function toJson(value) {
|
|
100
123
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
101
124
|
return JSON.parse(
|
|
@@ -346,7 +369,7 @@ const METHODS = [
|
|
|
346
369
|
params: s("SchemaGetParams", {
|
|
347
370
|
name: t.string({ required: true, description: 'Component name, e.g. "render/Camera3d".' })
|
|
348
371
|
}),
|
|
349
|
-
handler: (_, p) =>
|
|
372
|
+
handler: (_, p) => knownComponent(p.name).jsonSchema()
|
|
350
373
|
},
|
|
351
374
|
{
|
|
352
375
|
name: "world.stats",
|
|
@@ -439,6 +462,23 @@ const METHODS = [
|
|
|
439
462
|
return entityJson(world, entity);
|
|
440
463
|
}
|
|
441
464
|
},
|
|
465
|
+
{
|
|
466
|
+
name: "owners.describe",
|
|
467
|
+
description: "Owners (0061). With a name: its usage (entities, triangles, textures, bytes), limits, child owners, asset leases and GPU objects. Without: every live owner.",
|
|
468
|
+
params: s("OwnersDescribeParams", { name: t.string() }),
|
|
469
|
+
handler: ({ world }, p) => p.name ? world.owners.describe(requireOwner(world, p.name)) : { owners: world.owners.list() }
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "owners.release",
|
|
473
|
+
description: "Releases an owner and its child owners: despawns their entities, drops their asset leases (unloading assets nobody else leases, with their GPU objects).",
|
|
474
|
+
params: s("OwnersReleaseParams", { name: t.string({ required: true }) }),
|
|
475
|
+
handler: ({ world }, p) => {
|
|
476
|
+
const owner = requireOwner(world, p.name);
|
|
477
|
+
const entities = world.owners.describe(owner).usage.entities;
|
|
478
|
+
world.owners.release(owner);
|
|
479
|
+
return { released: owner.name, entities };
|
|
480
|
+
}
|
|
481
|
+
},
|
|
442
482
|
{
|
|
443
483
|
name: "entity.despawn",
|
|
444
484
|
description: "Despawns an entity (and its children unless recursive is false).",
|
|
@@ -467,10 +507,19 @@ const METHODS = [
|
|
|
467
507
|
},
|
|
468
508
|
{
|
|
469
509
|
name: "resource.set",
|
|
470
|
-
description: "Merges fields into a plain-object resource (e.g. render/AmbientLight).",
|
|
510
|
+
description: "Merges fields into a plain-object resource (e.g. render/AmbientLight), marking it changed and waking an on-demand app.",
|
|
471
511
|
params: s("ResourceSetParams", { name: t.string({ required: true }), value: t.json() }),
|
|
472
512
|
handler: ({ world }, p) => {
|
|
473
513
|
const def = findResource(p.name);
|
|
514
|
+
if (def?.hostOnly) {
|
|
515
|
+
throw new ShardError(
|
|
516
|
+
"core/owner-not-authorable",
|
|
517
|
+
`Resource "${p.name}" is written by the host only`,
|
|
518
|
+
{
|
|
519
|
+
hint: "Host code sets it; the protocol can read it with resource.get."
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
}
|
|
474
523
|
const current = def && world.tryResource(def);
|
|
475
524
|
if (!def || !isPlainObject(current)) {
|
|
476
525
|
throw new ShardError(
|
|
@@ -485,7 +534,7 @@ const METHODS = [
|
|
|
485
534
|
throw new ShardError("schema/type-mismatch", '"value" must be an object', {
|
|
486
535
|
path: "/value"
|
|
487
536
|
});
|
|
488
|
-
|
|
537
|
+
world.patchResource(def, p.value);
|
|
489
538
|
return toJson(current);
|
|
490
539
|
}
|
|
491
540
|
},
|
|
@@ -502,10 +551,11 @@ const METHODS = [
|
|
|
502
551
|
name: "time.resume",
|
|
503
552
|
description: "Resumes a paused game.",
|
|
504
553
|
params: none,
|
|
505
|
-
handler: ({ world }) => {
|
|
554
|
+
handler: ({ app, world }) => {
|
|
506
555
|
const control = world.resource(AppControlResource);
|
|
507
556
|
control.paused = false;
|
|
508
557
|
control.pausedByError = false;
|
|
558
|
+
app.requestFrame();
|
|
509
559
|
return { paused: false, frame: world.resource(Time).frame };
|
|
510
560
|
}
|
|
511
561
|
},
|
|
@@ -732,6 +782,20 @@ const METHODS = [
|
|
|
732
782
|
return { queued: true };
|
|
733
783
|
}
|
|
734
784
|
},
|
|
785
|
+
{
|
|
786
|
+
name: "input.simulate",
|
|
787
|
+
description: "Plays pointer gestures a step per frame, in CSS pixels, so controls and drags run headless (0060): [{ drag: { from: [x, y], to: [x, y], button, pointer, modifiers, frames, cancel } }, { pinch: { center, from, to, twist, pan, frames } }, { wheel: { at, dy, dx, modifiers } }, { tap: [x, y] }, { wait: frames }]. Needs gesturesPlugin. Returns how many frames they take.",
|
|
788
|
+
params: s("SimulateParams", {
|
|
789
|
+
gestures: t.json({ required: true, description: "The gestures, played one after another." })
|
|
790
|
+
}),
|
|
791
|
+
handler: ({ world }, p) => {
|
|
792
|
+
if (!Array.isArray(p.gestures))
|
|
793
|
+
throw new ShardError("protocol/invalid-params", "gestures must be a list", {
|
|
794
|
+
path: "/gestures"
|
|
795
|
+
});
|
|
796
|
+
return { frames: simulateGestures(world, p.gestures) };
|
|
797
|
+
}
|
|
798
|
+
},
|
|
735
799
|
{
|
|
736
800
|
name: "input.record",
|
|
737
801
|
description: "Starts recording input, or stops and returns the recording.",
|
|
@@ -1021,10 +1085,12 @@ function createProtocolServer(app, options = {}) {
|
|
|
1021
1085
|
}
|
|
1022
1086
|
};
|
|
1023
1087
|
byName.set(subscribe.name, subscribe);
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1088
|
+
const allow = options.allow ? new Set(options.allow) : void 0;
|
|
1089
|
+
const lookup = (name) => allow && !allow.has(name) ? void 0 : byName.get(name) ?? app.methods.find((m) => m.name === name);
|
|
1090
|
+
const allowed = (m) => !allow || allow.has(m.name);
|
|
1091
|
+
const server = {
|
|
1026
1092
|
get methods() {
|
|
1027
|
-
return [...byName.values(), ...app.methods.filter((m) => !byName.has(m.name))];
|
|
1093
|
+
return [...byName.values(), ...app.methods.filter((m) => !byName.has(m.name))].filter(allowed);
|
|
1028
1094
|
},
|
|
1029
1095
|
async handle(request) {
|
|
1030
1096
|
const id = request.id ?? null;
|
|
@@ -1033,13 +1099,21 @@ function createProtocolServer(app, options = {}) {
|
|
|
1033
1099
|
if (request?.jsonrpc !== "2.0" || typeof request.method !== "string") {
|
|
1034
1100
|
return reply({ error: { code: ERROR.invalid, message: "Invalid JSON-RPC request" } });
|
|
1035
1101
|
}
|
|
1102
|
+
if (allow && !allow.has(request.method)) {
|
|
1103
|
+
const err = new ShardError(
|
|
1104
|
+
"protocol/method-not-allowed",
|
|
1105
|
+
`"${request.method}" isn't allowed on this server`,
|
|
1106
|
+
{ hint: `This host allows: ${[...allow].join(", ")}.` }
|
|
1107
|
+
);
|
|
1108
|
+
return reply({ error: { code: ERROR.shard, message: err.message, data: err.toJSON() } });
|
|
1109
|
+
}
|
|
1036
1110
|
const method = lookup(request.method);
|
|
1037
1111
|
if (!method) {
|
|
1038
1112
|
return reply({
|
|
1039
1113
|
error: {
|
|
1040
1114
|
code: ERROR.notFound,
|
|
1041
1115
|
message: `Unknown method "${request.method}"`,
|
|
1042
|
-
data: { methods:
|
|
1116
|
+
data: { methods: server.methods.map((m) => m.name) }
|
|
1043
1117
|
}
|
|
1044
1118
|
});
|
|
1045
1119
|
}
|
|
@@ -1079,6 +1153,7 @@ function createProtocolServer(app, options = {}) {
|
|
|
1079
1153
|
listeners.clear();
|
|
1080
1154
|
}
|
|
1081
1155
|
};
|
|
1156
|
+
return server;
|
|
1082
1157
|
}
|
|
1083
1158
|
export {
|
|
1084
1159
|
METHODS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-protocol",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@aethervtt/shard-assets": "0.0
|
|
20
|
-
"@aethervtt/shard-core": "0.0
|
|
21
|
-
"@aethervtt/shard-input": "0.0
|
|
22
|
-
"@aethervtt/shard-platform": "0.0
|
|
23
|
-
"@aethervtt/shard-render": "0.0
|
|
24
|
-
"@aethervtt/shard-runtime": "0.0
|
|
25
|
-
"@aethervtt/shard-scene": "0.0
|
|
26
|
-
"@aethervtt/shard-text": "0.0
|
|
27
|
-
"@aethervtt/shard-texture": "0.0
|
|
28
|
-
"@aethervtt/shard-transform": "0.0
|
|
19
|
+
"@aethervtt/shard-assets": "0.1.0",
|
|
20
|
+
"@aethervtt/shard-core": "0.1.0",
|
|
21
|
+
"@aethervtt/shard-input": "0.1.0",
|
|
22
|
+
"@aethervtt/shard-platform": "0.1.0",
|
|
23
|
+
"@aethervtt/shard-render": "0.1.0",
|
|
24
|
+
"@aethervtt/shard-runtime": "0.1.0",
|
|
25
|
+
"@aethervtt/shard-scene": "0.1.0",
|
|
26
|
+
"@aethervtt/shard-text": "0.1.0",
|
|
27
|
+
"@aethervtt/shard-texture": "0.1.0",
|
|
28
|
+
"@aethervtt/shard-transform": "0.1.0"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"provenance": true
|
|
38
38
|
},
|
|
39
39
|
"shard": {
|
|
40
|
-
"buildSha": "
|
|
40
|
+
"buildSha": "8e14c9896f82a3554b156f1ffdd68f2cc3e7d78a"
|
|
41
41
|
}
|
|
42
42
|
}
|