import { ExtractQueryParams, Region } from './types' import { version } from '../package.json' import { paths } from './generatedApiTypes' const euRegionUrl = 'https://eu.api.fpjs.io/' const apRegionUrl = 'https://ap.api.fpjs.io/' const globalRegionUrl = 'https://api.fpjs.io/' type QueryStringScalar = string | number | boolean | null | undefined type QueryStringParameters = Record & { api_key?: string ii: string } export function getIntegrationInfo() { return `fingerprint-pro-server-node-sdk/${version}` } function isEmptyValue(value: any): boolean { return value === undefined || value === null } function serializeQueryStringParams(params: QueryStringParameters): string { const entries: [string, string][] = [] for (const [key, value] of Object.entries(params)) { // Use the helper for the main value if (isEmptyValue(value)) { continue } if (Array.isArray(value)) { for (const v of value) { // Also use the helper for each item in the array if (isEmptyValue(v)) { continue } entries.push([`${key}[]`, String(v)]) } } else { entries.push([key, String(value)]) } } if (!entries.length) { return '' } const urlSearchParams = new URLSearchParams(entries) return urlSearchParams.toString() } function getServerApiUrl(region: Region): string { switch (region) { case Region.EU: return euRegionUrl case Region.AP: return apRegionUrl case Region.Global: return globalRegionUrl default: throw new Error('Unsupported region') } } /** * Extracts parameter placeholders into a literal union type. * For example `extractPathParams<'/users/{userId}/posts/{postId}'>` resolves to `"userId" | "postId" */ type ExtractPathParams = T extends `${string}{${infer Param}}${infer Rest}` ? Param | ExtractPathParams : never type PathParams = ExtractPathParams extends never ? { pathParams?: never } : { pathParams: ExtractPathParams extends never ? never : string[] } type QueryParams = ExtractQueryParams extends never ? { queryParams?: any } // No query params : { queryParams?: ExtractQueryParams // Optional query params } type GetRequestPathOptions = { path: Path method: Method apiKey?: string region: Region } & PathParams & QueryParams /** * Formats a URL for the FingerprintJS server API by replacing placeholders and * appending query string parameters. * * @internal * * @param {GetRequestPathOptions} options * @param {Path} options.path - The path of the API endpoint * @param {string[]} [options.pathParams] - Path parameters to be replaced in the path * @param {string} [options.apiKey] - API key to be included in the query string * @param {QueryParams["queryParams"]} [options.queryParams] - Query string * parameters to be appended to the URL * @param {Region} options.region - The region of the API endpoint * @param {Method} options.method - The method of the API endpoint * * @returns {string} The formatted URL with parameters replaced and query string * parameters appended */ export function getRequestPath({ path, pathParams, apiKey, queryParams, region, // method mention here so that it can be referenced in JSDoc // eslint-disable-next-line @typescript-eslint/no-unused-vars method: _, }: GetRequestPathOptions): string { // Step 1: Extract the path parameters (placeholders) from the path const placeholders = Array.from(path.matchAll(/{(.*?)}/g)).map((match) => match[1]) // Step 2: Replace the placeholders with provided pathParams let formattedPath: string = path placeholders.forEach((placeholder, index) => { if (pathParams?.[index]) { formattedPath = formattedPath.replace(`{${placeholder}}`, pathParams[index]) } else { throw new Error(`Missing path parameter for ${placeholder}`) } }) const queryStringParameters: QueryStringParameters = { ...(queryParams ?? {}), ii: getIntegrationInfo(), } if (apiKey) { queryStringParameters.api_key = apiKey } const url = new URL(getServerApiUrl(region)) url.pathname = formattedPath url.search = serializeQueryStringParams(queryStringParameters) return url.toString() }