Skip to content

Commit 72dde67

Browse files
committed
v0.1.5 - Web Extension Note Editing + Sync
1 parent e5c451b commit 72dde67

File tree

15 files changed

+925
-90
lines changed

15 files changed

+925
-90
lines changed

android/app/src/main/assets/public/assets/index-B1frYiu1.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser-extension/background.js

Lines changed: 162 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// CryptoService implementation
21
class CryptoService {
32
constructor(key) {
43
this.key = key;
@@ -54,6 +53,9 @@ class CryptoService {
5453
}
5554

5655
let cryptoService = null;
56+
let webappTabCheckInterval = null;
57+
let lastSyncTime = 0;
58+
const SYNC_COOLDOWN = 2000;
5759

5860
async function encryptAndStoreNotes(notes) {
5961
try {
@@ -76,16 +78,173 @@ async function encryptAndStoreNotes(notes) {
7678
}
7779
}
7880

81+
async function checkForPendingSync() {
82+
try {
83+
const now = Date.now();
84+
if (now - lastSyncTime < SYNC_COOLDOWN) {
85+
return;
86+
}
87+
88+
const result = await chrome.storage.local.get(['encrypted_notes', 'seed_phrase', 'lastUpdated']);
89+
if (!result.encrypted_notes) return;
90+
91+
if (result.lastUpdated && now - result.lastUpdated < SYNC_COOLDOWN) {
92+
return;
93+
}
94+
95+
if (!cryptoService && result.seed_phrase) {
96+
cryptoService = await CryptoService.new(result.seed_phrase);
97+
}
98+
if (!cryptoService) return;
99+
100+
let notes = await cryptoService.decrypt(result.encrypted_notes);
101+
const pendingNotes = notes.filter(note => note.pending_sync);
102+
103+
if (pendingNotes.length > 0 || now - lastSyncTime > 10000) {
104+
const tabs = await chrome.tabs.query({ url: 'https://notes.toolworks.dev/*' });
105+
if (tabs.length > 0) {
106+
const tab = tabs[0];
107+
108+
const success = await chrome.scripting.executeScript({
109+
target: { tabId: tab.id },
110+
func: (extensionNotes, lastSync) => {
111+
return new Promise((resolve) => {
112+
const checkAndSync = () => {
113+
try {
114+
const notesJson = localStorage.getItem('notes');
115+
if (!notesJson) {
116+
setTimeout(checkAndSync, 500);
117+
return;
118+
}
119+
120+
let webappNotes = JSON.parse(notesJson);
121+
122+
const lastWebappUpdate = Math.max(...webappNotes.map(n => n.updated_at));
123+
if (lastWebappUpdate > lastSync && Date.now() - lastWebappUpdate < 2000) {
124+
resolve({
125+
success: true,
126+
notes: extensionNotes,
127+
hasChanges: false
128+
});
129+
return;
130+
}
131+
132+
const webappNotesMap = new Map(
133+
webappNotes.map(note => [note.id, note])
134+
);
135+
const extensionNotesMap = new Map(
136+
extensionNotes.map(note => [note.id, note])
137+
);
138+
139+
let hasChanges = false;
140+
const allNoteIds = new Set([
141+
...webappNotesMap.keys(),
142+
...extensionNotesMap.keys()
143+
]);
144+
145+
const finalNotesMap = new Map();
146+
147+
allNoteIds.forEach(id => {
148+
const webappNote = webappNotesMap.get(id);
149+
const extensionNote = extensionNotesMap.get(id);
150+
151+
if (!webappNote) {
152+
hasChanges = true;
153+
} else if (!extensionNote) {
154+
hasChanges = true;
155+
finalNotesMap.set(id, webappNote);
156+
} else if (extensionNote.pending_sync) {
157+
hasChanges = true;
158+
finalNotesMap.set(id, {
159+
...extensionNote,
160+
pending_sync: false
161+
});
162+
} else if (webappNote.updated_at > extensionNote.updated_at) {
163+
hasChanges = true;
164+
finalNotesMap.set(id, webappNote);
165+
} else {
166+
finalNotesMap.set(id, extensionNote);
167+
}
168+
});
169+
170+
if (!hasChanges) {
171+
resolve({
172+
success: true,
173+
notes: extensionNotes,
174+
hasChanges: false
175+
});
176+
return;
177+
}
178+
179+
const mergedNotes = Array.from(finalNotesMap.values());
180+
181+
localStorage.setItem('notes', JSON.stringify(mergedNotes));
182+
183+
window.dispatchEvent(new StorageEvent('storage', {
184+
key: 'notes',
185+
oldValue: notesJson,
186+
newValue: JSON.stringify(mergedNotes)
187+
}));
188+
189+
resolve({
190+
success: true,
191+
notes: mergedNotes,
192+
hasChanges: true
193+
});
194+
} catch (error) {
195+
console.error('Sync attempt failed:', error);
196+
setTimeout(checkAndSync, 500);
197+
}
198+
};
199+
200+
checkAndSync();
201+
});
202+
},
203+
args: [notes, lastSyncTime]
204+
});
205+
206+
if (success && success[0]?.result?.success) {
207+
if (success[0].result.hasChanges) {
208+
notes = success[0].result.notes;
209+
await encryptAndStoreNotes(notes);
210+
await chrome.tabs.reload(tab.id);
211+
}
212+
lastSyncTime = Date.now();
213+
}
214+
}
215+
}
216+
} catch (error) {
217+
console.error('Error checking for pending sync:', error);
218+
}
219+
}
220+
221+
function startPeriodicCheck() {
222+
if (webappTabCheckInterval) {
223+
clearInterval(webappTabCheckInterval);
224+
}
225+
webappTabCheckInterval = setInterval(checkForPendingSync, 2000);
226+
}
227+
228+
startPeriodicCheck();
229+
79230
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
80231
if (message.type === 'NOTES_UPDATED' && message.notes) {
232+
lastSyncTime = Date.now();
81233
encryptAndStoreNotes(message.notes);
82234
}
83235
return true;
84236
});
85237

86-
// For Chrome compatibility, keep the service worker alive
238+
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
239+
if (changeInfo.status === 'complete' &&
240+
tab.url &&
241+
tab.url.startsWith('https://notes.toolworks.dev')) {
242+
checkForPendingSync();
243+
}
244+
});
245+
87246
chrome.runtime.onConnect.addListener(function(port) {
88247
port.onDisconnect.addListener(function() {
89-
// Reconnect or perform cleanup if needed
248+
startPeriodicCheck();
90249
});
91250
});

browser-extension/content.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
function checkAndNotifyWebappReady() {
2+
console.log('Checking webapp status...');
3+
const notes = localStorage.getItem('notes');
4+
if (notes !== null) {
5+
console.log('Webapp ready, sending message to extension...');
6+
const sendReadyMessage = (attempts = 0) => {
7+
chrome.runtime.sendMessage({
8+
type: 'WEBAPP_READY',
9+
notes: JSON.parse(notes)
10+
}, response => {
11+
if (chrome.runtime.lastError) {
12+
console.log('Failed to send WEBAPP_READY message:', chrome.runtime.lastError);
13+
if (attempts < 3) {
14+
setTimeout(() => sendReadyMessage(attempts + 1), 500);
15+
}
16+
} else {
17+
console.log('WEBAPP_READY message sent successfully');
18+
}
19+
});
20+
};
21+
sendReadyMessage();
22+
return true;
23+
}
24+
console.log('Webapp not ready yet, notes:', !!notes);
25+
return false;
26+
}
27+
128
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
229
if (message.type === 'GET_SYNC_SETTINGS') {
330
const script = document.createElement('script');
@@ -34,6 +61,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
3461
window.dispatchEvent(event);
3562
console.debug('Dispatched open-sync-settings event');
3663
return true;
64+
} else if (message.type === 'UPDATE_NOTES') {
65+
console.log('Updating webapp notes:', message.notes);
66+
localStorage.setItem('notes', JSON.stringify(message.notes));
67+
window.dispatchEvent(new StorageEvent('storage', {
68+
key: 'notes',
69+
newValue: JSON.stringify(message.notes)
70+
}));
71+
return true;
3772
}
3873
});
3974

@@ -47,9 +82,73 @@ window.addEventListener('storage', async (event) => {
4782
type: 'NOTES_UPDATED',
4883
notes: notes
4984
});
85+
86+
if (!event.oldValue) {
87+
console.log('Initial notes detected, checking webapp ready state...');
88+
checkAndNotifyWebappReady();
89+
}
5090
} catch (error) {
5191
console.error('Failed to process notes update:', error);
5292
}
5393
}
5494
});
95+
96+
console.log('Content script loaded');
97+
let checkAttempts = 0;
98+
const maxAttempts = 40;
99+
100+
function attemptCheck() {
101+
if (checkAttempts >= maxAttempts) {
102+
console.log('Max check attempts reached');
103+
return;
104+
}
105+
106+
if (!checkAndNotifyWebappReady()) {
107+
checkAttempts++;
108+
setTimeout(attemptCheck, 500);
109+
}
110+
}
111+
112+
attemptCheck();
113+
114+
window.addEventListener('load', () => {
115+
console.log('Window loaded, starting webapp checks');
116+
checkAttempts = 0;
117+
attemptCheck();
118+
});
119+
120+
document.addEventListener('DOMContentLoaded', () => {
121+
console.log('DOMContentLoaded, starting webapp checks');
122+
checkAttempts = 0;
123+
attemptCheck();
124+
});
125+
126+
const observer = new MutationObserver((mutations) => {
127+
for (const mutation of mutations) {
128+
if (mutation.addedNodes.length) {
129+
for (const node of mutation.addedNodes) {
130+
if ((node.id === 'root' || node.id === 'app') && node.children.length > 0) {
131+
console.log('React root detected, checking webapp status');
132+
checkAttempts = 0;
133+
attemptCheck();
134+
}
135+
}
136+
}
137+
}
138+
});
139+
140+
observer.observe(document.body, {
141+
childList: true,
142+
subtree: true
143+
});
144+
145+
const startTime = Date.now();
146+
const interval = setInterval(() => {
147+
if (Date.now() - startTime > 60000) {
148+
clearInterval(interval);
149+
return;
150+
}
151+
console.log('Periodic check for webapp');
152+
checkAndNotifyWebappReady();
153+
}, 5000);
55154

browser-extension/manifest.chrome.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "Trusty Notes",
4-
"version": "0.1.4",
4+
"version": "0.1.5",
55
"description": "Quick access to your encrypted notes",
66
"externally_connectable": {
77
"matches": ["https://notes.toolworks.dev/*"]
@@ -25,7 +25,7 @@
2525
"https://notes.toolworks.dev/*"
2626
],
2727
"action": {
28-
"default_icon": "icons/icon-96.png",
28+
"default_icon": "icons/icon-128.png",
2929
"default_title": "Trusty Notes",
3030
"default_popup": "popup/popup.html"
3131
},
@@ -37,5 +37,8 @@
3737
"matches": ["https://notes.toolworks.dev/*"],
3838
"js": ["content.js"],
3939
"run_at": "document_idle"
40-
}]
40+
}],
41+
"content_security_policy": {
42+
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
43+
}
4144
}

browser-extension/manifest.firefox.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "Trusty Notes",
4-
"version": "0.1.4",
4+
"version": "0.1.5",
55
"description": "Quick access to your encrypted notes",
66
"externally_connectable": {
77
"matches": ["https://notes.toolworks.dev/*"]
@@ -18,14 +18,17 @@
1818
"scripting"
1919
],
2020
"web_accessible_resources": [{
21-
"resources": ["content.js", "scripts/getStorageData.js"],
21+
"resources": [
22+
"content.js",
23+
"scripts/getStorageData.js"
24+
],
2225
"matches": ["https://notes.toolworks.dev/*"]
2326
}],
2427
"host_permissions": [
2528
"https://notes.toolworks.dev/*"
2629
],
2730
"action": {
28-
"default_icon": "icons/icon-96.png",
31+
"default_icon": "icons/icon-128.png",
2932
"default_title": "Trusty Notes",
3033
"default_popup": "popup/popup.html"
3134
},
@@ -41,5 +44,8 @@
4144
"gecko": {
4245
4346
}
47+
},
48+
"content_security_policy": {
49+
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
4450
}
4551
}

0 commit comments

Comments
 (0)