API Reference — SDK Type Declarations
Auto-generated from
sdk-typescript/dist/index.d.ts(1496 lines). Do not edit this file — update the SDK source and rebuild.
typescript
/**
* keys/index.ts - SDK 密钥体系
*
* 架构设计 §1.3.1 HD 派生路径规范:
* - m/44'/0'/0'/0/0 → Ed25519(身份认证/签名)
* - m/44'/1'/0'/0/0 → X25519(ECDH 消息加密)
*
* 依赖:
* - @scure/bip39:助记词生成/验证
* - @noble/curves/ed25519:Ed25519 签名
* - @noble/curves/x25519:X25519 ECDH
* - @noble/hashes/sha512:PBKDF KDF
*/
interface KeyPair {
privateKey: Uint8Array;
publicKey: Uint8Array;
}
interface Identity {
mnemonic: string;
/** Ed25519 身份密钥,用于 Challenge-Response 认证 */
signingKey: KeyPair;
/** X25519 ECDH 密钥,用于消息会话密钥协商 */
ecdhKey: KeyPair;
}
/** 生成 12 词英文助记词 */
declare function newMnemonic(): string;
/** 验证助记词是否合法(12 词,BIP-39 词库)*/
declare function validateMnemonicWords(mnemonic: string): boolean;
/**
* 从助记词完整派生 Identity(包含两对密钥)
*/
declare function deriveIdentity(mnemonic: string): Identity;
/**
* ECDH 密钥协商:根据己方私钥和对方 X25519 公钥计算 SharedSecret
* SharedSecret 再经 KDF 得到 32 字节 AES-256 会话密钥
*/
declare function computeSharedSecret(myPrivateKey: Uint8Array, theirPublicKey: Uint8Array): Uint8Array;
/**
* 计算 60 字符安全码
* 算法:SHA-256(min(pubA, pubB) ‖ max(pubA, pubB))[0..30] → hex
* 双方使用相同的确定性拼接顺序,MITM 无法伪造一致结果
*/
declare function computeSecurityCode(myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): string;
/**
* formatSecurityCode — 把 60 hex 字符切成 5 字符 × 12 组的展示格式
*
* 用户看到的形式: "a3f2c 8b91d 4567a ..." (12 组用空格分隔)
* 用于"高级模式: 完整 60 位指纹"展示, 主流程已改用 directional code(见下)。
*/
declare function formatSecurityCode(hex60: string): string;
/**
* normalizeSecurityCode — 比对前归一化用户输入
*
* 用户从外部粘贴的码可能含: 空格 / 换行 / Tab / 中文括号 / 标点污染。
* 归一化只保留 hex 字符 (0-9 a-f), 转小写。用于完整 60 位指纹模式。
*/
declare function normalizeSecurityCode(input: string): string;
/**
* 计算单方向核对码 (Base32 编码 80 bits, 16 字符 4-4-4-4 分组显示)
*
* @param sharedSecret X25519 ECDH 共享密钥 (32 字节)
* @param fromPub 发送方 X25519 公钥 (32 字节)
* @param toPub 接收方 X25519 公钥 (32 字节)
* @returns 例如 "5KQF-2T3X-NMRE-W8YA"
*/
declare function computeDirectionalCode(sharedSecret: Uint8Array, fromPub: Uint8Array, toPub: Uint8Array): string;
/**
* 归一化用户输入的 directional code (去除空格/横线/转大写, 只保留 base32 字符)
* 用于核对前的输入清洗。
*/
declare function normalizeDirectionalCode(input: string): string;
/**
* toBase64 — 安全 Base64 编码
*
* 老实现 `btoa(String.fromCharCode(...bytes))` 在大 Uint8Array(>~100KB)上
* 会触发 "Maximum call stack size exceeded"(spread 参数过多)。
* 新实现分片处理 + 合并,支持任意大小。
*/
declare function toBase64(bytes: Uint8Array): string;
/**
* fromBase64 — 严格 Base64 解码
*
* 校验输入是合法 base64(字符集 + 长度是 4 的倍数)。非法输入会抛出
* 而不是静默产生 NaN 字节(老实现对非法字符会 produce Uint8Array with NaN)。
*/
declare function fromBase64(b64: string): Uint8Array;
declare function toHex(bytes: Uint8Array): string;
declare function fromHex(hex: string): Uint8Array;
/**
* keys/store.ts - T-012 IndexedDB 三存储持久化
* 关键数据结构:identity / sessions / offlineInbox
*/
interface StoredIdentity {
uuid: string;
aliasId: string;
nickname: string;
mnemonic: string;
signingPublicKey: string;
ecdhPublicKey: string;
}
/**
* trustState 三档 (1.0.36 强制双边密钥核对):
* - 'unverified': 默认 / 冷启动 / reset 后, 用户从未本地核对成功 (或 IDB 已被清)
* - 'my_side_verified': 用户已在 KeyVerificationModal 比对成功并调过 verify-mark, 但对方还没核对
* - 'verified': 双方都已完成核对 (服务端 NATS friend_verified 触发或 syncFriends 看到 verified_at NOT NULL)
*
* 状态机约束: unverified → verified 必须经过 my_side_verified
* 单独的 NATS 通知不能让 unverified 跳到 verified (防服务端 root 伪造)
*
* SDK gate:
* - unverified / my_side_verified: sendMessage 抛 UNVERIFIED_SESSION, handleIncomingMsg 入 quarantine
* - verified: 正常发收
*/
type SessionTrustState = 'unverified' | 'my_side_verified' | 'verified';
interface SessionRecord {
conversationId: string;
theirAliasId: string;
theirEcdhPublicKey: string;
theirEd25519PublicKey?: string;
sessionKeyBase64: string;
trustState: SessionTrustState;
friendshipId?: number;
createdAt: number;
}
/**
* QuarantinedMessage — 未核对会话收到的密文, 等核对完成后批量重新解密。
* 关键: 不立即解密展示, 因为如果服务端 MITM, 密文是用假 sessionKey 加密的, 内容不可信。
*/
interface QuarantinedMessage {
conversationId: string;
msgId: string;
seq: number;
fromAliasId: string;
payloadEncrypted: string;
envelope: string;
receivedAt: number;
}
declare function loadIdentity(): Promise<StoredIdentity | undefined>;
declare function clearIdentity(): Promise<void>;
declare function loadSession(conversationId: string): Promise<SessionRecord | undefined>;
declare function listSessions(): Promise<SessionRecord[]>;
declare function deleteSession(conversationId: string): Promise<void>;
declare function markSessionVerified(conversationId: string): Promise<void>;
/**
* 用户在 KeyVerificationModal 本地比对成功后调。
* 注: 不直接升 verified, 必须等服务端 NATS friend_verified 通知才升级。
*/
declare function markSessionMyVerified(conversationId: string): Promise<void>;
/**
* 服务端 NATS friend_verified 通知或 syncFriends 看到 verified_at NOT NULL 时调。
* 关键约束: 必须本地已是 my_side_verified 才能升 verified, 否则忽略 (防服务端 root 伪造)。
* 返回是否真的升级了。
*/
declare function maybeMarkSessionVerified(conversationId: string): Promise<boolean>;
/**
* Reset 会话到未核对状态。
* 用户主动重新核对或服务端 NATS friend_verify_reset 触发。
* quarantine 保留 (重新核对成功后还能解密老密文)。
*/
declare function resetSessionTrust(conversationId: string): Promise<void>;
/**
* 用户在 KeyVerificationModal 本地比对成功后调。
* 1) 本地 trustState 升级到 my_side_verified (写 IDB)
* 2) 通知服务端 verify-mark
*
* 注: 不直接升 verified, 必须等服务端 NATS friend_verified 才升级。
* 这是 spec §4.5.2 状态机约束。
*
* 抛错场景: 服务端返回非 2xx (比如 friendship 不在 accepted 状态、被踢出参与者等)
* 本地 IDB 写入失败
*/
declare function markMyVerified(apiBase: string, token: string, friendshipId: number, conversationId: string): Promise<{
myVerifiedAt?: string;
verifiedAt?: string | null;
}>;
/**
* 重置核对状态 (用户主动重新核对)。
* 1) 调服务端 verify-reset
* 2) 本地 trustState 重置为 unverified
*
* 服务端会通过 NATS friend_verify_reset 通知对端, 对端 SDK 也会 reset 本地 trustState。
*/
declare function verifyReset(apiBase: string, token: string, friendshipId: number, conversationId: string): Promise<void>;
interface StoredMessage {
id: string;
conversationId: string;
text: string;
isMe: boolean;
time: number;
status: 'sending' | 'sent' | 'delivered' | 'read' | 'failed';
msgType?: string;
mediaUrl?: string;
caption?: string;
seq?: number;
fromAliasId?: string;
replyToId?: string;
}
interface OutboxIntent {
internalId: string;
conversationId: string;
toAliasId: string;
text: string;
addedAt: number;
replyToId?: string;
}
type NetworkState = 'connected' | 'connecting' | 'disconnected';
type NetworkListener = (state: NetworkState) => void;
declare class RobustWSTransport implements WSTransport {
private ws;
isConnected: boolean;
private messageHandlers;
private openHandlers;
private closeHandlers;
private networkListeners;
private goawayListeners;
private reconnectAttempts;
private reconnectTimer;
private heartbeatTimer;
private intentionalClose;
lastUrl: string;
private connectingPromise;
constructor();
onNetworkStateChange(fn: NetworkListener): () => void;
/** 监听 GOAWAY 帧(被其他设备踢下线) */
onGoaway(fn: (reason: string) => void): () => void;
private emitNetworkState;
private urlProvider;
connect(urlOrProvider: string | (() => Promise<string>)): void;
private _doConnect;
private _scheduleReconnect;
private _startHeartbeat;
private _stopHeartbeat;
send(data: string): void;
onMessage(handler: (data: string) => void): void;
onOpen(handler: () => void): void;
onClose(handler: () => void): void;
disconnect(): void;
}
/**
* sdk-typescript/src/messaging/index.ts — T-100+T-101
* MessageModule:完整的消息收发封装 + 离线同步引擎 + 强制本地持久化 (Vibe Coding Refactor)
*/
interface OutgoingMessage {
conversationId: string;
toAliasId: string;
text: string;
replyToId?: string;
}
interface MessageStatus {
id: string;
status: 'sending' | 'sent' | 'delivered' | 'read' | 'failed';
}
interface WSTransport {
send(data: string): void;
onMessage(handler: (data: string) => void): void;
onOpen(handler: () => void): void;
onClose(handler: () => void): void;
isConnected: boolean;
}
declare class MessageModule {
private transport;
onMessage?: (msg: StoredMessage) => void;
onStatusChange?: (status: MessageStatus) => void;
onChannelPost?: (data: any) => void;
/** 对方正在输入通知 */
onTyping?: (data: {
fromAliasId: string;
conversationId: string;
}) => void;
/** 链上支付确认通知(pay-worker 确认后 WS 推送,由 VanityModule 订阅)*/
onPaymentConfirmed?: (data: {
type: string;
order_id: string;
ref_id: string;
}) => void;
/** P0-C(2026-04-26): 通讯录变更通知 — 收到好友请求 / 对方同意你的请求时触发,UI 应重新拉好友列表。
* 服务端在 friends/handler.go 用 PublishCore("msg.notify.{uuid}", ...) 推送,
* gateway 用 NATS 订阅转成 WS 帧 {type:'friend_request'|'friend_accepted', ...}。
* 之前两端用 10s 轮询掩盖这个事件链断点,现在补齐。*/
onContactsChange?: (data: {
type: 'friend_request' | 'friend_accepted';
from?: string;
by?: string;
conv_id?: string;
}) => void;
/**
* 1.0.38: trustState 变化通知 — 三态版。客户端 UI 用此触发"聊天页解锁/阻塞"切换。
* - trustState='verified': 双方都完成核对, UI 解锁聊天
* - trustState='my_side_verified': 本端调 markMyVerified 后, UI 切到"等对方"视图
* - trustState='unverified': 一方 reset, UI 切回阻塞 + 提示重新核对
*
* 历史: 1.0.36/1.0.37 这里只声明了 'verified' | 'unverified', 缺 my_side_verified —
* 但运行时实际并没有从这里发 my_side_verified, UI 端只能自行 loadSession() 兜底。
* 1.0.38 起 markMyVerified 也会触发本回调, 类型补齐。
*/
onTrustStateChange?: (data: {
conversationId: string;
trustState: SessionTrustState;
}) => void;
/**
* 1.0.38: 由独立函数 markMyVerified() 调用, 在本地 trustState 升 my_side_verified
* 后通知 UI。也可由 verifyReset 调用通知 UI 降 unverified。
* 公开方法避免反射 hack — 之前 PWA/Android 用 (client.messages as any).inner.onTrustStateChange
* 都改用这个稳定 API。
*/
notifyTrustStateChange(conversationId: string, trustState: SessionTrustState): void;
constructor(transport: WSTransport);
send(msg: OutgoingMessage): Promise<string>;
private _trySendIntent;
sendDelivered(convId: string, seq: number, toAliasId: string): void;
sendRead(convId: string, seq: number, toAliasId: string): void;
sendTyping(toAliasId: string, convId: string): void;
/** 发送消息撤回帧(架构 §4.2 V1.1 新增) */
sendRetract(messageId: string, toAliasId: string, convId: string): void;
private handleFrame;
/**
* 1.0.36: 处理 quarantine 队列。
* 在双方都核对完成后调用, 把之前缓存的密文重新走 handleIncomingMsg 流程解密。
* 完成后清空 quarantine。
*/
private processQuarantine;
private handleIncomingMsg;
private handleStatusChange;
/**
* 处理 delivered/read 回执帧(基于 conv_id 批量更新)
* 回执帧格式:{type:'delivered'|'read', conv_id, seq, to}
* 不含消息 id,所以需要按 conv_id 查找自己发出的消息并更新
*/
private handleReceiptByConvId;
private onConnected;
private handleRetract;
}
declare class HttpClient {
private apiBase;
private token;
constructor(apiBase?: string);
setApiBase(apiBase: string): void;
getApiBase(): string;
setToken(token: string | null): void;
getToken(): string | null;
getHeaders(customHeaders?: Record<string, string>): Record<string, string>;
get<T = any>(path: string): Promise<T>;
post<T = any>(path: string, body: any): Promise<T>;
put<T = any>(path: string, body?: any): Promise<T>;
delete<T = any>(path: string): Promise<T>;
/**
* For direct fetch calls (like Media Presigned URL PUT / GET)
*/
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
}
declare class AuthModule {
private http;
private _uuid;
/**
* 串行化 restoreSession / reauthenticate,防止并发自踢:
* 多个调用点(冷启动/SW 唤醒/标签恢复)几乎同时调用时,
* await 之间的微任务窗口让两个 promise 都先读到 token == null,
* 各跑一次 authenticate,服务端 revokeOldSessions 把先到的 JTI 撤销,
* 30s 后 revalidateLoop 推 GOAWAY → 误踢。
*/
private authPromise;
constructor(http: HttpClient);
/**
* 供 SDK 内部建立 WebSocket 时调用,防误用
*/
get internalUUID(): string;
/**
* 恢复会话:从本地 IndexedDB 加载身份 -> 防伪签名挑战 -> 写入 Token
*
* 🔁 幂等保证(关键!):
* 服务端 revokeOldSessions 每次新认证都会撤销旧 JTI。
* 短时间内多处调用 restoreSession(冷启动 / SW 唤醒 / 标签切换),
* 第一个 JTI 会被第二个撤销,30 秒后 revalidateLoop 命中导致 jwt_revoked GOAWAY。
* 因此:已经有 token 时直接复用,只在第一次或主动 logout 后才重新认证。
*/
restoreSession(): Promise<{
aliasId: string;
nickname: string;
} | null>;
/**
* 强制重新认证(用于 jwt_revoked 自愈场景)
* 不复用现有 token,始终走完整 authenticate 拿新 JTI。
* 但仍要和 restoreSession 共享同一把"in-flight"锁,避免和并发的 restoreSession 互相撤 JTI。
*/
reauthenticate(): Promise<{
aliasId: string;
nickname: string;
} | null>;
/**
* 注册:执行 PoW 防刷验证 -> 计算公钥 -> /register -> /auth/challenge -> /auth/verify
* V1.4.1 方案 A:靓号在注册完成后通过 vanity.bind() 接口绑定,注册时不再传入靓号订单
*/
registerAccount(mnemonic: string, nickname: string): Promise<{
aliasId: string;
}>;
/**
* 针对给定的 UUID 和私钥执行防伪鉴权,成功后将 token 注册到 http 内部并返回
*/
performAuthChallenge(userUUID: string, signingPrivateKey: Uint8Array): Promise<string>;
}
/**
* Observable<T> — 0.3.0 响应式数据层原语
*
* 设计目标:
* - bundle size ≤ 3KB gzipped(不引入 rxjs)
* - 语义与 Kotlin StateFlow / Swift AsyncStream 对齐
* - 零依赖
*
* 语义约定:
* - 只实现 "hot" 流(BehaviorSubject 语义):新订阅者立即收到当前值
* - emit 顺序严格,背压策略 = 最新覆盖(IM 场景合适)
* - unsubscribe 幂等,多次调用无副作用
*
* 不做什么:
* - 不做 rxjs 全集(Subject 以外的各种高级操作符)
* - 不做 cold observable(所有流一经创建即 hot)
* - 不做错误信道(错误走 client.events.error 总线)
*/
interface Subscription {
unsubscribe(): void;
readonly closed: boolean;
}
interface Observer<T> {
next?: (value: T) => void;
error?: (err: Error) => void;
complete?: () => void;
}
type Subscribable<T> = Observer<T> | ((value: T) => void);
interface Observable<T> {
subscribe(observer: Subscribable<T>): Subscription;
/** BehaviorSubject 语义:当前值(订阅时立即收到) */
readonly value: T;
/** 操作符:映射 */
map<U>(fn: (value: T) => U): Observable<U>;
/** 操作符:过滤(谓词为 false 的值不发射) */
filter(predicate: (value: T) => boolean): Observable<T>;
/** 操作符:值变化才发射(浅比较) */
distinctUntilChanged(compare?: (a: T, b: T) => boolean): Observable<T>;
}
/**
* BehaviorSubject<T> — 带当前值的 Observable
* SDK 内部使用,对外一律以 Observable<T> 类型暴露(readonly)
*/
declare class BehaviorSubject<T> implements Observable<T> {
private _value;
private _listeners;
private _completed;
constructor(initial: T);
get value(): T;
/** 内部 API:推送新值 */
next(value: T): void;
/** 内部 API:终结流(之后不再发射,但已有订阅者仍持有最后一个值) */
complete(): void;
subscribe(observer: Subscribable<T>): Subscription;
map<U>(fn: (value: T) => U): Observable<U>;
filter(predicate: (value: T) => boolean): Observable<T>;
distinctUntilChanged(compare?: (a: T, b: T) => boolean): Observable<T>;
}
/**
* src/events — 0.3.0 全局事件总线
*
* 取代 0.2.x 的 client.on('message', cb) 风格,改为四个顶层 Observable:
* - client.events.network: 连接状态
* - client.events.sync: 消息补同步状态
* - client.events.error: 非致命错误
* - client.events.message: 跨会话的全局消息流(UI 通知场景)
*
* 内部实现:包装 BehaviorSubject,对外只暴露 Observable<T> 只读视图
*/
type SyncState = {
tag: 'idle';
} | {
tag: 'syncing';
progress: number;
pendingMessages: number;
} | {
tag: 'done';
catchUpDurationMs: number;
};
type SDKErrorKind = 'auth' | 'network' | 'rate_limit' | 'crypto' | 'server' | 'unknown';
interface SDKError {
kind: SDKErrorKind;
message: string;
details?: Record<string, unknown>;
at: number;
}
/**
* SDK 内部使用的事件总线,持有可写的 BehaviorSubject
* 通过 .toPublic() 产出对外视图
*/
declare class EventBus {
readonly _network: BehaviorSubject<NetworkState>;
readonly _sync: BehaviorSubject<SyncState>;
readonly _error: BehaviorSubject<SDKError | null>;
readonly _message: BehaviorSubject<StoredMessage | null>;
emitNetwork(state: NetworkState): void;
emitSync(state: SyncState): void;
emitError(err: Omit<SDKError, 'at'>): void;
emitMessage(msg: StoredMessage): void;
toPublic(): PublicEventBus;
}
/** 对外只读的事件总线视图 */
interface PublicEventBus {
/** WebSocket 连接状态 · offline | connecting | online */
readonly network: Observable<NetworkState>;
/** 消息补同步状态 · idle | syncing | done */
readonly sync: Observable<SyncState>;
/** 非致命错误流(初值 null) */
readonly error: Observable<SDKError | null>;
/** 全局消息流 · 每条到达的消息(跨会话)· 初值 null */
readonly message: Observable<StoredMessage | null>;
}
/**
* src/contacts/module.ts — 0.4.0 ContactsModule(响应式首版)
*
* 直接命名 ContactsModule, 不叫 ReactiveContactsModule。
* 产品未公开发布, 没有历史包袱, 第一版就是终态。
*/
interface FriendProfile {
friendship_id: number;
alias_id: string;
nickname: string;
status: 'pending' | 'accepted' | 'rejected';
direction: 'sent' | 'received';
conversation_id: string;
x25519_public_key: string;
ed25519_public_key: string;
created_at: string;
}
declare class ContactsModule {
private readonly http;
private readonly events?;
private _friends;
private _primed;
private _refreshPromise;
constructor(http: HttpClient, events?: EventBus | undefined);
observeFriends(): Observable<FriendProfile[]>;
observeAccepted(): Observable<FriendProfile[]>;
observePending(): Observable<FriendProfile[]>;
observePendingCount(): Observable<number>;
/** 快照读取(非订阅) */
get friends(): FriendProfile[];
lookupUser(aliasId: string): Promise<{
alias_id: string;
nickname: string;
x25519_public_key: string;
ed25519_public_key: string;
}>;
/** 发送好友请求,成功后自动 refresh */
sendRequest(toAliasId: string): Promise<void>;
/** 接受好友请求(乐观更新 + rollback),返回 conversationId */
accept(friendshipId: number): Promise<string>;
/** 拒绝好友请求(乐观更新 + rollback) */
reject(friendshipId: number): Promise<void>;
/**
* 兼容 0.2.x API · 一次性拉取好友列表快照
* 指定描述: 底层执行 refresh(), 返回最新 friends 数组
* 推荐新代码用 observeFriends().subscribe() 或 observeFriends().value
*/
/** 兼容 0.2.x API */
acceptFriendRequest(friendshipId: number): Promise<string>;
/** 兼容 0.2.x API */
sendFriendRequest(toAliasId: string): Promise<void>;
/** 兼容 0.2.x API */
rejectFriendRequest(friendshipId: number): Promise<void>;
syncFriends(): Promise<FriendProfile[]>;
/** 手动触发刷新(WS 收到好友事件时 SDK 内部调用) */
refresh(): Promise<void>;
private _refresh;
private _reportError;
}
/**
* src/messaging/module.ts — 0.4.0 MessagesModule(响应式)
*
* 底层保留 MessageModule(单数, index.ts 里的)作为 WebSocket / Outbox 引擎。
* 本类是对外 API, 暴露 observeConversations / observeMessages / send。
*/
/** 1.0.38 公开三态 trustState 回调签名 — UI 用此监听核对状态切换。 */
type TrustStateChangeData = {
conversationId: string;
trustState: SessionTrustState;
};
type TrustStateChangeListener = (data: TrustStateChangeData) => void;
interface ConversationSummary {
conversationId: string;
peerAliasId: string;
peerNickname: string;
lastMessage?: {
text: string;
at: number;
fromMe: boolean;
status?: StoredMessage['status'];
};
unreadCount: number;
}
declare class MessagesModule {
private readonly inner;
private readonly events?;
private _conversations;
private _byConvId;
private _primed;
private _trustStateListeners;
constructor(inner: MessageModule, events?: EventBus | undefined);
/**
* 1.0.38 订阅 trustState 三态变化。返回 unsubscribe 函数。
*
* 触发时机:
* - `markMyVerified()` 后 → trustState='my_side_verified'
* - 服务端 NATS friend_verified → trustState='verified'(仅在本端已 my_side_verified 时)
* - `verifyReset()` 或服务端 NATS friend_verify_reset → trustState='unverified'
*
* 用法(替代 1.0.37 及更早的反射 hack):
* ```ts
* const unsub = client.messages.onTrustStateChange(({ conversationId, trustState }) => {
* if (conversationId !== activeChatId) return
* if (trustState === 'verified') { setShowModal(false); unlockChat() }
* else if (trustState === 'my_side_verified') { switchToWaitingView() }
* else { showVerifyOverlay() }
* })
* useEffect(() => unsub, [])
* ```
*/
onTrustStateChange(listener: TrustStateChangeListener): () => void;
observeConversations(): Observable<ConversationSummary[]>;
observeMessages(conversationId: string): Observable<StoredMessage[]>;
/** 发送消息,立即写本地,订阅者立刻看到 sending 状态。返回 messageId */
send(msg: OutgoingMessage): Promise<string>;
/** 清除某会话的本地消息(测试/退出账号场景) */
clearConversation(conversationId: string): Promise<void>;
/** 向对方发送正在输入事件 */
sendTyping(convId: string, toAliasId: string): void;
/** 标记对方消息为已读 */
markAsRead(convId: string, maxSeq: number, toAliasId: string): void;
/** 撤回自己发的消息 */
retract(messageId: string, toAliasId: string, conversationId: string): void;
/** 获取本地历史消息 */
getHistory(convId: string, opts?: {
limit?: number;
before?: number;
}): Promise<StoredMessage[]>;
/** 清除指定会话的本地消息 */
clearHistory(convId: string): Promise<void>;
/** 清除全部会话的本地消息 */
clearAllConversations(): Promise<void>;
/** 导出所有会话为 Blob URL(NDJSON) */
exportAll(): Promise<string>;
private _refreshSummary;
private _buildSummary;
private _loadConversation;
private _handleIncoming;
private _handleStatusChange;
}
/**
* sdk-typescript/src/media/manager.ts — 多媒体上传/下载(零知识盲中转)
* 支持图片(压缩)、文件(原始)、语音(原始)三类
*/
declare class MediaModule$1 {
private http;
constructor(http: HttpClient);
/**
* 极简外壳:自动压缩、调用端侧加密、并获得安全返回
* 成功即返回拼接好的快捷消息内容: "[img]media_key"
*/
uploadImage(conversationId: string, file: File, maxDim?: number, quality?: number): Promise<string>;
/**
* 上传通用文件(不压缩,直接加密分片上传)
* 返回 "[file]media_key|original_name|size_bytes"
*/
uploadFile(file: File, conversationId: string): Promise<string>;
/**
* 上传语音消息(不压缩,直接加密分片上传)
* @param durationMs 录音时长(毫秒)
* 返回 "[voice]media_key|duration_ms"
*/
uploadVoice(blob: Blob, conversationId: string, durationMs: number): Promise<string>;
/**
* 分片上传加密大文件 (由于原生 AES-GCM 限制,采用基于 Chunk 的流式加密)
* 返回 "[img]media_key" (此处重用业务层格式)
*/
uploadEncryptedFile(file: File, conversationId: string, maxDim?: number, quality?: number): Promise<string>;
/**
* 通用加密分片上传(共享逻辑)
* @returns media_key
*/
private encryptAndUpload;
/**
* 下载加密的媒体文件 (裸流,仅供内部解密使用)
*/
private downloadMedia;
/**
* 下载并流式解密媒体文件
*/
downloadDecryptedMedia(mediaKey: string, conversationId: string): Promise<ArrayBuffer>;
/**
* 压缩图片到指定最大尺寸(宽/高)
*/
private compressImage;
}
/**
* src/media/module.ts — 0.4.0 MediaModule(响应式上传进度)
*
* 对 0.2.x 老 MediaModule (manager.ts) 的上层包装, 提供:
* - sendImage / sendFile / sendVoice 返回 messageId
* - observeUpload(messageId) 返回 Observable<UploadProgress>
*
* 0.5+ 会把进度回调从伪曲线改成真实 byte-level, 届时接口不变。
*/
type UploadPhase = 'encrypting' | 'uploading' | 'done' | 'failed';
interface UploadProgress {
messageId: string;
phase: UploadPhase;
loaded: number;
total: number;
error?: string;
}
type MediaKind = 'image' | 'file' | 'voice';
declare class MediaModule {
private readonly events?;
private _uploads;
private _inner;
private _messages?;
constructor(low: MediaModule$1, events?: EventBus | undefined, messages?: MessagesModule);
sendImage(conversationId: string, toAliasId: string, file: File, opts?: {
maxDim?: number;
quality?: number;
thumbnail?: string;
replyToId?: string;
}): Promise<string>;
sendFile(conversationId: string, toAliasId: string, file: File, opts?: {
replyToId?: string;
}): Promise<string>;
sendVoice(conversationId: string, toAliasId: string, blob: Blob, durationMs: number, opts?: {
replyToId?: string;
}): Promise<string>;
/** 内部:通过 messages 模块把 payload 作为 IM 消息发给对端 */
private _sendMessage;
observeUpload(messageId: string): Observable<UploadProgress>;
/** 兼容 0.2.x API · 下载并解密媒介 */
downloadDecryptedMedia(mediaKey: string, conversationId: string): Promise<ArrayBuffer>;
/** 释放已完成上传的进度对象 */
dispose(messageId: string): void;
private _upload;
}
/**
* sdk-typescript/src/calls/index.ts — T-072+T-073
* WebRTC 信令状态机 + Insertable Streams E2EE(视频帧加密)
*
* 架构 §4:通话建立流程
* Caller → call_offer(含 SDP + Ed25519 签名)
* → Relay(透明转发)
* → Callee → call_answer / call_reject
* → ICE Candidate 交换
* ← TURN 中继 ← RTP
*/
type CallState = 'idle' | 'calling' | 'ringing' | 'connecting' | 'connected' | 'hangup' | 'rejected' | 'ended';
interface CallOptions {
audio?: boolean;
video?: boolean;
}
interface SignalTransport {
send(env: unknown): void;
onMessage(handler: (env: unknown) => void): void;
}
declare class CallModule {
private transport;
private iceConfigProvider;
private pc;
private callId;
private state;
private localStream;
private remoteStream;
private pendingCandidates;
private _outboundIceBarrier;
private _answerTimer;
private _iceRestartedOnce;
private _disconnectedTimer;
private _rtpWatchdogTimer;
private _lastInboundProgressAt;
private _rtpPrevInbound;
private flushIceCandidates;
/**
* getUserMedia 带超时(不降级):
* - 主请求超过 timeoutMs 没 resolve/reject → 抛 'gUM timeout'
* - reject → 向上抛
*
* ⚠️ 严格模式:用户点"视频通话"就必须真的拿到摄像头。
* 旧版本曾在 video 失败时静默降级到音频纯模式,但后果是:
* 1. PWA 拿到的 stream 只有 audio,addTrack 也只 add 了 audio
* 2. createOffer 仍可能产生 m=video transceiver(unified plan 默认行为)
* 3. 对端 Android 看到 SDP 有 m=video → 也开摄像头并发回视频
* 4. PWA 这边没 sender,但对端却以为是视频通话 — 双方语义错位
* 5. 用户看到"PWA 没弹摄像头权限,Android 也黑屏"
* 因此现在严格行为:video 失败 = 通话失败,UI 必须告知用户"摄像头不可用"。
*
* 修复历史:某些浏览器/环境下 getUserMedia 既不 resolve 也不 reject,
* 用 timeoutMs 做兜底,避免 call() 永远卡在 await。
*/
private getUserMediaWithTimeout;
private signingPrivKey;
private signingPubKey;
private myAliasId;
onStateChange?: (state: CallState) => void;
onRemoteStream?: (stream: MediaStream) => void;
onLocalStream?: (stream: MediaStream) => void;
onIncomingCall?: (fromAlias: string, isVideo: boolean) => void;
onError?: (err: Error) => void;
getLocalStream(): MediaStream | null;
getRemoteStream(): MediaStream | null;
constructor(transport: SignalTransport, iceConfigProvider: () => Promise<RTCConfiguration>, opts: {
signingPrivKey: Uint8Array;
signingPubKey: Uint8Array;
myAliasId: string;
});
call(toAliasId: string, opts?: CallOptions): Promise<void>;
private _answering;
answer(): Promise<void>;
reject(): void;
hangup(): void;
private _callerAlias;
private _remoteAlias;
private handleSignal;
private handleOffer;
private handleAnswer;
/**
* connectionState === 'connected' 后调一次。理由:
* - 协商前 RtpSender.getParameters() 拿不到 codec_payload_type,
* 部分浏览器 setParameters 会抛 InvalidModificationError
* - 接通后 encoding 已就位,改 maxBitrate 最稳
*
* 双端 maxBitrate 必须一致,否则 BWE 取低值,改动等于失效。
* 失败静默(老浏览器 / RN WebView 不一定支持),不能因此断了通话。
*/
private applyVideoSenderParams;
private _diagTimer;
private _diagPrev;
private startDiagDump;
private stopDiagDump;
/**
* 把 SDP 中"本地有对应 track"的 m= section 强制改写为 a=sendrecv。
* 用于修复 Chrome createOffer/createAnswer 在某些场景下生成
* sendonly/recvonly/inactive 导致一端收不到画面的 bug。
*
* - 主叫端 createOffer 后,以 localStream 的 track kinds 为依据修方向
* - 接听端 createAnswer 后同理
*
* 不改 m= section 没有本地 track 的方向(那种 section 本来就该 recvonly)。
*/
private forceSendrecvOnSdp;
private armAnswerTimeout;
private clearAnswerTimeout;
private armDisconnectedTimer;
private clearDisconnectedTimer;
/**
* 接通后周期性 getStats(每 2s),监控入站 RTP 字节/帧是否在涨。
* 视频通话:看 inbound-rtp video.bytesReceived + framesDecoded
* 纯音频:看 inbound-rtp audio.bytesReceived
* 持续 15s 无增长 → cleanup('ended')。
*
* 阈值 15s:Opus DTX 静音可能 5~8s 不发包,15s 留裕度。
* 实测对端 App 被杀场景下,inbound 字节 ~5s 内停止,15s 后兜底触发,体感 OK。
*/
private startRtpWatchdog;
private stopRtpWatchdog;
private handleICE;
private createPeerConnection;
private sendSignal;
private setState;
private cleanup;
private _pubKeyCache;
private fetchPubKey;
private _sessionKeyCache;
private getSessionKey;
}
/**
* src/calls/module.ts — 0.4.0 CallsModule(响应式通话)
*
* 底层 CallModule (calls/index.ts) 保留作为 WebRTC 引擎, 本类是对外 API。
* 响应式流:
* - observeState: CallState('idle' | 'calling' | 'ringing' | 'connecting' | 'connected' | 'hangup' | 'rejected' | 'ended')
* - observeLocalStream / observeRemoteStream
*
* 命令式 API:
* - start(peerAliasId, { audio, video })
* - accept()
* - reject(reason?)
* - hangup()
*/
declare class CallsModule {
private readonly inner;
private readonly events?;
private _state;
private _localStream;
private _remoteStream;
private _extraOnStateChange;
private _extraOnLocalStream;
private _extraOnRemoteStream;
set onStateChange(cb: ((s: CallState) => void) | undefined);
get onStateChange(): ((s: CallState) => void) | undefined;
set onLocalStream(cb: ((s: MediaStream) => void) | undefined);
get onLocalStream(): ((s: MediaStream) => void) | undefined;
set onRemoteStream(cb: ((s: MediaStream) => void) | undefined);
get onRemoteStream(): ((s: MediaStream) => void) | undefined;
set onIncomingCall(cb: ((fromAlias: string, isVideo: boolean) => void) | undefined);
get onIncomingCall(): ((fromAlias: string, isVideo: boolean) => void) | undefined;
set onError(cb: ((err: Error) => void) | undefined);
get onError(): ((err: Error) => void) | undefined;
constructor(inner: CallModule, events?: EventBus | undefined);
observeState(): Observable<CallState>;
observeLocalStream(): Observable<MediaStream | null>;
observeRemoteStream(): Observable<MediaStream | null>;
get currentState(): CallState;
start(peerAliasId: string, options: CallOptions): Promise<void>;
accept(): Promise<void>;
reject(reason?: string): Promise<void>;
hangup(): Promise<void>;
/** 为 PWA 旧 API 提供的别名: start() */
call(peerAliasId: string, options?: CallOptions): Promise<void>;
/** 为 PWA 旧 API 提供的别名: accept() */
answer(): Promise<void>;
/** 获取本地流 */
getLocalStream(): MediaStream | null;
/** 获取远端流 */
getRemoteStream(): MediaStream | null;
}
/**
* security/index.ts — SecurityModule(P3-004 修复)
*
* 实现文档 §2.2.1 SecurityModule 接口设计:
* - getSecurityCode(contactId) → 60位安全码(MITM 防御)
* - verifyInputCode(contactId, code) → 输入验证(主路径)
* - markAsVerified(contactId) → 手动标记已验证(辅助路径)
* - getTrustState(contactId) → 信任状态
* - resetTrustState(contactId) → 重置信任
*
* 所有状态存储于 IndexedDB(服务器完全不知情)
* 防劫持守护:每条消息触发前调用 guardMessage() 检测公钥突变
*/
interface SecurityCode {
contactId: string;
/** 60 位十六进制字符串,每 4 字符一组,如 "AB12 · F39C · ..." */
displayCode: string;
/** 原始 hex(用于 verifyInputCode 内部比对)*/
fingerprintHex: string;
}
type TrustState = {
status: 'unverified';
} | {
status: 'verified';
verifiedAt: number;
fingerprintSnapshot: string;
};
interface SecurityViolationEvent {
type: 'security_violation';
contactId: string;
previousFingerprint: string;
currentFingerprint: string;
detectedAt: number;
message: null;
}
declare class SecurityModule {
/**
* 获取与指定联系人的安全码(60 位 hex 字符串)
* 每次加好友后全自动生成,UI 打开"加密详情"页时调用
*/
getSecurityCode(contactId: string, myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): Promise<SecurityCode>;
/**
* 输入验证(主路径):
* 用户粘贴对方通过微信/TG 发来的 60 位安全码,SDK 自动与本地计算值比对
* 返回 true → 一致(无 MITM)→ 自动写入 verified
* 返回 false → 不一致(公钥被篡改)
*/
verifyInputCode(contactId: string, inputCode: string, myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): Promise<boolean>;
/**
* 手动标记为"已验证"(辅助路径):
* 用户通过截图肉眼比对后,手动点击按钮调用此方法
* 服务器完全不知情(存储于 IndexedDB)
*/
markAsVerified(contactId: string, myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): Promise<void>;
/**
* 获取指定联系人当前的信任状态
*/
getTrustState(contactId: string): Promise<TrustState>;
/**
* 重置验证状态:将 trust_state 还原为 'unverified'
* 场景:用户主动换设备/助记词后需重新核查
*/
resetTrustState(contactId: string): Promise<void>;
/**
* 防劫持守护(文档 §2.2 流程图三):
* 每条消息到达时自动调用,若检测到公钥突变,返回 SecurityViolationEvent
*
* @returns null → 验证通过,可正常解密
* @returns SecurityViolationEvent → 公钥突变,拒绝解密并上报 UI
*/
guardMessage(contactId: string, currentMyEcdh: Uint8Array, currentTheirEcdh: Uint8Array): Promise<SecurityViolationEvent | null>;
}
/**
* src/security/module.ts — 0.4.0 SecurityModule(响应式)
*
* 底层 security/index.ts 里的 SecurityModule(命令式)保留不动,
* 本文件把它包装成响应式 ContactTrust 流。
*
* 重命名:对外类名用 SecurityService 避免与底层 SecurityModule 同名冲突。
*/
declare class SecurityService {
private readonly inner;
private _states;
constructor(inner: SecurityModule);
observeTrust(contactId: string): Observable<TrustState>;
getTrust(contactId: string): Promise<TrustState>;
getSafetyCode(contactId: string, myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): Promise<SecurityCode>;
verifyCode(contactId: string, inputCode: string, myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): Promise<boolean>;
markVerified(contactId: string, myEcdhPublicKey: Uint8Array, theirEcdhPublicKey: Uint8Array): Promise<void>;
reset(contactId: string): Promise<void>;
private _loadTrust;
private _emitCurrent;
}
interface ChannelInfo {
id: string;
name: string;
description: string;
role?: string;
is_subscribed?: boolean;
/** 频道是否处于出售状态 */
for_sale?: boolean;
/** 出售价格(USDT),仅当 for_sale=true 时有值 */
sale_price?: number;
}
interface ChannelPost {
id: string;
type: string;
content: string;
created_at: string;
author_alias_id: string;
}
/** 频道交易订单(来自 POST /api/v1/channels/{id}/buy)*/
interface ChannelTradeOrder {
order_id: string;
/** 单位 USDT */
price_usdt: number;
/** TRON 收款地址 */
pay_to: string;
/** 订单有效期(ISO 8601) */
expired_at: string;
}
declare class ChannelsModule {
private http;
constructor(http: HttpClient);
/**
* Search for public channels
*/
search(query: string): Promise<ChannelInfo[]>;
/**
* Get channels subscribed by current user
*/
getMine(): Promise<ChannelInfo[]>;
/**
* Get channel details
*/
getDetail(channelId: string): Promise<ChannelInfo>;
/**
* Create a new channel
*/
create(name: string, description: string, isPublic?: boolean): Promise<{
channel_id: string;
}>;
/**
* Subscribe to a channel
*/
subscribe(channelId: string): Promise<void>;
/**
* Unsubscribe from a channel
*/
unsubscribe(channelId: string): Promise<void>;
/**
* Post a message to a channel
*/
postMessage(channelId: string, content: string, type?: string): Promise<{
post_id: string;
}>;
/**
* Get channel post history
*/
getPosts(channelId: string): Promise<ChannelPost[]>;
/**
* Check if current user can post in the channel
*/
canPost(channelInfo: ChannelInfo | null): boolean;
/**
* 将自有频道挂牌出售(T-096,需要 JWT,必须是频道 Owner)
*
* 使用乐观锁 CAS 设置售价,挂牌后其他用户可通过 `buyChannel` 购买。
*
* @param channelId 要出售的频道 ID
* @param priceUsdt 出售价格(USDT,整数)
*
* @example
* await client.channels.listForSale('ch_abc123', 200)
*/
listForSale(channelId: string, priceUsdt: number): Promise<void>;
/**
* 购买频道 — 创建支付订单(T-096,需要 JWT)
*
* 使用乐观锁 CAS 防止超卖。返回后向用户展示 TRON 收款地址,
* 链上确认后 pay-worker 自动完成频道所有权转移,并推送 `payment_confirmed` WS 事件。
*
* @throws 409 — 频道刚被其他人抢购,请刷新后重试
* @throws 404 — 频道不存在
* @throws 400 — 试图购买自己的频道
*
* @example
* const order = await client.channels.buyChannel('ch_abc123')
* showQRCode(order.pay_to, order.price_usdt)
*/
buyChannel(channelId: string): Promise<ChannelTradeOrder>;
/**
* 购买额外频道创建配额(每次购买增加 1 个席位,固定 5 USDT)
*
* @throws 401 — 需要登录
* @example
* const order = await client.channels.buyQuota()
* showQRCode(order.pay_to, order.price_usdt)
*/
buyQuota(): Promise<ChannelTradeOrder>;
}
/**
* sdk-typescript/src/vanity/manager.ts — T-095 VanityModule
*
* 负责靓号的搜索、购买(创建支付订单)和链上确认回调订阅。
* 支付感知通过 WS `payment_confirmed` 帧 → MessageModule.onPaymentConfirmed → 本模块路由实现。
*
* V1.4.1(方案 A):靓号商店已移至注册完成后,主流程:
* 1. purchase(aliasId) — 注册后购买(需要 JWT),创建 PENDING 订单
* 2. 监听 onPaymentConfirmed() WS 推送 → 链上确认
* 3. bind(orderId) — 支付确认后绑定靓号到账户 alias_id
*
* @deprecated reserve() / orderStatus() 为旧版注册前流程遗留接口,不再使用
*/
/** 靓号列表项(来自 GET /api/v1/vanity/search)
* V1.3 规则引擎版:价格由后端 rules.go 实时评估,不再依赖预填表 */
interface VanityItem {
alias_id: string;
price_usdt: number;
/** 靓号等级:'top' | 'premium' | 'standard' */
tier: string;
is_featured: boolean;
}
/** 购买靓号返回的支付订单(来自 POST /api/v1/vanity/purchase,需 JWT)*/
interface PurchaseOrder {
order_id: string;
alias_id: string;
/** 单位 USDT */
price_usdt: number;
/** NOWPayments 托管支付页 URL(V1.5.0 新增,接入 NOWPayments 后返回)*/
payment_url: string;
/** @deprecated 原始 TRON 地址,NOWPayments 接入后不再使用 */
pay_to?: string;
/** 订单有效期(ISO 8601) */
expired_at: string;
}
/**
* 注册前预订靓号返回的订单(来自 POST /api/v1/vanity/reserve,**无需 JWT**)
* 与 PurchaseOrder 结构相同,但不绑定用户 UUID(注册前无身份)
*/
interface ReserveOrder {
order_id: string;
alias_id: string;
price_usdt: number;
/** TRON 收款地址 */
pay_to: string;
/** 订单有效期(ISO 8601) */
expired_at: string;
}
/**
* 订单状态查询结果(来自 GET /api/v1/vanity/order/{id}/status,**无需 JWT**)
* 用于注册前用户轮询支付结果
*/
interface OrderStatus {
status: 'pending' | 'confirmed' | 'expired';
alias_id: string;
}
/** WS payment_confirmed 事件(pay-worker 链上确认后推送)*/
interface PaymentConfirmedEvent {
type: 'payment_confirmed';
order_id: string;
/** 靓号购买 → alias_id;频道交易 → channel_id */
ref_id: string;
}
declare class VanityModule {
private http;
private paymentListeners;
constructor(http: HttpClient);
/**
* 搜索靓号 / 获取精选列表(T-091 公开接口,无需 JWT)
*
* - `q` 为空 → 返回精选 (is_featured=1),按价格升序
* - `q` 非空 → 按 alias_id 前缀匹配(LIKE 'q%')
*
* @example
* const featured = await client.vanity.search()
* const results = await client.vanity.search('888')
*/
search(q?: string): Promise<VanityItem[]>;
/**
* @deprecated V1.4.1 方案 A 后不再使用。旧版注册前公开预订靓号(无需 JWT)。
* 请改用 purchase()(注册后,需 JWT)+ bind() 流程。
*/
reserve(aliasId: string): Promise<ReserveOrder>;
/**
* @deprecated V1.4.1 方案 A 后不再使用。旧版注册前轮询订单状态(无需 JWT)。
* 注册后请改用 onPaymentConfirmed() WS 推送。
*/
orderStatus(orderId: string): Promise<OrderStatus>;
/**
* 购买靓号 — 创建支付订单(T-090,**需要 JWT**)
*
* V1.4.1 方案 A:注册完成后的首次引导页调用此方法。
* 使用乐观锁 CAS 占位 15 分钟。返回后向用户展示 TRON 收款地址,
* 用户链上转账后 pay-worker 自动完成确认,并通过 WS 推送 `payment_confirmed`。
* 收到推送后,调用 bind(orderId) 将靓号正式绑定到账户。
*
* @throws 409 — 靓号已被其他人抢占,请提示用户更换
* @throws 404 — 靓号不存在
*
* @example
* const order = await client.vanity.purchase('88888888')
* // 展示支付弹窗
* client.vanity.onPaymentConfirmed(async e => {
* const { alias_id } = await client.vanity.bind(e.order_id)
* store.setAliasId(alias_id)
* })
*/
purchase(aliasId: string): Promise<PurchaseOrder>;
/**
* 绑定靓号到当前账户(**V1.4.1 新增,需要 JWT**)
*
* 在 pay-worker 确认链上支付后,调用此方法将 `alias_id` 正式写入 identity 表。
* 通常在 onPaymentConfirmed() 回调内调用。
*
* @param orderId — 已确认的 `payment_order.id`
* @returns `{ alias_id }` — 绑定成功的靓号
*
* @throws 404 — 订单不存在或不属于当前用户
* @throws 409 — 订单未确认 / 靓号绑定冲突
*
* @example
* const { alias_id } = await client.vanity.bind(orderId)
* store.setAliasId(alias_id)
*/
bind(orderId: string): Promise<{
alias_id: string;
}>;
/**
* 订阅支付完成回调(链上确认后 pay-worker → WS 推送)
*
* 返回 unsubscribe 函数,可直接在 React `useEffect` 清理函数中调用。
*
* @example
* useEffect(() => {
* return client.vanity.onPaymentConfirmed(e => {
* toast(`🎉 靓号 ${e.ref_id} 已绑定到你的账号!`)
* router.push('/profile')
* })
* }, [])
*/
onPaymentConfirmed(cb: (e: PaymentConfirmedEvent) => void): () => void;
/**
* @internal SDK 内部路由入口,由 MessageModule handleFrame 在收到
* `payment_confirmed` WS 帧时调用,App 层不应直接调用此方法。
*/
_handlePaymentConfirmed(event: PaymentConfirmedEvent): void;
}
/**
* PushNotificationError — SDK push 模块的语义化错误
*
* 之前 enablePushNotifications 失败只 console.warn,UI 端拿到 generic Error
* 无法定位是哪一步出错。1.0.39 改成抛 PushNotificationError 带 kind 字段,
* UI 可以根据 kind 显示具体修复建议。
*
* kind 列表:
* - 'no_push_manager' SW 不支持 pushManager(老浏览器 / 不安全上下文)
* - 'no_vapid_key' 服务端没返回 VAPID 公钥(服务端配置缺失)
* - 'subscribe_failed' pushManager.subscribe 失败 (iOS 必须 standalone + user gesture)
* - 'permission_denied' 通知权限被拒(用户在系统 / 浏览器设置里拒了)
* - 'register_failed' 服务端 POST /push/subscribe 失败
* - 'unknown' 其他
*/
declare class PushNotificationError extends Error {
readonly kind: 'no_push_manager' | 'no_vapid_key' | 'subscribe_failed' | 'permission_denied' | 'register_failed' | 'unknown';
readonly cause?: unknown;
constructor(kind: PushNotificationError['kind'], message: string, cause?: unknown);
}
declare class PushModule {
private http;
constructor(http: HttpClient);
/**
* 浏览器申请推送凭证并向服务端注册
* 此方法需要依赖浏览器的 ServiceWorker API,仅在 Web 端有效
*
* 失败时抛 PushNotificationError 带 kind 字段,UI 可据此显示具体修复建议。
*/
enablePushNotifications(swRegistration: ServiceWorkerRegistration, vapidPublicKey?: string): Promise<void>;
/**
* 注销当前设备的推送订阅
* 对应服务端 POST /api/v1/push/disable — 清空 device_session.push_endpoint
* 对标 Android/iOS SDK: client.push.disable() / .disablePush()
*/
disablePush(): Promise<void>;
/**
* 1.0.39: 诊断当前推送链路状态, 返回 6 项检查结果。
* 用于 Settings → 推送诊断 UI, 让用户一键自查为什么收不到推送。
*
* 返回 PushDiagnostics 对象, 调用方根据 status 显示状态 + 修复建议。
*/
diagnose(swRegistration?: ServiceWorkerRegistration): Promise<PushDiagnostics>;
private urlBase64ToUint8Array;
}
/** 1.0.39: PushModule.diagnose() 返回值 */
interface PushDiagnostics {
overall: 'ok' | 'warn' | 'bad' | 'unknown';
checks: {
pushApiSupport?: {
ok: boolean;
detail: string;
};
serviceWorker?: {
ok: boolean;
detail: string;
};
permission?: {
ok: boolean;
detail: string;
};
subscription?: {
ok: boolean;
detail: string;
};
serverVapid?: {
ok: boolean;
detail: string;
};
standalone?: {
ok: boolean;
detail: string;
};
};
}
/**
* src/events/streams-ext.ts — 0.4.0 事件总线扩展流
*
* 为什么是扩展文件?
* events/index.ts 里 EventBus 已经有 4 个核心流 (network / sync / error / message)。
* PWA 实际 UI 还依赖:
* - typing 对方输入提示
* - statusChange 消息送达/已读回执
* - channelPost 公共频道新帖
* - goaway 服务端通知下线 (多设备登录被踢等)
*
* 不修改 events/index.ts 的前提下, 用一个 ExtendedEventBus 组合它 + 4 个新流。
* SecureChatClient.events 将是 PublicExtendedEventBus 类型。
*/
interface TypingEvent {
fromAliasId: string;
conversationId: string;
}
interface MessageStatusEvent {
id: string;
status: 'sending' | 'sent' | 'delivered' | 'read' | 'failed';
}
interface ChannelPostEvent {
channelId: string;
postId: string;
fromAliasId: string;
text: string;
at: number;
[k: string]: any;
}
interface GoawayEvent {
reason: string;
at: number;
}
/** P0-C(2026-04-26): 通讯录变更事件
* - friend_request: 收到他人发来的好友请求(`from` = 对方 user_uuid)
* - friend_accepted: 自己之前发出的请求被对方接受(`by` = 对方 user_uuid, `conv_id` = 新生成的 DM 会话 id)
* UI 应在收到此事件后重新拉取好友列表/请求列表,无需轮询 */
interface ContactsChangeEvent {
type: 'friend_request' | 'friend_accepted';
from?: string;
by?: string;
conv_id?: string;
at: number;
}
interface PublicExtendedEventBus extends PublicEventBus {
/** 对方正在输入 · 初值 null */
readonly typing: Observable<TypingEvent | null>;
/** 消息状态流转(send/delivered/read/failed)· 初值 null */
readonly messageStatus: Observable<MessageStatusEvent | null>;
/** 公共频道新帖 · 初值 null */
readonly channelPost: Observable<ChannelPostEvent | null>;
/** 服务端通知下线(多设备登录被踢 / 强制登出)· 初值 null */
readonly goaway: Observable<GoawayEvent | null>;
/** P0-C: 通讯录变更(收到好友请求 / 对方同意请求)· 初值 null
* UI 应订阅此流并重新拉取好友列表,替代之前的 10s 轮询 */
readonly contactsChange: Observable<ContactsChangeEvent | null>;
}
/**
* src/client-v2.ts — 0.4.0 SecureChatClient(响应式首版, 清爽 API)
*
* ⚠️ 重要:
* 晚上应用 patch 时, 把本文件改名为 client.ts(替换老文件)。
* 本轮 session 规则限制不能直接改 client.ts, 所以并行命名为 v2。
*
* 0.4.0 终态 API:
* const client = new SecureChatClient()
* await client.auth.registerAccount(mnemonic, 'Alice')
* client.contacts.observeFriends().subscribe(handler)
* client.events.network.subscribe(handler)
*
* 删除:
* - attachReactive(client) 门面
* - client.on / client.off EventEmitter API
* - syncFriends / getConversations / 所有命令式 getter
*/
interface SecureChatClientOptions {
/** 自定义 relay URL, 留空用默认 `https://relay.daomessage.com` */
relayUrl?: string;
}
declare class SecureChatClient {
readonly transport: RobustWSTransport;
readonly http: HttpClient;
readonly auth: AuthModule;
readonly contacts: ContactsModule;
readonly messages: MessagesModule;
readonly media: MediaModule;
readonly security: SecurityService;
readonly channels: ChannelsModule;
readonly vanity: VanityModule;
readonly push: PushModule;
calls: CallsModule | null;
readonly events: PublicExtendedEventBus;
private readonly _bus;
private readonly _messageInner;
static readonly CORE_API_BASE = "https://relay.daomessage.com";
constructor(opts?: SecureChatClientOptions);
/** 手动连接 WebSocket(`registerAccount / loginWithMnemonic / restoreSession` 会自动连, 通常不需要手动调) */
/**
* 初始化通话模块(需传入身份签名密钥对)
* 内部则建一个底层 CallModule + 响应式包装 CallsModule
*/
initCalls(opts: {
signingPrivKey: Uint8Array;
signingPubKey: Uint8Array;
myAliasId: string;
alwaysRelay?: boolean;
}): void;
connect(): Promise<void>;
/** 手动断开 WS(调试 / 省电模式) */
disconnect(): void;
get isReady(): boolean;
}
export { type CallOptions, type CallState, CallsModule, type ChannelPostEvent, type ChannelTradeOrder, ChannelsModule, ContactsModule, type ConversationSummary, type FriendProfile, type GoawayEvent, type Identity, type KeyPair, type MediaKind, MediaModule, type MessageStatus, type MessageStatusEvent, MessagesModule, type NetworkState, type Observable, type Observer, type OrderStatus, type OutboxIntent, type OutgoingMessage, type PaymentConfirmedEvent, type PublicEventBus, type PublicExtendedEventBus, type PurchaseOrder, type PushDiagnostics, PushModule, PushNotificationError, type QuarantinedMessage, type ReserveOrder, type SDKError, type SDKErrorKind, SecureChatClient, type SecureChatClientOptions, type SecurityCode, SecurityService, type SessionRecord, type SessionTrustState, type StoredIdentity, type StoredMessage, type Subscribable, type Subscription, type SyncState, type TrustState, type TypingEvent, type UploadPhase, type UploadProgress, type VanityItem, VanityModule, clearIdentity, computeDirectionalCode, computeSecurityCode, computeSharedSecret, deleteSession, deriveIdentity, formatSecurityCode, fromBase64, fromHex, listSessions, loadIdentity, loadSession, markMyVerified, markSessionMyVerified, markSessionVerified, maybeMarkSessionVerified, newMnemonic, normalizeDirectionalCode, normalizeSecurityCode, resetSessionTrust, toBase64, toHex, validateMnemonicWords, verifyReset };