Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"proxy",
"webRequest",
"webRequestAuthProvider",
"activeTab"
"activeTab",
"alarms"
]
}
51 changes: 51 additions & 0 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,54 @@
"config_proxy_type_pac": {
"message": "PAC Script"
},
"config_section_pac_url": {
"message": "PAC Script URL"
},
"config_section_pac_url_placeholder": {
"message": "https://example.com/proxy.pac"
},
"config_section_pac_url_hint": {
"message": "Optional. If set, the script body is refreshed periodically in the background."
},
"config_action_pac_fetch": {
"message": "Fetch Now"
},
"config_feedback_pac_fetched": {
"message": "PAC script fetched successfully."
},
"config_feedback_pac_fetch_failed": {
"message": "Failed to fetch PAC script: $1"
},
"config_pac_last_fetched": {
"message": "Last fetched: $1"
},
"config_pac_last_error": {
"message": "Last fetch error: $1"
},
"config_pac_manual_edit_warning": {
"message": "A source URL is set. Manual edits will be overwritten on the next refresh."
},
"config_section_pac_refresh_interval": {
"message": "Refresh Interval"
},
"config_section_pac_refresh_interval_hint": {
"message": "How often the PAC script is re-fetched in the background."
},
"config_pac_refresh_interval_minutes": {
"message": "Every $1 minutes"
},
"config_pac_refresh_interval_one_hour": {
"message": "Every hour"
},
"config_pac_refresh_interval_hours": {
"message": "Every $1 hours"
},
"config_pac_refresh_interval_one_day": {
"message": "Every day"
},
"config_pac_refresh_interval_disabled": {
"message": "Disabled (manual only)"
},
"config_proxy_type_auto": {
"message": "Auto Switch"
},
Expand Down Expand Up @@ -224,6 +272,9 @@
"form_is_required": {
"message": "$1 is required"
},
"form_invalid_url": {
"message": "$1 must be a valid http:// or https:// URL"
},

"feedback_error": {
"message": "Something wrong"
Expand Down
9 changes: 9 additions & 0 deletions src/adapters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ export abstract class BaseAdapter {
): void;
abstract sendMessage(message: any): Promise<any>;

// alarms
abstract createPeriodicAlarm(
name: string,
periodInMinutes: number
): Promise<void>;
abstract clearAlarm(name: string): Promise<void>;
abstract getAllAlarmNames(): Promise<string[]>;
abstract onAlarm(callback: (name: string) => void): void;

// i18n
abstract currentLocale(): string;
abstract getMessage(key: string, substitutions?: string | string[]): string;
Expand Down
18 changes: 18 additions & 0 deletions src/adapters/chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ export class Chrome extends BaseAdapter {
urls: ["<all_urls>"],
});
}

async createPeriodicAlarm(
name: string,
periodInMinutes: number
): Promise<void> {
await chrome.alarms.create(name, { periodInMinutes });
}
async clearAlarm(name: string): Promise<void> {
await chrome.alarms.clear(name);
}
async getAllAlarmNames(): Promise<string[]> {
const all = await chrome.alarms.getAll();
return all.map((a) => a.name);
}
onAlarm(callback: (name: string) => void): void {
chrome.alarms.onAlarm.addListener((alarm) => callback(alarm.name));
}

currentLocale(): string {
return chrome.i18n.getUILanguage();
}
Expand Down
18 changes: 18 additions & 0 deletions src/adapters/firefox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ export class Firefox extends BaseAdapter {
urls: ["<all_urls>"],
});
}

async createPeriodicAlarm(
name: string,
periodInMinutes: number
): Promise<void> {
await browser.alarms.create(name, { periodInMinutes });
}
async clearAlarm(name: string): Promise<void> {
await browser.alarms.clear(name);
}
async getAllAlarmNames(): Promise<string[]> {
const all = await browser.alarms.getAll();
return all.map((a) => a.name);
}
onAlarm(callback: (name: string) => void): void {
browser.alarms.onAlarm.addListener((alarm) => callback(alarm.name));
}

currentLocale(): string {
return browser.i18n.getUILanguage();
}
Expand Down
13 changes: 13 additions & 0 deletions src/adapters/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ export class WebBrowser extends BaseAdapter {
): void {
throw new Error("Method not implemented.");
}
async createPeriodicAlarm(_: string, __: number): Promise<void> {
// no-op for local dev
}
async clearAlarm(_: string): Promise<void> {
// no-op for local dev
}
async getAllAlarmNames(): Promise<string[]> {
return [];
}
onAlarm(_: (name: string) => void): void {
// no-op for local dev
}

currentLocale(): string {
return "en-US";
}
Expand Down
27 changes: 27 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ import {
getCurrentProxySetting,
} from "./services/proxy";
import { WebRequestStatsService } from "./services/stats";
import {
handlePacRefreshAlarm,
reconcilePacAlarms,
refreshActivePacIfStale,
} from "./services/proxy/pacFetcher";
import { onProfileUpdate } from "./services/profile";

// indicator
async function initIndicator() {
Expand All @@ -28,6 +34,27 @@ async function initIndicator() {

initIndicator().catch(console.error);

// Per-profile PAC script refresh. Each PAC profile with a sourceURL and a
// positive refresh interval owns a `pac-refresh:<profileID>` alarm. The
// wake-time refresh only hits the active profile (with a staleness guard) —
// the SW wakes on every main-frame request, so fetching all URL-backed
// profiles here would mean back-to-back PAC requests during normal browsing.
function initPacRefresh() {
Host.onAlarm((name) => {
handlePacRefreshAlarm(name).catch(console.error);
});

onProfileUpdate((profiles) => {
reconcilePacAlarms(profiles).catch(console.error);
});

Promise.all([reconcilePacAlarms(), refreshActivePacIfStale()]).catch(
console.error,
);
}

initPacRefresh();

// proxy auth provider
class ProxyAuthProvider {
// requests[requestID] = request attempts. 0 means the 1st attempt
Expand Down
Loading
Loading