Skip to content

Commit

Permalink
v0.1.5 - Web Extension Note Editing + Sync
Browse files Browse the repository at this point in the history
  • Loading branch information
0xGingi committed Dec 20, 2024
1 parent e5c451b commit 72dde67
Show file tree
Hide file tree
Showing 15 changed files with 925 additions and 90 deletions.

Large diffs are not rendered by default.

165 changes: 162 additions & 3 deletions browser-extension/background.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// CryptoService implementation
class CryptoService {
constructor(key) {
this.key = key;
Expand Down Expand Up @@ -54,6 +53,9 @@ class CryptoService {
}

let cryptoService = null;
let webappTabCheckInterval = null;
let lastSyncTime = 0;
const SYNC_COOLDOWN = 2000;

async function encryptAndStoreNotes(notes) {
try {
Expand All @@ -76,16 +78,173 @@ async function encryptAndStoreNotes(notes) {
}
}

async function checkForPendingSync() {
try {
const now = Date.now();
if (now - lastSyncTime < SYNC_COOLDOWN) {
return;
}

const result = await chrome.storage.local.get(['encrypted_notes', 'seed_phrase', 'lastUpdated']);
if (!result.encrypted_notes) return;

if (result.lastUpdated && now - result.lastUpdated < SYNC_COOLDOWN) {
return;
}

if (!cryptoService && result.seed_phrase) {
cryptoService = await CryptoService.new(result.seed_phrase);
}
if (!cryptoService) return;

let notes = await cryptoService.decrypt(result.encrypted_notes);
const pendingNotes = notes.filter(note => note.pending_sync);

if (pendingNotes.length > 0 || now - lastSyncTime > 10000) {
const tabs = await chrome.tabs.query({ url: 'https://notes.toolworks.dev/*' });
if (tabs.length > 0) {
const tab = tabs[0];

const success = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (extensionNotes, lastSync) => {
return new Promise((resolve) => {
const checkAndSync = () => {
try {
const notesJson = localStorage.getItem('notes');
if (!notesJson) {
setTimeout(checkAndSync, 500);
return;
}

let webappNotes = JSON.parse(notesJson);

const lastWebappUpdate = Math.max(...webappNotes.map(n => n.updated_at));
if (lastWebappUpdate > lastSync && Date.now() - lastWebappUpdate < 2000) {
resolve({
success: true,
notes: extensionNotes,
hasChanges: false
});
return;
}

const webappNotesMap = new Map(
webappNotes.map(note => [note.id, note])
);
const extensionNotesMap = new Map(
extensionNotes.map(note => [note.id, note])
);

let hasChanges = false;
const allNoteIds = new Set([
...webappNotesMap.keys(),
...extensionNotesMap.keys()
]);

const finalNotesMap = new Map();

allNoteIds.forEach(id => {
const webappNote = webappNotesMap.get(id);
const extensionNote = extensionNotesMap.get(id);

if (!webappNote) {
hasChanges = true;
} else if (!extensionNote) {
hasChanges = true;
finalNotesMap.set(id, webappNote);
} else if (extensionNote.pending_sync) {
hasChanges = true;
finalNotesMap.set(id, {
...extensionNote,
pending_sync: false
});
} else if (webappNote.updated_at > extensionNote.updated_at) {
hasChanges = true;
finalNotesMap.set(id, webappNote);
} else {
finalNotesMap.set(id, extensionNote);
}
});

if (!hasChanges) {
resolve({
success: true,
notes: extensionNotes,
hasChanges: false
});
return;
}

const mergedNotes = Array.from(finalNotesMap.values());

localStorage.setItem('notes', JSON.stringify(mergedNotes));

window.dispatchEvent(new StorageEvent('storage', {
key: 'notes',
oldValue: notesJson,
newValue: JSON.stringify(mergedNotes)
}));

resolve({
success: true,
notes: mergedNotes,
hasChanges: true
});
} catch (error) {
console.error('Sync attempt failed:', error);
setTimeout(checkAndSync, 500);
}
};

checkAndSync();
});
},
args: [notes, lastSyncTime]
});

if (success && success[0]?.result?.success) {
if (success[0].result.hasChanges) {
notes = success[0].result.notes;
await encryptAndStoreNotes(notes);
await chrome.tabs.reload(tab.id);
}
lastSyncTime = Date.now();
}
}
}
} catch (error) {
console.error('Error checking for pending sync:', error);
}
}

function startPeriodicCheck() {
if (webappTabCheckInterval) {
clearInterval(webappTabCheckInterval);
}
webappTabCheckInterval = setInterval(checkForPendingSync, 2000);
}

startPeriodicCheck();

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'NOTES_UPDATED' && message.notes) {
lastSyncTime = Date.now();
encryptAndStoreNotes(message.notes);
}
return true;
});

// For Chrome compatibility, keep the service worker alive
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete' &&
tab.url &&
tab.url.startsWith('https://notes.toolworks.dev')) {
checkForPendingSync();
}
});

chrome.runtime.onConnect.addListener(function(port) {
port.onDisconnect.addListener(function() {
// Reconnect or perform cleanup if needed
startPeriodicCheck();
});
});
99 changes: 99 additions & 0 deletions browser-extension/content.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
function checkAndNotifyWebappReady() {
console.log('Checking webapp status...');
const notes = localStorage.getItem('notes');
if (notes !== null) {
console.log('Webapp ready, sending message to extension...');
const sendReadyMessage = (attempts = 0) => {
chrome.runtime.sendMessage({
type: 'WEBAPP_READY',
notes: JSON.parse(notes)
}, response => {
if (chrome.runtime.lastError) {
console.log('Failed to send WEBAPP_READY message:', chrome.runtime.lastError);
if (attempts < 3) {
setTimeout(() => sendReadyMessage(attempts + 1), 500);
}
} else {
console.log('WEBAPP_READY message sent successfully');
}
});
};
sendReadyMessage();
return true;
}
console.log('Webapp not ready yet, notes:', !!notes);
return false;
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_SYNC_SETTINGS') {
const script = document.createElement('script');
Expand Down Expand Up @@ -34,6 +61,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
window.dispatchEvent(event);
console.debug('Dispatched open-sync-settings event');
return true;
} else if (message.type === 'UPDATE_NOTES') {
console.log('Updating webapp notes:', message.notes);
localStorage.setItem('notes', JSON.stringify(message.notes));
window.dispatchEvent(new StorageEvent('storage', {
key: 'notes',
newValue: JSON.stringify(message.notes)
}));
return true;
}
});

Expand All @@ -47,9 +82,73 @@ window.addEventListener('storage', async (event) => {
type: 'NOTES_UPDATED',
notes: notes
});

if (!event.oldValue) {
console.log('Initial notes detected, checking webapp ready state...');
checkAndNotifyWebappReady();
}
} catch (error) {
console.error('Failed to process notes update:', error);
}
}
});

console.log('Content script loaded');
let checkAttempts = 0;
const maxAttempts = 40;

function attemptCheck() {
if (checkAttempts >= maxAttempts) {
console.log('Max check attempts reached');
return;
}

if (!checkAndNotifyWebappReady()) {
checkAttempts++;
setTimeout(attemptCheck, 500);
}
}

attemptCheck();

window.addEventListener('load', () => {
console.log('Window loaded, starting webapp checks');
checkAttempts = 0;
attemptCheck();
});

document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded, starting webapp checks');
checkAttempts = 0;
attemptCheck();
});

const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if ((node.id === 'root' || node.id === 'app') && node.children.length > 0) {
console.log('React root detected, checking webapp status');
checkAttempts = 0;
attemptCheck();
}
}
}
}
});

observer.observe(document.body, {
childList: true,
subtree: true
});

const startTime = Date.now();
const interval = setInterval(() => {
if (Date.now() - startTime > 60000) {
clearInterval(interval);
return;
}
console.log('Periodic check for webapp');
checkAndNotifyWebappReady();
}, 5000);

9 changes: 6 additions & 3 deletions browser-extension/manifest.chrome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Trusty Notes",
"version": "0.1.4",
"version": "0.1.5",
"description": "Quick access to your encrypted notes",
"externally_connectable": {
"matches": ["https://notes.toolworks.dev/*"]
Expand All @@ -25,7 +25,7 @@
"https://notes.toolworks.dev/*"
],
"action": {
"default_icon": "icons/icon-96.png",
"default_icon": "icons/icon-128.png",
"default_title": "Trusty Notes",
"default_popup": "popup/popup.html"
},
Expand All @@ -37,5 +37,8 @@
"matches": ["https://notes.toolworks.dev/*"],
"js": ["content.js"],
"run_at": "document_idle"
}]
}],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
12 changes: 9 additions & 3 deletions browser-extension/manifest.firefox.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Trusty Notes",
"version": "0.1.4",
"version": "0.1.5",
"description": "Quick access to your encrypted notes",
"externally_connectable": {
"matches": ["https://notes.toolworks.dev/*"]
Expand All @@ -18,14 +18,17 @@
"scripting"
],
"web_accessible_resources": [{
"resources": ["content.js", "scripts/getStorageData.js"],
"resources": [
"content.js",
"scripts/getStorageData.js"
],
"matches": ["https://notes.toolworks.dev/*"]
}],
"host_permissions": [
"https://notes.toolworks.dev/*"
],
"action": {
"default_icon": "icons/icon-96.png",
"default_icon": "icons/icon-128.png",
"default_title": "Trusty Notes",
"default_popup": "popup/popup.html"
},
Expand All @@ -41,5 +44,8 @@
"gecko": {
"id": "[email protected]"
}
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
Loading

0 comments on commit 72dde67

Please sign in to comment.