Some improvements to the type system
This commit is contained in:
@ -1,105 +1,128 @@
|
||||
//@template-ignore
|
||||
import { VerificationError } from "./ts_base";
|
||||
//@template-ignore
|
||||
import { type RequestObject, type ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
|
||||
|
||||
import {
|
||||
//@template-ignore
|
||||
type RequestObject,
|
||||
//@template-ignore
|
||||
type ResponseObject,
|
||||
//@template-ignore
|
||||
Logging,
|
||||
//@template-ignore
|
||||
} from "./ts_service_base";
|
||||
|
||||
export type IMessageCallback = (data: any) => void;
|
||||
|
||||
export type ResponseListener = {
|
||||
ok: (response:any)=>void;
|
||||
err: (error: Error)=>void;
|
||||
}
|
||||
ok: (response: any) => void;
|
||||
err: (error: Error) => void;
|
||||
};
|
||||
|
||||
export class Service {
|
||||
public _name: string = null as any;
|
||||
public _name: string = null as any;
|
||||
|
||||
constructor(protected _provider: ServiceProvider, name: string) {
|
||||
this._name = name;
|
||||
this._provider.services.set(name, this);
|
||||
}
|
||||
constructor(
|
||||
protected _provider: ServiceProvider,
|
||||
name: string,
|
||||
) {
|
||||
this._name = name;
|
||||
this._provider.services.set(name, this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ServiceProvider {
|
||||
services = new Map<string, Service>();
|
||||
requests = new Map<string, ResponseListener |undefined>();
|
||||
services = new Map<string, Service>();
|
||||
requests = new Map<string, ResponseListener | undefined>();
|
||||
|
||||
constructor(private sendPacket: IMessageCallback) {}
|
||||
constructor(private sendPacket: IMessageCallback) {}
|
||||
|
||||
onPacket(msg: RequestObject | ResponseObject) {
|
||||
Logging.log("CLIENT: Received message:", msg);
|
||||
if("method" in msg) {
|
||||
if(msg.id){
|
||||
Logging.log("CLIENT: Determined type is Request");
|
||||
// Request, which are not supported by client, so ignore
|
||||
return;
|
||||
} else {
|
||||
Logging.log("CLIENT: Determined type is Notification");
|
||||
//Notification. Send to Notification handler
|
||||
const [srvName, fncName] = msg.method.split(".");
|
||||
let service = this.services.get(srvName)
|
||||
if(!service) {
|
||||
Logging.log("CLIENT: Did not find Service wanted by Notification!", srvName);
|
||||
} else {
|
||||
//TODO: Implement Event thingy (or so :))
|
||||
}
|
||||
}
|
||||
onPacket(msg: RequestObject | ResponseObject) {
|
||||
Logging.log("CLIENT: Received message:", msg);
|
||||
if ("method" in msg) {
|
||||
if (msg.id) {
|
||||
Logging.log("CLIENT: Determined type is Request");
|
||||
// Request, which are not supported by client, so ignore
|
||||
return;
|
||||
} else {
|
||||
Logging.log("CLIENT: Determined type is Response");
|
||||
// Response
|
||||
let resListener = this.requests.get(msg.id);
|
||||
if(!resListener) return; // Ignore wrong responses
|
||||
if(msg.error) {
|
||||
if(msg.error.data && msg.error.data.$ == "verification_error") {
|
||||
resListener.err(new VerificationError(msg.error.data.type, msg.error.data.field, msg.error.data.value))
|
||||
} else {
|
||||
resListener.err(new Error(msg.error.message));
|
||||
}
|
||||
} else {
|
||||
resListener.ok(msg.result);
|
||||
}
|
||||
Logging.log("CLIENT: Determined type is Notification");
|
||||
//Notification. Send to Notification handler
|
||||
const [srvName, fncName] = msg.method.split(".");
|
||||
let service = this.services.get(srvName);
|
||||
if (!service) {
|
||||
Logging.log(
|
||||
"CLIENT: Did not find Service wanted by Notification!",
|
||||
srvName,
|
||||
);
|
||||
} else {
|
||||
//TODO: Implement Event thingy (or so :))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Logging.log("CLIENT: Determined type is Response");
|
||||
// Response
|
||||
let resListener = this.requests.get(msg.id);
|
||||
if (!resListener) return; // Ignore wrong responses
|
||||
if (msg.error) {
|
||||
if (msg.error.data && msg.error.data.$ == "verification_error") {
|
||||
resListener.err(
|
||||
new VerificationError(
|
||||
msg.error.data.type,
|
||||
msg.error.data.field,
|
||||
msg.error.data.value,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
resListener.err(new Error(msg.error.message));
|
||||
}
|
||||
} else {
|
||||
resListener.ok(msg.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(msg: RequestObject, res?: ResponseListener) {
|
||||
Logging.log("CLIENT: Sending Messgage", msg);
|
||||
if(msg.id) {
|
||||
this.requests.set(msg.id, res);
|
||||
}
|
||||
this.sendPacket(msg)
|
||||
}
|
||||
sendNotification(method: string, params: any[]) {
|
||||
Logging.log("CLIENT: Sending Notification", method, params);
|
||||
this.sendPacket({
|
||||
jsonrpc: "2.0",
|
||||
method,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
sendRequest(method: string, params: any[], res?: ResponseListener) {
|
||||
Logging.log("CLIENT: Sending Request", method, params);
|
||||
const id = getRandomID(16);
|
||||
this.requests.set(id, res);
|
||||
this.sendPacket({
|
||||
jsonrpc: "2.0",
|
||||
method,
|
||||
params,
|
||||
id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
declare var require: any;
|
||||
export const getRandomBytes = (
|
||||
typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
|
||||
? function () {
|
||||
// Browsers
|
||||
var crypto = self.crypto || (self as any).msCrypto;
|
||||
var QUOTA = 65536;
|
||||
return function (n: number) {
|
||||
var a = new Uint8Array(n);
|
||||
for (var i = 0; i < n; i += QUOTA) {
|
||||
crypto.getRandomValues(
|
||||
a.subarray(i, i + Math.min(n - i, QUOTA))
|
||||
);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
}
|
||||
: function () {
|
||||
// Node
|
||||
return require("crypto").randomBytes;
|
||||
}
|
||||
typeof self !== "undefined" && (self.crypto || (self as any).msCrypto)
|
||||
? function () {
|
||||
// Browsers
|
||||
var crypto = self.crypto || (self as any).msCrypto;
|
||||
var QUOTA = 65536;
|
||||
return function (n: number) {
|
||||
var a = new Uint8Array(n);
|
||||
for (var i = 0; i < n; i += QUOTA) {
|
||||
crypto.getRandomValues(a.subarray(i, i + Math.min(n - i, QUOTA)));
|
||||
}
|
||||
return a;
|
||||
};
|
||||
}
|
||||
: function () {
|
||||
// Node
|
||||
return require("crypto").randomBytes;
|
||||
}
|
||||
)() as (cnt: number) => Uint8Array;
|
||||
|
||||
export const getRandomID = (length: number) => {
|
||||
return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any));
|
||||
return btoa(String.fromCharCode.apply(null, getRandomBytes(length) as any));
|
||||
};
|
||||
|
@ -1,130 +1,162 @@
|
||||
//@template-ignore
|
||||
import { VerificationError } from "./ts_base";
|
||||
//@template-ignore
|
||||
import { type RequestObject, type ResponseObject, ErrorCodes, Logging } from "./ts_service_base";
|
||||
|
||||
import {
|
||||
//@template-ignore
|
||||
type RequestObject,
|
||||
//@template-ignore
|
||||
type ResponseObject,
|
||||
//@template-ignore
|
||||
ErrorCodes,
|
||||
//@template-ignore
|
||||
Logging,
|
||||
//@template-ignore
|
||||
} from "./ts_service_base";
|
||||
|
||||
export class Service<T> {
|
||||
public name: string = null as any;
|
||||
public functions = new Set<string>();
|
||||
public name: string = null as any;
|
||||
public functions = new Set<string>();
|
||||
|
||||
constructor() { }
|
||||
_into_parameters(params: string[], value: any[] | any): any[] {
|
||||
let p: any[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
p = params;
|
||||
if (p.length > params.length) {
|
||||
throw new VerificationError(`Too many parameters provided`);
|
||||
}
|
||||
|
||||
while (p.length < params.length) {
|
||||
p.push(undefined);
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
p = params.map((p) => value[p]);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
type ISendMessageCB = (data: any, catchedErr?: Error) => void;
|
||||
|
||||
export class ServiceProvider<T = any> {
|
||||
services = new Map<string, Service<T>>();
|
||||
addService(service: Service<T>) {
|
||||
this.services.set(service.name, service);
|
||||
Logging.log("SERVER: Adding Service to provider:", service.name);
|
||||
Logging.log("SERVER: Service provides:", [...service.functions.keys()])
|
||||
}
|
||||
services = new Map<string, Service<T>>();
|
||||
addService(service: Service<T>) {
|
||||
this.services.set(service.name, service);
|
||||
Logging.log("SERVER: Adding Service to provider:", service.name);
|
||||
Logging.log("SERVER: Service provides:", [...service.functions.keys()]);
|
||||
}
|
||||
|
||||
getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> {
|
||||
return new Session(this, send, ctx);
|
||||
}
|
||||
getSession(send: ISendMessageCB, ctx?: Partial<T>): Session<T> {
|
||||
return new Session(this, send, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
class Session<T> {
|
||||
ctx: Partial<T>;
|
||||
ctx: Partial<T>;
|
||||
|
||||
constructor(
|
||||
private provider: ServiceProvider,
|
||||
private _send: ISendMessageCB,
|
||||
ctx?: Partial<T>
|
||||
) {
|
||||
this.ctx = ctx || {};
|
||||
}
|
||||
constructor(
|
||||
private provider: ServiceProvider,
|
||||
private _send: ISendMessageCB,
|
||||
ctx?: Partial<T>,
|
||||
) {
|
||||
this.ctx = ctx || {};
|
||||
}
|
||||
|
||||
send(data: any, catchedErr?: Error) {
|
||||
Logging.log("SERVER: Sending Message", data)
|
||||
this._send(data, catchedErr);
|
||||
}
|
||||
send(data: any, catchedErr?: Error) {
|
||||
Logging.log("SERVER: Sending Message", data);
|
||||
this._send(data, catchedErr);
|
||||
}
|
||||
|
||||
async onMessage(data: RequestObject) {
|
||||
Logging.log("SERVER: Received Message", data);
|
||||
try {
|
||||
if (!data.method) {
|
||||
if (data.id) {
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.InvalidRequest,
|
||||
message: "No method defined!",
|
||||
},
|
||||
} as ResponseObject);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const [srvName, fncName] = data.method.split(".");
|
||||
Logging.log("SERVER: Message for", srvName, fncName);
|
||||
|
||||
const service = this.provider.services.get(srvName);
|
||||
if (!service) {
|
||||
Logging.log("SERVER: Did not find Service");
|
||||
if (data.id) {
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.MethodNotFound,
|
||||
message: "Service not found!",
|
||||
},
|
||||
} as ResponseObject);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const fnc = service.functions.has(fncName);
|
||||
if (!fnc) {
|
||||
Logging.log("SERVER: Did not find Function");
|
||||
if (data.id) {
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.MethodNotFound,
|
||||
message: "Function not found!",
|
||||
},
|
||||
} as ResponseObject);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let result = await (service as any)["_" + fncName](data.params, this.ctx);
|
||||
if (data.id) { //Request
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
result: result,
|
||||
} as ResponseObject);
|
||||
} //else Notification and response is ignored
|
||||
} catch (err) {
|
||||
if (data.id) {
|
||||
this.send(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.InternalError,
|
||||
message: err.message,
|
||||
data: err instanceof VerificationError ? {
|
||||
$: "verification_error",
|
||||
type: err.type,
|
||||
field: err.field,
|
||||
value: err.value
|
||||
} : {
|
||||
$: "unknown_error"
|
||||
},
|
||||
},
|
||||
} as ResponseObject,
|
||||
err
|
||||
);
|
||||
}
|
||||
//TODO: Think about else case
|
||||
async onMessage(data: RequestObject) {
|
||||
Logging.log("SERVER: Received Message", data);
|
||||
try {
|
||||
if (!data.method) {
|
||||
if (data.id) {
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.InvalidRequest,
|
||||
message: "No method defined!",
|
||||
},
|
||||
} as ResponseObject);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const [srvName, fncName] = data.method.split(".");
|
||||
Logging.log("SERVER: Message for", srvName, fncName);
|
||||
|
||||
const service = this.provider.services.get(srvName);
|
||||
if (!service) {
|
||||
Logging.log("SERVER: Did not find Service");
|
||||
if (data.id) {
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.MethodNotFound,
|
||||
message: "Service not found!",
|
||||
},
|
||||
} as ResponseObject);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const fnc = service.functions.has(fncName);
|
||||
if (!fnc) {
|
||||
Logging.log("SERVER: Did not find Function");
|
||||
if (data.id) {
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.MethodNotFound,
|
||||
message: "Function not found!",
|
||||
},
|
||||
} as ResponseObject);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let result = await (service as any)["_" + fncName](data.params, this.ctx);
|
||||
if (data.id) {
|
||||
//Request
|
||||
this.send({
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
result: result,
|
||||
} as ResponseObject);
|
||||
} //else Notification and response is ignored
|
||||
} catch (err) {
|
||||
if (data.id) {
|
||||
this.send(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id: data.id,
|
||||
error: {
|
||||
code: ErrorCodes.InternalError,
|
||||
message: err.message,
|
||||
data:
|
||||
err instanceof VerificationError
|
||||
? {
|
||||
$: "verification_error",
|
||||
type: err.type,
|
||||
field: err.field,
|
||||
value: err.value,
|
||||
}
|
||||
: {
|
||||
$: "unknown_error",
|
||||
},
|
||||
},
|
||||
} as ResponseObject,
|
||||
err,
|
||||
);
|
||||
}
|
||||
//TODO: Think about else case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user