diff --git a/background/kiro/publisher-kiro-rs.js b/background/kiro/publisher-kiro-rs.js index 66557dd..7cd7724 100644 --- a/background/kiro/publisher-kiro-rs.js +++ b/background/kiro/publisher-kiro-rs.js @@ -284,13 +284,6 @@ await addLog(message, level, nodeId ? { nodeId } : {}); } - async function getExecutionState(state = {}) { - if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) { - return state; - } - return getState(); - } - async function applyRuntimeState(currentState = {}, patch = {}) { const nextPatch = mergeRuntimePatch(currentState, patch); await setState(nextPatch); @@ -313,7 +306,7 @@ async function executeKiroUploadCredential(state = {}) { const nodeId = String(state?.nodeId || 'kiro-upload-credential').trim(); - const currentState = await getExecutionState(state); + const currentState = await getState(); try { const targetId = resolveKiroTargetId(currentState); const targetConfig = resolveKiroTargetConfig(currentState, targetId); diff --git a/background/message-router.js b/background/message-router.js index f5da644..c45f0b9 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -1430,21 +1430,21 @@ || phoneSignupReloginAfterBindEmailChanged; const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled') && updates.oauthFlowTimeoutEnabled === false; - await setPersistentSettings(updates); + const canonicalSettingsUpdates = await setPersistentSettings(updates); const stateUpdates = { - ...updates, + ...canonicalSettingsUpdates, ...sessionUpdates, ...(oauthFlowTimeoutDisabled ? { oauthFlowDeadlineAt: null, oauthFlowDeadlineSourceUrl: null, } : {}), }; - if (Object.prototype.hasOwnProperty.call(updates, 'activeFlowId') + if (Object.prototype.hasOwnProperty.call(canonicalSettingsUpdates, 'activeFlowId') && !Object.prototype.hasOwnProperty.call(stateUpdates, 'flowId')) { - stateUpdates.flowId = updates.activeFlowId; + stateUpdates.flowId = canonicalSettingsUpdates.activeFlowId; } - if (Object.prototype.hasOwnProperty.call(updates, 'icloudHostPreference')) { - const nextHostPreference = String(updates.icloudHostPreference || '').trim().toLowerCase(); + if (Object.prototype.hasOwnProperty.call(canonicalSettingsUpdates, 'icloudHostPreference')) { + const nextHostPreference = String(canonicalSettingsUpdates.icloudHostPreference || '').trim().toLowerCase(); stateUpdates.preferredIcloudHost = nextHostPreference === 'icloud.com' || nextHostPreference === 'icloud.com.cn' ? nextHostPreference : ''; diff --git a/tests/background-kiro-publisher-kiro-rs-module.test.js b/tests/background-kiro-publisher-kiro-rs-module.test.js index 25600bb..da6b137 100644 --- a/tests/background-kiro-publisher-kiro-rs-module.test.js +++ b/tests/background-kiro-publisher-kiro-rs-module.test.js @@ -64,3 +64,101 @@ test('kiro publisher builds kiro.rs payload from desktop auth runtime without pr assert.match(machineId, /^[0-9a-f]{64}$/); assert.equal(Object.prototype.hasOwnProperty.call(payload, 'profileArn'), false); }); + +test('kiro publisher reads latest kiro.rs key from background state instead of stale node snapshot', async () => { + const api = loadPublisherApi(); + const requests = []; + let liveState = { + kiroTargetId: 'kiro-rs', + kiroRsUrl: 'https://kiro.example.com/admin', + kiroRsKey: 'live-key', + email: 'aws-user@example.com', + kiroRuntime: { + register: { + email: 'aws-user@example.com', + }, + desktopAuth: { + region: 'us-east-1', + clientId: 'client-001', + clientSecret: 'secret-001', + refreshToken: 'refresh-token-001', + }, + upload: { + targetId: 'kiro-rs', + }, + }, + settingsState: { + flows: { + kiro: { + targetId: 'kiro-rs', + targets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: 'live-key', + }, + }, + }, + }, + }, + }; + const completed = []; + const publisher = api.createKiroRsPublisher({ + addLog: async () => {}, + completeNodeFromBackground: async (nodeId, payload) => { + completed.push({ nodeId, payload }); + }, + fetchImpl: async (url, options = {}) => { + requests.push({ + url, + method: options.method || 'GET', + apiKey: options.headers?.['x-api-key'], + }); + if ((options.method || 'GET') === 'GET') { + return { + ok: true, + status: 200, + statusText: 'OK', + text: async () => JSON.stringify({ items: [] }), + }; + } + return { + ok: true, + status: 200, + statusText: 'OK', + text: async () => JSON.stringify({ + message: 'Credential uploaded.', + credentialId: 9, + email: 'aws-user@example.com', + }), + }; + }, + getState: async () => ({ ...liveState }), + setState: async (updates = {}) => { + liveState = { ...liveState, ...updates }; + }, + }); + + await publisher.executeKiroUploadCredential({ + nodeId: 'kiro-upload-credential', + kiroRsKey: '', + settingsState: { + flows: { + kiro: { + targetId: 'kiro-rs', + targets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: '', + }, + }, + }, + }, + }, + }); + + assert.equal(requests.length, 2); + assert.equal(requests[0].apiKey, 'live-key'); + assert.equal(requests[1].apiKey, 'live-key'); + assert.equal(completed.length, 1); + assert.equal(completed[0].nodeId, 'kiro-upload-credential'); +}); diff --git a/tests/background-message-router-module.test.js b/tests/background-message-router-module.test.js index c2c8e40..bb3b94a 100644 --- a/tests/background-message-router-module.test.js +++ b/tests/background-message-router-module.test.js @@ -120,7 +120,7 @@ test('SAVE_SETTING broadcasts free phone reuse setting updates for realtime side }, broadcastDataUpdate: (payload) => broadcasts.push(payload), getState: async () => ({ ...state }), - setPersistentSettings: async () => {}, + setPersistentSettings: async (updates) => ({ ...updates }), setState: async (updates) => { state = { ...state, ...updates }; }, @@ -181,6 +181,7 @@ test('SAVE_SETTING preserves phone reuse preferences while phone signup is selec getState: async () => ({ ...state }), setPersistentSettings: async (updates) => { persistedPayloads.push({ ...updates }); + return { ...updates }; }, setState: async (updates) => { state = { ...state, ...updates }; @@ -239,7 +240,7 @@ test('SAVE_SETTING allows phone reuse preferences after switching back to email }), broadcastDataUpdate: () => {}, getState: async () => ({ ...state }), - setPersistentSettings: async () => {}, + setPersistentSettings: async (updates) => ({ ...updates }), setState: async (updates) => { state = { ...state, ...updates }; }, @@ -280,7 +281,7 @@ test('SAVE_SETTING broadcasts operation delay setting without background success : {}, broadcastDataUpdate: (payload) => broadcasts.push(payload), getState: async () => ({ ...state }), - setPersistentSettings: async () => {}, + setPersistentSettings: async (updates) => ({ ...updates }), setState: async (updates) => { state = { ...state, ...updates }; }, }); @@ -311,7 +312,7 @@ test('SAVE_SETTING mirrors activeFlowId into flowId when switching to kiro flow' : {}, broadcastDataUpdate: (payload) => broadcasts.push(payload), getState: async () => ({ ...state }), - setPersistentSettings: async () => {}, + setPersistentSettings: async (updates) => ({ ...updates }), setState: async (updates) => { state = { ...state, ...updates }; }, }); @@ -331,6 +332,123 @@ test('SAVE_SETTING mirrors activeFlowId into flowId when switching to kiro flow' }); }); +test('SAVE_SETTING syncs canonical kiro settingsState back into session state', async () => { + const source = fs.readFileSync('background/message-router.js', 'utf8'); + const globalScope = { console }; + const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope); + const canonicalSettingsState = { + schemaVersion: 4, + activeFlowId: 'kiro', + services: { + account: { customPassword: '' }, + email: { provider: 'duck' }, + proxy: { enabled: false, provider: '711proxy', mode: 'account' }, + }, + flows: { + openai: { + integrationTargetId: 'cpa', + integrationTargets: { + cpa: { vpsUrl: '', vpsPassword: '', localCpaStep9Mode: 'submit' }, + sub2api: { + sub2apiUrl: '', + sub2apiEmail: '', + sub2apiPassword: '', + sub2apiGroupName: 'codex', + sub2apiGroupNames: ['codex', 'openai-plus'], + sub2apiAccountPriority: 1, + sub2apiDefaultProxyName: '', + }, + codex2api: { codex2apiUrl: '', codex2apiAdminKey: '' }, + }, + signup: { + signupMethod: 'email', + phoneVerificationEnabled: false, + phoneSignupReloginAfterBindEmailEnabled: false, + }, + plus: { + plusModeEnabled: false, + plusPaymentMethod: 'paypal', + }, + autoRun: { + stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 }, + }, + }, + kiro: { + targetId: 'kiro-rs', + targets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: 'live-key', + }, + }, + autoRun: { + stepExecutionRange: { enabled: false, fromStep: 1, toStep: 9 }, + }, + }, + }, + }; + let state = { + activeFlowId: 'kiro', + flowId: 'kiro', + kiroTargetId: 'kiro-rs', + kiroRsUrl: 'https://kiro.example.com/admin', + kiroRsKey: '', + settingsSchemaVersion: 4, + settingsState: { + ...canonicalSettingsState, + flows: { + ...canonicalSettingsState.flows, + kiro: { + ...canonicalSettingsState.flows.kiro, + targets: { + 'kiro-rs': { + baseUrl: 'https://kiro.example.com/admin', + apiKey: '', + }, + }, + }, + }, + }, + plusModeEnabled: false, + plusPaymentMethod: 'paypal', + }; + + const router = api.createMessageRouter({ + addLog: async () => {}, + buildLuckmailSessionSettingsPayload: () => ({}), + buildPersistentSettingsPayload: (input = {}) => ({ + activeFlowId: String(input.activeFlowId || 'kiro'), + kiroRsKey: String(input.kiroRsKey || ''), + }), + broadcastDataUpdate: () => {}, + getState: async () => ({ ...state }), + setPersistentSettings: async () => ({ + activeFlowId: 'kiro', + flowId: 'kiro', + kiroTargetId: 'kiro-rs', + kiroRsUrl: 'https://kiro.example.com/admin', + kiroRsKey: 'live-key', + settingsSchemaVersion: 4, + settingsState: canonicalSettingsState, + }), + setState: async (updates) => { + state = { ...state, ...updates }; + }, + }); + + const response = await router.handleMessage({ + type: 'SAVE_SETTING', + payload: { + activeFlowId: 'kiro', + kiroRsKey: 'live-key', + }, + }); + + assert.equal(response.ok, true); + assert.equal(state.kiroRsKey, 'live-key'); + assert.equal(state.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'live-key'); +}); + test('AUTO_RUN applies current flow selection from payload before starting loop', async () => { const source = fs.readFileSync('background/message-router.js', 'utf8'); const globalScope = { console }; @@ -435,7 +553,7 @@ test('SAVE_SETTING re-resolves signup method when panel mode changes', async () broadcastDataUpdate: () => {}, getState: async () => ({ ...state }), resolveSignupMethod: (nextState = {}) => nextState.panelMode === 'cpa' ? 'email' : 'phone', - setPersistentSettings: async () => {}, + setPersistentSettings: async (updates) => ({ ...updates }), setState: async (updates) => { state = { ...state, ...updates }; }, @@ -479,6 +597,7 @@ test('SAVE_SETTING applies shared mode-switch normalization before persisting in ), setPersistentSettings: async (updates) => { persistedPayloads.push({ ...updates }); + return { ...updates }; }, setState: async (updates) => { state = { ...state, ...updates };