diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..ca1a55d
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,25 @@
+BookmarkHub Auto-Sync
+Copyright 2024 Maciej Pindela
+
+This product includes software originally developed as BookmarkHub
+Copyright (c) dudor (https://github.com/dudor/BookmarkHub)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+================================================================================
+MODIFICATIONS:
+- Added real-time auto-sync functionality
+- Implemented visual sync status indicators
+- Improved sync conflict resolution logic
+- Removed popup notifications in favor of badge indicators
+================================================================================
\ No newline at end of file
diff --git a/README.md b/README.md
index 9a835a1..779de17 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,11 @@
-
- BookmarkHub is a browser plug-in that can synchronize your bookmarks between different browsers.
+ BookmarkHub with real-time auto-sync - automatically syncs your bookmarks when changes are detected.
+
+ Based on original BookmarkHub by dudor
Feedback
·
diff --git a/bookmarkhub-0.0.6-firefox.zip b/bookmarkhub-1.0.1-chrome.zip
similarity index 83%
rename from bookmarkhub-0.0.6-firefox.zip
rename to bookmarkhub-1.0.1-chrome.zip
index d30d988..b74db28 100644
Binary files a/bookmarkhub-0.0.6-firefox.zip and b/bookmarkhub-1.0.1-chrome.zip differ
diff --git a/bookmarkhub-0.0.6-chrome.zip b/bookmarkhub-1.0.1-firefox.zip
similarity index 83%
rename from bookmarkhub-0.0.6-chrome.zip
rename to bookmarkhub-1.0.1-firefox.zip
index ebaca38..7f04123 100644
Binary files a/bookmarkhub-0.0.6-chrome.zip and b/bookmarkhub-1.0.1-firefox.zip differ
diff --git a/bookmarkhub-0.0.6-sources.zip b/bookmarkhub-1.0.1-sources.zip
similarity index 97%
rename from bookmarkhub-0.0.6-sources.zip
rename to bookmarkhub-1.0.1-sources.zip
index a1a7cae..88208a3 100644
Binary files a/bookmarkhub-0.0.6-sources.zip and b/bookmarkhub-1.0.1-sources.zip differ
diff --git a/chrome-extension-build/manifest.json b/chrome-extension-build/manifest.json
index 0fbebc2..5a4c37a 100644
--- a/chrome-extension-build/manifest.json
+++ b/chrome-extension-build/manifest.json
@@ -1 +1 @@
-{"manifest_version":3,"name":"__MSG_extensionName__","description":"__MSG_extensionDescription__","version":"1.0.0","icons":{"16":"icons/16.png","32":"icons/32.png","48":"icons/48.png","128":"icons/128.png"},"default_locale":"en","permissions":["storage","bookmarks","notifications"],"host_permissions":["https://*.github.com/","https://*.githubusercontent.com/"],"optional_host_permissions":["*://*/*"],"background":{"service_worker":"background.js"},"action":{"default_popup":"popup.html"},"options_ui":{"open_in_tab":false,"page":"options.html"}}
\ No newline at end of file
+{"manifest_version":3,"name":"__MSG_extensionName__","description":"__MSG_extensionDescription__","version":"1.0.1","icons":{"16":"icons/16.png","32":"icons/32.png","48":"icons/48.png","128":"icons/128.png"},"default_locale":"en","permissions":["storage","bookmarks","notifications"],"host_permissions":["https://*.github.com/","https://*.githubusercontent.com/"],"optional_host_permissions":["*://*/*"],"background":{"service_worker":"background.js"},"action":{"default_popup":"popup.html"},"options_ui":{"open_in_tab":false,"page":"options.html"}}
\ No newline at end of file
diff --git a/create_icon.py b/create_icon.py
deleted file mode 100644
index e4ab021..0000000
--- a/create_icon.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/env python3
-from PIL import Image, ImageDraw, ImageFont
-import os
-
-def create_sync_icon(size, output_path):
- """Create a sync icon with circular arrows"""
- # Create a new image with transparent background
- img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
- draw = ImageDraw.Draw(img)
-
- # Define colors
- primary_color = (66, 133, 244, 255) # Google Blue
- secondary_color = (52, 168, 83, 255) # Google Green
-
- # Calculate dimensions
- margin = size // 8
- center_x = size // 2
- center_y = size // 2
- radius = (size - 2 * margin) // 2
-
- # Draw bookmark shape (simplified book icon)
- book_width = size // 3
- book_height = size // 2.5
- book_x = center_x - book_width // 2
- book_y = center_y - book_height // 2
-
- # Draw book shape
- draw.rounded_rectangle(
- [book_x, book_y, book_x + book_width, book_y + book_height],
- radius=3,
- fill=primary_color
- )
-
- # Draw sync arrows around the book
- arrow_thickness = max(2, size // 32)
-
- # Top-right arrow (clockwise)
- arrow_start_x = book_x + book_width + 5
- arrow_start_y = book_y
- arrow_end_x = arrow_start_x + size // 8
- arrow_end_y = book_y + size // 8
-
- # Draw curved arrow using arc
- arc_box = [book_x - 10, book_y - 10, book_x + book_width + 10, book_y + book_height + 10]
- draw.arc(arc_box, start=270, end=90, fill=secondary_color, width=arrow_thickness)
-
- # Draw arrow heads
- # Top arrow head
- arrow_size = max(4, size // 16)
- draw.polygon([
- (book_x + book_width + 8, book_y),
- (book_x + book_width + 8 + arrow_size, book_y - arrow_size // 2),
- (book_x + book_width + 8 + arrow_size, book_y + arrow_size // 2)
- ], fill=secondary_color)
-
- # Bottom arrow head
- draw.polygon([
- (book_x - 8, book_y + book_height),
- (book_x - 8 - arrow_size, book_y + book_height - arrow_size // 2),
- (book_x - 8 - arrow_size, book_y + book_height + arrow_size // 2)
- ], fill=secondary_color)
-
- # Save the image
- img.save(output_path, 'PNG')
- print(f"Created {output_path}")
-
-# Create icons in different sizes
-sizes = {
- 'icon-16.png': 16,
- 'icon-32.png': 32,
- 'icon-48.png': 48,
- 'icon-96.png': 96,
- 'icon-128.png': 128,
-}
-
-os.makedirs('public', exist_ok=True)
-
-for filename, size in sizes.items():
- output_path = os.path.join('public', filename)
- create_sync_icon(size, output_path)
-
-print("All icons created successfully!")
\ No newline at end of file
diff --git a/package.json b/package.json
index 659face..62099d2 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"sync bookmarks"
],
"private": true,
- "version": "1.0.0",
+ "version": "1.0.2",
"type": "module",
"author": "dudor",
"license": "",
diff --git a/releases/v1.0.2/bookmarkhub-1.0.2-chrome.zip b/releases/v1.0.2/bookmarkhub-1.0.2-chrome.zip
new file mode 100644
index 0000000..a8c5816
Binary files /dev/null and b/releases/v1.0.2/bookmarkhub-1.0.2-chrome.zip differ
diff --git a/releases/v1.0.2/bookmarkhub-1.0.2-firefox.zip b/releases/v1.0.2/bookmarkhub-1.0.2-firefox.zip
new file mode 100644
index 0000000..d6314eb
Binary files /dev/null and b/releases/v1.0.2/bookmarkhub-1.0.2-firefox.zip differ
diff --git a/releases/v1.0.2/bookmarkhub-1.0.2-sources.zip b/releases/v1.0.2/bookmarkhub-1.0.2-sources.zip
new file mode 100644
index 0000000..26d4461
Binary files /dev/null and b/releases/v1.0.2/bookmarkhub-1.0.2-sources.zip differ
diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts
index 7cd5da9..ca80bbc 100644
--- a/src/entrypoints/background.ts
+++ b/src/entrypoints/background.ts
@@ -10,13 +10,28 @@ export default defineBackground(() => {
initializeAutoSync();
});
+ // Also sync on browser startup (when browser opens)
+ browser.runtime.onStartup.addListener(() => {
+ console.log('Browser startup detected, initializing auto-sync...');
+ initializeAutoSync();
+ });
+
let curOperType = OperType.NONE;
let curBrowserType = BrowserType.CHROME;
let autoSyncDebounceTimer: NodeJS.Timeout | null = null;
+ let autoSyncIntervalTimer: NodeJS.Timeout | null = null;
let lastRemoteUpdateTime: number = 0;
let isSyncing: boolean = false;
const DEBOUNCE_DELAY = 3000; // 3 seconds debounce delay
+ // Load last remote update time from storage on startup
+ browser.storage.local.get('lastRemoteUpdateTime').then(result => {
+ if (result.lastRemoteUpdateTime) {
+ lastRemoteUpdateTime = result.lastRemoteUpdateTime;
+ console.log('Loaded last remote update time:', new Date(lastRemoteUpdateTime).toISOString());
+ }
+ });
+
// Icon states
const ICON_SYNCED = "✓";
const ICON_NOT_SYNCED = "!";
@@ -407,21 +422,58 @@ export default defineBackground(() => {
// Auto-sync functions
async function initializeAutoSync() {
- // Clear any existing debounce timer
+ // Clear any existing timers
if (autoSyncDebounceTimer) {
clearTimeout(autoSyncDebounceTimer);
autoSyncDebounceTimer = null;
}
+ if (autoSyncIntervalTimer) {
+ clearInterval(autoSyncIntervalTimer);
+ autoSyncIntervalTimer = null;
+ }
const setting = await Setting.build();
// If auto-sync is enabled, perform an initial sync to get the latest state
if (setting.enableAutoSync && setting.githubToken && setting.gistID) {
- // Show synced state on startup (optimistic)
- browser.action.setBadgeText({ text: ICON_SYNCED });
- browser.action.setBadgeBackgroundColor({ color: "#00AA00" });
+ // Show syncing state on startup
+ browser.action.setBadgeText({ text: ICON_SYNCING });
+ browser.action.setBadgeBackgroundColor({ color: "#0000FF" });
+ // Perform initial sync to get the latest state
await performAutoSync();
+
+ // Set up periodic sync to check for remote changes
+ // Use more frequent checks initially, then slower for battery efficiency
+ const syncIntervalMinutes = setting.autoSyncInterval || 5; // Default to 5 minutes
+ let currentIntervalMs = Math.min(60000, syncIntervalMinutes * 60 * 1000); // Start with 1 minute or configured interval
+
+ // Initial quick check after 30 seconds to catch immediate changes
+ setTimeout(async () => {
+ if (!isSyncing) {
+ await checkForRemoteChanges();
+ }
+ }, 30000);
+
+ // Set up regular interval
+ autoSyncIntervalTimer = setInterval(async () => {
+ // Only check for remote changes if not already syncing
+ if (!isSyncing) {
+ await checkForRemoteChanges();
+ }
+
+ // Gradually increase interval up to the configured max
+ if (currentIntervalMs < syncIntervalMinutes * 60 * 1000) {
+ clearInterval(autoSyncIntervalTimer!);
+ currentIntervalMs = Math.min(currentIntervalMs * 2, syncIntervalMinutes * 60 * 1000);
+
+ autoSyncIntervalTimer = setInterval(async () => {
+ if (!isSyncing) {
+ await checkForRemoteChanges();
+ }
+ }, currentIntervalMs);
+ }
+ }, currentIntervalMs);
}
}
@@ -445,6 +497,67 @@ export default defineBackground(() => {
}, DEBOUNCE_DELAY);
}
+ async function checkForRemoteChanges() {
+ try {
+ const setting = await Setting.build();
+
+ // Check if auto-sync is still enabled and configured
+ if (!setting.enableAutoSync || !setting.githubToken || !setting.gistID) {
+ return;
+ }
+
+ // Don't show syncing indicator for background checks to avoid UI noise
+ // Get remote bookmarks
+ const remoteGist = await BookmarkService.get();
+
+ if (remoteGist) {
+ const remoteData: SyncDataInfo = JSON.parse(remoteGist);
+
+ // Get local bookmarks for comparison
+ const localBookmarks = await getBookmarks();
+ const localData = formatBookmarks(localBookmarks);
+ const localContentHash = JSON.stringify(localData);
+ const remoteContentHash = JSON.stringify(remoteData.bookmarks);
+
+ // If content differs and remote is newer, download changes
+ if (localContentHash !== remoteContentHash) {
+ // Check if remote has newer changes based on timestamp
+ if (!lastRemoteUpdateTime || remoteData.createDate > lastRemoteUpdateTime) {
+ console.log('Auto-sync: Detected newer remote changes, downloading...');
+
+ // Show syncing state
+ isSyncing = true;
+ browser.action.setBadgeText({ text: ICON_SYNCING });
+ browser.action.setBadgeBackgroundColor({ color: "#0000FF" });
+ curOperType = OperType.SYNC;
+
+ // Download remote changes
+ await clearBookmarkTree();
+ await createBookmarkTree(remoteData.bookmarks);
+ const remoteCount = getBookmarkCount(remoteData.bookmarks);
+ await browser.storage.local.set({ remoteCount: remoteCount });
+ lastRemoteUpdateTime = remoteData.createDate;
+ await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime });
+
+ // Show synced state
+ browser.action.setBadgeText({ text: ICON_SYNCED });
+ browser.action.setBadgeBackgroundColor({ color: "#00AA00" });
+
+ curOperType = OperType.NONE;
+ isSyncing = false;
+ await refreshLocalCount();
+ }
+ } else {
+ // Update timestamp even if content is the same
+ lastRemoteUpdateTime = remoteData.createDate;
+ await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime });
+ }
+ }
+ } catch (error) {
+ console.error('Check for remote changes error:', error);
+ }
+ }
+
async function performAutoSync() {
try {
const setting = await Setting.build();
@@ -474,11 +587,6 @@ export default defineBackground(() => {
const remoteData: SyncDataInfo = JSON.parse(remoteGist);
const remoteCount = getBookmarkCount(remoteData.bookmarks);
- // Smart sync decision:
- // 1. If we haven't tracked remote time yet, compare content
- // 2. If remote was updated after our last known update AND content differs, download
- // 3. Otherwise upload our changes
-
const localContentHash = JSON.stringify(localData);
const remoteContentHash = JSON.stringify(remoteData.bookmarks);
@@ -486,13 +594,16 @@ export default defineBackground(() => {
// Already in sync, nothing to do
console.log('Auto-sync: Already in sync');
lastRemoteUpdateTime = remoteData.createDate;
- } else if (lastRemoteUpdateTime > 0 && remoteData.createDate > lastRemoteUpdateTime) {
- // Remote has newer changes, download them
- console.log('Auto-sync: Downloading newer remote changes');
+ await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime });
+ } else if (remoteData.createDate > Date.now() - 10000) {
+ // If remote was updated in the last 10 seconds, it's probably from another browser
+ // Download those changes instead of overwriting
+ console.log('Auto-sync: Recent remote changes detected, downloading...');
await clearBookmarkTree();
await createBookmarkTree(remoteData.bookmarks);
await browser.storage.local.set({ remoteCount: remoteCount });
lastRemoteUpdateTime = remoteData.createDate;
+ await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime });
} else {
// Upload local changes
console.log('Auto-sync: Uploading local changes');
@@ -513,6 +624,7 @@ export default defineBackground(() => {
await browser.storage.local.set({ remoteCount: localCount });
lastRemoteUpdateTime = syncdata.createDate;
+ await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime });
}
} else {
// No remote data, upload initial
@@ -534,6 +646,7 @@ export default defineBackground(() => {
await browser.storage.local.set({ remoteCount: localCount });
lastRemoteUpdateTime = syncdata.createDate;
+ await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime });
}
// Show synced icon (keep it persistent)