Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion manifest.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"permissions": [
"activeTab",
"storage",
"tabs",
"offscreen",
"notifications",
"identity",
Expand Down
10 changes: 2 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 33 additions & 1 deletion src/background/service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Promise.all([
updateExtensionBadge();
});
console.log('Upload completion callback registered');
// Auth is now initialized — safe to start processing any queued uploads
numbersApi.upload.startProcessing();
} catch (error) {
console.error('Failed to initialize NumbersApiManager:', error);
}
Expand Down Expand Up @@ -162,6 +164,7 @@ async function handleScreenshotCaptureMessage(message: CaptureScreenshotMessage)
let pendingSelectionResolve: ((value: any) => void) | null = null;
let pendingSelectionReject: ((reason: any) => void) | null = null;
let pendingSelectionFromPopup = false;
let pendingSelectionTimeoutId: ReturnType<typeof setTimeout> | null = null;

/**
* Handle selection mode capture
Expand All @@ -172,6 +175,22 @@ async function handleSelectionCapture(tab: chrome.tabs.Tab): Promise<any> {
throw new Error('No active tab found');
}

// Validate that the tab is on a page that supports content script injection
if (!tab.url?.match(/^https?:\/\//)) {
throw new Error('Selection mode is only supported on web pages with http:// or https:// URLs. Chrome extension pages, local files, and browser pages cannot be captured.');
}

// Reject any existing pending selection to avoid resource leaks
if (pendingSelectionReject) {
pendingSelectionReject(new Error('Selection cancelled: a new selection was started'));
pendingSelectionResolve = null;
pendingSelectionReject = null;
}
if (pendingSelectionTimeoutId !== null) {
clearTimeout(pendingSelectionTimeoutId);
pendingSelectionTimeoutId = null;
}

// Inject the selection overlay content script
try {
await chrome.scripting.executeScript({
Expand All @@ -189,11 +208,12 @@ async function handleSelectionCapture(tab: chrome.tabs.Tab): Promise<any> {
pendingSelectionReject = reject;

// Timeout after 60 seconds
setTimeout(() => {
pendingSelectionTimeoutId = setTimeout(() => {
if (pendingSelectionReject) {
pendingSelectionReject(new Error('Selection timed out'));
pendingSelectionResolve = null;
pendingSelectionReject = null;
pendingSelectionTimeoutId = null;
}
}, 60000);
});
Expand All @@ -205,6 +225,10 @@ async function handleSelectionCapture(tab: chrome.tabs.Tab): Promise<any> {
async function handleSelectionComplete(payload: any) {
if (payload.cancelled) {
console.log('Selection cancelled:', payload.reason);
if (pendingSelectionTimeoutId !== null) {
clearTimeout(pendingSelectionTimeoutId);
pendingSelectionTimeoutId = null;
}
if (pendingSelectionResolve) {
pendingSelectionResolve({ cancelled: true, reason: payload.reason });
pendingSelectionResolve = null;
Expand Down Expand Up @@ -357,6 +381,10 @@ async function handleSelectionComplete(payload: any) {
});

// Resolve the pending promise
if (pendingSelectionTimeoutId !== null) {
clearTimeout(pendingSelectionTimeoutId);
pendingSelectionTimeoutId = null;
}
if (pendingSelectionResolve) {
pendingSelectionResolve({
assetId,
Expand All @@ -370,6 +398,10 @@ async function handleSelectionComplete(payload: any) {
}
} catch (error: any) {
console.error('Failed to capture selection:', error);
if (pendingSelectionTimeoutId !== null) {
clearTimeout(pendingSelectionTimeoutId);
pendingSelectionTimeoutId = null;
}
if (pendingSelectionReject) {
pendingSelectionReject(error);
pendingSelectionResolve = null;
Expand Down
20 changes: 16 additions & 4 deletions src/services/StorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,15 @@ export class StorageService {
async getSettings(): Promise<StoredSettings> {
const result = await chrome.storage.local.get('user_settings');
if (result.user_settings) {
const saved = JSON.parse(result.user_settings);
// Merge with defaults to ensure new fields are present
return { ...DEFAULT_SETTINGS, ...saved };
try {
const saved = JSON.parse(result.user_settings);
// Merge with defaults to ensure new fields are present
return { ...DEFAULT_SETTINGS, ...saved };
} catch (error) {
console.error('Failed to parse user_settings from storage, resetting to defaults:', error);
await this.setSettings(DEFAULT_SETTINGS);
return DEFAULT_SETTINGS;
}
}
return DEFAULT_SETTINGS;
}
Expand Down Expand Up @@ -166,7 +172,13 @@ export class StorageService {
async getUploadQueueIds(): Promise<string[]> {
const result = await chrome.storage.local.get('upload_queue');
if (result.upload_queue) {
return JSON.parse(result.upload_queue);
try {
return JSON.parse(result.upload_queue);
} catch (error) {
console.error('Failed to parse upload_queue from storage, resetting to empty queue:', error);
await chrome.storage.local.remove('upload_queue');
return [];
}
}
return [];
}
Expand Down
15 changes: 12 additions & 3 deletions src/services/UploadService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class UploadService {
}

/**
* Restore upload queue from storage on initialization
* Restore upload queue from storage on initialization.
* Does NOT start processing — call startProcessing() after auth is ready.
*/
private async restoreQueue() {
try {
Expand All @@ -51,14 +52,22 @@ export class UploadService {
}
this.uploadQueue = assets;
console.log(`Restored ${assets.length} assets to upload queue`);
// Auto-start processing if not paused
this.processQueue();
// Processing is deferred until startProcessing() is called after auth init
}
} catch (error) {
console.error('Failed to restore upload queue:', error);
}
}

/**
* Start processing the upload queue.
* Must be called explicitly after authentication has been initialized
* to avoid uploading with a missing auth token.
*/
startProcessing(): void {
this.processQueue();
}

/**
* Add an asset to the upload queue
* @param asset - The asset to upload
Expand Down