Revision loading, sync isses fix

This commit was merged in pull request #1.
This commit is contained in:
2025-09-24 07:34:25 +00:00
5 changed files with 143 additions and 120 deletions

View File

@@ -1,126 +1,54 @@
function saveRevision(source) { import { createBookmarks, removeBookmarks } from "./bookmark.js";
console.log('Syncing started from: ' + source); import { saveRevision, getRevision } from "./revision.js";
chrome.bookmarks.getTree(async (tree) => { const syncBookmarks = async (id, bookmark) => {
const apiUrl = await chrome.storage.sync.get('apiUrl'); console.log('Syncing bookmarks!');
const syncToken = await chrome.storage.sync.get("syncToken"); await saveRevision();
if (!apiUrl || !syncToken)
return;
const savedRev = await chrome.storage.sync.get('revId');
const url = apiUrl.apiUrl + '/saveRevision';
const options = {
headers: {
'content-type': 'application/json',
'syncToken': syncToken.syncToken
},
method: 'POST',
body: JSON.stringify({
'revId': savedRev.revId,
'bookmarks': tree
})
};
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json(); // Convert to JSON
chrome.storage.sync.set({ revId: data.revId });
} catch (e) {
// TODO GD: Indicate error
}
});
} }
async function getRevision() { const loadBookmarks = async () => {
const apiUrl = await chrome.storage.sync.get('apiUrl'); const data = await getRevision();
const syncToken = await chrome.storage.sync.get("syncToken"); if (!data)
if (!apiUrl || !syncToken)
return; return;
const url = apiUrl.apiUrl + '/getRevision'; const savedRev = await chrome.storage.sync.get('revId');
const options = { if (data.revId === savedRev.revId) {
headers: { console.log('Same revision ID, no update required');
'content-type': 'application/json', return;
'syncToken': syncToken.syncToken
},
method: 'GET'
};
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
} }
const data = await response.json(); // Convert to JSON removeListeners();
const savedRev = await chrome.storage.sync.get('revId');
if (data.revId === savedRev.revId)
return;
chrome.storage.sync.set({ revId: data.revId }); chrome.storage.sync.set({ revId: data.revId });
removeAllChildren("1", () => createBookmarkTree(data.bookmarks, "1")); // TODO GD: "Raw" replacement, make this more efficient to avoid flickering
removeAllChildren("2", () => createBookmarkTree(data.bookmarks, "2")); for (var child of data.bookmarks[0].children) {
} catch (e) { await removeBookmarks(child.id);
// TODO GD: Indicate error await createBookmarks(child.children, child.id);
} }
addListeners();
} }
// Recursively delete all children function removeListeners() {
function removeAllChildren(parentId, callback) { chrome.bookmarks.onCreated.removeListener(syncBookmarks);
chrome.bookmarks.getChildren(parentId, (children) => { chrome.bookmarks.onRemoved.removeListener(syncBookmarks);
if (!children || children.length === 0) return callback?.(); chrome.bookmarks.onChanged.removeListener(syncBookmarks);
let count = children.length; chrome.bookmarks.onMoved.removeListener(syncBookmarks);
children.forEach((child) => { chrome.bookmarks.onChildrenReordered.removeListener(syncBookmarks);
if (child.url) { chrome.bookmarks.onImportEnded.removeListener(syncBookmarks);
chrome.bookmarks.remove(child.id, () => {
if (--count === 0) callback?.();
});
} else {
removeAllChildren(child.id, () => {
chrome.bookmarks.remove(child.id, () => {
if (--count === 0) callback?.();
});
});
}
});
});
} }
// Sync events function addListeners() {
chrome.bookmarks.onCreated.addListener(syncBookmarks);
chrome.bookmarks.onRemoved.addListener(syncBookmarks);
chrome.bookmarks.onChanged.addListener(syncBookmarks);
chrome.bookmarks.onMoved.addListener(syncBookmarks);
chrome.bookmarks.onChildrenReordered.addListener(syncBookmarks);
chrome.bookmarks.onImportEnded.addListener(syncBookmarks);
}
chrome.bookmarks.onCreated.addListener((id, bookmark) => { loadBookmarks();
saveRevision('onCreated');
});
chrome.bookmarks.onRemoved.addListener((id, removeInfo) => { // TODO GD: Make this websocket based
saveRevision('onRemoved'); setInterval(loadBookmarks, 10000);
});
chrome.bookmarks.onChanged.addListener((id, changeInfo) => {
saveRevision('onChanged');
});
chrome.bookmarks.onMoved.addListener((id, moveInfo) => {
saveRevision('onMoved');
});
chrome.bookmarks.onChildrenReordered.addListener((id, reorderInfo) => {
saveRevision('onChildrenReordered');
});
chrome.bookmarks.onImportEnded.addListener(() => {
saveRevision('onImportEnded');
});
getRevision();
setInterval(getRevision, 5000);

34
bookmark.js Normal file
View File

@@ -0,0 +1,34 @@
// Recursively create bookmarks from nodes
export async function createBookmarks(nodes, parentId) {
for (const node of nodes) {
if (node.url) {
chrome.bookmarks.create({ parentId, title: node.title, url: node.url });
} else {
chrome.bookmarks.create({ parentId, title: node.title }, (newFolder) => {
if (node.children && node.children.length > 0) {
createBookmarks(node.children, newFolder.id);
}
});
}
}
}
// Recursively delete all children
export async function removeBookmarks(parentId, done) {
const children = await chrome.bookmarks.getChildren(parentId);
if (!children || children.length === 0) return done?.();
let count = children.length;
children.forEach((child) => {
if (child.url) {
chrome.bookmarks.remove(child.id, () => {
if (--count === 0) done?.();
});
} else {
removeBookmarks(child.id, () => {
chrome.bookmarks.remove(child.id, () => {
if (--count === 0) done?.();
});
});
}
});
}

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Simple Sync", "name": "Simple Sync",
"description": "A simple extension", "description": "A simple extension",
"version": "1.0.0", "version": "0.0.1",
"icons": { "icons": {
"16": "icons/icon.jpg", "16": "icons/icon.jpg",
"48": "icons/icon.jpg", "48": "icons/icon.jpg",
@@ -17,7 +17,8 @@
}, },
"options_page": "options.html", "options_page": "options.html",
"background": { "background": {
"service_worker": "background.js" "service_worker": "background.js",
"type": "module"
}, },
"permissions": [ "permissions": [
"storage", "storage",

View File

@@ -4,10 +4,6 @@ const apiUrl = document.getElementById('apiUrl');
const syncToken = document.getElementById('syncToken'); const syncToken = document.getElementById('syncToken');
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
chrome.storage.sync.get('revId', (savedRev) => {
revIdLabel.textContent = savedRev.revId || "No value stored";
});
chrome.storage.sync.get('apiUrl', (savedUrl) => { chrome.storage.sync.get('apiUrl', (savedUrl) => {
apiUrl.value = savedUrl.apiUrl || "No value stored"; apiUrl.value = savedUrl.apiUrl || "No value stored";
}); });

64
revision.js Normal file
View File

@@ -0,0 +1,64 @@
export async function saveRevision() {
console.log('ASDF');
const tree = await chrome.bookmarks.getTree();
const apiUrl = await chrome.storage.sync.get('apiUrl');
const syncToken = await chrome.storage.sync.get("syncToken");
if (!apiUrl || !syncToken)
return;
const savedRev = await chrome.storage.sync.get('revId');
const url = apiUrl.apiUrl + '/saveRevision';
const options = {
headers: {
'content-type': 'application/json',
'syncToken': syncToken.syncToken
},
method: 'POST',
body: JSON.stringify({
'revId': savedRev.revId,
'bookmarks': tree
})
};
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json(); // Convert to JSON
chrome.storage.sync.set({ revId: data.revId });
} catch (e) {
// TODO GD: Indicate error
}
}
export async function getRevision() {
const apiUrl = await chrome.storage.sync.get('apiUrl');
const syncToken = await chrome.storage.sync.get("syncToken");
if (!apiUrl || !syncToken)
return;
const url = apiUrl.apiUrl + '/getRevision';
const options = {
headers: {
'content-type': 'application/json',
'syncToken': syncToken.syncToken
},
method: 'GET'
};
try {
const response = await fetch(url, options);
if (!response.ok)
throw new Error(`HTTP error! Status: ${response.status}`);
return await response.json();
} catch (e) {
console.log(e);
return null;
}
}