@roster-lock/match-agent 0.1.0 → 0.2.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.
Files changed (107) hide show
  1. package/client/dist/assets/index-DeYshswm.js +125 -0
  2. package/client/dist/assets/index-gxAuIfGQ.css +1 -0
  3. package/client/dist/index.html +13 -0
  4. package/client/index.html +12 -0
  5. package/client/node_modules/.bin/terser +17 -0
  6. package/client/node_modules/.bin/tsc +17 -0
  7. package/client/node_modules/.bin/vite +17 -0
  8. package/client/node_modules/.vite/deps/@rjsf_core.js +6609 -0
  9. package/client/node_modules/.vite/deps/@rjsf_core.js.map +7 -0
  10. package/client/node_modules/.vite/deps/@rjsf_validator-ajv8.js +13 -0
  11. package/client/node_modules/.vite/deps/@rjsf_validator-ajv8.js.map +7 -0
  12. package/client/node_modules/.vite/deps/@roster-lock_ts-client.js +419 -0
  13. package/client/node_modules/.vite/deps/@roster-lock_ts-client.js.map +7 -0
  14. package/client/node_modules/.vite/deps/@roster-lock_types.js +5 -0
  15. package/client/node_modules/.vite/deps/@roster-lock_types.js.map +7 -0
  16. package/client/node_modules/.vite/deps/@roster-lock_utils.js +5 -0
  17. package/client/node_modules/.vite/deps/@roster-lock_utils.js.map +7 -0
  18. package/client/node_modules/.vite/deps/_metadata.json +97 -0
  19. package/client/node_modules/.vite/deps/chunk-4D2L7OBZ.js +16039 -0
  20. package/client/node_modules/.vite/deps/chunk-4D2L7OBZ.js.map +7 -0
  21. package/client/node_modules/.vite/deps/chunk-5KLROPUR.js +292 -0
  22. package/client/node_modules/.vite/deps/chunk-5KLROPUR.js.map +7 -0
  23. package/client/node_modules/.vite/deps/chunk-77TXW5ZT.js +280 -0
  24. package/client/node_modules/.vite/deps/chunk-77TXW5ZT.js.map +7 -0
  25. package/client/node_modules/.vite/deps/chunk-I55GDNGV.js +1004 -0
  26. package/client/node_modules/.vite/deps/chunk-I55GDNGV.js.map +7 -0
  27. package/client/node_modules/.vite/deps/chunk-IIDO6PKU.js +2337 -0
  28. package/client/node_modules/.vite/deps/chunk-IIDO6PKU.js.map +7 -0
  29. package/client/node_modules/.vite/deps/chunk-NE7PXY4V.js +466 -0
  30. package/client/node_modules/.vite/deps/chunk-NE7PXY4V.js.map +7 -0
  31. package/client/node_modules/.vite/deps/chunk-WMTNK64S.js +52 -0
  32. package/client/node_modules/.vite/deps/chunk-WMTNK64S.js.map +7 -0
  33. package/client/node_modules/.vite/deps/package.json +3 -0
  34. package/client/node_modules/.vite/deps/react-dom.js +6 -0
  35. package/client/node_modules/.vite/deps/react-dom.js.map +7 -0
  36. package/client/node_modules/.vite/deps/react-dom_client.js +20217 -0
  37. package/client/node_modules/.vite/deps/react-dom_client.js.map +7 -0
  38. package/client/node_modules/.vite/deps/react-router-dom.js +14702 -0
  39. package/client/node_modules/.vite/deps/react-router-dom.js.map +7 -0
  40. package/client/node_modules/.vite/deps/react.js +5 -0
  41. package/client/node_modules/.vite/deps/react.js.map +7 -0
  42. package/client/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
  43. package/client/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  44. package/client/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
  45. package/client/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  46. package/client/node_modules/.vite/deps_temp_ab51ebe9/package.json +3 -0
  47. package/client/package.json +30 -0
  48. package/client/src/App.tsx +56 -0
  49. package/client/src/NavBar.tsx +13 -0
  50. package/client/src/api/matchAgent.ts +159 -0
  51. package/client/src/bridge/hostBridge.ts +149 -0
  52. package/client/src/components/GameLauncherSettingsForm.tsx +140 -0
  53. package/client/src/components/InstallGameLauncherLightbox.tsx +31 -0
  54. package/client/src/components/Lightbox.tsx +15 -0
  55. package/client/src/components/RequireConnection.tsx +9 -0
  56. package/client/src/components/Selection/PieceCard.tsx +36 -0
  57. package/client/src/components/Selection/PieceTypeSection.tsx +112 -0
  58. package/client/src/components/Selection/PieceTypeTabs.tsx +31 -0
  59. package/client/src/components/Selection/PlayerSelectionPanel.tsx +40 -0
  60. package/client/src/components/Selection/index.tsx +147 -0
  61. package/client/src/components/Selection/selectionPlan.ts +75 -0
  62. package/client/src/context/DownloadSessionContext.tsx +48 -0
  63. package/client/src/context/IdentityContext.tsx +53 -0
  64. package/client/src/context/JoinSettingsContext.tsx +83 -0
  65. package/client/src/context/MatchAgentContext.tsx +85 -0
  66. package/client/src/hooks/useCursorInput.ts +55 -0
  67. package/client/src/hooks/useGamepads.ts +31 -0
  68. package/client/src/main.tsx +10 -0
  69. package/client/src/pages/Connect/index.tsx +46 -0
  70. package/client/src/pages/Download/index.tsx +175 -0
  71. package/client/src/pages/Game/index.tsx +65 -0
  72. package/client/src/pages/GameLauncher/Detail.tsx +12 -0
  73. package/client/src/pages/GameLauncher/index.tsx +32 -0
  74. package/client/src/pages/JoinSettings/index.tsx +58 -0
  75. package/client/src/pages/MatchMaking/index.tsx +190 -0
  76. package/client/src/styles/global.css +355 -0
  77. package/client/tsconfig.json +17 -0
  78. package/client/tsconfig.tsbuildinfo +1 -0
  79. package/client/vite.config.ts +33 -0
  80. package/dist/config/manage.js.map +1 -1
  81. package/dist/handle-room/version-1/game-launcher.js +181 -0
  82. package/dist/handle-room/version-1/game-launcher.js.map +1 -0
  83. package/dist/handle-room/version-1/index.js +15 -2
  84. package/dist/handle-room/version-1/index.js.map +1 -1
  85. package/dist/handle-room/version-1/piece-sort.js +1 -0
  86. package/dist/handle-room/version-1/piece-sort.js.map +1 -1
  87. package/dist/index.js +7 -4
  88. package/dist/index.js.map +1 -1
  89. package/dist/serve-client.js +88 -0
  90. package/dist/serve-client.js.map +1 -0
  91. package/dist/server.js +1 -1
  92. package/dist/server.js.map +1 -1
  93. package/package.json +12 -6
  94. package/src/config/manage.ts +5 -1
  95. package/src/handle-room/version-1/game-launcher.ts +263 -0
  96. package/src/handle-room/version-1/globals/types.ts +24 -0
  97. package/src/handle-room/version-1/index.ts +23 -3
  98. package/src/handle-room/version-1/piece-sort.ts +1 -1
  99. package/src/index.ts +25 -5
  100. package/src/serve-client.ts +100 -0
  101. package/src/server.ts +1 -1
  102. package/tests/integration/game-launcher-list.test.ts +77 -0
  103. package/tests/integration/game-launcher-settings.test.ts +138 -0
  104. package/tests/integration/game-launcher-start.test.ts +81 -0
  105. package/tests/integration/helpers/fakePluginRuntime.ts +26 -1
  106. package/tests/integration/helpers/server.ts +8 -2
  107. package/src/index.old.ts +0 -71
@@ -0,0 +1,138 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { startTestServer, TestServer, errorBody } from "./helpers/server";
5
+ import { makeTempFolder, cleanupFolder } from "./helpers/piece";
6
+ import { createFixturePluginDir } from "./helpers/plugin-dir";
7
+
8
+ const PLUGIN_NAME = "@roster-lock/game-launcher-ikemen-go";
9
+
10
+ // Mirrors plugins/game-launcher/ikemen-go/src/binaryLocation.ts's own mapping -
11
+ // duplicated here rather than imported since this test is about match-agent
12
+ // resolving binaryLocation before the plugin ever sees it, not about the
13
+ // plugin's own resolution logic.
14
+ const IKEMEN_EXECUTABLE_NAME = (
15
+ process.platform === "win32" ? "Ikemen_GO.exe" : process.platform === "darwin" ? "Ikemen_GO_MacOS" : "Ikemen_GO_Linux"
16
+ );
17
+
18
+ describe("game-launcher local settings/version/update routes", () => {
19
+ const cleanups: Array<() => Promise<void> | void> = [];
20
+ afterEach(async () => {
21
+ while (cleanups.length) await cleanups.pop()!();
22
+ });
23
+
24
+ async function setup(): Promise<{ server: TestServer, pluginDir: string }> {
25
+ const fixture = await createFixturePluginDir([PLUGIN_NAME]);
26
+ cleanups.push(fixture.cleanup);
27
+ const folder = await makeTempFolder();
28
+ cleanups.push(() => cleanupFolder(folder));
29
+ const server = await startTestServer(folder, undefined, fixture.pluginDir);
30
+ cleanups.push(() => server.close());
31
+ return { server, pluginDir: fixture.pluginDir };
32
+ }
33
+
34
+ function auth(server: TestServer): HeadersInit {
35
+ return { Authorization: `Bearer ${server.authCode}` };
36
+ }
37
+
38
+ it("GET settings returns {} before anything has been saved", async () => {
39
+ const { server } = await setup();
40
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, { headers: auth(server) });
41
+ expect(res.status).toBe(200);
42
+ expect(await res.json()).toEqual({});
43
+ });
44
+
45
+ it("PUT settings round-trips through GET", async () => {
46
+ const { server } = await setup();
47
+ const putRes = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, {
48
+ method: "PUT",
49
+ headers: { ...auth(server), "Content-Type": "application/json" },
50
+ body: JSON.stringify({ binaryLocation: "/opt/ikemen/Ikemen_GO" }),
51
+ });
52
+ expect(putRes.status).toBe(200);
53
+
54
+ const getRes = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, { headers: auth(server) });
55
+ expect(await getRes.json()).toEqual({ binaryLocation: "/opt/ikemen/Ikemen_GO" });
56
+ });
57
+
58
+ it("PUT settings rejects unknown fields", async () => {
59
+ const { server } = await setup();
60
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, {
61
+ method: "PUT",
62
+ headers: { ...auth(server), "Content-Type": "application/json" },
63
+ body: JSON.stringify({ binaryLocation: "/x", extra: true }),
64
+ });
65
+ expect(res.status).toBe(400);
66
+ });
67
+
68
+ it("GET version 400s when no binaryLocation has been configured", async () => {
69
+ const { server } = await setup();
70
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/version`, { headers: auth(server) });
71
+ expect(res.status).toBe(400);
72
+ expect((await errorBody(res)).error).toMatch(/binaryLocation/);
73
+ });
74
+
75
+ it("resolves a relative binaryLocation against pluginDir (docs/v2/binary-location.md)", async () => {
76
+ const { server, pluginDir } = await setup();
77
+
78
+ // "engine" here plays the role of wherever a USB-hosted setup would keep
79
+ // its binaries relative to --plugin-folder - pluginDir moves with the
80
+ // USB, so a relative binaryLocation should too, rather than needing to
81
+ // be an absolute path that goes stale once the USB remounts elsewhere.
82
+ const engineDir = join(pluginDir, "engine");
83
+ await mkdir(engineDir, { recursive: true });
84
+ await writeFile(join(engineDir, IKEMEN_EXECUTABLE_NAME), "not a real binary, just needs to exist", {
85
+ mode: 0o755,
86
+ });
87
+
88
+ await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, {
89
+ method: "PUT",
90
+ headers: { ...auth(server), "Content-Type": "application/json" },
91
+ body: JSON.stringify({ binaryLocation: "engine" }),
92
+ });
93
+
94
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/validate`, {
95
+ headers: auth(server),
96
+ });
97
+ expect(res.status).toBe(200);
98
+ // If "engine" had been treated literally (e.g. resolved against the
99
+ // process's own cwd instead of pluginDir) this would come back invalid,
100
+ // since nothing exists at a literal relative "engine" from wherever
101
+ // vitest happens to run.
102
+ expect(await res.json()).toEqual({ valid: true });
103
+ });
104
+
105
+ it("POST update 400s when the plugin has no updateBinary (ikemen-go doesn't declare one)", async () => {
106
+ const { server } = await setup();
107
+ await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, {
108
+ method: "PUT",
109
+ headers: { ...auth(server), "Content-Type": "application/json" },
110
+ body: JSON.stringify({ binaryLocation: "/opt/ikemen/Ikemen_GO" }),
111
+ });
112
+
113
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/update`, {
114
+ method: "POST",
115
+ headers: auth(server),
116
+ });
117
+ expect(res.status).toBe(400);
118
+ expect((await errorBody(res)).error).toMatch(/in-app updates/);
119
+ });
120
+
121
+ it("GET process/:handleId 404s for an unknown handle", async () => {
122
+ const { server } = await setup();
123
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/process/not-a-real-handle`, {
124
+ headers: auth(server),
125
+ });
126
+ expect(res.status).toBe(404);
127
+ });
128
+
129
+ it("POST install 400s with the underlying error for a package that can't be installed", async () => {
130
+ const { server } = await setup();
131
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent("not-a-real-package")}/install`, {
132
+ method: "POST",
133
+ headers: auth(server),
134
+ });
135
+ expect(res.status).toBe(400);
136
+ expect((await errorBody(res)).error).toBeTruthy();
137
+ });
138
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { startTestServer, TestServer, errorBody } from "./helpers/server";
3
+ import { makeTempFolder, cleanupFolder } from "./helpers/piece";
4
+ import { createFixturePluginDir } from "./helpers/plugin-dir";
5
+
6
+ const PLUGIN_NAME = "@roster-lock/game-launcher-ikemen-go";
7
+
8
+ describe("POST /v1/game-launcher/:pluginName/start", () => {
9
+ const cleanups: Array<() => Promise<void> | void> = [];
10
+ afterEach(async () => {
11
+ while (cleanups.length) await cleanups.pop()!();
12
+ });
13
+
14
+ async function setup(): Promise<TestServer> {
15
+ const fixture = await createFixturePluginDir([PLUGIN_NAME]);
16
+ cleanups.push(fixture.cleanup);
17
+ const folder = await makeTempFolder();
18
+ cleanups.push(() => cleanupFolder(folder));
19
+ const server = await startTestServer(folder, undefined, fixture.pluginDir);
20
+ cleanups.push(() => server.close());
21
+ return server;
22
+ }
23
+
24
+ function auth(server: TestServer): HeadersInit {
25
+ return { Authorization: `Bearer ${server.authCode}` };
26
+ }
27
+
28
+ async function configureBinaryLocation(server: TestServer) {
29
+ await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/settings`, {
30
+ method: "PUT",
31
+ headers: { ...auth(server), "Content-Type": "application/json" },
32
+ body: JSON.stringify({ binaryLocation: "/opt/ikemen/Ikemen_GO" }),
33
+ });
34
+ }
35
+
36
+ it("400s when the plugin has no binaryLocation configured yet", async () => {
37
+ const server = await setup();
38
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/start`, {
39
+ method: "POST",
40
+ headers: { ...auth(server), "Content-Type": "application/json" },
41
+ });
42
+ expect(res.status).toBe(400);
43
+ expect((await errorBody(res)).error).toMatch(/binaryLocation/);
44
+ });
45
+
46
+ it("400s on a connectionConfig the type doesn't allow (e.g. an unknown type)", async () => {
47
+ const server = await setup();
48
+ await configureBinaryLocation(server);
49
+
50
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/start`, {
51
+ method: "POST",
52
+ headers: { ...auth(server), "Content-Type": "application/json" },
53
+ body: JSON.stringify({
54
+ connectionConfig: { type: "carrier-pigeon" },
55
+ currentMachine: { machineId: "m1", publicKey: "pk", privateKey: "sk" },
56
+ allMachines: [],
57
+ selectionResult: {},
58
+ rosterConfig: {},
59
+ gameConfig: {},
60
+ relayRoomId: "room-1",
61
+ }),
62
+ });
63
+ expect(res.status).toBe(400);
64
+ expect((await errorBody(res)).error).toBe("Bad Form");
65
+ });
66
+
67
+ it("400s when required top-level fields are missing", async () => {
68
+ const server = await setup();
69
+ await configureBinaryLocation(server);
70
+
71
+ const res = await fetch(`${server.httpUrl}/v1/game-launcher/${encodeURIComponent(PLUGIN_NAME)}/start`, {
72
+ method: "POST",
73
+ headers: { ...auth(server), "Content-Type": "application/json" },
74
+ body: JSON.stringify({
75
+ connectionConfig: { type: "direct-tcp", party: "host", port: 7000 },
76
+ }),
77
+ });
78
+ expect(res.status).toBe(400);
79
+ expect((await errorBody(res)).error).toBe("Bad Form");
80
+ });
81
+ });
@@ -1,8 +1,9 @@
1
- import { IPluginManager, IPieceSort } from "@roster-lock/plugin-runtime";
1
+ import { IPluginManager, IPieceSort, IGameLauncher } from "@roster-lock/plugin-runtime";
2
2
 
3
3
  export class FakePluginManager implements IPluginManager {
4
4
  readonly pluginDir = "hello-world";
5
5
  pieceSort = new FakePieceSort()
6
+ gameLauncher = new FakeGameLauncher()
6
7
  uninstallPlugin(): ReturnType<IPluginManager["uninstallPlugin"]>{
7
8
  throw new Error("Not implemented");
8
9
  }
@@ -43,4 +44,28 @@ class FakePieceSort implements IPieceSort {
43
44
  handleGameComplete(): ReturnType<IPieceSort["handleGameComplete"]>{
44
45
  throw new Error("Not Implmented")
45
46
  }
47
+ }
48
+
49
+ class FakeGameLauncher implements IGameLauncher {
50
+ listAvailable(): ReturnType<IGameLauncher["listAvailable"]>{
51
+ throw new Error("Not Implemented");
52
+ }
53
+ getLocalSettings(): ReturnType<IGameLauncher["getLocalSettings"]>{
54
+ throw new Error("Not Implemented");
55
+ }
56
+ setLocalSettings(): ReturnType<IGameLauncher["setLocalSettings"]>{
57
+ throw new Error("Not Implemented");
58
+ }
59
+ getLocalVersion(): ReturnType<IGameLauncher["getLocalVersion"]>{
60
+ throw new Error("Not Implemented");
61
+ }
62
+ getSupportedVersion(): ReturnType<IGameLauncher["getSupportedVersion"]>{
63
+ throw new Error("Not Implemented");
64
+ }
65
+ updateBinary(): ReturnType<IGameLauncher["updateBinary"]>{
66
+ throw new Error("Not Implemented");
67
+ }
68
+ startGame(): ReturnType<IGameLauncher["startGame"]>{
69
+ throw new Error("Not Implemented");
70
+ }
46
71
  }
@@ -22,14 +22,20 @@ export async function startTestServer(folder: string, authCode: string = randomU
22
22
 
23
23
  const pluginRuntime = await PluginManager.create(pluginDir ?? folder);
24
24
  const fileDB = getSQLite3FolderDB(folder, pluginRuntime);
25
- const { httpRouter: v1HttpRouter, wsRouter: v1WsRouter } = createV1Routers(fileDB, pluginRuntime);
25
+ // port isn't known until the ephemeral listen() below resolves - getPort()
26
+ // is only ever called from a request handler, by which point `port` has
27
+ // already been assigned.
28
+ let port = 0;
29
+ const { httpRouter: v1HttpRouter, wsRouter: v1WsRouter } = createV1Routers(
30
+ fileDB, pluginRuntime, { authCode, getPort: () => port }
31
+ );
26
32
  server.httpRouter.use("/v1", authMiddleware(authCode), v1HttpRouter);
27
33
  server.wsRouter.use("/v1", authMiddleware(authCode), v1WsRouter);
28
34
  server.httpRouter.use("/editor/v1", authMiddleware(authCode), createEditorV1Router(pluginRuntime, fileDB));
29
35
 
30
36
  const nodeServer = server.listen(0);
31
37
  await once(nodeServer, "listening");
32
- const { port } = nodeServer.address() as AddressInfo;
38
+ ({ port } = nodeServer.address() as AddressInfo);
33
39
 
34
40
  return {
35
41
  authCode,
package/src/index.old.ts DELETED
@@ -1,71 +0,0 @@
1
-
2
- import {
3
- MatchLockRestrictionConfig,
4
- MatchLockSelectionConfig,
5
- GamePiece
6
- } from "@match-lock/shared";
7
- import { PublicKeyMap, validateSignedSelection } from "./validateSelection";
8
-
9
- import { handleConnection } from "./handleConnection";
10
-
11
-
12
- export function prepareGame(){
13
-
14
- return new MatchLockPrepare();
15
- }
16
-
17
- class MatchLockPrepare extends Promise<boolean> {
18
- constructor(
19
- public pieceFolder: string,
20
- public roomClient: RoomClient,
21
- public restrictionConfig: MatchLockRestrictionConfig,
22
- public userSelection: MatchLockSelectionConfig
23
- ) {
24
- super((res, rej)=>{
25
- Promise.resolve().then(async ()=>{
26
- const { publicKeys } = await roomClient.getUsers();
27
-
28
- }).then(()=>{
29
- res(true);
30
- }, rej)
31
- });
32
- }
33
- }
34
-
35
- import { ISimpleEventEmitter } from "@match-lock/shared";
36
-
37
- interface RoomClient {
38
- getUsers(): Promise<{ publicKeys: PublicKeyMap }>
39
- sendSelection(selection: MatchLockSelectionConfig): Promise<boolean>
40
- recieveSelection: ISimpleEventEmitter<[MatchLockSelectionConfig]>
41
- onDestroy: ISimpleEventEmitter<[MatchLockSelectionConfig]>
42
- }
43
-
44
- interface ClientConnection{
45
- handlePrivateKey(): Promise<string>
46
- handleSelection(): Promise<MatchLockSelectionConfig>
47
- onPiece: ISimpleEventEmitter<[GamePiece]>
48
- sendPieceProgress(pieceId: string, part: "logic" | "media", progress: number): Promise<void>
49
- onFinished(): Promise<void>
50
-
51
- onClose: ISimpleEventEmitter<[reason?: string]>
52
-
53
- connect(): void
54
- }
55
-
56
-
57
- interface DownloadProgress {
58
- onProgress: ISimpleEventEmitter<[number]>
59
- onFinished: ISimpleEventEmitter<[]>
60
- }
61
-
62
- function downloadPiece(restriction: MatchLockRestrictionConfig, peice: GamePiece): DownloadProgress {
63
-
64
-
65
-
66
- }
67
-
68
- function resolveDownloadable(url: string){
69
-
70
-
71
- }