Connecting to socket endpoint from a custom frontend
-
Hello, I am trying to get a realtime update for my custom frontend. I tried to connect to the socket endpoint using this plain jquery code:
var socket = io('http://localhost:4567', { transport: ["polling", "websocket"], path: "/socket.io/", }); socket.on('connect', () => { console.log("Connected ..."); }); socket.on('event:new_post', function (data) { console.log(data); }); socket.on('event:new_topic', function (data) { console.log(data); });
I can get the connect response on the browser console, but when I create a new topic, I can not get the update. Am I doing something wrong?
Please give me some advice. Thank you
-
@nullpointer I think
new_post
is different fromnew_topic
-
Update:
I can receive an event
event:user_status_change
on my browser console by using this additional code to catch all events:var listener = (eventName, ...args) => { console.log(eventName, args); } socket.onAny(listener);
But still, I can not receive
event:new_post
andevent:new_topic
. Did I miss something? -
-
-
I haven't looked too deeply into it, but I'm fairly sure the basic issue you're running into is that you're not in correct rooms - NodeBB actually doesn't just emit the events to every socket connected, instead it specifically selects users who are considered online.
The first one requires the client to be in a
uid_${uid}
room, while the latter is related to the way sockets report user activity and configurable via ACP (you can configure how long a client can be inactive before it's considered to be offline). I don't remember the specifics of the latter (I think you might just need to periodically send something), but the former requires your socket client to authenticate (you should be automatically put into an appropriate room if NodeBB can authenticate you).It works with cookie auth just fine, but it's won't be used by default across domains since browsers have good reasons not to let you just use other website's cookie. You can make it work by adding the website your code will run under to
Access-Control-Allow-Origin
(note:*
doesn't allow for credentials, you need to explicitly specify an origin here). which you can find under Settings>Advanced in NodeBB ACP (you may also need to setAccess-Control-Allow-Credentials
totrue
). Then you can just setwithCredentials
option in socket.io client configuration totrue
and it should work with your current logged-in user.If your code is running in eg. NodeJS you'll need to set
extraHeaders
option to an object like this:{ cookie: `express.sid=${cookieValue}` }
As to how to get the cookie - authorize the user via any HTTP client and retrieve it from the response. Unfortunately AFAIK for
socket.io
this is the only way to do this right now.Which I guess is something that might be changed? Right now sockets just check the session cookie, but now that WriteAPI is integrated with NodeBB it should be fairly simple to allow using Bearer Tokens from the write API if no session cookie is provided. But that's probably a feature request for NodeBB GitHub Issues
-
@oplik0 thanks for your detail explanation.
I have tried to change my configuration as you suggest. Here is my config:
Here is my client code:
var socket = io('https://dev-forum.domain.io', { transport: ["polling", "websocket"], path: "/socket.io/", extraHeaders: { withCredentials: true, cookie: 'express.sid=s%3Am0UuuyatTTSajjAnI5gGSukaJSHJaiKpIVhb3H7L.TW4c4vZn4blOnWV1gilF9HhMqFc9g9V7NnLLZHMAmFg; Domain=dev-forum.domain.io; Path=/; HttpOnly; SameSite=Lax', }, });
Now I got this error on my console:
Refused to set unsafe header "cookie" Access to XMLHttpRequest at 'https://dev-forum.domain.io/socket.io/?EIO=4&transport=polling&t=OKjgnov' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field withcredentials is not allowed by Access-Control-Allow-Headers in preflight response.
-
Update:
I saw this code in NodeBB unit test:
helpers.connectSocketIO = function (res, callback) { const io = require('socket.io-client'); let cookies = res.headers['set-cookie']; cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c)); const cookie = cookies[0]; const socket = io(nconf.get('base_url'), { path: `${nconf.get('relative_path')}/socket.io`, extraHeaders: { Origin: nconf.get('url'), Cookie: cookie, }, }); socket.on('connect', () => { callback(null, socket); }); socket.on('error', (err) => { callback(err); }); };
I have followed this code but still get the same result.
-
@nullpointer said in Connecting to socket endpoint from a custom frontend:
var socket = io('https://dev-forum.domain.io', { transport: ["polling", "websocket"], path: "/socket.io/", extraHeaders: { withCredentials: true, cookie: 'express.sid=s%3Am0UuuyatTTSajjAnI5gGSukaJSHJaiKpIVhb3H7L.TW4c4vZn4blOnWV1gilF9HhMqFc9g9V7NnLLZHMAmFg; Domain=dev-forum.domain.io; Path=/; HttpOnly; SameSite=Lax', }, });
withCredentials
is not a header - it's an option that should be on the same level in the settings object asextraHeaders
. It should be used instead of setting the cookie header manually in the browser - but it will only work if the browser has the cookies for the website you're connecting to and needs explicit permission from it via headers (theAccess-Control-Allow-*
headers).
This should look more like this:var socket = io('https://dev-forum.domain.io', { transport: ["polling", "websocket"], path: "/socket.io/", withCredentials: true, });
The reason the NodeBB test example works is that it's not ran inside a browser - NodeJS, as a server environment, has quite different security requirements than browsers and also just can't save credentials for each page (imagine what programming horror having this kind of mutable state in code would be...). I'm not sure what is your ultimate goal with this code, so that's why I mentioned the cookies as needed for server-side code.
-