重构多 flow 邮件轮询并接入 Grok SSO

This commit is contained in:
QLHazyCoder
2026-05-23 04:29:37 +08:00
parent 5610262f7d
commit 2ea9ad978e
37 changed files with 4345 additions and 843 deletions
+691
View File
@@ -0,0 +1,691 @@
(function attachBackgroundGrokRegisterRunner(root, factory) {
root.MultiPageBackgroundGrokRegisterRunner = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGrokRegisterRunnerModule() {
const GROK_SIGNUP_URL = 'https://accounts.x.ai/sign-up?redirect=grok-com';
const GROK_REGISTER_PAGE_SOURCE_ID = 'grok-register-page';
const DEFAULT_GROK_PAGE_TIMEOUT_MS = 90 * 1000;
const GROK_POST_PROFILE_CF_WAIT_MS = 20 * 1000;
const GROK_PRE_SSO_EXTRACT_WAIT_MS = 10 * 1000;
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
const GROK_COOKIE_CLEAR_DOMAINS = Object.freeze([
'x.ai',
'accounts.x.ai',
'grok.com',
]);
function cleanString(value = '') {
return String(value ?? '').trim();
}
function getErrorMessage(error) {
return error instanceof Error ? error.message : cleanString(error) || '未知错误';
}
function createGeneratedPassword() {
const alphabet = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*';
let output = '';
for (let index = 0; index < 18; index += 1) {
output += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return `${output}aA1!`;
}
function createGrokRegisterRunner(deps = {}) {
const {
addLog = async () => {},
chrome = (typeof globalThis !== 'undefined' ? globalThis.chrome : null),
completeNodeFromBackground,
ensureContentScriptReadyOnTab = null,
generatePassword = null,
generateRandomName = null,
getState = async () => ({}),
getTabId = async () => null,
isTabAlive = async () => false,
pollFlowVerificationCode = null,
registerTab = async () => {},
resolveSignupEmailForFlow = null,
reuseOrCreateTab = async () => null,
sendToContentScriptResilient = null,
setPasswordState = async () => {},
setState = async () => {},
sleepWithStop = async (ms) => {
await new Promise((resolve) => setTimeout(resolve, ms));
},
throwIfStopped = () => {},
waitForTabStableComplete = null,
GROK_REGISTER_INJECT_FILES = null,
markCurrentRegistrationAccountUsed = null,
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Grok register runner requires completeNodeFromBackground.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function activateTab(tabId) {
if (!Number.isInteger(tabId) || !chrome?.tabs?.update) {
return;
}
await chrome.tabs.update(tabId, { active: true });
}
async function getExecutionState(state = {}) {
if (state && typeof state === 'object' && !Array.isArray(state) && Object.keys(state).length) {
return state;
}
return getState();
}
async function persistState(patch = {}) {
await setState(patch);
return patch;
}
function buildGrokRuntimePatch(patch = {}) {
return {
runtimeState: {
flowState: {
grok: patch,
},
},
};
}
async function completeNode(nodeId, patch = {}) {
await persistState(patch);
await completeNodeFromBackground(nodeId, patch);
return patch;
}
async function isUsableTabId(tabId) {
if (!Number.isInteger(tabId)) {
return false;
}
if (typeof isTabAlive === 'function' && await isTabAlive(GROK_REGISTER_PAGE_SOURCE_ID)) {
return true;
}
if (chrome?.tabs?.get) {
const tab = await chrome.tabs.get(tabId).catch(() => null);
return Boolean(tab?.id === tabId);
}
return true;
}
async function ensureGrokRegisterTab(state = {}, options = {}) {
const existingTabId = Number(
state?.grokRegisterTabId
|| state?.runtimeState?.flowState?.grok?.session?.registerTabId
|| state?.tabRegistry?.[GROK_REGISTER_PAGE_SOURCE_ID]?.tabId
|| 0
);
if (Number.isInteger(existingTabId) && existingTabId > 0 && await isUsableTabId(existingTabId)) {
await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, existingTabId);
return existingTabId;
}
const tabId = await getTabId(GROK_REGISTER_PAGE_SOURCE_ID);
if (Number.isInteger(tabId) && await isUsableTabId(tabId)) {
await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, tabId);
return tabId;
}
if (!options.openIfMissing) {
throw new Error(options.missingMessage || '缺少 Grok 注册页,请先执行步骤 1。');
}
const openedTabId = await reuseOrCreateTab(GROK_REGISTER_PAGE_SOURCE_ID, GROK_SIGNUP_URL, {
inject: Array.isArray(GROK_REGISTER_INJECT_FILES) ? GROK_REGISTER_INJECT_FILES : null,
injectSource: GROK_REGISTER_PAGE_SOURCE_ID,
});
if (!Number.isInteger(openedTabId)) {
throw new Error('无法打开 Grok 注册页。');
}
await registerTab(GROK_REGISTER_PAGE_SOURCE_ID, openedTabId);
return openedTabId;
}
async function ensureContentReady(tabId, options = {}) {
if (!Number.isInteger(tabId)) {
throw new Error('缺少 Grok 注册页标签页,无法连接内容脚本。');
}
if (typeof waitForTabStableComplete === 'function') {
await waitForTabStableComplete(tabId, {
timeoutMs: options.timeoutMs || DEFAULT_GROK_PAGE_TIMEOUT_MS,
retryDelayMs: 300,
stableMs: Number(options.stableMs) || 1200,
initialDelayMs: Number(options.initialDelayMs) || 120,
});
}
if (typeof ensureContentScriptReadyOnTab === 'function') {
await ensureContentScriptReadyOnTab(GROK_REGISTER_PAGE_SOURCE_ID, tabId, {
inject: Array.isArray(GROK_REGISTER_INJECT_FILES) ? GROK_REGISTER_INJECT_FILES : null,
injectSource: GROK_REGISTER_PAGE_SOURCE_ID,
timeoutMs: options.timeoutMs || DEFAULT_GROK_PAGE_TIMEOUT_MS,
retryDelayMs: 700,
logMessage: options.logMessage || 'Grok 注册页内容脚本未就绪,正在等待页面恢复...',
});
}
}
async function sendGrokCommand(nodeId, payload = {}, options = {}) {
if (typeof sendToContentScriptResilient !== 'function') {
throw new Error('Grok 注册页通信能力不可用。');
}
const result = await sendToContentScriptResilient(GROK_REGISTER_PAGE_SOURCE_ID, {
type: 'EXECUTE_NODE',
nodeId,
step: options.step || 0,
source: 'background',
payload,
}, {
timeoutMs: options.timeoutMs || 45000,
retryDelayMs: 700,
logMessage: options.logMessage || '',
});
if (result?.error) {
throw new Error(result.error);
}
return result || {};
}
function shouldClearGrokCookie(cookie = {}) {
const domain = cleanString(cookie.domain).replace(/^\.+/, '').toLowerCase();
return GROK_COOKIE_CLEAR_DOMAINS.some((target) => (
domain === target || domain.endsWith(`.${target}`)
));
}
function buildCookieRemovalUrl(cookie = {}) {
const host = cleanString(cookie.domain).replace(/^\.+/, '').toLowerCase();
const path = cleanString(cookie.path) || '/';
return `https://${host}${path.startsWith('/') ? path : `/${path}`}`;
}
async function clearGrokCookiesBeforeStep1() {
if (!chrome?.cookies?.getAll || !chrome.cookies?.remove) {
await log('步骤 1:当前浏览器不支持 cookies API,跳过 Grok Cookie 清理。', 'warn', 'grok-open-signup-page');
return;
}
const stores = chrome.cookies.getAllCookieStores
? await chrome.cookies.getAllCookieStores()
: [{ id: undefined }];
let removedCount = 0;
const seen = new Set();
for (const store of stores) {
const storeId = store?.id;
const cookies = await chrome.cookies.getAll(storeId ? { storeId } : {}).catch(() => []);
for (const cookie of cookies || []) {
if (!shouldClearGrokCookie(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);
try {
const details = {
url: buildCookieRemovalUrl(cookie),
name: cookie.name,
};
if (cookie.storeId) {
details.storeId = cookie.storeId;
}
if (cookie.partitionKey) {
details.partitionKey = cookie.partitionKey;
}
const removed = await chrome.cookies.remove(details);
if (removed) {
removedCount += 1;
}
} catch (error) {
console.warn('[MultiPage:grok-register] remove cookie failed', {
domain: cookie?.domain,
name: cookie?.name,
message: getErrorMessage(error),
});
}
}
}
await log(`步骤 1:已清理 Grok/xAI Cookie ${removedCount} 个。`, removedCount ? 'ok' : 'info', 'grok-open-signup-page');
}
function resolveProfile(currentState = {}) {
const firstFromState = cleanString(currentState.grokFirstName);
const lastFromState = cleanString(currentState.grokLastName);
if (firstFromState && lastFromState) {
return {
firstName: firstFromState,
lastName: lastFromState,
};
}
const generated = typeof generateRandomName === 'function' ? generateRandomName() : null;
const fullName = cleanString(generated?.fullName || generated?.name || 'Alex Morgan');
const parts = fullName.split(/\s+/).filter(Boolean);
return {
firstName: firstFromState || cleanString(generated?.firstName || parts[0] || 'Alex'),
lastName: lastFromState || cleanString(generated?.lastName || parts.slice(1).join(' ') || 'Morgan'),
};
}
function resolvePassword(currentState = {}) {
return cleanString(currentState.grokPassword || currentState.customPassword || currentState.password)
|| (typeof generatePassword === 'function' ? generatePassword() : createGeneratedPassword());
}
function normalizeGrokVerificationCode(value = '') {
return cleanString(value).replace(/[^A-Za-z0-9]/g, '');
}
async function readSsoCookieFromChrome() {
if (!chrome?.cookies?.get) {
return '';
}
const candidates = [
{ url: 'https://x.ai/', name: 'sso' },
{ url: 'https://grok.com/', name: 'sso' },
{ url: 'https://accounts.x.ai/', name: 'sso' },
];
for (const details of candidates) {
const cookie = await chrome.cookies.get(details).catch(() => null);
const value = cleanString(cookie?.value);
if (value) {
return value;
}
}
return '';
}
async function executeGrokOpenSignupPage(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-open-signup-page';
const currentState = await getExecutionState(state);
try {
await clearGrokCookiesBeforeStep1();
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: true });
await activateTab(tabId);
await persistState({
grokRegisterTabId: tabId,
grokSignupUrl: GROK_SIGNUP_URL,
...buildGrokRuntimePatch({
session: {
registerTabId: tabId,
startedAt: Date.now(),
pageUrl: GROK_SIGNUP_URL,
lastError: '',
},
}),
});
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, {}, {
step: 1,
timeoutMs: DEFAULT_GROK_PAGE_TIMEOUT_MS,
logMessage: '步骤 1:正在打开 Grok 邮箱注册入口...',
});
await log('步骤 1:已打开 Grok 邮箱注册页。', 'ok', nodeId);
await completeNode(nodeId, {
grokRegisterTabId: tabId,
grokPageState: result.state || 'email_signup_ready',
grokPageUrl: result.url || GROK_SIGNUP_URL,
...buildGrokRuntimePatch({
session: {
registerTabId: tabId,
startedAt: Date.now(),
pageState: result.state || 'email_signup_ready',
pageUrl: result.url || GROK_SIGNUP_URL,
lastError: '',
},
register: {
status: 'signup_page_opened',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
}));
await log(`步骤 1${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokSubmitEmail(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-submit-email';
const currentState = await getExecutionState(state);
try {
if (typeof resolveSignupEmailForFlow !== 'function') {
throw new Error('Grok 邮箱步骤缺少公共邮箱解析能力,无法继续执行。');
}
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
await activateTab(tabId);
await ensureContentReady(tabId);
const resolvedEmail = await resolveSignupEmailForFlow(currentState, {
preserveAccountIdentity: true,
});
const email = cleanString(resolvedEmail).toLowerCase();
if (!email) {
throw new Error('Grok 注册邮箱为空,无法继续执行。');
}
const requestedAt = Date.now();
await persistState({
grokEmail: email,
email,
accountIdentifierType: 'email',
accountIdentifier: email,
...buildGrokRuntimePatch({
register: {
email,
verificationRequestedAt: requestedAt,
status: 'email_submitting',
},
}),
});
const result = await sendGrokCommand(nodeId, { email }, {
step: 2,
logMessage: '步骤 2:正在提交 Grok 注册邮箱...',
});
await log(`步骤 2:已提交 Grok 注册邮箱 ${email}`, 'ok', nodeId);
await completeNode(nodeId, {
grokEmail: email,
grokVerificationRequestedAt: requestedAt,
grokPageState: result.state || '',
grokPageUrl: result.url || '',
email,
accountIdentifierType: 'email',
accountIdentifier: email,
...buildGrokRuntimePatch({
session: {
pageState: result.state || '',
pageUrl: result.url || '',
lastError: '',
},
register: {
email,
verificationRequestedAt: requestedAt,
status: 'verification_requested',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 2${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokSubmitVerificationCode(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-submit-verification-code';
const currentState = await getExecutionState(state);
try {
if (typeof pollFlowVerificationCode !== 'function') {
throw new Error('Grok 验证码步骤缺少共享邮件轮询能力,无法继续执行。');
}
const requestedAt = Math.max(
0,
Number(
currentState.grokVerificationRequestedAt
|| currentState.runtimeState?.flowState?.grok?.register?.verificationRequestedAt
) || Date.now()
);
const filterAfterTimestamp = cleanString(currentState?.mailProvider).toLowerCase() === '2925'
? Math.max(0, requestedAt - MAIL_2925_FILTER_LOOKBACK_MS)
: requestedAt;
const email = cleanString(
currentState.grokEmail
|| currentState.runtimeState?.flowState?.grok?.register?.email
|| currentState.email
).toLowerCase();
const pollResult = await pollFlowVerificationCode({
actionLabel: 'Grok 验证码',
filterAfterTimestamp,
flowId: 'grok',
logStep: 3,
logStepKey: nodeId,
nodeId,
notFoundMessage: '步骤 3:邮箱轮询结束,但未获取到 xAI 验证码。',
state: {
...currentState,
activeFlowId: 'grok',
flowId: 'grok',
visibleStep: 3,
grokEmail: email,
email,
},
step: 3,
});
const code = normalizeGrokVerificationCode(pollResult?.code);
if (!code) {
throw new Error('未能获取到 xAI 邮箱验证码。');
}
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
await activateTab(tabId);
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, { code }, {
step: 3,
logMessage: '步骤 3:正在填写 xAI 邮箱验证码...',
});
await log(`步骤 3:已提交 xAI 邮箱验证码,当前页面状态:${result.state || 'unknown'}`, 'ok', nodeId);
await completeNode(nodeId, {
grokVerificationCode: code,
grokVerificationRawCode: cleanString(pollResult?.code),
grokVerificationMessageId: cleanString(pollResult?.messageId || pollResult?.mailId),
grokPageState: result.state || '',
grokPageUrl: result.url || '',
...buildGrokRuntimePatch({
session: {
pageState: result.state || '',
pageUrl: result.url || '',
lastError: '',
},
register: {
verificationCode: code,
status: 'verified',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 3${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokSubmitProfile(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-submit-profile';
const currentState = await getExecutionState(state);
try {
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
const profile = resolveProfile(currentState);
const password = resolvePassword(currentState);
await persistState({
grokFirstName: profile.firstName,
grokLastName: profile.lastName,
grokPassword: password,
...buildGrokRuntimePatch({
register: {
firstName: profile.firstName,
lastName: profile.lastName,
password,
status: 'profile_submitting',
},
}),
});
if (typeof setPasswordState === 'function') {
await setPasswordState(password);
}
await activateTab(tabId);
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, {
firstName: profile.firstName,
lastName: profile.lastName,
password,
}, {
step: 4,
logMessage: '步骤 4:正在填写 xAI 注册资料...',
});
await log(`步骤 4:已提交 Grok 注册资料,等待 ${Math.floor(GROK_POST_PROFILE_CF_WAIT_MS / 1000)} 秒完成注册验证...`, 'info', nodeId);
await sleepWithStop(GROK_POST_PROFILE_CF_WAIT_MS);
await ensureContentReady(tabId, { timeoutMs: DEFAULT_GROK_PAGE_TIMEOUT_MS });
await log('步骤 4:已提交 Grok 注册资料并完成等待。', 'ok', nodeId);
await completeNode(nodeId, {
grokFirstName: profile.firstName,
grokLastName: profile.lastName,
grokPassword: password,
grokPageState: result.state || 'profile_submitted',
grokPageUrl: result.url || '',
...buildGrokRuntimePatch({
session: {
pageState: result.state || 'profile_submitted',
pageUrl: result.url || '',
lastError: '',
},
register: {
firstName: profile.firstName,
lastName: profile.lastName,
password,
status: 'profile_submitted',
},
}),
});
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 4${message}`, 'error', nodeId);
throw error;
}
}
async function executeGrokExtractSsoCookie(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-extract-sso-cookie';
const currentState = await getExecutionState(state);
try {
const tabId = await ensureGrokRegisterTab(currentState, { openIfMissing: false });
await activateTab(tabId);
await log(`步骤 5:等待 ${Math.floor(GROK_PRE_SSO_EXTRACT_WAIT_MS / 1000)} 秒后提取 Grok SSO...`, 'info', nodeId);
await sleepWithStop(GROK_PRE_SSO_EXTRACT_WAIT_MS);
let ssoCookie = await readSsoCookieFromChrome();
if (!ssoCookie) {
await ensureContentReady(tabId);
const result = await sendGrokCommand(nodeId, {}, {
step: 5,
logMessage: '步骤 5:正在从 Grok 注册页读取 sso Cookie...',
});
ssoCookie = cleanString(result?.ssoCookie);
}
if (!ssoCookie) {
throw new Error('未找到 x.ai/grok sso Cookie。');
}
const latestState = await getState();
const existingSsoCookies = Array.isArray(latestState?.grokSsoCookies)
? latestState.grokSsoCookies
.map((entry) => cleanString(entry))
.filter(Boolean)
: [];
const nextSsoCookies = existingSsoCookies.includes(ssoCookie)
? existingSsoCookies
: [...existingSsoCookies, ssoCookie];
const completedAt = Date.now();
const completionPatch = {
grokSsoCookie: ssoCookie,
grokSsoCookies: nextSsoCookies,
grokSsoExtractedAt: completedAt,
grokCompletedAt: completedAt,
grokRegisterStatus: 'completed',
...buildGrokRuntimePatch({
register: {
status: 'completed',
completedAt,
},
sso: {
currentCookie: ssoCookie,
cookies: nextSsoCookies,
extractedAt: completedAt,
},
session: {
lastError: '',
},
}),
};
if (typeof markCurrentRegistrationAccountUsed === 'function') {
await markCurrentRegistrationAccountUsed({
...currentState,
...completionPatch,
}, {
logPrefix: 'Grok 注册成功',
level: 'ok',
});
}
await log('步骤 5:已提取 Grok SSO Cookie。', 'ok', nodeId);
await completeNode(nodeId, completionPatch);
} catch (error) {
const message = getErrorMessage(error);
await persistState(buildGrokRuntimePatch({
session: {
lastError: message,
},
register: {
status: 'error',
},
}));
await log(`步骤 5${message}`, 'error', nodeId);
throw error;
}
}
return {
executeGrokExtractSsoCookie,
executeGrokOpenSignupPage,
executeGrokSubmitEmail,
executeGrokSubmitProfile,
executeGrokSubmitVerificationCode,
};
}
return {
DEFAULT_GROK_PAGE_TIMEOUT_MS,
GROK_COOKIE_CLEAR_DOMAINS,
GROK_POST_PROFILE_CF_WAIT_MS,
GROK_PRE_SSO_EXTRACT_WAIT_MS,
GROK_REGISTER_PAGE_SOURCE_ID,
GROK_SIGNUP_URL,
createGrokRegisterRunner,
};
});
+374
View File
@@ -0,0 +1,374 @@
(function attachBackgroundGrokState(root, factory) {
root.MultiPageBackgroundGrokState = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGrokStateModule() {
function isPlainObject(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
function cloneValue(value) {
if (Array.isArray(value)) {
return value.map((entry) => cloneValue(entry));
}
if (isPlainObject(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, entryValue]) => [key, cloneValue(entryValue)])
);
}
return value;
}
function deepMerge(baseValue, patchValue) {
if (Array.isArray(patchValue)) {
return patchValue.map((entry) => cloneValue(entry));
}
if (!isPlainObject(patchValue)) {
return patchValue === undefined ? cloneValue(baseValue) : patchValue;
}
const baseObject = isPlainObject(baseValue) ? baseValue : {};
const next = {
...cloneValue(baseObject),
};
Object.entries(patchValue).forEach(([key, value]) => {
next[key] = deepMerge(baseObject[key], value);
});
return next;
}
function cleanString(value = '') {
return String(value ?? '').trim();
}
function normalizeInteger(value, fallback = 0) {
const numeric = Math.floor(Number(value));
return Number.isInteger(numeric) ? numeric : fallback;
}
function normalizeNullableInteger(value, fallback = null) {
if (value === null || value === undefined || value === '') {
return fallback;
}
const numeric = Math.floor(Number(value));
return Number.isInteger(numeric) ? numeric : fallback;
}
function normalizeSsoCookies(values = []) {
if (!Array.isArray(values)) {
return [];
}
return Array.from(new Set(
values
.map((entry) => cleanString(entry))
.filter(Boolean)
));
}
function buildDefaultRuntimeState() {
return {
session: {
registerTabId: null,
startedAt: 0,
pageState: '',
pageUrl: '',
lastError: '',
},
register: {
email: '',
firstName: '',
lastName: '',
password: '',
verificationRequestedAt: 0,
verificationCode: '',
status: '',
completedAt: 0,
},
sso: {
currentCookie: '',
cookies: [],
extractedAt: 0,
},
};
}
function normalizeRuntimeState(runtimeState = {}) {
const merged = deepMerge(buildDefaultRuntimeState(), runtimeState);
return {
session: {
registerTabId: normalizeNullableInteger(merged.session?.registerTabId),
startedAt: Math.max(0, normalizeInteger(merged.session?.startedAt)),
pageState: cleanString(merged.session?.pageState),
pageUrl: cleanString(merged.session?.pageUrl),
lastError: cleanString(merged.session?.lastError),
},
register: {
email: cleanString(merged.register?.email).toLowerCase(),
firstName: cleanString(merged.register?.firstName),
lastName: cleanString(merged.register?.lastName),
password: cleanString(merged.register?.password),
verificationRequestedAt: Math.max(0, normalizeInteger(merged.register?.verificationRequestedAt)),
verificationCode: cleanString(merged.register?.verificationCode),
status: cleanString(merged.register?.status),
completedAt: Math.max(0, normalizeInteger(merged.register?.completedAt)),
},
sso: {
currentCookie: cleanString(merged.sso?.currentCookie || merged.ssoCookie),
cookies: normalizeSsoCookies(merged.sso?.cookies || merged.ssoCookies),
extractedAt: Math.max(0, normalizeInteger(merged.sso?.extractedAt)),
},
};
}
function buildCanonicalRuntimeStatePatch(state = {}, runtimeState = {}) {
const normalizedRuntimeState = normalizeRuntimeState(runtimeState);
const baseRuntimeState = isPlainObject(state?.runtimeState)
? cloneValue(state.runtimeState)
: {};
const baseFlowState = isPlainObject(baseRuntimeState.flowState)
? cloneValue(baseRuntimeState.flowState)
: {};
return {
...baseRuntimeState,
flowState: {
...baseFlowState,
grok: normalizedRuntimeState,
},
};
}
function projectRuntimeFields(runtimeState = {}) {
const normalizedRuntimeState = normalizeRuntimeState(runtimeState);
return {
grokRegisterTabId: normalizedRuntimeState.session.registerTabId,
grokPageState: normalizedRuntimeState.session.pageState,
grokPageUrl: normalizedRuntimeState.session.pageUrl,
grokEmail: normalizedRuntimeState.register.email,
grokFirstName: normalizedRuntimeState.register.firstName,
grokLastName: normalizedRuntimeState.register.lastName,
grokPassword: normalizedRuntimeState.register.password,
grokVerificationRequestedAt: normalizedRuntimeState.register.verificationRequestedAt,
grokVerificationCode: normalizedRuntimeState.register.verificationCode,
grokRegisterStatus: normalizedRuntimeState.register.status,
grokCompletedAt: normalizedRuntimeState.register.completedAt,
grokSsoCookie: normalizedRuntimeState.sso.currentCookie,
grokSsoCookies: normalizedRuntimeState.sso.cookies,
grokSsoExtractedAt: normalizedRuntimeState.sso.extractedAt,
};
}
function ensureRuntimeState(state = {}) {
const runtimeFlowState = isPlainObject(state?.runtimeState?.flowState)
? state.runtimeState.flowState
: {};
const legacyFlowState = isPlainObject(state?.flowState?.grok)
? state.flowState.grok
: {};
const flatRuntime = {
session: {
registerTabId: state.grokRegisterTabId,
pageState: state.grokPageState,
pageUrl: state.grokPageUrl || state.grokPostVerificationUrl,
},
register: {
email: state.grokEmail || state.email,
firstName: state.grokFirstName,
lastName: state.grokLastName,
password: state.grokPassword,
verificationRequestedAt: state.grokVerificationRequestedAt,
verificationCode: state.grokVerificationCode,
status: state.grokRegisterStatus,
completedAt: state.grokCompletedAt,
},
sso: {
currentCookie: state.grokSsoCookie,
cookies: state.grokSsoCookies,
extractedAt: state.grokSsoExtractedAt || state.grokCompletedAt,
},
};
return normalizeRuntimeState(deepMerge(deepMerge(runtimeFlowState.grok || {}, legacyFlowState), flatRuntime));
}
function buildStateView(state = {}) {
const runtimeState = ensureRuntimeState(state);
const canonicalRuntimeState = buildCanonicalRuntimeStatePatch(state, runtimeState);
return {
...state,
...projectRuntimeFields(runtimeState),
runtimeState: canonicalRuntimeState,
flowState: {
...(isPlainObject(state?.flowState) ? state.flowState : {}),
grok: runtimeState,
},
flows: {
...(isPlainObject(state?.flows) ? state.flows : {}),
grok: runtimeState,
},
};
}
function buildRuntimeStatePatch(currentState = {}, patch = {}) {
if (!isPlainObject(patch)) {
return {};
}
const nextRuntimeState = normalizeRuntimeState(
deepMerge(ensureRuntimeState(currentState), patch)
);
return {
...projectRuntimeFields(nextRuntimeState),
runtimeState: buildCanonicalRuntimeStatePatch(currentState, nextRuntimeState),
};
}
function buildSessionStatePatch(currentState = {}, updates = {}) {
const runtimePatch = isPlainObject(updates?.runtimeState?.flowState?.grok)
? updates.runtimeState.flowState.grok
: (isPlainObject(updates?.flowState?.grok) ? updates.flowState.grok : null);
if (!runtimePatch) {
return {};
}
return buildRuntimeStatePatch(currentState, runtimePatch);
}
function buildRuntimeResetPatch(currentState = {}, patch = {}) {
return buildRuntimeStatePatch(currentState, patch);
}
function buildStartRegisterResetPatch(currentState = {}) {
return buildRuntimeStatePatch(currentState, buildDefaultRuntimeState());
}
function buildRegisterOnlyResetPatch(currentState = {}, registerPatch = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
return buildRuntimeStatePatch(currentState, {
...currentRuntimeState,
session: {
...currentRuntimeState.session,
pageState: '',
pageUrl: '',
lastError: '',
},
register: {
...buildDefaultRuntimeState().register,
email: currentRuntimeState.register?.email || '',
...registerPatch,
},
});
}
function buildSsoResetPatch(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
return buildRuntimeStatePatch(currentState, {
...currentRuntimeState,
sso: buildDefaultRuntimeState().sso,
});
}
function buildDownstreamResetPatch(stepKey = '', currentState = {}) {
switch (cleanString(stepKey)) {
case 'grok-open-signup-page':
return {
flowStartTime: null,
...buildStartRegisterResetPatch(currentState),
};
case 'grok-submit-email':
return buildRegisterOnlyResetPatch(currentState, {
email: '',
});
case 'grok-submit-verification-code':
return buildRegisterOnlyResetPatch(currentState, {});
case 'grok-submit-profile':
case 'grok-extract-sso-cookie':
return buildSsoResetPatch(currentState);
default:
return {};
}
}
function applyNodeCompletionPayload(currentState = {}, payload = {}) {
const runtimePatch = isPlainObject(payload?.runtimeState?.flowState?.grok)
? payload.runtimeState.flowState.grok
: (isPlainObject(payload?.flowState?.grok) ? payload.flowState.grok : null);
if (runtimePatch) {
return buildRuntimeStatePatch(currentState, runtimePatch);
}
const patch = {};
if (Object.prototype.hasOwnProperty.call(payload, 'grokRegisterTabId')) {
patch.session = { ...(patch.session || {}), registerTabId: payload.grokRegisterTabId };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokPageState')) {
patch.session = { ...(patch.session || {}), pageState: payload.grokPageState };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokPageUrl') || Object.prototype.hasOwnProperty.call(payload, 'grokSignupUrl') || Object.prototype.hasOwnProperty.call(payload, 'grokPostVerificationUrl')) {
patch.session = {
...(patch.session || {}),
pageUrl: payload.grokPageUrl || payload.grokPostVerificationUrl || payload.grokSignupUrl || '',
};
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokEmail') || Object.prototype.hasOwnProperty.call(payload, 'email')) {
patch.register = {
...(patch.register || {}),
email: payload.grokEmail || payload.email || '',
};
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokFirstName')) {
patch.register = { ...(patch.register || {}), firstName: payload.grokFirstName };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokLastName')) {
patch.register = { ...(patch.register || {}), lastName: payload.grokLastName };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokPassword')) {
patch.register = { ...(patch.register || {}), password: payload.grokPassword };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokVerificationRequestedAt')) {
patch.register = {
...(patch.register || {}),
verificationRequestedAt: payload.grokVerificationRequestedAt,
};
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokVerificationCode')) {
patch.register = { ...(patch.register || {}), verificationCode: payload.grokVerificationCode };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokRegisterStatus')) {
patch.register = { ...(patch.register || {}), status: payload.grokRegisterStatus };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokCompletedAt')) {
patch.register = { ...(patch.register || {}), completedAt: payload.grokCompletedAt };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokSsoCookie')) {
patch.sso = { ...(patch.sso || {}), currentCookie: payload.grokSsoCookie };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokSsoCookies')) {
patch.sso = { ...(patch.sso || {}), cookies: payload.grokSsoCookies };
}
if (Object.prototype.hasOwnProperty.call(payload, 'grokSsoExtractedAt') || Object.prototype.hasOwnProperty.call(payload, 'grokCompletedAt')) {
patch.sso = {
...(patch.sso || {}),
extractedAt: payload.grokSsoExtractedAt || payload.grokCompletedAt || 0,
};
}
if (!Object.keys(patch).length) {
return {};
}
return buildRuntimeStatePatch(currentState, patch);
}
function buildFreshKeepState(currentState = {}) {
const currentRuntimeState = ensureRuntimeState(currentState);
const nextRuntimeState = buildDefaultRuntimeState();
nextRuntimeState.sso = currentRuntimeState.sso || buildDefaultRuntimeState().sso;
return buildRuntimeStatePatch(currentState, nextRuntimeState);
}
return {
applyNodeCompletionPayload,
buildDefaultRuntimeState,
buildDownstreamResetPatch,
buildFreshKeepState,
buildRuntimeStatePatch,
buildSessionStatePatch,
buildStateView,
ensureRuntimeState,
normalizeSsoCookies,
projectRuntimeFields,
};
});