Files
FlowPilot/flows/grok/background/publisher-webchat2api.js
2026-05-23 20:41:22 +08:00

312 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function attachBackgroundGrokPublisherWebchat2Api(root, factory) {
root.MultiPageBackgroundGrokPublisherWebchat2Api = factory(root);
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundGrokPublisherWebchat2ApiModule(root) {
const grokStateApi = root?.MultiPageBackgroundGrokState || null;
const WEBCHAT2API_INJECT_PATH = '/api/remote-account/inject';
const DEFAULT_SOURCE_ID = 'flowpilot-grok-sso';
const DEFAULT_SOURCE_NAME = 'FlowPilot Grok SSO';
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 getErrorMessage(error) {
return error instanceof Error ? error.message : cleanString(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 readWebchat2ApiResponseMessage(body = {}, fallback = '') {
return cleanString(
body?.json?.error?.message
|| body?.json?.error
|| body?.json?.message
|| fallback
);
}
function normalizeWebchat2ApiBaseUrl(value = '') {
const rawUrl = cleanString(value);
if (!rawUrl) {
throw new Error('缺少 webchat2api 地址。');
}
const withProtocol = /^https?:\/\//i.test(rawUrl) ? rawUrl : `http://${rawUrl}`;
let parsed = null;
try {
parsed = new URL(withProtocol);
} catch (_error) {
throw new Error('webchat2api 地址格式无效,请检查配置。');
}
if (!/^https?:$/.test(parsed.protocol)) {
throw new Error('webchat2api 地址只支持 http 或 https。');
}
return parsed.origin;
}
function buildWebchat2ApiInjectUrl(value = '') {
return `${normalizeWebchat2ApiBaseUrl(value)}${WEBCHAT2API_INJECT_PATH}`;
}
function normalizeWebchat2ApiAdminKey(value = '') {
return cleanString(value);
}
function readGrokRuntime(state = {}) {
return grokStateApi?.ensureRuntimeState
? grokStateApi.ensureRuntimeState(state)
: (isPlainObject(state?.runtimeState?.flowState?.grok)
? state.runtimeState.flowState.grok
: (isPlainObject(state?.flowState?.grok) ? state.flowState.grok : {}));
}
function buildCanonicalRuntimePatch(currentState = {}, nextRuntimeState = {}) {
if (typeof grokStateApi?.buildRuntimeStatePatch === 'function') {
return grokStateApi.buildRuntimeStatePatch(currentState, nextRuntimeState);
}
const baseRuntimeState = isPlainObject(currentState?.runtimeState)
? cloneValue(currentState.runtimeState)
: {};
const baseFlowState = isPlainObject(baseRuntimeState.flowState)
? cloneValue(baseRuntimeState.flowState)
: {};
return {
runtimeState: {
...baseRuntimeState,
flowState: {
...baseFlowState,
grok: deepMerge(readGrokRuntime(currentState), nextRuntimeState),
},
},
};
}
function mergeRuntimePatch(currentState = {}, patch = {}) {
return buildCanonicalRuntimePatch(
currentState,
deepMerge(readGrokRuntime(currentState), patch)
);
}
function resolveGrokWebchat2ApiConfig(state = {}) {
const nestedConfig = state?.settingsState?.flows?.grok?.targets?.webchat2api || {};
return {
baseUrl: cleanString(nestedConfig.baseUrl || state?.grokWebchat2ApiUrl),
apiKey: normalizeWebchat2ApiAdminKey(nestedConfig.apiKey ?? state?.grokWebchat2ApiAdminKey ?? ''),
};
}
function resolveGrokSsoCookie(state = {}) {
const runtimeState = readGrokRuntime(state);
return cleanString(runtimeState?.sso?.currentCookie || state?.grokSsoCookie);
}
function buildGrokSsoInjectPayload(ssoCookie = '') {
const normalizedCookie = cleanString(ssoCookie);
if (!normalizedCookie) {
throw new Error('缺少 Grok SSO Cookie,请先完成步骤 5。');
}
return {
accounts: [{
token: normalizedCookie,
provider: 'grok',
type: 'sso',
}],
strategy: 'merge',
source_id: DEFAULT_SOURCE_ID,
source_name: DEFAULT_SOURCE_NAME,
provider: 'grok',
};
}
async function uploadGrokSsoToWebchat2Api(baseUrl, apiKey, ssoCookie, fetchImpl) {
const endpointUrl = buildWebchat2ApiInjectUrl(baseUrl);
const normalizedApiKey = normalizeWebchat2ApiAdminKey(apiKey);
if (!normalizedApiKey) {
throw new Error('缺少 webchat2api 管理密钥。');
}
const response = await fetchImpl(endpointUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${normalizedApiKey}`,
},
body: JSON.stringify(buildGrokSsoInjectPayload(ssoCookie)),
});
const body = await readResponse(response);
if (!response.ok) {
const message = readWebchat2ApiResponseMessage(body, response.statusText) || `HTTP ${response.status}`;
throw new Error(`webchat2api SSO 上传失败:${message}`);
}
if (isPlainObject(body.json) && Object.prototype.hasOwnProperty.call(body.json, 'code') && Number(body.json.code) !== 0) {
const message = readWebchat2ApiResponseMessage(body, `code=${body.json.code}`);
throw new Error(`webchat2api SSO 上传失败:${message}`);
}
return {
endpointUrl,
message: readWebchat2ApiResponseMessage(body, '') || '上传成功',
raw: body.json,
};
}
function createGrokWebchat2ApiPublisher(deps = {}) {
const {
addLog = async () => {},
completeNodeFromBackground,
fetchImpl = typeof fetch === 'function' ? fetch.bind(globalThis) : null,
getState = async () => ({}),
setState = async () => {},
} = deps;
if (typeof completeNodeFromBackground !== 'function') {
throw new Error('Grok webchat2api publisher requires completeNodeFromBackground.');
}
if (typeof fetchImpl !== 'function') {
throw new Error('Grok webchat2api publisher requires fetch support.');
}
async function log(message, level = 'info', nodeId = '') {
await addLog(message, level, nodeId ? { nodeId } : {});
}
async function applyRuntimeState(currentState = {}, patch = {}) {
const nextPatch = mergeRuntimePatch(currentState, patch);
await setState(nextPatch);
return nextPatch;
}
async function persistFailure(currentState = {}, message = '', targetUrl = '') {
const uploadPatch = {
status: 'error',
uploadedAt: 0,
message,
};
const normalizedTargetUrl = cleanString(targetUrl);
if (normalizedTargetUrl) {
uploadPatch.targetUrl = normalizedTargetUrl;
}
const nextPatch = mergeRuntimePatch(currentState, {
session: {
lastError: message,
},
upload: uploadPatch,
});
await setState(nextPatch);
}
async function executeGrokUploadSsoToWebchat2Api(state = {}) {
const nodeId = cleanString(state?.nodeId) || 'grok-upload-sso-to-webchat2api';
const currentState = await getState();
let failureTargetUrl = '';
try {
const targetConfig = resolveGrokWebchat2ApiConfig(currentState);
const endpointUrl = buildWebchat2ApiInjectUrl(targetConfig.baseUrl);
failureTargetUrl = endpointUrl;
const apiKey = normalizeWebchat2ApiAdminKey(targetConfig.apiKey);
if (!apiKey) {
throw new Error('缺少 webchat2api 管理密钥。');
}
const ssoCookie = resolveGrokSsoCookie(currentState);
if (!ssoCookie) {
throw new Error('缺少 Grok SSO Cookie,请先完成步骤 5。');
}
await applyRuntimeState(currentState, {
session: {
lastError: '',
},
upload: {
status: 'uploading',
uploadedAt: 0,
message: '',
targetUrl: endpointUrl,
},
});
await log('步骤 6:正在上传 Grok SSO 到 webchat2api...', 'info', nodeId);
const uploadResult = await uploadGrokSsoToWebchat2Api(
targetConfig.baseUrl,
apiKey,
ssoCookie,
fetchImpl
);
const uploadedAt = Date.now();
const payload = await applyRuntimeState(currentState, {
session: {
lastError: '',
},
upload: {
status: 'uploaded',
uploadedAt,
message: uploadResult.message || '上传成功',
targetUrl: uploadResult.endpointUrl,
},
});
await log(`步骤 6Grok SSO 已上传到 webchat2api,状态:${uploadResult.message || '上传成功'}`, 'ok', nodeId);
await completeNodeFromBackground(nodeId, payload);
} catch (error) {
const message = getErrorMessage(error);
await persistFailure(currentState, message, failureTargetUrl);
await log(`步骤 6${message}`, 'error', nodeId);
throw error;
}
}
return {
executeGrokUploadSsoToWebchat2Api,
};
}
return {
buildGrokSsoInjectPayload,
buildWebchat2ApiInjectUrl,
createGrokWebchat2ApiPublisher,
normalizeWebchat2ApiBaseUrl,
uploadGrokSsoToWebchat2Api,
};
});