diff --git a/.gitignore b/.gitignore index 5ea6d0e..c192313 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,4 @@ target/ test/ .vercel +.env*.local diff --git a/app/api/agent/stream/route.ts b/app/api/agent/stream/route.ts index 79a3f0c..719ce89 100644 --- a/app/api/agent/stream/route.ts +++ b/app/api/agent/stream/route.ts @@ -18,7 +18,11 @@ function sseComment(comment: string): Uint8Array { export async function GET(request: Request) { const { searchParams } = new URL(request.url); - const [sessionId, goal] = [searchParams.get("sessionId"), searchParams.get("goal")]; + const [sessionId, goal, fromChat] = [ + searchParams.get("sessionId"), + searchParams.get("goal"), + searchParams.get("fromChat") === "true" + ]; if (!sessionId || !goal) { return new Response( @@ -118,6 +122,7 @@ export async function GET(request: Request) { width: 1288, height: 711, }, + solveCaptchas: !fromChat, // false if session is from a search param, true otherwise }, }, useAPI: false, @@ -195,4 +200,4 @@ export async function GET(request: Request) { "X-Accel-Buffering": "no", }, }); -} +} \ No newline at end of file diff --git a/app/api/session/route.ts b/app/api/session/route.ts index c640ebf..d0f246e 100644 --- a/app/api/session/route.ts +++ b/app/api/session/route.ts @@ -1,5 +1,6 @@ -import { NextResponse } from "next/server"; import Browserbase from "@browserbasehq/sdk"; +import { getAll } from "@vercel/edge-config"; +import { NextResponse } from "next/server"; type BrowserbaseRegion = | "us-west-2" @@ -7,100 +8,178 @@ type BrowserbaseRegion = | "eu-central-1" | "ap-southeast-1"; -// Exact timezone matches for east coast cities -const exactTimezoneMap: Record = { - "America/New_York": "us-east-1", - "America/Detroit": "us-east-1", - "America/Toronto": "us-east-1", - "America/Montreal": "us-east-1", - "America/Boston": "us-east-1", - "America/Chicago": "us-east-1", +// Timezone abbreviation to region mapping +const timezoneAbbreviationMap: Record = { + // US East Coast + EST: "us-east-1", + EDT: "us-east-1", + + // US West Coast + PST: "us-west-2", + PDT: "us-west-2", + + // US Mountain/Central - route to appropriate region + MST: "us-west-2", + MDT: "us-west-2", + CST: "us-east-1", + CDT: "us-east-1", + + // Europe + GMT: "eu-central-1", + BST: "eu-central-1", + CET: "eu-central-1", + CEST: "eu-central-1", + EET: "eu-central-1", + EEST: "eu-central-1", + WET: "eu-central-1", + WEST: "eu-central-1", + + // Asia-Pacific + JST: "ap-southeast-1", // Japan Standard Time + KST: "ap-southeast-1", // Korea Standard Time + IST: "ap-southeast-1", // India Standard Time + AEST: "ap-southeast-1", // Australian Eastern Standard Time + AEDT: "ap-southeast-1", // Australian Eastern Daylight Time + AWST: "ap-southeast-1", // Australian Western Standard Time + NZST: "ap-southeast-1", // New Zealand Standard Time + NZDT: "ap-southeast-1", // New Zealand Daylight Time }; -// Prefix-based region mapping -const prefixToRegion: Record = { - America: "us-west-2", - US: "us-west-2", - Canada: "us-west-2", - Europe: "eu-central-1", - Africa: "eu-central-1", - Asia: "ap-southeast-1", - Australia: "ap-southeast-1", - Pacific: "ap-southeast-1", +// Default fallback distributions if edge config is not available +const defaultDistributions: Record< + BrowserbaseRegion, + Record +> = { + "us-west-2": { + "us-west-2": 100, + "us-east-1": 0, + "eu-central-1": 0, + "ap-southeast-1": 0, + }, + "us-east-1": { + "us-east-1": 100, + "us-west-2": 0, + "eu-central-1": 0, + "ap-southeast-1": 0, + }, + "eu-central-1": { + "eu-central-1": 100, + "us-west-2": 0, + "us-east-1": 0, + "ap-southeast-1": 0, + }, + "ap-southeast-1": { + "ap-southeast-1": 100, + "us-west-2": 0, + "us-east-1": 0, + "eu-central-1": 0, + }, }; -// Offset ranges to regions (inclusive bounds) -const offsetRanges: { - min: number; - max: number; - region: BrowserbaseRegion; -}[] = [ - { min: -24, max: -4, region: "us-west-2" }, // UTC-24 to UTC-4 - { min: -3, max: 4, region: "eu-central-1" }, // UTC-3 to UTC+4 - { min: 5, max: 24, region: "ap-southeast-1" }, // UTC+5 to UTC+24 -]; - -function getClosestRegion(timezone?: string): BrowserbaseRegion { - try { - if (!timezone) { - return "us-west-2"; // Default if no timezone provided - } +function selectRegionWithProbability( + baseRegion: BrowserbaseRegion, + distributions: Record> +): BrowserbaseRegion { + const distribution = distributions[baseRegion]; + if (!distribution) { + return baseRegion; + } + + const random = Math.random() * 100; // Generate random number between 0-100 - // Check exact matches first - if (timezone in exactTimezoneMap) { - return exactTimezoneMap[timezone]; + let cumulativeProbability = 0; + for (const [region, probability] of Object.entries(distribution)) { + cumulativeProbability += probability; + if (random < cumulativeProbability) { + return region as BrowserbaseRegion; } + } + + // Fallback to base region if something goes wrong + return baseRegion; +} - // Check prefix matches - const prefix = timezone.split("/")[0]; - if (prefix in prefixToRegion) { - return prefixToRegion[prefix]; +function getRegionFromTimezoneAbbr(timezoneAbbr?: string): BrowserbaseRegion { + try { + if (!timezoneAbbr) { + return "us-west-2"; // Default if no timezone provided } - // Use offset-based fallback - const date = new Date(); - // Create a date formatter for the given timezone - const formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone }); - // Get the timezone offset in minutes - const timeString = formatter.format(date); - const testDate = new Date(timeString); - const hourOffset = (testDate.getTime() - date.getTime()) / (1000 * 60 * 60); - - const matchingRange = offsetRanges.find( - (range) => hourOffset >= range.min && hourOffset <= range.max - ); + // Direct lookup from timezone abbreviation + const region = timezoneAbbreviationMap[timezoneAbbr.toUpperCase()]; + if (region) { + return region; + } - return matchingRange?.region ?? "us-west-2"; + // Fallback to us-west-2 for unknown abbreviations + return "us-west-2"; } catch { return "us-west-2"; } } +interface EdgeConfig { + advancedStealth: boolean | undefined; + proxies: boolean | undefined; + regionDistribution: + | Record> + | undefined; +} + async function createSession(timezone?: string) { const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY!, }); - const browserSettings: Browserbase.Sessions.SessionCreateParams.BrowserSettings = { - viewport: { - width: 2560, - height: 1440, - }, - //@ts-expect-error - not present in the types, but valid - os: "windows", - blockAds: true, - advancedStealth: true - }; - - console.log("timezone ", timezone); - console.log("getClosestRegion(timezone)", getClosestRegion(timezone)); + const config = await getAll(); + + const { + advancedStealth: advancedStealthConfig, + proxies: proxiesConfig, + regionDistribution: distributionsConfig, + } = config; + + const advancedStealth: boolean = advancedStealthConfig ?? false; + const proxies: boolean = proxiesConfig ?? true; + + // Build browserSettings conditionally + const browserSettings: Browserbase.Sessions.SessionCreateParams.BrowserSettings = + { + viewport: { + width: 2560, + height: 1440, + }, + blockAds: true, + advancedStealth, + // Only set os if advancedStealth is true + ...(advancedStealth + ? { + os: "mac", + } + : { + os: "linux", + }), + }; + + // Use timezone abbreviation to determine base region + const closestRegion = getRegionFromTimezoneAbbr(timezone); + + // Get distributions from config or use default + const distributions = distributionsConfig ?? defaultDistributions; + + // Apply probability routing for potential load balancing + const finalRegion = selectRegionWithProbability(closestRegion, distributions); + + console.log("timezone abbreviation:", timezone); + console.log("mapped to region:", closestRegion); + console.log("final region after probability routing:", finalRegion); const session = await bb.sessions.create({ projectId: process.env.BROWSERBASE_PROJECT_ID!, - proxies: true, + proxies, browserSettings, keepAlive: true, - region: getClosestRegion(timezone), + region: finalRegion, }); return { session, diff --git a/app/components/BrowserSessionContainer.tsx b/app/components/BrowserSessionContainer.tsx index 072d2c7..529e183 100644 --- a/app/components/BrowserSessionContainer.tsx +++ b/app/components/BrowserSessionContainer.tsx @@ -11,6 +11,7 @@ interface BrowserSessionContainerProps { isCompleted: boolean; initialMessage: string | undefined; sessionTime?: number; + isFromSearchParam?: boolean; onStop?: () => void; onRestart?: () => void; } @@ -104,6 +105,7 @@ const BrowserSessionContainer: React.FC = ({ isCompleted, initialMessage, sessionTime = 0, + isFromSearchParam = false, onStop = () => {}, onRestart = () => {}, }) => { @@ -192,7 +194,7 @@ const BrowserSessionContainer: React.FC = ({ sessionUrl ? (