Skip to content

Commit

Permalink
feat: support passing loadOptions in .init() method as well
Browse files Browse the repository at this point in the history
  • Loading branch information
TheUnderScorer committed Nov 1, 2023
1 parent 3ecc488 commit f772512
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 21 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,8 @@ import {
FingerprintJSPro
} from '@fingerprintjs/fingerprintjs-pro-spa';

// It can receive multiple parameters but the only required one is `loadOptions`,
// which contains the public API key
const fpjsClient = new FpjsClient({
// You can also pass these options later in `.init()` method
loadOptions: {
apiKey: "<PUBLIC_API_KEY>",
// endpoint: ["<CUSTOM_ENDPOINT>", FingerprintJSPro.defaultEndpoint],
Expand All @@ -104,7 +103,8 @@ const fpjsClient = new FpjsClient({
}
});
```
You can learn more about different load options here in the [JS Agent documentation](https://dev.fingerprint.com/docs/js-agent#initializing-the-agent).

> ⚠️ You must provide `loadOptions` which contain your public API key either in the constructor or in the `init` method. If you don't, the SDK will throw an error. You can learn more about different load options here in the [JS Agent documentation](https://dev.fingerprint.com/docs/js-agent#initializing-the-agent).
### 1 - Init the JS agent

Expand All @@ -113,14 +113,24 @@ Before you start making identification requests to the Fingerprint Pro API, you
```js
// with async/await
await fpjsClient.init()

const visitorData = await fpjsClient.getVisitorData()

// with promises
const visitorData = fpjsClient.init().then(() => {
return fpjsClient.getVisitorData()
})
```

:You can also pass the load options here
```js
await fpjsClient.init({
apiKey: "<PUBLIC_API_KEY>",
// endpoint: ["<CUSTOM_ENDPOINT>", FingerprintJSPro.defaultEndpoint],
// scriptUrlPattern: ["<CUSTOM_SCRIPT_URL>", FingerprintJSPro.defaultScriptUrlPattern],
// region: "eu"
}
)
```
### 2 - Calling an API
The `getVisitorData` method returns visitor identification data based on the request [options](https://dev.fingerprint.com/docs/js-agent#visitor-identification).
Set `ignoreCache` to `true` to make a request to the API even if the data is present in the cache.
Expand Down
31 changes: 31 additions & 0 deletions __tests__/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ describe(`SPA client`, () => {
)
})

it('should support passing loadOptions in .init()', async () => {
const client = new FpjsClient()

await client.init(getDefaultLoadOptions())

await expect(client.getVisitorData()).resolves.not.toThrow()

expect(loadSpy).toBeCalledTimes(1)
})

it('should merge loadOptions passed in .init() and in constructor', async () => {
const client = new FpjsClient({
loadOptions: {
...getDefaultLoadOptions(),
integrationInfo: ['integrationInfo1'],
},
})

await client.init({
integrationInfo: ['integrationInfo2'],
region: 'eu',
})

expect(loadSpy).toBeCalledTimes(1)
expect(loadSpy).toHaveBeenCalledWith({
...getDefaultLoadOptions(),
region: 'eu',
integrationInfo: ['integrationInfo1', 'integrationInfo2', `fingerprintjs-pro-spa/${packageInfo.version}`],
})
})

it('should allow calling .init() again in case of errors thrown by agent', async () => {
const client = new FpjsClient({ loadOptions: getDefaultLoadOptions() })

Expand Down
49 changes: 32 additions & 17 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as FingerprintJS from '@fingerprintjs/fingerprintjs-pro'
import { GetOptions } from '@fingerprintjs/fingerprintjs-pro'
import { GetOptions, LoadOptions } from '@fingerprintjs/fingerprintjs-pro'
import {
CacheKey,
CacheManager,
Expand Down Expand Up @@ -50,7 +50,9 @@ export interface CustomAgent {
load: (options: FingerprintJS.LoadOptions) => Promise<FingerprintJS.Agent>
}

export interface FpjsSpaOptions extends FpjsClientOptions {
export interface FpjsSpaOptions
extends Omit<FpjsClientOptions, 'loadOptions'>,
Pick<Partial<FpjsClientOptions>, 'loadOptions'> {
customAgent?: CustomAgent
}

Expand All @@ -59,41 +61,38 @@ export interface FpjsSpaOptions extends FpjsClientOptions {
*/
export class FpjsClient {
private cacheManager: CacheManager
private loadOptions: FingerprintJS.LoadOptions
private readonly loadOptions?: FingerprintJS.LoadOptions
private agent: FingerprintJS.Agent
private agentPromise: Promise<FingerprintJS.Agent> | null
private readonly customAgent?: CustomAgent
readonly cacheLocation?: CacheLocation

private inFlightRequests = new Map<string, Promise<VisitorData>>()

constructor(options: FpjsSpaOptions) {
constructor(options?: FpjsSpaOptions) {
this.agentPromise = null
this.customAgent = options.customAgent
this.customAgent = options?.customAgent

this.agent = {
get: () => {
throw new Error("FPJSAgent hasn't loaded yet. Make sure to call the init() method first.")
},
}

this.loadOptions = {
...options.loadOptions,
integrationInfo: [...(options.loadOptions.integrationInfo || []), `fingerprintjs-pro-spa/${packageInfo.version}`],
}
this.loadOptions = options?.loadOptions

if (options.cache && options.cacheLocation) {
if (options?.cache && options?.cacheLocation) {
console.warn(
'Both `cache` and `cacheLocation` options have been specified in the FpjsClient configuration; ignoring `cacheLocation` and using `cache`.'
)
}

let cache: ICache

if (options.cache) {
if (options?.cache) {
cache = options.cache
} else {
this.cacheLocation = options.cacheLocation || CacheLocation.SessionStorage
this.cacheLocation = options?.cacheLocation || CacheLocation.SessionStorage

if (!cacheFactory(this.cacheLocation)) {
throw new Error(`Invalid cache location "${this.cacheLocation}"`)
Expand All @@ -102,26 +101,42 @@ export class FpjsClient {
this.cacheLocation = CacheLocation.Memory
}

cache = cacheFactory(this.cacheLocation)(options.cachePrefix)
cache = cacheFactory(this.cacheLocation)(options?.cachePrefix)
}

if (options.cacheTimeInSeconds && options.cacheTimeInSeconds > MAX_CACHE_LIFE) {
if (options?.cacheTimeInSeconds && options.cacheTimeInSeconds > MAX_CACHE_LIFE) {
throw new Error(`Cache time cannot exceed 86400 seconds (24 hours)`)
}

const cacheTime = options.cacheTimeInSeconds ?? DEFAULT_CACHE_LIFE
const cacheTime = options?.cacheTimeInSeconds ?? DEFAULT_CACHE_LIFE
this.cacheManager = new CacheManager(cache, cacheTime)
}

/**
* Loads FPJS JS agent with certain settings and stores the instance in memory
* [https://dev.fingerprint.com/docs/js-agent#agent-initialization]
*
* @param passedLoadOptions Additional load options to be passed to the agent, they will be merged with load options provided in the constructor.
*/
public async init() {
public async init(passedLoadOptions?: Partial<LoadOptions>) {
if (!this.loadOptions && !passedLoadOptions) {
throw new TypeError('No load options provided')
}

const loadOptions: FingerprintJS.LoadOptions = {
...this.loadOptions!,
...passedLoadOptions!,
integrationInfo: [
...(this.loadOptions?.integrationInfo || []),
...(passedLoadOptions?.integrationInfo || []),
`fingerprintjs-pro-spa/${packageInfo.version}`,
],
}

if (!this.agentPromise) {
const agentLoader = this.customAgent ?? FingerprintJS
this.agentPromise = agentLoader
.load(this.loadOptions)
.load(loadOptions)
.then((agent) => {
this.agent = agent
return agent
Expand Down

0 comments on commit f772512

Please sign in to comment.