padloc/packages/core/src/transport.ts

158 lines
4.1 KiB
TypeScript

import { Serializable, AsSerializable, AsDate, AsBytes } from "./encoding";
import { Server } from "./server";
import { DeviceInfo } from "./platform";
import { EventEmitter } from "./event-target";
import { wait } from "./util";
/** Authentication data */
export class RequestAuthentication extends Serializable {
/** The id of the [[Session]] used for authentication */
session: string = "";
/** A time stamp of the request/response time */
@AsDate()
time!: Date;
/** The signature used to verify the authentiation */
@AsBytes()
signature!: Uint8Array;
constructor(vals: Partial<RequestAuthentication> = {}) {
super();
Object.assign(this, vals);
}
}
/** RPC request object */
export class Request extends Serializable {
/** Name of the method call */
method: string = "";
/** Arguments for the method */
params?: any[] = undefined;
/** Data used to authenticate the request */
@AsSerializable(RequestAuthentication)
auth?: RequestAuthentication;
/** Info about the device the request is coming from */
@AsSerializable(DeviceInfo)
device?: DeviceInfo;
ipAddress?: string;
location?: {
city?: string;
country?: string;
};
}
/** RPC response object */
export class Response extends Serializable {
/** The result of the RPC call */
result: any = null;
/** Error info, if an error occurred while processing the request */
error?: Error = undefined;
/** Data used to authenticate the response */
@AsSerializable(RequestAuthentication)
auth?: RequestAuthentication;
}
/** Error info */
export interface Error {
/** Error code */
code: number | string;
/** Error message */
message: string;
}
/** Generic interface for sending [[Request]]s */
export interface Sender {
send(req: Request, progress?: RequestProgress): Promise<Response>;
}
/** Generic interface for receiving [[Request]]s and processing them into a [[Response]] */
export interface Receiver {
listen(handler: (req: Request) => Promise<Response>): void;
}
/**
* Stub implementation of the [[Sender]] interface, passing requests directly
* into a [[Server]] instance. this is useful for testing, where client and
* server instances are managed by the same process
*/
export class DirectSender implements Sender {
constructor(private server: Server) {}
async send(req: Request) {
await wait(10);
return this.server.handle(req);
}
}
interface Progress {
loaded: number;
total: number;
}
export class RequestProgress extends EventEmitter {
completed: Promise<void> = new Promise((resolve, reject) => {
this._resolveCompleted = resolve;
this._rejectCompleted = reject;
});
get uploadProgress() {
return this._uploadProgress;
}
set uploadProgress(progress: Progress) {
this._uploadProgress = progress;
this.dispatch("upload-progress", progress);
this.dispatch("progress", this.progress);
}
get downloadProgress() {
return this._downloadProgress;
}
set downloadProgress(progress: Progress) {
this._downloadProgress = progress;
this.dispatch("download-progress", progress);
this.dispatch("progress", this.progress);
}
get progress() {
return {
loaded: this.uploadProgress.loaded + this.downloadProgress.loaded,
total: this.uploadProgress.total + this.downloadProgress.total,
};
}
set error(error: Error | undefined) {
this._error = error;
if (error) {
this.dispatch("error", { error });
this.uploadProgress = this.downloadProgress = { loaded: 0, total: 0 };
this._rejectCompleted(error);
}
}
get error() {
return this._error;
}
private _uploadProgress: Progress = { loaded: 0, total: 0 };
private _downloadProgress: Progress = { loaded: 0, total: 0 };
private _error?: Error;
private _resolveCompleted!: () => void;
private _rejectCompleted!: (err: Error) => void;
complete() {
this._resolveCompleted();
}
}