refactor kiro flow to web signin authorization
This commit is contained in:
@@ -785,6 +785,10 @@
|
||||
if (!cleanString(runtimeState.register?.email || currentState?.email)) {
|
||||
throw new Error('缺少已注册邮箱,请先完成注册页步骤。');
|
||||
}
|
||||
if (cleanString(runtimeState.register?.status) !== 'completed'
|
||||
|| cleanString(runtimeState.webAuth?.status) !== 'signed_in') {
|
||||
throw new Error('Kiro Web 登录态尚未建立,请先完成步骤 6。');
|
||||
}
|
||||
|
||||
const client = await desktopClientApi.registerDesktopClient({
|
||||
region: DEFAULT_REGION,
|
||||
|
||||
+117
-124
@@ -6,16 +6,11 @@
|
||||
const DEFAULT_REGION = kiroStateApi?.DEFAULT_REGION || 'us-east-1';
|
||||
const DEFAULT_TARGET_ID = kiroStateApi?.DEFAULT_TARGET_ID || 'kiro-rs';
|
||||
const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = kiroTimeoutApi?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000);
|
||||
const DEVICE_LOGIN_START_URL = 'https://view.awsapps.com/start';
|
||||
const DEFAULT_SCOPES = Object.freeze([
|
||||
'codewhisperer:completions',
|
||||
'codewhisperer:analysis',
|
||||
'codewhisperer:conversations',
|
||||
'codewhisperer:transformations',
|
||||
'codewhisperer:taskassist',
|
||||
]);
|
||||
const KIRO_SIGNIN_URL = 'https://app.kiro.dev/signin';
|
||||
const KIRO_REGISTER_PAGE_SOURCE_ID = 'kiro-register-page';
|
||||
const KIRO_STEP1_COOKIE_CLEAR_DOMAINS = Object.freeze([
|
||||
'kiro.dev',
|
||||
'app.kiro.dev',
|
||||
'awsapps.com',
|
||||
'view.awsapps.com',
|
||||
'login.awsapps.com',
|
||||
@@ -26,6 +21,8 @@
|
||||
'profile.aws.amazon.com',
|
||||
]);
|
||||
const KIRO_STEP1_COOKIE_CLEAR_ORIGINS = Object.freeze([
|
||||
'https://app.kiro.dev',
|
||||
'https://kiro.dev',
|
||||
'https://view.awsapps.com',
|
||||
'https://login.awsapps.com',
|
||||
'https://oidc.us-east-1.amazonaws.com',
|
||||
@@ -110,10 +107,6 @@
|
||||
return String(value ?? '').trim();
|
||||
}
|
||||
|
||||
function normalizeRegion(value = '', fallback = DEFAULT_REGION) {
|
||||
return cleanString(value) || fallback;
|
||||
}
|
||||
|
||||
function normalizePositiveInteger(value, fallback) {
|
||||
const numeric = Math.floor(Number(value));
|
||||
if (Number.isInteger(numeric) && numeric > 0) {
|
||||
@@ -152,10 +145,6 @@
|
||||
);
|
||||
}
|
||||
|
||||
function buildOidcBaseUrl(region = DEFAULT_REGION) {
|
||||
return `https://oidc.${normalizeRegion(region)}.amazonaws.com`;
|
||||
}
|
||||
|
||||
function readKiroRuntime(state = {}) {
|
||||
if (typeof kiroStateApi?.ensureRuntimeState === 'function') {
|
||||
return kiroStateApi.ensureRuntimeState(state);
|
||||
@@ -178,17 +167,6 @@
|
||||
return error instanceof Error ? error.message : String(error ?? '未知错误');
|
||||
}
|
||||
|
||||
async function readResponse(response) {
|
||||
const text = await response.text();
|
||||
let json = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch (_error) {
|
||||
json = null;
|
||||
}
|
||||
return { text, json };
|
||||
}
|
||||
|
||||
function normalizeKiroCookieDomain(domain = '') {
|
||||
return String(domain || '').trim().replace(/^\.+/, '').toLowerCase();
|
||||
}
|
||||
@@ -260,6 +238,58 @@
|
||||
return cookies;
|
||||
}
|
||||
|
||||
function shouldReadKiroWebCookie(cookie) {
|
||||
const domain = normalizeKiroCookieDomain(cookie?.domain);
|
||||
return domain === 'kiro.dev'
|
||||
|| domain === 'app.kiro.dev'
|
||||
|| domain.endsWith('.app.kiro.dev');
|
||||
}
|
||||
|
||||
async function collectKiroWebCookies(chromeApi) {
|
||||
if (!chromeApi?.cookies?.getAll) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const stores = chromeApi.cookies.getAllCookieStores
|
||||
? await chromeApi.cookies.getAllCookieStores()
|
||||
: [{ id: undefined }];
|
||||
const cookies = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const store of stores) {
|
||||
const storeId = store?.id;
|
||||
const batch = await chromeApi.cookies.getAll(storeId ? { storeId } : {});
|
||||
for (const cookie of batch || []) {
|
||||
if (!shouldReadKiroWebCookie(cookie)) {
|
||||
continue;
|
||||
}
|
||||
const key = [
|
||||
cookie.storeId || storeId || '',
|
||||
cookie.domain || '',
|
||||
cookie.path || '',
|
||||
cookie.name || '',
|
||||
cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : '',
|
||||
].join('|');
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
cookies.push(cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
async function captureKiroWebAuthSummary() {
|
||||
const cookies = await collectKiroWebCookies(chrome);
|
||||
const names = new Set(cookies.map((cookie) => cleanString(cookie?.name).toLowerCase()).filter(Boolean));
|
||||
return {
|
||||
hasAccessToken: names.has('accesstoken') || names.has('access_token'),
|
||||
hasSessionToken: names.has('sessiontoken') || names.has('session_token'),
|
||||
};
|
||||
}
|
||||
|
||||
async function removeKiroStep1Cookie(chromeApi, cookie) {
|
||||
const details = {
|
||||
url: buildKiroStep1CookieRemovalUrl(cookie),
|
||||
@@ -285,75 +315,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function startBuilderIdDeviceLogin(region, fetchImpl) {
|
||||
const normalizedRegion = normalizeRegion(region);
|
||||
const oidcBaseUrl = buildOidcBaseUrl(normalizedRegion);
|
||||
const registerResponse = await fetchImpl(`${oidcBaseUrl}/client/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientName: 'Codex Registration Extension',
|
||||
clientType: 'public',
|
||||
scopes: DEFAULT_SCOPES,
|
||||
grantTypes: ['urn:ietf:params:oauth:grant-type:device_code', 'refresh_token'],
|
||||
issuerUrl: DEVICE_LOGIN_START_URL,
|
||||
}),
|
||||
});
|
||||
const registerBody = await readResponse(registerResponse);
|
||||
if (!registerResponse.ok) {
|
||||
throw new Error(`Builder ID 客户端注册失败:${cleanString(registerBody.text || registerResponse.statusText) || registerResponse.status}`);
|
||||
}
|
||||
|
||||
const clientId = cleanString(registerBody.json?.clientId);
|
||||
const clientSecret = String(registerBody.json?.clientSecret || '');
|
||||
if (!clientId || !clientSecret) {
|
||||
throw new Error('Builder ID 客户端注册响应缺少凭据。');
|
||||
}
|
||||
|
||||
const authorizationResponse = await fetchImpl(`${oidcBaseUrl}/device_authorization`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientId,
|
||||
clientSecret,
|
||||
startUrl: DEVICE_LOGIN_START_URL,
|
||||
}),
|
||||
});
|
||||
const authorizationBody = await readResponse(authorizationResponse);
|
||||
if (!authorizationResponse.ok) {
|
||||
throw new Error(`Builder ID 设备授权失败:${cleanString(authorizationBody.text || authorizationResponse.statusText) || authorizationResponse.status}`);
|
||||
}
|
||||
|
||||
const deviceCode = String(authorizationBody.json?.deviceCode || '');
|
||||
const userCode = cleanString(authorizationBody.json?.userCode);
|
||||
const verificationUri = cleanString(authorizationBody.json?.verificationUri);
|
||||
const verificationUriComplete = cleanString(
|
||||
authorizationBody.json?.verificationUriComplete || verificationUri
|
||||
);
|
||||
const interval = normalizePositiveInteger(authorizationBody.json?.interval, 5);
|
||||
const expiresIn = normalizePositiveInteger(authorizationBody.json?.expiresIn, 600);
|
||||
if (!deviceCode || !userCode || !verificationUriComplete) {
|
||||
throw new Error('Builder ID 设备授权响应缺少必要字段。');
|
||||
}
|
||||
|
||||
return {
|
||||
clientId,
|
||||
clientSecret,
|
||||
deviceCode,
|
||||
expiresAt: Date.now() + expiresIn * 1000,
|
||||
expiresIn,
|
||||
interval,
|
||||
region: normalizedRegion,
|
||||
userCode,
|
||||
verificationUri,
|
||||
verificationUriComplete,
|
||||
};
|
||||
}
|
||||
|
||||
function createKiroRegisterRunner(deps = {}) {
|
||||
const {
|
||||
addLog = async () => {},
|
||||
@@ -362,7 +323,6 @@
|
||||
ensureContentScriptReadyOnTab = null,
|
||||
ensureIcloudMailSession = null,
|
||||
ensureMail2925MailboxSession = null,
|
||||
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
|
||||
generatePassword = null,
|
||||
generateRandomName = null,
|
||||
getMailConfig = null,
|
||||
@@ -399,9 +359,6 @@
|
||||
if (typeof completeNodeFromBackground !== 'function') {
|
||||
throw new Error('Kiro register runner requires completeNodeFromBackground.');
|
||||
}
|
||||
if (typeof fetchImpl !== 'function') {
|
||||
throw new Error('Kiro register runner requires fetch support.');
|
||||
}
|
||||
|
||||
async function log(message, level = 'info', nodeId = '') {
|
||||
await addLog(message, level, nodeId ? { nodeId } : {});
|
||||
@@ -883,24 +840,51 @@
|
||||
const currentState = await getExecutionState(state);
|
||||
try {
|
||||
await clearKiroCookiesBeforeStep1();
|
||||
const auth = await startBuilderIdDeviceLogin(DEFAULT_REGION, fetchImpl);
|
||||
const loginUrl = cleanString(auth.verificationUriComplete || auth.verificationUri);
|
||||
const tabId = loginUrl ? await reuseOrCreateTab(KIRO_REGISTER_PAGE_SOURCE_ID, loginUrl) : null;
|
||||
const loginUrl = KIRO_SIGNIN_URL;
|
||||
const tabId = await reuseOrCreateTab(KIRO_REGISTER_PAGE_SOURCE_ID, loginUrl);
|
||||
if (!Number.isInteger(tabId)) {
|
||||
throw new Error('无法打开 Kiro 注册页,请重试步骤 1。');
|
||||
}
|
||||
await registerTab(KIRO_REGISTER_PAGE_SOURCE_ID, tabId);
|
||||
await activateTab(tabId);
|
||||
|
||||
const landingResult = await ensureKiroPageState(tabId, {
|
||||
let landingResult = await ensureKiroPageState(tabId, {
|
||||
step: 1,
|
||||
targetStates: ['email_entry'],
|
||||
targetStates: ['kiro_signin_page', 'email_entry'],
|
||||
stableMs: 2500,
|
||||
initialDelayMs: 300,
|
||||
injectLogMessage: '步骤 1:Kiro 注册页内容脚本未就绪,正在等待页面恢复...',
|
||||
readyLogMessage: '步骤 1:正在等待 Kiro 注册页邮箱输入框加载完成...',
|
||||
readyLogMessage: '步骤 1:正在等待 Kiro 官方登录页加载完成...',
|
||||
});
|
||||
|
||||
if (landingResult?.state === 'kiro_signin_page') {
|
||||
await log('步骤 1:正在选择 AWS Builder ID 登录方式...', 'info', nodeId);
|
||||
const selectResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, {
|
||||
type: 'EXECUTE_NODE',
|
||||
nodeId: 'kiro-open-register-page',
|
||||
step: 1,
|
||||
source: 'background',
|
||||
payload: {},
|
||||
}, {
|
||||
timeoutMs: 30000,
|
||||
retryDelayMs: 700,
|
||||
onRetryableError: buildKiroRetryRecovery(tabId, {}),
|
||||
logMessage: '步骤 1:正在点击 Kiro 官方登录页的 Builder ID...',
|
||||
});
|
||||
if (selectResult?.error) {
|
||||
throw new Error(selectResult.error);
|
||||
}
|
||||
landingResult = await ensureKiroPageState(tabId, {
|
||||
step: 1,
|
||||
targetStates: ['email_entry'],
|
||||
stableMs: 2500,
|
||||
initialDelayMs: 300,
|
||||
injectLogMessage: '步骤 1:选择 Builder ID 后页面跳转中,正在等待 Kiro 注册页恢复...',
|
||||
readyLogMessage: '步骤 1:正在等待 Builder ID 邮箱输入框加载完成...',
|
||||
timeoutMessage: '选择 Builder ID 后未进入邮箱页,请检查当前 Kiro 登录页或代理状态。',
|
||||
});
|
||||
}
|
||||
|
||||
const nextPatch = {
|
||||
session: {
|
||||
currentStage: 'register',
|
||||
@@ -915,18 +899,16 @@
|
||||
email: '',
|
||||
fullName: '',
|
||||
verificationRequestedAt: 0,
|
||||
entryClientId: auth.clientId,
|
||||
entryClientSecret: auth.clientSecret,
|
||||
entryDeviceCode: auth.deviceCode,
|
||||
userCode: auth.userCode,
|
||||
loginUrl,
|
||||
verificationUri: auth.verificationUri,
|
||||
verificationUriComplete: loginUrl,
|
||||
entryExpiresAt: auth.expiresAt,
|
||||
entryIntervalSeconds: auth.interval,
|
||||
status: 'waiting_email',
|
||||
completedAt: 0,
|
||||
},
|
||||
webAuth: {
|
||||
status: 'signin_started',
|
||||
completedAt: 0,
|
||||
hasAccessToken: false,
|
||||
hasSessionToken: false,
|
||||
},
|
||||
desktopAuth: {
|
||||
region: DEFAULT_REGION,
|
||||
clientId: '',
|
||||
@@ -957,7 +939,7 @@
|
||||
};
|
||||
|
||||
const payload = await applyRuntimeState(currentState, nextPatch);
|
||||
await log(`Kiro 注册页已就绪,请在下一步中获取邮箱并继续。当前授权码:${auth.userCode}`, 'ok', nodeId);
|
||||
await log('Kiro 注册页已就绪,已进入 Builder ID 邮箱页,请在下一步中获取邮箱并继续。', 'ok', nodeId);
|
||||
await completeNodeFromBackground(nodeId, payload);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
@@ -1315,7 +1297,7 @@
|
||||
});
|
||||
let landingResult = await ensureKiroPageState(tabId, {
|
||||
step: 6,
|
||||
targetStates: ['authorization_page', 'success_page'],
|
||||
targetStates: ['authorization_page', 'kiro_web_signed_in'],
|
||||
stableMs: 1500,
|
||||
initialDelayMs: 150,
|
||||
injectLogMessage: '步骤 6:Kiro 授权确认页内容脚本未就绪,正在等待页面恢复...',
|
||||
@@ -1323,7 +1305,7 @@
|
||||
timeoutMessage: '未进入 Kiro 授权确认页,请检查当前页面状态。',
|
||||
});
|
||||
|
||||
if (landingResult?.state !== 'success_page') {
|
||||
if (landingResult?.state !== 'kiro_web_signed_in') {
|
||||
await log('步骤 6:正在确认访问并完成 Kiro 注册授权...', 'info', nodeId);
|
||||
const submitResult = await sendToContentScriptResilient(KIRO_REGISTER_PAGE_SOURCE_ID, {
|
||||
type: 'EXECUTE_NODE',
|
||||
@@ -1342,12 +1324,18 @@
|
||||
if (submitResult?.error) {
|
||||
throw new Error(submitResult.error);
|
||||
}
|
||||
landingResult = {
|
||||
state: String(submitResult?.state || ''),
|
||||
url: String(submitResult?.url || ''),
|
||||
};
|
||||
landingResult = await ensureKiroPageState(tabId, {
|
||||
step: 6,
|
||||
targetStates: ['kiro_web_signed_in'],
|
||||
stableMs: 2000,
|
||||
initialDelayMs: 300,
|
||||
injectLogMessage: '步骤 6:授权确认后页面跳转中,正在等待 Kiro Web 登录态恢复...',
|
||||
readyLogMessage: '步骤 6:授权确认已提交,正在等待回到 Kiro Web...',
|
||||
timeoutMessage: '授权确认后未回到 Kiro Web 登录完成页,请检查当前页面或代理状态。',
|
||||
});
|
||||
}
|
||||
|
||||
const webAuthSummary = await captureKiroWebAuthSummary();
|
||||
const payload = await applyRuntimeState(currentState, {
|
||||
session: {
|
||||
currentStage: 'desktop-authorize',
|
||||
@@ -1359,12 +1347,18 @@
|
||||
status: 'completed',
|
||||
completedAt: Date.now(),
|
||||
},
|
||||
webAuth: {
|
||||
status: 'signed_in',
|
||||
completedAt: Date.now(),
|
||||
hasAccessToken: Boolean(webAuthSummary.hasAccessToken),
|
||||
hasSessionToken: Boolean(webAuthSummary.hasSessionToken),
|
||||
},
|
||||
upload: {
|
||||
status: 'waiting_desktop_authorize',
|
||||
error: '',
|
||||
},
|
||||
});
|
||||
await log('步骤 6:注册页授权已完成,Builder ID 浏览器会话已建立。', 'ok', nodeId);
|
||||
await log('步骤 6:注册页授权已完成,Kiro Web 登录态已建立。', 'ok', nodeId);
|
||||
await completeNodeFromBackground(nodeId, payload);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
@@ -1380,12 +1374,11 @@
|
||||
executeKiroSubmitName,
|
||||
executeKiroSubmitPassword,
|
||||
executeKiroSubmitVerificationCode,
|
||||
startBuilderIdDeviceLogin,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
createKiroRegisterRunner,
|
||||
startBuilderIdDeviceLogin,
|
||||
KIRO_SIGNIN_URL,
|
||||
};
|
||||
});
|
||||
|
||||
+19
-16
@@ -58,6 +58,13 @@
|
||||
return Number.isInteger(numeric) ? numeric : fallback;
|
||||
}
|
||||
|
||||
function normalizeBoolean(value, fallback = false) {
|
||||
if (value === true || value === false) {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function buildDefaultRuntimeState() {
|
||||
return {
|
||||
session: {
|
||||
@@ -74,18 +81,16 @@
|
||||
email: '',
|
||||
fullName: '',
|
||||
verificationRequestedAt: 0,
|
||||
entryClientId: '',
|
||||
entryClientSecret: '',
|
||||
entryDeviceCode: '',
|
||||
userCode: '',
|
||||
loginUrl: '',
|
||||
verificationUri: '',
|
||||
verificationUriComplete: '',
|
||||
entryExpiresAt: 0,
|
||||
entryIntervalSeconds: 0,
|
||||
status: '',
|
||||
completedAt: 0,
|
||||
},
|
||||
webAuth: {
|
||||
status: '',
|
||||
completedAt: 0,
|
||||
hasAccessToken: false,
|
||||
hasSessionToken: false,
|
||||
},
|
||||
desktopAuth: {
|
||||
region: DEFAULT_REGION,
|
||||
clientId: '',
|
||||
@@ -133,18 +138,16 @@
|
||||
email: normalizeString(merged.register?.email),
|
||||
fullName: normalizeString(merged.register?.fullName),
|
||||
verificationRequestedAt: Math.max(0, normalizeInteger(merged.register?.verificationRequestedAt)),
|
||||
entryClientId: normalizeString(merged.register?.entryClientId),
|
||||
entryClientSecret: normalizeString(merged.register?.entryClientSecret),
|
||||
entryDeviceCode: normalizeString(merged.register?.entryDeviceCode),
|
||||
userCode: normalizeString(merged.register?.userCode),
|
||||
loginUrl: normalizeString(merged.register?.loginUrl),
|
||||
verificationUri: normalizeString(merged.register?.verificationUri),
|
||||
verificationUriComplete: normalizeString(merged.register?.verificationUriComplete),
|
||||
entryExpiresAt: Math.max(0, normalizeInteger(merged.register?.entryExpiresAt)),
|
||||
entryIntervalSeconds: Math.max(0, normalizeInteger(merged.register?.entryIntervalSeconds)),
|
||||
status: normalizeString(merged.register?.status),
|
||||
completedAt: Math.max(0, normalizeInteger(merged.register?.completedAt)),
|
||||
},
|
||||
webAuth: {
|
||||
status: normalizeString(merged.webAuth?.status),
|
||||
completedAt: Math.max(0, normalizeInteger(merged.webAuth?.completedAt)),
|
||||
hasAccessToken: normalizeBoolean(merged.webAuth?.hasAccessToken),
|
||||
hasSessionToken: normalizeBoolean(merged.webAuth?.hasSessionToken),
|
||||
},
|
||||
desktopAuth: {
|
||||
region: normalizeString(merged.desktopAuth?.region, DEFAULT_REGION),
|
||||
clientId: normalizeString(merged.desktopAuth?.clientId),
|
||||
|
||||
@@ -3,6 +3,7 @@ console.log('[MultiPage:kiro-register-page] Content script loaded on', location.
|
||||
const KIRO_REGISTER_PAGE_LISTENER_SENTINEL = 'data-multipage-kiro-register-page-listener';
|
||||
const DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS = globalThis.MultiPageKiroTimeouts?.DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS || (3 * 60 * 1000);
|
||||
const KIRO_CONTINUE_TEXT_PATTERN = /continue|继续/i;
|
||||
const KIRO_BUILDER_ID_TEXT_PATTERN = /aws\s*builder\s*id|builder\s*id/i;
|
||||
const KIRO_CONFIRM_CONTINUE_TEXT_PATTERN = /confirm and continue|确认并继续/i;
|
||||
const KIRO_ALLOW_ACCESS_TEXT_PATTERN = /allow access|允许访问/i;
|
||||
const KIRO_SUCCESS_TEXT_PATTERN = /authorization successful|you may now close this window|you are now signed in|授权成功|可以关闭此窗口|已登录/i;
|
||||
@@ -174,6 +175,39 @@ function findPasswordContinueButton(passwordInput = null) {
|
||||
});
|
||||
}
|
||||
|
||||
function isKiroWebHost(hostname = '') {
|
||||
const normalized = String(hostname || '').trim().toLowerCase();
|
||||
return normalized === 'app.kiro.dev'
|
||||
|| normalized === 'kiro.dev';
|
||||
}
|
||||
|
||||
function parseCurrentUrl() {
|
||||
try {
|
||||
return new URL(location.href);
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function findBuilderIdButton() {
|
||||
return findActionButton({
|
||||
textPattern: KIRO_BUILDER_ID_TEXT_PATTERN,
|
||||
});
|
||||
}
|
||||
|
||||
function isKiroWebSignedInPage(pageText = '') {
|
||||
const parsedUrl = parseCurrentUrl();
|
||||
const pathname = String(parsedUrl?.pathname || '').replace(/\/+$/, '') || '/';
|
||||
const authStatus = String(parsedUrl?.searchParams?.get('auth_status') || '').trim().toLowerCase();
|
||||
if (authStatus === 'success') {
|
||||
return true;
|
||||
}
|
||||
if (pathname !== '/signin') {
|
||||
return true;
|
||||
}
|
||||
return /signed\s*in|authorization\s*successful|登录成功|已登录|授权成功/i.test(pageText);
|
||||
}
|
||||
|
||||
function getAuthorizationActionKind(text = '') {
|
||||
if (KIRO_CONFIRM_CONTINUE_TEXT_PATTERN.test(text)) {
|
||||
return 'confirm_continue';
|
||||
@@ -264,6 +298,24 @@ function detectKiroRegisterPageState() {
|
||||
return fatalState;
|
||||
}
|
||||
|
||||
if (isKiroWebHost(location.hostname || '')) {
|
||||
const builderIdButton = findBuilderIdButton();
|
||||
if (builderIdButton) {
|
||||
return {
|
||||
state: 'kiro_signin_page',
|
||||
url: currentUrl,
|
||||
actionButton: builderIdButton,
|
||||
actionText: getElementActionText(builderIdButton),
|
||||
};
|
||||
}
|
||||
if (isKiroWebSignedInPage(pageText)) {
|
||||
return {
|
||||
state: 'kiro_web_signed_in',
|
||||
url: currentUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const passwordInputs = findVisiblePasswordInputs();
|
||||
const confirmPasswordInput = findVisibleConfirmPasswordInput();
|
||||
if (passwordInputs.length) {
|
||||
@@ -398,7 +450,7 @@ async function waitForKiroRegisterStateChange(payload = {}) {
|
||||
async function waitForKiroAuthorizationAdvance(previousState = {}, options = {}) {
|
||||
return waitForKiroState(
|
||||
(detected) => {
|
||||
if (detected.state === 'success_page') {
|
||||
if (detected.state === 'success_page' || detected.state === 'kiro_web_signed_in') {
|
||||
return true;
|
||||
}
|
||||
if (detected.state !== 'authorization_page') {
|
||||
@@ -421,6 +473,24 @@ async function waitForKiroAuthorizationAdvance(previousState = {}, options = {})
|
||||
);
|
||||
}
|
||||
|
||||
async function selectKiroBuilderId() {
|
||||
const readyState = await ensureKiroRegisterPageState({
|
||||
targetStates: ['kiro_signin_page'],
|
||||
timeoutMs: DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
|
||||
retryDelayMs: 250,
|
||||
});
|
||||
if (!readyState.actionButton) {
|
||||
throw new Error('Kiro 官方登录页未找到 Builder ID 登录按钮。');
|
||||
}
|
||||
simulateClick(readyState.actionButton);
|
||||
return {
|
||||
submitted: true,
|
||||
state: 'builder_id_selected',
|
||||
url: location.href,
|
||||
actionText: readyState.actionText || '',
|
||||
};
|
||||
}
|
||||
|
||||
async function submitKiroEmail(payload = {}) {
|
||||
const email = String(payload?.email || '').trim();
|
||||
if (!email) {
|
||||
@@ -534,7 +604,7 @@ async function submitKiroPassword(payload = {}) {
|
||||
|
||||
async function confirmKiroRegisterConsent(payload = {}) {
|
||||
let currentState = await ensureKiroRegisterPageState({
|
||||
targetStates: ['authorization_page', 'success_page'],
|
||||
targetStates: ['authorization_page', 'success_page', 'kiro_web_signed_in'],
|
||||
timeoutMs: payload?.timeoutMs || DEFAULT_KIRO_PAGE_LOAD_TIMEOUT_MS,
|
||||
retryDelayMs: payload?.retryDelayMs || 250,
|
||||
});
|
||||
@@ -558,7 +628,7 @@ async function confirmKiroRegisterConsent(payload = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
if (currentState.state !== 'success_page') {
|
||||
if (currentState.state !== 'success_page' && currentState.state !== 'kiro_web_signed_in') {
|
||||
throw new Error('Kiro 授权页未完成确认访问流程。');
|
||||
}
|
||||
|
||||
@@ -578,6 +648,9 @@ async function handleKiroRegisterCommand(message) {
|
||||
return waitForKiroRegisterStateChange(message.payload || {});
|
||||
case 'EXECUTE_NODE': {
|
||||
const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim();
|
||||
if (nodeId === 'kiro-open-register-page') {
|
||||
return selectKiroBuilderId();
|
||||
}
|
||||
if (nodeId === 'kiro-submit-email') {
|
||||
return submitKiroEmail(message.payload || {});
|
||||
}
|
||||
|
||||
+174
-1171
File diff suppressed because it is too large
Load Diff
@@ -255,6 +255,7 @@
|
||||
'content/kiro/register-page': {
|
||||
sourceId: 'kiro-register-page',
|
||||
commands: [
|
||||
'kiro-open-register-page',
|
||||
'kiro-submit-email',
|
||||
'kiro-submit-name',
|
||||
'kiro-submit-verification-code',
|
||||
@@ -362,7 +363,7 @@
|
||||
'kiro-runtime-status': {
|
||||
id: 'kiro-runtime-status',
|
||||
label: 'Kiro 运行态',
|
||||
rowIds: ['row-kiro-device-code', 'row-kiro-login-url', 'row-kiro-upload-status'],
|
||||
rowIds: ['row-kiro-web-status', 'row-kiro-login-url', 'row-kiro-upload-status'],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -143,7 +143,13 @@
|
||||
|| normalizedHost.includes(`.${normalizedFamily}.`);
|
||||
}
|
||||
|
||||
function isKiroAuthHost(hostname = '') {
|
||||
function isKiroWebHost(hostname = '') {
|
||||
const normalized = normalizeHostname(hostname);
|
||||
return normalized === 'app.kiro.dev'
|
||||
|| normalized === 'kiro.dev';
|
||||
}
|
||||
|
||||
function isKiroAwsAuthHost(hostname = '') {
|
||||
const normalized = normalizeHostname(hostname);
|
||||
return normalized === 'view.awsapps.com'
|
||||
|| normalized === 'login.awsapps.com'
|
||||
@@ -153,6 +159,10 @@
|
||||
|| normalized.endsWith('.amazonaws.com');
|
||||
}
|
||||
|
||||
function isKiroRegisterHost(hostname = '') {
|
||||
return isKiroWebHost(hostname) || isKiroAwsAuthHost(hostname);
|
||||
}
|
||||
|
||||
function getRuntimeSourceDefinitions() {
|
||||
return {
|
||||
...(typeof flowRegistryApi.getRuntimeSourceDefinitions === 'function'
|
||||
@@ -327,8 +337,9 @@
|
||||
case 'gopay-flow':
|
||||
return /gopay|gojek/i.test(candidate.hostname);
|
||||
case 'kiro-register-page':
|
||||
return isKiroRegisterHost(candidate.hostname);
|
||||
case 'kiro-desktop-authorize':
|
||||
return isKiroAuthHost(candidate.hostname);
|
||||
return isKiroAwsAuthHost(candidate.hostname);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -351,7 +362,7 @@
|
||||
if (normalizedHostname === 'www.icloud.com' || normalizedHostname === 'www.icloud.com.cn') return 'icloud-mail';
|
||||
if (normalizedUrl.includes('duckduckgo.com/email/settings/autofill')) return 'duck-mail';
|
||||
if (normalizedUrl.includes('2925.com')) return 'mail-2925';
|
||||
if (isKiroAuthHost(normalizedHostname)) return 'kiro-register-page';
|
||||
if (isKiroRegisterHost(normalizedHostname)) return 'kiro-register-page';
|
||||
if (isSignupEntryHost(normalizedHostname)) return 'chatgpt';
|
||||
return 'unknown-source';
|
||||
}
|
||||
|
||||
@@ -267,13 +267,13 @@
|
||||
<span class="data-label">连接测试</span>
|
||||
<span id="display-kiro-rs-test-status" class="data-value mono">未测试</span>
|
||||
</div>
|
||||
<div class="data-row" id="row-kiro-device-code" style="display:none;">
|
||||
<span class="data-label">授权码</span>
|
||||
<span id="display-kiro-device-code" class="data-value mono">未生成</span>
|
||||
<div class="data-row" id="row-kiro-web-status" style="display:none;">
|
||||
<span class="data-label">Kiro Web</span>
|
||||
<span id="display-kiro-web-status" class="data-value mono">未开始</span>
|
||||
</div>
|
||||
<div class="data-row" id="row-kiro-login-url" style="display:none;">
|
||||
<span class="data-label">授权地址</span>
|
||||
<span id="display-kiro-login-url" class="data-value mono">未生成</span>
|
||||
<span class="data-label">注册入口</span>
|
||||
<span id="display-kiro-login-url" class="data-value mono">未打开</span>
|
||||
</div>
|
||||
<div class="data-row" id="row-kiro-upload-status" style="display:none;">
|
||||
<span class="data-label">上传状态</span>
|
||||
|
||||
@@ -179,8 +179,8 @@ const inputKiroRsKey = document.getElementById('input-kiro-rs-key');
|
||||
const btnTestKiroRs = document.getElementById('btn-test-kiro-rs');
|
||||
const rowKiroRsTestStatus = document.getElementById('row-kiro-rs-test-status');
|
||||
const displayKiroRsTestStatus = document.getElementById('display-kiro-rs-test-status');
|
||||
const rowKiroDeviceCode = document.getElementById('row-kiro-device-code');
|
||||
const displayKiroDeviceCode = document.getElementById('display-kiro-device-code');
|
||||
const rowKiroWebStatus = document.getElementById('row-kiro-web-status');
|
||||
const displayKiroWebStatus = document.getElementById('display-kiro-web-status');
|
||||
const rowKiroLoginUrl = document.getElementById('row-kiro-login-url');
|
||||
const displayKiroLoginUrl = document.getElementById('display-kiro-login-url');
|
||||
const rowKiroUploadStatus = document.getElementById('row-kiro-upload-status');
|
||||
@@ -10215,19 +10215,20 @@ function applySettingsState(state) {
|
||||
if (typeof displayKiroRsTestStatus !== 'undefined' && displayKiroRsTestStatus) {
|
||||
displayKiroRsTestStatus.textContent = kiroRsConnectionTestStatusText;
|
||||
}
|
||||
if (typeof displayKiroDeviceCode !== 'undefined' && displayKiroDeviceCode) {
|
||||
const kiroDeviceCode = String(
|
||||
state?.kiroRuntime?.register?.userCode
|
||||
if (typeof displayKiroWebStatus !== 'undefined' && displayKiroWebStatus) {
|
||||
const kiroWebStatus = String(
|
||||
state?.kiroRuntime?.webAuth?.status
|
||||
|| state?.kiroRuntime?.register?.status
|
||||
|| ''
|
||||
).trim();
|
||||
displayKiroDeviceCode.textContent = kiroDeviceCode || '未生成';
|
||||
displayKiroWebStatus.textContent = kiroWebStatus || '未开始';
|
||||
}
|
||||
if (typeof displayKiroLoginUrl !== 'undefined' && displayKiroLoginUrl) {
|
||||
const kiroLoginUrl = String(
|
||||
state?.kiroRuntime?.register?.loginUrl
|
||||
|| ''
|
||||
).trim();
|
||||
displayKiroLoginUrl.textContent = kiroLoginUrl || '未生成';
|
||||
displayKiroLoginUrl.textContent = kiroLoginUrl || '未打开';
|
||||
}
|
||||
if (typeof displayKiroUploadStatus !== 'undefined' && displayKiroUploadStatus) {
|
||||
const kiroUploadStatus = String(
|
||||
|
||||
@@ -83,6 +83,13 @@ test('kiro desktop authorize runner uses a shared 3-minute page-load timeout bud
|
||||
assert.match(source, /finalizeDesktopAuthorizeCallback/);
|
||||
});
|
||||
|
||||
test('kiro desktop authorization is gated by completed Kiro Web sign-in', () => {
|
||||
const source = fs.readFileSync('background/kiro/desktop-authorize-runner.js', 'utf8');
|
||||
assert.match(source, /runtimeState\.register\?\.status/);
|
||||
assert.match(source, /runtimeState\.webAuth\?\.status/);
|
||||
assert.match(source, /Kiro Web 登录态尚未建立/);
|
||||
});
|
||||
|
||||
test('executeKiroCompleteDesktopAuthorize finishes from callback page without waiting for tracker replay', async () => {
|
||||
const api = loadDesktopAuthorizeRunnerApi();
|
||||
let currentState = createDesktopAuthorizeState();
|
||||
|
||||
@@ -10,52 +10,18 @@ function loadRegisterRunnerApi() {
|
||||
return globalScope.MultiPageBackgroundKiroRegisterRunner;
|
||||
}
|
||||
|
||||
test('kiro register runner module exposes a factory and device login bootstrap helper', () => {
|
||||
test('kiro register runner module exposes a factory and Kiro official sign-in entry', () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
assert.equal(typeof api?.createKiroRegisterRunner, 'function');
|
||||
assert.equal(typeof api?.startBuilderIdDeviceLogin, 'function');
|
||||
assert.equal(api?.KIRO_SIGNIN_URL, 'https://app.kiro.dev/signin');
|
||||
});
|
||||
|
||||
test('startBuilderIdDeviceLogin registers Builder ID client and returns login bootstrap payload', async () => {
|
||||
const api = loadRegisterRunnerApi();
|
||||
const requests = [];
|
||||
const result = await api.startBuilderIdDeviceLogin('us-east-1', async (url, options = {}) => {
|
||||
requests.push({ url, options });
|
||||
if (url.endsWith('/client/register')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (url.endsWith('/device_authorization')) {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => JSON.stringify({
|
||||
deviceCode: 'device-code-001',
|
||||
userCode: 'ABCD-1234',
|
||||
verificationUri: 'https://view.awsapps.com/start',
|
||||
verificationUriComplete: 'https://view.awsapps.com/start?user_code=ABCD-1234',
|
||||
interval: 7,
|
||||
expiresIn: 600,
|
||||
}),
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected request: ${url}`);
|
||||
});
|
||||
|
||||
assert.equal(requests.length, 2);
|
||||
assert.equal(requests[0].url, 'https://oidc.us-east-1.amazonaws.com/client/register');
|
||||
assert.equal(requests[1].url, 'https://oidc.us-east-1.amazonaws.com/device_authorization');
|
||||
assert.equal(result.clientId, 'client-001');
|
||||
assert.equal(result.clientSecret, 'secret-001');
|
||||
assert.equal(result.deviceCode, 'device-code-001');
|
||||
assert.equal(result.userCode, 'ABCD-1234');
|
||||
assert.equal(result.verificationUriComplete, 'https://view.awsapps.com/start?user_code=ABCD-1234');
|
||||
assert.equal(result.interval, 7);
|
||||
assert.equal(result.region, 'us-east-1');
|
||||
test('kiro register runner removed the old AWS device authorization bootstrap', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
assert.doesNotMatch(source, /startBuilderIdDeviceLogin/);
|
||||
assert.doesNotMatch(source, /device_authorization/);
|
||||
assert.doesNotMatch(source, /verificationUriComplete/);
|
||||
assert.match(source, /https:\/\/app\.kiro\.dev\/signin/);
|
||||
});
|
||||
|
||||
test('kiro register runner uses a shared 3-minute page-load timeout budget', () => {
|
||||
@@ -66,3 +32,10 @@ test('kiro register runner uses a shared 3-minute page-load timeout budget', ()
|
||||
assert.match(source, /timeoutBudget\.getRemainingMs\(1000\)/);
|
||||
assert.match(source, /onRetryableError: buildKiroRetryRecovery\(tabId, \{\s*\.\.\.options,\s*timeoutBudget,/);
|
||||
});
|
||||
|
||||
test('kiro register consent step treats Kiro Web signed-in page as completion', () => {
|
||||
const source = fs.readFileSync('background/kiro/register-runner.js', 'utf8');
|
||||
assert.match(source, /targetStates: \['authorization_page', 'kiro_web_signed_in'\]/);
|
||||
assert.match(source, /landingResult\?\.state !== 'kiro_web_signed_in'/);
|
||||
assert.doesNotMatch(source, /landingResult\?\.state !== 'success_page'/);
|
||||
});
|
||||
|
||||
@@ -32,10 +32,14 @@ test('kiro state module exposes canonical nested kiroRuntime view', () => {
|
||||
register: {
|
||||
email: 'aws-user@example.com',
|
||||
fullName: 'Ada Lovelace',
|
||||
userCode: 'ABCD-1234',
|
||||
loginUrl: 'https://device.example.com/complete',
|
||||
loginUrl: 'https://app.kiro.dev/signin',
|
||||
status: 'waiting_name',
|
||||
},
|
||||
webAuth: {
|
||||
status: 'signin_started',
|
||||
hasAccessToken: false,
|
||||
hasSessionToken: false,
|
||||
},
|
||||
desktopAuth: {
|
||||
clientId: 'client-001',
|
||||
clientSecret: 'secret-001',
|
||||
@@ -54,7 +58,8 @@ test('kiro state module exposes canonical nested kiroRuntime view', () => {
|
||||
assert.equal(view.kiroRuntime.session.currentStage, 'desktop-authorize');
|
||||
assert.equal(view.kiroRuntime.session.registerTabId, 88);
|
||||
assert.equal(view.kiroRuntime.register.email, 'aws-user@example.com');
|
||||
assert.equal(view.kiroRuntime.register.userCode, 'ABCD-1234');
|
||||
assert.equal(view.kiroRuntime.register.loginUrl, 'https://app.kiro.dev/signin');
|
||||
assert.equal(view.kiroRuntime.webAuth.status, 'signin_started');
|
||||
assert.equal(view.kiroRuntime.desktopAuth.clientId, 'client-001');
|
||||
assert.equal(view.kiroRuntime.desktopAuth.refreshToken, 'refresh-001');
|
||||
assert.equal(view.kiroRuntime.upload.status, 'ready_to_upload');
|
||||
|
||||
@@ -146,6 +146,9 @@ const captured = [];
|
||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const self = {
|
||||
MultiPageStepDefinitions: {
|
||||
getSteps(options) {
|
||||
@@ -160,8 +163,10 @@ const self = {
|
||||
};
|
||||
${extractFunction('isPlusModeState')}
|
||||
${extractFunction('normalizePlusPaymentMethod')}
|
||||
${extractFunction('normalizePlusAccountAccessStrategy')}
|
||||
${extractFunction('normalizeSignupMethod')}
|
||||
${extractFunction('getSignupMethodForStepDefinitions')}
|
||||
${extractFunction('buildResolvedStepDefinitionState')}
|
||||
${extractFunction('getStepDefinitionsForState')}
|
||||
return {
|
||||
getCaptured: () => captured.slice(),
|
||||
@@ -180,6 +185,7 @@ return {
|
||||
activeFlowId: 'openai',
|
||||
plusModeEnabled: true,
|
||||
plusPaymentMethod: 'gopay',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
signupMethod: 'phone',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
}]);
|
||||
|
||||
@@ -77,3 +77,16 @@ test('kiro register content does not misclassify the normal name page as a proxy
|
||||
|
||||
assert.equal(fatal, null);
|
||||
});
|
||||
|
||||
test('kiro register content treats Kiro web success callback as signed in', () => {
|
||||
const harness = createHarness({
|
||||
href: 'https://app.kiro.dev/signin?auth_status=success&redirect_from=KiroIDE',
|
||||
hostname: 'app.kiro.dev',
|
||||
title: 'Kiro',
|
||||
bodyText: 'Signed in',
|
||||
});
|
||||
|
||||
const detected = harness.detectKiroRegisterPageState();
|
||||
|
||||
assert.equal(detected.state, 'kiro_web_signed_in');
|
||||
});
|
||||
|
||||
@@ -195,6 +195,9 @@ test('collectSettingsPayload omits custom password and local sync settings in co
|
||||
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
|
||||
let latestState = { contributionMode: true };
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let cloudflareDomainEditMode = false;
|
||||
let cloudflareTempEmailDomainEditMode = false;
|
||||
const selectCfDomain = { value: 'example.com' };
|
||||
@@ -297,6 +300,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
|
||||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number.isFinite(Number(value)) ? Number(value) : fallback; }
|
||||
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
|
||||
function normalizePhoneSmsProvider(value = '') { return String(value || '').trim().toLowerCase() === '5sim' ? '5sim' : 'hero-sms'; }
|
||||
function getSelectedPhoneSmsProvider() { return normalizePhoneSmsProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider); }
|
||||
function normalizeFiveSimCountryId(value, fallback = DEFAULT_FIVE_SIM_COUNTRY_ID) { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || fallback; }
|
||||
|
||||
@@ -43,7 +43,7 @@ test('sidepanel html exposes flow selector and kiro source fields', () => {
|
||||
'id="row-kiro-rs-key"',
|
||||
'id="btn-test-kiro-rs"',
|
||||
'id="row-kiro-rs-test-status"',
|
||||
'id="row-kiro-device-code"',
|
||||
'id="row-kiro-web-status"',
|
||||
'id="row-kiro-login-url"',
|
||||
'id="row-kiro-upload-status"',
|
||||
].forEach((snippet) => {
|
||||
@@ -73,12 +73,14 @@ const window = {
|
||||
let latestState = { activeFlowId: 'openai' };
|
||||
let currentPlusModeEnabled = false;
|
||||
let currentPlusPaymentMethod = 'paypal';
|
||||
let currentPlusAccountAccessStrategy = 'oauth';
|
||||
let currentSignupMethod = 'email';
|
||||
let currentPhoneSignupReloginAfterBindEmailEnabled = false;
|
||||
let currentStepDefinitionFlowId = 'openai';
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const DEFAULT_SIGNUP_METHOD = 'email';
|
||||
const DEFAULT_PLUS_PAYMENT_METHOD = 'paypal';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = 'oauth';
|
||||
let stepDefinitions = [{ id: 6, key: 'openai' }];
|
||||
let STEP_IDS = [6];
|
||||
let STEP_DEFAULT_STATUSES = { 6: 'pending' };
|
||||
@@ -86,6 +88,9 @@ let SKIPPABLE_STEPS = new Set([6]);
|
||||
function renderStepsList() {
|
||||
calls.push({ type: 'render', stepIds: [...STEP_IDS] });
|
||||
}
|
||||
function normalizePlusAccountAccessStrategy(value = '') {
|
||||
return String(value || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY).trim().toLowerCase() || DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY;
|
||||
}
|
||||
${bundle}
|
||||
return {
|
||||
calls,
|
||||
@@ -110,6 +115,7 @@ return {
|
||||
activeFlowId: 'kiro',
|
||||
plusModeEnabled: false,
|
||||
plusPaymentMethod: 'paypal',
|
||||
plusAccountAccessStrategy: 'oauth',
|
||||
signupMethod: 'email',
|
||||
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||
},
|
||||
|
||||
@@ -86,6 +86,9 @@ test('collectSettingsPayload persists icloud target mailbox settings', () => {
|
||||
const api = new Function(`
|
||||
let latestState = { contributionMode: false };
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let cloudflareDomainEditMode = false;
|
||||
let cloudflareTempEmailDomainEditMode = false;
|
||||
const selectCfDomain = { value: '' };
|
||||
@@ -220,6 +223,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
|
||||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
|
||||
function normalizePhoneSmsProvider(value = '') { return String(value || '').trim().toLowerCase() === '5sim' ? '5sim' : 'hero-sms'; }
|
||||
function setPhoneSmsProviderSelectValue(provider) {
|
||||
const normalizedProvider = normalizePhoneSmsProvider(provider);
|
||||
@@ -363,6 +367,9 @@ test('applySettingsState restores icloud forward mailbox settings before UI refr
|
||||
|
||||
const api = new Function('calls', `
|
||||
let latestState = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
const inputEmail = { value: '' };
|
||||
const inputVpsUrl = { value: '' };
|
||||
const inputVpsPassword = { value: '' };
|
||||
@@ -460,6 +467,7 @@ function normalizePanelMode(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
|
||||
}
|
||||
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
|
||||
function isCustomMailProvider() { return false; }
|
||||
function setMail2925Mode() {}
|
||||
function normalizeIcloudFetchMode(value) { return String(value || '') === 'always_new' ? 'always_new' : 'reuse_existing'; }
|
||||
|
||||
@@ -159,6 +159,9 @@ let latestState = {
|
||||
currentMail2925AccountId: 'acc-2',
|
||||
};
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let cloudflareDomainEditMode = false;
|
||||
let cloudflareTempEmailDomainEditMode = false;
|
||||
const selectCfDomain = { value: 'example.com' };
|
||||
@@ -258,6 +261,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
|
||||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
|
||||
function normalizePhoneSmsProvider(value = '') { return String(value || '').trim().toLowerCase() === '5sim' ? '5sim' : 'hero-sms'; }
|
||||
function getSelectedPhoneSmsProvider() { return normalizePhoneSmsProvider(selectPhoneSmsProvider?.value || latestState?.phoneSmsProvider); }
|
||||
function normalizeFiveSimCountryId(value, fallback = DEFAULT_FIVE_SIM_COUNTRY_ID) { return String(value || '').trim().toLowerCase().replace(/[^a-z0-9_-]+/g, '') || fallback; }
|
||||
|
||||
@@ -873,6 +873,9 @@ return {
|
||||
test('collectSettingsPayload keeps local helper sync enabled while persisting sms toggle state', () => {
|
||||
const api = new Function('normalizeIcloudTargetMailboxType', 'normalizeIcloudForwardMailProvider', `
|
||||
const window = {};
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
|
||||
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
|
||||
const DEFAULT_PLUS_ACCOUNT_ACCESS_STRATEGY = PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH;
|
||||
let latestState = {
|
||||
contributionMode: false,
|
||||
mail2925UseAccountPool: false,
|
||||
@@ -1025,6 +1028,7 @@ function normalizeAutoRunThreadIntervalMinutes(value) { return Number(value) ||
|
||||
function normalizeAutoDelayMinutes(value) { return Number(value) || 30; }
|
||||
function normalizeAutoStepDelaySeconds(value) { return value === '' ? null : Number(value); }
|
||||
function normalizeVerificationResendCount(value, fallback) { return Number(value) || fallback; }
|
||||
function normalizePlusAccountAccessStrategy(value = '') { return String(value || '').trim().toLowerCase() === PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION ? PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION : PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH; }
|
||||
${extractFunction('normalizePhoneSmsProvider')}
|
||||
${extractFunction('normalizePhoneSmsProviderValue')}
|
||||
${extractFunction('normalizeFiveSimCountryCode')}
|
||||
|
||||
@@ -70,8 +70,8 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
|
||||
);
|
||||
assert.equal(
|
||||
registry.detectSourceFromLocation({
|
||||
url: 'https://view.awsapps.com/start',
|
||||
hostname: 'view.awsapps.com',
|
||||
url: 'https://app.kiro.dev/signin',
|
||||
hostname: 'app.kiro.dev',
|
||||
}),
|
||||
'kiro-register-page'
|
||||
);
|
||||
@@ -91,11 +91,19 @@ test('shared source registry exposes canonical Kiro sources and drivers', () =>
|
||||
'unknown-source'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
registry.matchesSourceUrlFamily(
|
||||
'kiro-register-page',
|
||||
'https://app.kiro.dev/signin',
|
||||
'https://app.kiro.dev/signin'
|
||||
),
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
registry.matchesSourceUrlFamily(
|
||||
'kiro-register-page',
|
||||
'https://signin.aws/register',
|
||||
'https://view.awsapps.com/start'
|
||||
'https://app.kiro.dev/signin'
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
+12
-9
@@ -170,7 +170,7 @@
|
||||
- `contributionStatusMessage`
|
||||
- `contributionLastPollAt`
|
||||
- OAuth 链接
|
||||
- Kiro 运行态:使用 `kiroRuntime.session / register / desktopAuth / upload` 命名空间,保存注册页授权码/地址、桌面授权 PKCE 凭据、上传状态和当前 Kiro 会话进度
|
||||
- Kiro 运行态:使用 `kiroRuntime.session / register / webAuth / desktopAuth / upload` 命名空间,保存 Kiro 官方登录入口、Builder ID 注册页状态、Kiro Web 登录态摘要、桌面授权 PKCE 凭据、上传状态和当前 Kiro 会话进度
|
||||
- 当前轮冻结后的注册方式 `resolvedSignupMethod`
|
||||
- 当前统一账号标识 `accountIdentifierType / accountIdentifier`
|
||||
- 当前邮箱 / 密码
|
||||
@@ -620,7 +620,7 @@ Plus 模式可见步骤:
|
||||
|
||||
## 6.2 Kiro Flow 链路
|
||||
|
||||
Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“注册 Builder ID -> 获取桌面端 refresh token -> 上传到 `kiro.rs`”这条链路。
|
||||
Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“Kiro 官方登录入口 -> Builder ID 注册 -> Kiro Web 登录态 -> 桌面端 refresh token -> 上传到 `kiro.rs`”这条链路。
|
||||
|
||||
当前 Kiro workflow 固定为 9 个节点:
|
||||
|
||||
@@ -638,14 +638,17 @@ Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“注册 B
|
||||
|
||||
1. sidepanel 把当前 flow 切到 `kiro`,target 固定为 `kiro-rs`
|
||||
2. 用户填写 `kiro.rs URL / API Key`
|
||||
3. 步骤 1~6 在 AWS Builder ID 注册页完成:
|
||||
- 清理 AWS Builder ID 相关 cookies
|
||||
- 打开注册页并等待邮箱输入框
|
||||
3. 步骤 1~6 先进入 Kiro 官方登录页,再完成 Builder ID 注册与 Kiro Web 登录态确认:
|
||||
- 清理 Kiro / AWS Builder ID 相关 cookies
|
||||
- 打开 `https://app.kiro.dev/signin`
|
||||
- 在 Kiro 官方登录页选择 Builder ID 登录入口
|
||||
- 等待跳转到 AWS Builder ID 邮箱输入页
|
||||
- 通过共享邮箱服务生成注册邮箱并提交
|
||||
- 填写姓名、拉取验证码、设置账户密码
|
||||
- 点击“确认并继续 / 允许访问”,完成 Builder ID 注册页授权
|
||||
- 保存注册页授权码、授权地址、注册邮箱和当前页面状态到 `kiroRuntime.register`
|
||||
4. 步骤 7 在后台注册桌面 OIDC client,并生成 PKCE 参数:
|
||||
- 点击“确认并继续 / 允许访问”
|
||||
- 等待回到 Kiro Web 登录完成页,确认 `kiroRuntime.webAuth.status = signed_in`
|
||||
- 保存注册邮箱、注册页状态和 Kiro Web 登录态摘要到 `kiroRuntime.register / webAuth`
|
||||
4. 步骤 7 必须在 `register.status = completed` 且 `webAuth.status = signed_in` 后执行;后台注册桌面 OIDC client,并生成 PKCE 参数:
|
||||
- 生成 `state / codeVerifier / codeChallenge`
|
||||
- 生成 localhost callback `redirectUri`
|
||||
- 构建桌面授权地址并打开授权标签页
|
||||
@@ -668,7 +671,7 @@ Kiro flow 的目标不是复用 OpenAI 注册链,而是独立完成“注册 B
|
||||
- Kiro 自动运行走通用 linear node runner,但执行器、运行态、页面驱动和上传器全部独立在 `background/kiro/*` 与 `content/kiro/*`
|
||||
- Kiro 运行态统一落在 `kiroRuntime` 命名空间,不再散落为多个平铺字段
|
||||
- `kiro.rs` 上传不再发送 `profileArn`,machineId 固定按 `sha256("KotlinNativeAPI/" + refreshToken)` 生成
|
||||
- Kiro 注册页和桌面授权页不再静态注入 content script,而是由后台按 source 动态注入,避免同域 AWS 页面被错误归到旧 source
|
||||
- Kiro 注册页覆盖 `app.kiro.dev` 与 AWS Builder ID 页面;桌面授权页只绑定 AWS authorize 页面,二者由后台按 source 动态注入,避免同域 AWS 页面被错误归到另一条 Kiro 子链路
|
||||
|
||||
## 7. 邮箱与 provider 链路
|
||||
|
||||
|
||||
+3
-3
@@ -52,8 +52,8 @@
|
||||
- `background/cloudmail-provider.js`:Cloud Mail / SkyMail 后台 provider 模块,负责 Token 获取、邮箱创建、邮件列表读取、验证码轮询和生成/转发收件目标邮箱选择;新生成的 Cloud Mail 地址会统一走共享注册邮箱状态持久化,既回写带来源标记的注册邮箱运行态,也能在 Step 8 `add-email` 的手机号身份链路中保留 `signupPhone* / accountIdentifier*`。
|
||||
- `background/yyds-mail-provider.js`:YYDS Mail 后台 provider 模块,负责通过 `X-API-Key` 创建临时邮箱、保存返回的地址与临时 Bearer token、按 `/messages` 与 `/messages/{id}` 读取验证码邮件,并在成功收尾或 reset 时清空当前运行态邮箱。
|
||||
- `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` 时,会按当前目标轮次读取邮箱池中的对应邮箱;Duck 生成前会优先结合 `registrationEmailState` 与侧栏当前可见邮箱解析对比基线,避免重复拿到旧地址;所有生成结果都会统一走共享注册邮箱状态持久化,普通邮箱流仍切回邮箱身份,Step 8 `add-email` 的手机号身份流则保留当前手机号身份。
|
||||
- `background/kiro/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。
|
||||
- `background/kiro/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、AWS Builder ID cookies 清理、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及注册页运行态推进。
|
||||
- `background/kiro/state.js`:Kiro 独立运行态模型与状态工具,负责 `kiroRuntime.session / register / webAuth / desktopAuth / upload` 的默认值、归一化、节点完成回写、下游重置和 fresh-attempt keep-state 构建。
|
||||
- `background/kiro/register-runner.js`:Kiro 注册页执行器,负责步骤 1~6 的编排、Kiro / AWS Builder ID cookies 清理、打开 `https://app.kiro.dev/signin`、选择 Builder ID 入口、注册标签页管理、邮箱/姓名/验证码/密码/授权确认,以及 Kiro Web 登录态推进。
|
||||
- `background/kiro/desktop-client.js`:Kiro 桌面授权协议层,负责桌面 OIDC client 注册、PKCE 参数生成、授权地址组装、callback 参数校验和授权码换 token。
|
||||
- `background/kiro/desktop-authorize-runner.js`:Kiro 桌面授权执行器,负责步骤 7~8 的标签页打开、授权页轮询、localhost callback 捕获,以及桌面授权完成后的凭据落库。
|
||||
- `background/kiro/publisher-kiro-rs.js`:Kiro 发布器,负责 `kiro.rs` 地址归一化、连通性探测、machineId 计算、上传 payload 构建与最终凭据上传。
|
||||
@@ -98,7 +98,7 @@
|
||||
- `content/gmail-mail.js`:Gmail 邮箱轮询脚本,负责在 Gmail 页面中匹配验证码邮件。
|
||||
- `content/icloud-mail.js`:iCloud 邮箱页面脚本,负责在 iCloud Mail 页面中读取邮件详情和验证码。
|
||||
- `content/inbucket-mail.js`:Inbucket 邮箱轮询脚本,负责在 Inbucket 页面中读取、删除验证码邮件。
|
||||
- `content/kiro/register-page.js`:Kiro 注册页内容脚本,负责识别注册页状态并执行邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。
|
||||
- `content/kiro/register-page.js`:Kiro 注册页内容脚本,负责识别 Kiro 官方登录页、AWS Builder ID 注册页、Kiro Web 登录完成页,并执行选择 Builder ID、填写邮箱、姓名、验证码、密码与“确认并继续 / 允许访问”等页面交互。
|
||||
- `content/kiro/desktop-authorize-page.js`:Kiro 桌面授权页内容脚本,负责识别桌面授权过程中的 relogin、OTP、consent 与跳转完成状态,并向后台汇报授权页进度。
|
||||
- `content/mail-163.js`:163 / 163 VIP / 126 邮箱轮询脚本,负责网页邮箱验证码读取和邮件清理。
|
||||
- `content/mail-2925.js`:2925 邮箱页面脚本,负责 2925 邮箱自动登录态确认、收件轮询、按步骤会话隔离“已试验证码”、在每次重发验证码之间执行一轮最多 15 次的邮箱刷新轮询、命中邮件后立即删当前邮件,以及在成功后配合后台执行整箱清理;若页面出现“子邮箱已达上限邮箱”提示,会立即上报后台进入切号链路;当后台显式开启 `mail2925MatchTargetEmail` 时,会对邮件里显式出现的目标邮箱做弱匹配,避免 `receive` 模式误捞别人的验证码。
|
||||
|
||||
Reference in New Issue
Block a user