fix: resolve HeroSMS Plus dev sync conflicts

This commit is contained in:
QLHazyCoder
2026-04-29 20:00:01 +08:00
15 changed files with 861 additions and 318 deletions
@@ -0,0 +1,39 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
test('background step-1 state plumbing persists and resets cpa oauth runtime keys', () => {
const source = fs.readFileSync('background.js', 'utf8');
assert.match(source, /cpaOAuthState:\s*null/);
assert.match(source, /cpaManagementOrigin:\s*null/);
assert.match(source, /payload\.cpaOAuthState[^\n]*updates\.cpaOAuthState/);
assert.match(source, /payload\.cpaManagementOrigin[^\n]*updates\.cpaManagementOrigin/);
assert.match(source, /if \(step <= 1\) \{[\s\S]*cpaOAuthState:\s*null,[\s\S]*cpaManagementOrigin:\s*null,/);
});
test('message router step-1 handler stores cpa oauth runtime keys', async () => {
const source = fs.readFileSync('background/message-router.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
const updates = [];
const router = api.createMessageRouter({
broadcastDataUpdate: () => {},
setState: async (payload) => {
updates.push(payload);
},
});
await router.handleStepData(1, {
cpaOAuthState: 'oauth-state-1',
cpaManagementOrigin: 'http://localhost:8317',
});
assert.deepStrictEqual(updates, [
{
cpaOAuthState: 'oauth-state-1',
cpaManagementOrigin: 'http://localhost:8317',
},
]);
});
@@ -0,0 +1,105 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background/message-router.js', 'utf8');
const globalScope = {};
const api = new Function('self', `${source}; return self.MultiPageBackgroundMessageRouter;`)(globalScope);
test('message router appends success record on Plus final step instead of hard-coded step 10', async () => {
const appendCalls = [];
const router = api.createMessageRouter({
addLog: async () => {},
appendAccountRunRecord: async (...args) => {
appendCalls.push(args);
},
batchUpdateLuckmailPurchases: async () => {},
buildLocalhostCleanupPrefix: () => '',
buildLuckmailSessionSettingsPayload: () => ({}),
buildPersistentSettingsPayload: () => ({}),
broadcastDataUpdate: () => {},
cancelScheduledAutoRun: async () => {},
checkIcloudSession: async () => {},
clearAutoRunTimerAlarm: async () => {},
clearLuckmailRuntimeState: async () => {},
clearStopRequest: () => {},
closeLocalhostCallbackTabs: async () => {},
closeTabsByUrlPrefix: async () => {},
deleteHotmailAccount: async () => {},
deleteHotmailAccounts: async () => {},
deleteIcloudAlias: async () => {},
deleteUsedIcloudAliases: async () => {},
disableUsedLuckmailPurchases: async () => {},
doesStepUseCompletionSignal: () => false,
ensureManualInteractionAllowed: async () => ({}),
executeStep: async () => {},
executeStepViaCompletionSignal: async () => {},
exportSettingsBundle: async () => ({}),
fetchGeneratedEmail: async () => '',
finalizeStep3Completion: async () => {},
finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
findHotmailAccount: async () => null,
flushCommand: async () => {},
getCurrentLuckmailPurchase: () => null,
getPendingAutoRunTimerPlan: () => null,
getSourceLabel: () => '',
getState: async () => ({ plusModeEnabled: true, stepStatuses: { 13: 'pending' } }),
getLastStepIdForState: () => 13,
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'oauth-login' : 'platform-verify' }),
getStepIdsForState: () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
getTabId: async () => null,
getStopRequested: () => false,
handleAutoRunLoopUnhandledError: async () => {},
handleCloudflareSecurityBlocked: async () => '',
importSettingsBundle: async () => {},
invalidateDownstreamAfterStepRestart: async () => {},
isCloudflareSecurityBlockedError: () => false,
isAutoRunLockedState: () => false,
isHotmailProvider: () => false,
isLocalhostOAuthCallbackUrl: () => true,
isLuckmailProvider: () => false,
isStopError: () => false,
isTabAlive: async () => false,
launchAutoRunTimerPlan: async () => {},
listIcloudAliases: async () => [],
listLuckmailPurchasesForManagement: async () => [],
normalizeHotmailAccounts: (items) => items,
normalizeRunCount: (value) => value,
AUTO_RUN_TIMER_KIND_SCHEDULED_START: 'scheduled',
notifyStepComplete: () => {},
notifyStepError: () => {},
patchHotmailAccount: async () => {},
patchMail2925Account: async () => {},
registerTab: async () => {},
requestStop: async () => {},
resetState: async () => {},
resumeAutoRun: async () => {},
scheduleAutoRun: async () => {},
selectLuckmailPurchase: async () => {},
setCurrentHotmailAccount: async () => {},
setCurrentMail2925Account: async () => {},
setContributionMode: async () => {},
setEmailState: async () => {},
setEmailStateSilently: async () => {},
setIcloudAliasPreservedState: async () => {},
setIcloudAliasUsedState: async () => {},
setLuckmailPurchaseDisabledState: async () => {},
setLuckmailPurchasePreservedState: async () => {},
setLuckmailPurchaseUsedState: async () => {},
setPersistentSettings: async () => {},
setState: async () => {},
setStepStatus: async () => {},
skipAutoRunCountdown: async () => false,
skipStep: async () => {},
startAutoRunLoop: async () => {},
syncHotmailAccounts: async () => {},
testHotmailAccountMailAccess: async () => {},
upsertHotmailAccount: async () => {},
verifyHotmailAccount: async () => {},
});
await router.handleMessage({ type: 'STEP_COMPLETE', step: 13, payload: {} }, {});
assert.equal(appendCalls.length, 1);
assert.equal(appendCalls[0][0], 'success');
});
@@ -65,6 +65,7 @@ function createRouter(overrides = {}) {
getState: async () => overrides.state || { stepStatuses: { 3: 'pending' } },
getStepDefinitionForState: overrides.getStepDefinitionForState,
getStepIdsForState: overrides.getStepIdsForState,
getLastStepIdForState: overrides.getLastStepIdForState,
getTabId: overrides.getTabId || (async () => null),
getStopRequested: () => false,
handleAutoRunLoopUnhandledError: async () => {},
@@ -71,3 +71,48 @@ test('panel bridge can request codex2api oauth url via protocol', async () => {
globalThis.fetch = originalFetch;
}
});
test('panel bridge can request cpa oauth url via management api', async () => {
const source = fs.readFileSync('background/panel-bridge.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:8317/v0/management/codex-auth-url');
assert.equal(options.method, 'GET');
assert.equal(options.headers.Authorization, 'Bearer cpa-key');
assert.equal(options.headers['X-Management-Key'], 'cpa-key');
return {
ok: true,
json: async () => ({
status: 'ok',
url: 'https://auth.openai.com/authorize?state=cpa-oauth-state',
state: 'cpa-oauth-state',
}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundPanelBridge;`)({});
const bridge = api.createPanelBridge({
addLog: async () => {},
getPanelMode: () => 'cpa',
normalizeCodex2ApiUrl: (value) => value,
normalizeSub2ApiUrl: (value) => value,
DEFAULT_SUB2API_GROUP_NAME: 'codex',
SUB2API_STEP1_RESPONSE_TIMEOUT_MS: 90000,
});
const result = await bridge.requestOAuthUrlFromPanel({
panelMode: 'cpa',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
}, { logLabel: '步骤 7' });
assert.deepStrictEqual(result, {
oauthUrl: 'https://auth.openai.com/authorize?state=cpa-oauth-state',
cpaOAuthState: 'cpa-oauth-state',
cpaManagementOrigin: 'http://localhost:8317',
});
} finally {
globalThis.fetch = originalFetch;
}
});
@@ -0,0 +1,239 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const test = require('node:test');
function createDeps(overrides = {}) {
const logs = [];
const completed = [];
const uiCalls = [];
const deps = {
addLog: async (message, level = 'info') => {
logs.push({ message, level });
},
chrome: {
tabs: {
update: async () => {},
},
},
closeConflictingTabsForSource: async () => {},
completeStepFromBackground: async (step, payload) => {
completed.push({ step, payload });
},
ensureContentScriptReadyOnTab: async () => {},
getPanelMode: () => 'cpa',
getTabId: async () => 0,
isLocalhostOAuthCallbackUrl: (value) => String(value || '').includes('/auth/callback?code='),
isTabAlive: async () => false,
normalizeCodex2ApiUrl: (value) => value,
normalizeSub2ApiUrl: (value) => value,
rememberSourceLastUrl: async () => {},
reuseOrCreateTab: async () => 91,
sendToContentScript: async () => ({}),
sendToContentScriptResilient: async (source, message, options) => {
uiCalls.push({ source, message, options });
return {};
},
shouldBypassStep9ForLocalCpa: () => false,
SUB2API_STEP9_RESPONSE_TIMEOUT_MS: 120000,
...overrides,
};
return { deps, logs, completed, uiCalls };
}
test('platform verify module submits CPA callback via management API first', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let uiCalled = false;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:8317/v0/management/oauth-callback');
assert.equal(options.method, 'POST');
assert.equal(options.headers.Authorization, 'Bearer cpa-key');
assert.equal(options.headers['X-Management-Key'], 'cpa-key');
assert.deepStrictEqual(JSON.parse(options.body), {
provider: 'codex',
redirect_url: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
});
return {
ok: true,
json: async () => ({
message: 'CPA API 回调提交成功',
}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, logs, completed } = createDeps({
sendToContentScriptResilient: async () => {
uiCalled = true;
return {};
},
});
const executor = api.createStep10Executor(deps);
await executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
});
assert.equal(uiCalled, false);
assert.deepStrictEqual(completed, [
{
step: 10,
payload: {
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
verifiedStatus: 'CPA API 回调提交成功',
},
},
]);
assert.deepStrictEqual(logs, [
{ message: '步骤 10:正在通过 CPA 管理接口提交回调地址...', level: 'info' },
{ message: '步骤 10CPA API 回调提交成功', level: 'ok' },
]);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module prefers cpaManagementOrigin when provided', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
assert.equal(url, 'http://localhost:9999/v0/management/oauth-callback');
assert.equal(options.method, 'POST');
assert.equal(options.headers.Authorization, 'Bearer cpa-key');
assert.equal(options.headers['X-Management-Key'], 'cpa-key');
return {
ok: true,
json: async () => ({
message: 'CPA API 回调提交成功',
}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, completed } = createDeps();
const executor = api.createStep10Executor(deps);
await executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
cpaManagementOrigin: 'http://localhost:9999',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
});
assert.equal(completed.length, 1);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module fails fast when CPA API submit fails', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => ({
ok: false,
status: 500,
json: async () => ({ message: 'failed to persist oauth callback' }),
});
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, logs, completed, uiCalls } = createDeps();
const executor = api.createStep10Executor(deps);
await assert.rejects(
() => executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
}),
/failed to persist oauth callback/
);
assert.equal(uiCalls.length, 0);
assert.equal(completed.length, 0);
assert.equal(logs[0].message, '步骤 10:正在通过 CPA 管理接口提交回调地址...');
assert.match(logs[1].message, /步骤 10CPA 接口提交失败:failed to persist oauth callback/);
assert.equal(logs[1].level, 'error');
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module requires management key for CPA API-only flow', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
fetchCalled = true;
return {
ok: true,
json: async () => ({}),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps, logs, completed, uiCalls } = createDeps();
const executor = api.createStep10Executor(deps);
await assert.rejects(
() => executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=oauth-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: ' ',
}),
/尚未配置 CPA 管理密钥/
);
assert.equal(fetchCalled, false);
assert.equal(uiCalls.length, 0);
assert.equal(completed.length, 0);
assert.equal(logs.length, 0);
} finally {
globalThis.fetch = originalFetch;
}
});
test('platform verify module rejects callback when cpa oauth state mismatches', async () => {
const source = fs.readFileSync('background/steps/platform-verify.js', 'utf8');
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
fetchCalled = true;
return {
ok: true,
json: async () => ({ message: 'should not happen' }),
};
};
try {
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep10;`)({});
const { deps } = createDeps();
const executor = api.createStep10Executor(deps);
await assert.rejects(
() => executor.executeStep10({
panelMode: 'cpa',
localhostUrl: 'http://localhost:1455/auth/callback?code=callback-code&state=callback-state',
cpaOAuthState: 'expected-state',
vpsUrl: 'http://localhost:8317/admin/oauth',
vpsPassword: 'cpa-key',
}),
/CPA 回调 state 与当前授权会话不匹配/
);
assert.equal(fetchCalled, false);
} finally {
globalThis.fetch = originalFetch;
}
});