Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem trying when trying to comunicate with Typescript websocket #76

Open
Drethek opened this issue Jan 11, 2025 · 10 comments
Open

Problem trying when trying to comunicate with Typescript websocket #76

Drethek opened this issue Jan 11, 2025 · 10 comments
Labels
Solved Question/Ask solved.

Comments

@Drethek
Copy link

Drethek commented Jan 11, 2025

So i'm trying to comunicate with a react app, sending data seems to work really good but i got troubles sending events.
I'm using the library Buffer to replicate the EventManager but without success.
Pretty sure is something wrong that i'm doing but can't figure it out.
Any help would be much appreciated!

Here some screenshots / the code i'm using

Sent Event:
brave_T0Owb61NkA

Received Event:
brave_ylIew0jSGj

Received Data:
brave_MR2GYIwT88

import { Buffer } from "buffer";

class WebSocketClient {
    private readonly ProtocolKey: string = "Ny://";
    private static instance: WebSocketClient;
    private socket: WebSocket;

    private constructor() {
        this.socket = new WebSocket("ws://127.0.0.1:1986/API");
        this.socket.binaryType = "arraybuffer";
        this.socket.onerror = (error) => {
            console.error('WebSocket error:', error);
        };
        this.socket.onopen = () => {
            console.log('WebSocket connected');
            this.SendEvent("Test_Event_Name", "Test_Event_Payload");
        };
        this.socket.onclose = () => {
            console.log('WebSocket disconnected');
        };
        this.socket.onmessage = (event) => {
            console.log("Result: ", event.data);
        };
    }

    public static getInstance(): WebSocketClient {
        if (!WebSocketClient.instance) {
            WebSocketClient.instance = new WebSocketClient();
        }
        return WebSocketClient.instance;
    }

    private IsConnected(): boolean {
        return this.socket.readyState === WebSocket.OPEN;
    }

    public SendEvent(name: string, buffer: ArrayBuffer | string): void {
        if (!this.IsConnected() || !buffer)
            return;

        if (typeof buffer === 'string') {
            this.socket.send(this.Create(name, Buffer.from(buffer)));
            return;
        }

        this.socket.send(this.Create(name, buffer));
    }

    public Create(name: string, data: ArrayBuffer): ArrayBuffer {
        const protocolKeyBuffer = Buffer.from(this.ProtocolKey, 'ascii');
        const nameBuffer = Buffer.from(name, 'utf8');
        const dataBuffer = Buffer.from(data);

        const totalLength = protocolKeyBuffer.length + nameBuffer.length + dataBuffer.length;
        const resultBuffer = Buffer.alloc(totalLength);

        protocolKeyBuffer.copy(resultBuffer, 0);
        nameBuffer.copy(resultBuffer, protocolKeyBuffer.length);
        dataBuffer.copy(resultBuffer, protocolKeyBuffer.length + nameBuffer.length);

        return resultBuffer;
    }
}

export default WebSocketClient;
using Netly;
using Test.Core.Utils;

namespace Test.Core.Network
{
    public class NetworkManager : IDisposable
    {
        private readonly string _ipAddress;
        private readonly int _port;
        private readonly HTTP.Server _server = new();

        public NetworkManager(string ipAddress, int port)
        {
            _ipAddress = ipAddress;
            _port = port;

            _server.On.Open(() =>
            {
                Logger.Instance.Information("Websocket server started on " + _ipAddress + ":" + _port);
            });

            _server.On.Close(() =>
            {
                Logger.Instance.Information("Websocket server stopped");
            });

            _server.On.Error(exception =>
            {
                Logger.Instance.Error($"Websocket error: {exception.Message}");
            });

            _server.Map.WebSocket("/API", (_, ws) =>
            {
                ws.On.Event(((name, bytes, type) => {Logger.Instance.Information($"Received Event {name} - {bytes} - {type}");} ));
                ws.On.Open(() => {
                    Logger.Instance.Information("Client Connected.");
                });
                ws.On.Close(() =>
                {
                    Logger.Instance.Information("Client Disconnected.");
                });
                ws.On.Error((exception =>
                {
                    Logger.Instance.Error($"Websocket error: {exception.Message}");
                    
                }));
                ws.On.Data(((bytes, type) =>
                {
                    _server.To.WebsocketEventBroadcast("Test_Event_Name", "Test_Event_Payload", HTTP.MessageType.Binary);
                    _server.To.WebsocketDataBroadcast("Test_Data_Payload", HTTP.MessageType.Binary);
                    Logger.Instance.Information($"Data Received: {type} - String {string.Join(", ", bytes)}");
                }));
            });
        }

        public void Dispose()
        {
            Close();
            GC.SuppressFinalize(this);
        }
        
        public Task Connect()
        {
            _server.To.Open(new Uri($"http://{_ipAddress}:{_port}"));
            return Task.CompletedTask;
        }

        private void Close()
        {
            _server.To.Close();
        }
    }
}
@alec1o
Copy link
Owner

alec1o commented Jan 11, 2025

Okay... I'll send you the code with EventManager.ts and examples. <3.

I'm very happy; you made the code very easy to understand. You did a great job! However, you missed some details about Byter.

@Drethek
Copy link
Author

Drethek commented Jan 12, 2025

Oh right... i forgot about byter...
So the problem is that i'm not adding the overhead....
I should send the overhead like:

D + (bytes length of ProtocolKey) + ProtocolKey + D + (bytes length of Event_Name) + Event_Name + "Prefix of data " + (bytes length of data if need, like for bytes or string) + the data bytes

Pretty much i forgot to include the overhead right? that make sense....

@alec1o
Copy link
Owner

alec1o commented Jan 12, 2025

I can send event now, I'm impl. about receiving events to.

Node is very hard to use binary manipulation.

@alec1o
Copy link
Owner

alec1o commented Jan 12, 2025

Give 30min more, I'm on final.

@Drethek
Copy link
Author

Drethek commented Jan 12, 2025

Oh, thank you so much! But I didn't mean to rush you, I was just asking to understand if that was the issue :)
Anyway, thanks a lot, even though it wasn't necessary for you to spend time on my problem! I didn’t want to bother you :)

@alec1o
Copy link
Owner

alec1o commented Jan 12, 2025

Nan... I always had on mind to create bind to TypeScript and Python, because i know people frequetly should use those language as client or server. (-_-..

@alec1o
Copy link
Owner

alec1o commented Jan 12, 2025

Netly with Javascript / Typescript

This project includes EventManager for TypeScript (and JavaScript).

.NET side

public class Logger
{
    public static Logger Instance { get; set; } = new Logger();

    public Action<string> Error { get; set; } = Console.WriteLine;
    public Action<string> Information { get; set; } = Console.WriteLine;
}
public class Program
{
    public static void Main(string[] args)
    {
        var network = new NetworkManager("127.0.0.1", 8080);
        
        network.Connect().GetAwaiter().GetResult();

        Thread.Sleep(TimeSpan.FromDays(1));
        
        Logger.Instance.Information("Exit: 0");
    }
}
using Byter;
using Netly;

public class NetworkManager : IDisposable
{
    private readonly string _ip;
    private readonly int _port;
    private readonly HTTP.Server _server = new();

    public NetworkManager(string ip, int port)
    {
        _ip = ip;
        _port = port;

        _server.On.Open(() => Logger.Instance.Information($"Websocket server started on '{_ip}:{_port}'"));

        _server.On.Close(() => Logger.Instance.Information("Websocket server stopped"));

        _server.On.Error(error => Logger.Instance.Error($"Websocket error: {error}"));

        _server.Map.WebSocket("/API", (_, ws) =>
        {
            ws.On.Event((name, bytes, type) => Logger.Instance.Information($"Received event {name} - {bytes} *{type}"));

            ws.On.Open(() => Logger.Instance.Information("Client Connected."));

            ws.On.Close(() => Logger.Instance.Information("Client Disconnected."));

            ws.On.Error(error => Logger.Instance.Error($"Websocket error: {error.Message}"));

            ws.On.Data((data, type) =>
            {
                _server.To.WebsocketDataBroadcast("Sample.Data.Ping", HTTP.Binary);

                Logger.Instance.Information($"Websocket server data: {data.GetString()} - *{type}");
            });

            ws.On.Event((name, data, type) =>
            {
                _server.To.WebsocketEventBroadcast("Event.Name", "Sample.Event.Ping", HTTP.Binary);

                Logger.Instance.Information($"Websocket server event ({name}): {data.GetString()} *{type}");
            });
        });
    }

    public void Dispose()
    {
        Close();
        GC.SuppressFinalize(this);
    }

    public Task Connect() => _server.To.Open(new Uri($"https://{_ip}:{_port}"));

    private void Close() => _server.To.Close().GetAwaiter().GetResult();
}

Node.js side (TypeScript)

"index.ts";

import WebSocketClient from "./WebsocketClient";

console.log("Initializing websocket client...");

let client: WebSocketClient = WebSocketClient.GetSingletonInstance();
"WebsocketClient.ts";

import {Buffer} from "buffer";
import EventManager from "./EventManager";

export default class WebSocketClient {
    private static instance: WebSocketClient;
    private socket: WebSocket;
    private isConnected: boolean
    private isConnecting: boolean
    private readonly ip: string
    private readonly port: number

    constructor(ip: string, port: number) {

        this.isConnected = false;

        this.isConnecting = false;

        this.ip = ip;

        this.port = port;

        this.socket = new WebSocket(`ws://${this.ip}:${this.port}/API`);

        this.socket.binaryType = "arraybuffer";

        this.socket.onopen = () => {
            console.log("Websocket client connected");

            // send welcome...
            this.SendData("hello server, from data.");
            this.SendEvent("Welcome", "Hello server, from event.");
        };

        this.socket.onerror = (error) => {
            console.error("Websocket client error:", error);
        };

        this.socket.onclose = () => {
            console.log("Websocket client disconnected");
        };

        this.socket.onmessage = (event) => {

            let eventMessage = EventManager.Verify(event.data);

            if (eventMessage) {
                console.log(`Websocket received event (${eventMessage.name}): ${this.GetString(eventMessage.data)}`);
            } else {
                console.log(`Websocket data received: ${this.GetString(event.data)}`);
            }
        };
    }

    public static GetSingletonInstance(): WebSocketClient {
        if (!WebSocketClient.instance) {
            WebSocketClient.instance = new WebSocketClient("127.0.0.1", 8080);
        }
        return WebSocketClient.instance;
    }

    private IsConnected(): boolean {
        return this.socket.readyState === WebSocket.OPEN;
    }

    public SendData(buffer: ArrayBuffer | string): void {
        if (!this.IsConnected() || !buffer) return;

        this.socket.send(buffer)
    }

    public SendEvent(name: string, buffer: ArrayBuffer | string): void {
        if (!buffer || !name || name.length <= 0) return;

        let eventBuffer: ArrayBuffer = (typeof buffer === "string") ? Buffer.from(buffer) : buffer;

        if (eventBuffer.byteLength <= 0) return;

        this.SendData(EventManager.Create(name, eventBuffer));
    }

    public GetString(buffer: ArrayBuffer): string {
        const decoder = new TextDecoder("utf-8");
        return decoder.decode(buffer);
    }
}
import {Buffer} from "buffer";

"EventManager.ts"

export default class EventManager {

    static ProtocolKey: string = "Ny://";
    static protocolBuffer = this.Write(this.ProtocolKey, "ascii");

    public static Create(name: string, buffer: ArrayBuffer): ArrayBuffer {

        const nameBuffer = this.Write(name, "utf8")

        const dataBuffer = this.Write(buffer, "ascii");

        return this.ConcatBuffers(this.protocolBuffer, nameBuffer, dataBuffer);
    }

    public static Verify(buffer: ArrayBuffer): { name: string, data: ArrayBuffer } | null {

        /*
            * p = Prefix (2 bytes)
            * s = Size/Length (4) bytes
            * b = Buffer (1+) bytes
                                 p.  s.  b.    p.  s. b+   p.   s.  b+            */
        if (buffer.byteLength < this.protocolBuffer.byteLength) return null;

        try {
            let offset: number = 0;
            const protocol: ArrayBuffer = buffer.slice(offset, this.protocolBuffer.byteLength + offset);

            // compare protocol buffer
            {
                if (!this.CompareBuffer(protocol, this.protocolBuffer)) return null;
                offset += protocol.byteLength;
            }

            const prefix = new ArrayBuffer(2);
            const view = new DataView(prefix);

            view.setUint8(0, 76);
            view.setUint8(1, 0);

            const receivedNamePrefix: ArrayBuffer = buffer.slice(offset, 2 + offset);
            if (!this.CompareBuffer(prefix, receivedNamePrefix)) return null;
            offset += 2;

            const nameSizeBuffer: ArrayBuffer = buffer.slice(offset, 4 + offset);
            const nameSizeValue: number = this.NumberFromLittleEndianBytes(nameSizeBuffer);
            offset += 4;

            if (nameSizeValue <= 0 || nameSizeValue > buffer.byteLength - offset) return null;

            const name: string = this.BufferToString(buffer.slice(offset, nameSizeValue + offset));
            offset += nameSizeValue;

            view.setUint8(0, 66);
            view.setUint8(1, 0);

            const receivedDataPrefix = buffer.slice(offset, 2 + offset);
            if (!this.CompareBuffer(prefix, receivedDataPrefix)) return null;
            offset += prefix.byteLength;

            const bufferSizeBuffer: ArrayBuffer = buffer.slice(offset, 4 + offset);
            const bufferSizeValue: number = this.NumberFromLittleEndianBytes(bufferSizeBuffer);
            offset += bufferSizeBuffer.byteLength;

            if (bufferSizeValue <= 0 || bufferSizeValue > buffer.byteLength - offset) return null;

            const data: ArrayBuffer = buffer.slice(offset, bufferSizeValue + offset);

            return {name: name, data: data}

        } catch (e) {
            return null;
        }
    }

    public static Write(value: string | ArrayBuffer, encoding: "utf8" | "ascii"): ArrayBuffer {

        const isBinary = (typeof value != "string");

        const buffer: ArrayBuffer = isBinary ? value : Buffer.from(value, encoding);

        const size: ArrayBuffer = this.NumberToLittleEndianBytes(buffer.byteLength);

        const prefix: ArrayBuffer = new ArrayBuffer(2);

        {
            const view = new DataView(prefix);

            if (isBinary) {
                // write 'B' char in bytes (string prefix) [66,00]
                view.setUint8(0, 66);
                view.setUint8(1, 0);
            } else {
                // write 'L' char in bytes (string prefix) [76,00]
                view.setUint8(0, 76);
                view.setUint8(1, 0);
            }
        }

        return this.ConcatBuffers(prefix, size, buffer);
    }

    public static NumberToLittleEndianBytes(value: number): ArrayBuffer {
        const buffer = new ArrayBuffer(4);
        const view = new DataView(buffer);
        view.setUint32(0, value, true);
        return new Uint8Array(buffer).buffer;
    }

    public static NumberFromLittleEndianBytes(value: ArrayBuffer): number {
        const view = new DataView(value);
        return view.getUint32(0, true);
    }

    public static ConcatBuffers(...buffers: ArrayBuffer[]): ArrayBuffer {

        const size = buffers.reduce((x, buffer) => x + buffer.byteLength, 0);

        let offset = 0;

        const result = new Uint8Array(size);

        buffers.forEach(buffer => {
            const bytes = new Uint8Array(buffer);

            result.set(bytes, offset);

            offset += bytes.byteLength;
        });

        return result.buffer;
    }

    public static CompareBuffer(a: ArrayBuffer, b: ArrayBuffer): boolean {
        if (a === b) return true;
        if (a.byteLength !== b.byteLength) {
            return false;
        }

        const viewA = new Uint8Array(a);
        const viewB = new Uint8Array(b);

        for (let i = 0; i < viewA.length; i++) {
            if (viewA[i] !== viewB[i]) {
                throw new Error(`Invalid data: [${i}]  ${viewA[i]} ${viewB[i]}`);
            }
        }

        return true;
    }

    public static BufferToString(buffer: ArrayBuffer): string {
        const decoder = new TextDecoder("utf-8");
        return decoder.decode(buffer);
    }
}

I tested it and it's working perfectly. I hope this implementation helps you! <3.

Ooh My God!!!

ws-1
ws-2

@Drethek
Copy link
Author

Drethek commented Jan 12, 2025

Nice! it work very well! thank you man!

@alec1o
Copy link
Owner

alec1o commented Jan 12, 2025

Ooh!!! Enjoy... I never had working with bytes before on js/ts <3. Hope success on your project/s

@alec1o alec1o added the Solved Question/Ask solved. label Jan 12, 2025
@alec1o
Copy link
Owner

alec1o commented Jan 12, 2025

Any question: you are welcome! (ˆ _**..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Solved Question/Ask solved.
Projects
None yet
Development

No branches or pull requests

2 participants