feat: custom domains for OpenWebUI + fix README issues

- Add custom domain support in Options page so users can add
  self-hosted AI services (e.g. https://ai.myserver.com)
- Background worker dynamically injects content scripts on
  custom domains using scripting.executeScript
- Add optional_host_permissions so Chrome can grant per-domain access
- Rewrite README: add clone step to Firefox instructions, clarify
  what "credentials" means in step 3, add Windows commands alongside
  Mac/Linux for every terminal step
- Bump version to 0.2.0

https://claude.ai/code/session_01Dvgwe7XMoSxnWXkih8p1Cw
This commit is contained in:
Claude
2026-03-26 00:35:21 +00:00
parent 1fe813337b
commit 60b810b1b9
8 changed files with 286 additions and 118 deletions
+59 -53
View File
@@ -8,63 +8,69 @@
* Communication: page script <-> content script via window.postMessage
*/
'use strict';
(function () {
'use strict';
// Cross-browser API
const api =
typeof browser !== 'undefined' && browser.runtime
? browser
: typeof chrome !== 'undefined'
? chrome
: null;
// Prevent double-injection on custom domains
if (window.__silentSendInjected) return;
window.__silentSendInjected = true;
// Load mappings and settings, then inject into page
async function init() {
const result = await api.storage.local.get(['ss_mappings', 'ss_identity', 'ss_settings']);
const mappings = result.ss_mappings || [];
const identity = result.ss_identity || {};
const settings = result.ss_settings || { enabled: true };
// Cross-browser API
const api =
typeof browser !== 'undefined' && browser.runtime
? browser
: typeof chrome !== 'undefined'
? chrome
: null;
// Inject the main interception script into the page's world
const script = document.createElement('script');
script.setAttribute('data-ss-config', JSON.stringify({ mappings, identity, settings }));
script.src = api.runtime.getURL('src/content/content.js');
(document.head || document.documentElement).appendChild(script);
script.onload = () => script.remove();
// Load mappings and settings, then inject into page
async function init() {
const result = await api.storage.local.get(['ss_mappings', 'ss_identity', 'ss_settings']);
const mappings = result.ss_mappings || [];
const identity = result.ss_identity || {};
const settings = result.ss_settings || { enabled: true };
// Listen for substitution events from the page script
window.addEventListener('message', (event) => {
if (event.source !== window) return;
if (event.data?.type === 'ss:substitution-performed') {
api.runtime.sendMessage({
type: 'substitution:performed',
count: event.data.count,
replacements: event.data.replacements,
}).catch(() => {});
}
});
// Inject the main interception script into the page's world
const script = document.createElement('script');
script.setAttribute('data-ss-config', JSON.stringify({ mappings, identity, settings }));
script.src = api.runtime.getURL('src/content/content.js');
(document.head || document.documentElement).appendChild(script);
script.onload = () => script.remove();
// Forward storage changes to the page script
api.storage.onChanged.addListener((changes) => {
if (changes.ss_mappings || changes.ss_identity || changes.ss_settings) {
window.postMessage({
type: 'ss:config-updated',
mappings: changes.ss_mappings?.newValue,
identity: changes.ss_identity?.newValue,
settings: changes.ss_settings?.newValue,
}, '*');
}
});
// Listen for substitution events from the page script
window.addEventListener('message', (event) => {
if (event.source !== window) return;
if (event.data?.type === 'ss:substitution-performed') {
api.runtime.sendMessage({
type: 'substitution:performed',
count: event.data.count,
replacements: event.data.replacements,
}).catch(() => {});
}
});
// Listen for settings updates from popup via runtime messages
api.runtime.onMessage.addListener((message) => {
if (message.type === 'settings:updated') {
window.postMessage({
type: 'ss:config-updated',
settings: message.settings,
}, '*');
}
});
}
// Forward storage changes to the page script
api.storage.onChanged.addListener((changes) => {
if (changes.ss_mappings || changes.ss_identity || changes.ss_settings) {
window.postMessage({
type: 'ss:config-updated',
mappings: changes.ss_mappings?.newValue,
identity: changes.ss_identity?.newValue,
settings: changes.ss_settings?.newValue,
}, '*');
}
});
init();
// Listen for settings updates from popup via runtime messages
api.runtime.onMessage.addListener((message) => {
if (message.type === 'settings:updated') {
window.postMessage({
type: 'ss:config-updated',
settings: message.settings,
}, '*');
}
});
}
init();
})();