diff --git a/amnet-native/models/AMServer.ts b/amnet-native/models/AMServer.ts index 7b16bce..08e5f33 100644 --- a/amnet-native/models/AMServer.ts +++ b/amnet-native/models/AMServer.ts @@ -1,7 +1,7 @@ import {Realm} from 'realm'; -import {AMServerInfoResponse} from "amnet-shared"; +import {AMServerConnectionInfo, AMServerMetadata} from "amnet-shared"; -export class AMServer extends Realm.Object implements AMServerInfoResponse { +export class AMServer extends Realm.Object implements AMServerConnectionInfo, AMServerMetadata { _id!: Realm.BSON.ObjectId; apiVersion: number; diff --git a/amnet-shared/dist/index.d.mts b/amnet-shared/dist/index.d.mts index 543e219..ad11302 100644 --- a/amnet-shared/dist/index.d.mts +++ b/amnet-shared/dist/index.d.mts @@ -2,23 +2,38 @@ * The latest version of the AMNet API this library supports */ declare const AMNET_API_VERSION = 1; +/** + * The minimum version of the AMNet API this library supports + */ +declare const MIN_AMNET_API_VERSION = 1; interface AMCardInRequest { cardId: string; } /** - * Information needed to perform a request against an AMNet server + * Server connection info (api version, address, etc.) */ interface AMServerConnectionInfo { apiVersion: number; serverAddress: string; } /** - * Received metadata from an AMNet server + * User-definable server metadata */ -interface AMServerInfoResponse extends AMServerConnectionInfo { +interface AMServerMetadata { serverName: string; gameId?: string | null | undefined; } +/** + * Metrics about the current game session + */ +interface AMServerInstanceMetrics { + timeSinceLastPoll?: number; + sessionUptime?: number; +} +/** + * The response from an AMNet server's info endpoint + */ +type AMServerInfo = AMServerMetadata & AMServerConnectionInfo & AMServerInstanceMetrics; /** * Performs the card-in request to the specified server * @param server The server to perform the request against @@ -31,7 +46,7 @@ declare function performCardIn(server: AMServerConnectionInfo, request: AMCardIn * @param serverAddress The origin address of the server to connect to * @param abort The abort signal to use for the request (for cancellation) */ -declare function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise; +declare function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise; declare const CARD_ID_REGEX: RegExp; declare function generateCardNumber(): string; @@ -47,4 +62,4 @@ declare function validateCardId(id: string): boolean; */ declare function getGameName(id: string): string | null; -export { type AMCardInRequest, AMNET_API_VERSION, type AMServerConnectionInfo, type AMServerInfoResponse, CARD_ID_REGEX, fetchServerInfo, generateCardNumber, getGameName, performCardIn, validateCardId }; +export { type AMCardInRequest, AMNET_API_VERSION, type AMServerConnectionInfo, type AMServerInfo, type AMServerInstanceMetrics, type AMServerMetadata, CARD_ID_REGEX, MIN_AMNET_API_VERSION, fetchServerInfo, generateCardNumber, getGameName, performCardIn, validateCardId }; diff --git a/amnet-shared/dist/index.d.ts b/amnet-shared/dist/index.d.ts index 543e219..ad11302 100644 --- a/amnet-shared/dist/index.d.ts +++ b/amnet-shared/dist/index.d.ts @@ -2,23 +2,38 @@ * The latest version of the AMNet API this library supports */ declare const AMNET_API_VERSION = 1; +/** + * The minimum version of the AMNet API this library supports + */ +declare const MIN_AMNET_API_VERSION = 1; interface AMCardInRequest { cardId: string; } /** - * Information needed to perform a request against an AMNet server + * Server connection info (api version, address, etc.) */ interface AMServerConnectionInfo { apiVersion: number; serverAddress: string; } /** - * Received metadata from an AMNet server + * User-definable server metadata */ -interface AMServerInfoResponse extends AMServerConnectionInfo { +interface AMServerMetadata { serverName: string; gameId?: string | null | undefined; } +/** + * Metrics about the current game session + */ +interface AMServerInstanceMetrics { + timeSinceLastPoll?: number; + sessionUptime?: number; +} +/** + * The response from an AMNet server's info endpoint + */ +type AMServerInfo = AMServerMetadata & AMServerConnectionInfo & AMServerInstanceMetrics; /** * Performs the card-in request to the specified server * @param server The server to perform the request against @@ -31,7 +46,7 @@ declare function performCardIn(server: AMServerConnectionInfo, request: AMCardIn * @param serverAddress The origin address of the server to connect to * @param abort The abort signal to use for the request (for cancellation) */ -declare function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise; +declare function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise; declare const CARD_ID_REGEX: RegExp; declare function generateCardNumber(): string; @@ -47,4 +62,4 @@ declare function validateCardId(id: string): boolean; */ declare function getGameName(id: string): string | null; -export { type AMCardInRequest, AMNET_API_VERSION, type AMServerConnectionInfo, type AMServerInfoResponse, CARD_ID_REGEX, fetchServerInfo, generateCardNumber, getGameName, performCardIn, validateCardId }; +export { type AMCardInRequest, AMNET_API_VERSION, type AMServerConnectionInfo, type AMServerInfo, type AMServerInstanceMetrics, type AMServerMetadata, CARD_ID_REGEX, MIN_AMNET_API_VERSION, fetchServerInfo, generateCardNumber, getGameName, performCardIn, validateCardId }; diff --git a/amnet-shared/dist/index.js b/amnet-shared/dist/index.js index 9312eac..fb2c6ec 100644 --- a/amnet-shared/dist/index.js +++ b/amnet-shared/dist/index.js @@ -59,6 +59,7 @@ var src_exports = {}; __export(src_exports, { AMNET_API_VERSION: () => AMNET_API_VERSION, CARD_ID_REGEX: () => CARD_ID_REGEX, + MIN_AMNET_API_VERSION: () => MIN_AMNET_API_VERSION, fetchServerInfo: () => fetchServerInfo, generateCardNumber: () => generateCardNumber, getGameName: () => getGameName, @@ -69,8 +70,12 @@ module.exports = __toCommonJS(src_exports); // src/api.ts var AMNET_API_VERSION = 1; +var MIN_AMNET_API_VERSION = 1; function performCardIn(server, request, signal) { return __async(this, null, function* () { + if (server.apiVersion < MIN_AMNET_API_VERSION || server.apiVersion > AMNET_API_VERSION) { + throw new Error("Unsupported server API version"); + } try { const response = yield fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, { method: "POST", @@ -92,6 +97,9 @@ function fetchServerInfo(serverAddress, abort) { const response = yield fetch(`${new URL(serverAddress).origin}/amnet/info`, { signal: abort }); + if (!response.ok) { + return null; + } return __spreadProps(__spreadValues({}, yield response.json()), { serverAddress }); @@ -143,6 +151,7 @@ function getGameName(id) { 0 && (module.exports = { AMNET_API_VERSION, CARD_ID_REGEX, + MIN_AMNET_API_VERSION, fetchServerInfo, generateCardNumber, getGameName, diff --git a/amnet-shared/dist/index.js.map b/amnet-shared/dist/index.js.map index f47eeb7..8db7e48 100644 --- a/amnet-shared/dist/index.js.map +++ b/amnet-shared/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/api.ts","../src/card.ts","../src/gameIds.ts"],"sourcesContent":["export * from './api';\nexport * from './card';\nexport * from './gameIds';","/**\n * The latest version of the AMNet API this library supports\n */\nexport const AMNET_API_VERSION = 1;\n\nexport interface AMCardInRequest {\n cardId: string;\n}\n\n/**\n * Information needed to perform a request against an AMNet server\n */\nexport interface AMServerConnectionInfo {\n apiVersion: number;\n serverAddress: string;\n}\n\n/**\n * Received metadata from an AMNet server \n */\nexport interface AMServerInfoResponse extends AMServerConnectionInfo {\n serverName: string;\n gameId?: string | null | undefined;\n}\n\n/**\n * Performs the card-in request to the specified server\n * @param server The server to perform the request against\n * @param request The card-in request to send\n * @param signal The abort signal to use for the request\n */\nexport async function performCardIn(server: AMServerConnectionInfo, request: AMCardInRequest, signal: AbortSignal) {\n try {\n const response = await fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(request),\n signal: signal\n });\n\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Attempts to connect to the specified server and fetch its info\n * @param serverAddress The origin address of the server to connect to\n * @param abort The abort signal to use for the request (for cancellation)\n */\nexport async function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise {\n try {\n const response = await fetch(`${new URL(serverAddress).origin}/amnet/info`, {\n signal: abort\n });\n\n return {\n ...await response.json(),\n serverAddress: serverAddress\n } satisfies AMServerInfoResponse;\n } catch {\n return null;\n }\n}\n","export const CARD_ID_REGEX = /^(?:[0-9]{4} ?){5}$/i;\n\nexport function generateCardNumber(): string {\n let cardId = '';\n\n while (true) {\n // don't start with a 3\n const firstDigit = Math.floor(Math.random() * 10);\n\n if (firstDigit !== 3) {\n cardId = firstDigit.toString();\n break;\n }\n }\n\n for (let i = 0; i < 19; i++) {\n cardId += Math.floor(Math.random() * 10).toString();\n }\n\n return cardId;\n}\n\n/**\n * Validates the provided card id by known parameters\n * @param id The card id to validate\n */\nexport function validateCardId(id: string): boolean {\n return id[0] !== '3' && CARD_ID_REGEX.test(id);\n}\n","/**\n * Get the game name from the game identifier (4-letter code)\n * @param id\n */\nexport function getGameName(id: string): string | null {\n switch (id?.toUpperCase()) {\n case \"SDGT\":\n return \"Initial D The Arcade\";\n case \"SDHD\":\n case \"SDBT\":\n return \"CHUNITHM\";\n case \"SDDT\":\n return \"O.N.G.E.K.I.\";\n case \"SDEZ\":\n return \"maimai DX\";\n case \"SBZV\":\n return \"Project DIVA Future Tone\";\n \n default:\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,oBAAoB;AA4BjC,SAAsB,cAAc,QAAgC,SAA0B,QAAqB;AAAA;AAC/G,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,aAAa,EAAE,MAAM,iBAAiB;AAAA,QACjF,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,QAC5B;AAAA,MACJ,CAAC;AAED,aAAO,SAAS;AAAA,IACpB,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAOA,SAAsB,gBAAgB,eAAuB,OAA0D;AAAA;AACnH,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,aAAa,EAAE,MAAM,eAAe;AAAA,QACxE,QAAQ;AAAA,MACZ,CAAC;AAED,aAAO,iCACA,MAAM,SAAS,KAAK,IADpB;AAAA,QAEH;AAAA,MACJ;AAAA,IACJ,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;;;AClEO,IAAM,gBAAgB;AAEtB,SAAS,qBAA6B;AACzC,MAAI,SAAS;AAEb,SAAO,MAAM;AAET,UAAM,aAAa,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAEhD,QAAI,eAAe,GAAG;AAClB,eAAS,WAAW,SAAS;AAC7B;AAAA,IACJ;AAAA,EACJ;AAEA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AACzB,cAAU,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,EAAE,SAAS;AAAA,EACtD;AAEA,SAAO;AACX;AAMO,SAAS,eAAe,IAAqB;AAChD,SAAO,GAAG,CAAC,MAAM,OAAO,cAAc,KAAK,EAAE;AACjD;;;ACxBO,SAAS,YAAY,IAA2B;AACnD,UAAQ,yBAAI,eAAe;AAAA,IACvB,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AAAA,IACL,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IAEX;AACI,aAAO;AAAA,EACf;AACJ;","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/api.ts","../src/card.ts","../src/gameIds.ts"],"sourcesContent":["export * from './api';\nexport * from './card';\nexport * from './gameIds';","/**\n * The latest version of the AMNet API this library supports\n */\nexport const AMNET_API_VERSION = 1;\n\n/**\n * The minimum version of the AMNet API this library supports\n */\nexport const MIN_AMNET_API_VERSION = 1;\n\nexport interface AMCardInRequest {\n cardId: string;\n}\n\n/**\n * Server connection info (api version, address, etc.)\n */\nexport interface AMServerConnectionInfo {\n apiVersion: number;\n serverAddress: string;\n}\n\n/**\n * User-definable server metadata\n */\nexport interface AMServerMetadata {\n serverName: string;\n gameId?: string | null | undefined;\n}\n\n/**\n * Metrics about the current game session\n */\nexport interface AMServerInstanceMetrics {\n timeSinceLastPoll?: number;\n sessionUptime?: number;\n}\n\n/**\n * The response from an AMNet server's info endpoint\n */\nexport type AMServerInfo = AMServerMetadata & AMServerConnectionInfo & AMServerInstanceMetrics;\n\n/**\n * Performs the card-in request to the specified server\n * @param server The server to perform the request against\n * @param request The card-in request to send\n * @param signal The abort signal to use for the request\n */\nexport async function performCardIn(server: AMServerConnectionInfo, request: AMCardInRequest, signal: AbortSignal): Promise {\n if (server.apiVersion < MIN_AMNET_API_VERSION || server.apiVersion > AMNET_API_VERSION) {\n throw new Error(\"Unsupported server API version\");\n }\n \n try {\n const response = await fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(request),\n signal: signal\n });\n\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Attempts to connect to the specified server and fetch its info\n * @param serverAddress The origin address of the server to connect to\n * @param abort The abort signal to use for the request (for cancellation)\n */\nexport async function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise {\n try {\n const response = await fetch(`${new URL(serverAddress).origin}/amnet/info`, {\n signal: abort\n });\n \n if (!response.ok) {\n return null;\n }\n\n return {\n ...await response.json(),\n serverAddress: serverAddress\n } satisfies AMServerMetadata;\n } catch {\n return null;\n }\n}\n","export const CARD_ID_REGEX = /^(?:[0-9]{4} ?){5}$/i;\n\nexport function generateCardNumber(): string {\n let cardId = '';\n\n while (true) {\n // don't start with a 3\n const firstDigit = Math.floor(Math.random() * 10);\n\n if (firstDigit !== 3) {\n cardId = firstDigit.toString();\n break;\n }\n }\n\n for (let i = 0; i < 19; i++) {\n cardId += Math.floor(Math.random() * 10).toString();\n }\n\n return cardId;\n}\n\n/**\n * Validates the provided card id by known parameters\n * @param id The card id to validate\n */\nexport function validateCardId(id: string): boolean {\n return id[0] !== '3' && CARD_ID_REGEX.test(id);\n}\n","/**\n * Get the game name from the game identifier (4-letter code)\n * @param id\n */\nexport function getGameName(id: string): string | null {\n switch (id?.toUpperCase()) {\n case \"SDGT\":\n return \"Initial D The Arcade\";\n case \"SDHD\":\n case \"SDBT\":\n return \"CHUNITHM\";\n case \"SDDT\":\n return \"O.N.G.E.K.I.\";\n case \"SDEZ\":\n return \"maimai DX\";\n case \"SBZV\":\n return \"Project DIVA Future Tone\";\n \n default:\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,oBAAoB;AAK1B,IAAM,wBAAwB;AAyCrC,SAAsB,cAAc,QAAgC,SAA0B,QAAuC;AAAA;AACjI,QAAI,OAAO,aAAa,yBAAyB,OAAO,aAAa,mBAAmB;AACpF,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACpD;AAEA,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,aAAa,EAAE,MAAM,iBAAiB;AAAA,QACjF,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,QAC5B;AAAA,MACJ,CAAC;AAED,aAAO,SAAS;AAAA,IACpB,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAOA,SAAsB,gBAAgB,eAAuB,OAAkD;AAAA;AAC3G,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,aAAa,EAAE,MAAM,eAAe;AAAA,QACxE,QAAQ;AAAA,MACZ,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACd,eAAO;AAAA,MACX;AAEA,aAAO,iCACA,MAAM,SAAS,KAAK,IADpB;AAAA,QAEH;AAAA,MACJ;AAAA,IACJ,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;;;AC5FO,IAAM,gBAAgB;AAEtB,SAAS,qBAA6B;AACzC,MAAI,SAAS;AAEb,SAAO,MAAM;AAET,UAAM,aAAa,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAEhD,QAAI,eAAe,GAAG;AAClB,eAAS,WAAW,SAAS;AAC7B;AAAA,IACJ;AAAA,EACJ;AAEA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AACzB,cAAU,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,EAAE,SAAS;AAAA,EACtD;AAEA,SAAO;AACX;AAMO,SAAS,eAAe,IAAqB;AAChD,SAAO,GAAG,CAAC,MAAM,OAAO,cAAc,KAAK,EAAE;AACjD;;;ACxBO,SAAS,YAAY,IAA2B;AACnD,UAAQ,yBAAI,eAAe;AAAA,IACvB,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AAAA,IACL,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IAEX;AACI,aAAO;AAAA,EACf;AACJ;","names":[]} \ No newline at end of file diff --git a/amnet-shared/dist/index.mjs b/amnet-shared/dist/index.mjs index adf405a..a88051e 100644 --- a/amnet-shared/dist/index.mjs +++ b/amnet-shared/dist/index.mjs @@ -40,8 +40,12 @@ var __async = (__this, __arguments, generator) => { // src/api.ts var AMNET_API_VERSION = 1; +var MIN_AMNET_API_VERSION = 1; function performCardIn(server, request, signal) { return __async(this, null, function* () { + if (server.apiVersion < MIN_AMNET_API_VERSION || server.apiVersion > AMNET_API_VERSION) { + throw new Error("Unsupported server API version"); + } try { const response = yield fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, { method: "POST", @@ -63,6 +67,9 @@ function fetchServerInfo(serverAddress, abort) { const response = yield fetch(`${new URL(serverAddress).origin}/amnet/info`, { signal: abort }); + if (!response.ok) { + return null; + } return __spreadProps(__spreadValues({}, yield response.json()), { serverAddress }); @@ -113,6 +120,7 @@ function getGameName(id) { export { AMNET_API_VERSION, CARD_ID_REGEX, + MIN_AMNET_API_VERSION, fetchServerInfo, generateCardNumber, getGameName, diff --git a/amnet-shared/dist/index.mjs.map b/amnet-shared/dist/index.mjs.map index 862e4f0..be11df6 100644 --- a/amnet-shared/dist/index.mjs.map +++ b/amnet-shared/dist/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/api.ts","../src/card.ts","../src/gameIds.ts"],"sourcesContent":["/**\n * The latest version of the AMNet API this library supports\n */\nexport const AMNET_API_VERSION = 1;\n\nexport interface AMCardInRequest {\n cardId: string;\n}\n\n/**\n * Information needed to perform a request against an AMNet server\n */\nexport interface AMServerConnectionInfo {\n apiVersion: number;\n serverAddress: string;\n}\n\n/**\n * Received metadata from an AMNet server \n */\nexport interface AMServerInfoResponse extends AMServerConnectionInfo {\n serverName: string;\n gameId?: string | null | undefined;\n}\n\n/**\n * Performs the card-in request to the specified server\n * @param server The server to perform the request against\n * @param request The card-in request to send\n * @param signal The abort signal to use for the request\n */\nexport async function performCardIn(server: AMServerConnectionInfo, request: AMCardInRequest, signal: AbortSignal) {\n try {\n const response = await fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(request),\n signal: signal\n });\n\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Attempts to connect to the specified server and fetch its info\n * @param serverAddress The origin address of the server to connect to\n * @param abort The abort signal to use for the request (for cancellation)\n */\nexport async function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise {\n try {\n const response = await fetch(`${new URL(serverAddress).origin}/amnet/info`, {\n signal: abort\n });\n\n return {\n ...await response.json(),\n serverAddress: serverAddress\n } satisfies AMServerInfoResponse;\n } catch {\n return null;\n }\n}\n","export const CARD_ID_REGEX = /^(?:[0-9]{4} ?){5}$/i;\n\nexport function generateCardNumber(): string {\n let cardId = '';\n\n while (true) {\n // don't start with a 3\n const firstDigit = Math.floor(Math.random() * 10);\n\n if (firstDigit !== 3) {\n cardId = firstDigit.toString();\n break;\n }\n }\n\n for (let i = 0; i < 19; i++) {\n cardId += Math.floor(Math.random() * 10).toString();\n }\n\n return cardId;\n}\n\n/**\n * Validates the provided card id by known parameters\n * @param id The card id to validate\n */\nexport function validateCardId(id: string): boolean {\n return id[0] !== '3' && CARD_ID_REGEX.test(id);\n}\n","/**\n * Get the game name from the game identifier (4-letter code)\n * @param id\n */\nexport function getGameName(id: string): string | null {\n switch (id?.toUpperCase()) {\n case \"SDGT\":\n return \"Initial D The Arcade\";\n case \"SDHD\":\n case \"SDBT\":\n return \"CHUNITHM\";\n case \"SDDT\":\n return \"O.N.G.E.K.I.\";\n case \"SDEZ\":\n return \"maimai DX\";\n case \"SBZV\":\n return \"Project DIVA Future Tone\";\n \n default:\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGO,IAAM,oBAAoB;AA4BjC,SAAsB,cAAc,QAAgC,SAA0B,QAAqB;AAAA;AAC/G,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,aAAa,EAAE,MAAM,iBAAiB;AAAA,QACjF,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,QAC5B;AAAA,MACJ,CAAC;AAED,aAAO,SAAS;AAAA,IACpB,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAOA,SAAsB,gBAAgB,eAAuB,OAA0D;AAAA;AACnH,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,aAAa,EAAE,MAAM,eAAe;AAAA,QACxE,QAAQ;AAAA,MACZ,CAAC;AAED,aAAO,iCACA,MAAM,SAAS,KAAK,IADpB;AAAA,QAEH;AAAA,MACJ;AAAA,IACJ,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;;;AClEO,IAAM,gBAAgB;AAEtB,SAAS,qBAA6B;AACzC,MAAI,SAAS;AAEb,SAAO,MAAM;AAET,UAAM,aAAa,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAEhD,QAAI,eAAe,GAAG;AAClB,eAAS,WAAW,SAAS;AAC7B;AAAA,IACJ;AAAA,EACJ;AAEA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AACzB,cAAU,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,EAAE,SAAS;AAAA,EACtD;AAEA,SAAO;AACX;AAMO,SAAS,eAAe,IAAqB;AAChD,SAAO,GAAG,CAAC,MAAM,OAAO,cAAc,KAAK,EAAE;AACjD;;;ACxBO,SAAS,YAAY,IAA2B;AACnD,UAAQ,yBAAI,eAAe;AAAA,IACvB,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AAAA,IACL,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IAEX;AACI,aAAO;AAAA,EACf;AACJ;","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/api.ts","../src/card.ts","../src/gameIds.ts"],"sourcesContent":["/**\n * The latest version of the AMNet API this library supports\n */\nexport const AMNET_API_VERSION = 1;\n\n/**\n * The minimum version of the AMNet API this library supports\n */\nexport const MIN_AMNET_API_VERSION = 1;\n\nexport interface AMCardInRequest {\n cardId: string;\n}\n\n/**\n * Server connection info (api version, address, etc.)\n */\nexport interface AMServerConnectionInfo {\n apiVersion: number;\n serverAddress: string;\n}\n\n/**\n * User-definable server metadata\n */\nexport interface AMServerMetadata {\n serverName: string;\n gameId?: string | null | undefined;\n}\n\n/**\n * Metrics about the current game session\n */\nexport interface AMServerInstanceMetrics {\n timeSinceLastPoll?: number;\n sessionUptime?: number;\n}\n\n/**\n * The response from an AMNet server's info endpoint\n */\nexport type AMServerInfo = AMServerMetadata & AMServerConnectionInfo & AMServerInstanceMetrics;\n\n/**\n * Performs the card-in request to the specified server\n * @param server The server to perform the request against\n * @param request The card-in request to send\n * @param signal The abort signal to use for the request\n */\nexport async function performCardIn(server: AMServerConnectionInfo, request: AMCardInRequest, signal: AbortSignal): Promise {\n if (server.apiVersion < MIN_AMNET_API_VERSION || server.apiVersion > AMNET_API_VERSION) {\n throw new Error(\"Unsupported server API version\");\n }\n \n try {\n const response = await fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(request),\n signal: signal\n });\n\n return response.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Attempts to connect to the specified server and fetch its info\n * @param serverAddress The origin address of the server to connect to\n * @param abort The abort signal to use for the request (for cancellation)\n */\nexport async function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise {\n try {\n const response = await fetch(`${new URL(serverAddress).origin}/amnet/info`, {\n signal: abort\n });\n \n if (!response.ok) {\n return null;\n }\n\n return {\n ...await response.json(),\n serverAddress: serverAddress\n } satisfies AMServerMetadata;\n } catch {\n return null;\n }\n}\n","export const CARD_ID_REGEX = /^(?:[0-9]{4} ?){5}$/i;\n\nexport function generateCardNumber(): string {\n let cardId = '';\n\n while (true) {\n // don't start with a 3\n const firstDigit = Math.floor(Math.random() * 10);\n\n if (firstDigit !== 3) {\n cardId = firstDigit.toString();\n break;\n }\n }\n\n for (let i = 0; i < 19; i++) {\n cardId += Math.floor(Math.random() * 10).toString();\n }\n\n return cardId;\n}\n\n/**\n * Validates the provided card id by known parameters\n * @param id The card id to validate\n */\nexport function validateCardId(id: string): boolean {\n return id[0] !== '3' && CARD_ID_REGEX.test(id);\n}\n","/**\n * Get the game name from the game identifier (4-letter code)\n * @param id\n */\nexport function getGameName(id: string): string | null {\n switch (id?.toUpperCase()) {\n case \"SDGT\":\n return \"Initial D The Arcade\";\n case \"SDHD\":\n case \"SDBT\":\n return \"CHUNITHM\";\n case \"SDDT\":\n return \"O.N.G.E.K.I.\";\n case \"SDEZ\":\n return \"maimai DX\";\n case \"SBZV\":\n return \"Project DIVA Future Tone\";\n \n default:\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGO,IAAM,oBAAoB;AAK1B,IAAM,wBAAwB;AAyCrC,SAAsB,cAAc,QAAgC,SAA0B,QAAuC;AAAA;AACjI,QAAI,OAAO,aAAa,yBAAyB,OAAO,aAAa,mBAAmB;AACpF,YAAM,IAAI,MAAM,gCAAgC;AAAA,IACpD;AAEA,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,aAAa,EAAE,MAAM,iBAAiB;AAAA,QACjF,QAAQ;AAAA,QACR,SAAS;AAAA,UACL,gBAAgB;AAAA,QACpB;AAAA,QACA,MAAM,KAAK,UAAU,OAAO;AAAA,QAC5B;AAAA,MACJ,CAAC;AAED,aAAO,SAAS;AAAA,IACpB,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;AAOA,SAAsB,gBAAgB,eAAuB,OAAkD;AAAA;AAC3G,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,IAAI,IAAI,aAAa,EAAE,MAAM,eAAe;AAAA,QACxE,QAAQ;AAAA,MACZ,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AACd,eAAO;AAAA,MACX;AAEA,aAAO,iCACA,MAAM,SAAS,KAAK,IADpB;AAAA,QAEH;AAAA,MACJ;AAAA,IACJ,SAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAAA;;;AC5FO,IAAM,gBAAgB;AAEtB,SAAS,qBAA6B;AACzC,MAAI,SAAS;AAEb,SAAO,MAAM;AAET,UAAM,aAAa,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAEhD,QAAI,eAAe,GAAG;AAClB,eAAS,WAAW,SAAS;AAC7B;AAAA,IACJ;AAAA,EACJ;AAEA,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AACzB,cAAU,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,EAAE,SAAS;AAAA,EACtD;AAEA,SAAO;AACX;AAMO,SAAS,eAAe,IAAqB;AAChD,SAAO,GAAG,CAAC,MAAM,OAAO,cAAc,KAAK,EAAE;AACjD;;;ACxBO,SAAS,YAAY,IAA2B;AACnD,UAAQ,yBAAI,eAAe;AAAA,IACvB,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AAAA,IACL,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IAEX;AACI,aAAO;AAAA,EACf;AACJ;","names":[]} \ No newline at end of file diff --git a/amnet-shared/src/api.ts b/amnet-shared/src/api.ts index 12c19da..1b90f75 100644 --- a/amnet-shared/src/api.ts +++ b/amnet-shared/src/api.ts @@ -3,12 +3,17 @@ */ export const AMNET_API_VERSION = 1; +/** + * The minimum version of the AMNet API this library supports + */ +export const MIN_AMNET_API_VERSION = 1; + export interface AMCardInRequest { cardId: string; } /** - * Information needed to perform a request against an AMNet server + * Server connection info (api version, address, etc.) */ export interface AMServerConnectionInfo { apiVersion: number; @@ -16,20 +21,37 @@ export interface AMServerConnectionInfo { } /** - * Received metadata from an AMNet server + * User-definable server metadata */ -export interface AMServerInfoResponse extends AMServerConnectionInfo { +export interface AMServerMetadata { serverName: string; gameId?: string | null | undefined; } +/** + * Metrics about the current game session + */ +export interface AMServerInstanceMetrics { + timeSinceLastPoll?: number; + sessionUptime?: number; +} + +/** + * The response from an AMNet server's info endpoint + */ +export type AMServerInfo = AMServerMetadata & AMServerConnectionInfo & AMServerInstanceMetrics; + /** * Performs the card-in request to the specified server * @param server The server to perform the request against * @param request The card-in request to send * @param signal The abort signal to use for the request */ -export async function performCardIn(server: AMServerConnectionInfo, request: AMCardInRequest, signal: AbortSignal) { +export async function performCardIn(server: AMServerConnectionInfo, request: AMCardInRequest, signal: AbortSignal): Promise { + if (server.apiVersion < MIN_AMNET_API_VERSION || server.apiVersion > AMNET_API_VERSION) { + throw new Error("Unsupported server API version"); + } + try { const response = await fetch(`${new URL(server.serverAddress).origin}/amnet/signin`, { method: "POST", @@ -51,16 +73,20 @@ export async function performCardIn(server: AMServerConnectionInfo, request: AMC * @param serverAddress The origin address of the server to connect to * @param abort The abort signal to use for the request (for cancellation) */ -export async function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise { +export async function fetchServerInfo(serverAddress: string, abort: AbortSignal): Promise { try { const response = await fetch(`${new URL(serverAddress).origin}/amnet/info`, { signal: abort }); + + if (!response.ok) { + return null; + } return { ...await response.json(), serverAddress: serverAddress - } satisfies AMServerInfoResponse; + } satisfies AMServerMetadata; } catch { return null; } diff --git a/amnet-web/src/lib/database.ts b/amnet-web/src/lib/database.ts index 77e171e..d981324 100644 --- a/amnet-web/src/lib/database.ts +++ b/amnet-web/src/lib/database.ts @@ -1,9 +1,9 @@ import Dexie, { type EntityTable } from 'dexie'; -import {AMServerInfoResponse} from "amnet-shared"; +import {AMServerConnectionInfo, AMServerMetadata} from "amnet-shared"; export const CARD_LIMIT = 15; -interface AimeServer extends AMServerInfoResponse { +interface AimeServer extends AMServerMetadata, AMServerConnectionInfo { id: number; lastConnected: number; }