diff --git a/src/apiutils.ts b/src/apiutils.ts index cb1aa8e..ef4a8aa 100644 --- a/src/apiutils.ts +++ b/src/apiutils.ts @@ -74,6 +74,15 @@ export const validateRequestBody = (schema: z.ZodSchema) => (rq: express.R rs.status(400).json(genericResponseFormat(true, "Bad request", undefined, error.errors)); } }; +export const validateQuery = (schema: z.ZodSchema) => (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => { + try { + schema.parse(rq.query); + nxt(); + } catch (error) { + if (error instanceof z.ZodError) + rs.status(400).json(genericResponseFormat(true, "Bad request", undefined, error.errors)); + } +}; type genericResponse = { failure: boolean, diff --git a/src/data/live/presence.ts b/src/data/live/presence.ts index c3012a1..1f031b4 100644 --- a/src/data/live/presence.ts +++ b/src/data/live/presence.ts @@ -6,7 +6,7 @@ import Logging from "@proxnet/undead-logging"; const log = new Logging("Presence"); -interface PresenceExport { +export interface PresenceExport { roomInstance: RoomInstance | null; playerId: number; statusVisibility: PlayerStatusVisibility; @@ -14,7 +14,6 @@ interface PresenceExport { vrMovementMode?: VRMovementMode; } -// Hot mess class PlayerPresence { intervalId: number; @@ -59,27 +58,21 @@ class PlayerPresence { if (visibilityResult.success) this.statusVisibility = visibilityResult.data; } - async updateEnums() { + async update() { + this.updateOffline(); if (!this.offline) await this.updateStatusVisibility(); else this.statusVisibility = PlayerStatusVisibility.Offline; - // deviceClass - const DeviceClassEnum = z.nativeEnum(DeviceClass); - type DeviceClassEnum = z.infer; + this.deviceClass = await this.#profile.getKnownDeviceClass(); - const deviceClassResult = DeviceClassEnum.safeParse(await this.#profile.getKnownDeviceClass()); - if (deviceClassResult.success) this.deviceClass = deviceClassResult.data; - - // vrMovementMode - const VRMovementModeEnum = z.nativeEnum(VRMovementMode); - type VRMovementModeEnum = z.infer; - - const vrMovementMoveResult = VRMovementModeEnum.safeParse(await this.#profile.getVRMovementMode()); - if (vrMovementMoveResult.success) this.vrMovementMode = vrMovementMoveResult.data; + this.vrMovementMode = await this.#profile.getVRMovementMode(); } + /** + * Export presence object. Please make sure to update values with `Presence.update()` (async) before calling this. + */ async export() { - await this.updateEnums(); + await this.update(); const exp: PresenceExport = { playerId: this.playerId, roomInstance: this.roomInstance, @@ -108,7 +101,10 @@ class PresenceBase { */ async getAllPresences() { const presSet: Set = new Set(); - for (const pres of presence.values()) presSet.add(await pres.export()); + for (const pres of presence.values()) { + await pres.update(); + presSet.add(await pres.export()); + } return presSet; } @@ -119,7 +115,7 @@ class PresenceBase { async create(player: Profile) { if (!presence.values().find(pres => pres.playerId == player.getId())) { const pres = new PlayerPresence(player); - await pres.updateEnums(); + await pres.update(); presence.add(pres); } log.d(`Presences: ${JSON.stringify(Array.from(await Presence.getAllPresences()))}`); @@ -130,7 +126,7 @@ class PresenceBase { if (pres) return pres; else { const pres = new PlayerPresence(player, true); - await pres.updateEnums(); + await pres.update(); presence.add(pres); return pres; } diff --git a/src/data/profile/progression.ts b/src/data/profile/progression.ts index efcb8ae..76d8f74 100644 --- a/src/data/profile/progression.ts +++ b/src/data/profile/progression.ts @@ -57,7 +57,7 @@ export class ProfileProgressionManager extends ProfileContentManager { if (xp >= item.RequiredXp) { const current = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item)]; - if (typeof current == 'undefined') return null; + if (typeof current == 'undefined') return 1; else return current.Level; } @@ -72,7 +72,9 @@ export class ProfileProgressionManager extends ProfileContentManager { const parsedData = parseInt(data); if (isNaN(parsedData)) { log.w(`Parsed xp data for ${this.profileId} is NaN!`); - return 0; // fallback since progression data is required + const one = config?.LevelProgressionMaps[1]; + if (typeof one == 'undefined' && !one) return 0; // fallback since progression data is required + else return one.RequiredXp; } else return parsedData; } diff --git a/src/data/profile/relationships.ts b/src/data/profile/relationships.ts new file mode 100644 index 0000000..5270199 --- /dev/null +++ b/src/data/profile/relationships.ts @@ -0,0 +1,64 @@ +import { Profile } from "../profiles.ts"; +import { ProfileContentManager } from "./base/profilemanagerbase.ts"; + +enum RelationshipType { + None, + FriendRequestSent, + FriendRequestReceived, + Friend +} + +enum ReciprocalStatus { + None, + Local, + Remote, + Mutual +} + +interface Relationship { + PlayerID: number, // target player, not this player + RelationshipType: RelationshipType, + Muted: ReciprocalStatus, + Ignored: ReciprocalStatus +} + +export class ProfileRelationshipManager extends ProfileContentManager { + + #baseRelationships: Relationship[] = [ + { + PlayerID: 1, + RelationshipType: RelationshipType.Friend, + Muted: ReciprocalStatus.None, + Ignored: ReciprocalStatus.None + }, + { + PlayerID: 2, + RelationshipType: RelationshipType.Friend, + Muted: ReciprocalStatus.None, + Ignored: ReciprocalStatus.None + } + ] + + getRelationships() { + return this.#baseRelationships; // temporary + } + + setPlayerIgnored(player: Profile) { + + } + + setPlayerMuted(player: Profile) { + + } + + getPlayerRelationship(player: Profile) { + + } + + sendPlayerFriendRequest(player: Profile) { + + } + + + +} \ No newline at end of file diff --git a/src/data/profile/reputation.ts b/src/data/profile/reputation.ts index 9fea0fc..3210586 100644 --- a/src/data/profile/reputation.ts +++ b/src/data/profile/reputation.ts @@ -5,9 +5,10 @@ export class ProfileReputationManager extends ProfileContentManager { async getReputation() { // async temporary return { AccountId: this.profileId, - Noterity: 0.0, + Noteriety: 0.0, CheerGeneral: 0, CheerHelpful: 0, + CheerGreatHost: 0, CheerSportsman: 0, CheerCreative: 0, CheerCredit: 0, diff --git a/src/data/profiles.ts b/src/data/profiles.ts index 4dbe4a3..5493772 100644 --- a/src/data/profiles.ts +++ b/src/data/profiles.ts @@ -12,6 +12,7 @@ import { ProfileSettingsManager } from "./profile/settings.ts"; import { ProfileProgressionManager } from "./profile/progression.ts"; import { ProfileReputationManager } from "./profile/reputation.ts"; import Logging from "@proxnet/undead-logging"; +import { ProfileRelationshipManager } from "./profile/relationships.ts"; const config = Config.getConfig(); @@ -19,11 +20,17 @@ const log = new Logging("Profiles"); interface ProfileInitOptions { username: string; + /** + * Ignore the random generation of profile IDs; use this ID. + * + * Make sure the target account does not exist before using this option. + */ + id?: number; } interface AccountExport { accountId: number; profileImage: string; - isJunior: boolean; + isJunior?: boolean; platforms: number; username: string; displayName: string; @@ -34,6 +41,8 @@ export interface ProfileTokenFormat extends TokenBaseFormat { typ: AuthType.Game; } +const reservedIds = [1, 2]; + class Profile { static async exists(id: number) { return (await Redis.Database.exists( @@ -44,6 +53,11 @@ class Profile { ), )) >= 1; } + static async existsByName(name: string) { + return (await Redis.Database.exists( + Redis.buildKey(Redis.KeyGroups.Profile_Usernames, name), + )) >= 1; + } static async getUniqueId() { let id = Math.round(Math.random() * Math.pow(2, 31)); @@ -55,18 +69,15 @@ class Profile { Redis.KeyGroups.Profiles.Username, ), )) >= 1 - ) { - id = await this.getUniqueId(); - } + || reservedIds.includes(id) + ) id = await this.getUniqueId(); return id; } static async byName(name: string) { - const id = await Redis.Database.get( - Redis.buildKey(Redis.KeyGroups.Profile_Usernames, name), - ); + const id = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profile_Usernames, name)); if (id == null) return null; - else return new Profile(parseInt(id, 10)); + else return new Profile(parseInt(id)); } static async getUniqueUsername() { @@ -83,12 +94,11 @@ class Profile { static async init(options?: ProfileInitOptions) { const optionsSpecified = typeof options !== "undefined"; - if (options?.username) { - const existingUser = await Profile.byName(options.username); - if (existingUser == null) return null; - } + if (options?.username && await Profile.existsByName(options.username)) return null; - const newId = await this.getUniqueId(); + let newId: number; + if (options?.id) newId = options.id; + else newId = await this.getUniqueId(); const newUsername = optionsSpecified ? options.username : await this.getUniqueUsername(); @@ -153,6 +163,7 @@ class Profile { Settings = new ProfileSettingsManager(); Progression = new ProfileProgressionManager(); Reputation = new ProfileReputationManager(); + Relationships = new ProfileRelationshipManager(); constructor(id: number) { this.#id = id; @@ -161,6 +172,7 @@ class Profile { this.Settings.setProfile(this.#id); this.Progression.setProfile(this.#id); this.Reputation.setProfile(this.#id); + this.Relationships.setProfile(this.#id); } setInstance(instance: RoomInstance | null) { @@ -183,11 +195,13 @@ class Profile { return await Profile.getExportAccount(this.#id); } - async setKnownDeviceClass(deviceClass: string | number) { + async setKnownDeviceClass(deviceClass: DeviceClass) { await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.#id.toString(), Redis.KeyGroups.Profiles.DeviceClass), deviceClass); } async getKnownDeviceClass() { + if (reservedIds.includes(this.#id)) return DeviceClass.Unknown; + const data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.#id.toString(), Redis.KeyGroups.Profiles.DeviceClass)); if (data == null) { log.w(`No known device class for ${this.#id}`); @@ -212,14 +226,16 @@ class Profile { } async getVRMovementMode() { + if (reservedIds.includes(this.#id)) return VRMovementMode.Teleport; + const data = await this.Settings.getSetting(SettingKey.VRMovementMode); if (data == null) { - log.w(`No known device class for ${this.#id}`); + log.w(`No known VR movement mode for ${this.#id} (harmless if OOBE not ran)`); return VRMovementMode.Teleport; } const parsedData = parseInt(data); if (isNaN(parsedData)) { - log.w(`Malformed device class for ${this.#id}`); + log.w(`Malformed VR movement mode for ${this.#id}`); return VRMovementMode.Teleport; } @@ -258,6 +274,7 @@ class Profile { const profiles: Map = new Map() +// Control what is available to references class UnifiedProfileBase { get(id: number) { @@ -278,6 +295,10 @@ class UnifiedProfileBase { return await Profile.exists(id); } + async existsByName(name: string) { + return await Profile.existsByName(name); + } + } const UnifiedProfile = new UnifiedProfileBase(); diff --git a/src/main.ts b/src/main.ts index a90fba9..810e97e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -188,8 +188,8 @@ try { PLACE TEST HERE */ - if (!(await UnifiedProfile.exists(1))) UnifiedProfile.create({ username: "Coach" }); // create Coach if they do not exist - if (!(await UnifiedProfile.exists(2))) UnifiedProfile.create({ username: "Server" }); // create Server if they do not exist + if (!(await UnifiedProfile.existsByName("Coach"))) UnifiedProfile.create({ username: "Coach", id: 1 }); // create Coach id 1 if they do not exist + if (!(await UnifiedProfile.existsByName("Server"))) UnifiedProfile.create({ username: "Server", id: 2 }); // create Server id 2 if they do not exist }); diff --git a/src/routes/account/account.ts b/src/routes/account/account.ts index 5d249ea..8a25b63 100644 --- a/src/routes/account/account.ts +++ b/src/routes/account/account.ts @@ -28,6 +28,12 @@ route.router.post("/create", async (_rq, rs) => { const newAcc = await Profile.init(); + if (newAcc == null) { + rs.json({ + success: false + }); + return; + } rs.locals.user.addAssociatedProfile(newAcc.getId()); diff --git a/src/routes/api/config.ts b/src/routes/api/config.ts index fd11dd1..2306071 100644 --- a/src/routes/api/config.ts +++ b/src/routes/api/config.ts @@ -3,7 +3,9 @@ import { GameConfigs } from "../../data/config.ts"; export const route = APIUtils.createRouter("/config"); -route.router.get("/v2", (_rq, rs) => { +const rateLimit = new APIUtils.RateLimiter(60, 2); + +route.router.get("/v2", rateLimit.middle(), (_rq, rs) => { const config = GameConfigs.getConfig(); if (config == null) rs.sendStatus(500); else rs.json(config); @@ -11,6 +13,7 @@ route.router.get("/v2", (_rq, rs) => { route.router.get('/v1/amplitude', APIUtils.setCacheAllowed, + rateLimit.middle(), (_rq, rs) => { rs.json({AmplitudeKey: ""}); } diff --git a/src/routes/api/playerReputation.ts b/src/routes/api/playerReputation.ts index a1ad0f0..bcb962e 100644 --- a/src/routes/api/playerReputation.ts +++ b/src/routes/api/playerReputation.ts @@ -10,7 +10,7 @@ route.router.get('/v1/:id', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), - (rq: express.Request<{ id: string }>, rs) => { + async (rq: express.Request<{ id: string }>, rs) => { const unparsedPlayerId = rq.params.id; const parsedPlayerId = parseInt(unparsedPlayerId); if (isNaN(parsedPlayerId)) { @@ -18,7 +18,7 @@ route.router.get('/v1/:id', return; } - rs.json(UnifiedProfile.get(parsedPlayerId).Reputation.getReputation()); + rs.json(await UnifiedProfile.get(parsedPlayerId).Reputation.getReputation()); } ); \ No newline at end of file diff --git a/src/routes/api/players.ts b/src/routes/api/players.ts index 36a649c..ea74499 100644 --- a/src/routes/api/players.ts +++ b/src/routes/api/players.ts @@ -5,11 +5,13 @@ import UnifiedProfile from "../../data/profiles.ts"; const log = new Logging("ProgressionRoute"); +const rateLimit = new APIUtils.RateLimiter(60, 2); + export const route = APIUtils.createRouter("/players"); route.router.get('/v1/progression/:id', - APIUtils.Authentication, + rateLimit.middle(), async (rq: express.Request<{ id: string }>, rs) => { const unparsedPlayerId = rq.params.id; diff --git a/src/routes/api/relationships.ts b/src/routes/api/relationships.ts index f868e09..22aa2c2 100644 --- a/src/routes/api/relationships.ts +++ b/src/routes/api/relationships.ts @@ -9,7 +9,7 @@ route.router.get('/v2/get', APIUtils.AuthenticationType(AuthType.Game), (_rq, rs) => { - rs.json([]); // temporary + rs.json(rs.locals.profile.Relationships.getRelationships()); } ); \ No newline at end of file diff --git a/src/routes/auth/connect.ts b/src/routes/auth/connect.ts index b97d28e..5a2e3a1 100644 --- a/src/routes/auth/connect.ts +++ b/src/routes/auth/connect.ts @@ -98,6 +98,7 @@ route.router.post("/token", rq.body.platform === "0", rq.body.ver === '20191120', rq.body.device_class.length === 1, + !isNaN(Number(rq.body.device_class)), !(rq.body.device_id.length > 96), !(rq.body.client_secret.length > 96), !(rq.body.platform_id.length > 32), @@ -110,9 +111,8 @@ route.router.post("/token", return; } - const accounts = await rs.locals.user.getAssociatedProfiles(); let targetAccount: number; - + if (rq.body.grant_type == 'cached_login') targetAccount = parseInt(rq.body.account_id); else { const refreshToken = rq.body.refresh_token; @@ -128,15 +128,17 @@ route.router.post("/token", requestFailed(); return; } - + targetAccount = parseInt(decodedToken.sub ? decodedToken.sub : "NaN"); - + } - + if (isNaN(targetAccount)) { requestFailed(); return; } + + const accounts = await rs.locals.user.getAssociatedProfiles(); if (!accounts.has(targetAccount)) { requestFailed("access_denied"); return; @@ -160,6 +162,6 @@ route.router.post("/token", refresh_token: token, }); - await profile.setKnownDeviceClass(rq.body.device_class); + await profile.setKnownDeviceClass(Number(rq.body.device_class)); }, ); diff --git a/src/routes/match/player.ts b/src/routes/match/player.ts index 232cc94..98dae71 100644 --- a/src/routes/match/player.ts +++ b/src/routes/match/player.ts @@ -2,9 +2,10 @@ import { z } from "zod"; import { APIUtils, NoBody } from "../../apiutils.ts"; import express from "express"; import Matchmaking from "../../data/live/base.ts"; -import Presence from "../../data/live/presence.ts"; +import Presence, { PresenceExport } from "../../data/live/presence.ts"; import { AuthType } from "../../data/users.ts"; import Logging from "@proxnet/undead-logging"; +import UnifiedProfile from "../../data/profiles.ts"; const log = new Logging("MatchPlayerRoute"); @@ -18,6 +19,31 @@ const LoginSchema = z.object({ LoginLock: z.string().uuid("LoginLock must be a UUIDv4") }); +route.router.get('/', + + APIUtils.Authentication, + APIUtils.AuthenticationType(AuthType.Game), + APIUtils.validateQuery(z.object({ id: z.union([z.string(), z.array(z.string())]) })), + + async (rq: express.Request, rs) => { + let ids: number[] = []; + if (typeof rq.query.id == 'object') ids = rq.query.id.map(val => parseInt(val)); + else ids.push(parseInt(rq.query.id)); + ids = ids.filter(val => !isNaN(val)); + + const presExport: PresenceExport[] = []; + for (const id of ids) { + const pres = await Presence.get(UnifiedProfile.get(id)); + await pres.update(); + presExport.push(await pres.export()); + } + + rs.json(presExport); + log.d(JSON.stringify(presExport)); + } + +) + route.router.post('/login', APIUtils.Authentication, @@ -40,8 +66,9 @@ route.router.post('/logout', express.urlencoded({extended: true}), APIUtils.validateRequestBody(LoginSchema), - (rq, rs) => { + (_rq, rs) => { Matchmaking.deleteLoginLock(rs.locals.profile); + rs.sendStatus(200); } ) @@ -56,6 +83,7 @@ route.router.post('/heartbeat', async (_rq, rs) => { const pres = await Presence.get(rs.locals.profile); + await pres.update(); log.d(`pres heartbeat for ${rs.locals.profile.getId()}: ${JSON.stringify(await pres.export())}`); rs.json(await pres.export()); } diff --git a/src/socket/route.ts b/src/socket/route.ts index c454167..e96dfda 100644 --- a/src/socket/route.ts +++ b/src/socket/route.ts @@ -2,7 +2,6 @@ import { APIUtils } from "../apiutils.ts"; import { Config } from "../config.ts"; import { AuthType } from "../data/users.ts"; import { SocketHandoff } from "./handoff.ts"; -import express from "express"; const config = Config.getConfig(); @@ -12,8 +11,6 @@ route.router.post('/hub/v1/negotiate', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), - express.urlencoded({ extended: true }), - APIUtils.logBody, (_rq, rs) => { const handoff = new SocketHandoff(); diff --git a/src/socket/socket.ts b/src/socket/socket.ts index 9b187d2..d1eb323 100644 --- a/src/socket/socket.ts +++ b/src/socket/socket.ts @@ -1,6 +1,18 @@ import { Profile } from "../data/profiles.ts"; import Logging from "@proxnet/undead-logging"; -import { Message, MessageKind, SignalMessageType, SignalRMessage, SignalRMessageSchema, TargetResult, TargetResultFailure, TargetResultSuccess, TargetResultType } from "./types.ts"; +import { + CompletionMessage, + Message, + MessageKind, + SignalMessageType, + SignalRMessage, + SignalRMessageSchema, + TargetResult, + TargetResultFailure, + TargetResultNotATarget, + TargetResultSuccess, + TargetResultType +} from "./types.ts"; import { SocketTarget } from "./targets/targetbase.ts"; import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts"; @@ -26,22 +38,55 @@ export class SignalRSocketHandler { } - async #dispatchTarget(target: string, args: object[]): Promise { + async #dispatchTarget(target: string, args: unknown): Promise { const targetExec = this.#Targets.get(target); - if (!targetExec) return { type: TargetResultType.Failure } as TargetResultFailure; - else return { type: TargetResultType.Success, data: await targetExec.exec(args) } as TargetResultSuccess; + if (!targetExec) return { type: TargetResultType.NotATarget } as TargetResultNotATarget; + else { + try { + return { type: TargetResultType.Success, data: await targetExec.exec(args) } as TargetResultSuccess; + } catch (err) { + this.#log.w(`Target '${target}' function error: ${err}`); + if (err instanceof Error) return { type: TargetResultType.Failure, err: err } as TargetResultFailure; + else return { type: TargetResultType.Failure, err: `${err}` } as TargetResultFailure; + } + } } - #onMessage(message: Message) { + async #onMessage(message: Message) { if (message.kind == MessageKind.Protocol) { this.sendRaw({}); return; } else { this.#log.d(`CLIENT MESSAGE\n Type: ${message.data.type} (${SignalMessageType[message.data.type]})\n ${JSON.stringify(message.data)}`); + if (message.data.type == SignalMessageType.Invocation && message.data.invocationId) { // don't send completion messages for nonblocking invocations + const res = await this.#dispatchTarget(message.data.target, message.data.arguments[0]); // rec room only uses the first index + if (res.type == TargetResultType.Success) { + const signalRes: CompletionMessage = { + type: SignalMessageType.Completion, + invocationId: message.data.invocationId, + result: JSON.stringify(res.data) + } + this.sendRaw(signalRes); + } else if (res.type == TargetResultType.Failure) { + const signalRes: CompletionMessage = { + type: SignalMessageType.Completion, + invocationId: message.data.invocationId, + error: res.err instanceof Error ? res.err.message : res.err + } + this.sendRaw(signalRes); + } else { + const signalRes: CompletionMessage = { + type: SignalMessageType.Completion, + invocationId: message.data.invocationId, + error: "Target not found" + } + this.sendRaw(signalRes); + } + } } } - async #init() { + #init() { this.#log.source += this.#profile.getId().toString(); this.#log.i(`Created hub socket`); @@ -82,6 +127,9 @@ export class SignalRSocketHandler { sendRaw(data: object) { this.#socket.send(`${JSON.stringify(data)}\u001e`); + // todo sometime: make this less confusing + const type = `Type: ${JSON.stringify(data) == '{}' ? 'Protocol Message' : `${(data as SignalRMessage).type} (${SignalMessageType[(data as SignalRMessage).type]})`}`; + this.#log.d(`SERVER MESSAGE\n ${type}\n ${JSON.stringify(data as SignalRMessage)}`); } } \ No newline at end of file diff --git a/src/socket/targets/SubscribeToPlayers.ts b/src/socket/targets/SubscribeToPlayers.ts index 1a2593e..e62c63f 100644 --- a/src/socket/targets/SubscribeToPlayers.ts +++ b/src/socket/targets/SubscribeToPlayers.ts @@ -1,5 +1,10 @@ +import { z } from "zod"; import { SocketTarget } from "./targetbase.ts"; +const ArgumentSchema = z.object({ + PlayerIds: z.array(z.number()) +}); + export class PlayerSocketSubscriptionTarget extends SocketTarget { subscriptions: number[] = []; @@ -9,8 +14,12 @@ export class PlayerSocketSubscriptionTarget extends SocketTarget { } // deno-lint-ignore require-await - override async exec(_args: (object | string | number | boolean)[]) { - return; + override async exec(args: unknown) { + const parsed = ArgumentSchema.safeParse(args); + if (parsed.success) { + this.setSubscriptions(parsed.data.PlayerIds); + return; + } else throw new Error("Invalid arguments"); } } \ No newline at end of file diff --git a/src/socket/targets/targetbase.ts b/src/socket/targets/targetbase.ts index 5756552..ff0a972 100644 --- a/src/socket/targets/targetbase.ts +++ b/src/socket/targets/targetbase.ts @@ -13,7 +13,7 @@ export class SocketTarget { } // deno-lint-ignore require-await - async exec(_args: (object | string | number | boolean)[]) { + async exec(_args: unknown) { throw new Error("Execution for this target is not set."); } diff --git a/src/socket/types.ts b/src/socket/types.ts index 471ee90..4795419 100644 --- a/src/socket/types.ts +++ b/src/socket/types.ts @@ -4,14 +4,14 @@ export enum MessageKind { Protocol, Data } -interface MessageBase { +export interface MessageBase { kind: MessageKind } -interface DataMessage extends MessageBase { +export interface DataMessage extends MessageBase { kind: MessageKind.Data, data: SignalRMessage } -interface ProtocolMessage extends MessageBase { +export interface ProtocolMessage extends MessageBase { kind: MessageKind.Protocol } export type Message = ProtocolMessage | DataMessage; @@ -34,68 +34,68 @@ export enum SignalMessageType { Close } -interface BaseMessage { +export interface BaseMessage { type: SignalMessageType; } -interface InvocationMessage extends BaseMessage { +export interface InvocationMessage extends BaseMessage { type: SignalMessageType.Invocation; target: string; arguments: unknown[]; invocationId?: string; } -interface StreamItemMessage extends BaseMessage { +export interface StreamItemMessage extends BaseMessage { type: SignalMessageType.StreamItem; invocationId: string; item: unknown; } -interface CompletionMessage extends BaseMessage { +export interface CompletionMessage extends BaseMessage { type: SignalMessageType.Completion; invocationId: string; result?: unknown; error?: string; } -interface PingMessage extends BaseMessage { +export interface PingMessage extends BaseMessage { type: SignalMessageType.Ping; } -interface CloseMessage extends BaseMessage { +export interface CloseMessage extends BaseMessage { type: SignalMessageType.Close; error?: string; } -const BaseMessageSchema = z.object({ +export const BaseMessageSchema = z.object({ type: z.nativeEnum(SignalMessageType), }); -const InvocationMessageSchema = BaseMessageSchema.extend({ +export const InvocationMessageSchema = BaseMessageSchema.extend({ type: z.literal(SignalMessageType.Invocation), target: z.string(), arguments: z.array(z.unknown()), invocationId: z.string().optional(), }); -const StreamItemMessageSchema = BaseMessageSchema.extend({ +export const StreamItemMessageSchema = BaseMessageSchema.extend({ type: z.literal(SignalMessageType.StreamItem), invocationId: z.string(), item: z.unknown(), }); -const CompletionMessageSchema = BaseMessageSchema.extend({ +export const CompletionMessageSchema = BaseMessageSchema.extend({ type: z.literal(SignalMessageType.Completion), invocationId: z.string(), result: z.unknown().optional(), error: z.string().optional(), }); -const PingMessageSchema = BaseMessageSchema.extend({ +export const PingMessageSchema = BaseMessageSchema.extend({ type: z.literal(SignalMessageType.Ping), }); -const CloseMessageSchema = BaseMessageSchema.extend({ +export const CloseMessageSchema = BaseMessageSchema.extend({ type: z.literal(SignalMessageType.Close), error: z.string().optional(), }); @@ -113,7 +113,7 @@ export enum TargetResultType { Failure, NotATarget } -interface TargetResultBase { +export interface TargetResultBase { type: TargetResultType } export interface TargetResultSuccess extends TargetResultBase { @@ -122,6 +122,7 @@ export interface TargetResultSuccess extends TargetResultBase { } export interface TargetResultFailure extends TargetResultBase { type: TargetResultType.Failure + err: string | Error } export interface TargetResultNotATarget extends TargetResultBase { type: TargetResultType.NotATarget