Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
464c77f
feat: implement basic proto file
OmegaCreations Jun 8, 2025
268a99a
feat: Duplex stream with specific payload message events.
OmegaCreations Jun 10, 2025
7b7888a
feat: Duplex stream with specific payload message events.
OmegaCreations Jun 10, 2025
6373548
feat: Create typescript interfaces for duplex stream message model
OmegaCreations Jun 10, 2025
255e567
feat: Add description to typescript interfaces
OmegaCreations Jun 10, 2025
7edca58
feat: implement serialization and deserialization methods
OmegaCreations Jun 10, 2025
0485204
feat: add package dependencies and write unit tests for serialization…
OmegaCreations Jun 10, 2025
58666d5
fix: fixed oneof variable naming
OmegaCreations Jun 10, 2025
0118271
feat: Duplex stream with specific payload message events.
OmegaCreations Jun 10, 2025
7c5d552
feat: Create typescript interfaces for duplex stream message model
OmegaCreations Jun 10, 2025
4b18459
feat: implement serialization and deserialization methods
OmegaCreations Jun 10, 2025
dba904c
feat: add package dependencies and write unit tests for serialization…
OmegaCreations Jun 10, 2025
273b8e2
feat: Create grpc wrapper with basic connection manager and central s…
OmegaCreations Jun 10, 2025
6467c55
fix: fix grpc addresses to properly connect both client and central s…
OmegaCreations Jun 10, 2025
1a86d49
fix: refactor comments
OmegaCreations Jun 11, 2025
bec195c
fix: change central system class name to avoid conflicts with central…
OmegaCreations Jun 11, 2025
c6a3f01
feat: implement Connection class and a map storing all receiving and …
OmegaCreations Jun 11, 2025
fb12e55
feat: implement replacing new token for client from central system
OmegaCreations Jun 11, 2025
63faee3
feat: implement token revokation by changing connection status
OmegaCreations Jun 11, 2025
456bf84
fix: remove unnecessary params
OmegaCreations Jun 11, 2025
93c3a51
fix: fix enum naming
OmegaCreations Jun 17, 2025
ff0faf0
fix: fix proto types order based on Bookkeeping standards
OmegaCreations Jun 17, 2025
d207f78
Merge branch 'feature/TKN/OGUI-1702/basic-proto-file' of github.com:A…
OmegaCreations Jun 17, 2025
fb6c39c
fix: add copyright banner
OmegaCreations Jun 17, 2025
8106fe1
Merge branch 'feature/TKN/OGUI-1702/basic-proto-file' of github.com:A…
OmegaCreations Jun 17, 2025
72042c8
fix: add copyright banner and change enum naming convention
OmegaCreations Jun 17, 2025
d762f9f
Merge branch 'feature/TKN/OGUI-1703/basic-typescript-interfaces' of g…
OmegaCreations Jun 17, 2025
b70cf9e
fix: fix serialization comments and tests
OmegaCreations Jun 17, 2025
aff4c59
Merge branch 'feature/TKN/OGUI-1704/data-serialization-utils' of gith…
OmegaCreations Jun 17, 2025
9de3ce5
Merge branch 'dev' into feature/TKN/OGUI-1703/basic-typescript-interf…
graduta Jun 18, 2025
bdba63d
feat: add description comments and implement .listen() instead of aut…
OmegaCreations Jul 8, 2025
326eea5
fix: change enum values to numbers
OmegaCreations Jul 8, 2025
4011499
Merge branch 'feature/TKN/OGUI-1703/basic-typescript-interfaces' of g…
OmegaCreations Jul 8, 2025
5857652
fix: move test directory
OmegaCreations Jul 8, 2025
54447ea
feat: add webui logger instead of console logs and fix paths
OmegaCreations Jul 8, 2025
af429ce
Merge branch 'feature/TKN/OGUI-1703/basic-typescript-interfaces' of g…
OmegaCreations Jul 8, 2025
9db612e
feat: write tests for central system and connection manager
OmegaCreations Jul 9, 2025
ec193ca
Merge branch 'feature/TKN/OGUI-1705/client-central-basic-stream' of g…
OmegaCreations Jul 9, 2025
d0f4dad
feat: muldularize connection management. Add project building.
OmegaCreations Jul 10, 2025
b4a0bf9
fix: fix connection and remove exponential backoff for simplified ver…
OmegaCreations Jul 10, 2025
8d9c89a
fix: test setup fixes
OmegaCreations Jul 20, 2025
1081941
Merge branch 'feature/TKN/OGUI-1708/basic-connection-class' of github…
OmegaCreations Jul 20, 2025
b6695a4
fix: fix tscofing for js builds and typescript files/tests runtime
OmegaCreations Jul 28, 2025
e79215c
feat: refactor code for command design pattern
OmegaCreations Aug 4, 2025
90d713d
feat: unit tests for token revokation
OmegaCreations Aug 4, 2025
e079438
feat: implement token revokation logic and testing
OmegaCreations Aug 5, 2025
5527944
feat: implement connection direction info inside of payload
OmegaCreations Aug 5, 2025
090da6b
fix: fix tests
OmegaCreations Aug 7, 2025
fd0e1dd
feat: implement command for new token from central system
OmegaCreations Aug 7, 2025
5965abe
feat: add missing comments, banners and tests
OmegaCreations Aug 7, 2025
4562762
fix: fixed banner processing. removed console logs
OmegaCreations Aug 7, 2025
ec36841
feat: add github actions for wrapper tests
OmegaCreations Aug 16, 2025
836ff51
fix: paths
OmegaCreations Aug 16, 2025
c61ac97
fix: pathing
OmegaCreations Aug 16, 2025
351099d
Merge branch 'feature/TKN/OGUI-1710/handle-newly-generated-token' of …
OmegaCreations Aug 16, 2025
ec8ea1a
fix: fix tests
OmegaCreations Aug 16, 2025
6ac9f71
feat: implement simple connection peer to peers
OmegaCreations Aug 27, 2025
6ba86f2
fix: remove unnecessary utils
OmegaCreations Aug 31, 2025
2569c5c
fix: remove unused variable
OmegaCreations Aug 31, 2025
dd246bd
Merge branch 'dev' of github.com:AliceO2Group/WebUi into feature/TKN/…
OmegaCreations Aug 31, 2025
21fb63e
fix: refactor unit tests
OmegaCreations Aug 31, 2025
957a7be
feat: update unit tests for p2p connections
OmegaCreations Aug 31, 2025
f41266f
Merge branch 'dev' of github.com:AliceO2Group/WebUi into feature/TKN/…
OmegaCreations Nov 6, 2025
37a8021
fix: remove duplicates
OmegaCreations Nov 6, 2025
feeb637
fix: unit tests
OmegaCreations Nov 6, 2025
44f58e4
fix: move .gitignore to parent .gitignore
OmegaCreations Nov 6, 2025
c87f1f1
fix: default wrapper port changed to 4100
OmegaCreations Nov 6, 2025
b2f24c6
fix: change private variables naming
OmegaCreations Nov 6, 2025
d043717
fix: rename directories to start with small letters
OmegaCreations Nov 6, 2025
48c51dc
fix: fix imports and add breaklines after banners
OmegaCreations Nov 6, 2025
9ae5c9b
feat: inform about central system address on connection
OmegaCreations Nov 6, 2025
75a9f14
fix: tests testing initializtion of object shouuld have object creati…
OmegaCreations Nov 6, 2025
f6cb712
feat: implement missing descriptions
OmegaCreations Nov 6, 2025
d0c57bf
fix: make sure that proto file enums have number 0 with default, unde…
OmegaCreations Nov 6, 2025
3c82edc
feat: implement eslint and fix its errors
OmegaCreations Nov 6, 2025
6552480
Merge branch 'feature/TKN/OGUI-1710/handle-newly-generated-token' of …
OmegaCreations Nov 11, 2025
95ff34f
Merge branch 'feature/TKN/OGUI-1746/add-github-actions-for-wrapper' o…
OmegaCreations Nov 11, 2025
a253f51
fix: fix files, unit tests and add tests for connection
OmegaCreations Nov 12, 2025
866f599
fix: rebuild package lock
OmegaCreations Nov 12, 2025
b85554f
Merge branch 'dev' into feature/TKN/OGUI-1747/simple-client-p2p-conne…
OmegaCreations Nov 12, 2025
9d05309
Merge branch 'dev' of github.com:AliceO2Group/WebUi into feature/TKN/…
OmegaCreations Nov 13, 2025
74af151
Merge branch 'feature/TKN/OGUI-1747/simple-client-p2p-connection' of …
OmegaCreations Nov 13, 2025
3d832ba
fix: fix rebase
OmegaCreations Nov 13, 2025
f0e4643
fix: tests, remove express, fix comments in gRPCWrapper
OmegaCreations Nov 13, 2025
baa9511
fix: tests
OmegaCreations Nov 13, 2025
6d658fb
feat: move peer listening to separate method and util file
OmegaCreations Nov 13, 2025
16a2ec6
fix: remove additional description
OmegaCreations Nov 13, 2025
b742ccc
Merge branch 'dev' into feature/TKN/OGUI-1747/simple-client-p2p-conne…
OmegaCreations Nov 13, 2025
0d22a81
Merge branch 'dev' into feature/TKN/OGUI-1747/simple-client-p2p-conne…
OmegaCreations Nov 16, 2025
1009225
Merge branch 'dev' into feature/TKN/OGUI-1747/simple-client-p2p-conne…
OmegaCreations Nov 18, 2025
0aab6bc
Merge branch 'dev' into feature/TKN/OGUI-1747/simple-client-p2p-conne…
OmegaCreations Nov 26, 2025
d5a86e8
Merge branch 'dev' into feature/TKN/OGUI-1747/simple-client-p2p-conne…
OmegaCreations Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions Tokenization/backend/proto/wrapper.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ service CentralSystem {
rpc ClientStream(stream Payload) returns (stream Payload);
}

// Peer2Peer service handling HTTP-like requests between wrapper clients
service Peer2Peer {
rpc Fetch (HttpLikeRequest) returns (HttpLikeResponse);
}


// ======================================
// MESSAGES
// ======================================
Expand All @@ -51,14 +57,37 @@ message Payload {
}
}

// http method enum
enum HttpMethod {
HTTP_METHOD_UNSPECIFIED = 0;
GET = 1;
POST = 2;
PUT = 3;
PATCH = 4;
DELETE = 5;
HEAD = 6;
OPTIONS = 7;
}

message HttpLikeRequest {
HttpMethod method = 1; // GET/POST/...
string path = 2; // request path e.g. "/orders/add"
map<string,string> headers = 3; // "content-type": "application/json"
bytes body = 4; // body (e.g. JSON)
}

message HttpLikeResponse {
int32 status = 1;
map<string,string> headers = 2;
bytes body = 3;
}


// ======================================
// ENUMS
// ======================================

enum MessageEvent {
// Unspecified event type
MESSAGE_EVENT_UNSPECIFIED = 0;

// Default value, represents an empty event
MESSAGE_EVENT_EMPTY = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class NewTokenHandler implements CommandHandler<NewTokenCommand> {

for (const dir of directions) {
let conn = this.manager.getConnectionByAddress(targetAddress, dir);
conn ??= this.manager.createNewConnection(targetAddress, dir, token);
conn ??= await this.manager.createNewConnection(targetAddress, dir, token);
conn.token = token;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
*/

import type { ConnectionDirection } from '../../models/message.model';
import type { ConnectionHeaders, FetchOptions, FetchResponse } from '../../models/connection.model';
import { ConnectionStatus } from '../../models/connection.model';
import * as grpc from '@grpc/grpc-js';

/**
* @description This class represents a connection to a target client and manages sending messages to it.
Expand All @@ -22,6 +24,7 @@ export class Connection {
private _token: string;
private _targetAddress: string;
private _status: ConnectionStatus;
private _peerClient: any;
public direction: ConnectionDirection;

/**
Expand All @@ -31,9 +34,10 @@ export class Connection {
* @param targetAddress - The unique address of the target client.
* @param direction - The direction of the connection (e.g., sending or receiving).
*/
constructor(token: string, targetAddress: string, direction: ConnectionDirection) {
constructor(token: string, targetAddress: string, direction: ConnectionDirection, peerCtor: any) {
this._token = token;
this._targetAddress = targetAddress;
this._peerClient = new peerCtor(targetAddress, grpc.credentials.createInsecure());
this.direction = direction;

this._status = ConnectionStatus.CONNECTED;
Expand Down Expand Up @@ -72,11 +76,63 @@ export class Connection {
return this._status;
}

/**
* Sets the status of this connection.
* @param status The new status of the connection.
*/
public set status(status: ConnectionStatus) {
this._status = status;
}

/**
* Returns target address for this Connection object
* @returns Target address
*/
public get targetAddress(): string {
return this._targetAddress;
}

/**
* "HTTP-like" fetch via gRPC protocol
* @returns Promise with peer's response
*/
public fetch(options: FetchOptions = {}): Promise<FetchResponse> {
if (!this._peerClient) {
return Promise.reject(new Error(`Peer client not attached for ${this.targetAddress}`));
}

// Build a request object
const method = (options.method ?? 'POST').toUpperCase();
const path = options.path ?? '/';
const headers: ConnectionHeaders = { ...(options.headers ?? {}) };

let bodyBuf: Buffer = Buffer.alloc(0);
const b = options.body;
if (b != null) {
if (Buffer.isBuffer(b)) bodyBuf = b;
else if (b instanceof Uint8Array) bodyBuf = Buffer.from(b);
else if (typeof b === 'string') bodyBuf = Buffer.from(b, 'utf8');
else return Promise.reject(new Error('Body must be a string/Buffer/Uint8Array'));
}

const req = { method, path, headers, body: bodyBuf };

// Return promise with response
return new Promise<FetchResponse>((resolve, reject) => {
this._peerClient.Fetch(req, (err: any, resp: any) => {
if (err) return reject(err);

const resBody = resp?.body ? Buffer.from(resp.body) : Buffer.alloc(0);
const fetchResponse: FetchResponse = {
status: Number(resp?.status ?? 200),
headers: resp?.headers ?? {},
body: resBody,
text: async () => resBody.toString('utf8'),
json: async () => JSON.parse(resBody.toString('utf8')),
};

resolve(fetchResponse);
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ import { LogManager } from '@aliceo2/web-ui';
import type { Command, CommandHandler } from 'models/commands.model';
import type { DuplexMessageEvent } from '../../models/message.model';
import { ConnectionDirection } from '../../models/message.model';
import { ConnectionStatus } from '../../models/connection.model';
import { peerListener } from '../../utils/connection/peerListener';

/**
* @description Manages all the connection between clients and central system.
*/
/**
* Manages the lifecycle and connection logic for a gRPC client communicating with the central system.
*
Expand All @@ -45,6 +44,10 @@ export class ConnectionManager {
private _centralConnection: CentralConnection;
private _sendingConnections = new Map<string, Connection>();
private _receivingConnections = new Map<string, Connection>();
private _wrapper: any;
private _peerCtor: any;
private _peerServer: grpc.Server | undefined;
private _baseAPIPath: string = '';

/**
* Initializes a new instance of the ConnectionManager class.
Expand All @@ -64,9 +67,10 @@ export class ConnectionManager {
});

const proto = grpc.loadPackageDefinition(packageDef) as any;
const wrapper = proto.webui.tokenization;
this._wrapper = proto.webui.tokenization;
this._peerCtor = this._wrapper.Peer2Peer;

const client = new wrapper.CentralSystem(centralAddress, grpc.credentials.createInsecure());
const client = new this._wrapper.CentralSystem(centralAddress, grpc.credentials.createInsecure());

// Event dispatcher for central system events
this._centralDispatcher = new CentralCommandDispatcher();
Expand Down Expand Up @@ -109,13 +113,15 @@ export class ConnectionManager {
* @param token Optional token for connection
*/
createNewConnection(address: string, direction: ConnectionDirection, token?: string) {
const conn = new Connection(token ?? '', address, direction);
const conn = new Connection(token ?? '', address, direction, this._peerCtor);

if (direction === ConnectionDirection.RECEIVING) {
this._receivingConnections.set(address, conn);
} else {
this._sendingConnections.set(address, conn);
}
conn.status = ConnectionStatus.CONNECTED;
this._logger.infoMessage(`Connection with ${address} has been estabilished. Status: ${conn.status}`);

return conn;
}
Expand Down Expand Up @@ -150,4 +156,26 @@ export class ConnectionManager {
receiving: [...this._receivingConnections.values()],
};
}

/** Starts a listener server for p2p connections */
public async listenForPeers(port: number, baseAPIPath?: string): Promise<void> {
if (baseAPIPath) this._baseAPIPath = baseAPIPath;

if (this._peerServer) {
this._peerServer.forceShutdown();
this._peerServer = undefined;
}

this._peerServer = new grpc.Server();
this._peerServer.addService(this._wrapper.Peer2Peer.service, {
Fetch: async (call: grpc.ServerUnaryCall<any, any>, callback: grpc.sendUnaryData<any>) =>
peerListener(call, callback, this._logger, this._receivingConnections, this._peerCtor, this._baseAPIPath),
});

await new Promise<void>((resolve, reject) => {
this._peerServer?.bindAsync(`localhost:${port}`, grpc.ServerCredentials.createInsecure(), (err) => (err ? reject(err) : resolve()));
});

this._logger.infoMessage(`Peer server listening on localhost:${port}`);
}
}
31 changes: 28 additions & 3 deletions Tokenization/backend/wrapper/src/client/gRPCWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

import { ConnectionManager } from './connectionManager/ConnectionManager';
import { RevokeTokenHandler } from './commands/revokeToken/revokeToken.handler';
import { DuplexMessageEvent } from '../models/message.model';
import type { Connection } from './connection/Connection';
import { ConnectionDirection, DuplexMessageEvent } from '../models/message.model';
import { NewTokenHandler } from './commands/newToken/newToken.handler';
import type { Connection } from './connection/Connection';

/**
* @description Wrapper class for managing secure gRPC wrapper.
Expand Down Expand Up @@ -56,12 +56,37 @@ export class gRPCWrapper {
}

/**
* Starts the Connection Manager stream connection with Central System
* Connects to the central system using the underlying ConnectionManager.
*
* @remarks
* This method starts the duplex stream connection with the central gRPC server.
*/
public connectToCentralSystem() {
this._connectionManager.connectToCentralSystem();
}

/**
* Establishes a new connection to a target client.
*
* @param address - The target address of the client.
* @param token - Optional authentication token for the connection.
*
* @returns A promise that resolves to the newly created connection ready to use for fetching data.
*/
public async connectToClient(address: string, token?: string): Promise<Connection> {
return this._connectionManager.createNewConnection(address, ConnectionDirection.SENDING, token ?? '');
}

/**
* Starts a listener server for p2p connections.
* @param port The port number to bind the p2p server to.
* @param baseAPIPath Optional base API path to forward requests to e.g. '/api'.
* @returns A promise that resolves when the p2p listener server is started.
*/
public async listenForPeers(port: number, baseAPIPath?: string): Promise<void> {
return this._connectionManager.listenForPeers(port, baseAPIPath);
}

/**
* Returns all saved connections.
*
Expand Down
32 changes: 32 additions & 0 deletions Tokenization/backend/wrapper/src/models/connection.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,35 @@ export enum ConnectionStatus {
// The connection is refreshing its authentication token
TOKEN_REFRESH = 'TOKEN_REFRESH',
}

export type ConnectionHeaders = Record<string, string>;

export type FetchOptions = {
method?: string;
path?: string;
headers?: ConnectionHeaders;
body?: string | Buffer | Uint8Array | null;
};

export type FetchResponse = {
status: number;
headers: ConnectionHeaders;
body: Buffer;
text: () => Promise<string>;
json: () => Promise<any>;
};

export type HttpLikeRequest = {
method: string;
path: string;
headers: Headers;
body: Buffer;
correlation_id?: string;
sequence_number?: number;
};

export type HttpLikeResponse = {
status: number;
headers: Headers;
body: Buffer;
};
1 change: 0 additions & 1 deletion Tokenization/backend/wrapper/src/models/message.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
* @property MESSAGE_EVENT_REVOKE_TOKEN: Event for revoking an existing token.
*/
export enum DuplexMessageEvent {
MESSAGE_EVENT_UNSPECIFIED = 'MESSAGE_EVENT_UNSPECIFIED',
MESSAGE_EVENT_EMPTY = 'MESSAGE_EVENT_EMPTY',
MESSAGE_EVENT_NEW_TOKEN = 'MESSAGE_EVENT_NEW_TOKEN',
MESSAGE_EVENT_REVOKE_TOKEN = 'MESSAGE_EVENT_REVOKE_TOKEN',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { Connection } from '../../../client/connection/Connection';
import { ConnectionManager } from '../../../client/connectionManager/ConnectionManager';
import { Command } from 'models/commands.model';
import { ConnectionDirection, DuplexMessageEvent } from '../../../models/message.model';
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import path from 'path';

/**
* Helper to create a new token command with given address, direction, and token.
Expand All @@ -36,6 +39,19 @@ function createEventMessage(targetAddress: string, connectionDirection: Connecti
describe('NewTokenHandler', () => {
let manager: ConnectionManager;

const protoPath = path.join(__dirname, '..', '..', '..', '..', '..', 'proto', 'wrapper.proto');
const packageDef = protoLoader.loadSync(protoPath, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDef) as any;
const wrapper = proto.webui.tokenization;
const peerCtor = wrapper.Peer2Peer;

beforeEach(() => {
manager = {
sendingConnections: new Map<string, Connection>(),
Expand All @@ -49,7 +65,7 @@ describe('NewTokenHandler', () => {
return undefined;
}),
createNewConnection: jest.fn(function (this: any, address: string, dir: ConnectionDirection, token: string) {
const conn = new Connection(token, address, dir);
const conn = new Connection(token, address, dir, peerCtor);
if (dir === ConnectionDirection.SENDING) {
this.sendingConnections.set(address, conn);
} else {
Expand All @@ -62,7 +78,7 @@ describe('NewTokenHandler', () => {

it('should update token on existing SENDING connection', async () => {
const targetAddress = 'peer-123';
const conn = new Connection('old-token', targetAddress, ConnectionDirection.SENDING);
const conn = new Connection('old-token', targetAddress, ConnectionDirection.SENDING, peerCtor);
(manager as any).sendingConnections.set(targetAddress, conn);

const handler = new NewTokenHandler(manager);
Expand Down
Loading