Some improvements to the type system

This commit is contained in:
Fabian Stamm
2025-05-27 17:30:13 +02:00
parent 45ebb2c0d7
commit 0e73f7b5b3
6 changed files with 649 additions and 358 deletions

View File

@ -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));
};

View File

@ -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
}
}
}