Files
LingChair/client-protocol/LingChairClient.ts
CrescentLeaf 149f003175 fix: typo
2025-11-09 10:39:06 +08:00

164 lines
5.0 KiB
TypeScript

// deno-lint-ignore-file no-explicit-any
import { io, ManagerOptions, Socket, SocketOptions } from 'socket.io-client'
import crypto from 'node:crypto'
import { CallMethod, ClientEvent } from './ApiDeclare.ts'
import ApiCallbackMessage from './ApiCallbackMessage.ts'
import User from "./User.ts"
import UserMySelf from "./UserMySelf.ts"
import CallbackError from "./CallbackError.ts"
import Chat from "./Chat.ts"
export {
User,
Chat,
UserMySelf,
}
export default class LingChairClient {
declare client: Socket
declare access_token: string
declare refresh_token?: string
constructor(args: {
server_url: string
device_id: string,
io?: Partial<ManagerOptions & SocketOptions>
}) {
this.client = io(args.server_url, {
transports: ["polling", "websocket", "webtransport"],
...args.io,
auth: {
...args.io?.auth,
device_id: args.device_id,
session_id: crypto.randomUUID(),
},
})
this.client.on("The_White_Silk", (name: string, data: unknown, _callback: (ret: unknown) => void) => {
try {
if (name == null || data == null) return
this.events[name]?.forEach((v) => v(data))
} catch (e) {
console.error(e)
}
})
}
connect() {
this.client.connect()
}
disconnect() {
this.client.disconnect()
}
reconnect() {
this.disconnect()
this.connect()
}
invoke(method: CallMethod, args: object = {}, timeout: number = 10000): Promise<ApiCallbackMessage> {
return new Promise((resolve) => {
this.client!.timeout(timeout).emit("The_White_Silk", method, args, (err: Error, res: ApiCallbackMessage) => {
// 错误处理
if (err) return resolve({
code: -1,
msg: err.message,
})
resolve(res)
})
})
}
events: { [key: string]: ((data: any) => void)[] } = {}
on(eventName: ClientEvent, func: (data: any) => void) {
if (this.events[eventName] == null)
this.events[eventName] = []
if (this.events[eventName].indexOf(func) == -1)
this.events[eventName].push(func)
}
off(eventName: ClientEvent, func: (data: any) => void) {
if (this.events[eventName] == null)
this.events[eventName] = []
const index = this.events[eventName].indexOf(func)
if (index != -1)
this.events[eventName].splice(index, 1)
}
async auth(args: {
refresh_token?: string,
access_token?: string,
account?: string,
password?: string,
}) {
try {
await this.authOrThrow(args)
return true
} catch (_) {
return false
}
}
async authOrThrow(args: {
refresh_token?: string,
access_token?: string,
account?: string,
password?: string,
}) {
if ((!args.access_token && !args.refresh_token) && (!args.account && !args.password))
throw new Error('Access/Refresh token or account & password required')
this.refresh_token = args.refresh_token
let access_token = args.access_token
if (!access_token && args.refresh_token) {
const re = await this.invoke('User.refreshAccessToken', {
refresh_token: args.refresh_token,
})
if (re.code == 200)
access_token = re.data!.access_token as string | undefined
else
throw new CallbackError(re)
}
if (!access_token && (args.account && args.password)) {
const re = await this.invoke('User.login', {
account: args.account,
password: crypto.createHash('sha256').update(args.password).digest('hex'),
})
if (re.code == 200)
access_token = re.data!.access_token as string | undefined
else
throw new CallbackError(re)
}
const re = await this.invoke('User.auth', {
access_token: access_token
})
if (re.code == 200)
this.access_token = access_token as string
else
throw new CallbackError(re)
}
async register(args: {
nickname: string,
username?: string,
password: string,
}) {
try {
await this.registerOrThrow(args)
return true
} catch (_) {
return false
}
}
async registerOrThrow({
nickname,
username,
password,
}: {
nickname: string,
username?: string,
password: string,
}) {
const re = await this.invoke('User.register', {
nickname,
username,
password,
})
if (re.code != 200)
throw new CallbackError(re)
}
}