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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | 69x 69x 69x 82x 81x 82x 82x 69x 81x 69x 36x 65x 65x 52x 52x 13x 781x 781x 1x 1x 780x 153x 153x 627x 627x 627x 627x 429x 198x | import { isProtectedApiHttpMethod, ProtectedApi, ProtectedApiHttpMethod } from '../../../shared/types'
import { findMatchingRoute, parseRoutes, Route } from '@fingerprintjs/url-matcher'
import { logger } from '../../shared/logger'
/**
* Function that processes agent data received from the worker for protected API requests.
*
* @example
* ```typescript
* const data = response.headers.get(AGENT_DATA_HEADER)
* if (data) {
* ctx.processAgentData(data)
* }
* ```
* */
type AgentDataProcessor = (data: string) => void
/**
* Context interface for patchers that provides access to signals data.
* Used in instrumentation to share signal information between different patching components.
*/
export type PatcherContext = {
/**
* Retrieves the current signals' data.
* @returns Signals string if set, undefined otherwise
*/
getSignals: () => Promise<string | undefined>
/**
* Processes agent data received from the worker for protected API requests.
* @param data - Agent data
* */
processAgentData: AgentDataProcessor
/**
* Determines whether a given URL with a specified HTTP method is classified as protected.
*
* @param {string} url - The URL to be evaluated.
* @param {string} method - The HTTP method (e.g., GET, POST, PUT, DELETE) used in the request.
* @returns {boolean} Returns true if the URL is protected, otherwise false.
*/
isProtectedUrl: (url: string, method: string) => boolean
}
/**
* Writable implementation of PatcherContext that allows both reading and writing.
* Provides a mutable context for patchers that need to handle specific fingerprinting tasks.
*
* @note: Providers can only be set once to prevent accidental overwrites.
*/
export class WritablePatcherContext implements PatcherContext {
/**
* Function that resolves to the signal data.
* */
private signalsProvider?: () => Promise<string | undefined>
/**
* Function that processes agent data received from the worker for protected API requests.
* */
private agentDataProcessor?: AgentDataProcessor
/**
* Represents an array of route configurations specifically intended for protected
* endpoints within an API. Each route object in the array defines the route's
* properties and specifies the HTTP methods allowed for the protected resource.
*/
private readonly protectedRoutes: Route<{ methods: ProtectedApiHttpMethod[] }>[]
/**
* A set containing the uppercased names of HTTP methods that are designated as protected.
*
* In most cases, protected methods will be a POST, PUT or DELETE request.
* In instances where multiple GET requests are being made, this set can be used to quickly filter out these requests without
* unnecessary route matching.
*/
private readonly protectedMethods = new Set<ProtectedApiHttpMethod>()
constructor(protectedApis: ProtectedApi[]) {
const routeMethodMap: Record<string, ProtectedApiHttpMethod[]> = {}
protectedApis.forEach((api) => {
if (!routeMethodMap[api.url]) {
routeMethodMap[api.url] = []
}
this.protectedMethods.add(api.method)
routeMethodMap[api.url].push(api.method)
})
const routeObjects = Object.entries(routeMethodMap).map(([url, methods]) => {
return {
url,
metadata: {
methods,
},
}
})
this.protectedRoutes = parseRoutes(routeObjects)
}
/**
* Retrieves the current signals' data using signals' provider.
* @returns Signals string if set, undefined otherwise
*/
async getSignals(): Promise<string | undefined> {
return this.signalsProvider?.()
}
/**
* Sets signals data provider. Can only be called once - subsequent calls will log a warning and return early.
* @param signalsProvider - The signals provider to store in the context
*/
setSignalsProvider(signalsProvider: () => Promise<string | undefined>) {
Iif (this.signalsProvider) {
logger.warn('Invalid attempt to set signals provider that are already set.')
return
}
this.signalsProvider = signalsProvider
}
/**
* Sets agent data processor. Can only be called once - subsequent calls will log a warning and return early.
* @param agentDataProcessor - The agent data processor to store in the context
* */
setAgentDataProcessor(agentDataProcessor: AgentDataProcessor) {
Iif (this.agentDataProcessor) {
logger.warn('Invalid attempt to set agent data processor that is already set.')
return
}
this.agentDataProcessor = agentDataProcessor
}
/**
* Processes agent data received from the worker for protected API requests.
* @param data - Agent data
* */
processAgentData(data: string): void {
this.agentDataProcessor?.(data)
}
/**
* Determines whether the specified URL and method correspond to a protected route.
*
* @param {string} url - The URL to check against the protected routes.
* @param {string} method - The HTTP method to verify for the specified URL.
* @return {boolean} Returns true if the URL and method match a protected route, otherwise false.
*/
isProtectedUrl(url: string, method: string): boolean {
const normalizedMethod = method.toUpperCase()
if (!isProtectedApiHttpMethod(normalizedMethod)) {
logger.debug('Method is not a protected API method', normalizedMethod)
return false
}
// Check method first to avoid unnecessary route matching
if (!this.protectedMethods.has(normalizedMethod)) {
logger.debug('Method not protected:', normalizedMethod)
return false
}
const urlToMatch = new URL(url, location.origin)
logger.debug('Matching URL:', urlToMatch.href)
const matchedRoute = findMatchingRoute(urlToMatch, this.protectedRoutes)
if (matchedRoute) {
return Boolean(matchedRoute.metadata?.methods.includes(normalizedMethod))
}
return false
}
}
|