test: document IP proxy integration and harden sidepanel restore
This commit is contained in:
+69
-27
@@ -2746,6 +2746,44 @@ function applySettingsState(state) {
|
||||
if (typeof syncStepDefinitionsForMode === 'function') {
|
||||
syncStepDefinitionsForMode(Boolean(state?.plusModeEnabled));
|
||||
}
|
||||
const fallbackIpProxyService = '711proxy';
|
||||
const fallbackIpProxyMode = 'account';
|
||||
const fallbackIpProxyProtocol = 'http';
|
||||
const resolveIpProxyService = (value) => (typeof normalizeIpProxyService === 'function'
|
||||
? normalizeIpProxyService(value)
|
||||
: String(value || fallbackIpProxyService).trim().toLowerCase() || fallbackIpProxyService);
|
||||
const resolveIpProxyMode = (value) => {
|
||||
if (typeof normalizeIpProxyModeForCurrentRelease === 'function') {
|
||||
return normalizeIpProxyModeForCurrentRelease(value);
|
||||
}
|
||||
if (typeof normalizeIpProxyMode === 'function') {
|
||||
return normalizeIpProxyMode(value);
|
||||
}
|
||||
const normalized = String(value || fallbackIpProxyMode).trim().toLowerCase();
|
||||
return normalized || fallbackIpProxyMode;
|
||||
};
|
||||
const resolveIpProxyProtocol = (value) => (typeof normalizeIpProxyProtocol === 'function'
|
||||
? normalizeIpProxyProtocol(value)
|
||||
: String(value || fallbackIpProxyProtocol).trim().toLowerCase() || fallbackIpProxyProtocol);
|
||||
const resolveIpProxyPort = (value) => {
|
||||
if (typeof normalizeIpProxyPort === 'function') {
|
||||
return normalizeIpProxyPort(value);
|
||||
}
|
||||
const numeric = Number.parseInt(String(value || '').trim(), 10);
|
||||
return Number.isInteger(numeric) && numeric > 0 && numeric <= 65535 ? numeric : 0;
|
||||
};
|
||||
const resolveIpProxyAccountList = (value) => (typeof normalizeIpProxyAccountList === 'function'
|
||||
? normalizeIpProxyAccountList(value || '')
|
||||
: String(value || '').replace(/\r/g, '').trim());
|
||||
const resolveIpProxySessionPrefix = (value) => (typeof normalizeIpProxyAccountSessionPrefix === 'function'
|
||||
? normalizeIpProxyAccountSessionPrefix(value || '')
|
||||
: String(value || '').trim());
|
||||
const resolveIpProxyAccountLifeMinutes = (value) => (typeof normalizeIpProxyAccountLifeMinutes === 'function'
|
||||
? normalizeIpProxyAccountLifeMinutes(value || '')
|
||||
: String(value || '').trim());
|
||||
const resolveIpProxyPoolTargetCount = (value) => (typeof normalizeIpProxyPoolTargetCount === 'function'
|
||||
? normalizeIpProxyPoolTargetCount(value || '', 20)
|
||||
: String(value || '20').trim() || '20');
|
||||
syncLatestState(state);
|
||||
syncAutoRunState(state);
|
||||
renderStepStatuses(latestState);
|
||||
@@ -2770,7 +2808,7 @@ function applySettingsState(state) {
|
||||
inputSub2ApiPassword.value = state?.sub2apiPassword || '';
|
||||
inputSub2ApiGroup.value = state?.sub2apiGroupName || '';
|
||||
inputSub2ApiDefaultProxy.value = state?.sub2apiDefaultProxyName || '';
|
||||
const normalizedIpProxyService = normalizeIpProxyService(state?.ipProxyService);
|
||||
const normalizedIpProxyService = resolveIpProxyService(state?.ipProxyService);
|
||||
const normalizedIpProxyServiceProfiles = typeof normalizeIpProxyServiceProfiles === 'function'
|
||||
? normalizeIpProxyServiceProfiles(state?.ipProxyServiceProfiles || {}, state || {})
|
||||
: (state?.ipProxyServiceProfiles || {});
|
||||
@@ -2781,60 +2819,62 @@ function applySettingsState(state) {
|
||||
ipProxyServiceProfiles: normalizedIpProxyServiceProfiles,
|
||||
})
|
||||
: {
|
||||
mode: typeof normalizeIpProxyModeForCurrentRelease === 'function'
|
||||
? normalizeIpProxyModeForCurrentRelease(state?.ipProxyMode)
|
||||
: normalizeIpProxyMode(state?.ipProxyMode),
|
||||
mode: resolveIpProxyMode(state?.ipProxyMode),
|
||||
apiUrl: String(state?.ipProxyApiUrl || '').trim(),
|
||||
accountList: normalizeIpProxyAccountList(state?.ipProxyAccountList || ''),
|
||||
accountSessionPrefix: normalizeIpProxyAccountSessionPrefix(state?.ipProxyAccountSessionPrefix || ''),
|
||||
accountLifeMinutes: normalizeIpProxyAccountLifeMinutes(state?.ipProxyAccountLifeMinutes || ''),
|
||||
poolTargetCount: normalizeIpProxyPoolTargetCount(state?.ipProxyPoolTargetCount || '', 20),
|
||||
accountList: resolveIpProxyAccountList(state?.ipProxyAccountList || ''),
|
||||
accountSessionPrefix: resolveIpProxySessionPrefix(state?.ipProxyAccountSessionPrefix || ''),
|
||||
accountLifeMinutes: resolveIpProxyAccountLifeMinutes(state?.ipProxyAccountLifeMinutes || ''),
|
||||
poolTargetCount: resolveIpProxyPoolTargetCount(state?.ipProxyPoolTargetCount || ''),
|
||||
host: String(state?.ipProxyHost || '').trim(),
|
||||
port: String(normalizeIpProxyPort(state?.ipProxyPort || '') || ''),
|
||||
protocol: normalizeIpProxyProtocol(state?.ipProxyProtocol),
|
||||
port: String(resolveIpProxyPort(state?.ipProxyPort || '') || ''),
|
||||
protocol: resolveIpProxyProtocol(state?.ipProxyProtocol),
|
||||
username: String(state?.ipProxyUsername || '').trim(),
|
||||
password: String(state?.ipProxyPassword || ''),
|
||||
region: String(state?.ipProxyRegion || '').trim(),
|
||||
};
|
||||
if (selectIpProxyService) {
|
||||
if (typeof selectIpProxyService !== 'undefined' && selectIpProxyService) {
|
||||
selectIpProxyService.value = normalizedIpProxyService;
|
||||
}
|
||||
if (inputIpProxyApiUrl) {
|
||||
if (typeof inputIpProxyApiUrl !== 'undefined' && inputIpProxyApiUrl) {
|
||||
inputIpProxyApiUrl.value = String(activeIpProxyProfile.apiUrl || '').trim();
|
||||
}
|
||||
if (inputIpProxyAccountList) {
|
||||
if (typeof inputIpProxyAccountList !== 'undefined' && inputIpProxyAccountList) {
|
||||
inputIpProxyAccountList.value = activeIpProxyProfile.accountList;
|
||||
}
|
||||
if (inputIpProxyAccountSessionPrefix) {
|
||||
if (typeof inputIpProxyAccountSessionPrefix !== 'undefined' && inputIpProxyAccountSessionPrefix) {
|
||||
inputIpProxyAccountSessionPrefix.value = activeIpProxyProfile.accountSessionPrefix;
|
||||
}
|
||||
if (inputIpProxyAccountLifeMinutes) {
|
||||
if (typeof inputIpProxyAccountLifeMinutes !== 'undefined' && inputIpProxyAccountLifeMinutes) {
|
||||
inputIpProxyAccountLifeMinutes.value = activeIpProxyProfile.accountLifeMinutes;
|
||||
}
|
||||
if (inputIpProxyPoolTargetCount) {
|
||||
if (typeof inputIpProxyPoolTargetCount !== 'undefined' && inputIpProxyPoolTargetCount) {
|
||||
inputIpProxyPoolTargetCount.value = activeIpProxyProfile.poolTargetCount;
|
||||
}
|
||||
if (inputIpProxyHost) {
|
||||
if (typeof inputIpProxyHost !== 'undefined' && inputIpProxyHost) {
|
||||
inputIpProxyHost.value = activeIpProxyProfile.host;
|
||||
}
|
||||
if (inputIpProxyPort) {
|
||||
const normalizedPort = normalizeIpProxyPort(activeIpProxyProfile.port || '');
|
||||
if (typeof inputIpProxyPort !== 'undefined' && inputIpProxyPort) {
|
||||
const normalizedPort = resolveIpProxyPort(activeIpProxyProfile.port || '');
|
||||
inputIpProxyPort.value = normalizedPort > 0 ? String(normalizedPort) : '';
|
||||
}
|
||||
if (selectIpProxyProtocol) {
|
||||
selectIpProxyProtocol.value = normalizeIpProxyProtocol(activeIpProxyProfile.protocol);
|
||||
if (typeof selectIpProxyProtocol !== 'undefined' && selectIpProxyProtocol) {
|
||||
selectIpProxyProtocol.value = resolveIpProxyProtocol(activeIpProxyProfile.protocol);
|
||||
}
|
||||
if (inputIpProxyUsername) {
|
||||
if (typeof inputIpProxyUsername !== 'undefined' && inputIpProxyUsername) {
|
||||
inputIpProxyUsername.value = activeIpProxyProfile.username;
|
||||
}
|
||||
if (inputIpProxyPassword) {
|
||||
if (typeof inputIpProxyPassword !== 'undefined' && inputIpProxyPassword) {
|
||||
inputIpProxyPassword.value = activeIpProxyProfile.password;
|
||||
}
|
||||
if (inputIpProxyRegion) {
|
||||
if (typeof inputIpProxyRegion !== 'undefined' && inputIpProxyRegion) {
|
||||
inputIpProxyRegion.value = activeIpProxyProfile.region;
|
||||
}
|
||||
setIpProxyMode(activeIpProxyProfile.mode);
|
||||
setIpProxyEnabled(Boolean(state?.ipProxyEnabled));
|
||||
if (typeof setIpProxyMode === 'function') {
|
||||
setIpProxyMode(activeIpProxyProfile.mode);
|
||||
}
|
||||
if (typeof setIpProxyEnabled === 'function') {
|
||||
setIpProxyEnabled(Boolean(state?.ipProxyEnabled));
|
||||
}
|
||||
syncLatestState({
|
||||
ipProxyService: normalizedIpProxyService,
|
||||
ipProxyServiceProfiles: normalizedIpProxyServiceProfiles,
|
||||
@@ -2842,7 +2882,9 @@ function applySettingsState(state) {
|
||||
? buildIpProxyStatePatchFromServiceProfile(normalizedIpProxyService, activeIpProxyProfile)
|
||||
: {}),
|
||||
});
|
||||
updateIpProxyUI(latestState);
|
||||
if (typeof updateIpProxyUI === 'function') {
|
||||
updateIpProxyUI(latestState);
|
||||
}
|
||||
inputCodex2ApiUrl.value = state?.codex2apiUrl || '';
|
||||
inputCodex2ApiAdminKey.value = state?.codex2apiAdminKey || '';
|
||||
const restoredMailProvider = isCustomMailProvider(state?.mailProvider)
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
function loadIpProxyCore({ accountListEnabled = true } = {}) {
|
||||
const providerSource = fs.readFileSync('background/ip-proxy-provider-711proxy.js', 'utf8');
|
||||
const coreSource = fs.readFileSync('background/ip-proxy-core.js', 'utf8');
|
||||
return new Function(`
|
||||
const self = {};
|
||||
const chrome = {};
|
||||
const DEFAULT_IP_PROXY_SERVICE = '711proxy';
|
||||
const IP_PROXY_SERVICE_VALUES = ['711proxy', 'lumiproxy', 'iproyal', 'omegaproxy'];
|
||||
const IP_PROXY_ENABLED_SERVICE_VALUES = ['711proxy'];
|
||||
const DEFAULT_IP_PROXY_MODE = 'account';
|
||||
const IP_PROXY_MODE_VALUES = ['api', 'account'];
|
||||
const DEFAULT_IP_PROXY_PROTOCOL = 'http';
|
||||
const IP_PROXY_PROTOCOL_VALUES = ['http', 'https', 'socks4', 'socks5'];
|
||||
const IP_PROXY_FETCH_TIMEOUT_MS = 20000;
|
||||
const IP_PROXY_SETTINGS_SCOPE = 'regular';
|
||||
const IP_PROXY_BYPASS_LIST = ['<local>', 'localhost', '127.0.0.1'];
|
||||
const IP_PROXY_ROUTE_ALL_TRAFFIC = true;
|
||||
const IP_PROXY_ACCOUNT_LIST_ENABLED = ${accountListEnabled ? 'true' : 'false'};
|
||||
const IP_PROXY_TARGET_HOST_PATTERNS = [
|
||||
'openai.com',
|
||||
'*.openai.com',
|
||||
'chatgpt.com',
|
||||
'*.chatgpt.com',
|
||||
];
|
||||
${providerSource}
|
||||
const transformIpProxyAccountEntryByProvider = self.transformIpProxyAccountEntryByProvider;
|
||||
${coreSource}
|
||||
return {
|
||||
buildIpProxyPacScript,
|
||||
getAccountModeProxyPoolFromState,
|
||||
normalizeIpProxyAccountList,
|
||||
normalizeProxyPoolEntries,
|
||||
parseIpProxyLine,
|
||||
resolveIpProxyAutoSwitchThreshold,
|
||||
};
|
||||
`)();
|
||||
}
|
||||
|
||||
test('IP proxy parser ignores disabled lines and normalizes proxy entries', () => {
|
||||
const api = loadIpProxyCore();
|
||||
|
||||
assert.equal(
|
||||
api.normalizeIpProxyAccountList([
|
||||
'# disabled',
|
||||
' // disabled',
|
||||
'; disabled',
|
||||
'global.rotgb.711proxy.com:10000:user:pass',
|
||||
'',
|
||||
].join('\n')),
|
||||
'global.rotgb.711proxy.com:10000:user:pass'
|
||||
);
|
||||
|
||||
const pool = api.normalizeProxyPoolEntries([
|
||||
'http://global.rotgb.711proxy.com:10000:user:pa:ss',
|
||||
'http://global.rotgb.711proxy.com:10000:user:pa:ss',
|
||||
{ host: 'us.proxy.example', port: '8080', username: 'u2', password: 'p2' },
|
||||
]);
|
||||
|
||||
assert.equal(pool.length, 2);
|
||||
assert.deepStrictEqual(pool[0], {
|
||||
host: 'global.rotgb.711proxy.com',
|
||||
port: 10000,
|
||||
username: 'user',
|
||||
password: 'pa:ss',
|
||||
protocol: 'http',
|
||||
region: '',
|
||||
provider: '711proxy',
|
||||
});
|
||||
assert.equal(pool[1].host, 'us.proxy.example');
|
||||
assert.equal(pool[1].port, 8080);
|
||||
});
|
||||
|
||||
test('711 fixed-account mode applies region and sticky session parameters', () => {
|
||||
const api = loadIpProxyCore();
|
||||
const pool = api.getAccountModeProxyPoolFromState({
|
||||
ipProxyService: '711proxy',
|
||||
ipProxyMode: 'account',
|
||||
ipProxyHost: 'global.rotgb.711proxy.com',
|
||||
ipProxyPort: '10000',
|
||||
ipProxyProtocol: 'http',
|
||||
ipProxyUsername: 'USER047152-zone-custom',
|
||||
ipProxyPassword: 'secret',
|
||||
ipProxyRegion: 'US',
|
||||
ipProxyAccountSessionPrefix: 'sticky_001',
|
||||
ipProxyAccountLifeMinutes: '30',
|
||||
});
|
||||
|
||||
assert.equal(pool.length, 1);
|
||||
assert.equal(pool[0].host, 'global.rotgb.711proxy.com');
|
||||
assert.equal(pool[0].port, 10000);
|
||||
assert.equal(pool[0].region, 'US');
|
||||
assert.match(pool[0].username, /region-US/);
|
||||
assert.match(pool[0].username, /session-sticky_001/);
|
||||
assert.match(pool[0].username, /sessTime-30/);
|
||||
});
|
||||
|
||||
test('IP proxy PAC keeps local traffic direct and routes target traffic through proxy', () => {
|
||||
const api = loadIpProxyCore();
|
||||
const pac = api.buildIpProxyPacScript({
|
||||
host: 'global.rotgb.711proxy.com',
|
||||
port: 10000,
|
||||
protocol: 'http',
|
||||
});
|
||||
|
||||
assert.match(pac, /FindProxyForURL/);
|
||||
assert.match(pac, /localhost/);
|
||||
assert.match(pac, /PROXY global\.rotgb\.711proxy\.com:10000/);
|
||||
assert.match(pac, /chatgpt\.com/);
|
||||
assert.match(pac, /openai\.com/);
|
||||
});
|
||||
|
||||
test('sidepanel loads IP proxy scripts before sidepanel bootstrap', () => {
|
||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||
const providerIndex = html.indexOf('<script src="ip-proxy-provider-711proxy.js"></script>');
|
||||
const panelIndex = html.indexOf('<script src="ip-proxy-panel.js"></script>');
|
||||
const sidepanelIndex = html.indexOf('<script src="sidepanel.js"></script>');
|
||||
|
||||
assert.notEqual(providerIndex, -1);
|
||||
assert.notEqual(panelIndex, -1);
|
||||
assert.notEqual(sidepanelIndex, -1);
|
||||
assert.ok(providerIndex < panelIndex);
|
||||
assert.ok(panelIndex < sidepanelIndex);
|
||||
});
|
||||
|
||||
test('IP proxy auto-switch threshold is clamped to the supported range', () => {
|
||||
const api = loadIpProxyCore();
|
||||
|
||||
assert.equal(api.resolveIpProxyAutoSwitchThreshold({ ipProxyPoolTargetCount: '0' }), 1);
|
||||
assert.equal(api.resolveIpProxyAutoSwitchThreshold({ ipProxyPoolTargetCount: '25' }), 25);
|
||||
assert.equal(api.resolveIpProxyAutoSwitchThreshold({ ipProxyPoolTargetCount: '9999' }), 500);
|
||||
});
|
||||
+42
-1
@@ -145,6 +145,7 @@
|
||||
- localhost 回调地址
|
||||
- 自动运行轮次信息
|
||||
- 当前自动运行 session 标识 `autoRunSessionId`
|
||||
- IP 代理运行态:`ipProxyApiPool / ipProxyAccountPool / ipProxyCurrent / ipProxyApplied*`,用于记录当前代理池、当前代理节点、PAC 接管结果、出口检测结果和诊断信息
|
||||
- 标签注册表
|
||||
- 最近打开的来源地址
|
||||
- LuckMail 当前运行时选择
|
||||
@@ -160,6 +161,7 @@
|
||||
|
||||
- CPA / SUB2API 配置
|
||||
- Codex2API 配置
|
||||
- IP 代理持久配置:`ipProxyEnabled`、服务商、模式、API 地址、服务商配置快照、账号列表、固定 Host / Port / Protocol / Username / Password、地区参数、session 与自动切换阈值
|
||||
- Plus 模式开关 `plusModeEnabled`
|
||||
- PayPal 登录配置 `paypalEmail / paypalPassword`
|
||||
- 邮箱 provider 配置
|
||||
@@ -195,6 +197,8 @@
|
||||
- `ICLOUD_LOGIN_REQUIRED`
|
||||
- `ICLOUD_ALIASES_CHANGED`
|
||||
|
||||
IP 代理模块在同步、切换、Change、出口探测和自动运行成功阈值切换后,会通过 `DATA_UPDATED` 广播 `ipProxyApplied*` 与当前代理池字段,sidepanel 只根据广播更新状态卡,不在 UI 层重新推断后台代理结果。
|
||||
|
||||
### 4.4 贡献模式链路
|
||||
|
||||
贡献模式的目标不是新增一个 provider,而是在 sidepanel 内开启一套“贡献账号”专用 UI,并把公开贡献服务并进原有 10 步主链:
|
||||
@@ -764,6 +768,42 @@ Hide My Email 获取与管理链路:
|
||||
- iCloud 登录态提示必须区分“真实未登录”和“已登录但请求上下文波动”;不能把所有 401 / 403 / 421 都直接提示为未登录。
|
||||
- iCloud 别名缓存只用于短暂失败回退,不应改变最终的已用 / 保留状态来源;状态仍以 `manualAliasUsage`、`preservedAliases` 与最新线上列表合并后的结果为准。
|
||||
|
||||
### 7.7 IP Proxy
|
||||
|
||||
组成:
|
||||
|
||||
- [background/ip-proxy-core.js](c:/Users/projectf/Downloads/codex注册扩展/background/ip-proxy-core.js)
|
||||
- [background/ip-proxy-provider-711proxy.js](c:/Users/projectf/Downloads/codex注册扩展/background/ip-proxy-provider-711proxy.js)
|
||||
- [sidepanel/ip-proxy-panel.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/ip-proxy-panel.js)
|
||||
- [sidepanel/ip-proxy-provider-711proxy.js](c:/Users/projectf/Downloads/codex注册扩展/sidepanel/ip-proxy-provider-711proxy.js)
|
||||
- [docs/ip-proxy-module.md](c:/Users/projectf/Downloads/codex注册扩展/docs/ip-proxy-module.md)
|
||||
|
||||
职责边界:
|
||||
|
||||
- `background/ip-proxy-core.js` 负责代理条目解析、运行态代理池、PAC 应用与清除、`webRequest.onAuthRequired` 代理鉴权回填、出口探测和失败时的目标站点 fail-close 规则。
|
||||
- `background/ip-proxy-provider-711proxy.js` 只承接 711Proxy 的账号串参数规则,负责把 `region / session / sessTime` 叠加到最终用户名。
|
||||
- `background/message-router.js` 只暴露 `REFRESH_IP_PROXY_POOL / SWITCH_IP_PROXY / CHANGE_IP_PROXY_EXIT / PROBE_IP_PROXY_EXIT` 消息,不在路由层复制代理解析逻辑。
|
||||
- `sidepanel/ip-proxy-panel.js` 负责 UI 状态渲染、表单快照、按钮动作和 711 参数输入联动;`sidepanel/sidepanel.js` 只保留配置保存、事件绑定和广播接线。
|
||||
|
||||
基础链路:
|
||||
|
||||
1. 用户在 sidepanel 打开 `IP代理`,选择服务和账号模式。
|
||||
2. 首版只开放 `711proxy + account` 主路径;API 模式和账号列表入口保留但默认禁用。
|
||||
3. 用户填写固定账号的 Host / Port / Protocol / Username / Password,可选填写地区、session 和时长。
|
||||
4. 点击 `同步` 后,sidepanel 发送 `REFRESH_IP_PROXY_POOL`,后台解析当前账号为代理池并调用 `chrome.proxy.settings.set` 写入 PAC。
|
||||
5. 如果代理需要账号密码,后台通过 `webRequest.onAuthRequired` 对代理 407 challenge 回填用户名和密码。
|
||||
6. PAC 成功应用后,后台关闭 fail-close 规则;如果配置缺失、应用失败或出口探测失败,则开启只覆盖 OpenAI / ChatGPT 目标域的阻断规则,避免误以为已经走代理。
|
||||
7. `检测出口` 会优先用页面上下文探测公网 IP,再用后台请求兜底,并把出口 IP、地区、探测来源和诊断摘要写回运行态。
|
||||
8. `下一条` 根据当前代理池 index 切换节点;`Change` 在 711 session 场景下强制重绑代理链路,用于刷新出口。
|
||||
9. 自动运行成功轮数命中 `ipProxyPoolTargetCount` 时,后台会尝试自动切到下一条代理;只有代理候选数量大于 1 时才执行切换。
|
||||
|
||||
维护约定:
|
||||
|
||||
- 代理解析、PAC、鉴权和出口检测必须留在后台模块,不应继续堆进 `background.js`。
|
||||
- UI 层不能凭输入值直接判断“代理已接管”,只能展示后台返回的 `ipProxyApplied*` 状态。
|
||||
- 新增代理服务商时,应优先新增 provider 规则模块,并让共享解析/运行态继续走 `background/ip-proxy-core.js`。
|
||||
- 修改代理字段、权限或链路时,需要同步更新 [docs/ip-proxy-module.md](c:/Users/projectf/Downloads/codex注册扩展/docs/ip-proxy-module.md)、当前完整链路说明和结构说明。
|
||||
|
||||
## 8. 自动运行完整链路
|
||||
|
||||
文件:
|
||||
@@ -794,7 +834,8 @@ Hide My Email 获取与管理链路:
|
||||
- 手动停止与自动停止会写入“停止”状态(若后续同邮箱成功/失败,会被覆盖为最新状态)
|
||||
8. 如果配置了线程间隔,则挂计时计划;计时计划会带上当前 `autoRunSessionId`
|
||||
9. 旧 timer / 旧 alarm / 旧恢复入口只有在 session 仍有效时才允许恢复执行;Stop 会立即使当前 session 失效,防止“停止后旧倒计时又把流程重新拉起”
|
||||
10. 所有轮次结束后输出汇总,并清空当前 session 标识
|
||||
10. 如果启用 IP 代理,后台会在每轮成功后按 `ipProxyPoolTargetCount` 检查是否需要切换出口;候选代理不足时只记录日志并跳过切换
|
||||
11. 所有轮次结束后输出汇总,并清空当前 session 标识
|
||||
|
||||
## 9. 新增功能时最容易漏掉的地方
|
||||
|
||||
|
||||
+14
-9
@@ -29,7 +29,7 @@
|
||||
- `mail-provider-utils.js`:网页邮箱 provider 配置纯工具,负责 `163 / 163 VIP / 126 / QQ / Inbucket / Hotmail` 的基础归一化与页面入口配置,并统一承接 iCloud 转发收码目标邮箱 provider 的归一化、选项列表和收码入口配置。
|
||||
- `mail2925-utils.js`:2925 账号池相关的纯工具函数,负责账号归一化、冷却期判断、可用账号挑选、批量导入解析与列表更新。
|
||||
- `managed-alias-utils.js`:共享 Gmail / 2925 的别名邮箱规则,负责解析基邮箱、校验“当前完整注册邮箱”是否仍与基邮箱兼容、生成 Gmail `+tag` 与 2925 随机后缀邮箱,并输出 sidepanel 可复用的 UI 文案;当前还统一承接 `mail2925Mode` 的归一化与“2925 仅在 provide 模式下参与别名生成”的共享判定。
|
||||
- `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集。
|
||||
- `manifest.json`:Chrome 扩展清单,声明权限、背景脚本、侧边栏、内容脚本与规则集;当前额外声明 `proxy / webRequest / webRequestAuthProvider`,用于 IP 代理 PAC 接管和代理鉴权回填。
|
||||
- `microsoft-email.js`:Microsoft Graph / Outlook 邮件读取辅助模块,负责刷新令牌换 token、邮箱夹轮询和验证码提取。
|
||||
- `package.json`:仓库最小 Node 包配置,目前主要提供测试脚本定义。
|
||||
- `rules.json`:静态 DNR 规则,主要处理 iCloud 相关请求头。
|
||||
@@ -46,15 +46,17 @@
|
||||
- `background/auto-run-controller.js`:自动运行主控制器,封装多轮执行、重试、轮次摘要、线程间隔与倒计时恢复逻辑;当前自动流程会绑定 `autoRunSessionId`,手动停止后旧的倒计时计划、旧重试链路和旧恢复入口不会再复活已失效的自动运行;fresh-attempt reset 时会额外保留 Plus 模式与 PayPal 配置、`gmailBaseEmail`、`mail2925BaseEmail` 与当前 2925 账号选择,避免自动流程重置后丢失关键持久配置。
|
||||
- `background/contribution-oauth.js`:贡献模式的公开 OAuth 流程模块,负责调用 `apikey.qzz.io` 的公开贡献接口、保存贡献会话运行态、在主 10 步流程里为步骤 7 提供贡献登录地址、在步骤 9/10 衔接 callback 捕获与兼容提交 `/oauth/api/submit-callback`,并把真实服务端状态映射回 sidepanel 运行态。
|
||||
- `background/generated-email-helpers.js`:生成邮箱辅助层,除了 Duck / Cloudflare / iCloud / Cloudflare Temp Email,也统一承接 Gmail / 2925 的别名邮箱生成入口与自定义邮箱池读取;当 provider 为 2925 且 `mail2925Mode = provide` 时,会先确保当前账号池里已有可用账号,再基于该账号生成别名邮箱;若 `mail2925Mode = receive`,则会回退到普通邮箱生成链路;当生成方式为 iCloud 且 `icloudFetchMode = always_new` 时,会强制跳过未用别名复用并新建别名;当生成方式为 `custom-pool` 时,会按当前目标轮次读取邮箱池中的对应邮箱。
|
||||
- `background/ip-proxy-core.js`:IP 代理核心模块,负责代理条目解析、账号/接口代理池运行态、PAC 应用与清除、代理鉴权回填、出口探测、失败时的目标站点 fail-close 防漏规则,以及自动运行成功阈值后的代理切换支撑。
|
||||
- `background/ip-proxy-provider-711proxy.js`:711Proxy provider 规则模块,负责从账号串中识别和写回 `region / session / sessTime` 等参数,并在固定账号模式下把侧栏配置转换为最终生效的代理账号。
|
||||
- `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层;当前额外承接 `add-phone / 手机号页` 这类认证 fatal 错误的共享判定,并会把 Step 2 的“手机号输入模式未切成功”与真正的 auth `add-phone` 页面区分开,避免自动运行误停机。
|
||||
- `background/mail-2925-session.js`:2925 会话模块,负责 2925 账号池持久化、当前账号切换、cookie 清理登出、自动登录、命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。
|
||||
- `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;当前额外接入 2925 账号池的新增、导入、切换、登录、禁用与删除消息。
|
||||
- `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;当前额外接入 2925 账号池的新增、导入、切换、登录、禁用与删除消息,以及 IP 代理的同步、切换、Change 与出口检测消息。
|
||||
- `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API / Codex2API 地址归一化、来源标签页家族判断与步骤跳转相关判断。
|
||||
- `background/phone-verification-flow.js`:手机号验证共享流程模块,负责在 OAuth 链路命中 `add-phone / phone-verification` 页面后向 HeroSMS 申请或复用号码、轮询短信验证码、提交手机号码与短信验证码,并在号码长期收不到短信时把后续自动流拉回步骤 7 重新拿号。
|
||||
- `background/panel-bridge.js`:来源桥接层;CPA / SUB2API 继续封装页面打开、脚本注入和通信,Codex2API 则直接通过后台协议生成 OAuth 地址。
|
||||
- `background/signup-flow-helpers.js`:注册页辅助层,负责打开注册入口、等待密码页以及解析当前流程所用邮箱;在 Gmail 与 `2925 + provide` 模式下会优先复用已经存在且仍兼容的完整注册邮箱,只有不兼容或为空时才重新生成;当 provider 为 2925 且启用了 provide 模式下的号池时,会先确保账号池中已选中可用账号。
|
||||
- `background/tab-runtime.js`:标签页与内容脚本运行时基础设施,封装标签注册、冲突清理、消息超时、注入重试与队列;当前等待标签完成、等待标签完成并短暂稳定、注入后的短暂延迟和内容脚本重试等待都已做 Stop 感知,避免用户停止后后台还继续等待并恢复执行。
|
||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 `mail2925Mode = receive`,会额外把目标注册邮箱传给 2925 内容脚本做弱匹配;若登录验证码提交后页面转入 `add-phone / 手机号页`,则会把“后续需要手机号验证”的结果继续传给步骤 9,而不是误判为普通失败;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||
- `background/verification-flow.js`:注册/登录验证码共享流程层,封装重发、轮询、提交、失败回退、自定义邮箱跳过、共享验证码自动重发次数配置以及 2925 长轮询参数;当前验证码提交重试上限为 15 次,2925 每次重发验证码之间都会固定跑完一轮 15 次邮箱刷新轮询;若 `mail2925Mode = receive`,会额外把目标注册邮箱传给 2925 内容脚本做弱匹配;若登录验证码提交后页面转入 `add-phone / 手机号页`,则会把“后续需要手机号验证”的结果继续传给步骤 9,而不是误判为普通失败;若登录验证码提交后通信中断但认证页已进入 OAuth 同意页或手机号验证页,会按真实页面状态继续后续流程;若 2925 轮询命中“子邮箱已达上限邮箱”,会转入 2925 会话模块执行“记录时间、禁用 24 小时、切下一个账号并重新登录”,然后直接结束当前尝试。
|
||||
|
||||
## `background/steps/`
|
||||
|
||||
@@ -102,6 +104,7 @@
|
||||
## `docs/`
|
||||
|
||||
- `docs/仓库协作者AI分析PR与合并标准流程.md`:仓库协作者进行 AI 分析 PR 与合并时的流程说明。
|
||||
- `docs/ip-proxy-module.md`:IP Proxy 模块说明,记录模块定位、后台/侧栏代码结构、当前功能范围、711Proxy 参数联动、操作方式、发布策略与已知限制。
|
||||
- `docs/使用教程.md`:面向使用者的补充教程文档,当前说明扩展更新流程与 Clash Verge 的“非港轮询”配置步骤。
|
||||
- `docs/images/十轮自动.png`:README 中展示的自动运行效果图。
|
||||
- `docs/images/微信.png`:README 中展示的微信收款码图片。
|
||||
@@ -130,8 +133,10 @@
|
||||
- `sidepanel/account-records-manager.js`:侧边栏邮箱记录面板管理器,负责“记录”按钮、覆盖层开关、分页列表、成功/失败/停止统计摘要和清理确认。
|
||||
- `sidepanel/contribution-content-update-service.js`:侧边栏贡献内容更新服务,负责拉取 `https://apikey.qzz.io/api/content-summary`、缓存公开公告/教程摘要,并输出可用于提示展示的 `promptVersion` 与最新更新时间。
|
||||
- `sidepanel/contribution-mode.js`:侧边栏贡献模式管理器,负责顶部“贡献”按钮、确认弹窗、贡献模式显隐、复用主自动流程启动、侧栏内贡献状态轮询、上传页跳转,以及贡献模式下对来源选择、配置入口、记录入口和敏感配置行(包括 Codex2API 配置)的禁用与隐藏。
|
||||
- `sidepanel/sidepanel.css`:侧边栏样式文件;当前额外提供 Hotmail / 2925 共用的号池表单容器、操作按钮行与统一的收起态列表高度样式。
|
||||
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,顶部新增“贡
|
||||
- `sidepanel/ip-proxy-panel.js`:侧边栏 IP 代理面板逻辑,负责代理配置显隐、服务/模式切换、账号串参数同步、状态卡渲染,以及同步、下一条、Change、检测出口、检查 IP 等按钮行为。
|
||||
- `sidepanel/ip-proxy-provider-711proxy.js`:侧边栏 711Proxy 输入辅助逻辑,负责从 host、username、region 输入中推断和归一化国家/地区参数。
|
||||
- `sidepanel/sidepanel.css`:侧边栏样式文件;当前额外提供 Hotmail / 2925 共用的号池表单容器、操作按钮行、统一的收起态列表高度样式,以及 IP 代理配置区、状态卡和操作按钮样式。
|
||||
- `sidepanel/sidepanel.html`:侧边栏页面结构;当前步骤列表已改为动态容器,日志区提供“记录”按钮并挂接邮箱记录覆盖层,顶部新增“贡
|
||||
献”按钮并在设置卡片中新增贡献模式主面板;贡献按钮下方额外挂接一个可关闭的轻提示气泡,用于提示公开公告 / 使用教程有更新;贡献面板内
|
||||
展示 `OAUTH / 回调 / 总状态` 三块真实运行态信息,同时把“接码”开关与“验证码重发”拆成同一行展示;只有开启接码后,下面的 HeroSMS 平
|
||||
台、国家与 API Key 行才会显示;页面继续加载 `managed-alias-utils.js` 与 `mail-provider-utils.js`,并把旧的“邮箱前缀”字段语义改为“别
|
||||
@@ -139,9 +144,8 @@
|
||||
receive 模式把账号池开关一起隐藏;当前在 `邮箱生成` 区域新增 `自定义邮箱池` 选项和多行邮箱池输入框,并在 `邮箱服务 = 自定义邮箱` 时
|
||||
额外显示 `自定义号池` 文本框;当邮箱服务为 iCloud 时,额外提供目标邮箱类型与转发邮箱 provider 配置,用于选择直接从 iCloud 收件箱收
|
||||
码或从 QQ / 网易 / Gmail 转发目标邮箱收码;来源下拉框当前支持 `CPA / SUB2API / Codex2API`,其中 Codex2API 额外提供后台地址和管理密
|
||||
钥配置行;设置卡片新增 `Plus 模式` 开关与 PayPal 账号/密码输入行;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添
|
||||
加”按钮和共享表单容器。
|
||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装
|
||||
钥配置行;设置卡片新增 `IP代理` 开关与代理配置折叠区,支持 711Proxy 账号密码模式、代理状态卡与出口检测按钮;设置卡片新增 `Plus 模式` 开关与 PayPal 账号/密码输入行;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器。
|
||||
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装
|
||||
配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 邮箱记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启
|
||||
动逻辑,而独立 manager 负责贡献运行态展示与轮询,同时把 Gmail / 2925 的基邮箱输入、完整注册邮箱输入、自动生成按钮与兼容性校验统一接
|
||||
到共享别名逻辑上;当 provider 为 2925 时,会根据 `mail2925Mode` 决定是否启用别名基邮箱链路,而 2925 号池开关与当前账号选择则独立显
|
||||
@@ -152,7 +156,7 @@
|
||||
据配置;Step 8 的自定义邮箱确认弹窗当前额外提供“出现手机号验证”按钮,用于直接走与真实 add-phone 一致的 fatal 分支;HeroSMS 国家列表
|
||||
会在 sidepanel 初始化时拉取并恢复到本地保存的国家/API 设置,接码开关关闭时会直接隐藏相关配置;账号记录快照同步改为默认自动模式,只
|
||||
要本地 helper 可用就会自动落盘;新来源 Codex2API 在这里仅补充来源配置接线、表单显隐和 Step 10 按钮文案,不承接协议业务逻辑;
|
||||
Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;侧边栏初始化与点击“自动”前会刷新一次贡献站
|
||||
Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;IP 代理配置保存、服务配置快照、开关应用和运行态回显由这里接入消息路由,具体解析/状态渲染下沉到 `sidepanel/ip-proxy-panel.js`;侧边栏初始化与点击“自动”前会刷新一次贡献站
|
||||
公开内容摘要,并按本地关闭版本决定是否展示轻提示,同时在首次初始化后按现有规则决定是否弹出新手引导提示。
|
||||
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Ultra` / 历史 `Pro` / legacy `v` 版本族排序、缓存读取与版本展示。
|
||||
|
||||
@@ -176,6 +180,7 @@
|
||||
- `tests/background-icloud.test.js`:测试 iCloud 相关后台纯函数与别名收尾逻辑。
|
||||
- `tests/background-icloud-login-help.test.js`:测试 iCloud 登录判定、页面上下文回退、别名缓存回退和保留异常恢复的关键源码约束。
|
||||
- `tests/background-icloud-mail-provider.test.js`:测试 iCloud 邮箱 provider 配置解析,并覆盖转发收码模式复用共享 provider 配置。
|
||||
- `tests/background-ip-proxy-core.test.js`:测试 IP 代理核心解析与接入约束,覆盖注释行过滤、代理条目归一化、711 固定账号参数叠加、PAC 生成、侧栏脚本加载顺序和自动切换阈值钳制。
|
||||
- `tests/background-logging-status-module.test.js`:测试日志 / 状态模块已接入且导出工厂。
|
||||
- `tests/background-luckmail.test.js`:测试 LuckMail 相关后台逻辑,如购买、复用、标记已用与重置。
|
||||
- `tests/background-mail2925-session-module.test.js`:测试 2925 会话模块已接入且导出工厂,并覆盖命中上限后“禁用 24 小时 + 切下一个号 + 自动登录”的核心链路。
|
||||
|
||||
Reference in New Issue
Block a user