Created
February 12, 2025 02:32
-
-
Save tonyonodi/29c4d5f7c234de5c7491c7e3acddce50 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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