Skip to content

Instantly share code, notes, and snippets.

@tonyonodi
Created February 12, 2025 02:32
Show Gist options
  • Save tonyonodi/29c4d5f7c234de5c7491c7e3acddce50 to your computer and use it in GitHub Desktop.
Save tonyonodi/29c4d5f7c234de5c7491c7e3acddce50 to your computer and use it in GitHub Desktop.
import fs from "fs";
import express from "express";
import { WebSocketServer } from "ws";
import { NodeWSServerAdapter } from "@automerge/automerge-repo-network-websocket";
import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs";
import os from "os";
import type { DocumentId, Message, PeerId } from "@automerge/automerge-repo";
import {
isValidAutomergeUrl,
isValidDocumentId,
Repo,
} from "@automerge/automerge-repo";
const wss = new WebSocketServer({ noServer: true });
const dir = "automerge-sync-server-data";
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
var hostname = os.hostname();
const PORT = process.env.PORT !== undefined ? parseInt(process.env.PORT) : 3030;
const app = express();
app.use(express.static("public"));
const isMessage = (message: any): message is Message => {
return message.hasOwnProperty("documentId");
};
const db = new Map<DocumentId, PeerId>();
class AuthWSServerAdapter extends NodeWSServerAdapter {
send(message: any) {
// If the message is not a message, send it as is.
if (!isMessage(message)) {
super.send(message);
return;
}
// We only want to intercept messages with a documentId.
const { documentId, targetId } = message;
if (!documentId) {
super.send(message);
return;
}
const isDocOwner = db.has(documentId) && db.get(documentId) === targetId;
if (!isDocOwner) {
console.log("intercepting message", message);
super.send({
type: "error",
senderId: targetId,
message: "You are not the owner of this document",
targetId: targetId,
});
return;
}
super.send(message);
}
}
const network = new AuthWSServerAdapter(wss);
// Add document to db if it's not already there.
network.addListener("message", ({ documentId, senderId }) => {
if (!isValidDocumentId(documentId as DocumentId)) return;
if (db.has(documentId as DocumentId)) return;
db.set(documentId as DocumentId, senderId as PeerId);
});
const serverRepo = new Repo({
network: [network],
storage: new NodeFSStorageAdapter(dir),
peerId: `storage-server-${hostname}` as PeerId,
// Decides whethe a peer should get documents they haven't asked for.
// If they do ask for a document they get it regardless.
sharePolicy: async (peerId: PeerId, docId: DocumentId | undefined) => {
// I've no idea under what circumstances docId is undefined. Just return true for now.
// TODO: work out what to do here.
if (!docId) {
return true;
}
const isDocOwner = db.has(docId) && db.get(docId) === peerId;
return isDocOwner;
},
});
app.get("/", (req, res) => {
res.send(`👍 @automerge/example-sync-server is running`);
});
const server = app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
server.on("upgrade", (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (socket) => {
wss.emit("connection", socket, request);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment