Add browser extension context

This commit is contained in:
Idrees Hassan
2025-11-02 14:45:18 -05:00
parent 18749acff6
commit 8a6b1a584e
5 changed files with 283 additions and 61 deletions

114
dist/birb.js vendored
View File

@@ -839,6 +839,8 @@
} }
} }
const SAVE_KEY = "birbSaveData";
/** /**
* @typedef {import('./application.js').BirbSaveData} BirbSaveData * @typedef {import('./application.js').BirbSaveData} BirbSaveData
*/ */
@@ -858,9 +860,9 @@
/** /**
* @abstract * @abstract
* @returns {BirbSaveData|{}} * @returns {Promise<BirbSaveData|{}>}
*/ */
getSaveData() { async getSaveData() {
throw new Error("Method not implemented"); throw new Error("Method not implemented");
} }
@@ -868,7 +870,7 @@
* @abstract * @abstract
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
throw new Error("Method not implemented"); throw new Error("Method not implemented");
} }
@@ -882,45 +884,62 @@
class LocalContext extends Context { class LocalContext extends Context {
/**
* @override
* @returns {boolean}
*/
isContextActive() { isContextActive() {
return window.location.hostname === "127.0.0.1" return window.location.hostname === "127.0.0.1"
|| window.location.hostname === "localhost" || window.location.hostname === "localhost"
|| window.location.hostname.startsWith("192.168."); || window.location.hostname.startsWith("192.168.");
} }
getSaveData() { /**
* @override
* @returns {Promise<BirbSaveData|{}>}
*/
async getSaveData() {
log("Loading save data from localStorage"); log("Loading save data from localStorage");
return JSON.parse(localStorage.getItem("birbSaveData") ?? "{}"); return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}");
} }
/** /**
* @override * @override
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
log("Saving data to localStorage"); log("Saving data to localStorage");
localStorage.setItem("birbSaveData", JSON.stringify(saveData)); localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
} }
/** @override */
resetSaveData() { resetSaveData() {
log("Resetting save data in localStorage"); log("Resetting save data in localStorage");
localStorage.removeItem("birbSaveData"); localStorage.removeItem(SAVE_KEY);
} }
} }
class UserScriptContext extends Context { class UserScriptContext extends Context {
/**
* @override
* @returns {boolean}
*/
isContextActive() { isContextActive() {
// @ts-expect-error // @ts-expect-error
return typeof GM_getValue === "function"; return typeof GM_getValue === "function";
} }
getSaveData() { /**
* @override
* @returns {Promise<BirbSaveData|{}>}
*/
async getSaveData() {
log("Loading save data from UserScript storage"); log("Loading save data from UserScript storage");
/** @type {BirbSaveData|{}} */ /** @type {BirbSaveData|{}} */
let saveData = {}; let saveData = {};
// @ts-expect-error // @ts-expect-error
saveData = GM_getValue("birbSaveData", {}) ?? {}; saveData = GM_getValue(SAVE_KEY, {}) ?? {};
return saveData; return saveData;
} }
@@ -928,22 +947,75 @@
* @override * @override
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
log("Saving data to UserScript storage"); log("Saving data to UserScript storage");
// @ts-expect-error // @ts-expect-error
GM_setValue("birbSaveData", saveData); GM_setValue(SAVE_KEY, saveData);
} }
/** @override */
resetSaveData() { resetSaveData() {
log("Resetting save data in UserScript storage"); log("Resetting save data in UserScript storage");
// @ts-expect-error // @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<BirbSaveData|{}>}
*/
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 = [ const CONTEXTS = [
new LocalContext(),
new UserScriptContext(), new UserScriptContext(),
new BrowserExtensionContext(),
new LocalContext()
]; ];
function getContext() { function getContext() {
@@ -1795,7 +1867,7 @@
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), 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"); const styleElement = document.createElement("style");
@@ -1835,13 +1907,13 @@
/** @type {StickyNote[]} */ /** @type {StickyNote[]} */
let stickyNotes = []; let stickyNotes = [];
function load() { async function load() {
/** @type {Record<string, any>} */ /** @type {BirbSaveData|Object} */
let saveData = getContext().getSaveData(); let saveData = await getContext().getSaveData();
debug("Loaded data: " + JSON.stringify(saveData)); debug("Loaded data: " + JSON.stringify(saveData));
if (!saveData.settings) { if (!('settings' in saveData)) {
log("No user settings found in save data, starting fresh"); log("No user settings found in save data, starting fresh");
} }
@@ -1937,8 +2009,10 @@
error("Failed to load font: " + e); error("Failed to load font: " + e);
} }
load(); load().then(onLoad);
}
function onLoad() {
styleElement.textContent = STYLESHEET; styleElement.textContent = STYLESHEET;
document.head.appendChild(styleElement); document.head.appendChild(styleElement);

116
dist/birb.user.js vendored
View File

@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Pocket Bird // @name Pocket Bird
// @namespace https://idreesinc.com // @namespace https://idreesinc.com
// @version 2025.11.2.0 // @version 2025.11.2.44
// @description birb // @description birb
// @author Idrees // @author Idrees
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js // @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 * @typedef {import('./application.js').BirbSaveData} BirbSaveData
*/ */
@@ -872,9 +874,9 @@
/** /**
* @abstract * @abstract
* @returns {BirbSaveData|{}} * @returns {Promise<BirbSaveData|{}>}
*/ */
getSaveData() { async getSaveData() {
throw new Error("Method not implemented"); throw new Error("Method not implemented");
} }
@@ -882,7 +884,7 @@
* @abstract * @abstract
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
throw new Error("Method not implemented"); throw new Error("Method not implemented");
} }
@@ -896,45 +898,62 @@
class LocalContext extends Context { class LocalContext extends Context {
/**
* @override
* @returns {boolean}
*/
isContextActive() { isContextActive() {
return window.location.hostname === "127.0.0.1" return window.location.hostname === "127.0.0.1"
|| window.location.hostname === "localhost" || window.location.hostname === "localhost"
|| window.location.hostname.startsWith("192.168."); || window.location.hostname.startsWith("192.168.");
} }
getSaveData() { /**
* @override
* @returns {Promise<BirbSaveData|{}>}
*/
async getSaveData() {
log("Loading save data from localStorage"); log("Loading save data from localStorage");
return JSON.parse(localStorage.getItem("birbSaveData") ?? "{}"); return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}");
} }
/** /**
* @override * @override
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
log("Saving data to localStorage"); log("Saving data to localStorage");
localStorage.setItem("birbSaveData", JSON.stringify(saveData)); localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
} }
/** @override */
resetSaveData() { resetSaveData() {
log("Resetting save data in localStorage"); log("Resetting save data in localStorage");
localStorage.removeItem("birbSaveData"); localStorage.removeItem(SAVE_KEY);
} }
} }
class UserScriptContext extends Context { class UserScriptContext extends Context {
/**
* @override
* @returns {boolean}
*/
isContextActive() { isContextActive() {
// @ts-expect-error // @ts-expect-error
return typeof GM_getValue === "function"; return typeof GM_getValue === "function";
} }
getSaveData() { /**
* @override
* @returns {Promise<BirbSaveData|{}>}
*/
async getSaveData() {
log("Loading save data from UserScript storage"); log("Loading save data from UserScript storage");
/** @type {BirbSaveData|{}} */ /** @type {BirbSaveData|{}} */
let saveData = {}; let saveData = {};
// @ts-expect-error // @ts-expect-error
saveData = GM_getValue("birbSaveData", {}) ?? {}; saveData = GM_getValue(SAVE_KEY, {}) ?? {};
return saveData; return saveData;
} }
@@ -942,22 +961,75 @@
* @override * @override
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
log("Saving data to UserScript storage"); log("Saving data to UserScript storage");
// @ts-expect-error // @ts-expect-error
GM_setValue("birbSaveData", saveData); GM_setValue(SAVE_KEY, saveData);
} }
/** @override */
resetSaveData() { resetSaveData() {
log("Resetting save data in UserScript storage"); log("Resetting save data in UserScript storage");
// @ts-expect-error // @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<BirbSaveData|{}>}
*/
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 = [ const CONTEXTS = [
new LocalContext(),
new UserScriptContext(), new UserScriptContext(),
new BrowserExtensionContext(),
new LocalContext()
]; ];
function getContext() { function getContext() {
@@ -1809,7 +1881,7 @@
insertModal(`${birdBirb()} Mode`, message); insertModal(`${birdBirb()} Mode`, message);
}), }),
new Separator(), 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"); const styleElement = document.createElement("style");
@@ -1849,13 +1921,13 @@
/** @type {StickyNote[]} */ /** @type {StickyNote[]} */
let stickyNotes = []; let stickyNotes = [];
function load() { async function load() {
/** @type {Record<string, any>} */ /** @type {BirbSaveData|Object} */
let saveData = getContext().getSaveData(); let saveData = await getContext().getSaveData();
debug("Loaded data: " + JSON.stringify(saveData)); debug("Loaded data: " + JSON.stringify(saveData));
if (!saveData.settings) { if (!('settings' in saveData)) {
log("No user settings found in save data, starting fresh"); log("No user settings found in save data, starting fresh");
} }
@@ -1951,8 +2023,10 @@
error("Failed to load font: " + e); error("Failed to load font: " + e);
} }
load(); load().then(onLoad);
}
function onLoad() {
styleElement.textContent = STYLESHEET; styleElement.textContent = STYLESHEET;
document.head.appendChild(styleElement); document.head.appendChild(styleElement);

View File

@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Pocket Bird", "name": "Pocket Bird",
"description": "It's a bird, in your browser. What more could you want?", "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", "homepage_url": "https://idreesinc.com",
"icons": { "icons": {
"48": "images/icons/transparent/48x48x1.png", "48": "images/icons/transparent/48x48x1.png",

View File

@@ -271,13 +271,13 @@ Promise.all([
/** @type {StickyNote[]} */ /** @type {StickyNote[]} */
let stickyNotes = []; let stickyNotes = [];
function load() { async function load() {
/** @type {Record<string, any>} */ /** @type {BirbSaveData|Object} */
let saveData = getContext().getSaveData(); let saveData = await getContext().getSaveData();
debug("Loaded data: " + JSON.stringify(saveData)); debug("Loaded data: " + JSON.stringify(saveData));
if (!saveData.settings) { if (!('settings' in saveData)) {
log("No user settings found in save data, starting fresh"); log("No user settings found in save data, starting fresh");
} }
@@ -373,8 +373,10 @@ Promise.all([
error("Failed to load font: " + e); error("Failed to load font: " + e);
} }
load(); load().then(onLoad);
}
function onLoad() {
styleElement.textContent = STYLESHEET; styleElement.textContent = STYLESHEET;
document.head.appendChild(styleElement); document.head.appendChild(styleElement);

View File

@@ -1,6 +1,8 @@
import { debug, log, error } from "./shared.js"; import { debug, log, error } from "./shared.js";
const SAVE_KEY = "birbSaveData";
/** /**
* @typedef {import('./application.js').BirbSaveData} BirbSaveData * @typedef {import('./application.js').BirbSaveData} BirbSaveData
*/ */
@@ -20,9 +22,9 @@ export class Context {
/** /**
* @abstract * @abstract
* @returns {BirbSaveData|{}} * @returns {Promise<BirbSaveData|{}>}
*/ */
getSaveData() { async getSaveData() {
throw new Error("Method not implemented"); throw new Error("Method not implemented");
} }
@@ -30,7 +32,7 @@ export class Context {
* @abstract * @abstract
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
throw new Error("Method not implemented"); throw new Error("Method not implemented");
} }
@@ -44,45 +46,62 @@ export class Context {
export class LocalContext extends Context { export class LocalContext extends Context {
/**
* @override
* @returns {boolean}
*/
isContextActive() { isContextActive() {
return window.location.hostname === "127.0.0.1" return window.location.hostname === "127.0.0.1"
|| window.location.hostname === "localhost" || window.location.hostname === "localhost"
|| window.location.hostname.startsWith("192.168."); || window.location.hostname.startsWith("192.168.");
} }
getSaveData() { /**
* @override
* @returns {Promise<BirbSaveData|{}>}
*/
async getSaveData() {
log("Loading save data from localStorage"); log("Loading save data from localStorage");
return JSON.parse(localStorage.getItem("birbSaveData") ?? "{}"); return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}");
} }
/** /**
* @override * @override
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
log("Saving data to localStorage"); log("Saving data to localStorage");
localStorage.setItem("birbSaveData", JSON.stringify(saveData)); localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
} }
/** @override */
resetSaveData() { resetSaveData() {
log("Resetting save data in localStorage"); log("Resetting save data in localStorage");
localStorage.removeItem("birbSaveData"); localStorage.removeItem(SAVE_KEY);
} }
} }
export class UserScriptContext extends Context { export class UserScriptContext extends Context {
/**
* @override
* @returns {boolean}
*/
isContextActive() { isContextActive() {
// @ts-expect-error // @ts-expect-error
return typeof GM_getValue === "function"; return typeof GM_getValue === "function";
} }
getSaveData() { /**
* @override
* @returns {Promise<BirbSaveData|{}>}
*/
async getSaveData() {
log("Loading save data from UserScript storage"); log("Loading save data from UserScript storage");
/** @type {BirbSaveData|{}} */ /** @type {BirbSaveData|{}} */
let saveData = {}; let saveData = {};
// @ts-expect-error // @ts-expect-error
saveData = GM_getValue("birbSaveData", {}) ?? {}; saveData = GM_getValue(SAVE_KEY, {}) ?? {};
return saveData; return saveData;
} }
@@ -90,22 +109,75 @@ export class UserScriptContext extends Context {
* @override * @override
* @param {BirbSaveData} saveData * @param {BirbSaveData} saveData
*/ */
putSaveData(saveData) { async putSaveData(saveData) {
log("Saving data to UserScript storage"); log("Saving data to UserScript storage");
// @ts-expect-error // @ts-expect-error
GM_setValue("birbSaveData", saveData); GM_setValue(SAVE_KEY, saveData);
} }
/** @override */
resetSaveData() { resetSaveData() {
log("Resetting save data in UserScript storage"); log("Resetting save data in UserScript storage");
// @ts-expect-error // @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<BirbSaveData|{}>}
*/
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 = [ const CONTEXTS = [
new LocalContext(),
new UserScriptContext(), new UserScriptContext(),
new BrowserExtensionContext(),
new LocalContext()
]; ];
export function getContext() { export function getContext() {