Skip to content

Commit

Permalink
feat: add cacheHit flag to response that indicates if it was retrie…
Browse files Browse the repository at this point in the history
…ved from cache
  • Loading branch information
TheUnderScorer committed Nov 1, 2023
1 parent f772512 commit c101318
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 12 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ const fpjsClient = new FpjsClient({

Cache keys are based on the combination of _GetOptions_. For example, API responses for calls with `extendedResult: true` and `extendedResult: false` are stored independently.

You can check if your response was retrieved from cache like this:
```js

const result = await fpjsClient.getVisitorData({ extendedResult: true })

// true if cache was hit
console.log(result.cacheHit)

```

#### Creating a custom cache

The SDK can use a custom cache store implemented inside your application. This is useful when a different data store is more convenient in your environment, such as a hybrid mobile app.
Expand Down
49 changes: 44 additions & 5 deletions __tests__/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,29 @@ describe(`SPA client`, () => {
expect(result2?.visitorId).toBe(mockVisitorId)
})

it('should return cached response on second call if it exists', async () => {
const client = new FpjsClient({
loadOptions: getDefaultLoadOptions(),
cacheLocation: CacheLocation.LocalStorage,
})
await client.init()

const result1 = await client.getVisitorData()
expect(result1).toEqual({
visitorId: mockVisitorId,
cacheHit: false,
})

const result2 = await client.getVisitorData()

expect(result2).toEqual({
visitorId: mockVisitorId,
cacheHit: true,
})

expect(agentGetMock).toHaveBeenCalledTimes(1)
})

it('should remove in-flight request even if it throws', async () => {
agentGetMock.mockReset().mockRejectedValue(new Error())

Expand All @@ -251,6 +274,7 @@ describe(`SPA client`, () => {

await expect(client.getVisitorData({}, true)).resolves.toEqual({
visitorId: mockVisitorId,
cacheHit: false,
})

expect(agentGetMock).toHaveBeenCalledTimes(2)
Expand Down Expand Up @@ -280,8 +304,14 @@ describe(`SPA client`, () => {
const result2 = await getCall2

expect(agentGetMock).toHaveBeenCalledTimes(0)
expect(result1?.visitorId).toBe(mockVisitorId)
expect(result2?.visitorId).toBe(mockVisitorId)
expect(result1).toEqual({
visitorId: mockVisitorId,
cacheHit: true,
})
expect(result2).toEqual({
visitorId: mockVisitorId,
cacheHit: true,
})
})

it(`shouldn't get cached data if there is an in-flight request already if options are different`, async () => {
Expand All @@ -308,8 +338,14 @@ describe(`SPA client`, () => {
const result2 = await getCall2

expect(agentGetMock).toHaveBeenCalledTimes(1)
expect(result1?.visitorId).toBe(mockVisitorId)
expect(result2?.visitorId).toBe(mockVisitorId)
expect(result1).toEqual({
visitorId: mockVisitorId,
cacheHit: true,
})
expect(result2).toEqual({
visitorId: mockVisitorId,
cacheHit: false,
})
})

it(`shouldn't get cached data if a flag to ignore cache is set to true`, async () => {
Expand All @@ -332,7 +368,10 @@ describe(`SPA client`, () => {
const result = await client.getVisitorData(options, true)

expect(agentGetMock).toHaveBeenCalledTimes(1)
expect(result?.visitorId).toBe(mockVisitorId)
expect(result).toEqual({
visitorId: mockVisitorId,
cacheHit: false,
})
})
})

Expand Down
26 changes: 19 additions & 7 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
MAX_CACHE_LIFE,
SessionStorageCache,
} from './cache'
import { CacheLocation, FpjsClientOptions, VisitorData } from './global'
import { CacheLocation, FpjsClientOptions, FpjsSpaResponse, VisitorData } from './global'
import * as packageInfo from '../package.json'

const cacheLocationBuilders: Record<CacheLocation, (prefix?: string) => ICache> = {
Expand Down Expand Up @@ -158,7 +158,10 @@ export class FpjsClient {
* @param options
* @param ignoreCache if set to true a request to the API will be made even if the data is present in cache
*/
public async getVisitorData<TExtended extends boolean>(options: GetOptions<TExtended> = {}, ignoreCache = false) {
public async getVisitorData<TExtended extends boolean>(
options: GetOptions<TExtended> = {},
ignoreCache = false
): Promise<FpjsSpaResponse<VisitorData<TExtended>>> {
const cacheKey = FpjsClient.makeCacheKey(options)
const key = cacheKey.toKey()

Expand All @@ -169,7 +172,7 @@ export class FpjsClient {
this.inFlightRequests.set(key, promise)
}

return (await this.inFlightRequests.get(key)) as VisitorData<TExtended>
return (await this.inFlightRequests.get(key)) as FpjsSpaResponse<VisitorData<TExtended>>
}

/**
Expand All @@ -186,19 +189,28 @@ export class FpjsClient {
return new CacheKey<TExtended>(options)
}

private async _identify<TExtended extends boolean>(options: GetOptions<TExtended>, ignoreCache = false) {
private async _identify<TExtended extends boolean>(
options: GetOptions<TExtended>,
ignoreCache = false
): Promise<FpjsSpaResponse<VisitorData<TExtended>>> {
const key = FpjsClient.makeCacheKey(options)

if (!ignoreCache) {
const cacheResult = await this.cacheManager.get(key)

if (cacheResult) {
return cacheResult
return {
...cacheResult,
cacheHit: true,
}
}
}

const agentResult = await this.agent.get(options)
const agentResult = (await this.agent.get(options)) as VisitorData<TExtended>
await this.cacheManager.set(key, agentResult)
return agentResult
return {
...agentResult,
cacheHit: false,
}
}
}
5 changes: 5 additions & 0 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export type VisitorData<TExtended extends boolean = false> = TExtended extends f
? FingerprintJS.GetResult
: FingerprintJS.ExtendedGetResult

export type FpjsSpaResponse<T> = T & {
// Indicates whether the response was retrieved from cache.
cacheHit: boolean
}

export enum CacheLocation {
Memory = 'memory',
LocalStorage = 'localstorage',
Expand Down

0 comments on commit c101318

Please sign in to comment.