All files / src urlUtils.ts

100% Statements 35/35
100% Branches 22/22
100% Functions 6/6
100% Lines 34/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120        8x   8x 8x 8x                 72x       61x   61x 129x 4x     125x 7x 17x 2x   15x     118x       61x   61x       62x   27x   1x   33x   1x                                                                                 63x     63x 63x 47x 47x 46x   1x       63x         63x 63x 63x   63x    
import { Region } from './types'
import { version } from '../package.json'
import { paths } from './generatedApiTypes'
 
const apiVersion = 'v4'
 
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<string, QueryStringScalar | QueryStringScalar[]> & {
  ii: string
}
 
export function getIntegrationInfo() {
  return `fingerprint-pro-server-node-sdk/${version}`
}
 
function serializeQueryStringParams(params: QueryStringParameters): string {
  const entries: [string, string][] = []
 
  for (const [key, value] of Object.entries(params)) {
    if (value === null || value === undefined) {
      continue
    }
 
    if (Array.isArray(value)) {
      for (const v of value) {
        if (v === null || v === undefined) {
          continue
        }
        entries.push([key, String(v)])
      }
    } else {
      entries.push([key, String(value)])
    }
  }
 
  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')
  }
}
 
export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'
 
export interface GetRequestPathOptions {
  path: keyof paths
  method: HttpMethod
  pathParams?: string[]
  queryParams?: Record<string, QueryStringScalar | QueryStringScalar[]>
  region?: Region
}
 
/**
 * Formats a URL for the FingerprintJS server API by replacing placeholders and
 * appending query string parameters.
 *
 * @internal
 *
 * @param {GetRequestPathOptions} options
 * @param {keyof paths} options.path - The path of the API endpoint
 * @param {string[]} [options.pathParams] - Path parameters to be replaced in the path
 * @param {GetRequestPathOptions["queryParams"]} [options.queryParams] - Query string
 *   parameters to be appended to the URL
 * @param {Region} options.region - The region of the API endpoint
 * @param {HttpMethod} 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,
  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 = `${apiVersion}${path}`
  placeholders.forEach((placeholder, index) => {
    const param = pathParams?.[index]
    if (param !== undefined && param !== '') {
      formattedPath = formattedPath.replace(`{${placeholder}}`, param)
    } else {
      throw new Error(`Missing path parameter for ${placeholder}`)
    }
  })
 
  const queryStringParameters: QueryStringParameters = {
    ...(queryParams ?? {}),
    ii: getIntegrationInfo(),
  }
 
  const url = new URL(getServerApiUrl(region ?? Region.Global))
  url.pathname = formattedPath
  url.search = serializeQueryStringParams(queryStringParameters)
 
  return url.toString()
}