diff --git a/background.js b/background.js index 8b3a970..a236564 100644 --- a/background.js +++ b/background.js @@ -1,126 +1,54 @@ -function saveRevision(source) { - console.log('Syncing started from: ' + source); +import { createBookmarks, removeBookmarks } from "./bookmark.js"; +import { saveRevision, getRevision } from "./revision.js"; - chrome.bookmarks.getTree(async (tree) => { - 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 - } - }); +const syncBookmarks = async (id, bookmark) => { + console.log('Syncing bookmarks!'); + await saveRevision(); } -async function getRevision() { - const apiUrl = await chrome.storage.sync.get('apiUrl'); - const syncToken = await chrome.storage.sync.get("syncToken"); - - if (!apiUrl || !syncToken) +const loadBookmarks = async () => { + const data = await getRevision(); + if (!data) 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}`); - } - - const data = await response.json(); // Convert to JSON - const savedRev = await chrome.storage.sync.get('revId'); - if (data.revId === savedRev.revId) - return; - - chrome.storage.sync.set({ revId: data.revId }); - - removeAllChildren("1", () => createBookmarkTree(data.bookmarks, "1")); - removeAllChildren("2", () => createBookmarkTree(data.bookmarks, "2")); - } catch (e) { - // TODO GD: Indicate error + const savedRev = await chrome.storage.sync.get('revId'); + if (data.revId === savedRev.revId) { + console.log('Same revision ID, no update required'); + return; } + + removeListeners(); + + chrome.storage.sync.set({ revId: data.revId }); + + // TODO GD: "Raw" replacement, make this more efficient to avoid flickering + for (var child of data.bookmarks[0].children) { + await removeBookmarks(child.id); + await createBookmarks(child.children, child.id); + } + + addListeners(); } -// Recursively delete all children -function removeAllChildren(parentId, callback) { - chrome.bookmarks.getChildren(parentId, (children) => { - if (!children || children.length === 0) return callback?.(); - let count = children.length; - children.forEach((child) => { - if (child.url) { - chrome.bookmarks.remove(child.id, () => { - if (--count === 0) callback?.(); - }); - } else { - removeAllChildren(child.id, () => { - chrome.bookmarks.remove(child.id, () => { - if (--count === 0) callback?.(); - }); - }); - } - }); - }); +function removeListeners() { + chrome.bookmarks.onCreated.removeListener(syncBookmarks); + chrome.bookmarks.onRemoved.removeListener(syncBookmarks); + chrome.bookmarks.onChanged.removeListener(syncBookmarks); + chrome.bookmarks.onMoved.removeListener(syncBookmarks); + chrome.bookmarks.onChildrenReordered.removeListener(syncBookmarks); + chrome.bookmarks.onImportEnded.removeListener(syncBookmarks); } -// 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) => { - saveRevision('onCreated'); -}); +loadBookmarks(); -chrome.bookmarks.onRemoved.addListener((id, removeInfo) => { - saveRevision('onRemoved'); -}); - -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); \ No newline at end of file +// TODO GD: Make this websocket based +setInterval(loadBookmarks, 10000); \ No newline at end of file diff --git a/bookmark.js b/bookmark.js new file mode 100644 index 0000000..e13e422 --- /dev/null +++ b/bookmark.js @@ -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?.(); + }); + }); + } + }); +} diff --git a/manifest.json b/manifest.json index ba3393b..73cb48e 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Simple Sync", "description": "A simple extension", - "version": "1.0.0", + "version": "0.0.1", "icons": { "16": "icons/icon.jpg", "48": "icons/icon.jpg", @@ -17,7 +17,8 @@ }, "options_page": "options.html", "background": { - "service_worker": "background.js" + "service_worker": "background.js", + "type": "module" }, "permissions": [ "storage", diff --git a/popup.js b/popup.js index a87127c..8e1038c 100644 --- a/popup.js +++ b/popup.js @@ -4,10 +4,6 @@ const apiUrl = document.getElementById('apiUrl'); const syncToken = document.getElementById('syncToken'); document.addEventListener('DOMContentLoaded', () => { - chrome.storage.sync.get('revId', (savedRev) => { - revIdLabel.textContent = savedRev.revId || "No value stored"; - }); - chrome.storage.sync.get('apiUrl', (savedUrl) => { apiUrl.value = savedUrl.apiUrl || "No value stored"; }); diff --git a/revision.js b/revision.js new file mode 100644 index 0000000..623de46 --- /dev/null +++ b/revision.js @@ -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; + } +} \ No newline at end of file