TypeScript 97.5%
SQL 1.4%
Python 0.8%
1"use server";23import { revalidatePath } from "next/cache";4import { z } from "zod";5import { COUNTRIES } from "@fetcha/core";6import { getWorkspace } from "@/lib/session";7import { internalApi, InternalApiError } from "@/lib/api";8import type { ActionResult } from "./projects";910/** Session as returned by the Fetcha API (`POST /v1/sessions`). Provider details are never included. */11export interface DashboardSession {12 id: string;13 label: string | null;14 status: string;15 network: string;16 country: string | null;17 region: string | null;18 city: string | null;19 request_count: number;20 last_used_at: string | null;21 expires_at: string;22 created_at: string;23}2425const createSchema = z.object({26 country: z27 .string()28 .trim()29 .length(2)30 .toUpperCase()31 .refine((c) => c in COUNTRIES, "Unsupported country")32 .optional(),33 region: z.string().trim().max(64).optional(),34 city: z.string().trim().max(128).optional(),35 // Only `auto` and `residential` are live in V1; the API rejects the others with NETWORK_UNAVAILABLE.36 network: z.enum(["auto", "residential", "datacenter", "isp", "mobile"]).default("auto"),37 ttl: z.coerce.number().int().min(60).max(1800).default(600),38 label: z.string().trim().max(128).optional(),39});4041export type CreateSessionInput = z.input<typeof createSchema>;4243function clean<T extends Record<string, unknown>>(o: T): Partial<T> {44 return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined && v !== "")) as Partial<T>;45}4647export async function createDashboardSession(input: CreateSessionInput): Promise<ActionResult<DashboardSession>> {48 const ws = await getWorkspace();49 const parsed = createSchema.safeParse(clean(input as Record<string, unknown>));50 if (!parsed.success) return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input", field: String(parsed.error.issues[0]?.path[0] ?? "") };51 try {52 const session = (await internalApi.createSession(ws.project.id, ws.user.id, clean(parsed.data))) as DashboardSession;53 revalidatePath("/dashboard/sessions");54 revalidatePath("/dashboard");55 return { ok: true, data: session };56 } catch (e) {57 if (e instanceof InternalApiError) return { ok: false, error: e.code === "NETWORK_UNAVAILABLE" ? "That network class is not available yet. Use auto or residential." : e.message };58 return { ok: false, error: "Could not create the session. Please try again." };59 }60}6162export async function closeDashboardSession(id: string): Promise<ActionResult> {63 const ws = await getWorkspace();64 if (!/^sess_[A-Za-z0-9]{4,64}$/.test(id)) return { ok: false, error: "Invalid session id" };65 try {66 await internalApi.closeSession(ws.project.id, ws.user.id, id);67 revalidatePath("/dashboard/sessions");68 revalidatePath("/dashboard");69 return { ok: true };70 } catch (e) {71 if (e instanceof InternalApiError) {72 if (e.code === "SESSION_NOT_FOUND") return { ok: false, error: "Session not found in this project." };73 return { ok: false, error: e.message };74 }75 return { ok: false, error: "Could not close the session. Please try again." };76 }77}78