seitime-frappe/realtime/index.js
Ankush Menat b7f2073380 feat: Extensible SocketIO
You can now specify custom event handlers for SocketIO.

Usage:

1. In your app's hooks.py add `has_realtime_event_handlers=True` so
Framework can assume your app contains custom handler and import them.
2. Create a file called `/app/realtime/handlers.js` with single module
   export a function that will setup handlers using socket.

Here's sample code:

```js
// This is /app_root/realtime/handler.js`

function chat_app_handlers(socket) {
    socket.on("hello_chat") {
	console.log("hello world!");
    }
}

module.exports = chat_app_handlers;
```

3. Restart SocketIO server and see if it worked by sending event from
   client. In desk based app you can do
   `frappe.realtime.socket.emit("hello_chat")`

Middlewares are not yet possible... will be worked upon __some other day__ [tm]

closes https://github.com/frappe/frappe/issues/21528
2024-04-06 18:17:27 +05:30

76 lines
2.1 KiB
JavaScript

const { Server } = require("socket.io");
const http = require("node:http");
const fs = require("fs");
const path = require("path");
const { get_conf, get_redis_subscriber } = require("../node_utils");
const conf = get_conf();
const server = http.createServer();
let io = new Server(server, {
cors: {
// Should be fine since we are ensuring whether hostname and origin are same before adding setting listeners for s socket
origin: true,
credentials: true,
},
cleanupEmptyChildNamespaces: true,
});
// Multitenancy implementation.
// allow arbitrary sitename as namespaces
// namespaces get validated during authentication.
const realtime = io.of(/^\/.*$/);
// load and register middlewares
const authenticate = require("./middlewares/authenticate");
realtime.use(authenticate);
// =======================
// load and register handlers
const frappe_handlers = require("./handlers/frappe_handlers");
function on_connection(socket) {
frappe_handlers(realtime, socket);
socket.installed_apps.forEach((app) => {
let file = `../../${app}/realtime/handlers.js`;
let abs_path = path.resolve(__dirname, file);
if (fs.existsSync(abs_path)) {
let handler_factory = require(file);
handler_factory(socket);
}
});
// ESBUild "open in editor" on error
socket.on("open_in_editor", async (data) => {
await subscriber.connect();
subscriber.publish("open_in_editor", JSON.stringify(data));
});
}
realtime.on("connection", on_connection);
// =======================
// Consume events sent from python via redis pub-sub channel.
const subscriber = get_redis_subscriber();
(async () => {
await subscriber.connect();
subscriber.subscribe("events", (message) => {
message = JSON.parse(message);
let namespace = "/" + message.namespace;
if (message.room) {
io.of(namespace).to(message.room).emit(message.event, message.message);
} else {
// publish to ALL sites only used for things like build event.
realtime.emit(message.event, message.message);
}
});
})();
// =======================
let uds = conf.socketio_uds;
let port = conf.socketio_port;
server.listen(uds || port, () => {
console.log("Realtime service listening on: ", uds || port);
});