diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index cfd009b..17b077b 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `isValidConnectionMode()` and `CONNECTION_MODES` for runtime validation of connection mode values + ### Changed - Add `validatePeerKey` method to `IKeyManager` interface for peer public key validation at handshake and resume time ([#70](https://github.com/MetaMask/mobile-wallet-protocol/pull/70)) diff --git a/packages/core/src/domain/connection-mode.ts b/packages/core/src/domain/connection-mode.ts index 6697c90..fee6657 100644 --- a/packages/core/src/domain/connection-mode.ts +++ b/packages/core/src/domain/connection-mode.ts @@ -3,4 +3,9 @@ * 'trusted': A streamlined flow for same-device or trusted contexts that bypasses OTP. * 'untrusted': The high-security flow requiring user verification via OTP. */ -export type ConnectionMode = "trusted" | "untrusted"; +export const CONNECTION_MODES = ["trusted", "untrusted"] as const; +export type ConnectionMode = (typeof CONNECTION_MODES)[number]; + +export function isValidConnectionMode(value: unknown): value is ConnectionMode { + return typeof value === "string" && CONNECTION_MODES.includes(value as ConnectionMode); +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7de7f7a..9a550b3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,6 @@ export { BaseClient } from "./base-client"; export { ClientState } from "./domain/client-state"; -export type { ConnectionMode } from "./domain/connection-mode"; +export { CONNECTION_MODES, type ConnectionMode, isValidConnectionMode } from "./domain/connection-mode"; export { CryptoError, ErrorCode, ProtocolError, SessionError, TransportError } from "./domain/errors"; export type { IKeyManager } from "./domain/key-manager"; export type { KeyPair } from "./domain/key-pair"; diff --git a/packages/dapp-client/src/client.ts b/packages/dapp-client/src/client.ts index d76c084..edca1e6 100644 --- a/packages/dapp-client/src/client.ts +++ b/packages/dapp-client/src/client.ts @@ -7,6 +7,7 @@ import { type IKeyManager, type ISessionStore, type ITransport, + isValidConnectionMode, type Message, type ProtocolMessage, type Session, @@ -107,9 +108,11 @@ export class DappClient extends BaseClient { */ public async connect(options: DappConnectOptions = {}): Promise { if (this.state !== ClientState.DISCONNECTED) throw new SessionError(ErrorCode.SESSION_INVALID_STATE, `Cannot connect when state is ${this.state}`); - this.state = ClientState.CONNECTING; const { mode = "untrusted", initialPayload } = options; + if (!isValidConnectionMode(mode)) throw new SessionError(ErrorCode.SESSION_INVALID_STATE, `Invalid connection mode: "${String(mode)}"`); + + this.state = ClientState.CONNECTING; const { pendingSession, request } = this._createPendingSessionAndRequest(mode, initialPayload); this.session = pendingSession; this.emit("session_request", request); diff --git a/packages/wallet-client/src/client.ts b/packages/wallet-client/src/client.ts index 1187a26..aa723bf 100644 --- a/packages/wallet-client/src/client.ts +++ b/packages/wallet-client/src/client.ts @@ -6,6 +6,7 @@ import { type IKeyManager, type ISessionStore, type ITransport, + isValidConnectionMode, type ProtocolMessage, type Session, SessionError, @@ -77,11 +78,13 @@ export class WalletClient extends BaseClient { */ public async connect(options: { sessionRequest: SessionRequest }): Promise { if (this.state !== ClientState.DISCONNECTED) throw new SessionError(ErrorCode.SESSION_INVALID_STATE, `Cannot connect when state is ${this.state}`); - this.state = ClientState.CONNECTING; const request = options.sessionRequest; + if (!isValidConnectionMode(request.mode)) throw new SessionError(ErrorCode.SESSION_INVALID_STATE, `Invalid connection mode: "${String(request.mode)}"`); if (Date.now() > request.expiresAt) throw new SessionError(ErrorCode.REQUEST_EXPIRED, "Session request expired"); + this.state = ClientState.CONNECTING; + const self = this; const context: IConnectionHandlerContext = { transport: this.transport,