Send message to socketcluster channel from outside worker - javascript

There is an external resource I am watching and whenever the data changes I would like to send a message to a specific channel depending on the data.
I found two ways to do this by bonking my head repeatedly against the SC docs:
by watching from the workers, which is not ideal because when there are 1000 clients online the external data source gets hammered with requests from each worker
from a client, this is not ideal because i have to subscribe to the channel, send my message, then unsubscribe
How can I do this from within server.js?

A good example I have of sending data or a message when an external resource is changed, is for that external resource (like a website) to call your socket cluster on/emit through the standard way:
socket.on('documents/upload-file', function (data, respond) {
documents.uploadFile(data, respond, socket);
});
documents.uploadFile I use to upload a file with a base64 string.
I then save it file and store the document into the database.
The code the call a functiondocuments.getFileList which pull all the document info from the db
then that data is publish over a channel:
socket.exchange.publish('channels/documentList', updatedDocumentList);
The whole point of a worker is to handle external incoming events. Without knowing what your external resource is then I can't specifically say if this is the best approach for you, but hopefully it's a good place to start with.

Related

Client (JRE) read server (node) variables directly?

I am trying to set up a server where clients can connect and essentially "raise their hand", which lights up for every client, but only one at a time. I currently just use the express module to send a POST response on button-click. The server takes it as JSON and writes it to a file. All the clients are constantly requesting this file to check the status to see if the channel is clear.
I suspect there is a more streamlined approach for this, but I do not know what modules or methods might be best. Can the server push variables to the clients in some way, instead of the clients constantly requesting a file? Then the client script can receive the variable and change the page elements accordingly?
Usually, this kind of task is done by using WebSockets. Since you already have socket.io set up, it'd be great to reuse it.
From the server, start emitting different messages:
socket.emit("hand", { userId: <string> });
From the client, listen to the new event and invoke whatever the appropriate behavior is:
socket.on("hand", (payload) => {
// payload.userId contains user ID
});

Get real time updates from Microsoft Graph API (beta) Communication API

I am current using Microsoft Graph API (beta) to get Presence Status e.g. Online, away etc. in an spfx webpart (using React) using GraphClient:
this.props.context.msGraphClientFactory.getClient().then(async (client: MSGraphClient) => {
let response = await client
.api('/communications/getPresencesByUserId')
.version('beta')
.post(postData)
console.log("Communication API Response: "+ response);
this.usersWithPresence = response.value;
});
This is working fine, but to get updated status of a user, I have to refresh the page so another API call is made and updated presence status. I want to do it like this happens in 'Skype'.
What I need is suggestions about a mechanism that I can apply to get
real time updates in user's presence status, so as soon as user
updates the status this is reflected in my webpart. I know I can use
setInterval or setTimeout functions to request for presence status
after specific intervals but for learning purposes i don't want to
request API this way again and again but rather getting updated
message from server like this happens using web sockets. How a web
socket sort of stuff can be applied with this API?
your suggestions are welcome.
Today this API doesn't support any kind of subscription mechanism to when the status changes. There is a uservoice entry you can upvote for it. That means the only way to get any changes is to periodically poll it. As for socket io, the only support today is for SharePoint lists, for any other resource you need to stand up your own infrastructure to relay the message.

Web sockets, socket.io or other alternative

I'm kinda new on sockets related subject so I'm sorry for any dumb question.
I would like to do something like this… I have am hybrid app and a website, and I wanted that when I click in a button on the app, it shows me alert/notificaion on the website. I read about Socket io and it does the job on localhost, but I want na alternative that not uses a server behind, since I'm not being able to run it using CPANEL (What I have access to)
Is it possible to have like a "direct" connection from the app to the site when I click the button?
You can consider using firebase for this:
In your javascript:
// execute the following script on click
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.8.1/firebase-messaging.js');
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': 'YOUR-SENDER-ID'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
messaging.send({data: "your data if you want to send"}).then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
// similarly, on your browser:
messaging.onMessage(function(payload) {
console.log('Message received. ', payload);
// ...
});
link: https://firebase.google.com/docs/
Hope it helps
Let's break the problem down into a few parts, starting with the transport to the browser, as that's what you're asking about.
Web Sockets are a way to establish a bi-directional connection between a server and a client. It's a standard implemented by most any modern browser. Socket.IO is a web-socket-like abstraction that can use Web Sockets or other transports under the hood. It was originally built as sort of a polyfill, allowing messages to be sent via Web Sockets, or even long-polling. Using Socket.IO doesn't give you any additional capability than you have with just the browser, but it does provide some nice abstractions for "rooms" and such.
If you're sending data only from the server to the client, Web Sockets aren't the ideal choice. For streaming of data in general, the Fetch API and ReadableStream are more appropriate. Then, you can just make a normal HTTP connection. However, what you're looking for is event-style data, and for that there are Server-Sent Events (SSE). https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events Basically, you instantiate an EventSource object on the client, pointed at a URL on the server. The client automatically maintains a connection, reconnecting if necessary. It's also capable of synchronizing to a point in the stream, providing the server with the last message received so that the client can be caught up to present time.
Now, how does your server endpoint know when to send this data, and what to send? Ideally, you'll use some sort of pub/sub system. These capabilities are built into Redis, which is commonly used for this. (There are others as well, if you don't like Redis for some reason.) Basically, when your server receives something from the app, the app is "publishing" a message to a particular channel where all "subscribers" will receive it. Your server will be that EventSource and can simply relay data (verifying it and authenticating of course, along the way).
You can write a PHP script that has a POST/GET endpoint. Your app will communicate to this endpoint. The endpoint needs to handle the message and write it to a database. Your website can then poll to see if there are any new entries, and show something if there are
Alright, let's do it in PHP. This is just the most basic example. Just put it somewhere and link to the script from your app.
<?php
function requestVars($type = 'REQUEST'){
if($type == 'REQUEST')
$r = $_REQUEST;
elseif($type == 'POST')
$r = $_POST;
elseif($type == 'GET')
$r = $_GET;
$ret = array();
foreach($r as $r1 => $r2)
$ret[$r1] = $r2;
return $ret;
}
$vars = requestVars(); //get variables from request
echo $vars['var1']; // var1 is what comes in from the client
?>
I haven't tested this, so if something is wrong let me know.

Force Service Worker to only return cached responses when specific page is open

I have built a portal which provides access to several features, including trouble ticket functionality.
The client has asked me to make trouble ticket functionality available offline. They want to be able to "check out" specific existing tickets while online, which are then accessible (view/edit) while the user's device is out-of-range of any internet connection. Also, they want the ability to create new tickets while offline. Then, when the connection is available, they will check in the changed/newly created tickets.
I have been tinkering with Service Workers and reviewing some good documentation on them, and I feel I have a basic understanding of how to cache the data.
However, since I only want to make the Ticketing portion of the portal available offline, I don't want the service worker caching or returning cached data when any other page of the portal is being accessed. All pages are in the same directory, so the service worker, once loaded, would by default intercept all requests from all pages in the portal.
How can I set up the service worker to only respond with cached data when the Tickets page is open?
Do I have to manually check the window.location value when fetch events occur? For example,
if (window.location == 'https://www.myurl.com/tickets')
{
// try to get the request from network. If successful, cache the result.
// If not successful, try returning the request from the cache.
}
else
{
// only try the network, and don't cache the result.
}
There are many supporting files that need to be loaded for the page (i.e. css files, js files, etc.) so it's not enough to simply check the request.url for the page name. Will 'window.location' be accessible in the service worker event, and is this a reasonable way to accomplish this?
Use service worker scoping
I know that you mentioned that you currently have all pages served from the same directory... but if you have any flexibility over your web app's URL structure at all, then the cleanest approach would be to serve your ticket functionality from URLs that begin with a unique path prefix (like /tickets/) and then host your service worker from /tickets/service-worker.js. The effort to reorganize your URLs may be worthwhile if it means being able to take advantage of the default service worker scoping and just not have to worry about pages outside of /tickets/ being controlled by a service worker.
Infer the referrer
There's information in this answer about determining what the referring window client URL is from within your service worker's fetch handler. You can combine that with an initial check in the fetch handler to see if it's a navigation request and use that to exit early.
const TICKETS = '/tickets';
self.addEventListener('fetch', event => {
const requestUrl = new URL(event.request.url);
if (event.request.mode === 'navigate' && requestUrl.pathname !== TICKETS) {
return;
}
const referrerUrl = ...; // See https://stackoverflow.com/questions/50045641
if (referrerUrl.pathname !== TICKETS) {
return;
}
// At this point, you know that it's either a navigation for /tickets,
// or a request for a subresource from /tickets.
});

How do I send a channel message using channel.trigger with websocket-rails gem

I'm building a simple real-time chat app to learn how to use websockets with RoR and I don't think I'm understanding how channels work because they're not doing what I expect. I can successfully send a message to my Rails app using the dispatcher.trigger() method, and use my websocket controller to broadcast a message to all clients that subscribe to the channel. That all works fine. What does NOT work is using a channel (via the channel.trigger() method) to send a message to other clients. The websocket-rails wiki says...
Channel events currently happen outside of the Event Router flow. They
are meant for broadcasting events to a group of connected clients
simultaneously. If you wish to handle events with actions on the
server, trigger the event on the main dispatcher and specify which
controller action should handle it using the Event Router.
If I understand this correctly, I should be able to user the channel.trigger() method to broadcast a message to clients connected to the channel, without the message being routed through my RoR app, but it should still reach the other connected clients. So here's my code...
var dispatcher = new WebSocketRails('localhost:3000/websocket');
var channel = dispatcher.subscribe('channel_name');
channel.bind('channel_message', function(data) {
alert(data.message);
});
$("#send_message_button").click(function() {
obj = {message: "test"};
channel.trigger('channel_message', obj);
});
With the code listed above, I would expect that when I click the button, it sends a channel message using channel.trigger() and the channel_message binding should be executed on all clients, displaying an alert that reads "test". That doesn't happen. I'm using Chrome tools to inspect the websocket traffic and it shows the message being sent...
["channel_message",{"id":113458,"channel":'channel_name',"data":{"message":"test"},"token":"96fd4f51-6321-4309-941f-38110635f86f"}]
...but no message is received. My questions are...
Am I misunderstanding how channel-based websockets work with the websocket-rails gem?
If not, what am I doing wrong?
Thanks in advance for all your wisdom!
I was able to reproduce a working copy based on an off-the-shelf solution from the wiki along with your very own code.
I've packaged the whole thing here. The files you might be interested are home_controller.rb, application.js and home/index.html.erb.
It seems your understanding of channel-based websockets is correct. About the code, make sure to load the websocket javascript files and to enclose your code inside a document.ready. I had the exact same problem you're having without the latter.
//= require websocket_rails/main
$(function() {
// your code here...
});
Let me know if it works. Best Luck!

Categories

Resources