diff --git a/src/nurl.ts b/src/nurl.ts index de40308..a21ae8b 100644 --- a/src/nurl.ts +++ b/src/nurl.ts @@ -1,5 +1,4 @@ import {decode, encode} from './punycode' -import {mask, MaskOptions, match as matchUrlPattern} from './utils' import { extractPathKey, getDynamicPaths, @@ -9,7 +8,10 @@ import { refineQueryWithPathname, convertQueryToArray, Query, -} from './utils/internal' + match as matchUrlPattern, + mask, + MaskOptions, +} from './utils' interface URLOptions extends Partial< diff --git a/src/utils/external.ts b/src/utils/external.ts deleted file mode 100644 index 1e0240f..0000000 --- a/src/utils/external.ts +++ /dev/null @@ -1,65 +0,0 @@ -import NURL from '../nurl' -import {extractPathKey, getPathPriority, isDynamicPath, refinePathnameWithQuery} from './internal' - -export const match = (url: string, pattern: string): Record | null => { - if (!NURL.canParse(url) || !NURL.canParse(pattern)) { - return null - } - - const urlSegments = url.split(/[?#]/)[0]?.split('/').filter(Boolean) || [] - const patternSegments = pattern.split(/[?#]/)[0]?.split('/').filter(Boolean) || [] - - if (urlSegments.length !== patternSegments.length) { - return null - } - - const params: Record = {} - - for (let i = 0; i < patternSegments.length; i++) { - const patternSegment = patternSegments[i] - const urlSegment = urlSegments[i] - - if (isDynamicPath(patternSegment)) { - const pathKey = extractPathKey(patternSegment) - params[pathKey] = urlSegment - } else if (patternSegment !== urlSegment) { - return null - } - } - - return params -} - -export interface MaskOptions { - patterns: string[] - sensitiveParams: string[] - maskChar?: string - maskLength?: number - preserveLength?: boolean -} - -export const mask = ( - url: string, - {patterns, sensitiveParams, maskChar = '*', maskLength = 4, preserveLength = false}: MaskOptions, -) => { - const sortedPatterns = [...patterns].sort((a, b) => (getPathPriority(b) > getPathPriority(a) ? 1 : -1)) - for (const pattern of sortedPatterns) { - const urlObj = NURL.create(url) - const matchedParams = match(urlObj.pathname, pattern) - if (!matchedParams) { - continue - } - sensitiveParams.forEach((sensitiveParam) => { - if (sensitiveParam in matchedParams) { - const originalValue = matchedParams[sensitiveParam] - const lengthToMask = preserveLength ? originalValue.length : maskLength - matchedParams[sensitiveParam] = maskChar.repeat(lengthToMask) - } - }) - - urlObj.pathname = refinePathnameWithQuery(pattern, matchedParams) - return urlObj.toString() - } - - return url -} diff --git a/src/utils/index.ts b/src/utils/index.ts index eea7f38..c7bb35f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1 @@ -export * from './external' +export * from './url' diff --git a/src/utils/internal.ts b/src/utils/url.ts similarity index 50% rename from src/utils/internal.ts rename to src/utils/url.ts index 82128aa..e008865 100644 --- a/src/utils/internal.ts +++ b/src/utils/url.ts @@ -1,3 +1,5 @@ +import NURL from '../nurl' + const DYNAMIC_PATH_COLON_REGEXP = /^:/ const DYNAMIC_PATH_BRACKETS_REGEXP = /^\[.*\]$/ @@ -87,11 +89,91 @@ export function convertQueryToArray(query: Query): string[][] { * @param {string} pathname * @returns {string} path priority representation * - * @example /user/:id/profile -> 212 - * @example /user/admin/:tab -> 221 + * @example getPathPriority('/user/:id/profile') -> '212' + * @example getPathPriority('/user/admin/:tab') -> '221' */ export function getPathPriority(pathname: string): string { const segments = pathname.split('/').filter(Boolean) return segments.map((segment) => (isDynamicPath(segment) ? '1' : '2')).join('') } + +/** + * Match a URL against a pattern and extract dynamic parameters. + * @param {string} url - The URL to match. + * @param {string} pattern - The pattern to match against. + * @returns {Record | null} - An object containing the extracted parameters or null if no match. + */ +export const match = (url: string, pattern: string): Record | null => { + if (!NURL.canParse(url) || !NURL.canParse(pattern)) { + return null + } + + const urlSegments = url.split(/[?#]/)[0]?.split('/').filter(Boolean) || [] + const patternSegments = pattern.split(/[?#]/)[0]?.split('/').filter(Boolean) || [] + + if (urlSegments.length !== patternSegments.length) { + return null + } + + const params: Record = {} + + for (let i = 0; i < patternSegments.length; i++) { + const patternSegment = patternSegments[i] + const urlSegment = urlSegments[i] + + if (isDynamicPath(patternSegment)) { + const pathKey = extractPathKey(patternSegment) + params[pathKey] = urlSegment + } else if (patternSegment !== urlSegment) { + return null + } + } + + return params +} + +export interface MaskOptions { + /** Patterns to match against the URL pathname */ + patterns: string[] + /** Sensitive parameters to mask */ + sensitiveParams: string[] + /** Character used for masking (default: '*') */ + maskChar?: string + /** Length of the mask (default: 4) */ + maskLength?: number + /** Whether to preserve the length of the sensitive value when masking (default: false) */ + preserveLength?: boolean +} + +/** + * Masks sensitive parameters in a URL based on provided options. + * @param {string} url - The URL to mask. + * @param {MaskOptions} options - The masking options. + * @returns {string} - The masked URL. + */ +export const mask = ( + url: string, + {patterns, sensitiveParams, maskChar = '*', maskLength = 4, preserveLength = false}: MaskOptions, +) => { + const sortedPatterns = [...patterns].sort((a, b) => (getPathPriority(b) > getPathPriority(a) ? 1 : -1)) + for (const pattern of sortedPatterns) { + const urlObj = NURL.create(url) + const matchedParams = match(urlObj.pathname, pattern) + if (!matchedParams) { + continue + } + sensitiveParams.forEach((sensitiveParam) => { + if (sensitiveParam in matchedParams) { + const originalValue = matchedParams[sensitiveParam] + const lengthToMask = preserveLength ? originalValue.length : maskLength + matchedParams[sensitiveParam] = maskChar.repeat(lengthToMask) + } + }) + + urlObj.pathname = refinePathnameWithQuery(pattern, matchedParams) + return urlObj.toString() + } + + return url +}