Improved drawing sync: MIME detection, WebP/JPEG support, sync log + frontend updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
a8779d7be1
commit
d97591b242
7 files changed
+544
-144
No files matched your search
+47
-5
@@ -136,6 +136,18 @@ function decrypt(data) {
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
function detectImageMime(buffer, filePath = "") {
|
||||
if (buffer && buffer.length >= 12) {
|
||||
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) return "image/png";
|
||||
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return "image/jpeg";
|
||||
if (buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WEBP") return "image/webp";
|
||||
}
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
||||
if (ext === ".webp") return "image/webp";
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
/* ─────────── Vector clock conflict resolution ─────────── */
|
||||
function mergeVectorClocks(clockA, clockB) {
|
||||
const merged = { ...clockA };
|
||||
@@ -313,6 +325,20 @@ app.get("/api/notes/:id", authMiddleware, (req, res) => {
|
||||
}
|
||||
}
|
||||
delete note.content_encrypted;
|
||||
|
||||
// Read drawing data from file if exists
|
||||
if (note.drawing_data_path && fs.existsSync(note.drawing_data_path)) {
|
||||
try {
|
||||
const raw = fs.readFileSync(note.drawing_data_path);
|
||||
const mime = detectImageMime(raw, note.drawing_data_path);
|
||||
note.drawingData = `data:${mime};base64,` + raw.toString("base64");
|
||||
} catch {
|
||||
note.drawingData = null;
|
||||
}
|
||||
} else {
|
||||
note.drawingData = null;
|
||||
}
|
||||
|
||||
res.json(note);
|
||||
});
|
||||
|
||||
@@ -404,12 +430,28 @@ app.put("/api/notes/:id/drawing", authMiddleware, (req, res) => {
|
||||
const note = db.prepare("SELECT id FROM notes WHERE id = ? AND user_id = ?").get(req.params.id, req.userId);
|
||||
if (!note) return res.status(404).json({ error: "Not found" });
|
||||
|
||||
const filePath = path.join(UPLOAD_DIR, `drawing_${req.params.id}.png`);
|
||||
const base64Data = drawingData.replace(/^data:image\/\w+;base64,/, "");
|
||||
if (!drawingData || typeof drawingData !== "string") return res.status(400).json({ error: "drawingData required" });
|
||||
const m = drawingData.match(/^data:(image\/(?:png|jpeg|jpg|webp));base64,(.+)$/);
|
||||
if (!m) return res.status(400).json({ error: "Invalid drawing format" });
|
||||
const mime = m[1];
|
||||
const ext = mime === "image/png" ? "png" : mime === "image/webp" ? "webp" : "jpg";
|
||||
const base64Data = m[2];
|
||||
const filePath = path.join(UPLOAD_DIR, `drawing_${req.params.id}.${ext}`);
|
||||
|
||||
// Remove older file variants from previous format writes.
|
||||
for (const oldExt of ["png", "jpg", "jpeg", "webp"]) {
|
||||
const oldPath = path.join(UPLOAD_DIR, `drawing_${req.params.id}.${oldExt}`);
|
||||
if (oldPath !== filePath && fs.existsSync(oldPath)) {
|
||||
try { fs.unlinkSync(oldPath); } catch {}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(filePath, Buffer.from(base64Data, "base64"));
|
||||
|
||||
db.prepare("UPDATE notes SET drawing_data_path = ?, updated_at = datetime('now'), version = version + 1 WHERE id = ?")
|
||||
.run(filePath, req.params.id);
|
||||
db.prepare("UPDATE notes SET drawing_data_path = ?, updated_at = datetime('now'), version = version + 1, device_id = ? WHERE id = ?")
|
||||
.run(filePath, req.deviceId, req.params.id);
|
||||
db.prepare("INSERT INTO sync_log (user_id, entity_type, entity_id, action, payload, device_id) VALUES (?, 'note', ?, 'update', ?, ?)")
|
||||
.run(req.userId, req.params.id, JSON.stringify({ drawingUpdated: true }), req.deviceId);
|
||||
broadcastToUser(req.userId, { type: "note_updated", data: { id: req.params.id, drawingUpdated: true } }, req.deviceId);
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
@@ -537,4 +579,4 @@ server.listen(PORT, () => {
|
||||
console.log(` 📡 WebSocket available at ws://localhost:${PORT}/ws`);
|
||||
console.log(` 🔒 JWT Secret: ${JWT_SECRET.slice(0, 12)}...`);
|
||||
console.log(` 💾 Database: ${path.join(DATA_DIR, "inknote.db")}\n`);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user