// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause import { SWRConfiguration } from "swr" export const swrConfig: SWRConfiguration = { fetcher: (url: string) => apiFetch(url, "GET"), onError: (err, _) => console.error(err), } let csrfToken: string let synoToken: string | undefined // required for synology API requests let unraidCsrfToken: string | undefined // required for unraid POST requests (#8062) // apiFetch wraps the standard JS fetch function with csrf header // management and param additions specific to the web client. // // apiFetch adds the `api` prefix to the request URL, // so endpoint should be provided without the `api` prefix // (i.e. provide `/data` rather than `api/data`). export function apiFetch( endpoint: string, method: "GET" | "POST" | "PATCH", body?: any ): Promise { const urlParams = new URLSearchParams(window.location.search) const nextParams = new URLSearchParams() if (synoToken) { nextParams.set("SynoToken", synoToken) } else { const token = urlParams.get("SynoToken") if (token) { nextParams.set("SynoToken", token) } } const search = nextParams.toString() const url = `api${endpoint}${search ? `?${search}` : ""}` var contentType: string if (unraidCsrfToken && method === "POST") { const params = new URLSearchParams() params.append("csrf_token", unraidCsrfToken) if (body) { params.append("ts_data", JSON.stringify(body)) } body = params.toString() contentType = "application/x-www-form-urlencoded;charset=UTF-8" } else { body = body ? JSON.stringify(body) : undefined contentType = "application/json" } return fetch(url, { method: method, headers: { Accept: "application/json", "Content-Type": contentType, "X-CSRF-Token": csrfToken, }, body: body, }) .then((r) => { updateCsrfToken(r) if (!r.ok) { return r.text().then((err) => { throw new Error(err) }) } return r }) .then((r) => { if (r.headers.get("Content-Type") === "application/json") { return r.json() } }) } function updateCsrfToken(r: Response) { const tok = r.headers.get("X-CSRF-Token") if (tok) { csrfToken = tok } } export function setSynoToken(token?: string) { synoToken = token } export function setUnraidCsrfToken(token?: string) { unraidCsrfToken = token } // incrementMetric hits the client metrics local API endpoint to // increment the given counter metric by one. export function incrementMetric(metricName: MetricName) { const postData: MetricsPOSTData[] = [ { Name: metricName, Type: "counter", Value: 1, }, ] apiFetch("/local/v0/upload-client-metrics", "POST", postData).catch( (error) => { console.error(error) } ) } type MetricsPOSTData = { Name: MetricName Type: MetricType Value: number } type MetricType = "counter" | "gauge" export type MetricName = | "web_client_advertise_exitnode_enable" | "web_client_advertise_exitnode_disable" | "web_client_use_exitnode_enable" | "web_client_use_exitnode_disable" | "web_client_ssh_enable" | "web_client_ssh_disable" | "web_client_node_connect" | "web_client_node_disconnect" | "web_client_advertise_routes_change" | "web_client_device_details_click"