WizardConnect/packages/core/src/protocols/base.ts
2026-03-06 11:38:09 +01:00

95 lines
3.5 KiB
TypeScript

// Copyright (C) 2026 Whiterun LLC,
// This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later.
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/lgpl-3.0.html
/// Base protocol — DappReadyMessage and WalletReadyMessage
///
/// These are protocol-agnostic handshake messages shared across all
/// application-level protocols (hdwalletv1, future hdwalletv2, etc.).
///
/// The wallet populates `session` for every protocol it supports, keyed by
/// protocol name. The dapp selects the first protocol from its own
/// `supported_protocols` list that is also present in the wallet's list,
/// then reads `session[selectedProtocol]` for protocol-specific data.
import {
RelayMsgAction,
ProtocolMessage,
isProtocolMessage,
} from "./hdwalletv1.js";
export interface DappReadyMessage extends ProtocolMessage {
action: RelayMsgAction.DappReady;
/// Protocols this dapp supports, in preference order.
supported_protocols: string[];
/// Set on the reactive dapp_ready (after seeing wallet_ready).
/// Absent on the proactive send (before the dapp has seen the wallet).
selected_protocol?: string;
/// True if the dapp has already seen this wallet in the current runtime session.
wallet_discovered: boolean;
dapp_name?: string;
dapp_icon?: string;
}
export interface WalletReadyMessage extends ProtocolMessage {
action: RelayMsgAction.WalletReady;
wallet_name: string;
wallet_icon: string;
/// True if the wallet has already seen this dapp in the current runtime session.
/// The dapp uses this to skip sending a reactive dapp_ready when already known.
dapp_discovered: boolean;
/// Protocols this wallet supports.
supported_protocols: string[];
/// Session data keyed by protocol name.
/// e.g. { "hdwalletv1": { paths: PathXpub[], next_indices: NextIndex[] } }
/// The dapp picks the first compatible protocol and reads session[selectedProtocol].
session: Record<string, unknown>;
/// Wallet's Nostr x-only public key (hex, 32 bytes). Replaces the old key_exchange_response.
public_key: string;
/// Echo of the shared secret from the connection URI (hex, 8 bytes). MITM prevention.
secret: string;
}
export function isDappReadyMessage(msg: unknown): msg is DappReadyMessage {
return (
isProtocolMessage(msg) &&
msg.action === RelayMsgAction.DappReady &&
Array.isArray((msg as DappReadyMessage).supported_protocols) &&
typeof (msg as DappReadyMessage).wallet_discovered === "boolean"
);
}
export function isWalletReadyMessage(msg: unknown): msg is WalletReadyMessage {
const m = msg as WalletReadyMessage;
return (
isProtocolMessage(msg) &&
msg.action === RelayMsgAction.WalletReady &&
typeof m.wallet_name === "string" &&
typeof m.wallet_icon === "string" &&
typeof m.dapp_discovered === "boolean" &&
Array.isArray(m.supported_protocols) &&
m.session !== null &&
typeof m.session === "object" &&
typeof m.public_key === "string" &&
typeof m.secret === "string"
);
}
export enum DisconnectReason {
ProtocolMismatch = "protocol_mismatch",
UserDisconnect = "user_disconnect",
}
export interface DisconnectMessage extends ProtocolMessage {
action: RelayMsgAction.Disconnect;
reason: DisconnectReason;
message?: string;
}
export function isDisconnectMessage(msg: unknown): msg is DisconnectMessage {
return (
isProtocolMessage(msg) &&
(msg as DisconnectMessage).action === RelayMsgAction.Disconnect &&
typeof (msg as DisconnectMessage).reason === "string"
);
}