You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;
}
/** 等待查价表单出现上限(默认 20sbatch 可设更长) */
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,
};
}