Export profiles as PAC scripts — Generate standards-compliant
.pacfiles for use in browsers or proxy systems using File System Access API.
Implemented PAC (Proxy Auto-Config) script export functionality, achieving feature parity with ZeroOmega’s omega-pac module. Users can export configured profiles as PAC scripts for use in browsers or proxy systems.
src/core/pac/pac-generator.tsMain Function:
generatePacScript(profile: Profile, allProfiles: Profile[]): string
Supported Profile Types:
| Profile Type | Status | Description |
|---|---|---|
| FixedProfile | ✅ | Single proxy with optional bypass rules |
| SwitchProfile | ✅ | Rule-based switching with conditions |
| DirectProfile | ✅ | Returns “DIRECT” connection |
| SystemProfile | ✅ | Returns “DIRECT” (system proxy not applicable in PAC) |
| PacProfile | ⏳ | Returns original PAC URL (future enhancement) |
Supported Condition Types:
All 7 condition types from the schema:
| Condition Type | PAC Function |
|---|---|
| HostWildcard | shExpMatch(host, pattern) |
| UrlWildcard | shExpMatch(url, pattern) |
| HostRegex | JavaScript RegExp matching on host |
| UrlRegex | JavaScript RegExp matching on URL |
| Keyword | Simple substring search in URL |
| HostLevels | Domain level counting |
| Bypass | For fixed profiles with bypass rules |
Special Features:
| Feature | Implementation |
|---|---|
| CIDR notation | 192.168.0.0/16 → isInNet(host, "192.168.0.0", "255.255.0.0") |
| Proxy protocol mapping | HTTP→PROXY, HTTPS→HTTPS, SOCKS4→SOCKS, SOCKS5→SOCKS5 |
| Wildcard escaping | Converts user-friendly wildcards to PAC-safe patterns |
| Minification | Removes comments and whitespace for production use |
src/options/OptionsApp.vueLocation: Profile Detail View → Action buttons (between Edit and Delete)
Button:
<button
v-if="selectedProfile.profileType === 'FixedProfile' || selectedProfile.profileType === 'SwitchProfile'"
@click="exportProfileAsPac(selectedProfile)"
class="px-3 py-1.5 text-[12px] font-medium rounded-md bg-blue-100 dark:bg-blue-950/30 text-blue-700 dark:text-blue-400"
title="Export as PAC script"
>
<Download class="h-3.5 w-3.5" />
Export PAC
</button>
Export Function:
import { saveBlobToFile } from '@/lib/fileSaver';
async function exportProfileAsPac(profile: Profile) {
// 1. Generate PAC script using pac-generator module
const pacScript = generatePacScript(profile, profiles.value);
// 2. Create downloadable blob
const blob = new Blob([pacScript], { type: 'application/x-ns-proxy-autoconfig' });
// 3. Save file (File System Access API with anchor fallback)
const safeName = profile.name.replace(/[^a-zA-Z0-9-_]/g, '_');
const filename = `${safeName}.pac`;
try {
await saveBlobToFile(blob, filename, 'application/x-ns-proxy-autoconfig');
toastRef.value?.success(`PAC script exported: ${filename}`, 'Exported', 3000);
} catch (err) {
// User cancelled or error - handle gracefully
if (err && (err.name === 'AbortError' || err.message?.includes('cancelled'))) {
toastRef.value?.info('Export cancelled', 'Cancelled');
} else {
toastRef.value?.error('Failed to export PAC script', 'Error');
console.error('Export failed', err);
}
}
}
Fixed Profile with Bypass:
function FindProxyForURL(url, host) {
// Direct access for bypass rules
if (shExpMatch(host, "*.local")) return "DIRECT";
if (shExpMatch(host, "localhost")) return "DIRECT";
// Fixed proxy
return "PROXY proxy.example.com:8080";
}
Switch Profile with Rules:
function FindProxyForURL(url, host) {
// Rule: Work Sites
if (shExpMatch(host, "*.company.com")) {
return "PROXY corporate-proxy:3128";
}
// Rule: Streaming
if (shExpMatch(host, "*.netflix.com") || shExpMatch(host, "*.youtube.com")) {
return "SOCKS5 streaming-proxy:1080";
}
// Default fallback
return "DIRECT";
}
npm run buildchrome://extensions → Load unpacked → Select dist/.pac file.pac file opens in text editor with expected FindProxyForURL logicUnit tests for file save helper: tests/lib/fileSaver.spec.ts
# Test PAC file in browser
1. Open chrome://net-internals/#proxy
2. Check "Use custom proxy configuration"
3. Enter: file:///path/to/exported.pac
4. Test URLs to verify routing
| Path | Description |
|---|---|
src/core/pac/pac-generator.ts |
Core PAC generation logic (345 lines) |
src/options/OptionsApp.vue |
UI integration |
src/lib/fileSaver.ts |
File save helper with File System Access API |
| Dependency | Usage |
|---|---|
| Native Blob API | Create downloadable content |
| URL.createObjectURL() | Generate blob URLs |
| File System Access API | Native save dialog (progressive enhancement) |
src/lib/fileSaver.ts |
saveBlobToFile() helper |
| TypeScript types | @/core/schema |
| Measure | Description |
|---|---|
| Filename sanitization | Prevents path traversal |
| Regex validation | Handled by existing regexSafe.ts |
| Browser sandbox | PAC scripts run in browser sandbox |
| No eval() | No dynamic code execution |
| Permissions | No downloads permission required; uses File System Access API with anchor fallback |
| Status | Description |
|---|---|
| ✅ COMPLETE | PAC export fully functional |
| ✅ TESTED | Core logic, UI integration, build successful |
src/core/pac/pac-generator.ts