@siemsiem/tonpleun 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@siemsiem/tonpleun",
3
+ "version": "0.1.0",
4
+ "description": "uhhh dit zijn zeer goede docs (nee) tonpleun is een websocket server die websocket clients verzoeken laat maken aan andere websocket clients die zich als service registreren. voor nu is tonpleun in js maar wrs moet ik het porten naar python omdat ik dan meer code kan hergebruiken voor andere projecten.",
5
+ "keywords": [],
6
+ "homepage": "https://github.com/supersiem/Tonpleun#readme",
7
+ "bugs": {
8
+ "url": "https://github.com/supersiem/Tonpleun/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/supersiem/Tonpleun.git"
13
+ },
14
+ "license": "UNLICENSED",
15
+ "author": "Siemvk",
16
+ "type": "module",
17
+ "main": "index.js",
18
+ "scripts": {
19
+ "start": "tsc && clear && node ./dist/server.js",
20
+ "test": "tsc && clear && node ./dist/client.js",
21
+ "testDeTest": "tsc && clear && node ./dist/client2.js",
22
+ "build": "tsc"
23
+ },
24
+ "dependencies": {
25
+ "@types/ws": "^8.18.1",
26
+ "ws": "^8.18.3"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.9.3"
30
+ },
31
+ "packageManager": "pnpm@10.12.1"
32
+ }
package/readme.md ADDED
@@ -0,0 +1,28 @@
1
+ # tonpleun
2
+
3
+ uhhh dit zijn zeer goede docs (nee)
4
+ tonpleun is een websocket server die websocket clients verzoeken laat maken aan andere websocket clients die zich als service registreren.
5
+ voor nu is tonpleun in js maar wrs moet ik het porten naar python omdat ik dan meer code kan hergebruiken voor andere projecten.
6
+
7
+ ## Installatie
8
+
9
+ gebruik dit gewoon niet...
10
+
11
+ maar als je het toch wilt gebruiken download de repo compile het met tsc en run main.js met node.
12
+
13
+ ## Gebruik
14
+
15
+ gebruik het niet
16
+ maar als je onverstandig genoeg bent om het toch te gebruiken gebruik lib.ts om een client te maken die verbinding maakt met de tonpleun server.
17
+
18
+ ## Configuratie
19
+
20
+ tonpleun heeft geen configuratie opties.
21
+
22
+ ## Licentie
23
+
24
+ heel eerlijk het boeit me niet wat je doet met deze slechte code dus doe er mee wat je wilt.
25
+
26
+ ## Bijdragen
27
+
28
+ uhhhhh maak een pr ofzo.
package/src/client.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { getService, initializeClient, registerService, ws, registerConfigItem, SetConfigItem, getConfigValue } from './clientLib.js'
2
+ import assert from 'node:assert/strict';
3
+ function echo(args: any[]) {
4
+ console.log(args)
5
+ return 'pindakaas'
6
+ }
7
+ async function main() {
8
+ await initializeClient('testClient');
9
+ console.log(await registerService('echo', ['string'], echo))
10
+ console.log(await getService('getServices', 'tonpleun', []))
11
+ // Config tests: register and update a config item, then verify via tonpleun service
12
+ await registerConfigItem('greeting', 'Simple greeting config', 'hello', 'conf:greeting');
13
+ const configsAfterRegister: any = await getService('getConfigs', 'tonpleun', []);
14
+ assert.ok(configsAfterRegister['testClient'], 'configs for testClient should exist');
15
+ assert.ok(configsAfterRegister['testClient']['conf:greeting'], 'conf:greeting should be registered');
16
+ assert.equal(configsAfterRegister['testClient']['conf:greeting'].defaultValue, 'hello');
17
+ console.log('[Client1] registered config OK');
18
+
19
+ await SetConfigItem('conf:greeting', 'hi');
20
+ const configsAfterSet: any = await getService('getConfigs', 'tonpleun', []);
21
+ assert.equal(configsAfterSet['testClient']['conf:greeting'].value, 'hi');
22
+ console.log('[Client1] set config value OK');
23
+ assert.equal(getConfigValue('conf:greeting'), 'hi');
24
+ console.log('[Client1] local config value OK');
25
+ // Wait for a potential remote update (e.g., from Client2) and verify locally
26
+ await new Promise<void>((resolve) => {
27
+ const expected = 'bye';
28
+ const start = Date.now();
29
+ const interval = setInterval(() => {
30
+ const current = getConfigValue('conf:greeting');
31
+ if (current === expected) {
32
+ clearInterval(interval);
33
+ console.log('[Client1] remote config update applied OK');
34
+ resolve();
35
+ } else if (Date.now() - start > 5000) {
36
+ clearInterval(interval);
37
+ console.warn('[Client1] timeout waiting remote update; current:', current);
38
+ resolve();
39
+ }
40
+ }, 200);
41
+ });
42
+ }
43
+ main();
package/src/client2.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { getService, initializeClient, SetConfigItem } from './clientLib.js'
2
+ import assert from 'node:assert/strict';
3
+ async function main() {
4
+ await initializeClient('Client2');
5
+ const result = await getService('echo', 'testClient', ['hoi!!!!'])
6
+ console.log('[Client2] echo result:', result)
7
+ // Validate that configs registered by Client1 are visible and updated
8
+ const configs: any = await getService('getConfigs', 'tonpleun', []);
9
+ assert.ok(configs['testClient'], 'configs for testClient should exist');
10
+ assert.ok(configs['testClient']['conf:greeting'], 'conf:greeting should be present');
11
+ console.log('[Client2] observed config:', configs['testClient']['conf:greeting']);
12
+ // Trigger a remote config update for owner 'testClient' and verify server state
13
+ await SetConfigItem('conf:greeting', 'bye', 'testClient');
14
+ const configsAfterSet: any = await getService('getConfigs', 'tonpleun', []);
15
+ assert.equal(configsAfterSet['testClient']['conf:greeting'].value, 'bye');
16
+ console.log('[Client2] remote config update OK');
17
+ }
18
+ main();
@@ -0,0 +1,134 @@
1
+ import WebSocket from 'ws';
2
+ import { randomUUID } from 'crypto';
3
+ import { requestType, stringPacketOptions, type getServicePacketClient, type GetServiceResponsePacketToServer, type GetServiceResponsePacketToClient, type InitPacket, type packet, type StringPacket, type registerConfigPacket, type setConfigPacket, type fakeTypeType } from './types.js';
4
+ import { WsSend } from './helpers.js';
5
+
6
+ const url = "ws://localhost:8765";
7
+ export let ws: WebSocket;
8
+ const serviceCallbacks = new Map<string, (...args: any[]) => any>();
9
+ let clinetIDStore = "error";
10
+ const localConfigs = new Map<string, registerConfigPacket>();
11
+
12
+ export async function awaitServiceMessage(expectedFor: stringPacketOptions): Promise<StringPacket> {
13
+ return new Promise<StringPacket>((resolve) => {
14
+ const handler = (raw: Buffer) => {
15
+ const rawPacket = JSON.parse(raw.toString()) as packet;
16
+ if (rawPacket.type === requestType.Success) {
17
+ const data = rawPacket.data as StringPacket;
18
+ if (data.for === expectedFor) {
19
+ ws.removeListener('message', handler);
20
+ resolve(data);
21
+ }
22
+ }
23
+ };
24
+ ws.on('message', handler);
25
+ });
26
+ }
27
+ export async function registerConfigItem(name: string, description: string, value: string, idthing: string) {
28
+
29
+ WsSend(ws, { type: requestType.RegisterConifg, data: { name: name, description: description, defaultValue: value, type: typeof value, id: idthing } as registerConfigPacket })
30
+ // Wacht tot de verbinding is geopend
31
+ return new Promise<void>((resolve) => {
32
+ awaitServiceMessage(stringPacketOptions.registerConfigSuccess).then(() => {
33
+ localConfigs.set(idthing, { name, id: idthing, description, type: typeof value as fakeTypeType, defaultValue: value } as registerConfigPacket)
34
+ resolve();
35
+ });
36
+ });
37
+ }
38
+ export async function SetConfigItem(idthing: string, newValue: string, clientId?: string) {
39
+ WsSend(ws, { type: requestType.SetConfig, data: { ClientId: clientId || clinetIDStore, id: idthing, newValue: newValue } as setConfigPacket })
40
+ return new Promise<void>((resolve) => {
41
+ awaitServiceMessage(stringPacketOptions.setConfigSuccess).then(() => {
42
+ const existing = localConfigs.get(idthing);
43
+ if (existing) {
44
+ localConfigs.set(idthing, { ...existing, value: newValue } as registerConfigPacket);
45
+ }
46
+ resolve();
47
+ });
48
+ });
49
+ }
50
+
51
+ export async function registerService(ServiceId: string, args: fakeTypeType[], callback: (...args: any[]) => any) {
52
+ WsSend(ws, { type: requestType.RegisterService, data: { ServiceId, args } });
53
+ serviceCallbacks.set(ServiceId, callback);
54
+ return new Promise<void>((resolve) => {
55
+ awaitServiceMessage(stringPacketOptions.registerServiceSuccess).then(() => {
56
+ resolve();
57
+ });
58
+ });
59
+ }
60
+ export async function getService(ServiceId: string, ClientId: string, inputs: any[]): Promise<any> {
61
+ const connectionId = randomUUID();
62
+ WsSend(ws, { type: requestType.GetService, data: { ClientId, ServiceId, args: inputs, connectionId } as getServicePacketClient });
63
+ return new Promise<any>((resolve) => {
64
+ const handler = (raw: Buffer) => {
65
+ const rawPacket = JSON.parse(raw.toString()) as packet;
66
+ if (rawPacket.type === requestType.GetServiceResponse) {
67
+ const data = rawPacket.data as GetServiceResponsePacketToClient;
68
+ if (data.serviceId === ServiceId && data.connectionId === connectionId) {
69
+ ws.removeListener('message', handler);
70
+ resolve(data.result);
71
+ }
72
+ }
73
+ };
74
+ ws.on('message', handler);
75
+ });
76
+ }
77
+
78
+ export async function initializeClient(ClientId: string) {
79
+ clinetIDStore = ClientId;
80
+ ws = new WebSocket(url);
81
+
82
+ ws.on('open', () => {
83
+ console.log('Verbonden met tonpleun server.');
84
+ WsSend(ws, { type: requestType.Init, data: { ClientId } as InitPacket })
85
+
86
+ });
87
+ ws.on('close', () => {
88
+ console.log('Verbinding met tonpleun server gesloten.');
89
+ });
90
+ ws.on('error', (error) => {
91
+ console.error('Fout opgetreden:', error);
92
+ ws.close();
93
+ });
94
+ ws.on('message', (data) => {
95
+ const rawPacket = JSON.parse(data.toString()) as packet
96
+ if (rawPacket.type === requestType.GetService) {
97
+ const serviceData = rawPacket.data as getServicePacketClient;
98
+ const callback = serviceCallbacks.get(serviceData.ServiceId);
99
+ if (callback) {
100
+ try {
101
+ const result = callback(...serviceData.args);
102
+ WsSend(ws, {
103
+ type: requestType.GetServiceResponse, data: {
104
+ result: result,
105
+ ServiceId: serviceData.ServiceId,
106
+ connectionId: serviceData.connectionId,
107
+ } as GetServiceResponsePacketToServer
108
+ });
109
+ } catch (error) {
110
+ console.error(`Fout bij uitvoeren van service ${serviceData.ServiceId}:`, error);
111
+ }
112
+ }
113
+
114
+ } else if (rawPacket.type === requestType.SetConfig) {
115
+ const cfg = rawPacket.data as setConfigPacket;
116
+ const existing = localConfigs.get(cfg.id);
117
+ if (existing) {
118
+ localConfigs.set(cfg.id, { ...existing, value: cfg.newValue } as registerConfigPacket);
119
+ }
120
+ }
121
+ });
122
+
123
+ // Wacht tot de verbinding is geopend
124
+ return new Promise<void>((resolve) => {
125
+ awaitServiceMessage(stringPacketOptions.initSuccess).then(() => {
126
+ resolve();
127
+ });
128
+ });
129
+ }
130
+
131
+ export function getConfigValue(id: string): any | undefined {
132
+ const item = localConfigs.get(id);
133
+ return item ? (item.value ?? item.defaultValue) : undefined;
134
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type WebSocket from "ws";
2
+ import { requestType, stringPacketOptions, type packet, type StringPacket } from "./types.js";
3
+ export function log(id: any, ...args: any) {
4
+ // .toString() als je niet groen wil
5
+ console.log('[', id, ']', args);
6
+ }
7
+ export function WsSend(ws: WebSocket, data: packet) {
8
+ ws.send(JSON.stringify(data));
9
+ }
10
+ export function successPacketBuilder(msg: string, B: stringPacketOptions) {
11
+ return { type: requestType.Success, data: { msg: msg, for: B } as StringPacket } as packet
12
+ }
package/src/server.ts ADDED
@@ -0,0 +1,125 @@
1
+ import { log, successPacketBuilder, WsSend } from './helpers.js';
2
+ import { WebSocketServer, WebSocket } from 'ws';
3
+ import { requestType, stringPacketOptions, type args, type getServicePacket, type getServicePacketClient, type GetServiceResponsePacketToClient, type InitPacket, type packet, type registerConfigPacket, type RegisterServicePacket, type setConfigPacket } from './types.js';
4
+
5
+ function mapToObject(map: Map<any, any>) {
6
+ return Object.fromEntries([...map.entries()].map(([kMaxLength, v]): any => [kMaxLength, v instanceof Map ? mapToObject(v) : v]))
7
+ }
8
+
9
+ let clients: Record<string, WebSocket> = {};
10
+ let services = new Map<string, Map<string, args>>();
11
+ let configs = new Map<string, Map<string, registerConfigPacket>>();
12
+ let localServices = new Map<string, (...args: any[]) => any>();
13
+ localServices.set('getServices', (...args: any[]) => { return mapToObject(services) })
14
+ localServices.set('getConfigs', (...args: any[]) => { return mapToObject(configs) })
15
+ // Map a unique connectionId to the original requester WebSocket
16
+ const connectionMap = new Map<string, WebSocket>();
17
+ const wsServer = new WebSocketServer({ host: '0.0.0.0', port: 8765 });
18
+ wsServer.on('connection', (ws, req) => {
19
+ let id = req.socket.remoteAddress as string;
20
+ log(id, 'ws verbonden wachten op init.');
21
+
22
+ ws.on('message', async (raw) => {
23
+ const jsonData = JSON.parse(raw.toString()) as packet;
24
+ let data;
25
+ switch (jsonData.type) {
26
+ case requestType.Init:
27
+ data = jsonData.data as InitPacket;
28
+ clients[data.ClientId] = ws;
29
+ id = data.ClientId;
30
+ services.set(id, new Map())
31
+ // initialize per-client config store to avoid undefined access
32
+ configs.set(id, new Map())
33
+ log(id, 'ws init gedaan, client id gegeven. ip: ', req.socket.remoteAddress);
34
+ WsSend(ws, successPacketBuilder('init gedaan.', stringPacketOptions.initSuccess));
35
+ break;
36
+ case requestType.RegisterService:
37
+ data = jsonData.data as RegisterServicePacket;
38
+ services.get(id)!.set(data.ServiceId, data.args);
39
+ log(id, `service ${data.ServiceId} geregistreerd.`);
40
+ WsSend(ws, successPacketBuilder(`service ${data.ServiceId} geregistreerd.`, stringPacketOptions.registerServiceSuccess));
41
+ break;
42
+ case requestType.GetService:
43
+ data = jsonData.data as getServicePacket;
44
+ log(id, `service ${data.ServiceId} opgevraagd bij client ${data.ClientId}.`);
45
+ if (!(data.ClientId == "tonpleun")) {
46
+ const serviceOwnerWs = clients[data.ClientId];
47
+ if (serviceOwnerWs && services.get(data.ClientId)?.has(data.ServiceId)) {
48
+ // store mapping from this request's connectionId to the original requester
49
+ connectionMap.set(data.connectionId, ws);
50
+ WsSend(serviceOwnerWs, {
51
+ type: requestType.GetService,
52
+ data: {
53
+ ServiceId: data.ServiceId,
54
+ args: data.args,
55
+ connectionId: data.connectionId,
56
+ } as getServicePacketClient
57
+ });
58
+ } else {
59
+ log(id, `service ${data.ServiceId} niet gevonden voor client ${data.ClientId}.`);
60
+ }
61
+ } else {
62
+ const returnMe = localServices.get(data.ServiceId)!(data.args);
63
+ WsSend(ws, {
64
+ type: requestType.GetServiceResponse,
65
+ data: {
66
+ result: returnMe,
67
+ connectionId: data.connectionId,
68
+ serviceId: data.ServiceId
69
+ } as GetServiceResponsePacketToClient
70
+ })
71
+ }
72
+ break;
73
+ case requestType.GetServiceResponse:
74
+ data = jsonData.data;
75
+ log(id, `antwoord voor service ${data.ServiceId} ontvangen, doorsturen naar client.`);
76
+ const originalRequesterWs = connectionMap.get(data.connectionId);
77
+ if (originalRequesterWs) {
78
+ WsSend(originalRequesterWs, {
79
+ type: requestType.GetServiceResponse,
80
+ data: {
81
+ result: data.result,
82
+ serviceId: data.ServiceId,
83
+ connectionId: data.connectionId,
84
+ } as GetServiceResponsePacketToClient
85
+ });
86
+ connectionMap.delete(data.connectionId);
87
+ }
88
+ break;
89
+ case requestType.RegisterConifg:
90
+ data = jsonData.data as registerConfigPacket;
91
+ log(id, 'registeer een nieuwe config ityem');
92
+ // fuck het we slaan de hele packet op
93
+ configs.get(id)!.set(data.id, data);
94
+ WsSend(ws, successPacketBuilder('dinges', stringPacketOptions.registerConfigSuccess))
95
+ break;
96
+ case requestType.SetConfig:
97
+ data = jsonData.data as setConfigPacket;
98
+ log(id, 'update de dinges');
99
+ const otherGuy = clients[data.ClientId]
100
+ if (otherGuy) {
101
+ const prev = configs.get(data.ClientId)!.get(data.id) as registerConfigPacket;
102
+ configs.get(data.ClientId)!.set(
103
+ data.id,
104
+ { ...prev, value: data.newValue } as registerConfigPacket
105
+ )
106
+ WsSend(otherGuy, { type: requestType.SetConfig, data: data as setConfigPacket })
107
+ }
108
+ WsSend(ws, successPacketBuilder('dinges', stringPacketOptions.setConfigSuccess))
109
+
110
+ break;
111
+ default:
112
+ log(id, 'invalid msg');
113
+ break;
114
+ }
115
+ });
116
+
117
+ ws.on('close', () => {
118
+ services.delete(id);
119
+ log(id, 'ws gesloten.')
120
+ })
121
+
122
+ });
123
+ wsServer.on('listening', () => {
124
+ console.log('klaar voor clients.')
125
+ })
package/src/types.ts ADDED
@@ -0,0 +1,73 @@
1
+ export enum requestType {
2
+ Init,
3
+ Success,
4
+ Error,
5
+ RegisterService,
6
+ GetServiceResponse,
7
+ GetService,
8
+ RegisterConifg,
9
+ SetConfig
10
+ }
11
+ export enum stringPacketOptions { Error, initSuccess, registerServiceSuccess, getServiceSuccess, registerConfigSuccess, setConfigSuccess };
12
+ export type fakeTypeType = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
13
+ export type packet = {
14
+ type: requestType,
15
+ data: any,
16
+ }
17
+ // requestType.Init
18
+ export type InitPacket = {
19
+ ClientId: string,
20
+ }
21
+ // requestType.Success en requestType.Error
22
+ export type StringPacket = {
23
+ msg: string,
24
+ for: stringPacketOptions
25
+ }
26
+ // requestType.RegisterService
27
+ export type RegisterServicePacket = {
28
+ ServiceId: string,
29
+ args: fakeTypeType[]
30
+ }
31
+ // requestType.GetService ( client1 -> server)
32
+ export type getServicePacket = {
33
+ ClientId: string,
34
+ ServiceId: string,
35
+ connectionId: string,
36
+ args: any[],
37
+ }
38
+ export type args = fakeTypeType[];
39
+ // requestType.getService (server -> client2)
40
+ export type getServicePacketClient = {
41
+ ServiceId: string,
42
+ args: any[],
43
+ connectionId: string,
44
+ }
45
+ // requestType.GetServiceResponse (client -> server)
46
+ // client2 -> server
47
+ export type GetServiceResponsePacketToServer = {
48
+ ServiceId: string,
49
+ result: any,
50
+ connectionId: string,
51
+ }
52
+ // requestType.GetServiceResponse (server -> client)
53
+ // server -> client1
54
+ export type GetServiceResponsePacketToClient = {
55
+ result: any,
56
+ serviceId: string,
57
+ connectionId: string,
58
+ }
59
+ // client -> server
60
+ export type registerConfigPacket = {
61
+ name: string,
62
+ id: string,
63
+ description: string,
64
+ type: fakeTypeType,
65
+ defaultValue: any,
66
+ value?: any
67
+ }
68
+ // server -> client
69
+ export type setConfigPacket = {
70
+ ClientId: string,
71
+ newValue: any,
72
+ id: string
73
+ }
@@ -0,0 +1,20 @@
1
+ import { config, configType, initializeClient, registerConfigItem, registerService } from "./lib.js";
2
+
3
+ async function main() {
4
+
5
+ await initializeClient();
6
+
7
+ // Init onze services
8
+ function serviceEcho(args: any[]): any {
9
+ if (config['echo']) {
10
+ console.log('Echo service aangeroepen met args:', args);
11
+ }
12
+ return args[0] || "ontvangen";
13
+ }
14
+
15
+ await registerService('test.echo', serviceEcho, ['message']);
16
+
17
+ await registerConfigItem('echo', 'gebruik echo', configType.bool, true);
18
+ }
19
+
20
+ main();
package/src/v1/lib.ts ADDED
@@ -0,0 +1,194 @@
1
+ import WebSocket from 'ws';
2
+
3
+ const url = "ws://localhost:8765";
4
+
5
+ export type Message = {
6
+ status: string;
7
+ human_readable: string;
8
+ callbackId?: number;
9
+ serviceId?: string;
10
+ args?: any[];
11
+ data?: any;
12
+ id?: string;
13
+ value?: any;
14
+
15
+ };
16
+
17
+ export enum configType {
18
+ str,
19
+ bool,
20
+ int
21
+ }
22
+
23
+ export let config: Record<string, any> = {};
24
+ // voor wie complete controle wil over ws client gedrag
25
+ export let ws: WebSocket;
26
+
27
+ export let successHandler: ((data: any) => void) | null = null;
28
+ export let errorHandler: ((error: any) => void) | null = null;
29
+ export let debugLogging: boolean = false;
30
+ let serviceHandlers: Record<string, (args: any[]) => any> = {};
31
+ let waitingForData: boolean = false;
32
+
33
+ function debugLog(...message: any[]) {
34
+ if (debugLogging) {
35
+ console.log('[DEBUG]', message);
36
+ }
37
+ }
38
+
39
+ function parseData(msg: Message): any | null {
40
+ if (ws) {
41
+ if (msg.status === "200") {
42
+ debugLog('Ontvangen data:', msg.data);
43
+ if (successHandler && !waitingForData) successHandler(msg.data);
44
+ }
45
+ else if (msg.status === "210") {
46
+ if (waitingForData && successHandler) successHandler(msg.data)
47
+ }
48
+ else if (msg.status === '700') {
49
+ debugLog('Service oproep ontvangen:', msg.callbackId);
50
+ serviceHandlers[msg.serviceId || '']?.(msg.args || []);
51
+ ws.send(JSON.stringify({
52
+ action: 'response',
53
+ callbackId: msg.callbackId,
54
+ data: msg.args,
55
+ }));
56
+ }
57
+ else if (msg.status === "600") {
58
+ debugLog('config update ontvangen')
59
+ if (msg?.id) {
60
+ config[msg?.id] = msg?.value;
61
+ }
62
+ } else {
63
+ if (errorHandler) errorHandler(msg.data)
64
+ // console.log('error code', msg.status)
65
+ }
66
+ } else {
67
+ console.error('WebSocket is niet geïnitialiseerd.');
68
+ }
69
+ }
70
+
71
+ export function registerService(serviceId: string, handler: (args: any[]) => any, args?: string[]) {
72
+ if (ws) {
73
+ serviceHandlers[serviceId] = handler;
74
+
75
+ ws.send(JSON.stringify({
76
+ action: 'register',
77
+ service: serviceId,
78
+ args: args || [],
79
+ }));
80
+ } else {
81
+ console.error('WebSocket is niet geïnitialiseerd.');
82
+ }
83
+ }
84
+ export async function registerServiceAsync(serviceId: string, handler: (args: any[]) => any, args?: string[]): Promise<any> {
85
+ if (ws) {
86
+ return new Promise((resolve, reject) => {
87
+ successHandler = resolve;
88
+ errorHandler = reject;
89
+ ws.send(JSON.stringify({
90
+ action: 'get',
91
+ service: serviceId,
92
+ args: args,
93
+ }));
94
+ });
95
+ } else {
96
+ return Promise.reject('WebSocket is niet geïnitialiseerd.');
97
+ }
98
+ }
99
+ // met callback
100
+ export function callService(serviceId: string, args: any[], onSuccess: (data: any) => void, onError: (error: any) => void) {
101
+ if (ws) {
102
+ successHandler = onSuccess;
103
+ errorHandler = onError;
104
+ waitingForData = true;
105
+ ws.send(JSON.stringify({
106
+ action: 'get',
107
+ service: serviceId,
108
+ args: args,
109
+ }));
110
+ } else {
111
+ console.error('WebSocket is niet geïnitialiseerd.');
112
+ }
113
+ }
114
+ // met promise
115
+ export async function callServiceAsync(serviceId: string, args: any[]): Promise<any> {
116
+ if (ws) {
117
+ return new Promise((resolve, reject) => {
118
+ successHandler = resolve;
119
+ errorHandler = reject;
120
+ waitingForData = true;
121
+ ws.send(JSON.stringify({
122
+ action: 'get',
123
+ service: serviceId,
124
+ args: args,
125
+ }));
126
+ });
127
+ } else {
128
+ return Promise.reject('WebSocket is niet geïnitialiseerd.');
129
+ }
130
+ }
131
+
132
+ export async function registerConfigItem(id: string, human_name: string, configType: configType, value: any): Promise<any> {
133
+ if (ws) {
134
+ config[id] = value;
135
+ return new Promise((resolve, reject) => {
136
+ successHandler = resolve;
137
+ errorHandler = reject;
138
+ ws.send(JSON.stringify({
139
+ action: "makeConfigOption",
140
+ human: human_name,
141
+ id: id,
142
+ configType: configType,
143
+ value: value
144
+ }));
145
+ });
146
+ } else {
147
+ return Promise.reject('WebSocket is niet geïnitialiseerd.d')
148
+ }
149
+ }
150
+ export async function setConfigItem(id: string, value: any) {
151
+ if (ws) {
152
+ return new Promise((resolve, reject) => {
153
+ successHandler = resolve;
154
+ errorHandler = reject;
155
+ ws.send(JSON.stringify({
156
+ action: "makeConfigOption",
157
+ id: id,
158
+ value: value
159
+ }));
160
+ });
161
+
162
+ } else {
163
+ return Promise.reject('WebSocket is niet geïnitialiseerd.')
164
+ }
165
+ }
166
+
167
+ export async function initializeClient() {
168
+ ws = new WebSocket(url);
169
+
170
+ ws.on('open', () => {
171
+ debugLog('Verbonden met de server');
172
+ console.log('Verbonden met tonpleun server.');
173
+ });
174
+
175
+ ws.on('message', (data) => {
176
+ parseData(JSON.parse(data.toString()) as Message);
177
+ });
178
+
179
+ ws.on('close', () => {
180
+ debugLog('Verbinding gesloten');
181
+ console.log('Verbinding met tonpleun server gesloten.');
182
+ });
183
+
184
+ ws.on('error', (error) => {
185
+ console.error('Fout opgetreden:', error);
186
+ });
187
+
188
+ // Wacht tot de verbinding is geopend
189
+ return new Promise<void>((resolve) => {
190
+ ws.on('open', () => {
191
+ resolve();
192
+ });
193
+ });
194
+ }
package/src/v1/main.ts ADDED
@@ -0,0 +1,224 @@
1
+ import { WebSocketServer, WebSocket } from 'ws';
2
+
3
+ type ServiceClient = 'BUILTIN' | WebSocket;
4
+
5
+ type ServiceDef = {
6
+ id: string | number;
7
+ args: string[];
8
+ client: ServiceClient;
9
+ FUNC?: (args: any[]) => any | Promise<any>;
10
+ };
11
+ enum configType {
12
+ str,
13
+ bool,
14
+ int
15
+ };
16
+ type configOption = {
17
+ id: string;
18
+ human_name: string;
19
+ configType: configType
20
+ owner: ServiceClient;
21
+ value: any;
22
+ };
23
+
24
+ const clients = new Set<WebSocket>();
25
+
26
+ function getServices(args: any[]): string[] {
27
+ return Object.keys(servicesAvailable);
28
+ };
29
+
30
+ let servicesAvailable: Record<string, ServiceDef> = {
31
+ 'connector.status': {
32
+ id: 0,
33
+ args: [],
34
+ client: 'BUILTIN',
35
+ },
36
+ 'connector.services': {
37
+ id: 1,
38
+ args: [],
39
+ client: 'BUILTIN',
40
+ FUNC: getServices,
41
+ },
42
+ 'connector.echo': {
43
+ id: 2,
44
+ args: ['message'],
45
+ client: 'BUILTIN',
46
+ FUNC: (args: any[]) => args[0] || "ontvangen",
47
+ }
48
+ };
49
+
50
+ let configOptions: Record<string, configOption> = {};
51
+
52
+ let awaitingResponses: Record<number, WebSocket> = {};
53
+
54
+ const wss = new WebSocketServer({ host: '0.0.0.0', port: 8765 });
55
+
56
+ wss.on('connection', (ws, req) => {
57
+ clients.add(ws);
58
+ console.log('Client verbonden:', req.socket.remoteAddress);
59
+
60
+ ws.on('message', async (raw) => {
61
+ const message = raw.toString();
62
+ console.log(`Ontvangen bericht van ${req.socket.remoteAddress}: ${message}`);
63
+
64
+ let data: any;
65
+ try {
66
+ data = JSON.parse(message);
67
+ } catch {
68
+ ws.send(JSON.stringify({ status: '400', human_readable: 'Ongeldige JSON' }));
69
+ return;
70
+ }
71
+
72
+ if (data?.action === 'get') {
73
+ const service = servicesAvailable[data?.service as string];
74
+
75
+ if (service) {
76
+ if (service.client === 'BUILTIN') {
77
+ if (!service.FUNC) {
78
+ ws.send(
79
+ JSON.stringify({
80
+ status: '200',
81
+ human_readable: 'online, geen functie om uit te voeren.',
82
+ data: null,
83
+ }),
84
+ );
85
+ } else {
86
+ try {
87
+ const result = await Promise.resolve(service.FUNC(data?.args || []));
88
+ ws.send(
89
+ JSON.stringify({
90
+ status: '200',
91
+ human_readable: 'success',
92
+ data: result,
93
+ }),
94
+ );
95
+ } catch {
96
+ ws.send(
97
+ JSON.stringify({
98
+ status: '500',
99
+ human_readable: 'Fout bij uitvoeren van service',
100
+ }),
101
+ );
102
+ }
103
+ }
104
+ } else {
105
+ const id = Date.now();
106
+ service.client.send(JSON.stringify({
107
+ status: '700',
108
+ human_readable: 'service call',
109
+ args: data?.args || [],
110
+ serviceId: service.id,
111
+ callbackId: id,
112
+ }));
113
+ awaitingResponses[id] = ws;
114
+ ws.send(JSON.stringify({
115
+ status: '202',
116
+ human_readable: 'Verzoek verzonden naar service client',
117
+ calbackId: id,
118
+ }));
119
+ }
120
+ } else {
121
+ ws.send(JSON.stringify({ status: '404', human_readable: 'Service niet gevonden' }));
122
+ }
123
+ }
124
+ else if (data?.action == "register") {
125
+ const newSerivice: ServiceDef = {
126
+ id: data?.service,
127
+ args: data?.args || [],
128
+ client: ws,
129
+ };
130
+ servicesAvailable[data?.service as string] = newSerivice;
131
+ ws.send(JSON.stringify({
132
+ status: '201',
133
+ human_readable: `Service ${data?.service} geregistreerd.`,
134
+ data: { serviceId: newSerivice.id },
135
+ }));
136
+
137
+ }
138
+ else if (data?.action == "response") {
139
+ const callbackId = data?.callbackId;
140
+ const responseData = data?.data;
141
+
142
+ const originalRequester = awaitingResponses[callbackId];
143
+ if (originalRequester) {
144
+ originalRequester.send(JSON.stringify({
145
+ status: '210',
146
+ human_readable: 'Response van service client',
147
+ data: responseData,
148
+ }));
149
+ delete awaitingResponses[callbackId];
150
+ } else {
151
+ ws.send(JSON.stringify({
152
+ status: '404',
153
+ human_readable: 'Oorspronkelijke aanvrager niet gevonden voor deze callbackId',
154
+ }));
155
+ }
156
+
157
+ }
158
+ else if (data?.action === "getConfigOptions") {
159
+ ws.send(JSON.stringify({ status: "200", human_readable: "config data", data: configOptions }))
160
+ } else if (data?.action === "makeConfigOption") {
161
+ configOptions[data?.id] = {
162
+ id: data?.id,
163
+ human_name: data?.human,
164
+ owner: ws,
165
+ configType: data?.configType,
166
+ value: data?.value
167
+ }
168
+ ws.send(JSON.stringify({ status: '200', human_readable: "gemaakt!" }))
169
+ } else if (data?.action === "setConfig") {
170
+ if (data?.id && data?.value) {
171
+ let c = configOptions[data.id];
172
+ if (!(c)) {
173
+ ws.send(JSON.stringify({
174
+ status: '404',
175
+ human_readable: 'config item niet gevonden',
176
+ }));
177
+ } else {
178
+ c.value = data.value;
179
+ configOptions[data.id] = c;
180
+ if (!(c.owner === "BUILTIN")) {
181
+ c.owner.send(JSON.stringify({
182
+ status: "600",
183
+ id: data.id,
184
+ value: data.value
185
+ }))
186
+ }
187
+ ws.send(
188
+ JSON.stringify(
189
+ { status: "200", human_readable: "Config geupdate" }
190
+ )
191
+ )
192
+ }
193
+
194
+ } else {
195
+ ws.send(JSON.stringify({
196
+ status: "400",
197
+ human_readable: "je verzoek was niet goed"
198
+ }))
199
+ }
200
+ }
201
+ });
202
+
203
+ ws.on('close', () => {
204
+ // vewijder alles wat deze client heet geregistreerd
205
+ for (const [name, service] of Object.entries(servicesAvailable)) {
206
+ if (service.client === ws) {
207
+ delete servicesAvailable[name];
208
+ console.log(`Service ${name} verwijderd vanwege client disconnect.`);
209
+ }
210
+ }
211
+ for (const [name, service] of Object.entries(configOptions)) {
212
+ if (service.owner === ws) {
213
+ delete servicesAvailable[name];
214
+ console.log(`config ${name} verwijderd vanwege client disconnect.`);
215
+ }
216
+ }
217
+
218
+ clients.delete(ws);
219
+ console.log('Client weg');
220
+
221
+ });
222
+ });
223
+
224
+ console.log('Server draait op ws://localhost:8765');
@@ -0,0 +1,14 @@
1
+ import { initializeClient, callServiceAsync, setConfigItem } from "./lib.js";
2
+
3
+ async function main() {
4
+ await initializeClient();
5
+
6
+ const test = await callServiceAsync('test.echo', ['Hallo van de client!'])
7
+ console.log('Antwoord van echo service (promise):', test);
8
+
9
+ await setConfigItem('echo', false)
10
+
11
+ const test2 = await callServiceAsync('test.echo', ['Hallo van de client!'])
12
+ console.log('Antwoord van echo service (promise):', test2);
13
+ }
14
+ main();
package/tsconfig.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ // Environment Settings
8
+ // See also https://aka.ms/tsconfig/module
9
+ "module": "nodenext",
10
+ "target": "esnext",
11
+ "types": [],
12
+ // For nodejs:
13
+ // "lib": ["esnext"],
14
+ // "types": ["node"],
15
+ // and npm install -D @types/node
16
+ // Other Outputs
17
+ "sourceMap": true,
18
+ "declaration": true,
19
+ "declarationMap": true,
20
+ // Stricter Typechecking Options
21
+ "noUncheckedIndexedAccess": true,
22
+ "exactOptionalPropertyTypes": true,
23
+ // Style Options
24
+ // "noImplicitReturns": true,
25
+ // "noImplicitOverride": true,
26
+ // "noUnusedLocals": true,
27
+ // "noUnusedParameters": true,
28
+ // "noFallthroughCasesInSwitch": true,
29
+ // "noPropertyAccessFromIndexSignature": true,
30
+ // Recommended Options
31
+ "strict": true,
32
+ "jsx": "react-jsx",
33
+ "verbatimModuleSyntax": true,
34
+ "isolatedModules": true,
35
+ "noUncheckedSideEffectImports": true,
36
+ "moduleDetection": "force",
37
+ "skipLibCheck": true,
38
+ }
39
+ }
@@ -0,0 +1,186 @@
1
+ // Browser versie
2
+
3
+ var requestType;
4
+ (function (requestType) {
5
+ requestType[requestType["Init"] = 0] = "Init";
6
+ requestType[requestType["Success"] = 1] = "Success";
7
+ requestType[requestType["Error"] = 2] = "Error";
8
+ requestType[requestType["RegisterService"] = 3] = "RegisterService";
9
+ requestType[requestType["GetServiceResponse"] = 4] = "GetServiceResponse";
10
+ requestType[requestType["GetService"] = 5] = "GetService";
11
+ requestType[requestType["RegisterConifg"] = 6] = "RegisterConifg";
12
+ requestType[requestType["SetConfig"] = 7] = "SetConfig";
13
+ })(requestType || (requestType = {}));
14
+
15
+ var stringPacketOptions;
16
+ (function (stringPacketOptions) {
17
+ stringPacketOptions[stringPacketOptions["Error"] = 0] = "Error";
18
+ stringPacketOptions[stringPacketOptions["initSuccess"] = 1] = "initSuccess";
19
+ stringPacketOptions[stringPacketOptions["registerServiceSuccess"] = 2] = "registerServiceSuccess";
20
+ stringPacketOptions[stringPacketOptions["getServiceSuccess"] = 3] = "getServiceSuccess";
21
+ stringPacketOptions[stringPacketOptions["registerConfigSuccess"] = 4] = "registerConfigSuccess";
22
+ stringPacketOptions[stringPacketOptions["setConfigSuccess"] = 5] = "setConfigSuccess";
23
+ })(stringPacketOptions || (stringPacketOptions = {}));
24
+
25
+ const url = "ws://localhost:8765";
26
+ export let ws;
27
+ const serviceCallbacks = new Map();
28
+ const localConfigs = new Map();
29
+
30
+ export async function awaitServiceMessage(expectedFor) {
31
+ return new Promise((resolve) => {
32
+ const handler = (event) => {
33
+ try {
34
+ const rawPacket = JSON.parse(event.data);
35
+ if (rawPacket.type === requestType.Success) {
36
+ const data = rawPacket.data;
37
+ if (data.for === expectedFor) {
38
+ ws.removeEventListener('message', handler);
39
+ resolve(data);
40
+ }
41
+ }
42
+ } catch (e) {
43
+ // ignore malformed packets
44
+ }
45
+ };
46
+ ws.addEventListener('message', handler);
47
+ });
48
+ }
49
+
50
+ function WsSend(ws, data) {
51
+ if (ws && ws.readyState === WebSocket.OPEN) {
52
+ ws.send(JSON.stringify(data));
53
+ }
54
+ }
55
+
56
+ export async function registerService(ServiceId, args, callback) {
57
+ WsSend(ws, { type: requestType.RegisterService, data: { ServiceId, args } });
58
+ serviceCallbacks.set(ServiceId, callback);
59
+ return await awaitServiceMessage(stringPacketOptions.registerServiceSuccess);
60
+ }
61
+
62
+ export async function registerConfigItem(name, description, value, idthing) {
63
+ WsSend(ws, {
64
+ type: requestType.RegisterConifg,
65
+ data: {
66
+ name: name,
67
+ description: description,
68
+ defaultValue: value,
69
+ type: typeof value,
70
+ id: idthing
71
+ }
72
+ });
73
+ return await awaitServiceMessage(stringPacketOptions.registerConfigSuccess).then((msg) => {
74
+ localConfigs.set(idthing, {
75
+ name,
76
+ id: idthing,
77
+ description,
78
+ type: typeof value,
79
+ defaultValue: value
80
+ });
81
+ return msg;
82
+ });
83
+ }
84
+
85
+ export async function SetConfigItem(idthing, newValue, clientId) {
86
+ WsSend(ws, {
87
+ type: requestType.SetConfig,
88
+ data: {
89
+ ClientId: clientId,
90
+ id: idthing,
91
+ newValue: newValue
92
+ }
93
+ });
94
+ return await awaitServiceMessage(stringPacketOptions.setConfigSuccess).then((msg) => {
95
+ const existing = localConfigs.get(idthing);
96
+ if (existing) {
97
+ localConfigs.set(idthing, { ...existing, value: newValue });
98
+ }
99
+ return msg;
100
+ });
101
+ }
102
+
103
+ export async function getService(ServiceId, ClientId, inputs) {
104
+ const connectionId =
105
+ (typeof crypto !== 'undefined' && crypto.randomUUID)
106
+ ? crypto.randomUUID()
107
+ : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
108
+
109
+ WsSend(ws, { type: requestType.GetService, data: { ClientId, ServiceId, args: inputs, connectionId } });
110
+
111
+ return new Promise((resolve) => {
112
+ const handler = (event) => {
113
+ try {
114
+ const rawPacket = JSON.parse(event.data);
115
+ if (rawPacket.type === requestType.GetServiceResponse) {
116
+ const data = rawPacket.data;
117
+ if (data.serviceId === ServiceId && data.connectionId === connectionId) {
118
+ ws.removeEventListener('message', handler);
119
+ resolve(data.result);
120
+ }
121
+ }
122
+ } catch (e) {
123
+ // ignore malformed packets
124
+ }
125
+ };
126
+ ws.addEventListener('message', handler);
127
+ });
128
+ }
129
+
130
+ export async function initializeClient(ClientId) {
131
+ ws = new WebSocket(url);
132
+
133
+ ws.addEventListener('open', () => {
134
+ console.log('Verbonden met tonpleun server.');
135
+ WsSend(ws, { type: requestType.Init, data: { ClientId } });
136
+ });
137
+
138
+ ws.addEventListener('close', () => {
139
+ console.log('Verbinding met tonpleun server gesloten.');
140
+ });
141
+
142
+ ws.addEventListener('error', (event) => {
143
+ console.error('Fout opgetreden:', event);
144
+ try { ws.close(); } catch { }
145
+ });
146
+
147
+ ws.addEventListener('message', async (event) => {
148
+ try {
149
+ const rawPacket = JSON.parse(event.data);
150
+ if (rawPacket.type === requestType.GetService) {
151
+ const serviceData = rawPacket.data;
152
+ const callback = serviceCallbacks.get(serviceData.ServiceId);
153
+ if (callback) {
154
+ try {
155
+ const result = await Promise.resolve(callback(...serviceData.args));
156
+ WsSend(ws, {
157
+ type: requestType.GetServiceResponse,
158
+ data: {
159
+ result,
160
+ ServiceId: serviceData.ServiceId,
161
+ connectionId: serviceData.connectionId,
162
+ }
163
+ });
164
+ } catch (error) {
165
+ console.error(`Fout bij uitvoeren van service ${serviceData.ServiceId}:`, error);
166
+ }
167
+ }
168
+ } else if (rawPacket.type === requestType.SetConfig) {
169
+ const cfg = rawPacket.data;
170
+ const existing = localConfigs.get(cfg.id);
171
+ if (existing) {
172
+ localConfigs.set(cfg.id, { ...existing, value: cfg.newValue });
173
+ }
174
+ }
175
+ } catch (e) {
176
+ // ignore malformed packets
177
+ }
178
+ });
179
+
180
+ return await awaitServiceMessage(stringPacketOptions.initSuccess);
181
+ }
182
+
183
+ export function getConfigValue(id) {
184
+ const item = localConfigs.get(id);
185
+ return item ? (item.value ?? item.defaultValue) : undefined;
186
+ }
@@ -0,0 +1,22 @@
1
+ <html lang="en">
2
+
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tonpleun webui</title>
7
+ <script type="module" src="main.js"></script>
8
+ </head>
9
+
10
+ <body id="out">
11
+ <div id="buttonList">
12
+ </div>
13
+ <div id="container"></div>
14
+ <script>function onchangeOfthing(newValue) {
15
+ const container = document.getElementById('container');
16
+ const localdata = window.dataMap.get(newValue);
17
+ container.innerText = JSON.stringify(localdata);
18
+ }</script>
19
+
20
+ </body>
21
+
22
+ </html>
package/webui/main.js ADDED
@@ -0,0 +1,22 @@
1
+ import { getService, initializeClient } from "./clientLib.js";
2
+ async function main() {
3
+ await initializeClient('webUI');
4
+ getData();
5
+ setInterval(getData, 1000);
6
+ }
7
+ async function getData() {
8
+ const data = await getService('getConfigs', 'tonpleun', []);
9
+ console.log('data update')
10
+ window.tonpleunData = data;
11
+ update(window.tonpleunData);
12
+
13
+ }
14
+ function update(data) {
15
+ window.dataMap = new Map(Object.entries(data));
16
+ document.getElementById('buttonList').innerHTML = '';
17
+ window.dataMap.forEach((dinges, name) => {
18
+ console.log(name, dinges);
19
+ document.getElementById('buttonList').innerHTML += '<button onclick="onchangeOfthing(\'' + name + '\')">' + name + '</button>';
20
+ });
21
+ }
22
+ main();