@anweather/game-plugin-ssdk 1.24.0 → 1.26.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-cjs/runtime/conformance.js +7 -0
- package/dist-cjs/runtime/index.js +6 -1
- package/dist-cjs/runtime/presentation.js +98 -0
- package/dist-cjs/runtime/servePlugin.js +18 -1
- package/dist-cjs/src/models/models_0.js +5 -0
- package/dist-cjs/src/protocols/Aws_restJson1.js +1 -0
- package/dist-cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist-es/runtime/conformance.js +7 -0
- package/dist-es/runtime/index.js +1 -0
- package/dist-es/runtime/presentation.js +93 -0
- package/dist-es/runtime/servePlugin.js +18 -1
- package/dist-es/src/models/models_0.js +5 -0
- package/dist-es/src/protocols/Aws_restJson1.js +1 -0
- package/dist-es/tsconfig.es.tsbuildinfo +1 -1
- package/dist-types/runtime/index.d.ts +2 -0
- package/dist-types/runtime/presentation.d.ts +68 -0
- package/dist-types/runtime/servePlugin.d.ts +7 -0
- package/dist-types/src/models/models_0.d.ts +1 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ exports.runConformance = runConformance;
|
|
|
4
4
|
exports.assertConformance = assertConformance;
|
|
5
5
|
const node_util_1 = require("node:util");
|
|
6
6
|
const version_1 = require("./version");
|
|
7
|
+
const presentation_1 = require("./presentation");
|
|
7
8
|
const conformance_1 = require("./live/conformance");
|
|
8
9
|
function participantIds(n) {
|
|
9
10
|
return Array.from({ length: n }, (_, i) => `00000000-0000-4000-8000-${String(i + 1).padStart(12, "0")}`);
|
|
@@ -34,6 +35,12 @@ async function runConformance(service, context, options = {}) {
|
|
|
34
35
|
expect(minPlayers >= 1, `minPlayers ${metadata.minPlayers} < 1`);
|
|
35
36
|
expect(maxPlayers >= minPlayers, `maxPlayers ${metadata.maxPlayers} < minPlayers ${metadata.minPlayers}`);
|
|
36
37
|
});
|
|
38
|
+
if (metadata.presentation !== undefined) {
|
|
39
|
+
await check("GetMetadata: presentation is valid", async () => {
|
|
40
|
+
const problems = (0, presentation_1.validatePresentation)(metadata.presentation);
|
|
41
|
+
expect(problems.length === 0, problems.join("; "));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
37
44
|
await check("Health: ok, same gameType as metadata, current protocol version", async () => {
|
|
38
45
|
const health = await service.Health({}, context);
|
|
39
46
|
expect(health.status === "ok", `status is "${health.status}"`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MAX_MEMBERSHIP_AGE_MS = exports.FakeSpaceClient = exports.NotASpaceMemberError = exports.SpaceApiError = exports.isAlreadyASpace = exports.createMembershipGuard = exports.createSpaceClient = exports.MAX_LINK_FRAME_BYTES = exports.LinkCloseCode = exports.LIVE_PATH = exports.LIVE_PROTOCOL_VERSION = exports.assertLiveConformance = exports.runLiveConformance = exports.loadRoomCheckpoint = exports.createLiveHarness = exports.attachLive = exports.isVersionConflict = exports.BlobStoreError = exports.allBlobOwners = exports.blobOwner = exports.casUpdate = exports.createBlobClient = exports.runtimeVersion = exports.PLUGIN_PROTOCOL_VERSION = exports.assertConformance = exports.runConformance = exports.createRegistrationLoop = exports.createPluginApp = exports.servePlugin = exports.createGamePluginService = exports.mountSsdkHandler = void 0;
|
|
3
|
+
exports.MAX_MEMBERSHIP_AGE_MS = exports.FakeSpaceClient = exports.NotASpaceMemberError = exports.SpaceApiError = exports.isAlreadyASpace = exports.createMembershipGuard = exports.createSpaceClient = exports.MAX_LINK_FRAME_BYTES = exports.LinkCloseCode = exports.LIVE_PATH = exports.LIVE_PROTOCOL_VERSION = exports.assertLiveConformance = exports.runLiveConformance = exports.loadRoomCheckpoint = exports.createLiveHarness = exports.attachLive = exports.isVersionConflict = exports.BlobStoreError = exports.allBlobOwners = exports.blobOwner = exports.casUpdate = exports.createBlobClient = exports.runtimeVersion = exports.PLUGIN_PROTOCOL_VERSION = exports.MAX_PRESENTATION_BYTES = exports.PRESENTATION_CATEGORIES = exports.assertPresentation = exports.validatePresentation = exports.assertConformance = exports.runConformance = exports.createRegistrationLoop = exports.createPluginApp = exports.servePlugin = exports.createGamePluginService = exports.mountSsdkHandler = void 0;
|
|
4
4
|
var express_1 = require("./express");
|
|
5
5
|
Object.defineProperty(exports, "mountSsdkHandler", { enumerable: true, get: function () { return express_1.mountSsdkHandler; } });
|
|
6
6
|
var defaultService_1 = require("./defaultService");
|
|
@@ -13,6 +13,11 @@ Object.defineProperty(exports, "createRegistrationLoop", { enumerable: true, get
|
|
|
13
13
|
var conformance_1 = require("./conformance");
|
|
14
14
|
Object.defineProperty(exports, "runConformance", { enumerable: true, get: function () { return conformance_1.runConformance; } });
|
|
15
15
|
Object.defineProperty(exports, "assertConformance", { enumerable: true, get: function () { return conformance_1.assertConformance; } });
|
|
16
|
+
var presentation_1 = require("./presentation");
|
|
17
|
+
Object.defineProperty(exports, "validatePresentation", { enumerable: true, get: function () { return presentation_1.validatePresentation; } });
|
|
18
|
+
Object.defineProperty(exports, "assertPresentation", { enumerable: true, get: function () { return presentation_1.assertPresentation; } });
|
|
19
|
+
Object.defineProperty(exports, "PRESENTATION_CATEGORIES", { enumerable: true, get: function () { return presentation_1.PRESENTATION_CATEGORIES; } });
|
|
20
|
+
Object.defineProperty(exports, "MAX_PRESENTATION_BYTES", { enumerable: true, get: function () { return presentation_1.MAX_PRESENTATION_BYTES; } });
|
|
16
21
|
var version_1 = require("./version");
|
|
17
22
|
Object.defineProperty(exports, "PLUGIN_PROTOCOL_VERSION", { enumerable: true, get: function () { return version_1.PLUGIN_PROTOCOL_VERSION; } });
|
|
18
23
|
Object.defineProperty(exports, "runtimeVersion", { enumerable: true, get: function () { return version_1.runtimeVersion; } });
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_PRESENTATION_BYTES = exports.PRESENTATION_CATEGORIES = void 0;
|
|
4
|
+
exports.validatePresentation = validatePresentation;
|
|
5
|
+
exports.assertPresentation = assertPresentation;
|
|
6
|
+
exports.PRESENTATION_CATEGORIES = ["classic", "strategy", "cards", "word", "wip"];
|
|
7
|
+
exports.MAX_PRESENTATION_BYTES = 32 * 1024;
|
|
8
|
+
const str = { kind: "string" };
|
|
9
|
+
const bool = { kind: "boolean" };
|
|
10
|
+
const oneOf = (...values) => ({ kind: "oneOf", values });
|
|
11
|
+
const obj = (fields) => ({ kind: "object", fields });
|
|
12
|
+
const SCHEMA = obj({
|
|
13
|
+
catalogue: obj({
|
|
14
|
+
title: str,
|
|
15
|
+
category: oneOf(...exports.PRESENTATION_CATEGORIES),
|
|
16
|
+
featured: bool,
|
|
17
|
+
poster: obj({ from: { kind: "token" }, to: { kind: "token" }, text: { kind: "token" } }),
|
|
18
|
+
}),
|
|
19
|
+
rules: { kind: "strings" },
|
|
20
|
+
board: obj({
|
|
21
|
+
layout: oneOf("fullscreen", "page"),
|
|
22
|
+
page: obj({ nameplates: oneOf("split", "below", "none"), fullBleed: bool }),
|
|
23
|
+
moveErrors: oneOf("platform", "board"),
|
|
24
|
+
nativeGestures: oneOf("suppress", "allow"),
|
|
25
|
+
recap: { kind: "falseOr", object: obj({ stepMs: { kind: "int", min: 100, max: 10_000 } }) },
|
|
26
|
+
host: obj({ overflowX: oneOf("auto", "hidden", "clip"), width: oneOf("fit", "full") }),
|
|
27
|
+
}),
|
|
28
|
+
rematch: obj({ seatNames: { kind: "strings", size: 2 } }),
|
|
29
|
+
});
|
|
30
|
+
const POSTER_TOKEN = /^([a-z]+-(50|[1-9]00|950)|white|black)$/;
|
|
31
|
+
function validatePresentation(value) {
|
|
32
|
+
const problems = [];
|
|
33
|
+
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
34
|
+
const visit = (v, rule, path) => {
|
|
35
|
+
switch (rule.kind) {
|
|
36
|
+
case "string":
|
|
37
|
+
if (typeof v !== "string")
|
|
38
|
+
problems.push(`${path} is not a string`);
|
|
39
|
+
return;
|
|
40
|
+
case "boolean":
|
|
41
|
+
if (typeof v !== "boolean")
|
|
42
|
+
problems.push(`${path} is not a boolean`);
|
|
43
|
+
return;
|
|
44
|
+
case "oneOf":
|
|
45
|
+
if (typeof v !== "string" || !rule.values.includes(v))
|
|
46
|
+
problems.push(`${path} is not one of ${rule.values.join(", ")}`);
|
|
47
|
+
return;
|
|
48
|
+
case "int":
|
|
49
|
+
if (!Number.isInteger(v) || v < rule.min || v > rule.max) {
|
|
50
|
+
problems.push(`${path} is not an integer in ${rule.min}..${rule.max}`);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
case "strings":
|
|
54
|
+
if (!Array.isArray(v) || !v.every((s) => typeof s === "string"))
|
|
55
|
+
problems.push(`${path} is not a list of strings`);
|
|
56
|
+
else if (rule.size !== undefined && v.length !== rule.size)
|
|
57
|
+
problems.push(`${path} does not have ${rule.size} entries`);
|
|
58
|
+
return;
|
|
59
|
+
case "token":
|
|
60
|
+
if (typeof v !== "string" || !POSTER_TOKEN.test(v))
|
|
61
|
+
problems.push(`${path} is not a palette token like "amber-700"`);
|
|
62
|
+
return;
|
|
63
|
+
case "falseOr":
|
|
64
|
+
if (v === false)
|
|
65
|
+
return;
|
|
66
|
+
if (!isObject(v))
|
|
67
|
+
problems.push(`${path} is neither false nor an object`);
|
|
68
|
+
else
|
|
69
|
+
visit(v, rule.object, path);
|
|
70
|
+
return;
|
|
71
|
+
case "object":
|
|
72
|
+
if (!isObject(v)) {
|
|
73
|
+
problems.push(`${path} is not an object`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const [key, child] of Object.entries(v)) {
|
|
77
|
+
const childRule = rule.fields[key];
|
|
78
|
+
if (!childRule)
|
|
79
|
+
problems.push(`${path}.${key} is not a known field`);
|
|
80
|
+
else if (child !== undefined)
|
|
81
|
+
visit(child, childRule, `${path}.${key}`);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
visit(value, SCHEMA, "presentation");
|
|
87
|
+
if (problems.length === 0) {
|
|
88
|
+
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
89
|
+
if (bytes > exports.MAX_PRESENTATION_BYTES)
|
|
90
|
+
problems.push(`presentation is ${bytes} bytes; the limit is ${exports.MAX_PRESENTATION_BYTES}`);
|
|
91
|
+
}
|
|
92
|
+
return problems;
|
|
93
|
+
}
|
|
94
|
+
function assertPresentation(value) {
|
|
95
|
+
const problems = validatePresentation(value);
|
|
96
|
+
if (problems.length > 0)
|
|
97
|
+
throw new Error(`invalid presentation:\n ${problems.join("\n ")}`);
|
|
98
|
+
}
|
|
@@ -10,8 +10,22 @@ const express_2 = require("./express");
|
|
|
10
10
|
const registration_1 = require("./registration");
|
|
11
11
|
const version_1 = require("./version");
|
|
12
12
|
const server_1 = require("./live/server");
|
|
13
|
+
const presentation_1 = require("./presentation");
|
|
13
14
|
const auth_1 = require("./auth");
|
|
14
15
|
const permissiveValidation = () => undefined;
|
|
16
|
+
function withPresentation(service, presentation) {
|
|
17
|
+
if (presentation === undefined)
|
|
18
|
+
return service;
|
|
19
|
+
return {
|
|
20
|
+
...service,
|
|
21
|
+
GetMetadata: async (input, context) => {
|
|
22
|
+
const metadata = await service.GetMetadata(input, context);
|
|
23
|
+
return metadata.presentation === undefined
|
|
24
|
+
? { ...metadata, presentation: presentation }
|
|
25
|
+
: metadata;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
15
29
|
function createPluginApp(options) {
|
|
16
30
|
const env = options.env ?? process.env;
|
|
17
31
|
const port = Number(env.PORT ?? options.defaultPort ?? 0);
|
|
@@ -19,6 +33,9 @@ function createPluginApp(options) {
|
|
|
19
33
|
const platformUrl = env.PLATFORM_URL || undefined;
|
|
20
34
|
const backendBaseUrl = env.PLUGIN_BACKEND_BASE_URL || `http://localhost:${port}`;
|
|
21
35
|
const log = options.log ?? console;
|
|
36
|
+
if (options.presentation !== undefined)
|
|
37
|
+
(0, presentation_1.assertPresentation)(options.presentation);
|
|
38
|
+
const definition = withPresentation(options.service, options.presentation);
|
|
22
39
|
const service = (0, defaultService_1.createGamePluginService)({
|
|
23
40
|
Health: async () => ({
|
|
24
41
|
status: "ok",
|
|
@@ -26,7 +43,7 @@ function createPluginApp(options) {
|
|
|
26
43
|
protocolVersion: version_1.PLUGIN_PROTOCOL_VERSION,
|
|
27
44
|
runtimeVersion: (0, version_1.runtimeVersion)(),
|
|
28
45
|
}),
|
|
29
|
-
...
|
|
46
|
+
...definition,
|
|
30
47
|
});
|
|
31
48
|
const handler = (0, GamePluginService_1.getGamePluginServiceHandler)(service, options.validationCustomizer ?? permissiveValidation);
|
|
32
49
|
const app = (0, express_1.default)();
|
|
@@ -600,6 +600,10 @@ var GameTypeMetadata;
|
|
|
600
600
|
memberValidators["live"] = new server_common_1.CompositeStructureValidator(new server_common_1.NoOpValidator(), LiveCapability.validate);
|
|
601
601
|
break;
|
|
602
602
|
}
|
|
603
|
+
case "presentation": {
|
|
604
|
+
memberValidators["presentation"] = new server_common_1.NoOpValidator();
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
609
|
return memberValidators[member];
|
|
@@ -622,6 +626,7 @@ var GameTypeMetadata;
|
|
|
622
626
|
...getMemberValidator("hasSaveFileEditor").validate(obj.hasSaveFileEditor, `${path}/hasSaveFileEditor`),
|
|
623
627
|
...getMemberValidator("spaces").validate(obj.spaces, `${path}/spaces`),
|
|
624
628
|
...getMemberValidator("live").validate(obj.live, `${path}/live`),
|
|
629
|
+
...getMemberValidator("presentation").validate(obj.presentation, `${path}/presentation`),
|
|
625
630
|
];
|
|
626
631
|
};
|
|
627
632
|
})(GameTypeMetadata || (exports.GameTypeMetadata = GameTypeMetadata = {}));
|
|
@@ -643,6 +643,7 @@ const serializeGetMetadataResponse = async (input, ctx) => {
|
|
|
643
643
|
'live': _ => se_LiveCapability(_, context),
|
|
644
644
|
'maxPlayers': [],
|
|
645
645
|
'minPlayers': [],
|
|
646
|
+
'presentation': _ => se_Document(_, context),
|
|
646
647
|
'spaces': _ => se_SpaceCapability(_, context),
|
|
647
648
|
'spectatorSafe': [],
|
|
648
649
|
'teamPlay': [],
|