@roster-lock/match-agent 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.
Files changed (174) hide show
  1. package/LICENSE.md +11 -0
  2. package/dist/authentication.js +53 -0
  3. package/dist/authentication.js.map +1 -0
  4. package/dist/config/constants.js +12 -0
  5. package/dist/config/constants.js.map +1 -0
  6. package/dist/config/findFile.js +32 -0
  7. package/dist/config/findFile.js.map +1 -0
  8. package/dist/config/index.js +29 -0
  9. package/dist/config/index.js.map +1 -0
  10. package/dist/config/manage.js +41 -0
  11. package/dist/config/manage.js.map +1 -0
  12. package/dist/cors.js +20 -0
  13. package/dist/cors.js.map +1 -0
  14. package/dist/editor-support/index.js +247 -0
  15. package/dist/editor-support/index.js.map +1 -0
  16. package/dist/handle-room/version-1/file-routes.js +53 -0
  17. package/dist/handle-room/version-1/file-routes.js.map +1 -0
  18. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/MultiAbort.js +76 -0
  19. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/MultiAbort.js.map +1 -0
  20. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/ensurePieceExists.js +218 -0
  21. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/ensurePieceExists.js.map +1 -0
  22. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/getVersions.js +18 -0
  23. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/getVersions.js.map +1 -0
  24. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/index.js +16 -0
  25. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/index.js.map +1 -0
  26. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/schema.js +370 -0
  27. package/dist/handle-room/version-1/globals/FolderDB/SQLFolderDB/schema.js.map +1 -0
  28. package/dist/handle-room/version-1/globals/FolderDB/index.js +18 -0
  29. package/dist/handle-room/version-1/globals/FolderDB/index.js.map +1 -0
  30. package/dist/handle-room/version-1/globals/FolderDB/types.js +3 -0
  31. package/dist/handle-room/version-1/globals/FolderDB/types.js.map +1 -0
  32. package/dist/handle-room/version-1/globals/Room/WebsocketRoom.js +95 -0
  33. package/dist/handle-room/version-1/globals/Room/WebsocketRoom.js.map +1 -0
  34. package/dist/handle-room/version-1/globals/Room/index.js +18 -0
  35. package/dist/handle-room/version-1/globals/Room/index.js.map +1 -0
  36. package/dist/handle-room/version-1/globals/Room/room.js +3 -0
  37. package/dist/handle-room/version-1/globals/Room/room.js.map +1 -0
  38. package/dist/handle-room/version-1/globals/index.js +20 -0
  39. package/dist/handle-room/version-1/globals/index.js.map +1 -0
  40. package/dist/handle-room/version-1/globals/types.js +3 -0
  41. package/dist/handle-room/version-1/globals/types.js.map +1 -0
  42. package/dist/handle-room/version-1/handleDownloads/DownloadTracker.js +83 -0
  43. package/dist/handle-room/version-1/handleDownloads/DownloadTracker.js.map +1 -0
  44. package/dist/handle-room/version-1/handleDownloads/index.js +35 -0
  45. package/dist/handle-room/version-1/handleDownloads/index.js.map +1 -0
  46. package/dist/handle-room/version-1/handleDownloads/types.js +3 -0
  47. package/dist/handle-room/version-1/handleDownloads/types.js.map +1 -0
  48. package/dist/handle-room/version-1/handleRoomSelections/encryption.js +21 -0
  49. package/dist/handle-room/version-1/handleRoomSelections/encryption.js.map +1 -0
  50. package/dist/handle-room/version-1/handleRoomSelections/random.js +12 -0
  51. package/dist/handle-room/version-1/handleRoomSelections/random.js.map +1 -0
  52. package/dist/handle-room/version-1/index.js +28 -0
  53. package/dist/handle-room/version-1/index.js.map +1 -0
  54. package/dist/handle-room/version-1/piece-sort.js +71 -0
  55. package/dist/handle-room/version-1/piece-sort.js.map +1 -0
  56. package/dist/handle-room/version-1/room-handler-bridge/exchange-and-download-selections.js +89 -0
  57. package/dist/handle-room/version-1/room-handler-bridge/exchange-and-download-selections.js.map +1 -0
  58. package/dist/handle-room/version-1/room-handler-bridge/http-handler.js +19 -0
  59. package/dist/handle-room/version-1/room-handler-bridge/http-handler.js.map +1 -0
  60. package/dist/handle-room/version-1/room-handler-bridge/steps.js +170 -0
  61. package/dist/handle-room/version-1/room-handler-bridge/steps.js.map +1 -0
  62. package/dist/handle-room/version-1/room-handler-bridge/ws-handler.js +28 -0
  63. package/dist/handle-room/version-1/room-handler-bridge/ws-handler.js.map +1 -0
  64. package/dist/handle-room/version-1/schema/count.js +16 -0
  65. package/dist/handle-room/version-1/schema/count.js.map +1 -0
  66. package/dist/handle-room/version-1/schema/lock.js +46 -0
  67. package/dist/handle-room/version-1/schema/lock.js.map +1 -0
  68. package/dist/handle-room/version-1/schema/selected.js +13 -0
  69. package/dist/handle-room/version-1/schema/selected.js.map +1 -0
  70. package/dist/handle-room/version-1/select.js +94 -0
  71. package/dist/handle-room/version-1/select.js.map +1 -0
  72. package/dist/index.js +180 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/server.js +88 -0
  75. package/dist/server.js.map +1 -0
  76. package/dist/utils/fs.js +32 -0
  77. package/dist/utils/fs.js.map +1 -0
  78. package/dist/utils/http-router.js +49 -0
  79. package/dist/utils/http-router.js.map +1 -0
  80. package/dist/utils/pagination.js +23 -0
  81. package/dist/utils/pagination.js.map +1 -0
  82. package/dist/utils/websocket-router.js +15 -0
  83. package/dist/utils/websocket-router.js.map +1 -0
  84. package/package.json +66 -0
  85. package/src/authentication.ts +48 -0
  86. package/src/config/constants.ts +7 -0
  87. package/src/config/findFile.ts +34 -0
  88. package/src/config/index.ts +17 -0
  89. package/src/config/manage.ts +48 -0
  90. package/src/cors.ts +23 -0
  91. package/src/editor-support/index.ts +289 -0
  92. package/src/handle-room/version-0/piece-download/availableGamePiece/index.ts +109 -0
  93. package/src/handle-room/version-0/piece-download/availableGamePiece/isUpToDate.ts +78 -0
  94. package/src/handle-room/version-0/piece-download/downloadPieceAssets/downloadSingleAsset.ts +129 -0
  95. package/src/handle-room/version-0/piece-download/downloadPieceAssets/example.ts +116 -0
  96. package/src/handle-room/version-0/piece-download/downloadPieceAssets/index.ts +144 -0
  97. package/src/handle-room/version-0/piece-download/downloadPieceAssets/types.ts +32 -0
  98. package/src/handle-room/version-0/piece-download/downloadPieceAssets/validateAssetFile.ts +54 -0
  99. package/src/handle-room/version-0/piece-download/index.ts +5 -0
  100. package/src/handle-room/version-0/piece-selection/constants.ts +39 -0
  101. package/src/handle-room/version-0/piece-selection/handleRoomSelections.ts +202 -0
  102. package/src/handle-room/version-0/piece-selection/index.ts +31 -0
  103. package/src/handle-room/version-0/piece-selection/roomErrors.ts +25 -0
  104. package/src/handle-room/version-0/piece-selection/steps/encrypton.ts +70 -0
  105. package/src/handle-room/version-0/piece-selection/steps/final-selection.ts +72 -0
  106. package/src/handle-room/version-0/piece-selection/steps/player-selection.ts +100 -0
  107. package/src/handle-room/version-1/constants.ts +44 -0
  108. package/src/handle-room/version-1/file-routes.ts +68 -0
  109. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/MultiAbort.ts +83 -0
  110. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/ensurePieceExists.ts +317 -0
  111. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/getVersions.ts +31 -0
  112. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/index.ts +13 -0
  113. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/schema.ts +547 -0
  114. package/src/handle-room/version-1/globals/FolderDB/SQLFolderDB/types.ts +6 -0
  115. package/src/handle-room/version-1/globals/FolderDB/index.ts +3 -0
  116. package/src/handle-room/version-1/globals/FolderDB/types.ts +72 -0
  117. package/src/handle-room/version-1/globals/Room/WebsocketRoom.ts +118 -0
  118. package/src/handle-room/version-1/globals/Room/index.ts +3 -0
  119. package/src/handle-room/version-1/globals/Room/room.ts +8 -0
  120. package/src/handle-room/version-1/globals/index.ts +4 -0
  121. package/src/handle-room/version-1/globals/types.ts +7 -0
  122. package/src/handle-room/version-1/handleDownloads/DownloadTracker.ts +98 -0
  123. package/src/handle-room/version-1/handleDownloads/index.ts +41 -0
  124. package/src/handle-room/version-1/handleDownloads/types.ts +9 -0
  125. package/src/handle-room/version-1/handleRoomSelections/encryption.ts +29 -0
  126. package/src/handle-room/version-1/handleRoomSelections/random.ts +9 -0
  127. package/src/handle-room/version-1/index.ts +33 -0
  128. package/src/handle-room/version-1/piece-sort.ts +96 -0
  129. package/src/handle-room/version-1/room-handler-bridge/exchange-and-download-selections.ts +125 -0
  130. package/src/handle-room/version-1/room-handler-bridge/http-handler.ts +16 -0
  131. package/src/handle-room/version-1/room-handler-bridge/server.pseudo.ts +85 -0
  132. package/src/handle-room/version-1/room-handler-bridge/steps.ts +255 -0
  133. package/src/handle-room/version-1/room-handler-bridge/ws-handler.ts +30 -0
  134. package/src/handle-room/version-1/room-handler-state/RoomState.ts +132 -0
  135. package/src/handle-room/version-1/room-handler-state/bind-to-websocket.ts +93 -0
  136. package/src/handle-room/version-1/room-handler-state/handleRoomSelections.ts +71 -0
  137. package/src/handle-room/version-1/room-handler-state/handleRoomWithErrors.ts +55 -0
  138. package/src/handle-room/version-1/room-handler-state/roomErrors.ts +35 -0
  139. package/src/handle-room/version-1/room-handler-state/user-selection.ts +98 -0
  140. package/src/handle-room/version-1/schema/count.ts +13 -0
  141. package/src/handle-room/version-1/schema/lock.ts +47 -0
  142. package/src/handle-room/version-1/schema/selected.ts +14 -0
  143. package/src/handle-room/version-1/select.ts +130 -0
  144. package/src/handle-room/version-1/validateUnknown.ts +11 -0
  145. package/src/index.old.ts +71 -0
  146. package/src/index.ts +231 -0
  147. package/src/server.ts +85 -0
  148. package/src/utils/fs.ts +43 -0
  149. package/src/utils/http-router.ts +55 -0
  150. package/src/utils/pagination.ts +17 -0
  151. package/src/utils/websocket-router.ts +23 -0
  152. package/tests/integration/editor-pieces-search.test.ts +147 -0
  153. package/tests/integration/game-complete.test.ts +72 -0
  154. package/tests/integration/handle-full-selection.test.ts +143 -0
  155. package/tests/integration/helpers/bridge.ts +22 -0
  156. package/tests/integration/helpers/fakeFolderDB.ts +37 -0
  157. package/tests/integration/helpers/fakePluginRuntime.ts +46 -0
  158. package/tests/integration/helpers/lockConfig.ts +71 -0
  159. package/tests/integration/helpers/make-tar-gz.ts +41 -0
  160. package/tests/integration/helpers/piece.ts +98 -0
  161. package/tests/integration/helpers/plugin-dir.ts +26 -0
  162. package/tests/integration/helpers/room.ts +42 -0
  163. package/tests/integration/helpers/server.ts +51 -0
  164. package/tests/integration/helpers/validLockConfig.ts +80 -0
  165. package/tests/integration/piece-download-and-list.test.ts +194 -0
  166. package/tests/integration/piece-ensure-ws.test.ts +256 -0
  167. package/tests/integration/piece-files.test.ts +139 -0
  168. package/tests/integration/piece-sort-list.test.ts +113 -0
  169. package/tests/integration/protocol.test.ts +129 -0
  170. package/tests/integration/root-and-auth.test.ts +100 -0
  171. package/tests/integration/sync-dl.test.ts +62 -0
  172. package/tests/tsconfig.json +14 -0
  173. package/tsconfig.json +22 -0
  174. package/vitest.config.ts +10 -0
@@ -0,0 +1,55 @@
1
+ import { RosterLockV1Config, UserInput } from "@roster-lock/types";
2
+
3
+ import { IRoom } from "../globals/Room";
4
+ import { waitForExternalError, waitForStoppedRoomHeartbeat } from "./roomErrors";
5
+ import { handleRoomSelections } from "./handleRoomSelections";
6
+ import { ExternalUserError, MATCHLOCK_SELECTION_STATE } from "../constants";
7
+ import { IFolderDB } from "../globals/FolderDB";
8
+ import { handleDownloads } from "../handleDownloads";
9
+
10
+ type Results = Awaited<ReturnType<typeof doWork>>;
11
+
12
+ export async function tryToHandleRoomVersion1(
13
+ folderDB: IFolderDB,
14
+ room: IRoom,
15
+ lockConfig: RosterLockV1Config,
16
+ selection: UserInput["userSelection"],
17
+ ): Promise<Results>{
18
+ const abortController = new AbortController();
19
+ try {
20
+ return await Promise.race([
21
+ doWork(folderDB, room, abortController.signal, lockConfig, selection),
22
+ waitForExternalError<Results>(room, abortController.signal),
23
+ waitForStoppedRoomHeartbeat<Results>(room, 10_000, abortController.signal),
24
+ ])
25
+ }catch(e){
26
+ console.log("Match To Start Failed", e);
27
+ if(!(e instanceof ExternalUserError) && e instanceof Error){
28
+ room.broadcast(MATCHLOCK_SELECTION_STATE.failure, e.message);
29
+ }
30
+ room.disconnect();
31
+ throw e;
32
+ }finally{
33
+ abortController.abort();
34
+ }
35
+ }
36
+
37
+ async function doWork(
38
+ db: IFolderDB,
39
+ room: IRoom,
40
+ abortSignal: AbortSignal,
41
+ lockConfig: RosterLockV1Config,
42
+ userSelection: UserInput["userSelection"],
43
+ ){
44
+ const finalSelection = await handleRoomSelections(room, abortSignal, lockConfig, userSelection);
45
+ const downloadResults = await handleDownloads(
46
+ db, lockConfig, finalSelection, {
47
+ onProgress: (event)=>{room.broadcast(event.type, event)},
48
+ abortSignal
49
+ }
50
+ );
51
+ return {
52
+ finalSelection,
53
+ downloadResults
54
+ };
55
+ }
@@ -0,0 +1,35 @@
1
+
2
+ import { IRoom } from "../globals/Room/room";
3
+ import { ExternalUserError, ROOM_EVENT, MATCHLOCK_DOWNLOAD_STATE, MATCHLOCK_SELECTION_STATE } from "../constants";
4
+
5
+ import { waitForEvent, waitForEventTimeout } from "@roster-lock/utils";
6
+ export async function waitForExternalError<T>(
7
+ room: IRoom, abortSignal: AbortSignal
8
+ ): Promise<T> {
9
+ const [userId, event, message] = await waitForEvent(
10
+ room.listen.filter((userId, event, message)=>{
11
+ if(event === ROOM_EVENT.userLeft) return true;
12
+ if(event === MATCHLOCK_SELECTION_STATE.failure) return true;
13
+ if(event === MATCHLOCK_DOWNLOAD_STATE.downloadFullFailure) return true;
14
+ return false;
15
+ }),
16
+ abortSignal
17
+ )
18
+ throw new ExternalUserError(userId, message);
19
+ }
20
+
21
+ export async function waitForStoppedRoomHeartbeat<T>(
22
+ room: IRoom, timeout: number, abortSignal: AbortSignal
23
+ ): Promise<T> {
24
+ await waitForEventTimeout(
25
+ room.listen.filter((userId, event, message)=>{
26
+ if(event === ROOM_EVENT.userLeft) return false;
27
+ if(event === MATCHLOCK_SELECTION_STATE.failure) return false;
28
+ if(event === MATCHLOCK_DOWNLOAD_STATE.downloadFullFailure) return false;
29
+ return true;
30
+ }),
31
+ timeout,
32
+ abortSignal
33
+ );
34
+ throw new Error("Heartbeat Timeout");
35
+ }
@@ -0,0 +1,98 @@
1
+ import {
2
+ PieceType, UserId,
3
+ runSelection,
4
+ } from "@roster-lock/shared";
5
+ import {
6
+ RosterLockV1Config, SelectedPiece,
7
+ UserInput, FinalSelection,
8
+ } from "@roster-lock/types";
9
+ import { JSONSchemaType } from "ajv";
10
+
11
+ // Using 'as unknown as' because AJV's JSONSchemaType doesn't handle recursive $ref well
12
+ export const selectedPieceSchema: JSONSchemaType<SelectedPiece> = {
13
+ $id: "SelectedPiece",
14
+ type: "object",
15
+ required: ["id", "required"],
16
+ additionalProperties: false,
17
+ properties: {
18
+ id: { type: "string" },
19
+ required: {
20
+ type: "object",
21
+ required: [],
22
+ additionalProperties: {
23
+ type: "array",
24
+ items: { $ref: "SelectedPiece" },
25
+ },
26
+ },
27
+ },
28
+ } as unknown as JSONSchemaType<SelectedPiece>;
29
+
30
+ export const userInputSchema: JSONSchemaType<UserInput> = {
31
+ type: "object",
32
+ required: ["randomSeed", "userSelection"],
33
+ additionalProperties: false,
34
+ properties: {
35
+ randomSeed: { type: "string" },
36
+ userSelection: {
37
+ type: "object",
38
+ required: [],
39
+ additionalProperties: {
40
+ type: "array",
41
+ items: selectedPieceSchema,
42
+ },
43
+ },
44
+ },
45
+ }
46
+
47
+ import { PluginHandler } from "@roster-lock/plugin-runtime";
48
+ export function finalizeSelection(
49
+ config: RosterLockV1Config,
50
+ gameControlledSelections: Record<PieceType, Array<SelectedPiece> | Record<UserId, Array<SelectedPiece>>>,
51
+ userInputs: Record<UserId, UserInput>,
52
+ pluginRuntime: PluginHandler
53
+ ): Promise<FinalSelection> {
54
+
55
+ return runSelection(
56
+ config,
57
+ gameControlledSelections,
58
+ userInputs,
59
+ (script)=>(pluginRuntime.runUntrustedScript(script))
60
+ )
61
+ }
62
+
63
+ export const finalSelectionSchema: JSONSchemaType<FinalSelection> = {
64
+ type: "object",
65
+ required: [],
66
+ additionalProperties: {
67
+ anyOf: [
68
+ {
69
+ type: "object",
70
+ required: ["type", "value"],
71
+ additionalProperties: false,
72
+ properties: {
73
+ type: { type: "string", const: "shared" },
74
+ value: {
75
+ type: "array",
76
+ items: selectedPieceSchema,
77
+ },
78
+ },
79
+ },
80
+ {
81
+ type: "object",
82
+ required: ["type", "value"],
83
+ additionalProperties: false,
84
+ properties: {
85
+ type: { type: "string", const: "personal" },
86
+ value: {
87
+ type: "object",
88
+ required: [],
89
+ additionalProperties: {
90
+ type: "array",
91
+ items: selectedPieceSchema,
92
+ },
93
+ },
94
+ },
95
+ },
96
+ ],
97
+ }
98
+ }
@@ -0,0 +1,13 @@
1
+
2
+ import { Count } from "@roster-lock/types";
3
+ import { z } from "zod";
4
+
5
+ export const CountSchema: z.ZodType<Count> = z.union([
6
+ z.number().positive(),
7
+ z.literal("*"),
8
+ z.tuple([z.number().min(0), z.union([z.number().positive(), z.literal("*")])]),
9
+ ]).refine((count) => {
10
+ if(!Array.isArray(count)) return true;
11
+ if(count[1] === "*") return true;
12
+ return count[0] < count[1];
13
+ }, { message: "maximum should be greater than the minimum" });
@@ -0,0 +1,47 @@
1
+ import { RosterLockV1Config, RosterLockPiece } from "@roster-lock/types";
2
+ import z, { ZodType } from "zod";
3
+
4
+ import { CountSchema } from "./count";
5
+
6
+ export const engineCaster: ZodType<RosterLockV1Config["engine"]> = z.object({
7
+ name: z.string(),
8
+ version: z.string(),
9
+ pieceDefinitions: z.record(z.string(), z.object({
10
+ selectionStrategy: z.enum(["mandatory", "personal", "shared", "on demand"]),
11
+ requires: z.array(z.string()),
12
+ pathVariables: z.array(z.string()),
13
+ assets: z.array(z.object({
14
+ name: z.string(),
15
+ classification: z.enum(["logic", "media", "doc"]),
16
+ count: CountSchema,
17
+ glob: z.array(z.string()),
18
+ })),
19
+ })),
20
+ })
21
+
22
+ const pieceObjectCaster = z.object({
23
+ id: z.string(),
24
+ version: z.object({
25
+ logic: z.string(),
26
+ media: z.string(),
27
+ docs: z.string(),
28
+ }),
29
+ humanInfo: z.object({
30
+ name: z.string(),
31
+ author: z.string(),
32
+ url: z.string(),
33
+ image: z.string().optional(),
34
+ }),
35
+ downloadSources: z.array(z.string()),
36
+ pathVariables: z.record(z.string(), z.string()),
37
+ requiredPieces: z.record(z.string(), z.object({
38
+ expected: z.array(z.string()),
39
+ selectable: z.boolean(),
40
+ })),
41
+ })
42
+
43
+ export const pieceCaster: ZodType<RosterLockPiece> = pieceObjectCaster;
44
+
45
+ // Piece file routes only resolve a piece's on-disk folder (by content hash +
46
+ // pathVariables), so they don't need the rest of RosterLockPiece.
47
+ export const pieceFileInfoCaster = pieceObjectCaster.pick({ version: true, pathVariables: true });
@@ -0,0 +1,14 @@
1
+ import { UserInput, SelectedPiece } from "@roster-lock/types";
2
+ import { z, ZodType } from "zod";
3
+
4
+ const SelectedPieceSchema: ZodType<SelectedPiece> = z.lazy(() =>
5
+ z.object({
6
+ id: z.string(),
7
+ required: z.record(z.string(), z.object({
8
+ mandatory: z.array(SelectedPieceSchema),
9
+ selectable: z.array(SelectedPieceSchema),
10
+ })),
11
+ }).strict()
12
+ );
13
+
14
+ export const UserSelectionSchema: z.ZodType<UserInput["userSelection"]> = z.record(z.string(), z.array(SelectedPieceSchema));
@@ -0,0 +1,130 @@
1
+
2
+ import { RosterLockPiece, RosterLockV1Config, DownloadResult } from "@roster-lock/types";
3
+ import { jsonBody, HTTPRequestHandler, HTTPError } from "../../utils/http-router";
4
+ import { WebSocketHandlerCallback } from "../../utils/websocket-router";
5
+ import { handlePagination } from "../../utils/pagination"
6
+ import { MessageBridge } from "@roster-lock/utils";
7
+ import z, { ZodType } from "zod";
8
+ import { engineCaster, pieceCaster } from "./schema/lock";
9
+ import { V1Env } from "./globals/types";
10
+
11
+ type GetPieceInfo = {
12
+ engine: RosterLockV1Config["engine"]
13
+ pieceType: string
14
+ piece: RosterLockPiece
15
+ }
16
+
17
+ const ensurePieceSchema: ZodType<GetPieceInfo> = z.object({
18
+ engine: engineCaster,
19
+ pieceType: z.string(),
20
+ piece: pieceCaster,
21
+ })
22
+
23
+ export const ensurePieceDownloaded: HTTPRequestHandler = async function(
24
+ this: V1Env, { req, res }
25
+ ){
26
+ const { fileDB } = this;
27
+ const body = await jsonBody(req)
28
+ const parseResult = ensurePieceSchema.safeParse(body);
29
+ if(!parseResult.success){
30
+ throw new HTTPError(400, "Bad Form", parseResult.error);
31
+ }
32
+ const { engine, pieceType, piece } = parseResult.data;
33
+
34
+ const abortController = new AbortController();
35
+ req.on("close", ()=>abortController.abort());
36
+
37
+ const pieceFolder = await fileDB.ensurePieceExists(
38
+ engine, pieceType, piece, {
39
+ onProgress: ()=>{},
40
+ abortSignal: abortController.signal,
41
+ }
42
+ );
43
+
44
+ const dlResult: DownloadResult = {
45
+ pieceType,
46
+ pieceId: piece.id,
47
+ pieceVersions: { logic: piece.version.logic, media: piece.version.media },
48
+ folder: pieceFolder,
49
+ }
50
+
51
+ res.writeHead(200, { "Content-Type": "application/json" });
52
+ res.end(JSON.stringify(dlResult));
53
+ }
54
+
55
+ export const ensurePieceDownloadedWs: WebSocketHandlerCallback = async function(
56
+ this: V1Env, { ws }, params, next
57
+ ){
58
+ const { fileDB } = this;
59
+ try {
60
+ const bridge = new MessageBridge((message)=>ws.send(JSON.stringify(message)));
61
+ ws.on("message", (message)=>{
62
+ bridge.handleMessage(JSON.parse(message.toString()));
63
+ });
64
+
65
+ const abortController = new AbortController();
66
+ ws.on("close", ()=>abortController.abort());
67
+
68
+ bridge.onRequest("ensure-piece", async (body)=>{
69
+ const parseResult = ensurePieceSchema.safeParse(body);
70
+ if(!parseResult.success){
71
+ throw new Error("Bad Form: " + parseResult.error.message);
72
+ }
73
+ const { engine, pieceType, piece } = parseResult.data;
74
+
75
+ const pieceFolder = await fileDB.ensurePieceExists(
76
+ engine, pieceType, piece, {
77
+ onProgress: (update)=>{bridge.sendEvent("download-progress", update)},
78
+ abortSignal: abortController.signal,
79
+ }
80
+ );
81
+
82
+ const dlResult: DownloadResult = {
83
+ pieceType,
84
+ pieceId: piece.id,
85
+ pieceVersions: { logic: piece.version.logic, media: piece.version.media },
86
+ folder: pieceFolder,
87
+ }
88
+ return dlResult;
89
+ });
90
+ bridge.sendEvent("ready", {});
91
+ }catch(e){
92
+ ws.terminate();
93
+ next(e);
94
+ }
95
+ }
96
+
97
+
98
+ type ListDownloadedPiecesDirect = {
99
+ engineName: string,
100
+ pieceType: string,
101
+ logicIds: Array<string>,
102
+ }
103
+
104
+ const listPiecesDirectSchema: ZodType<ListDownloadedPiecesDirect> = z.object({
105
+ engineName: z.string(),
106
+ pieceType: z.string(),
107
+ logicIds: z.array(z.string()),
108
+ })
109
+
110
+ export const listDownloadedPiecesDirect: HTTPRequestHandler = async function(
111
+ this: V1Env, { req, res }, routeInfo
112
+ ){
113
+ const { fileDB } = this;
114
+ const { page, limit } = handlePagination(routeInfo.url.searchParams);
115
+
116
+ const body = await jsonBody(req)
117
+ const parseResult = listPiecesDirectSchema.safeParse(body);
118
+ if(!parseResult.success) throw new HTTPError(400, "Bad Form", parseResult.error);
119
+ const config = parseResult.data;
120
+
121
+ const pieces = await fileDB.listPieces(
122
+ config.engineName,
123
+ config.pieceType,
124
+ config.logicIds,
125
+ { page, limit }
126
+ );
127
+ res.writeHead(200, { "content-type": "application/json"});
128
+ res.end(JSON.stringify(pieces));
129
+ }
130
+
@@ -0,0 +1,11 @@
1
+ import { JSON_Unknown } from "@roster-lock/utils";
2
+ import { Ajv, JSONSchemaType } from "ajv";
3
+
4
+ export function validateUnknown<T>(schema: JSONSchemaType<T>, data: JSON_Unknown): T {
5
+ const ajv = new Ajv();
6
+ const validate = ajv.compile(schema);
7
+ if(!validate(data)){
8
+ throw new Error("Invalid Data");
9
+ }
10
+ return data;
11
+ }
@@ -0,0 +1,71 @@
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
+ }