Skip to content

Commit

Permalink
feat: add option for bootstrapping config in JS SDK (#569)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajwootto authored Oct 18, 2023
1 parent bcacb28 commit 7d1ccfe
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 2 deletions.
34 changes: 34 additions & 0 deletions sdk/js/__tests__/Client.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,40 @@ describe('DevCycleClient tests', () => {
expect(client.config).toStrictEqual(testConfig)
})

it('should use bootstrapped config when set, not fetch a new one, initialize SDK', async () => {
const client = new DevCycleClient(
'test_sdk_key',
{ user_id: 'user1' },
{ bootstrapConfig: testConfig },
)
await client.onInitialized
expect(getConfigJson_mock).not.toBeCalled()
expect(client.config).toStrictEqual(testConfig)
expect(client.user.user_id).toEqual('user1')
})

it('should use bootstrapped config when set, ignore cached config', async () => {
getConfigJson_mock.mockImplementation(() => {
return Promise.resolve({ ...testConfig, cached: true })
})
const spy = jest.spyOn(window.localStorage.__proto__, 'getItem')
const client = new DevCycleClient('test_sdk_key', { user_id: 'user1' })
await client.onInitialized
// one call to get anon ID, one to get cached config
expect(spy).toHaveBeenCalledTimes(2)
// construct another client to test if it reads from the cache populated by the initialization of the first
// client
const client2 = new DevCycleClient(
'test_sdk_key',
{ user_id: 'user1' },
{ bootstrapConfig: testConfig },
)
await client2.onInitialized
// one more call to get anon ID
expect(spy).toHaveBeenCalledTimes(3)
expect(client2.config).toStrictEqual(testConfig)
})

it('should establish a streaming connection if available', async () => {
getConfigJson_mock.mockImplementation(() => {
return Promise.resolve({
Expand Down
20 changes: 18 additions & 2 deletions sdk/js/src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type variableEvaluatedHandler = (

export type DevCycleOptionsWithDeferredInitialization = DevCycleOptions & {
deferInitialization: true
bootstrapConfig?: never
}

export const isDeferredOptions = (
Expand Down Expand Up @@ -132,6 +133,10 @@ export class DevCycleClient<
throw new Error('User must be provided to initialize SDK')
}
this.clientInitialization(user)
} else if (this.options.bootstrapConfig) {
throw new Error(
'bootstrapConfig option can not be combined with deferred initialization!',
)
}

if (!options?.reactNative && typeof window !== 'undefined') {
Expand Down Expand Up @@ -171,7 +176,9 @@ export class DevCycleClient<
storedAnonymousId,
)

await this.getConfigCache(this.user)
if (!this.options.bootstrapConfig) {
await this.getConfigCache(this.user)
}

// set up requestConsolidator and hook up callback methods
this.requestConsolidator = new ConfigRequestConsolidator(
Expand All @@ -189,7 +196,15 @@ export class DevCycleClient<
)

try {
await this.requestConsolidator.queue(this.user)
if (!this.options.bootstrapConfig) {
await this.requestConsolidator.queue(this.user)
} else {
this.handleConfigReceived(
this.options.bootstrapConfig,
this.user,
Date.now(),
)
}
} catch (err) {
this.eventEmitter.emitInitialized(false)
this.eventEmitter.emitError(err)
Expand Down Expand Up @@ -727,6 +742,7 @@ export class DevCycleClient<
user,
this.options.configCacheTTL,
)

if (cachedConfig) {
this.config = cachedConfig
this.isConfigCached = true
Expand Down
10 changes: 10 additions & 0 deletions sdk/js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
VariableValue,
DVCJSON,
DVCCustomDataJSON,
ConfigBody,
BucketedUserConfig,
} from '@devcycle/types'

export type DVCVariableValue = VariableValue
Expand Down Expand Up @@ -103,6 +105,14 @@ export interface DevCycleOptions {
* Controls the maximum size the event queue can grow to until events are dropped. Defaults to `1000`.
*/
maxEventQueueSize?: number

/**
* A full configuration payload to boostrap the SDK with. This will immediately initialize the SDK with
* the provided data and prevent a server roundtrip to fetch a new config. This option can be used for passing
* in a config that was prefetched in a server-side rendered environment, or to implement a custom caching
* system.
*/
bootstrapConfig?: BucketedUserConfig
}

export interface DevCycleUser {
Expand Down
1 change: 1 addition & 0 deletions sdk/react/src/initializeDevCycleClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const initializeDevCycleClient = (
return initializeDevCycle(sdkKey, {
...options,
deferInitialization: true, // make typescript happy
bootstrapConfig: undefined,
})
}
return initializeDevCycle(sdkKey, user, options)
Expand Down

3 comments on commit 7d1ccfe

@vercel
Copy link

@vercel vercel bot commented on 7d1ccfe Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

js-sdks-next-js – ./

js-sdks-next-js-git-main-devcyclehq.vercel.app
js-sdks-next-js-devcyclehq.vercel.app
dvc-nextjs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 7d1ccfe Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7d1ccfe Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.