fix(anki): preserve managed proxy state when settings sync fails

This commit is contained in:
2026-09-22 15:49:24 -07:00
parent ab5e50adac
commit 9fe4c7eb33
3 changed files with 68 additions and 3 deletions
@@ -61,6 +61,65 @@ test('Yomitan restores direct Anki after disabling its managed proxy without a p
assert.equal(managedUrl, null);
});
for (const failure of ['optionsGetFull', 'setAllSettings', 'storageSet']) {
test(`Yomitan retries disabling its managed proxy after ${failure} fails`, async () => {
const proxyUrl = 'http://127.0.0.1:8766';
const directUrl = 'http://127.0.0.1:8765';
let options = { profiles: [{ options: { anki: { server: proxyUrl } } }] };
let managedUrl: string | null = proxyUrl;
let shouldFail = true;
const context = vm.createContext({
chrome: {
storage: {
local: {
get: async () => ({ subminerAnkiProxyUrl: managedUrl }),
set: async (value: { subminerAnkiProxyUrl: string | null }) => {
if (shouldFail && failure === 'storageSet') throw new Error('Storage unavailable');
managedUrl = value.subminerAnkiProxyUrl;
},
},
},
runtime: {
sendMessage: (
message: { action: string; params?: { value: typeof options } },
callback: (response: { result?: unknown; error?: { message: string } }) => void,
) => {
if (shouldFail && message.action === failure) {
callback({ error: { message: 'Settings unavailable' } });
} else if (message.action === 'optionsGetFull') {
callback({ result: structuredClone(options) });
} else if (message.action === 'setAllSettings' && message.params) {
options = structuredClone(message.params.value);
callback({ result: null });
} else {
assert.fail(`Unexpected action: ${message.action}`);
}
},
},
},
});
const deps = createDeps(async (script) =>
structuredClone(await vm.runInContext(script, context)),
);
const errors: string[] = [];
const logger = { error: (message: string) => errors.push(message) };
assert.equal(await syncYomitanDefaultAnkiServer(directUrl, deps, logger), false);
assert.equal(managedUrl, proxyUrl);
assert.equal(
options.profiles[0]?.options.anki.server,
failure === 'storageSet' ? directUrl : proxyUrl,
);
assert.equal(errors.length, 1);
shouldFail = false;
assert.equal(await syncYomitanDefaultAnkiServer(directUrl, deps, logger), true);
assert.equal(options.profiles[0]?.options.anki.server, directUrl);
assert.equal(managedUrl, null);
assert.equal(errors.length, 1);
});
}
test('syncYomitanDefaultAnkiServer updates default profile server when script reports update', async () => {
let scriptValue = '';
const deps = createDeps(async (script) => {
@@ -1442,7 +1442,6 @@ export async function syncYomitanDefaultAnkiServer(
});
}
const { subminerAnkiProxyUrl: previousManagedProxy } = await chrome.storage.local.get('subminerAnkiProxyUrl');
await chrome.storage.local.set({ subminerAnkiProxyUrl: forceOverride ? targetServer : null });
const optionsFull = await invoke("optionsGetFull", undefined);
const profiles = Array.isArray(optionsFull.profiles) ? optionsFull.profiles : [];
if (profiles.length === 0) {
@@ -1472,6 +1471,8 @@ export async function syncYomitanDefaultAnkiServer(
forceOverride || currentServer.length === 0 || currentServer === "http://127.0.0.1:8765" ||
(typeof previousManagedProxy === 'string' && currentServer === previousManagedProxy);
if (!canReplaceCurrent) {
// A custom endpoint needs no settings change, but the proxy is no longer managed.
await chrome.storage.local.set({ subminerAnkiProxyUrl: null });
return { updated: false, matched: false, reason: "blocked-existing-server", currentServer, targetServer };
}
@@ -1509,11 +1510,16 @@ export async function syncYomitanDefaultAnkiServer(
}
}
if (changed) {
await invoke("setAllSettings", { value: optionsFull, source: "subminer" });
}
// Preserve the previous managed endpoint until settings are saved so failed switches can retry.
await chrome.storage.local.set({ subminerAnkiProxyUrl: forceOverride ? targetServer : null });
if (!changed) {
return { updated: false, matched: true, reason: "already-target", currentServer, targetServer, targetDeck };
}
await invoke("setAllSettings", { value: optionsFull, source: "subminer" });
return { updated: true, matched: true, currentServer, targetServer, targetDeck };
})();
`;