@anweather/game-plugin-ssdk 1.16.0 → 1.17.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/auth.js +8 -0
- package/dist-cjs/runtime/conformance.js +6 -0
- package/dist-cjs/runtime/index.js +15 -1
- package/dist-cjs/runtime/live/checkpoint.js +31 -0
- package/dist-cjs/runtime/live/conformance.js +215 -0
- package/dist-cjs/runtime/live/harness.js +144 -0
- package/dist-cjs/runtime/live/outbox.js +72 -0
- package/dist-cjs/runtime/live/protocol.js +227 -0
- package/dist-cjs/runtime/live/rooms.js +579 -0
- package/dist-cjs/runtime/live/server.js +105 -0
- package/dist-cjs/runtime/servePlugin.js +8 -2
- package/dist-cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist-es/runtime/auth.js +5 -0
- package/dist-es/runtime/conformance.js +6 -0
- package/dist-es/runtime/index.js +5 -0
- package/dist-es/runtime/live/checkpoint.js +28 -0
- package/dist-es/runtime/live/conformance.js +210 -0
- package/dist-es/runtime/live/harness.js +141 -0
- package/dist-es/runtime/live/outbox.js +68 -0
- package/dist-es/runtime/live/protocol.js +217 -0
- package/dist-es/runtime/live/rooms.js +575 -0
- package/dist-es/runtime/live/server.js +102 -0
- package/dist-es/runtime/servePlugin.js +8 -2
- package/dist-es/tsconfig.es.tsbuildinfo +1 -1
- package/dist-types/runtime/auth.d.ts +3 -0
- package/dist-types/runtime/conformance.d.ts +6 -0
- package/dist-types/runtime/index.d.ts +11 -0
- package/dist-types/runtime/live/checkpoint.d.ts +15 -0
- package/dist-types/runtime/live/conformance.d.ts +24 -0
- package/dist-types/runtime/live/harness.d.ts +71 -0
- package/dist-types/runtime/live/outbox.d.ts +39 -0
- package/dist-types/runtime/live/protocol.d.ts +169 -0
- package/dist-types/runtime/live/rooms.d.ts +302 -0
- package/dist-types/runtime/live/server.d.ts +36 -0
- package/dist-types/runtime/servePlugin.d.ts +8 -0
- package/package.json +6 -3
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_KICK_REASON = exports.LinkCloseCode = exports.MAX_LINK_FRAME_BYTES = exports.LIVE_HEADERS = exports.LIVE_PATH = exports.LIVE_PROTOCOL_VERSION = void 0;
|
|
4
|
+
exports.decodePlatformFrame = decodePlatformFrame;
|
|
5
|
+
exports.decodePluginFrame = decodePluginFrame;
|
|
6
|
+
exports.isKickCode = isKickCode;
|
|
7
|
+
exports.encodeFrame = encodeFrame;
|
|
8
|
+
exports.byteLength = byteLength;
|
|
9
|
+
exports.parseScope = parseScope;
|
|
10
|
+
exports.checkHandshake = checkHandshake;
|
|
11
|
+
const auth_1 = require("../auth");
|
|
12
|
+
exports.LIVE_PROTOCOL_VERSION = 1;
|
|
13
|
+
exports.LIVE_PATH = "/v1/live";
|
|
14
|
+
exports.LIVE_HEADERS = {
|
|
15
|
+
auth: "x-plugin-auth",
|
|
16
|
+
scope: "x-live-scope",
|
|
17
|
+
epoch: "x-live-epoch",
|
|
18
|
+
gameType: "x-game-type",
|
|
19
|
+
protocol: "x-live-protocol",
|
|
20
|
+
};
|
|
21
|
+
exports.MAX_LINK_FRAME_BYTES = 64 * 1024;
|
|
22
|
+
exports.LinkCloseCode = {
|
|
23
|
+
NORMAL: 1000,
|
|
24
|
+
GOING_AWAY: 1001,
|
|
25
|
+
POLICY: 1008,
|
|
26
|
+
TOO_BIG: 1009,
|
|
27
|
+
INTERNAL_ERROR: 1011,
|
|
28
|
+
RESTART: 1012,
|
|
29
|
+
TOO_SLOW: 4008,
|
|
30
|
+
SUPERSEDED: 4409,
|
|
31
|
+
GONE: 4410,
|
|
32
|
+
};
|
|
33
|
+
function parseObject(text) {
|
|
34
|
+
let value;
|
|
35
|
+
try {
|
|
36
|
+
value = JSON.parse(text);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return "not JSON";
|
|
40
|
+
}
|
|
41
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
42
|
+
return "not a JSON object";
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
const isStr = (v) => typeof v === "string";
|
|
46
|
+
const isNonEmpty = (v) => typeof v === "string" && v.length > 0;
|
|
47
|
+
class Malformed extends Error {
|
|
48
|
+
}
|
|
49
|
+
function need(o, field, check, what) {
|
|
50
|
+
const v = o[field];
|
|
51
|
+
if (!check(v))
|
|
52
|
+
throw new Malformed(`${o.t}: ${field} must be ${what}`);
|
|
53
|
+
return v;
|
|
54
|
+
}
|
|
55
|
+
const isRole = (v) => v === "PARTICIPANT" || v === "SPECTATOR";
|
|
56
|
+
const isState = (v) => v === "ACTIVE" || v === "AWAY";
|
|
57
|
+
const isRevision = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
58
|
+
const isMember = (v) => {
|
|
59
|
+
if (typeof v !== "object" || v === null)
|
|
60
|
+
return false;
|
|
61
|
+
const m = v;
|
|
62
|
+
return isNonEmpty(m.playerId) && isStr(m.displayName) && isStr(m.role) && isStr(m.status);
|
|
63
|
+
};
|
|
64
|
+
const isMembers = (v) => Array.isArray(v) && v.every(isMember);
|
|
65
|
+
const isBool = (v) => typeof v === "boolean";
|
|
66
|
+
const isAnything = (v) => v !== undefined;
|
|
67
|
+
function decodePlatformFrame(text) {
|
|
68
|
+
const o = parseObject(text);
|
|
69
|
+
if (typeof o === "string")
|
|
70
|
+
return { ok: false, unknown: false, error: o };
|
|
71
|
+
if (!isStr(o.t))
|
|
72
|
+
return { ok: false, unknown: false, error: "t must be a string" };
|
|
73
|
+
try {
|
|
74
|
+
switch (o.t) {
|
|
75
|
+
case "join":
|
|
76
|
+
return {
|
|
77
|
+
ok: true,
|
|
78
|
+
frame: {
|
|
79
|
+
t: "join",
|
|
80
|
+
conn: need(o, "conn", isNonEmpty, "a non-empty string"),
|
|
81
|
+
participantId: need(o, "participantId", isNonEmpty, "a non-empty string"),
|
|
82
|
+
playerId: need(o, "playerId", isNonEmpty, "a non-empty string"),
|
|
83
|
+
displayName: need(o, "displayName", isStr, "a string"),
|
|
84
|
+
role: need(o, "role", isRole, "PARTICIPANT or SPECTATOR"),
|
|
85
|
+
gameId: need(o, "gameId", isNonEmpty, "a non-empty string"),
|
|
86
|
+
hidden: o.hidden === undefined ? false : need(o, "hidden", isBool, "a boolean"),
|
|
87
|
+
state: o.state === undefined ? "ACTIVE" : need(o, "state", isState, "ACTIVE or AWAY"),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
case "leave":
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
frame: {
|
|
94
|
+
t: "leave",
|
|
95
|
+
conn: need(o, "conn", isNonEmpty, "a non-empty string"),
|
|
96
|
+
reason: o.reason === undefined ? "closed" : need(o, "reason", isStr, "a string"),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
case "sig":
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
frame: {
|
|
103
|
+
t: "sig",
|
|
104
|
+
conn: need(o, "conn", isNonEmpty, "a non-empty string"),
|
|
105
|
+
participantId: need(o, "participantId", isNonEmpty, "a non-empty string"),
|
|
106
|
+
d: need(o, "d", isAnything, "present"),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
case "presence":
|
|
110
|
+
return {
|
|
111
|
+
ok: true,
|
|
112
|
+
frame: {
|
|
113
|
+
t: "presence",
|
|
114
|
+
conn: need(o, "conn", isNonEmpty, "a non-empty string"),
|
|
115
|
+
state: need(o, "state", isState, "ACTIVE or AWAY"),
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
case "game":
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
frame: {
|
|
122
|
+
t: "game",
|
|
123
|
+
gameId: need(o, "gameId", isNonEmpty, "a non-empty string"),
|
|
124
|
+
revision: need(o, "revision", isRevision, "a non-negative integer"),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
case "members":
|
|
128
|
+
return { ok: true, frame: { t: "members", members: need(o, "members", isMembers, "an array of members") } };
|
|
129
|
+
default:
|
|
130
|
+
return { ok: false, unknown: true, t: o.t };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
if (e instanceof Malformed)
|
|
135
|
+
return { ok: false, unknown: false, error: e.message };
|
|
136
|
+
throw e;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function decodePluginFrame(text) {
|
|
140
|
+
const o = parseObject(text);
|
|
141
|
+
if (typeof o === "string")
|
|
142
|
+
return { ok: false, unknown: false, error: o };
|
|
143
|
+
if (!isStr(o.t))
|
|
144
|
+
return { ok: false, unknown: false, error: "t must be a string" };
|
|
145
|
+
try {
|
|
146
|
+
switch (o.t) {
|
|
147
|
+
case "send": {
|
|
148
|
+
const to = o.to;
|
|
149
|
+
if (!(to === "all" || (Array.isArray(to) && to.every(isNonEmpty)))) {
|
|
150
|
+
throw new Malformed(`send: to must be "all" or an array of conn ids`);
|
|
151
|
+
}
|
|
152
|
+
const frame = { t: "send", to: to, d: need(o, "d", isAnything, "present") };
|
|
153
|
+
if (o.k !== undefined)
|
|
154
|
+
frame.k = need(o, "k", isNonEmpty, "a non-empty string");
|
|
155
|
+
return { ok: true, frame };
|
|
156
|
+
}
|
|
157
|
+
case "kick": {
|
|
158
|
+
const frame = {
|
|
159
|
+
t: "kick",
|
|
160
|
+
conn: need(o, "conn", isNonEmpty, "a non-empty string"),
|
|
161
|
+
code: need(o, "code", isKickCode, "4403 or an integer from 4900 to 4999"),
|
|
162
|
+
};
|
|
163
|
+
if (o.reason !== undefined)
|
|
164
|
+
frame.reason = need(o, "reason", isCloseReason, "a string of at most 120 characters");
|
|
165
|
+
return { ok: true, frame };
|
|
166
|
+
}
|
|
167
|
+
case "ready":
|
|
168
|
+
return { ok: true, frame: { t: "ready" } };
|
|
169
|
+
default:
|
|
170
|
+
return { ok: false, unknown: true, t: o.t };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
if (e instanceof Malformed)
|
|
175
|
+
return { ok: false, unknown: false, error: e.message };
|
|
176
|
+
throw e;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.MAX_KICK_REASON = 120;
|
|
180
|
+
const isCloseReason = (v) => typeof v === "string" && v.length <= exports.MAX_KICK_REASON;
|
|
181
|
+
function isKickCode(v) {
|
|
182
|
+
return typeof v === "number" && Number.isInteger(v) && (v === 4403 || (v >= 4900 && v <= 4999));
|
|
183
|
+
}
|
|
184
|
+
function encodeFrame(frame) {
|
|
185
|
+
return JSON.stringify(frame);
|
|
186
|
+
}
|
|
187
|
+
function byteLength(text) {
|
|
188
|
+
return Buffer.byteLength(text, "utf8");
|
|
189
|
+
}
|
|
190
|
+
function parseScope(header) {
|
|
191
|
+
if (!header)
|
|
192
|
+
return null;
|
|
193
|
+
const colon = header.indexOf(":");
|
|
194
|
+
if (colon <= 0)
|
|
195
|
+
return null;
|
|
196
|
+
const kind = header.slice(0, colon);
|
|
197
|
+
const id = header.slice(colon + 1);
|
|
198
|
+
if (!id || /\s/.test(id))
|
|
199
|
+
return null;
|
|
200
|
+
if (kind !== "game" && kind !== "space")
|
|
201
|
+
return null;
|
|
202
|
+
return { kind, id, key: `${kind}:${id}` };
|
|
203
|
+
}
|
|
204
|
+
const header = (h, name) => {
|
|
205
|
+
const v = h[name];
|
|
206
|
+
return Array.isArray(v) ? v[0] : v;
|
|
207
|
+
};
|
|
208
|
+
function checkHandshake(headers, expect) {
|
|
209
|
+
if (expect.authToken && !(0, auth_1.bearerMatches)(header(headers, exports.LIVE_HEADERS.auth), expect.authToken)) {
|
|
210
|
+
return { ok: false, status: 401, message: "missing or invalid X-Plugin-Auth" };
|
|
211
|
+
}
|
|
212
|
+
const protocol = header(headers, exports.LIVE_HEADERS.protocol);
|
|
213
|
+
if (protocol !== String(exports.LIVE_PROTOCOL_VERSION)) {
|
|
214
|
+
return { ok: false, status: 400, message: `unsupported X-Live-Protocol ${protocol ?? "(absent)"}; this plugin speaks ${exports.LIVE_PROTOCOL_VERSION}` };
|
|
215
|
+
}
|
|
216
|
+
const gameType = header(headers, exports.LIVE_HEADERS.gameType);
|
|
217
|
+
if (gameType !== expect.gameType) {
|
|
218
|
+
return { ok: false, status: 404, message: `this plugin hosts ${expect.gameType}, not ${gameType ?? "(absent)"}` };
|
|
219
|
+
}
|
|
220
|
+
const scope = parseScope(header(headers, exports.LIVE_HEADERS.scope));
|
|
221
|
+
if (!scope)
|
|
222
|
+
return { ok: false, status: 400, message: "X-Live-Scope must be game:{id} or space:{id}" };
|
|
223
|
+
const epoch = header(headers, exports.LIVE_HEADERS.epoch);
|
|
224
|
+
if (!epoch)
|
|
225
|
+
return { ok: false, status: 400, message: "X-Live-Epoch is required" };
|
|
226
|
+
return { ok: true, scope, epoch, protocol: exports.LIVE_PROTOCOL_VERSION };
|
|
227
|
+
}
|