diff --git a/.idea/runConfigurations/Start_CDN.xml b/.idea/runConfigurations/Start_CDN.xml
index 8228c9671..90fafd6c4 100644
--- a/.idea/runConfigurations/Start_CDN.xml
+++ b/.idea/runConfigurations/Start_CDN.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index dfbf2c4a5..4582bd986 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -24,12 +24,19 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
@@ -43,56 +50,56 @@
- {
+ "keyToString": {
+ "NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix",
+ "NIXITCH_NIX_CONF_DIR": "",
+ "NIXITCH_NIX_OTHER_STORES": "",
+ "NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source",
+ "NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile",
+ "NIXITCH_NIX_REMOTE": "",
+ "NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory",
+ "Node.js.Server.ts.executor": "Debug",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "RunOnceActivity.git.unshallow": "true",
+ "git-widget-placeholder": "master",
+ "javascript.nodejs.core.library.configured.version": "24.8.0",
+ "javascript.nodejs.core.library.typings.version": "24.7.0",
+ "last_opened_file_path": "/home/Rory/git/spacebar/server-master/src/util/migration/postgres",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.standard": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.standard": "",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_interpreter_path": "node",
+ "nodejs_package_manager_path": "npm",
+ "npm.Start API.executor": "Run",
+ "npm.Start CDN.executor": "Run",
+ "npm.Start Gateway.executor": "Run",
+ "npm.build.executor": "Run",
+ "npm.start.executor": "Debug",
+ "prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier",
+ "settings.editor.selected.configurable": "com.github.copilot.settings.customInstructions.CopilotInstructionsConfigurable",
+ "ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib"
},
- "keyToStringList": {
- "DatabaseDriversLRU": [
- "postgresql"
+ "keyToStringList": {
+ "DatabaseDriversLRU": [
+ "postgresql"
],
- "GitStage.ChangesTree.GroupingKeys": [
- "directory",
- "module",
- "repository"
+ "GitStage.ChangesTree.GroupingKeys": [
+ "directory",
+ "module",
+ "repository"
]
}
-}]]>
+}
+
-
@@ -117,6 +124,7 @@
+
@@ -165,6 +173,8 @@
+
+
diff --git a/src/gateway/listener/listener.ts b/src/gateway/listener/listener.ts
index f76adfb29..f7e41f5ed 100644
--- a/src/gateway/listener/listener.ts
+++ b/src/gateway/listener/listener.ts
@@ -187,9 +187,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
}
break;
case "CHANNEL_CREATE":
- if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) {
- return;
- }
+ if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) return;
this.events[id] = await listenEvent(id, consumer, listenOpts);
break;
case "RELATIONSHIP_ADD":
diff --git a/src/util/util/Event.ts b/src/util/util/Event.ts
index 6f2b0d9a9..a1c841437 100644
--- a/src/util/util/Event.ts
+++ b/src/util/util/Event.ts
@@ -21,7 +21,14 @@ import { RabbitMQ } from "./RabbitMQ";
import EventEmitter from "events";
import { EVENT, Event } from "../interfaces";
import { randomUUID } from "crypto";
+import path from "path";
+import { Socket } from "node:net";
+import { FSWatcher } from "node:fs";
+import { Stopwatch } from "./Stopwatch";
+import { Config } from "./Config";
export const events = new EventEmitter();
+let unixSocketListener: UnixSocketListener | null = null;
+let unixSocketWriter: UnixSocketWriter | null = null;
export async function emitEvent(payload: Omit) {
const id = (payload.guild_id || payload.channel_id || payload.user_id) as string;
@@ -42,6 +49,12 @@ export async function emitEvent(payload: Omit) {
// todo: should we retry publishng the event?
console.log("[RabbitMQ] ", e);
}
+ } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) {
+ if (!unixSocketWriter) {
+ unixSocketWriter = new UnixSocketWriter(process.env.EVENT_SOCKET_PATH);
+ await unixSocketWriter.init();
+ }
+ await unixSocketWriter.emit(payload);
} else if (process.env.EVENT_TRANSMISSION === "process") {
process.send?.({ type: "event", event: payload, id } as ProcessEvent);
} else {
@@ -57,6 +70,14 @@ export async function initEvent() {
// use event emitter
// use process messages
}
+
+ await listenEvent("spacebar", async (event) => {
+ console.log("[Event] Received spacebar event:", event);
+ if ((event.event as string) === "SB_RELOAD_CONFIG") {
+ console.log("[Event] Reloading config due to RELOAD_CONFIG event");
+ await Config.init(true);
+ }
+ });
}
export interface EventOpts extends Event {
@@ -84,6 +105,12 @@ export async function listenEvent(event: string, callback: (event: EventOpts) =>
return await rabbitListen(channel, event, callback, {
acknowledge: opts?.acknowledge,
});
+ } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) {
+ if (!unixSocketListener) {
+ unixSocketListener = new UnixSocketListener(path.join(process.env.EVENT_SOCKET_PATH, `${process.pid}.sock`));
+ await unixSocketListener.init();
+ }
+ return await unixSocketListener.listen(event, callback);
} else if (process.env.EVENT_TRANSMISSION === "process") {
const cancel = async () => {
process.removeListener("message", listener);
@@ -155,3 +182,126 @@ async function rabbitListen(channel: Channel, id: string, callback: (event: Even
return cancel;
}
+
+class UnixSocketListener {
+ eventEmitter: EventEmitter;
+ socketPath: string;
+
+ constructor(socketPath: string) {
+ this.eventEmitter = new EventEmitter();
+ this.socketPath = socketPath;
+ }
+
+ async init() {
+ const net = await import("net");
+ const server = net.createServer((socket) => {
+ socket.on("connect", () => {
+ console.log("[Events] Unix socket client connected");
+ });
+ socket.on("data", (data) => {
+ try {
+ const payload = JSON.parse(data.toString());
+ this.eventEmitter.emit(payload.id, payload.event);
+ } catch (e) {
+ console.error("[Events] Failed to parse unix socket data:", e);
+ }
+ });
+ });
+
+ server.listen(this.socketPath, () => {
+ console.log(`Unix socket server listening on ${this.socketPath}`);
+ });
+
+ const shutdown = () => {
+ console.log("[Events] Closing unix socket server");
+ server.close();
+ process.exit(0);
+ };
+ for (const sig of ["SIGINT", "SIGTERM", "SIGQUIT"] as const) {
+ process.on(sig, shutdown);
+ }
+ }
+
+ async listen(event: string, callback: (event: EventOpts) => unknown): Promise<() => Promise> {
+ const listener = (data: any) => {
+ callback({
+ ...data,
+ cancel,
+ });
+ };
+
+ this.eventEmitter.addListener(event, listener);
+
+ const cancel = async () => {
+ this.eventEmitter.removeListener(event, listener);
+ this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() - 1);
+ };
+
+ this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() + 1);
+
+ return cancel;
+ }
+}
+
+class UnixSocketWriter {
+ socketPath: string;
+ clients: { [key: string]: Socket } = {};
+ watcher: FSWatcher;
+
+ constructor(socketPath: string) {
+ this.socketPath = socketPath;
+ }
+
+ async init() {
+ const net = await import("net");
+ const fs = await import("fs");
+
+ if (!fs.opendirSync(this.socketPath)) throw new Error("Unix socket path does not exist or is not a directory: " + this.socketPath);
+
+ console.log("[Events] Unix socket writer initializing for", this.socketPath);
+
+ const connect = (file: string) => {
+ const fullPath = path.join(this.socketPath, file);
+ this.clients[fullPath] = net.createConnection(fullPath, () => {
+ console.log("[Events] Unix socket client connected to", fullPath);
+ });
+ };
+
+ // connect to all sockets, now and in the future
+ this.watcher = fs.watch(this.socketPath, {}, (eventType, filename) => {
+ console.log("[Events] Unix socket writer received watch sig", eventType, filename);
+ connect(filename!);
+ });
+
+ // connect to existing sockets if any
+ fs.readdir(this.socketPath, (err, files) => {
+ if (err) return console.error("[Events] Unix socket writer failed to read directory:", err);
+
+ console.log("[Events] Unix socket writer found existing sockets:", files);
+ files.forEach((file) => {
+ connect(file);
+ });
+ });
+ }
+
+ async emit(event: Event) {
+ if (!this.clients) throw new Error("UnixSocketWriter not initialized");
+
+ const tsw = Stopwatch.startNew();
+ const payload = JSON.stringify({ id: (event.guild_id || event.channel_id || event.user_id) as string, event });
+ for (const socket of Object.entries(this.clients)) {
+ if(socket[1].destroyed) {
+ console.log("[Events] Unix socket writer found destroyed socket, removing:", socket[0]);
+ delete this.clients[socket[0]];
+ continue;
+ }
+ try {
+ socket[1].write(payload);
+ } catch (e) {
+ console.error("[Events] Unix socket writer failed to write to socket", socket[0], ":", e);
+ }
+ }
+
+ console.log(`[Events] Unix socket writer emitted to ${Object.entries(this.clients).length} sockets in ${tsw.elapsed().totalMilliseconds}ms`);
+ }
+}