Websockets sending personal messages

So I have finally set up the channels code and got that running as a chat app… Code is below for anyone interested.

My question is now… How would I send a message to a specific person? Say I have a situation where player A, B and D who are listening on websockets are in specific channels (all different), but I want them all to get a message without C or E seeing it.

Is there a way I can say, “Take this list and then send this message no matter what channel they are on”?


/** ensure that we have URL.username and set a default channel */
if (!isNull(URL.username))
    Session.username = URL.username;

if (isEmpty(URL.username ?: "")){

    echo("<p>Username is not defined. Set it using the URL parameter username, e.g. ?Username=Lucy");
    abort;
}



if (isEmpty(URL.channel ?: "")){

    echo("<p>Channel is not defined. Set it using the URL parameter username, e.g. ?Channel=Galaxy Radio");
    abort;
}


param name="channel" default="default";
<script>
    var channel  = "#url.Channel#";
    var endpoint = "/WS/Chat/" + channel;
    var protocol = (document.location.protocol == "https:") ? "wss://" : "ws://";

    var wschat   = new WebSocket(protocol + document.location.host + endpoint);

    document.write(`<p>[Lance Chat .2a] connected to "${channel}" as "#URL.username#"`);


	function onopen(evt)
	{
		var msg = evt.data ? JSON.parse(evt.data) : evt;
		console.log(msg);

		var ChatFrom = msg.FROM;
		var ChatMessage = msg.MESSAGE;

		if (ChatFrom == '<server>') {
			ChatFrom = ChatFrom + "System";
		}

		document.getElementById("ChatScreen").innerHTML = document.getElementById("ChatScreen").innerHTML + ChatFrom + ': ' + ChatMessage + '<br>';
	}

	function onmessage(evt)
	{
		var msg = evt.data ? JSON.parse(evt.data) : evt;
		console.log(msg);

		var ChatFrom = msg.FROM;
		var ChatMessage = msg.MESSAGE;

		if (ChatFrom == '<server>') {
			ChatFrom = ChatFrom + "System";
		}

		document.getElementById("ChatScreen").innerHTML = document.getElementById("ChatScreen").innerHTML + ChatFrom + ': ' + ChatMessage + '<br>';
	}

	function onerror(evt)
	{
		var msg = evt.data ? JSON.parse(evt.data) : evt;
		console.log(msg);

		var ChatFrom = msg.FROM;
		var ChatMessage = msg.MESSAGE;

		if (ChatFrom == '<server>') {
			ChatFrom = ChatFrom + "System";
		}

		document.getElementById("ChatScreen").innerHTML = document.getElementById("ChatScreen").innerHTML + ERROR + ': ' + ChatMessage + '<br>';
	}

	function onclose(evt)
	{
		var msg = evt.data ? JSON.parse(evt.data) : evt;
		console.log(msg);

		var ChatFrom = msg.FROM;
		var ChatMessage = msg.MESSAGE;

		if (ChatFrom == '<server>') {
			ChatFrom = ChatFrom + "System";
		}

		document.getElementById("ChatScreen").innerHTML = document.getElementById("ChatScreen").innerHTML + ChatFrom + '(Close): ' + ChatMessage + '<br>';
	}

    wschat.onopen    = onopen;
    wschat.onmessage = onmessage;
    wschat.onerror   = onerror;
	wschat.onclose   = onclose;


	function SendMessage()
	{
		var message = document.getElementById("Message").value;
		wschat.send(message);
		document.getElementById("Message").value='';
		
	}

</script>

<form onkeypress="return event.keyCode != 13">
	<input type="Text" ID="Message" value="">
	<input type="Button" name="Submit" value="Submit" onclick="SendMessage();">
</form>
<P>


<select name="Channel" ID="Channel" onchange="window.location.href = 'index.cfm?username=#url.Username#&Channel=' + document.getElementById('Channel').value;">

	<option selected>Please select a channel</option>
	<option value="Galaxy Radio">Galaxy Radio</option>
	<option value="GNN">GNN</option>
	<option value="Davul Government">Davul Government</option>
</select>

<HR>
<div ID="ChatScreen">
</div>
------------------------------
1 Like

We had the same problem when migrating from an old CF Websocket Extension to Lucee, see https://lucee.daemonite.io/t/lucee-websockets-extension/2067/9

In the end the solution was quit simple, also put the client into an own channel, e.g.:

onopen() {
   [...]
   arguments.websocket.subscribe(this.channel & "-" & arguments.websocket.getId());
}

So when you need to send a client specific message, send it to his personal channel, normal messages go to your main channel.

Ok… I get that (and yeah, I thought about inverting the problem by just having everyone on their own channel and generating a list of everyone “subscribed” to it.

So I think it works… But my testing is slowed down by this other issue.

When I have code like this…

function onMessage(websocket, message, sessionScope, applicationScope){

    this.notifyChannel(
         arguments.websocket
        ,{
             from   : arguments.sessionScope.username
            ,message: arguments.message
			,location: '#arguments.location#'
        }
    );
}

/**
* This is a helper method that adds a timestamp to the data, serializes
* it as JSON, and broadcasts it to all of the subscribers to the channel
* of this websocket connection
*/
private function notifyChannel(websocket, data){

    var chanId  = arguments.websocket.getPathParameters().channel;
    var connMgr = arguments.websocket.getConnectionManager();
    arguments.data.channel   = chanId;
    arguments.data.timestamp = getTickCount();
    arguments.data.location = 'Test';

    connMgr.broadcast(chanId, serializeJson(arguments.data));
}

it doesn’t show me “location”. Just the standard 4 things still…

Sorry if this sounds like a noob question. It is I’m sure because I’m still trying to learn websockets… But why when I send a message, it doesn’t show “location”? I’m trying to piecemeal something together from the example given and it’s slow going.

From the docs at Listener Component API · isapir/lucee-websocket Wiki · GitHub :

All of the methods are called with named arguments, meaning that using different names or order in the method signature will have no affect on the arguments.

Yes. I saw that. However, I am unsure what you mean by that as all the
arguments are still named.

This is the code…

function onMessage(websocket, message, sessionScope, applicationScope){

    this.notifyChannel(
         arguments.websocket
        ,{
             from   : arguments.sessionScope.username
            ,message: arguments.message

,location: “#arguments.location#”
}
);
}

/**
* This is a helper method that adds a timestamp to the data, serializes
* it as JSON, and broadcasts it to all of the subscribers to the channel
* of this websocket connection
*/
private function notifyChannel(websocket, data){

    var chanId  = arguments.websocket.getPathParameters().channel;
    var connMgr = arguments.websocket.getConnectionManager();
    arguments.data.channel   = chanId;
    arguments.data.timestamp = getTickCount();
    arguments.data.location = 'Test';

    connMgr.broadcast(chanId, serializeJson(arguments.data));
}

Please tell me how I can make the ‘location’ show up in the console with
the following code…

function onmessage(evt)
{
var msg = evt.data ? JSON.parse(evt.data) : evt;
console.log(msg);
}

Because the only thing that shows up is Channel, timestamp, from, message.
Why isn’t location working?

It means that in onMessage(websocket, message, sessionScope, applicationScope) you only have 4 arguments: websocket, message, sessionScope, applicationScope.

There is no arguments.location

Yes… I tried that solution earlier…

function onMessage(websocket, message, sessionScope, applicationScope, location){

    this.notifyChannel(
         arguments.websocket
        ,{
             from   : arguments.sessionScope.username
            ,message: arguments.message
			,location: "#arguments.location#"
        }
    );
}

Same result. Location does not show up.

Object {MESSAGE: “asdadas”, TIMESTAMP: 1502046027204, CHANNEL: “Galaxy”, FROM: “GM”}
CHANNEL

Please read my replies again.

Yes. I get that it uses named arguments. I also understand that using different names or order in the method signature will have no affect on the arguments.

Ok… First of all… It makes no sense. If you use a name that isn’t in the arguments variable (like “Arguments.zxyyz”, it’s not going to work because it doesn’t exist. arguments.location DOES exist…

private function notifyChannel(websocket, data){

    var chanId  = arguments.websocket.getPathParameters().channel;
    var connMgr = arguments.websocket.getConnectionManager();
    arguments.data.channel   = chanId;
    arguments.data.timestamp = getTickCount();
    arguments.data.location = getTickCount();

    connMgr.broadcast(chanId, serializeJson(arguments.data));
}

“It means that in onMessage(websocket, message, sessionScope, applicationScope) you only have 4 arguments: websocket, message, sessionScope, applicationScope.”

Yes. I understand that…

“There is no arguments.location”

I just told you I put that there before and it didn’t work. It was one of the first things I tried before posting here.

Please look at my reply.

function onMessage(websocket, message, sessionScope, applicationScope, location){

    this.notifyChannel(
         arguments.websocket
        ,{
             from   : arguments.sessionScope.username
            ,message: arguments.message
			,location: "#arguments.location#"
        }
    );
}

Please note location is now listed as an argument.

Instead of telling me to re-read something that may be obvious to you, the person who WROTE the extension, perhaps you can take a step back and look at it from someone new to websockets and see the issue I’m seeing (though we seem to have a history of not being able to communicate well when I tell you the output to the console is not json compatible).

If it’s simple to you, please tell me what exactly I need to change in my code to get location to show up and I can take it from there. I need that first step please.

You keep saying that you understand that. I’m sorry, but your statements clearly show that you don’t.

I have very limited time allotted to FOSS. I’m trying to help you, and instead of making it easier for me to help you, you keep complaining and practically “yelling” at me, and I yes, you did that before too in the JSON thread that you referenced.

These are not WebSocket issues. These are standard Lucee/CFML/JavaScript/JSON. There is plenty of documentation out there on those subjects. I did not invent any new APIs here.

But since you did not re-read my posts as I asked, I will try one more time, pasting my above posts inline here, and trying to emphasize in bold:

That means that even if you write method signature of onMessage in your Listener Component like any of these examples:

function onMessage(){ ... }

or

function onMessage(someArgumentNameThatWillBeIgnored){ ... }

or

function onMessage(x, y, z){ ... }

or

function onMessage(a, b, c, d, e){ ... }

You still get only 4 arguments: websocket, message, sessionScope, applicationScope

There will be no someArgumentNameThatWillBeIgnored, x, y, z, a, b, c, d, nor e in the arguments collection.

The only reason that we have the argument names in the method signature is for it to be self-documenting, so that you can easily see the argument names without having to refer to the documentation.

I don’t know, because I have no idea what you are trying to do. I don’t know what location means in this context, or where it is supposed to come from.

Even if the arguments were not passed by-name (the other way is by-position, BTW), the onMessage() method is called by the extension and you have no control over that.

How do you expect the extension to pass an argument named location? Where is the value supposed to come from? How would the extension know that you modified the signature of the event handler and that it is supposed to pass that argument? (these are rhetorical questions, no need to answer them).

The extension doesn’t know either what location means in this context, and that’s one of the reasons why there is no location argument.

The only way to add the location argument to onMessage() is to modify the extension so that it passes that argument when it calls onMessage(). Since it does not pass that argument, it does not exist.

I really hope that this clarifies things for you.

Thank you for the more thought out reply…

It does clear everything up. So in order to pass it INTO the call so it comes out, I have to pass it into the sessionScope or applicationScope?

If so, why can I put in TIMESTAMP when I notify the channel in the code below? Why can I not just put in

arguments.data.location = ‘Test’;

since you seem to be setting timestamp and channel arguments here.

private function notifyChannel(websocket, data){

    var chanId  = arguments.websocket.getPathParameters().channel;
    var connMgr = arguments.websocket.getConnectionManager();
    arguments.data.channel   = chanId;
    arguments.data.timestamp = getTickCount();
 
    connMgr.broadcast(chanId, serializeJson(arguments.data));
}

You are correct that you should be able to send location as listed below (i.e., in the data argument)…if it’s not working, try re-initializing your app / restarting lucee as it may be using the old code.

private function notifyChannel(websocket, data){

    var chanId  = arguments.websocket.getPathParameters().channel;
    var connMgr = arguments.websocket.getConnectionManager();
    arguments.data.channel   = chanId;
    arguments.data.timestamp = getTickCount();
    arguments.data.location = "Test";
 
    connMgr.broadcast(chanId, serializeJson(arguments.data));
}
function onMessage(websocket, message, sessionScope, applicationScope){

    this.notifyChannel(
         arguments.websocket
        ,{
             from   : arguments.sessionScope.username
            ,message: arguments.message
        }
    );
}

Thanks… I’ll try it out when I’m on the computer where it works… On my
main one, I keep getting an error…

ClassFormatError:net/twentyonesolutions/lucee/websocket/functions/WebsocketRegister
: Unsupported major.minor version 52.0

Any idea what could be causing this?

Thank you Psarin. It’s now working (well, except for the error I posted above).

What did you add in? It didn’t show up in the post :slight_smile:

It was a mispost. I didn’t add in anything. Just had to restart it over again. I fixed it.

Please see https://lucee.daemonite.io/t/websocketregister-error/2172