fix: persist and sync LuckMail settings before runs
This commit is contained in:
@@ -465,6 +465,10 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
hotmailServiceMode: HOTMAIL_SERVICE_MODE_LOCAL,
|
hotmailServiceMode: HOTMAIL_SERVICE_MODE_LOCAL,
|
||||||
hotmailRemoteBaseUrl: DEFAULT_HOTMAIL_REMOTE_BASE_URL,
|
hotmailRemoteBaseUrl: DEFAULT_HOTMAIL_REMOTE_BASE_URL,
|
||||||
hotmailLocalBaseUrl: DEFAULT_HOTMAIL_LOCAL_BASE_URL,
|
hotmailLocalBaseUrl: DEFAULT_HOTMAIL_LOCAL_BASE_URL,
|
||||||
|
luckmailApiKey: '',
|
||||||
|
luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
|
||||||
|
luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
|
||||||
|
luckmailDomain: '',
|
||||||
cloudflareDomain: '',
|
cloudflareDomain: '',
|
||||||
cloudflareDomains: [],
|
cloudflareDomains: [],
|
||||||
cloudflareTempEmailBaseUrl: '',
|
cloudflareTempEmailBaseUrl: '',
|
||||||
@@ -1295,6 +1299,14 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
return normalizeHotmailRemoteBaseUrl(value);
|
return normalizeHotmailRemoteBaseUrl(value);
|
||||||
case 'hotmailLocalBaseUrl':
|
case 'hotmailLocalBaseUrl':
|
||||||
return normalizeHotmailLocalBaseUrl(value);
|
return normalizeHotmailLocalBaseUrl(value);
|
||||||
|
case 'luckmailApiKey':
|
||||||
|
return String(value || '');
|
||||||
|
case 'luckmailBaseUrl':
|
||||||
|
return normalizeLuckmailBaseUrl(value);
|
||||||
|
case 'luckmailEmailType':
|
||||||
|
return normalizeLuckmailEmailType(value);
|
||||||
|
case 'luckmailDomain':
|
||||||
|
return String(value || '').trim();
|
||||||
case 'cloudflareDomain':
|
case 'cloudflareDomain':
|
||||||
return normalizeCloudflareDomain(value);
|
return normalizeCloudflareDomain(value);
|
||||||
case 'cloudflareDomains':
|
case 'cloudflareDomains':
|
||||||
|
|||||||
+11
-4
@@ -1407,6 +1407,12 @@ async function settlePendingSettingsBeforeImport() {
|
|||||||
await waitForSettingsSaveIdle();
|
await waitForSettingsSaveIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function persistCurrentSettingsForAction() {
|
||||||
|
clearTimeout(settingsAutoSaveTimer);
|
||||||
|
await waitForSettingsSaveIdle();
|
||||||
|
await saveSettings({ silent: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
function downloadTextFile(content, fileName, mimeType = 'application/json;charset=utf-8') {
|
function downloadTextFile(content, fileName, mimeType = 'application/json;charset=utf-8') {
|
||||||
const blob = new Blob([content], { type: mimeType });
|
const blob = new Blob([content], { type: mimeType });
|
||||||
const objectUrl = URL.createObjectURL(blob);
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
@@ -2559,10 +2565,10 @@ function scheduleSettingsAutoSave() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveSettings(options = {}) {
|
async function saveSettings(options = {}) {
|
||||||
const { silent = false } = options;
|
const { silent = false, force = false } = options;
|
||||||
clearTimeout(settingsAutoSaveTimer);
|
clearTimeout(settingsAutoSaveTimer);
|
||||||
|
|
||||||
if (!settingsDirty && !settingsSaveInFlight && silent) {
|
if (!force && !settingsDirty && !settingsSaveInFlight && silent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5005,6 +5011,7 @@ stepsList?.addEventListener('click', async (event) => {
|
|||||||
if (!(await maybeTakeoverAutoRun(`执行步骤 ${step}`))) {
|
if (!(await maybeTakeoverAutoRun(`执行步骤 ${step}`))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await persistCurrentSettingsForAction();
|
||||||
if (step === 3) {
|
if (step === 3) {
|
||||||
if (inputPassword.value !== (latestState?.customPassword || '')) {
|
if (inputPassword.value !== (latestState?.customPassword || '')) {
|
||||||
await chrome.runtime.sendMessage({
|
await chrome.runtime.sendMessage({
|
||||||
@@ -5236,8 +5243,8 @@ async function startAutoRunFromCurrentSettings() {
|
|||||||
console.warn('Failed to refresh contribution content hint before auto run:', error);
|
console.warn('Failed to refresh contribution content hint before auto run:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof settingsDirty !== 'undefined' && settingsDirty && typeof saveSettings === 'function') {
|
if (typeof persistCurrentSettingsForAction === 'function') {
|
||||||
await saveSettings({ silent: true });
|
await persistCurrentSettingsForAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
|
const customEmailPoolEnabled = typeof usesCustomEmailPoolGenerator === 'function'
|
||||||
|
|||||||
@@ -437,6 +437,58 @@ return {
|
|||||||
assert.equal(snapshot.tokenCodeCalls, 2);
|
assert.equal(snapshot.tokenCodeCalls, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('buildPersistentSettingsPayload keeps LuckMail config fields for storage.local persistence', () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('normalizePersistentSettingValue'),
|
||||||
|
extractFunction('buildPersistentSettingsPayload'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const factory = new Function(`
|
||||||
|
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
|
||||||
|
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
|
||||||
|
const PERSISTED_SETTING_DEFAULTS = {
|
||||||
|
luckmailApiKey: '',
|
||||||
|
luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
|
||||||
|
luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
|
||||||
|
luckmailDomain: '',
|
||||||
|
};
|
||||||
|
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||||
|
function normalizeLuckmailBaseUrl(value) {
|
||||||
|
const normalized = String(value || '').trim() || DEFAULT_LUCKMAIL_BASE_URL;
|
||||||
|
return normalized.replace(/\\/$/, '');
|
||||||
|
}
|
||||||
|
function normalizeLuckmailEmailType(value) {
|
||||||
|
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
|
||||||
|
? String(value || '').trim()
|
||||||
|
: DEFAULT_LUCKMAIL_EMAIL_TYPE;
|
||||||
|
}
|
||||||
|
function resolveLegacyAutoStepDelaySeconds() {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
${bundle}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buildPersistentSettingsPayload,
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
|
||||||
|
const api = factory();
|
||||||
|
const payload = api.buildPersistentSettingsPayload({
|
||||||
|
luckmailApiKey: 'sk-live-demo',
|
||||||
|
luckmailBaseUrl: 'https://demo.example.com/',
|
||||||
|
luckmailEmailType: 'ms_imap',
|
||||||
|
luckmailDomain: ' outlook.com ',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(payload, {
|
||||||
|
luckmailApiKey: 'sk-live-demo',
|
||||||
|
luckmailBaseUrl: 'https://demo.example.com',
|
||||||
|
luckmailEmailType: 'ms_imap',
|
||||||
|
luckmailDomain: 'outlook.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('listLuckmailPurchasesByProject only keeps openai purchases', async () => {
|
test('listLuckmailPurchasesByProject only keeps openai purchases', async () => {
|
||||||
const bundle = extractFunction('listLuckmailPurchasesByProject');
|
const bundle = extractFunction('listLuckmailPurchasesByProject');
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ const console = {
|
|||||||
events.push({ type: 'warn', args });
|
events.push({ type: 'warn', args });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
async function persistCurrentSettingsForAction() {
|
||||||
|
events.push({ type: 'sync-settings' });
|
||||||
|
}
|
||||||
function getRunCountValue() { return ${Math.max(1, Number(runCount) || 1)}; }
|
function getRunCountValue() { return ${Math.max(1, Number(runCount) || 1)}; }
|
||||||
function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) || 0; }
|
function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) || 0; }
|
||||||
function shouldOfferAutoModeChoice() { return false; }
|
function shouldOfferAutoModeChoice() { return false; }
|
||||||
@@ -135,9 +138,9 @@ test('startAutoRunFromCurrentSettings refreshes contribution content hint before
|
|||||||
assert.equal(result, true);
|
assert.equal(result, true);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
api.getEvents().map((entry) => entry.type),
|
api.getEvents().map((entry) => entry.type),
|
||||||
['refresh', 'send']
|
['refresh', 'sync-settings', 'send']
|
||||||
);
|
);
|
||||||
assert.equal(api.getEvents()[1].message.type, 'AUTO_RUN');
|
assert.equal(api.getEvents()[2].message.type, 'AUTO_RUN');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('startAutoRunFromCurrentSettings continues auto run when contribution content refresh fails', async () => {
|
test('startAutoRunFromCurrentSettings continues auto run when contribution content refresh fails', async () => {
|
||||||
@@ -151,10 +154,10 @@ test('startAutoRunFromCurrentSettings continues auto run when contribution conte
|
|||||||
assert.equal(result, true);
|
assert.equal(result, true);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
events.map((entry) => entry.type),
|
events.map((entry) => entry.type),
|
||||||
['refresh', 'warn', 'send']
|
['refresh', 'warn', 'sync-settings', 'send']
|
||||||
);
|
);
|
||||||
assert.match(String(events[1].args[0]), /Failed to refresh contribution content hint before auto run/);
|
assert.match(String(events[1].args[0]), /Failed to refresh contribution content hint before auto run/);
|
||||||
assert.equal(events[2].message.type, 'AUTO_RUN');
|
assert.equal(events[3].message.type, 'AUTO_RUN');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('startAutoRunFromCurrentSettings does not block auto run when contribution content has updates', async () => {
|
test('startAutoRunFromCurrentSettings does not block auto run when contribution content has updates', async () => {
|
||||||
@@ -170,7 +173,7 @@ test('startAutoRunFromCurrentSettings does not block auto run when contribution
|
|||||||
assert.equal(result, true);
|
assert.equal(result, true);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
api.getEvents().map((entry) => entry.type),
|
api.getEvents().map((entry) => entry.type),
|
||||||
['refresh', 'send']
|
['refresh', 'sync-settings', 'send']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,10 +190,10 @@ test('startAutoRunFromCurrentSettings shows Plus risk warning before starting mo
|
|||||||
assert.equal(result, true);
|
assert.equal(result, true);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
events.map((entry) => entry.type),
|
events.map((entry) => entry.type),
|
||||||
['refresh', 'plus-risk-modal', 'send']
|
['refresh', 'sync-settings', 'plus-risk-modal', 'send']
|
||||||
);
|
);
|
||||||
assert.equal(events[1].totalRuns, 4);
|
assert.equal(events[2].totalRuns, 4);
|
||||||
assert.equal(events[2].message.payload.totalRuns, 4);
|
assert.equal(events[3].message.payload.totalRuns, 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('startAutoRunFromCurrentSettings aborts when Plus risk warning is declined', async () => {
|
test('startAutoRunFromCurrentSettings aborts when Plus risk warning is declined', async () => {
|
||||||
@@ -206,7 +209,7 @@ test('startAutoRunFromCurrentSettings aborts when Plus risk warning is declined'
|
|||||||
assert.equal(result, false);
|
assert.equal(result, false);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
api.getEvents().map((entry) => entry.type),
|
api.getEvents().map((entry) => entry.type),
|
||||||
['refresh', 'plus-risk-modal']
|
['refresh', 'sync-settings', 'plus-risk-modal']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,7 +227,58 @@ test('startAutoRunFromCurrentSettings aborts when Plus contribution prompt opens
|
|||||||
assert.equal(result, false);
|
assert.equal(result, false);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
api.getEvents().map((entry) => entry.type),
|
api.getEvents().map((entry) => entry.type),
|
||||||
['refresh', 'plus-contribution-modal']
|
['refresh', 'sync-settings', 'plus-contribution-modal']
|
||||||
);
|
);
|
||||||
assert.equal(api.getEvents()[1].plusModeEnabled, true);
|
assert.equal(api.getEvents()[2].plusModeEnabled, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('persistCurrentSettingsForAction forces a silent save even when settings are not marked dirty', async () => {
|
||||||
|
const bundle = [
|
||||||
|
extractFunction('waitForSettingsSaveIdle'),
|
||||||
|
extractFunction('saveSettings'),
|
||||||
|
extractFunction('persistCurrentSettingsForAction'),
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const api = new Function(`
|
||||||
|
let settingsAutoSaveTimer = 123;
|
||||||
|
let clearedTimer = null;
|
||||||
|
let settingsSaveInFlight = false;
|
||||||
|
let settingsDirty = false;
|
||||||
|
let settingsSaveRevision = 0;
|
||||||
|
const saveCalls = [];
|
||||||
|
function clearTimeout(value) {
|
||||||
|
clearedTimer = value;
|
||||||
|
}
|
||||||
|
function updateSaveButtonState() {}
|
||||||
|
function collectSettingsPayload() {
|
||||||
|
return { luckmailApiKey: 'autofilled-key' };
|
||||||
|
}
|
||||||
|
function syncLatestState() {}
|
||||||
|
function updatePanelModeUI() {}
|
||||||
|
function updateMailProviderUI() {}
|
||||||
|
function updateButtonStates() {}
|
||||||
|
function markSettingsDirty() {}
|
||||||
|
function applySettingsState() {}
|
||||||
|
const chrome = {
|
||||||
|
runtime: {
|
||||||
|
async sendMessage(message) {
|
||||||
|
saveCalls.push(message.payload);
|
||||||
|
return { state: { luckmailApiKey: message.payload.luckmailApiKey } };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
${bundle}
|
||||||
|
return {
|
||||||
|
persistCurrentSettingsForAction,
|
||||||
|
getSnapshot() {
|
||||||
|
return { clearedTimer, saveCalls };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
await api.persistCurrentSettingsForAction();
|
||||||
|
const snapshot = api.getSnapshot();
|
||||||
|
|
||||||
|
assert.equal(snapshot.clearedTimer, 123);
|
||||||
|
assert.deepStrictEqual(snapshot.saveCalls, [{ luckmailApiKey: 'autofilled-key' }]);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user