@roster-lock/types 1.0.0 → 1.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/v1/runtime/game-launcher/index.js +3 -0
- package/dist/v1/runtime/game-launcher/index.js.map +1 -0
- package/dist/v1/runtime/index.js +2 -0
- package/dist/v1/runtime/index.js.map +1 -1
- package/dist/v1/runtime/matchmaker-bridge/index.js +13 -0
- package/dist/v1/runtime/matchmaker-bridge/index.js.map +1 -0
- package/package.json +4 -1
- package/src/v1/lock/roster.ts +5 -1
- package/src/v1/lock/selection/index.ts +2 -2
- package/src/v1/relay/index.ts +5 -1
- package/src/v1/runtime/game-launcher/index.ts +228 -0
- package/src/v1/runtime/index.ts +2 -0
- package/src/v1/runtime/matchmaker-bridge/index.ts +86 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/v1/runtime/game-launcher/index.ts"],"names":[],"mappings":""}
|
package/dist/v1/runtime/index.js
CHANGED
|
@@ -17,4 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./download"), exports);
|
|
18
18
|
__exportStar(require("./unstrusted-script"), exports);
|
|
19
19
|
__exportStar(require("./piece-selection-sort"), exports);
|
|
20
|
+
__exportStar(require("./game-launcher"), exports);
|
|
21
|
+
__exportStar(require("./matchmaker-bridge"), exports);
|
|
20
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/v1/runtime/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,6CAA2B;AAC3B,sDAAoC;AACpC,yDAAuC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/v1/runtime/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,6CAA2B;AAC3B,sDAAoC;AACpC,yDAAuC;AACvC,kDAAgC;AAChC,sDAAoC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MATCHMAKER_BRIDGE_PATHS = void 0;
|
|
4
|
+
exports.MATCHMAKER_BRIDGE_PATHS = {
|
|
5
|
+
installGameLauncherPlugin: "installGameLauncherPlugin",
|
|
6
|
+
getInstalledGameLauncherPlugins: "getInstalledGameLauncherPlugins",
|
|
7
|
+
getIdentity: "getIdentity",
|
|
8
|
+
requestSelection: "requestSelection",
|
|
9
|
+
updateGameLauncherSettings: "updateGameLauncherSettings",
|
|
10
|
+
initiateRelay: "initiateRelay",
|
|
11
|
+
ready: "ready",
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/v1/runtime/matchmaker-bridge/index.ts"],"names":[],"mappings":";;;AA6Ea,QAAA,uBAAuB,GAAG;IACrC,yBAAyB,EAAE,2BAA2B;IACtD,+BAA+B,EAAE,iCAAiC;IAClE,WAAW,EAAE,aAAa;IAC1B,gBAAgB,EAAE,kBAAkB;IACpC,0BAA0B,EAAE,4BAA4B;IACxD,aAAa,EAAE,eAAe;IAC9B,KAAK,EAAE,OAAO;CACN,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roster-lock/types",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Types for roster lock",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"rimraf": "^6.1.3",
|
|
20
20
|
"typescript": "^7.0.2"
|
|
21
21
|
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"ajv": "^8.20.0"
|
|
24
|
+
},
|
|
22
25
|
"scripts": {
|
|
23
26
|
"build": "rimraf ./dist && tsc"
|
|
24
27
|
}
|
package/src/v1/lock/roster.ts
CHANGED
|
@@ -3,6 +3,10 @@ import { PieceId, Sha256 } from "../shared";
|
|
|
3
3
|
|
|
4
4
|
type URLType = string;
|
|
5
5
|
type DownloadableSource = string;
|
|
6
|
+
// A "data:image/<type>;base64,<data>" URI, embedded directly in the lock file rather
|
|
7
|
+
// than fetched - unlike downloadSources, selection UIs need this available offline/at
|
|
8
|
+
// select-time, before any piece content has been downloaded.
|
|
9
|
+
type ImageDataURI = string;
|
|
6
10
|
|
|
7
11
|
export type RosterLockPiece = {
|
|
8
12
|
id: PieceId,
|
|
@@ -15,7 +19,7 @@ export type RosterLockPiece = {
|
|
|
15
19
|
name: string,
|
|
16
20
|
author: string,
|
|
17
21
|
url: URLType,
|
|
18
|
-
image?:
|
|
22
|
+
image?: ImageDataURI,
|
|
19
23
|
}
|
|
20
24
|
downloadSources: Array<DownloadableSource>,
|
|
21
25
|
pathVariables: Record<string, string>,
|
|
@@ -8,7 +8,7 @@ import { SelectionUnselectableConfig } from "./selection-types/unselectable";
|
|
|
8
8
|
|
|
9
9
|
export * from "./meta";
|
|
10
10
|
|
|
11
|
-
export {
|
|
11
|
+
export type {
|
|
12
12
|
SelectionPreselectedConfig,
|
|
13
13
|
SelectionGameControlledConfig,
|
|
14
14
|
SelectionNormalConfig,
|
|
@@ -16,7 +16,7 @@ export {
|
|
|
16
16
|
UserSelectionValidation,
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export { UntrustedScriptRef };
|
|
19
|
+
export type { UntrustedScriptRef };
|
|
20
20
|
|
|
21
21
|
export type RosterLockSelectionConfig = {
|
|
22
22
|
piece: Record<PieceType, (
|
package/src/v1/relay/index.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
// Room Creation
|
|
3
3
|
export type RoomConfig = {
|
|
4
4
|
matchmakerId: string;
|
|
5
|
-
|
|
5
|
+
// `false` is an explicit, deliberate "this room has no game coordinator"
|
|
6
|
+
// choice (e.g. a fully-internal connection mode) - kept required rather
|
|
7
|
+
// than optional so a matchmaker can't omit it by accident and have that
|
|
8
|
+
// silently treated as an opt-out.
|
|
9
|
+
coordinatorId: string | false;
|
|
6
10
|
roomId: string;
|
|
7
11
|
rosterConfigHash: string;
|
|
8
12
|
machines: RoomMachine[];
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
|
|
2
|
+
import { RosterLockV1Config } from "../../lock";
|
|
3
|
+
import { RosterLockV1SyncDLResult } from "../../request";
|
|
4
|
+
import { RoomMachine } from "../../relay";
|
|
5
|
+
import { Sha256 } from "../../shared";
|
|
6
|
+
import type { AnySchema, JSONSchemaType } from "ajv";
|
|
7
|
+
|
|
8
|
+
export type ConnectionMode = "direct-tcp" | "room" | "internal";
|
|
9
|
+
|
|
10
|
+
// Mirrors NodeJS.Platform/NodeJS.Architecture (node_modules/@types/node's
|
|
11
|
+
// process.d.ts) rather than importing them - this package deliberately
|
|
12
|
+
// carries no @types/node dependency since it's also consumed by browser
|
|
13
|
+
// code (see package.json's empty "types" compiler option). A caller with
|
|
14
|
+
// @types/node can still pass process.platform/process.arch straight
|
|
15
|
+
// through unchanged.
|
|
16
|
+
export type PlatformName = (
|
|
17
|
+
"aix" | "android" | "darwin" | "freebsd" | "haiku" | "linux" |
|
|
18
|
+
"openbsd" | "sunos" | "win32" | "cygwin" | "netbsd"
|
|
19
|
+
);
|
|
20
|
+
export type ArchName = (
|
|
21
|
+
"arm" | "arm64" | "ia32" | "loong64" | "mips" | "mipsel" |
|
|
22
|
+
"ppc64" | "riscv64" | "s390x" | "x64"
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// What a caller resolves a multi-platform binaryLocation bundle against -
|
|
26
|
+
// see docs/v2/binary-location.md.
|
|
27
|
+
export type PlatformTarget = {
|
|
28
|
+
platform: PlatformName,
|
|
29
|
+
arch: ArchName,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// The two variants shared as-is between ConnectionSetup and ConnectionConfig
|
|
33
|
+
// below - neither needs anything resolved before a plugin can use it.
|
|
34
|
+
type RoomOrInternalConnection = (
|
|
35
|
+
// The room's actual connection work happens entirely on the game side -
|
|
36
|
+
// `version` just tells it which of its own supported approaches to use
|
|
37
|
+
// (see GameLauncherPlugin.supportedRoomVersions), `url` is where it does
|
|
38
|
+
// that work. No separate protocol field: whatever `version` names already
|
|
39
|
+
// implies its protocol.
|
|
40
|
+
| { type: "room", version: string, url: string }
|
|
41
|
+
// The game has its own online connection story - roster-lock hands it a
|
|
42
|
+
// selection and gets out of the way.
|
|
43
|
+
| { type: "internal" }
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// What a matchmaker/caller hands to match-agent's start-game route - a
|
|
47
|
+
// direct-tcp room's host address isn't knowable upfront by whoever builds
|
|
48
|
+
// this (it may be behind NAT, or simply not yet bound its listen socket), so
|
|
49
|
+
// both variants instead carry the address of a small rendezvous coordinator
|
|
50
|
+
// (see plugins/game-launcher/shared/direct-ip-coordinator and
|
|
51
|
+
// docs/v2/ikemen-go/game-coordinator.md) rather than a resolved address.
|
|
52
|
+
// match-agent's GameLauncher.startGame is what turns this into a
|
|
53
|
+
// ConnectionConfig below before a plugin ever sees it - no plugin talks to a
|
|
54
|
+
// coordinator itself.
|
|
55
|
+
export type ConnectionSetup = (
|
|
56
|
+
| { type: "direct-tcp", party: "host", port: number, coordinator: { host: string, port: number } }
|
|
57
|
+
| { type: "direct-tcp", party: "client", port: number, coordinator: { host: string, port: number } }
|
|
58
|
+
| RoomOrInternalConnection
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// What a GameLauncherPlugin's startGame actually receives - direct-tcp is
|
|
62
|
+
// already resolved by this point (see ConnectionSetup above), so a plugin
|
|
63
|
+
// only ever deals with an address to dial, never a coordinator of its own.
|
|
64
|
+
export type ConnectionConfig = (
|
|
65
|
+
| { type: "direct-tcp", party: "host", port: number }
|
|
66
|
+
// hostIp is what a coordinator (or whatever else resolved this) reported
|
|
67
|
+
// - the same value startGame would otherwise have had to go discover
|
|
68
|
+
// itself.
|
|
69
|
+
| { type: "direct-tcp", party: "client", port: number, hostIp: string }
|
|
70
|
+
| RoomOrInternalConnection
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export type GameProcessHandle = {
|
|
74
|
+
// Fires when the process this plugin is directly watching exits - not
|
|
75
|
+
// necessarily the same as "the game is over". Some games are started via
|
|
76
|
+
// an intermediary (a platform launcher, a wrapper script) that spawns the
|
|
77
|
+
// real game and exits 0 once it's handed off; for those, onExit only means
|
|
78
|
+
// the hand-off succeeded, not that play has ended - the plugin may have no
|
|
79
|
+
// handle to the actual game process at all. A plugin watching the real
|
|
80
|
+
// game process directly (e.g. spawning the engine binary itself, like
|
|
81
|
+
// ikemen-go does) can report both meaningfully; one that can't should say
|
|
82
|
+
// so in its publicInfo rather than let callers assume more than it can
|
|
83
|
+
// promise.
|
|
84
|
+
exited: false | { code: number },
|
|
85
|
+
onExit: (cb: (code: number | null) => void) => void,
|
|
86
|
+
onCrash: (cb: (error: Error) => void) => void,
|
|
87
|
+
// Best-effort - may not actually be able to stop the game if it's already
|
|
88
|
+
// handed off to a process this plugin no longer has a handle to.
|
|
89
|
+
stop: () => Promise<void>,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type StartGameArgs<T> = {
|
|
93
|
+
// Handed to the game coordinator on the relay room's success webhook -
|
|
94
|
+
// lets a plugin correlate itself back to that room if it ever needs to.
|
|
95
|
+
relayRoomId: string,
|
|
96
|
+
currentMachine: {
|
|
97
|
+
machineId: string,
|
|
98
|
+
publicKey: string,
|
|
99
|
+
// A temp file - written with restrictive/owner-only permissions before
|
|
100
|
+
// startGame is invoked, deleted once the returned GameProcessHandle
|
|
101
|
+
// reports exit/crash - containing this machine's private key, for
|
|
102
|
+
// plugins that need to sign messages to identify the user to a game
|
|
103
|
+
// coordinator. Most games (Ikemen included) have no identity concept in
|
|
104
|
+
// their own netcode and never touch this.
|
|
105
|
+
//
|
|
106
|
+
// Handled as a file the framework manages (see
|
|
107
|
+
// @roster-lock/plugin-runtime's GameLauncher) rather than handing plugins
|
|
108
|
+
// the raw key directly, so a plugin is safe by default even if it does
|
|
109
|
+
// something careless with it - e.g. forwarding it into the spawned
|
|
110
|
+
// process's argv/env would otherwise leak to other users via `ps`/`/proc`
|
|
111
|
+
// on a shared machine (see docs/v2/test-environments.md's Internet Cafe
|
|
112
|
+
// / Arcade targets). A plugin that does need to hand the game itself an
|
|
113
|
+
// identity can still pass this same file path through unchanged.
|
|
114
|
+
privateKeyFile: string,
|
|
115
|
+
},
|
|
116
|
+
allMachines: Array<RoomMachine>,
|
|
117
|
+
selectionResult: RosterLockV1SyncDLResult,
|
|
118
|
+
rosterConfig: RosterLockV1Config,
|
|
119
|
+
// Lets a plugin without direct filesystem access to the downloaded pieces
|
|
120
|
+
// (selectionResult.downloadResults[...].folder assumes it's running on the
|
|
121
|
+
// same machine as match-agent) pull file contents over HTTP instead.
|
|
122
|
+
matchAgent: { port: number, authCode: string },
|
|
123
|
+
// Room-shared settings every participant agreed to at room-creation time
|
|
124
|
+
// (e.g. team mode, round time). Validated against gameConfigSchema.
|
|
125
|
+
gameConfig: T,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type GameLauncherPlugin<T> = {
|
|
129
|
+
name: string,
|
|
130
|
+
publicInfo: {
|
|
131
|
+
title: string,
|
|
132
|
+
description: string,
|
|
133
|
+
},
|
|
134
|
+
// Which ConnectionConfig["type"] values this plugin actually knows how to
|
|
135
|
+
// consume in startGame - not just "would make sense for", since a mode
|
|
136
|
+
// like "room" typically depends on unbuilt bridge infrastructure. Only
|
|
137
|
+
// declare a mode once startGame genuinely handles it.
|
|
138
|
+
supportedConnectionModes: Array<ConnectionMode>,
|
|
139
|
+
// Which ConnectionConfig["room"]["version"] values this plugin supports.
|
|
140
|
+
// Only meaningful if "room" is in supportedConnectionModes.
|
|
141
|
+
supportedRoomVersions?: Array<string>,
|
|
142
|
+
// Identifies which RosterLockEngineConfig["pieceDefinitions"] shape this
|
|
143
|
+
// plugin was built against - a hash of that shape alone (not engine.name,
|
|
144
|
+
// engine.version, or engine.officialSelections, which are roster-author
|
|
145
|
+
// choices, not part of what the plugin structurally needs). Before a room
|
|
146
|
+
// can be created, this is compared against the same hash computed over the
|
|
147
|
+
// roster config's own engine.pieceDefinitions ("Engine Sha" in
|
|
148
|
+
// docs/v2/ikemen-go/general-plan.md) - a mismatch means the plugin's
|
|
149
|
+
// assumptions about piece layout (e.g. ikemen-go's defName path variable)
|
|
150
|
+
// don't hold for that roster.
|
|
151
|
+
engineSha: Sha256,
|
|
152
|
+
// JSON Schema for StartGameArgs["gameConfig"]. Set even when empty ({}).
|
|
153
|
+
gameConfigSchema: JSONSchemaType<T>,
|
|
154
|
+
// JSON Schema for this plugin's other per-machine settings (e.g. a
|
|
155
|
+
// preferred direct-tcp port). Excludes binaryLocation, which every Game
|
|
156
|
+
// Runner has by definition - see the functions below. Set even when empty.
|
|
157
|
+
localConfigSchema: AnySchema,
|
|
158
|
+
// Every platform/arch this plugin knows how to run on at all, independent
|
|
159
|
+
// of whether any particular binaryLocation currently has a binary for one
|
|
160
|
+
// of them - see docs/v2/binary-location.md. Lets a browsing/install UI
|
|
161
|
+
// warn "not supported on macOS" before the user has configured a
|
|
162
|
+
// binaryLocation to check against.
|
|
163
|
+
supportedPlatforms: Array<PlatformTarget>,
|
|
164
|
+
|
|
165
|
+
// binaryLocation is resolved locally (e.g. from match-agent's own config)
|
|
166
|
+
// and is what "disabled until configured" is about - every function below
|
|
167
|
+
// needs it pointed at something real to do anything useful. As of
|
|
168
|
+
// docs/v2/binary-location.md it's the root of a multi-platform bundle, not
|
|
169
|
+
// a path to one executable - each function below that needs a concrete
|
|
170
|
+
// binary resolves `target` against it itself; the layout convention for
|
|
171
|
+
// doing that is the plugin's own to define.
|
|
172
|
+
//
|
|
173
|
+
// `target` is required everywhere a concrete binary has to be resolved,
|
|
174
|
+
// rather than any function defaulting to "the current host" implicitly -
|
|
175
|
+
// see docs/v2/binary-location.md's "Platform targeting per function" for
|
|
176
|
+
// why (getSupportedVersion is the one exception, since it's a
|
|
177
|
+
// platform-agnostic upstream-metadata query).
|
|
178
|
+
getLocalVersion: (binaryLocation: string, target: PlatformTarget) => Promise<{ title: string, id: string }>,
|
|
179
|
+
// Knowing binaryLocation can help (e.g. picking a release channel), but
|
|
180
|
+
// this is expected to mostly be a network call, not a local read. No
|
|
181
|
+
// target - the answer ("what's the latest upstream version") doesn't vary
|
|
182
|
+
// by platform.
|
|
183
|
+
getSupportedVersion: (binaryLocation: string) => Promise<{ title: string, id: string }>,
|
|
184
|
+
// Optional - a plugin without one just means no in-app update; the user
|
|
185
|
+
// downloads a new version and re-points binaryLocation at it themselves.
|
|
186
|
+
// `target` is a floor, not a ceiling: the call guarantees at least that
|
|
187
|
+
// platform/arch gets updated, but a plugin may update other slots too as
|
|
188
|
+
// a side effect (e.g. one upstream artifact that already bundles every
|
|
189
|
+
// platform). Callers never loop over supportedPlatforms to force full
|
|
190
|
+
// coverage themselves - whether the bundle ends up complete for every
|
|
191
|
+
// platform after one call is entirely the plugin's own choice.
|
|
192
|
+
updateBinary?: (binaryLocation: string, target: PlatformTarget) => Promise<void>,
|
|
193
|
+
// Checks whether binaryLocation actually has a usable binary for `target`
|
|
194
|
+
// - "does the binary this host would run actually exist at the resolved
|
|
195
|
+
// path" (and on POSIX, is it executable) - so a caller can surface a
|
|
196
|
+
// plugin-authored reason (e.g. "no linux-x64 build in this folder")
|
|
197
|
+
// instead of a raw ENOENT/spawn error from startGame. Takes the same
|
|
198
|
+
// target startGame will actually receive, since that's the call it's
|
|
199
|
+
// answering for.
|
|
200
|
+
validateBinaryLocation: (
|
|
201
|
+
binaryLocation: string, target: PlatformTarget
|
|
202
|
+
) => Promise<{ valid: true } | { valid: false, message: string }>,
|
|
203
|
+
|
|
204
|
+
startGame: (
|
|
205
|
+
binaryLocation: string, target: PlatformTarget, connectionConfig: ConnectionConfig, args: StartGameArgs<T>
|
|
206
|
+
) => Promise<GameProcessHandle>,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// GameLauncherPlugin<T> is for *implementing* a plugin, where T (the plugin's
|
|
210
|
+
// own gameConfig shape) is known and worth enforcing at both
|
|
211
|
+
// gameConfigSchema and startGame. A caller that's just routing to *some*
|
|
212
|
+
// installed plugin by name (match-agent, plugin-runtime's GameLauncher) never
|
|
213
|
+
// knows T - it received the plugin dynamically, and gameConfig is exactly
|
|
214
|
+
// what gameConfigSchema exists to describe/validate at runtime instead.
|
|
215
|
+
//
|
|
216
|
+
// GameLauncherPlugin<any> alone doesn't work for that: T=any is right for the
|
|
217
|
+
// startGame/StartGameArgs positions (the usual erasure for storing/calling
|
|
218
|
+
// a value whose real generic argument varies per plugin instance - see
|
|
219
|
+
// PluginTypeMap["untrusted-script"]'s UntrustedScript<any> for the same
|
|
220
|
+
// pattern elsewhere), but it also leaks into gameConfigSchema, turning it
|
|
221
|
+
// into JSONSchemaType<any> - not the same thing as "some JSON Schema", and
|
|
222
|
+
// not what a caller that never touches T actually wants. localConfigSchema
|
|
223
|
+
// (AnySchema, no generic) is already the right shape for "opaque schema a
|
|
224
|
+
// caller passes through" - gameConfigSchema needs that same shape once T
|
|
225
|
+
// isn't known, hence the override below rather than just GameLauncherPlugin<any>.
|
|
226
|
+
export type AnyGameLauncherPlugin = Omit<GameLauncherPlugin<any>, "gameConfigSchema"> & {
|
|
227
|
+
gameConfigSchema: AnySchema,
|
|
228
|
+
};
|
package/src/v1/runtime/index.ts
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { RosterLockV1Config } from "../../lock";
|
|
2
|
+
import { UserSelection } from "../../request";
|
|
3
|
+
|
|
4
|
+
// The wire protocol between a matchmaker UI (loaded in an <iframe>, e.g.
|
|
5
|
+
// examples/services/match-makers/titled-room/client) and its host shell (e.g.
|
|
6
|
+
// core/match-agent/client), carried over a postMessage-backed MessageBridge
|
|
7
|
+
// (@roster-lock/utils). All six calls are iframe -> host; the host never
|
|
8
|
+
// initiates. MessageBridge itself is untyped (see its own docs) - these
|
|
9
|
+
// types exist purely so both sides' thin bridge wrapper modules agree on
|
|
10
|
+
// shape, not because MessageBridge enforces it.
|
|
11
|
+
//
|
|
12
|
+
// Deliberately excludes any raw-signing/private-key primitive - titled-room
|
|
13
|
+
// (the first matchmaker built against this protocol) uses JWT/bearer auth,
|
|
14
|
+
// not signatures, so nothing needs one yet. A future signature-auth
|
|
15
|
+
// matchmaker would need a new request added here, not a workaround.
|
|
16
|
+
|
|
17
|
+
// installGameLauncherPlugin(pluginName) - host installs the plugin package and
|
|
18
|
+
// opens a lightbox that also lets the user finish local config
|
|
19
|
+
// (binaryLocation) before resolving.
|
|
20
|
+
export type InstallGameLauncherPluginRequest = { pluginName: string };
|
|
21
|
+
export type InstallGameLauncherPluginResponse = {};
|
|
22
|
+
|
|
23
|
+
// getInstalledGameLauncherPlugins() - what game-launcher plugins this machine
|
|
24
|
+
// already has installed, so a matchmaker's room UI can tell a user "you
|
|
25
|
+
// don't have this one yet" without ever touching match-agent itself.
|
|
26
|
+
// gameConfigSchema is included so a matchmaker can render the room-shared
|
|
27
|
+
// settings a game launcher needs (e.g. ikemen-go's teamMode/roundTime/rounds)
|
|
28
|
+
// as a real form (e.g. via rjsf) instead of asking the user for raw JSON -
|
|
29
|
+
// it's already public (GameLauncherPlugin.gameConfigSchema, exposed by match-
|
|
30
|
+
// agent's own /v1/game-launcher/available route), just not previously carried
|
|
31
|
+
// across this bridge. Left as `unknown` here rather than ajv's AnySchema,
|
|
32
|
+
// matching how match-agent-client's own GameLauncherSettingsForm already treats
|
|
33
|
+
// localConfigSchema at this same kind of boundary - a consumer narrows it
|
|
34
|
+
// (e.g. an RJSFSchema type guard) at render time instead.
|
|
35
|
+
export type GetInstalledGameLauncherPluginsResponse = Array<{ id: string, version: string, gameConfigSchema: unknown }>;
|
|
36
|
+
|
|
37
|
+
// getIdentity() - never includes the private key, only what's safe for a
|
|
38
|
+
// matchmaker to see. playerCount reflects the host's own local player-slot
|
|
39
|
+
// count (Join Settings), not anything the guest can set.
|
|
40
|
+
export type GetIdentityResponse = { publicKey: string, machineId: string, playerCount: number };
|
|
41
|
+
|
|
42
|
+
// requestSelection(rosterLockConfig, numPlayers) - host opens the Selection
|
|
43
|
+
// lightbox for exactly `numPlayers` local player slots and resolves with the
|
|
44
|
+
// built selection once confirmed. Unlike the other calls, the selection
|
|
45
|
+
// *content* does cross back to the guest here - it isn't secret, only the
|
|
46
|
+
// private key is.
|
|
47
|
+
export type RequestSelectionRequest = { rosterLockConfig: RosterLockV1Config, numPlayers: number };
|
|
48
|
+
export type RequestSelectionResponse = Record<number, UserSelection>;
|
|
49
|
+
|
|
50
|
+
// updateGameLauncherSettings(pluginName, gameConfig) - room-shared settings a
|
|
51
|
+
// game launcher needs before it can start (e.g. ikemen-go's teamMode). The
|
|
52
|
+
// host holds onto this and folds it into the eventual
|
|
53
|
+
// /v1/game-launcher/:pluginName/start call it makes later, since the host
|
|
54
|
+
// (not the guest) is the one that ends up calling that route.
|
|
55
|
+
export type UpdateGameLauncherSettingsRequest = { pluginName: string, gameConfig: unknown };
|
|
56
|
+
export type UpdateGameLauncherSettingsResponse = {};
|
|
57
|
+
|
|
58
|
+
// initiateRelay(...) - fire-and-forget event, not a request: the guest is
|
|
59
|
+
// about to be torn down (host navigates away from the iframe entirely), so
|
|
60
|
+
// there's nothing useful to await. Host merges this with whatever it already
|
|
61
|
+
// has from requestSelection/updateGameLauncherSettings and navigates to
|
|
62
|
+
// wherever it runs its own download/start-game flow.
|
|
63
|
+
export type InitiateRelayEvent = {
|
|
64
|
+
relay: { url: string, roomId: string },
|
|
65
|
+
rosterConfig: RosterLockV1Config,
|
|
66
|
+
gameLauncherPlugin: string,
|
|
67
|
+
isHost: boolean,
|
|
68
|
+
// A direct-tcp game launcher's rendezvous coordinator (see
|
|
69
|
+
// plugins/game-launcher/shared/direct-ip-coordinator and
|
|
70
|
+
// docs/v2/ikemen-go/game-coordinator.md) - null for game launchers that
|
|
71
|
+
// don't use one (a matchmaker resolves this the same way it resolves
|
|
72
|
+
// `relay`, e.g. titled-room's /room/start response).
|
|
73
|
+
coordinator: { host: string, port: number } | null,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Bridge message/event path names, so both sides reference the same literal
|
|
77
|
+
// strings instead of hand-typing them.
|
|
78
|
+
export const MATCHMAKER_BRIDGE_PATHS = {
|
|
79
|
+
installGameLauncherPlugin: "installGameLauncherPlugin",
|
|
80
|
+
getInstalledGameLauncherPlugins: "getInstalledGameLauncherPlugins",
|
|
81
|
+
getIdentity: "getIdentity",
|
|
82
|
+
requestSelection: "requestSelection",
|
|
83
|
+
updateGameLauncherSettings: "updateGameLauncherSettings",
|
|
84
|
+
initiateRelay: "initiateRelay",
|
|
85
|
+
ready: "ready",
|
|
86
|
+
} as const;
|