diff --git a/dist/birb.js b/dist/birb.js index f857d70..fcd8f75 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -839,6 +839,8 @@ } } + const SAVE_KEY = "birbSaveData"; + /** * @typedef {import('./application.js').BirbSaveData} BirbSaveData */ @@ -858,9 +860,9 @@ /** * @abstract - * @returns {BirbSaveData|{}} + * @returns {Promise} */ - getSaveData() { + async getSaveData() { throw new Error("Method not implemented"); } @@ -868,7 +870,7 @@ * @abstract * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { throw new Error("Method not implemented"); } @@ -882,45 +884,62 @@ class LocalContext extends Context { + /** + * @override + * @returns {boolean} + */ isContextActive() { return window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost" || window.location.hostname.startsWith("192.168."); } - getSaveData() { + /** + * @override + * @returns {Promise} + */ + async getSaveData() { log("Loading save data from localStorage"); - return JSON.parse(localStorage.getItem("birbSaveData") ?? "{}"); + return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}"); } /** * @override * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { log("Saving data to localStorage"); - localStorage.setItem("birbSaveData", JSON.stringify(saveData)); + localStorage.setItem(SAVE_KEY, JSON.stringify(saveData)); } + /** @override */ resetSaveData() { log("Resetting save data in localStorage"); - localStorage.removeItem("birbSaveData"); + localStorage.removeItem(SAVE_KEY); } } class UserScriptContext extends Context { + /** + * @override + * @returns {boolean} + */ isContextActive() { // @ts-expect-error return typeof GM_getValue === "function"; } - getSaveData() { + /** + * @override + * @returns {Promise} + */ + async getSaveData() { log("Loading save data from UserScript storage"); /** @type {BirbSaveData|{}} */ let saveData = {}; // @ts-expect-error - saveData = GM_getValue("birbSaveData", {}) ?? {}; + saveData = GM_getValue(SAVE_KEY, {}) ?? {}; return saveData; } @@ -928,22 +947,75 @@ * @override * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { log("Saving data to UserScript storage"); // @ts-expect-error - GM_setValue("birbSaveData", saveData); + GM_setValue(SAVE_KEY, saveData); } + /** @override */ resetSaveData() { log("Resetting save data in UserScript storage"); // @ts-expect-error - GM_deleteValue("birbSaveData"); + GM_deleteValue(SAVE_KEY); + } + } + + class BrowserExtensionContext extends Context { + + /** + * @override + * @returns {boolean} + */ + isContextActive() { + // @ts-expect-error + return typeof chrome !== "undefined"; + } + + /** + * @override + * @returns {Promise} + */ + async getSaveData() { + log("Loading save data from browser extension storage"); + return new Promise((resolve) => { + // @ts-expect-error + chrome.storage.sync.get([SAVE_KEY], (result) => { + resolve(result[SAVE_KEY] ?? {}); + }); + }); + } + + /** + * @override + * @param {BirbSaveData} saveData + */ + async putSaveData(saveData) { + log("Saving data to browser extension storage"); + // @ts-expect-error + chrome.storage.sync.set({ [SAVE_KEY]: saveData }, function () { + // @ts-expect-error + if (chrome.runtime.lastError) { + // @ts-expect-error + console.error(chrome.runtime.lastError); + } else { + console.log("Settings saved successfully"); + } + }); + } + + /** @override */ + resetSaveData() { + log("Resetting save data in browser extension storage"); + // @ts-expect-error + chrome.storage.sync.clear(); } } const CONTEXTS = [ - new LocalContext(), new UserScriptContext(), + new BrowserExtensionContext(), + new LocalContext() ]; function getContext() { @@ -1795,7 +1867,7 @@ insertModal(`${birdBirb()} Mode`, message); }), new Separator(), - new MenuItem("2025.11.2.0", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.2.0"); }, false), + new MenuItem("2025.11.2.44", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.2.44"); }, false), ]; const styleElement = document.createElement("style"); @@ -1835,13 +1907,13 @@ /** @type {StickyNote[]} */ let stickyNotes = []; - function load() { - /** @type {Record} */ - let saveData = getContext().getSaveData(); + async function load() { + /** @type {BirbSaveData|Object} */ + let saveData = await getContext().getSaveData(); debug("Loaded data: " + JSON.stringify(saveData)); - if (!saveData.settings) { + if (!('settings' in saveData)) { log("No user settings found in save data, starting fresh"); } @@ -1937,8 +2009,10 @@ error("Failed to load font: " + e); } - load(); + load().then(onLoad); + } + function onLoad() { styleElement.textContent = STYLESHEET; document.head.appendChild(styleElement); diff --git a/dist/birb.user.js b/dist/birb.user.js index b2a311e..33ae7fb 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025.11.2.0 +// @version 2025.11.2.44 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js @@ -853,6 +853,8 @@ } } + const SAVE_KEY = "birbSaveData"; + /** * @typedef {import('./application.js').BirbSaveData} BirbSaveData */ @@ -872,9 +874,9 @@ /** * @abstract - * @returns {BirbSaveData|{}} + * @returns {Promise} */ - getSaveData() { + async getSaveData() { throw new Error("Method not implemented"); } @@ -882,7 +884,7 @@ * @abstract * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { throw new Error("Method not implemented"); } @@ -896,45 +898,62 @@ class LocalContext extends Context { + /** + * @override + * @returns {boolean} + */ isContextActive() { return window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost" || window.location.hostname.startsWith("192.168."); } - getSaveData() { + /** + * @override + * @returns {Promise} + */ + async getSaveData() { log("Loading save data from localStorage"); - return JSON.parse(localStorage.getItem("birbSaveData") ?? "{}"); + return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}"); } /** * @override * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { log("Saving data to localStorage"); - localStorage.setItem("birbSaveData", JSON.stringify(saveData)); + localStorage.setItem(SAVE_KEY, JSON.stringify(saveData)); } + /** @override */ resetSaveData() { log("Resetting save data in localStorage"); - localStorage.removeItem("birbSaveData"); + localStorage.removeItem(SAVE_KEY); } } class UserScriptContext extends Context { + /** + * @override + * @returns {boolean} + */ isContextActive() { // @ts-expect-error return typeof GM_getValue === "function"; } - getSaveData() { + /** + * @override + * @returns {Promise} + */ + async getSaveData() { log("Loading save data from UserScript storage"); /** @type {BirbSaveData|{}} */ let saveData = {}; // @ts-expect-error - saveData = GM_getValue("birbSaveData", {}) ?? {}; + saveData = GM_getValue(SAVE_KEY, {}) ?? {}; return saveData; } @@ -942,22 +961,75 @@ * @override * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { log("Saving data to UserScript storage"); // @ts-expect-error - GM_setValue("birbSaveData", saveData); + GM_setValue(SAVE_KEY, saveData); } + /** @override */ resetSaveData() { log("Resetting save data in UserScript storage"); // @ts-expect-error - GM_deleteValue("birbSaveData"); + GM_deleteValue(SAVE_KEY); + } + } + + class BrowserExtensionContext extends Context { + + /** + * @override + * @returns {boolean} + */ + isContextActive() { + // @ts-expect-error + return typeof chrome !== "undefined"; + } + + /** + * @override + * @returns {Promise} + */ + async getSaveData() { + log("Loading save data from browser extension storage"); + return new Promise((resolve) => { + // @ts-expect-error + chrome.storage.sync.get([SAVE_KEY], (result) => { + resolve(result[SAVE_KEY] ?? {}); + }); + }); + } + + /** + * @override + * @param {BirbSaveData} saveData + */ + async putSaveData(saveData) { + log("Saving data to browser extension storage"); + // @ts-expect-error + chrome.storage.sync.set({ [SAVE_KEY]: saveData }, function () { + // @ts-expect-error + if (chrome.runtime.lastError) { + // @ts-expect-error + console.error(chrome.runtime.lastError); + } else { + console.log("Settings saved successfully"); + } + }); + } + + /** @override */ + resetSaveData() { + log("Resetting save data in browser extension storage"); + // @ts-expect-error + chrome.storage.sync.clear(); } } const CONTEXTS = [ - new LocalContext(), new UserScriptContext(), + new BrowserExtensionContext(), + new LocalContext() ]; function getContext() { @@ -1809,7 +1881,7 @@ insertModal(`${birdBirb()} Mode`, message); }), new Separator(), - new MenuItem("2025.11.2.0", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.2.0"); }, false), + new MenuItem("2025.11.2.44", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.2.44"); }, false), ]; const styleElement = document.createElement("style"); @@ -1849,13 +1921,13 @@ /** @type {StickyNote[]} */ let stickyNotes = []; - function load() { - /** @type {Record} */ - let saveData = getContext().getSaveData(); + async function load() { + /** @type {BirbSaveData|Object} */ + let saveData = await getContext().getSaveData(); debug("Loaded data: " + JSON.stringify(saveData)); - if (!saveData.settings) { + if (!('settings' in saveData)) { log("No user settings found in save data, starting fresh"); } @@ -1951,8 +2023,10 @@ error("Failed to load font: " + e); } - load(); + load().then(onLoad); + } + function onLoad() { styleElement.textContent = STYLESHEET; document.head.appendChild(styleElement); diff --git a/manifest.json b/manifest.json index 8883dc8..cd121bb 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Pocket Bird", "description": "It's a bird, in your browser. What more could you want?", - "version": "2025.11.2.0", + "version": "2025.11.2.44", "homepage_url": "https://idreesinc.com", "icons": { "48": "images/icons/transparent/48x48x1.png", diff --git a/src/application.js b/src/application.js index 1469338..5ab3814 100644 --- a/src/application.js +++ b/src/application.js @@ -271,13 +271,13 @@ Promise.all([ /** @type {StickyNote[]} */ let stickyNotes = []; - function load() { - /** @type {Record} */ - let saveData = getContext().getSaveData(); + async function load() { + /** @type {BirbSaveData|Object} */ + let saveData = await getContext().getSaveData(); debug("Loaded data: " + JSON.stringify(saveData)); - if (!saveData.settings) { + if (!('settings' in saveData)) { log("No user settings found in save data, starting fresh"); } @@ -373,8 +373,10 @@ Promise.all([ error("Failed to load font: " + e); } - load(); + load().then(onLoad); + } + function onLoad() { styleElement.textContent = STYLESHEET; document.head.appendChild(styleElement); diff --git a/src/context.js b/src/context.js index 8ac3299..25b8ecf 100644 --- a/src/context.js +++ b/src/context.js @@ -1,6 +1,8 @@ import { debug, log, error } from "./shared.js"; +const SAVE_KEY = "birbSaveData"; + /** * @typedef {import('./application.js').BirbSaveData} BirbSaveData */ @@ -20,9 +22,9 @@ export class Context { /** * @abstract - * @returns {BirbSaveData|{}} + * @returns {Promise} */ - getSaveData() { + async getSaveData() { throw new Error("Method not implemented"); } @@ -30,7 +32,7 @@ export class Context { * @abstract * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { throw new Error("Method not implemented"); } @@ -44,45 +46,62 @@ export class Context { export class LocalContext extends Context { + /** + * @override + * @returns {boolean} + */ isContextActive() { return window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost" || window.location.hostname.startsWith("192.168."); } - getSaveData() { + /** + * @override + * @returns {Promise} + */ + async getSaveData() { log("Loading save data from localStorage"); - return JSON.parse(localStorage.getItem("birbSaveData") ?? "{}"); + return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}"); } /** * @override * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { log("Saving data to localStorage"); - localStorage.setItem("birbSaveData", JSON.stringify(saveData)); + localStorage.setItem(SAVE_KEY, JSON.stringify(saveData)); } + /** @override */ resetSaveData() { log("Resetting save data in localStorage"); - localStorage.removeItem("birbSaveData"); + localStorage.removeItem(SAVE_KEY); } } export class UserScriptContext extends Context { + /** + * @override + * @returns {boolean} + */ isContextActive() { // @ts-expect-error return typeof GM_getValue === "function"; } - getSaveData() { + /** + * @override + * @returns {Promise} + */ + async getSaveData() { log("Loading save data from UserScript storage"); /** @type {BirbSaveData|{}} */ let saveData = {}; // @ts-expect-error - saveData = GM_getValue("birbSaveData", {}) ?? {}; + saveData = GM_getValue(SAVE_KEY, {}) ?? {}; return saveData; } @@ -90,22 +109,75 @@ export class UserScriptContext extends Context { * @override * @param {BirbSaveData} saveData */ - putSaveData(saveData) { + async putSaveData(saveData) { log("Saving data to UserScript storage"); // @ts-expect-error - GM_setValue("birbSaveData", saveData); + GM_setValue(SAVE_KEY, saveData); } + /** @override */ resetSaveData() { log("Resetting save data in UserScript storage"); // @ts-expect-error - GM_deleteValue("birbSaveData"); + GM_deleteValue(SAVE_KEY); + } +} + +class BrowserExtensionContext extends Context { + + /** + * @override + * @returns {boolean} + */ + isContextActive() { + // @ts-expect-error + return typeof chrome !== "undefined"; + } + + /** + * @override + * @returns {Promise} + */ + async getSaveData() { + log("Loading save data from browser extension storage"); + return new Promise((resolve) => { + // @ts-expect-error + chrome.storage.sync.get([SAVE_KEY], (result) => { + resolve(result[SAVE_KEY] ?? {}); + }); + }); + } + + /** + * @override + * @param {BirbSaveData} saveData + */ + async putSaveData(saveData) { + log("Saving data to browser extension storage"); + // @ts-expect-error + chrome.storage.sync.set({ [SAVE_KEY]: saveData }, function () { + // @ts-expect-error + if (chrome.runtime.lastError) { + // @ts-expect-error + console.error(chrome.runtime.lastError); + } else { + console.log("Settings saved successfully"); + } + }); + } + + /** @override */ + resetSaveData() { + log("Resetting save data in browser extension storage"); + // @ts-expect-error + chrome.storage.sync.clear(); } } const CONTEXTS = [ - new LocalContext(), new UserScriptContext(), + new BrowserExtensionContext(), + new LocalContext() ]; export function getContext() {