|
|
import {
|
|
|
hasEffectiveFlockLogin,
|
|
|
mustForceFlockAccountLogin,
|
|
|
} from "@/lib/flock/login-context";
|
|
|
import { createFreshFlockTestAccount } from "@/lib/flock/fresh-account";
|
|
|
|
|
|
/** flock-manual-20260713-152051.js 人工录制验证通过的注册账号 */
|
|
|
export const FLOCK_RECORDING_TEST_ACCOUNT = {
|
|
|
firstName: "suiji",
|
|
|
lastName: "shengcheng",
|
|
|
company: "cs",
|
|
|
phone: "7536407420",
|
|
|
email: "3457189488@qq.com",
|
|
|
shipmentsPerMonth: "1-25",
|
|
|
} as const;
|
|
|
|
|
|
/** Flock RPA 开关:Mock、显式 true、或本地 development 默认开启 */
|
|
|
export function isFlockRpaEnabled(): boolean {
|
|
|
if (process.env.RPA_MOCK_MODE === "true") {
|
|
|
return true;
|
|
|
}
|
|
|
const raw = process.env.FLOCK_RPA_ENABLED?.trim().toLowerCase();
|
|
|
if (raw === "false" || raw === "0" || raw === "no") {
|
|
|
return false;
|
|
|
}
|
|
|
if (raw === "true" || raw === "1" || raw === "yes") {
|
|
|
return true;
|
|
|
}
|
|
|
return process.env.NODE_ENV === "development";
|
|
|
}
|
|
|
|
|
|
export function getFlockQuoteUrl(): string {
|
|
|
return (
|
|
|
process.env.FLOCK_QUOTE_URL?.trim() ||
|
|
|
"https://app.flockfreight.com/get-a-quote"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
export function getFlockStorageStatePath(): string {
|
|
|
return (
|
|
|
process.env.FLOCK_STORAGE_STATE_PATH?.trim() || ".rpa/flock-storage.json"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/** 点击查询后最多等待报价的毫秒数(默认 20s,超时截图后进入下一组) */
|
|
|
export function getFlockQuoteWaitMs(): number {
|
|
|
const n = Number(process.env.FLOCK_QUOTE_WAIT_MS ?? "20000");
|
|
|
return Number.isFinite(n) && n > 0 ? n : 20_000;
|
|
|
}
|
|
|
|
|
|
/** 等待查价表单出现上限(默认 20s;batch 可设更长) */
|
|
|
export function getFlockFormWaitMs(): number {
|
|
|
const n = Number(process.env.FLOCK_FORM_WAIT_MS ?? "20000");
|
|
|
return Number.isFinite(n) && n > 0 ? n : 20_000;
|
|
|
}
|
|
|
|
|
|
/** 每填一字段停顿(embed 默认 300ms 求快;batch 探针可设 1000) */
|
|
|
export function getFlockFieldPauseMs(): number {
|
|
|
const n = Number(process.env.FLOCK_FIELD_PAUSE_MS ?? "300");
|
|
|
return Number.isFinite(n) && n >= 0 ? n : 300;
|
|
|
}
|
|
|
|
|
|
export { getFlockPollTimeoutMs } from "@/lib/flock/poll-timeout";
|
|
|
|
|
|
/** 成功所需最少报价档数(默认 1:有任意一档即成功) */
|
|
|
export function getFlockMinQuotes(): number {
|
|
|
const n = Number(process.env.FLOCK_MIN_QUOTES ?? "1");
|
|
|
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1;
|
|
|
}
|
|
|
|
|
|
/** 单浏览器模式下同一邮箱连续查价次数,满额后换新邮箱+新浏览器(默认不限) */
|
|
|
export function getFlockQuotesPerEmail(): number {
|
|
|
const raw = process.env.FLOCK_QUOTES_PER_EMAIL?.trim();
|
|
|
if (!raw) {
|
|
|
return Number.POSITIVE_INFINITY;
|
|
|
}
|
|
|
const n = Number(raw);
|
|
|
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : Number.POSITIVE_INFINITY;
|
|
|
}
|
|
|
|
|
|
/** Worker 单 job 内换邮箱重试(默认 1:快失败;batch 探针可设 3) */
|
|
|
export function getFlockSessionMaxRotations(): number {
|
|
|
const n = Number(process.env.FLOCK_SESSION_MAX_ROTATIONS ?? "1");
|
|
|
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Flock 报价主路径:
|
|
|
* - `direct`:仅 POST /user/quotes
|
|
|
* - `dom`:仅 UI RPA
|
|
|
* - `direct_then_dom`(默认):Direct 优先,失败再 DOM
|
|
|
*/
|
|
|
export function getFlockQuoteMode():
|
|
|
| "direct"
|
|
|
| "dom"
|
|
|
| "direct_then_dom" {
|
|
|
const raw = process.env.FLOCK_QUOTE_MODE?.trim().toLowerCase();
|
|
|
if (raw === "direct" || raw === "dom" || raw === "direct_then_dom") {
|
|
|
return raw;
|
|
|
}
|
|
|
return "direct_then_dom";
|
|
|
}
|
|
|
|
|
|
export function isFlockDirectQuoteEnabled(): boolean {
|
|
|
const mode = getFlockQuoteMode();
|
|
|
return mode === "direct" || mode === "direct_then_dom";
|
|
|
}
|
|
|
|
|
|
export function isFlockDomQuoteFallbackEnabled(): boolean {
|
|
|
const mode = getFlockQuoteMode();
|
|
|
return mode === "dom" || mode === "direct_then_dom";
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Worker 进程内复用 Flock 浏览器(同邮箱 Build another quote,满额轮换)
|
|
|
* 默认:配置了 FLOCK_QUOTES_PER_EMAIL 时开启
|
|
|
*/
|
|
|
export function isFlockWorkerReuseSession(): boolean {
|
|
|
const raw = process.env.FLOCK_WORKER_REUSE_SESSION?.trim().toLowerCase();
|
|
|
if (raw === "false" || raw === "0" || raw === "no") {
|
|
|
return false;
|
|
|
}
|
|
|
if (raw === "true" || raw === "1" || raw === "yes") {
|
|
|
return true;
|
|
|
}
|
|
|
const perEmail = getFlockQuotesPerEmail();
|
|
|
return Number.isFinite(perEmail) && perEmail < Number.POSITIVE_INFINITY;
|
|
|
}
|
|
|
|
|
|
export type FlockLoginCredentials = {
|
|
|
email: string;
|
|
|
password: string;
|
|
|
};
|
|
|
|
|
|
/** DOM/RPA 保障路径登录凭据(Direct 匿名 bootstrap 不依赖此项) */
|
|
|
export function getFlockLoginCredentials(): FlockLoginCredentials | null {
|
|
|
const email = process.env.FLOCK_LOGIN_EMAIL?.trim() ?? "";
|
|
|
const password = process.env.FLOCK_LOGIN_PASSWORD?.trim() ?? "";
|
|
|
if (!email || !password) {
|
|
|
return null;
|
|
|
}
|
|
|
return { email, password };
|
|
|
}
|
|
|
|
|
|
export function hasFlockLoginCredentials(): boolean {
|
|
|
return getFlockLoginCredentials() !== null;
|
|
|
}
|
|
|
|
|
|
export function getFlockLoginUrl(): string {
|
|
|
return (
|
|
|
process.env.FLOCK_LOGIN_URL?.trim() ||
|
|
|
"https://app.flockfreight.com/login"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 每次查价使用全新浏览器会话:不加载/不保存 storage,清空 cookie 痕迹
|
|
|
* 配置了 FLOCK_LOGIN_* 时强制关闭(登录态需持久 session)
|
|
|
*/
|
|
|
export function isFlockFreshSessionPerQuote(): boolean {
|
|
|
if (hasEffectiveFlockLogin() || hasFlockLoginCredentials()) {
|
|
|
return false;
|
|
|
}
|
|
|
const raw = process.env.FLOCK_FRESH_SESSION_PER_QUOTE?.trim().toLowerCase();
|
|
|
if (raw === "false" || raw === "0" || raw === "no") {
|
|
|
return false;
|
|
|
}
|
|
|
if (raw === "true" || raw === "1" || raw === "yes") {
|
|
|
return true;
|
|
|
}
|
|
|
return isFlockRandomAccountPerQuote();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 加载录制会话(默认:存在 flock-storage.json 则加载,跳过注册)
|
|
|
* 无痕模式(FLOCK_FRESH_SESSION_PER_QUOTE)下永不加载
|
|
|
*/
|
|
|
export function shouldLoadFlockStorageState(fileExists: boolean): boolean {
|
|
|
// 客户账密:禁止吃共享 storage(可能是匿名/其他客户会话)
|
|
|
if (mustForceFlockAccountLogin()) {
|
|
|
return false;
|
|
|
}
|
|
|
if (isFlockFreshSessionPerQuote()) {
|
|
|
return false;
|
|
|
}
|
|
|
const raw = process.env.FLOCK_LOAD_STORAGE_STATE?.trim().toLowerCase();
|
|
|
if (raw === "false" || raw === "0" || raw === "no") {
|
|
|
return false;
|
|
|
}
|
|
|
return fileExists;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 报价后/登录后是否写 storage
|
|
|
* 登录凭据模式默认写回;无痕模式永不写入
|
|
|
*/
|
|
|
export function shouldSaveFlockStorageState(): boolean {
|
|
|
// 客户账密不写共享文件,避免串客户
|
|
|
if (mustForceFlockAccountLogin()) {
|
|
|
return false;
|
|
|
}
|
|
|
if (isFlockFreshSessionPerQuote()) {
|
|
|
return false;
|
|
|
}
|
|
|
if (hasEffectiveFlockLogin() || hasFlockLoginCredentials()) {
|
|
|
const raw = process.env.FLOCK_SAVE_STORAGE_AFTER_QUOTE?.trim().toLowerCase();
|
|
|
if (raw === "false" || raw === "0" || raw === "no") {
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
const raw = process.env.FLOCK_SAVE_STORAGE_AFTER_QUOTE?.trim().toLowerCase();
|
|
|
return raw === "true" || raw === "1" || raw === "yes";
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 每次查价换新注册邮箱(需配合无痕模式)
|
|
|
* 配置了 FLOCK_LOGIN_* 时关闭(禁止生产 DOM 换号注册)
|
|
|
*/
|
|
|
export function isFlockRandomAccountPerQuote(): boolean {
|
|
|
if (hasEffectiveFlockLogin() || hasFlockLoginCredentials()) {
|
|
|
return false;
|
|
|
}
|
|
|
const raw = process.env.FLOCK_RANDOM_ACCOUNT_PER_QUOTE?.trim().toLowerCase();
|
|
|
return raw === "true" || raw === "1" || raw === "yes";
|
|
|
}
|
|
|
|
|
|
export type FlockTestAccount = {
|
|
|
firstName: string;
|
|
|
lastName: string;
|
|
|
company: string;
|
|
|
phone: string;
|
|
|
email: string;
|
|
|
shipmentsPerMonth: string;
|
|
|
};
|
|
|
|
|
|
/** @deprecated 使用 createFreshFlockTestAccount */
|
|
|
export function createRandomFlockTestAccount(): FlockTestAccount {
|
|
|
return createFreshFlockTestAccount();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 注册页测试账号
|
|
|
* - 默认:录制验证账号(固定邮箱/电话)
|
|
|
* - 无痕/随机模式:每次新邮箱 + 新电话,不依赖 storage
|
|
|
*/
|
|
|
export function getFlockTestAccount(): FlockTestAccount {
|
|
|
if (isFlockFreshSessionPerQuote() || isFlockRandomAccountPerQuote()) {
|
|
|
return createFreshFlockTestAccount();
|
|
|
}
|
|
|
|
|
|
const rec = FLOCK_RECORDING_TEST_ACCOUNT;
|
|
|
return {
|
|
|
firstName: process.env.FLOCK_TEST_FIRST_NAME?.trim() || rec.firstName,
|
|
|
lastName: process.env.FLOCK_TEST_LAST_NAME?.trim() || rec.lastName,
|
|
|
company: process.env.FLOCK_TEST_COMPANY?.trim() || rec.company,
|
|
|
phone: (process.env.FLOCK_TEST_PHONE?.replace(/\D/g, "") || rec.phone).slice(
|
|
|
-10,
|
|
|
),
|
|
|
email: process.env.FLOCK_TEST_EMAIL?.trim() || rec.email,
|
|
|
shipmentsPerMonth:
|
|
|
process.env.FLOCK_TEST_SHIPMENTS_MONTH?.trim() || rec.shipmentsPerMonth,
|
|
|
};
|
|
|
}
|