diff --git a/README.md b/README.md
index ad0a4ca..e1ce794 100644
--- a/README.md
+++ b/README.md
@@ -70,3 +70,26 @@ Gmail, Drive, YouTube, Meet, Calendar, Sheets, Gemini
## β If you like BookmarkFolder
Give the repo a **star** π on GitHub β it helps a lot!
+
+
+## βοΈ Firebase Cloud Backup (optional)
+
+You can now back up and restore your bookmarks using Firebase so reinstalling the extension wonβt lose your data.
+
+1. Create a Firebase project
+2. Enable **Authentication β Email/Password**
+3. Enable **Firestore Database** (start in test mode while developing)
+4. Open the new tab page and enter:
+ - Firebase Web API Key
+ - Firebase Project ID
+5. Sign up / sign in from the sidebar panel
+6. Use **Backup to Cloud** and **Restore from Cloud**
+
+### Firestore document path used
+`bookmarkfolderUsers/{firebaseLocalUserId}`
+
+Each document stores:
+- `data` (stringified JSON payload)
+- `updatedAt` (unix timestamp in ms)
+
+> Security note: set Firestore security rules so users can only read/write their own doc.
diff --git a/manifest.json b/manifest.json
index 11f2107..95e8dc0 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,10 +2,14 @@
"manifest_version": 3,
"name": "Rishnu Dk-custom bookmark tab",
"version": "1.0",
-
- "permissions": ["storage"],
-
+ "permissions": [
+ "storage"
+ ],
"chrome_url_overrides": {
"newtab": "newtab.html"
- }
+ },
+ "host_permissions": [
+ "https://identitytoolkit.googleapis.com/*",
+ "https://firestore.googleapis.com/*"
+ ]
}
diff --git a/newtab.html b/newtab.html
index 951027f..7b1bd6e 100644
--- a/newtab.html
+++ b/newtab.html
@@ -24,6 +24,29 @@
Bookmarks
diff --git a/script.js b/script.js
index 1ddeb61..a41d308 100644
--- a/script.js
+++ b/script.js
@@ -10,12 +10,16 @@ let openFolders = {};
let dragSourceTile = null;
let dragPlaceholder = null;
+let firebaseConfigState = { apiKey: "", projectId: "" };
+let firebaseAuthState = { idToken: "", localId: "", email: "" };
+let lastUpdatedAt = Date.now();
+
// ---------------------------------------------------------
// LOAD EVERYTHING
// ---------------------------------------------------------
function loadAll() {
- chrome.storage.sync.get(["bookmarks", "folders", "openFolders"], res => {
+ chrome.storage.sync.get(["bookmarks", "folders", "openFolders", "firebaseConfig", "firebaseAuth", "lastUpdatedAt"], async res => {
const raw = res.bookmarks || [];
@@ -35,11 +39,18 @@ function loadAll() {
}
});
- chrome.storage.sync.set({ bookmarks, folders, openFolders });
+ firebaseConfigState = res.firebaseConfig || { apiKey: "", projectId: "" };
+ lastUpdatedAt = res.lastUpdatedAt || Date.now();
+ firebaseAuthState = res.firebaseAuth || { idToken: "", localId: "", email: "" };
+
+ chrome.storage.sync.set({ bookmarks, folders, openFolders, firebaseConfig: firebaseConfigState, firebaseAuth: firebaseAuthState, lastUpdatedAt });
renderFolders();
populateFolderSelect();
renderGoogleApps();
+ initializeCloudUI();
+
+ await restoreFromCloud(false);
});
}
loadAll();
@@ -48,7 +59,9 @@ loadAll();
// SAVE ALL
// ---------------------------------------------------------
function saveAll() {
- chrome.storage.sync.set({ bookmarks, folders, openFolders });
+ lastUpdatedAt = Date.now();
+ chrome.storage.sync.set({ bookmarks, folders, openFolders, firebaseConfig: firebaseConfigState, firebaseAuth: firebaseAuthState, lastUpdatedAt });
+ backupToCloud(false);
}
// ---------------------------------------------------------
@@ -584,3 +597,198 @@ function renderGoogleApps() {
div.appendChild(item);
});
}
+
+
+// ---------------------------------------------------------
+// FIREBASE CLOUD BACKUP (REST API)
+// ---------------------------------------------------------
+function initializeCloudUI() {
+ const apiKeyInput = document.getElementById("firebaseApiKey");
+ const projectIdInput = document.getElementById("firebaseProjectId");
+ const emailInput = document.getElementById("authEmail");
+ const passwordInput = document.getElementById("authPassword");
+
+ if (!apiKeyInput || !projectIdInput) return;
+
+ apiKeyInput.value = firebaseConfigState.apiKey || "";
+ projectIdInput.value = firebaseConfigState.projectId || "";
+ emailInput.value = firebaseAuthState.email || "";
+
+ apiKeyInput.addEventListener("change", () => {
+ firebaseConfigState.apiKey = apiKeyInput.value.trim();
+ persistCloudState();
+ setCloudStatus("Firebase config updated.");
+ });
+
+ projectIdInput.addEventListener("change", () => {
+ firebaseConfigState.projectId = projectIdInput.value.trim();
+ persistCloudState();
+ setCloudStatus("Firebase config updated.");
+ });
+
+ document.getElementById("signUpBtn")?.addEventListener("click", () => authWithFirebase("signUp"));
+ document.getElementById("signInBtn")?.addEventListener("click", () => authWithFirebase("signInWithPassword"));
+ document.getElementById("signOutBtn")?.addEventListener("click", signOutFirebase);
+ document.getElementById("backupNowBtn")?.addEventListener("click", () => backupToCloud(true));
+ document.getElementById("restoreNowBtn")?.addEventListener("click", () => restoreFromCloud(true));
+
+ updateCloudStatusFromState();
+}
+
+function persistCloudState() {
+ chrome.storage.sync.set({ firebaseConfig: firebaseConfigState, firebaseAuth: firebaseAuthState });
+}
+
+function setCloudStatus(message, isError = false) {
+ const status = document.getElementById("cloudStatus");
+ if (!status) return;
+ status.textContent = message;
+ status.style.color = isError ? "#fca5a5" : "#93c5fd";
+}
+
+function updateCloudStatusFromState() {
+ if (firebaseAuthState.email) {
+ setCloudStatus(`Connected as ${firebaseAuthState.email}`);
+ } else {
+ setCloudStatus("Not connected");
+ }
+}
+
+function cloudConfigReady() {
+ return Boolean(firebaseConfigState.apiKey && firebaseConfigState.projectId);
+}
+
+function cloudAuthReady() {
+ return Boolean(firebaseAuthState.idToken && firebaseAuthState.localId);
+}
+
+function cloudPayload() {
+ return {
+ bookmarks,
+ folders,
+ openFolders,
+ updatedAt: lastUpdatedAt
+ };
+}
+
+async function authWithFirebase(mode) {
+ const email = document.getElementById("authEmail")?.value.trim();
+ const password = document.getElementById("authPassword")?.value.trim();
+
+ if (!cloudConfigReady()) return setCloudStatus("Add Firebase API Key + Project ID first.", true);
+ if (!email || !password) return setCloudStatus("Enter email and password.", true);
+
+ try {
+ const endpoint = `https://identitytoolkit.googleapis.com/v1/accounts:${mode}?key=${firebaseConfigState.apiKey}`;
+ const res = await fetch(endpoint, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password, returnSecureToken: true })
+ });
+
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error?.message || "Authentication failed");
+
+ firebaseAuthState = { idToken: data.idToken, localId: data.localId, email: data.email || email };
+ persistCloudState();
+ updateCloudStatusFromState();
+ await restoreFromCloud(false);
+ } catch (err) {
+ setCloudStatus(`Auth failed: ${err.message}`, true);
+ }
+}
+
+function signOutFirebase() {
+ firebaseAuthState = { idToken: "", localId: "", email: "" };
+ persistCloudState();
+ updateCloudStatusFromState();
+}
+
+function cloudDocumentUrl() {
+ return `https://firestore.googleapis.com/v1/projects/${firebaseConfigState.projectId}/databases/(default)/documents/bookmarkfolderUsers/${firebaseAuthState.localId}`;
+}
+
+async function backupToCloud(showMessage = true) {
+ if (!cloudConfigReady() || !cloudAuthReady()) {
+ if (showMessage) setCloudStatus("Cloud backup skipped: sign in first.", true);
+ return;
+ }
+
+ try {
+ const payload = cloudPayload();
+ const body = {
+ fields: {
+ data: { stringValue: JSON.stringify(payload) },
+ updatedAt: { integerValue: String(payload.updatedAt) }
+ }
+ };
+
+ const res = await fetch(cloudDocumentUrl(), {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${firebaseAuthState.idToken}`
+ },
+ body: JSON.stringify(body)
+ });
+
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error?.message || "Backup failed");
+
+ if (showMessage) setCloudStatus("Backup complete.");
+ } catch (err) {
+ setCloudStatus(`Backup failed: ${err.message}`, true);
+ }
+}
+
+async function restoreFromCloud(showMessage = true) {
+ if (!cloudConfigReady() || !cloudAuthReady()) {
+ if (showMessage) setCloudStatus("Cloud restore skipped: sign in first.", true);
+ return;
+ }
+
+ try {
+ const res = await fetch(cloudDocumentUrl(), {
+ headers: { Authorization: `Bearer ${firebaseAuthState.idToken}` }
+ });
+
+ if (res.status === 404) {
+ if (showMessage) setCloudStatus("No cloud backup found yet.");
+ return;
+ }
+
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error?.message || "Restore failed");
+
+ const encoded = data.fields?.data?.stringValue;
+ if (!encoded) {
+ if (showMessage) setCloudStatus("Cloud document is empty.", true);
+ return;
+ }
+
+ const cloud = JSON.parse(encoded);
+ const localUpdatedAt = Number(lastUpdatedAt || 0);
+ const cloudUpdatedAt = Number(cloud.updatedAt || 0);
+
+ if (cloudUpdatedAt < localUpdatedAt) {
+ if (showMessage) setCloudStatus("Local data is newer than cloud.");
+ return;
+ }
+
+ bookmarks = Array.isArray(cloud.bookmarks) ? cloud.bookmarks : [];
+ folders = Array.isArray(cloud.folders) && cloud.folders.length ? cloud.folders : ["Default"];
+ openFolders = cloud.openFolders && typeof cloud.openFolders === "object" ? cloud.openFolders : {};
+
+ folders.forEach(f => {
+ if (openFolders[f] === undefined) openFolders[f] = f === "Default";
+ });
+
+ saveAll();
+ renderFolders();
+ populateFolderSelect();
+
+ if (showMessage) setCloudStatus("Restore complete.");
+ } catch (err) {
+ setCloudStatus(`Restore failed: ${err.message}`, true);
+ }
+}
diff --git a/style.css b/style.css
index f6224e3..cc4ebd2 100644
--- a/style.css
+++ b/style.css
@@ -723,3 +723,47 @@ body::before {
height: 60px;
}
}
+
+/* ===================================
+ FIREBASE CLOUD PANEL
+ =================================== */
+.cloud-panel {
+ margin-top: 16px;
+ background: rgba(255, 255, 255, 0.04);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 12px;
+ padding: 12px;
+}
+
+.cloud-panel h3 {
+ margin-bottom: 10px;
+ font-size: 14px;
+ color: #bfdbfe;
+}
+
+.cloud-panel input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 8px;
+ border-radius: 10px;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ background: rgba(15, 23, 42, 0.6);
+ color: #e2e8f0;
+}
+
+.cloud-actions {
+ display: grid;
+ gap: 8px;
+ grid-template-columns: 1fr;
+}
+
+.cloud-actions .small-btn {
+ margin-bottom: 0;
+}
+
+.cloud-status {
+ margin-top: 10px;
+ font-size: 12px;
+ color: #93c5fd;
+ line-height: 1.4;
+}