Coder Social home page Coder Social logo

kataras / neffos.js Goto Github PK

View Code? Open in Web Editor NEW
41.0 5.0 16.0 660 KB

Node.js and Browser support for the neffos real-time framework written in Typescript.

License: MIT License

TypeScript 85.26% HTML 8.73% JavaScript 6.01%
typescript neffos real-time websocket websocket-client javascript nodejs

neffos.js's Introduction

neffos.js is the client-side javascript library for the neffos real-time framework.

It can run through any modern browser, browserify and nodejs.

Node version JavaScript Style Guide: Good Parts Known Vulnerabilities chat backend pkg

Installation

node.js

$ npm install --save neffos.js
import * as neffos from `neffos.js`

Browsers

Production:

<script src="//cdn.jsdelivr.net/npm/neffos.js@latest/dist/neffos-es5.js"></script>

Remember to replace the version part with the exact release your project depends upon.

The ES5 version supports CommonJS loader and also exports globally as neffos.

Install Definition File

The definition file for javascript developers is part of the DefinitelyTyped.

$ npm i @types/neffos.js

This libray is written in Typescript and the types/index.d.ts definition file is part of the npm install. The library offers full documentation of all of its exported methods. Tools like VS Code supports syntax highlighting and autocompletion while typing. The definition file can be very helpful mostly for Pure JavaScript developers.

Documentation

Navigate through ./_examples for basic usage, and kataras/neffos/_examples for extensive use.

Versioning

Node version

Read more about Semantic Versioning 2.0.0

neffos.js's People

Contributors

kataras avatar alpjor avatar

Stargazers

勇往直前KJ avatar  avatar Restu Wahyu Saputra avatar 0XLΞung avatar dragoner avatar Kesus Kim avatar yudongdong avatar  avatar Kar avatar  avatar  avatar  avatar zw avatar  avatar Alan avatar  avatar Zhao Xiaohong avatar Zhangheng Qu avatar  avatar aqing avatar  avatar Amir Irani avatar  avatar chore avatar Tiago Neves avatar Silas Ribas Martins avatar  avatar Shadow-L avatar  avatar  avatar roamboy avatar  avatar venjiang avatar tm.d avatar alireza faghani avatar wagon avatar GongChao avatar Nguyen Van Vuong (Vic) avatar Go7hic avatar Majid Bigdeli avatar arden avatar

Watchers

James Cloos avatar wagon avatar  avatar Majid Bigdeli avatar  avatar

neffos.js's Issues

problems with neffos.js when using it in electron BrowserWindow process after doing electron build

here’s the background: I’m developing an Electron app with golang as backend and react & node.js as the frontend, and then we build a websocket between backend and the electron BrowserWindow process.

The problem is that everything is alright in dev env, but it comes out white screen after we build and open the .exe file. Eventually, we found out that it’s exactly the neffos.js dependency causes the problem ( if we remove it and of course also remove the websocket code involved, the white screen problem’s solved ). I don’t know whether there’s some bugs on it, plz check it out, thx in advance!

Reconnecting issue when using a reverse proxy

I am using Neffos behind a reverse proxy like Apache.
If for any reason I need to restart the Neffos service, the clients that were already connected to the websocket via the browser, will just fail on the first reconnect attempt with error 5xx and the reconnection loop will be cancelled.

I expect the client to continue the reconnecting loop attempt indefinitely until the backend is back online.

Using with quasar and vite.

How using neffos.js with quasar?
Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2019", "firefox78", "safari13.1")

node_modules/neffos.js/dist/neffos.js:20:19:
  20 │     const nodeWS = await import('ws');
     ╵     

image

error while updating dependencies: Error: Build failed with 1 error: node_modules/neffos.js/dist/neffos.js:20:19: ERROR: Top-level await is not available in the configured target environment ("chrome87", "edge88", "es2019", "firefox78", "safari13.1") at failureErrorWithLog (/app/node_modules/vite/node_modules/esbuild/lib/main.js:1624:15) at /app/node_modules/vite/node_modules/esbuild/lib/main.js:1266:28 at runOnEndCallbacks (/app/node_modules/vite/node_modules/esbuild/lib/main.js:1046:63) at buildResponseToResult (/app/node_modules/vite/node_modules/esbuild/lib/main.js:1264:7) at /app/node_modules/vite/node_modules/esbuild/lib/main.js:1377:14 at /app/node_modules/vite/node_modules/esbuild/lib/main.js:678:9 at handleIncomingPacket (/app/node_modules/vite/node_modules/esbuild/lib/main.js:775:9) at Socket.readFromStdout (/app/node_modules/vite/node_modules/esbuild/lib/main.js:644:7) at Socket.emit (node:events:513:28) at addChunk (node:internal/streams/readable:324:12) Vite Error, /node_modules/.q-cache/vite/spa/deps/neffos_js.js?v=3ad3b7dc optimized info should be defined Vite Error, /node_modules/.q-cache/vite/spa/deps/neffos_js.js?v=3ad3b7dc optimized info should be defined

image

calling `.connect` fast causes messages to have the same nonce and prevents promises from being resolved

In .connect( calls to connect to namespaces, the library adds a nonce to the websocket message so that the response message with the same nonce can be matched to the request and the open javascript promise that matches that nonce can be resolved. In the event that calls to .connect are done rapidly, like when joining more than one namespace in a forEach loop. The messages end up with the same window.performance.now(); nonce, and there is a race condition to see which of the namespaces are joined. I was able to get around this in my own code by calling .connect( on all my namespaces in series so that all the connections would work. However, while this works for the initial connection, when the dial( function is used with a reconnect parameter, recconnects try to connect to each previously connected namespace in the forEach loop here:

                            previouslyConnectedNamespacesNamesOnly.forEach((joinedRooms, name) => {
                                let whenConnected = (joinedRooms) => {
                                    return (newNSConn) => {
                                        joinedRooms.forEach((roomName) => {
                                            newNSConn.joinRoom(roomName);
                                        });
                                    };
                                };
                                newConn.connect(name).then(whenConnected(joinedRooms));
                            });

which calls .connect( really fast, which results in some of the messages getting the same nonce, which means that there is once again a race condition for reconnecting to the previously connected namespaces, and some re-connections fail as a result. Interestingly enough, once there is only 1 namespace left to reconnect to, it works fine since there is no nonce duplication and thus no race condition.

ways I can think to fix this:

  1. nonces need to be made more unique so that each message gets it's own unique nonce and duplicate nonces don't create race conditions
    window.performace.now() + Math.random(...)
  2. reconnect logic needs to connect to previously connected namespaces in series and not in parallel
  3. allow library users to set nonces for messages

need function `emitBinary` in `nsConn.Room`

I found nsConn has a function named emitBinary which can tarnsform data by binary. And I saw it in _examples\protobuf\browser\app.js. So I did a practice. But It doesn't work in nsConn.Room. My code like this

var room = "default_room"
sendBtn.onclick = function () {
    const input = inputTxt.value;
    inputTxt.value = "";
    var pbMsg = new proto.protobuf.ChatMsg();
    pbMsg.setMsgtype(10000).setMsgbody(input);
    addMessage("Me: " + input + " --room " + room);

    // work correct
    nsConn.emitBinary("OnChat", pbMsg.serializeBinary());
   
    //error becasue emitBinary undefined
    nsConn.room(room).emitBinary("OnChat",pbMsg.serializeBinary())
};

I read the examples again, and found this way to send a binary data in examle.

let message = new Message();
message.Namespace = "default";
message.Event = "OnChat";
message.Body = pbMsg.serializeBinary();
message.SetBinary = true;
message.Room = room;
nsConn.conn.write(message);

it works, Great!

But I want an easy way to write this code. So I changed neffos.js to implement Room.emitBinary.

var Room = function () {
    function a(a, b) {
        this.nsConn = a, this.name = b
    }

    return a.prototype.emit = function (a, b) {
        var c = new Message;
        return c.Namespace = this.nsConn.namespace, c.Room = this.name, c.Event = a, c.Body = b, this.nsConn.conn.write(c)
    },a.prototype.emitBinary = function (a, b) {
        var c = new Message;
        return c.Namespace = this.nsConn.namespace, c.Room = this.name, c.Event = a, c.Body = b, c.SetBinary = !0, this.nsConn.conn.write(c)
    }, a.prototype.leave = function () {
        var a = new Message;
        return a.Namespace = this.nsConn.namespace, a.Room = this.name, a.Event = OnRoomLeave, this.nsConn.askRoomLeave(a)
    }, a
}()

just add a.prototype.emitBinary, it works.
I am not familiar with typescripts, so I make an issue whitout pull request.

Sending pings to keep websocket connection alive

Hi,

I'm trying to figure out if neffos.js has a method to automatically send ping messages. My reverse proxy (nginx) kept closing the connection which made me aware that neffos.js doesn't do automatic pings. Or so it seems.
Is that something that has to be implemented manually by the user of the lib?

Thanks for your work!

It is recommended to use generics

class Message {
 unmarshal<T>(): T;
}
interface TestMessage {
  username: string;
  message:string
  created_at:number
}

message: (ns: NSConn, msg: Message) => {
  const body =  msg.unmarshal<TestMessage>()
  console.log(body.username);
  console.log(body.message);
}

[QUESTION] How to use the initializated con globaly?

I've implement neffos to connect with iris server with the example in the folder _example/websocket/basic/browser and i like to use, the returned nsConn variable, in other files than that one, this will help me to emit from another events.

Is it possible?
Could you please post a piece of code of it would be?

How do i use the room?

async function runExample() {
var events = new Object();
events._OnNamespaceConnected = function (nsConn, msg) {
if (nsConn.conn.wasReconnected()) {
addMessage("re-connected after " + nsConn.conn.reconnectTries.toString() + " trie(s)");
}

        addMessage("connected to namespace: " + msg.Namespace);
        handleNamespaceConnectedConn(nsConn);
    }

    events._OnNamespaceDisconnect = function (nsConn, msg) {
        addMessage("disconnected from namespace: " + msg.Namespace);
    }
    events._OnRoomJoined = function (nsConn, msg) {
        // nsConn.JoinRoom
    }
    events.chat = function (nsConn, msg) { // "chat" event.
        addMessage(msg.Body);
    }

advised to support non utf-8 char

now, the neffos.js does not support non utf-8 char,it will disconnect with server when it recv non utf-8 char from server.so i wish neffos.js can support non utf-8 char,thank you

How to check that the connection is open?

What's your idea about this issue that the event will not be caught e.g 5 hours after connecting,
When we press the F5 key (refresh) the problem will be fixed.
Is it possible for network problems ? If the problem is this, how to fix it?
E.g. sending a request to the server again for connecting

reconnect fail on Firefox

I write the sample-project "chat" with " options { reconnect: 5000 }". After I restart websocket server, ws-clients in Edge or Chrome are working fine and successfully reconnecting, except for Firefox.

OS: Ubuntu 22.04, Firefox: 116.0.2

Can not we set the header in the dial?

I know that There is no method in the JavaScript WebSockets API for specifying additional headers for the client/browser to send. The HTTP path ("GET /xyz") and protocol header ("Sec-WebSocket-Protocol") can be specified in the WebSocket constructor.

The Sec-WebSocket-Protocol header (which is sometimes extended to be used in websocket specific authentication) is generated from the optional second argument to the WebSocket constructor:

var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);

The above results in the following headers:
Sec-WebSocket-Protocol: protocol
and
Sec-WebSocket-Protocol: protocol1, protocol2

but I have error

WebSocket connection to 'ws://localhost:3811/echo' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
(anonymous) @ neffos.js:612
dial @ neffos.js:604
runExample @ (index):37
(anonymous) @ (index):59

my index.html


<input id="input" type="text" />

<button id="sendBtn" disabled>Send</button>

<pre id="output"></pre>

<script src="./neffos.js"></script>
<script>
    var scheme = document.location.protocol == "https:" ? "wss" : "ws";
    var port = ":3811"
    var wsURL = "ws://localhost:3811/echo" 
    var outputTxt = document.getElementById("output");
    function addMessage(msg) {
        outputTxt.innerHTML += msg + "\n";
    }
    function handleError(reason) {
        console.log(reason);
        window.alert(reason);
    }
    function handleNamespaceConnectedConn(nsConn) {
        let inputTxt = document.getElementById("input");
        let sendBtn = document.getElementById("sendBtn");
        sendBtn.disabled = false;
        sendBtn.onclick = function () {
            const input = inputTxt.value;
            inputTxt.value = "";
            nsConn.emit("send", input);
            addMessage("Me: " + input);
        };
    }
    async function runExample() {
        // You can omit the "default" and simply define only Events, the namespace will be an empty string"",
        // however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well.
        try {
            const conn = await neffos.dial(wsURL, {
                default: { // "default" namespace.
                    _OnNamespaceConnected: function (nsConn, msg) {
                        handleNamespaceConnectedConn(nsConn);
                    },
                    _OnNamespaceDisconnect: function (nsConn, msg) {
                        
                    },
                    chat: function (nsConn, msg) { // "chat" event.

                    }
                }
            },["123"]);
            // You can either wait to conenct or just conn.connect("connect")
            // and put the `handleNamespaceConnectedConn` inside `_OnNamespaceConnected` callback instead.
            // const nsConn = await conn.connect("default");
            // handleNamespaceConnectedConn(nsConn);
            conn.connect("default");
        } catch (err) {
            handleError(err);
        }
    }
    runExample();
  </script>

I used Query String to solve this problem.

change wsURL to ws://localhost:3811/echo?UserId=123

and remove ["123"] protocol in the dial method.

Can not we have a custom header on dial?

function dial(endpoint: string, connHandler: any, protocols?: string[]): Promise<Conn> {
    if (endpoint.indexOf("ws") == -1) {
        endpoint = "ws://" + endpoint;
    }

    return new Promise((resolve, reject) => {
        if (!WebSocket) {
            reject("WebSocket is not accessible through this browser.");
        }


        let namespaces = resolveNamespaces(connHandler, reject);
        if (isNull(namespaces)) {
            return;
        }

        let ws = new WebSocket(endpoint, protocols);
        let conn = new Conn(ws, namespaces);
        ws.binaryType = "arraybuffer";
        ws.onmessage = ((evt: MessageEvent) => {
            let err = conn.handle(evt);
            if (!isEmpty(err)) {
                reject(err);
                return;
            }

            if (conn.isAcknowledged()) {
                resolve(conn);
            }
        });
        ws.onopen = ((evt: Event) => {
            // let b = new Uint8Array(1)
            // b[0] = 1;
            // this.conn.send(b.buffer);
            ws.send(ackBinary);
        });
        ws.onerror = ((err: Event) => {
            conn.close();
            reject(err);
        });
    });
}

Like golang client

	client, err := neffos.Dial(
		// Optional context cancelation and deadline for dialing.
		nil,
		// The underline dialer, can be also a gobwas.Dialer/DefautlDialer or a gorilla.Dialer/DefaultDialer.
		// Here we wrap a custom gobwas dialer in order to send the username among, on the handshake state,
		// see `startServer().server.IDGenerator`.
		gobwas.Dialer(gobwas.Options{Header: gobwas.Header{"X-Username": []string{username}}}),
		// The endpoint, i.e ws://localhost:8080/path.
		endpoint,
		// The namespaces and events, can be optionally shared with the server's.
		serverAndClientEvents)

NPM Error with TextDecoder

Im working with npm and i install neffos affter the update you do for #4 the library send me an error: ReferenceError: TextDecoder is not defined i don't know why is this happend, but i add this correction to and works (for me at least) :

var encoding = require('text-encoding');
var textDecoder = new encoding.TextDecoder("utf-8");

this is on lines 248 of neffos.js file.

@kataras would you think this change could be upload whitout any issues? and could you update to this works on npm usage?

Reconnect was stopped after first-time retry.

After first time reconnect (retries = 1), and recived a "http-status 500" (or 503, 404 ...), neffos would no try any more reconnect .

file: /src/neffos.js
line : 993:

   let check = (): void => {
        // Note:
        // We do not fire a try immediately after the disconnection as most developers will expect.
        _fetch(endpointHTTP, fetchOptions).then(() => {
            notifyOnline(tries);
        }).catch(() => { // on network failures.
            // if (err !== undefined && err.toString() !== "TypeError: Failed to fetch") {
            //     console.log(err);
            // }
            tries++;
            setTimeout(() => {
                check();
            }, checkEvery)
        });
    };

This would not catch any bad http-status. the new code :

   let check = (): void => {
        // Note:
        // We do not fire a try immediately after the disconnection as most developers will expect.
        _fetch(endpointHTTP, fetchOptions).then((response) => {  // <--- on response
             if (response.ok) {   // on response ok 
                 notifyOnline(tries);
            } else {
                 return Promise.reject(`${response.status}:${response.statusText}`) // <-- reject 
            }
        }).catch((err) => { // on network failures.
            // if (err !== undefined && err.toString() !== "TypeError: Failed to fetch") {
            //     console.log(err);
            // }
            tries++;
            setTimeout(() => {
                check();
            }, checkEvery)
        });
    };

Now, even if the ws-server is restarted after a long time, the ws-client (mneffos.js) can reconnect normally.

[proposal] Can we provide WebSocket interfaces for customizing?

Hello, @kataras. the neffos.js works very well. but the coupling is too high. I want to change WebSocket. If there is an interface, I can implement it myself.

interface EventCallback {
    (evt: Event)
}

interface MsgCallback {
    (evt: MessageEvent)
}

interface WebSocket {
    Send(dat: string | ArrayBuffer);
    Close();

    OnMessage(cb: MsgCallback);
    OnOpen(cb: EventCallback);
    OnError(cb: EventCallback);
    OnClose(cb: EventCallback);
}

// WS provided by the runtime
let ws = new WS;

let adapter: WebSocket = {
    Send(dat) {
        // logger
        // ...
        ws.send(dat)
    },
    Close(): void {
        // ...
        ws.close()
    },
    OnMessage(cb) {
        // ...
        ws.onMessage(cb);
    },
    OnOpen(cb) {
        // ...
        ws.onOpen(cb);
    },
    OnError(cb) {
        // ...
        ws.onErro(cb);
    },
    OnClose(cb) {
        // ...
        ws.onClose(cb);
    }
};

neffos.SetWebsocketAdapter(adapter);

High frequency disconnection

when i use iris as backdend and neffos.js as frontend,i found the web page will fisconnect in High frequency,because there are a lot of messsage and requests between frontend and backend ;however connection between two clients between both in golang or one in golang and the other one in java will not happen this,so i guess is there some issues in neffos.js,if so ,i wishi you can fix this problem,thank you very much

Uncaught Error for declining connection initialization at "OnNameSpaceconnect"

Hi,

I'm declingin the initialiation the connection of the websocket to a namespace as follows:

var serverEvents = websocket.Namespaces{
	namespace: websocket.Events{
		websocket.OnNamespaceConnect: func(nsConn *websocket.NSConn, msg websocket.Message) error {
			return fmt.Errorf("No role 'admin'")
		},
		websocket.OnNamespaceConnected: [...]

At the client side the connection initialization is from the examples as follows:

async function runExample() {
        try {
          const conn = await neffos.dial(
            wsURL,
            {
              default: {
                // "default" namespace.
                _OnNamespaceConnect: function(nsConn, msg) {
                  console.log(nsConn);
                  console.log(msg);
                },
                _OnNamespaceConnected: function(nsConn, msg) {
                  addMessage("connected to namespace: " + msg.Namespace);
                  handleNamespaceConnectedConn(nsConn);
                },
                _OnNamespaceDisconnect: [...]

The error from the js library is as follows:

Uncaught (in promise) Error: No role 'admin'
at deserializeMessage (neffos.min.js:1)
at a.handleMessage (neffos.min.js:1)
at a.handle (neffos.min.js:1)
at WebSocket.i.onmessage (neffos.min.js:1)

My use case is that I want only allow specific users the connection to a websocket namespace. On the client side the returned error should be caught and sent to the end user properly.

Did I cancel the initialization of the websocket connection to the namespace in the correct way?

[BUG] Don't support binary message.

When i send message from iris, which i SetBinary: true.
Then i got this error.
jserror

error_detail

Here is my client. Just use the example code.

 var scheme = document.location.protocol == "https:" ? "wss" : "ws";
        var port = document.location.port ? ":" + document.location.port : "";
        var wsURL = scheme + "://" + document.location.hostname + port + "/stream";
        
        function handleError(reason) {
            console.log(reason);
        }

        function handleNamespaceConnectedConn(nsConn) {

        }
        // const username = window.prompt("Your username?");
        async function runExample() {
            // You can omit the "default" and simply define only Events, the namespace will be an empty string"",
            // however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well.
            try {
                const conn = await neffos.dial(wsURL, {
                    default: { // "default" namespace.
                        _OnNamespaceConnected: function (nsConn, msg) {
                            handleNamespaceConnectedConn(nsConn)
                        },
                        _OnNamespaceDisconnect: function (nsConn, msg) {
                        },
                        stream: function (nsConn, msg) { // "stream" event.
                            var serialized = new Uint8Array(msg.Body.split(","));
                        }
                    }
                },{
                    headers: {
                        "X-Username": "",
                    }
                });
                // You can either wait to conenct or just conn.connect("connect")
                // and put the `handleNamespaceConnectedConn` inside `_OnNamespaceConnected` callback instead.
                // const nsConn = await conn.connect("default");
                // nsConn.emit(...); handleNamespaceConnectedConn(nsConn);
                conn.connect("default");
            } catch (err) {
                handleError(err);
            }
        }

Bug: await

if (!isBrowser) {
const nodeWS = await import('ws');
WebSocket = nodeWS.WebSocket;
}

you should remove the keyword "await" in this code!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.