Skip to content

Commit

Permalink
fix: add 400 and 429 status mappings for DELETE API
Browse files Browse the repository at this point in the history
  • Loading branch information
TheUnderScorer committed Jun 24, 2024
1 parent 2d0687b commit 8574924
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 22 deletions.
65 changes: 61 additions & 4 deletions resources/fingerprint-server-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,16 @@ paths:
headers:
Retry-After:
description: >-
Indicates how long you should wait before attempting the next
request.
Indicates how many seconds you should wait before attempting the
next request.
schema:
type: integer
format: int32
minimum: 0
content:
application/json:
schema:
$ref: '#/components/schemas/ManyRequestsResponse'
$ref: '#/components/schemas/TooManyRequestsResponse'
delete:
tags:
- Fingerprint
Expand Down Expand Up @@ -258,6 +258,14 @@ paths:
responses:
'200':
description: OK. The visitor ID is scheduled for deletion.
'400':
description: >-
Bad request. The visitor ID parameter is missing or in the wrong
format.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorVisitsDelete400Response'
'403':
description: Forbidden. Access to this API is denied.
content:
Expand All @@ -272,6 +280,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorVisitsDelete404Response'
'429':
description: Too Many Requests. The request is throttled.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorCommon429Response'
/webhook:
trace:
tags:
Expand Down Expand Up @@ -631,6 +645,27 @@ components:
required:
- code
- message
ErrorCommon429Response:
type: object
additionalProperties: false
properties:
error:
type: object
additionalProperties: false
properties:
code:
type: string
description: |
Error code: * `TooManyRequests` - The request is throttled.
enum:
- TooManyRequests
example: TooManyRequests
message:
type: string
example: request throttled
required:
- code
- message
ErrorEvent404Response:
type: object
additionalProperties: false
Expand Down Expand Up @@ -664,7 +699,7 @@ components:
example: Forbidden (HTTP 403)
required:
- error
ManyRequestsResponse:
TooManyRequestsResponse:
type: object
additionalProperties: false
properties:
Expand Down Expand Up @@ -697,6 +732,28 @@ components:
required:
- code
- message
ErrorVisitsDelete400Response:
type: object
additionalProperties: false
properties:
error:
type: object
additionalProperties: false
properties:
code:
type: string
description: >
Error code: * `RequestCannotBeParsed` - The visitor ID parameter
is missing or in the wrong format.
enum:
- RequestCannotBeParsed
example: RequestCannotBeParsed
message:
type: string
example: invalid visitor id
required:
- code
- message
WebhookVisit:
type: object
properties:
Expand Down
44 changes: 41 additions & 3 deletions src/generatedApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ export interface components {
message: string
}
}
ErrorCommon429Response: {
error?: {
/**
* @description Error code: * `TooManyRequests` - The request is throttled.
*
* @example TooManyRequests
* @enum {string}
*/
code: 'TooManyRequests'
/** @example request throttled */
message: string
}
}
ErrorEvent404Response: {
/** ErrorEvent404ResponseError */
error?: {
Expand All @@ -185,7 +198,7 @@ export interface components {
*/
error: string
}
ManyRequestsResponse: {
TooManyRequestsResponse: {
/**
* @description Error text.
* @example request throttled
Expand All @@ -206,6 +219,19 @@ export interface components {
message: string
}
}
ErrorVisitsDelete400Response: {
error?: {
/**
* @description Error code: * `RequestCannotBeParsed` - The visitor ID parameter is missing or in the wrong format.
*
* @example RequestCannotBeParsed
* @enum {string}
*/
code: 'RequestCannotBeParsed'
/** @example invalid visitor id */
message: string
}
}
WebhookVisit: {
/** @example 3HNey93AkBW6CRbxV6xP */
visitorId: string
Expand Down Expand Up @@ -1092,11 +1118,11 @@ export interface operations {
/** @description Too Many Requests */
429: {
headers: {
/** @description Indicates how long you should wait before attempting the next request. */
/** @description Indicates how many seconds you should wait before attempting the next request. */
'Retry-After'?: number
}
content: {
'application/json': components['schemas']['ManyRequestsResponse']
'application/json': components['schemas']['TooManyRequestsResponse']
}
}
}
Expand All @@ -1123,6 +1149,12 @@ export interface operations {
200: {
content: never
}
/** @description Bad request. The visitor ID parameter is missing or in the wrong format. */
400: {
content: {
'application/json': components['schemas']['ErrorVisitsDelete400Response']
}
}
/** @description Forbidden. Access to this API is denied. */
403: {
content: {
Expand All @@ -1135,6 +1167,12 @@ export interface operations {
'application/json': components['schemas']['ErrorVisitsDelete404Response']
}
}
/** @description Too Many Requests. The request is throttled. */
429: {
content: {
'application/json': components['schemas']['ErrorCommon429Response']
}
}
}
}
}
21 changes: 19 additions & 2 deletions src/serverApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getDeleteVisitorDataUrl, getEventUrl, getVisitorsUrl } from './urlUtils'
import {
AuthenticationMode,
CommonError429,
DeleteVisitorError,
EventError,
EventResponse,
Expand All @@ -24,6 +25,8 @@ export class FingerprintJsServerApiClient {

protected readonly fetch: typeof fetch

protected static readonly DEFAULT_RETRY_AFTER = 1

/**
* FingerprintJS server API client used to fetch data from FingerprintJS
* @constructor
Expand Down Expand Up @@ -145,6 +148,14 @@ export class FingerprintJsServerApiClient {

const jsonResponse = await response.json()

if (response.status === 429) {
const retryAfter = this.getRetryAfter(response)

;(jsonResponse as CommonError429).retryAfter = retryAfter
? parseInt(retryAfter)
: FingerprintJsServerApiClient.DEFAULT_RETRY_AFTER

Check warning on line 156 in src/serverApiClient.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}

throw { ...(jsonResponse as DeleteVisitorError), response, status: response.status } as DeleteVisitorError
})
.catch((err) => {
Expand Down Expand Up @@ -211,8 +222,10 @@ export class FingerprintJsServerApiClient {
return jsonResponse as VisitorsResponse
}
if (response.status === 429) {
const retryAfter = response.headers.get('retry-after') || ''
;(jsonResponse as VisitorsError429).retryAfter = retryAfter === '' ? 1 : parseInt(retryAfter)
const retryAfter = this.getRetryAfter(response)
;(jsonResponse as VisitorsError429).retryAfter = retryAfter
? parseInt(retryAfter)
: FingerprintJsServerApiClient.DEFAULT_RETRY_AFTER
}
throw { ...(jsonResponse as VisitorsError), response, status: response.status } as VisitorsError
})
Expand All @@ -230,4 +243,8 @@ export class FingerprintJsServerApiClient {
private getHeaders() {
return this.authenticationMode === AuthenticationMode.AuthHeader ? { 'Auth-API-Key': this.apiKey } : undefined
}

private getRetryAfter(response: Response) {
return response.headers.get('retry-after')
}
}
38 changes: 25 additions & 13 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ export interface Options {
*/
export type VisitorHistoryFilter = paths['/visitors/{visitor_id}']['get']['parameters']['query']

export type TooManyRequestsError = {
status: 429
/**
* How many seconds to wait before retrying
*/
retryAfter: number
}

/**
* More info: https://dev.fingerprintjs.com/docs/server-api#response
*/
Expand All @@ -49,13 +57,7 @@ export type VisitorsError403 =
status: 403
}
export type VisitorsError429 =
paths['/visitors/{visitor_id}']['get']['responses']['429']['content']['application/json'] & {
status: 429
/**
* How many seconds to wait before retrying
*/
retryAfter: number
}
paths['/visitors/{visitor_id}']['get']['responses']['429']['content']['application/json'] & TooManyRequestsError

export type DeleteVisitError404 =
paths['/visitors/{visitor_id}']['delete']['responses']['404']['content']['application/json'] & {
Expand All @@ -67,9 +69,18 @@ export type DeleteVisitError403 =
status: 403
}

export type DeleteVisitError400 =
paths['/visitors/{visitor_id}']['delete']['responses']['400']['content']['application/json'] & {
status: 400
}

export type CommonError429 = components['schemas']['ErrorCommon429Response'] & TooManyRequestsError

export type VisitorsError = WithResponse<VisitorsError403 | VisitorsError429>

export type DeleteVisitorError = WithResponse<DeleteVisitError404 | DeleteVisitError403>
export type DeleteVisitorError = WithResponse<
DeleteVisitError404 | DeleteVisitError403 | DeleteVisitError400 | CommonError429
>

export function isVisitorsError(response: any): response is VisitorsError {
return (
Expand All @@ -82,14 +93,15 @@ export function isVisitorsError(response: any): response is VisitorsError {
}

export function isDeleteVisitorError(response: any): response is DeleteVisitorError {
return (
(response?.hasOwnProperty('status') &&
(response.status === 403 || response.status === 404) &&
const statusCodes = [400, 403, 404, 429]

return Boolean(
response?.hasOwnProperty('status') &&

Check warning on line 99 in src/types.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
statusCodes.includes(response.status) &&
response.error?.hasOwnProperty('message') &&
typeof response.error.message === 'string' &&
response.error?.hasOwnProperty('code') &&
typeof response.error.code === 'string') ||
false
typeof response.error.code === 'string'
)
}

Expand Down
2 changes: 2 additions & 0 deletions sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ examplesList=(
'get_event_200_identification_failed_error.json'
'get_event_200_identification_too_many_requests_error.json'
'get_visits_429_too_many_requests_error.json'
'delete_visits_400_error.json'
'delete_visits_404_error.json'
'delete_visits_403_error.json'
'delete_visits_429_error.json'
)

for example in ${examplesList[*]}; do
Expand Down
36 changes: 36 additions & 0 deletions tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FingerprintJsServerApiClient, Region } from '../../src'
import Error404 from './mocked-responses-data/external/delete_visits_404_error.json'
import Error403 from './mocked-responses-data/external/delete_visits_403_error.json'
import Error400 from './mocked-responses-data/external/delete_visits_400_error.json'
import Error429 from './mocked-responses-data/external/delete_visits_429_error.json'

jest.spyOn(global, 'fetch')

Expand Down Expand Up @@ -49,6 +51,40 @@ describe('[Mocked response] Delete visitor data', () => {
})
})

test('400 error', async () => {
;(fetch as unknown as jest.Mock).mockReturnValue(
Promise.resolve(
new Response(JSON.stringify(Error400), {
status: 400,
})
)
)

await expect(client.deleteVisitorData(existingVisitorId)).rejects.toMatchObject({
...Error400,
status: 400,
})
})

test('429 error', async () => {
;(fetch as unknown as jest.Mock).mockReturnValue(
Promise.resolve(
new Response(JSON.stringify(Error429), {
status: 429,
headers: {
'retry-after': '5',
},
})
)
)

await expect(client.deleteVisitorData(existingVisitorId)).rejects.toMatchObject({
...Error429,
status: 429,
retryAfter: 5,
})
})

test('Error with bad JSON', async () => {
;(fetch as unknown as jest.Mock).mockReturnValue(
Promise.resolve(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"error": {
"code": "RequestCannotBeParsed",
"message": "invalid visitor id"
}
}
Loading

0 comments on commit 8574924

Please sign in to comment.