padloc/packages/core/src/client.ts

109 lines
3.3 KiB
TypeScript

import { API, PromiseWithProgress } from "./api";
import { Sender, Request, Response, RequestProgress } from "./transport";
import { DeviceInfo } from "./platform";
import { Session } from "./session";
import { Account } from "./account";
import { Err, ErrorCode } from "./error";
import { Serializable } from "./encoding";
/**
* Client state, keeping track of [[session]], [[account]] and [[device]] info
*/
export interface ClientState {
/** Current session */
session: Session | null;
/** Currently logged in account */
account: Account | null;
/** info about current device */
device: DeviceInfo;
}
/**
* Client-side interface for Client-Server communication. Manages serialization,
* authentication and some state like current session and account.
*/
export class Client extends API {
online = true;
constructor(
/** Object for storing state */
public state: ClientState,
/** [[Sender]] implementation used for sending/receiving requests */
private sender: Sender,
private hook?: (req: Request, res: Response | null, err: Err | null) => void
) {
super();
for (const { method, output } of this.handlerDefinitions) {
this[method] = (input: Serializable | string | undefined) => {
const progress = new RequestProgress();
const promise = this.call(
method,
typeof input === "undefined" ? [] : [input instanceof Serializable ? input.toRaw() : input],
progress
).then((res) => {
return output
? Array.isArray(res.result)
? res.result.map((each) => new output().fromRaw(each))
: new output().fromRaw(res.result)
: res.result;
}) as PromiseWithProgress<any>;
promise.progress = progress;
return promise;
};
}
}
/** The current session */
get session() {
return this.state.session;
}
/** Generic method for making an RPC call */
async call(method: string, params?: any[], progress?: RequestProgress) {
const { session } = this.state;
const req = new Request();
req.method = method;
req.params = params;
req.device = this.state.device;
if (session) {
await session.authenticate(req);
}
let res;
try {
res = await this.sender.send(req, progress);
} catch (e) {
if (progress) {
progress.error = e;
}
this.hook && this.hook(req, null, e);
throw e;
}
if (res.error) {
const err = new Err(res.error.code as any as ErrorCode, res.error.message);
if (progress) {
progress.error = err;
}
this.hook && this.hook(req, res, err);
throw err;
}
if (session && !(await session.verify(res))) {
const err = new Err(ErrorCode.INVALID_RESPONSE);
if (progress) {
progress.error = err;
}
this.hook && this.hook(req, res, err);
throw err;
}
this.hook && this.hook(req, res, null);
return res;
}
}