Sockets, Obsidian and IPC

Published on:2024/12/09
Banner for Sockets, Obsidian and IPC

Introduction

After looking into I/O solutions for Obsidian, sockets seemed like the good next thing to try.

Prototype

In Obsidian

const net = require("net");
const fs = require("fs");

class SocketServerPlugin {
  constructor() {
    this.server = null;
    this.socketPath = "/tmp/obsidian-socket";
  }

  start() {
    // Remove existing socket file if it exists
    if (fs.existsSync(this.socketPath)) {
      fs.unlinkSync(this.socketPath);
    }

    this.server = net.createServer((socket) => {
      console.log("Client connected");

      socket.on("data", (data) => {
        const message = data.toString().trim();
        console.log("Received:", message);

        if (message === "get_active_file") {
          const activeFile = app.workspace.getActiveFile();
          socket.write(activeFile ? activeFile.path : "No active file");
        } else {
          socket.write("Unknown command");
        }
      });

      socket.on("end", () => {
        console.log("Client disconnected");
      });
    });

    this.server.listen(this.socketPath, () => {
      console.log("Server listening on", this.socketPath);
    });
  }

  stop() {
    if (this.server) {
      this.server.close();
    }
    if (fs.existsSync(this.socketPath)) {
      fs.unlinkSync(this.socketPath);
    }
  }
}

// Create and start the server
const socketServer = new SocketServerPlugin();
socketServer.start();

// `socketServer.stop()` to stop the server

Anywhere else

(abstracted)

SOCKET_PATH="/tmp/obsidian-socket"
echo -n "get_active_file" | nc -U $SOCKET_PATH

Or

socket_path = "/tmp/obsidian-socket"
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client_socket.connect(socket_path)
client_socket.sendall(b"get_active_file")
response = client_socket.recv(1024)
print("Received:", response.decode())
       

Conclusion

Some thoughts need to be put in before designing but this may be the long term solution.

We were also left with 2 problems.

  • the inability to get return values
  • the inability to reliably build layers of macros and functions across files.

The 2 could probably be fixed with the same plugin where Obsidian reads/write to a socket (allowing for return values)

And where the sockets listens for both:

  • raw Javascript code to execute, essentially opening a REPL in IPC
  • specific commands and functions called defined in a folder in the vault that plugin monitors