import { HttpResponse } from '@apimatic/core-interfaces'; import { getHeader } from '@apimatic/http-headers'; import { detect } from 'detect-browser'; import warning from 'tiny-warning'; /** * Validates the protocol and removes duplicate forward slashes * * @param url URL to clean * @returns Sanitized URL */ export function sanitizeUrl(url: string): string { // ensure that the urls are absolute const protocolRegex = /^https?:\/\/[^/]+/; const match = url.match(protocolRegex); if (match === null) { throw new Error(`Invalid URL format: ${url}`); } // remove redundant double-forward slashes const protocol = match[0]; const queryUrl = url.substring(protocol.length).replace(/\/\/+/g, '/'); return protocol + queryUrl; } /** * Create warning for deprecated method usage. * * This is called once per deprecated method. If this method is called again * with the same arguments, no warning is generated. * * @param methodName Method name for deprecated method * @param notice Optional message for deprecation */ export function deprecated(methodName: string, notice?: string): void { let message = `Method ${methodName} is deprecated.`; if (notice) { message += ` ${notice}`; } if (process.env.NODE_ENV !== 'production') { warning(false, message); } } /** * Replace the templated placeholders in user-agent with the platform * related information. * @param userAgent User-agent value to be updated * @returns Updated user-agent value */ export function updateUserAgent( userAgent: string, apiVersion?: string, detail?: string ): string { let updatedAgent = userAgent; const result = detect(); if (result) { updatedAgent = updatedAgent.replace('{engine}', result.name); } if (result?.version) { updatedAgent = updatedAgent.replace('{engine-version}', result.version); } if (result?.os) { updatedAgent = updatedAgent.replace('{os-info}', result.os); } if (typeof apiVersion !== 'undefined') { updatedAgent = updatedAgent.replace('{api-version}', apiVersion); } if (typeof detail !== 'undefined') { assertUserAgentDetail(detail); updatedAgent = updatedAgent.replace('{detail}', encodeURIComponent(detail)); } return updatedAgent; } function assertUserAgentDetail(detail: string) { if (detail.length > 128) { throw new Error('userAgentDetail length exceeds 128 characters limit'); } } /** * Replace the templated placeholders in error with the platform * related information. * @param message message value to be updated * @returns Updated message value */ export function updateErrorMessage( message: string, response: HttpResponse ): string { const placeholders = message.match(/\{\$.*?\}/g); const statusCodePlaceholder = placeholders?.includes('{$statusCode}'); const headerPlaceholders = placeholders?.filter((value) => value.startsWith('{$response.header') ); const bodyPlaceholders = placeholders?.filter((value) => value.startsWith('{$response.body') ); message = replaceStatusCodePlaceholder( message, response.statusCode, statusCodePlaceholder ); message = replaceHeaderPlaceholders( message, response.headers, headerPlaceholders ); if (typeof response.body === 'string') { message = replaceBodyPlaceholders(message, response.body, bodyPlaceholders); } return message; } function replaceStatusCodePlaceholder( message: string, statusCode: number, statusCodePlaceholder?: boolean ): string { if (statusCodePlaceholder) { return message.replace('{$statusCode}', statusCode.toString()); } return message; } function replaceHeaderPlaceholders( message: string, headers: Record, headerPlaceholders?: string[] ): string { if (headerPlaceholders) { headerPlaceholders.forEach((element) => { const headerName = element.split('.').pop()?.slice(0, -1); if (typeof headerName !== 'undefined') { const value = getHeader(headers, headerName) ?? ''; message = message.replace(element, value); } }); } return message; } function replaceBodyPlaceholders( message: string, body: string, bodyPlaceholders?: string[] ): string { let parsed = ''; try { parsed = JSON.parse(body); } catch (error) { // Handle the error if needed, or you can leave the catch block empty } bodyPlaceholders?.forEach((element) => { if (element.includes('#')) { const [, ...rest] = element?.split('#'); const nodePointer = rest.join('#')?.slice(0, -1); if (nodePointer) { const value = getValueByJsonPointer(parsed, nodePointer); const replaced_value = value !== null ? JSON.stringify(value) : ''; message = message.replace(element, replaced_value); } } else { message = message.replace(element, JSON.stringify(parsed)); } }); return message; } export function getValueByJsonPointer(obj: any, pointer: string): any { if (pointer === '') { return obj; } const pathParts = pointer.split('/').filter(Boolean); let result = obj; for (const key of pathParts) { if (!result || typeof result !== 'object' || !(key in result)) { return null; } result = result[key]; } return result; } export function updateByJsonPointer( obj: T, pointer: string, updater: (val: any) => any ): T { if (pointer === '') { return updater(obj); } return updateAtPath(updater, obj, pointer.split('/').filter(Boolean)); } function updateAtPath( updater: (val: any) => any, current: any, parts: string[], index: number = 0 ): any { if (!current || typeof current !== 'object') { return current; } const key = parts[index]; const next = current[key]; const cloned = Array.isArray(current) ? [...current] : { ...current }; cloned[key] = index === parts.length - 1 ? updater(next) // update directly for last part : updateAtPath(updater, next, parts, index + 1); // call recursively return cloned; }