Skip to content

Commit 316f045

Browse files
committed
fix: support react native SDK
1 parent 0b2403a commit 316f045

14 files changed

+197
-19
lines changed

e2e/BKTClient.spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import { BKTClient, BKTClientImpl } from '../src/BKTClient'
1212
import { DefaultComponent } from '../src/internal/di/Component'
1313
import { EvaluationStorageImpl } from '../src/internal/evaluation/EvaluationStorage'
1414
import { evaluation1 } from '../test/mocks/evaluations'
15+
import { fetchLike } from './fetchProvider'
1516

1617
suite('e2e/BKTClientTest', () => {
1718
let config: BKTConfig
1819
let user: BKTUser
1920

2021
afterEach(() => {
2122
destroyBKTClient()
22-
localStorage.clear()
2323
})
2424

2525
suite('get string variation using user attribute when initializing', () => {
@@ -29,7 +29,7 @@ suite('e2e/BKTClientTest', () => {
2929
apiKey: import.meta.env.VITE_BKT_API_KEY,
3030
featureTag: 'javascript',
3131
appVersion: '1.2.3',
32-
fetch: window.fetch,
32+
fetch: fetchLike,
3333
})
3434

3535
user = defineBKTUser({
@@ -54,7 +54,7 @@ suite('e2e/BKTClientTest', () => {
5454
apiKey: import.meta.env.VITE_BKT_API_KEY,
5555
featureTag: 'javascript',
5656
appVersion: '1.2.3',
57-
fetch: window.fetch,
57+
fetch: fetchLike,
5858
})
5959

6060
user = defineBKTUser({
@@ -99,7 +99,7 @@ suite('e2e/BKTClientTest', () => {
9999
apiKey: import.meta.env.VITE_BKT_API_KEY,
100100
featureTag: 'javascript',
101101
appVersion: '1.2.3',
102-
fetch: window.fetch,
102+
fetch: fetchLike,
103103
})
104104

105105
user = defineBKTUser({
@@ -201,7 +201,7 @@ suite('e2e/BKTClientTest', () => {
201201
apiEndpoint: import.meta.env.VITE_BKT_API_ENDPOINT,
202202
apiKey: import.meta.env.VITE_BKT_API_KEY,
203203
appVersion: '1.2.3',
204-
fetch: window.fetch,
204+
fetch: fetchLike,
205205
})
206206

207207
user = defineBKTUser({

e2e/evaluations.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
USER_ID,
1616
} from './constants'
1717
import './assertions'
18+
import { fetchLike } from './fetchProvider'
1819

1920
suite('e2e/evaluations', () => {
2021
let config: BKTConfig
@@ -26,7 +27,7 @@ suite('e2e/evaluations', () => {
2627
apiKey: import.meta.env.VITE_BKT_API_KEY,
2728
featureTag: 'javascript',
2829
appVersion: '1.2.3',
29-
fetch: window.fetch,
30+
fetch: fetchLike,
3031
})
3132

3233
user = defineBKTUser({
@@ -38,7 +39,6 @@ suite('e2e/evaluations', () => {
3839

3940
afterEach(() => {
4041
destroyBKTClient()
41-
localStorage.clear()
4242
})
4343

4444
suite('stringVariation', () => {

e2e/events.spec.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import './assertions'
2323
import { EventType } from '../src/internal/model/Event'
2424
import { ForbiddenException, TimeoutException } from '../src/BKTExceptions'
2525
import { ApiId, MetricsEventType } from '../src/internal/model/MetricsEventData'
26+
import { fetchLike } from './fetchProvider'
2627

2728
function getDefaultComponent(client: BKTClient): DefaultComponent {
2829
return (client as BKTClientImpl).component as DefaultComponent
@@ -38,7 +39,7 @@ suite('e2e/events', () => {
3839
apiKey: import.meta.env.VITE_BKT_API_KEY,
3940
featureTag: 'javascript',
4041
appVersion: '1.2.3',
41-
fetch: window.fetch,
42+
fetch: fetchLike,
4243
// DO NOT remove this line
4344
// Because the tests are asynchronous and share the same local storage,
4445
// It might fail randomly, having more or fewer events in the storage when checking the test.
@@ -55,7 +56,6 @@ suite('e2e/events', () => {
5556

5657
afterEach(() => {
5758
destroyBKTClient()
58-
localStorage.clear()
5959
})
6060

6161
test('goal event', async () => {
@@ -174,14 +174,14 @@ suite('e2e/events', () => {
174174

175175
test('Using a random string in the api key setting should throw Forbidden', async () => {
176176
destroyBKTClient()
177-
localStorage.clear()
177+
if (typeof localStorage !== 'undefined') { localStorage.clear() }
178178

179179
config = defineBKTConfig({
180180
apiEndpoint: import.meta.env.VITE_BKT_API_ENDPOINT,
181181
apiKey: 'some-random-string',
182182
featureTag: 'javascript',
183183
appVersion: '1.2.3',
184-
fetch: window.fetch,
184+
fetch: fetchLike,
185185
})
186186

187187
user = defineBKTUser({
@@ -224,7 +224,7 @@ suite('e2e/events', () => {
224224
apiKey: import.meta.env.VITE_BKT_API_KEY,
225225
featureTag: 'javascript',
226226
appVersion: '1.2.3',
227-
fetch: window.fetch,
227+
fetch: fetchLike,
228228
})
229229

230230
await initializeBKTClient(config, user)
@@ -257,14 +257,14 @@ suite('e2e/events', () => {
257257

258258
test('Using a random string in the featureTag setting should not affect api request', async () => {
259259
destroyBKTClient()
260-
localStorage.clear()
260+
if (typeof localStorage !== 'undefined') { localStorage.clear() }
261261

262262
config = defineBKTConfig({
263263
apiEndpoint: import.meta.env.VITE_BKT_API_ENDPOINT,
264264
apiKey: import.meta.env.VITE_BKT_API_KEY,
265265
featureTag: 'some-random-feature-tag',
266266
appVersion: '1.2.3',
267-
fetch: window.fetch,
267+
fetch: fetchLike,
268268
})
269269

270270
user = defineBKTUser({
@@ -278,14 +278,14 @@ suite('e2e/events', () => {
278278
// setting a very low value for the timeout
279279

280280
destroyBKTClient()
281-
localStorage.clear()
281+
if (typeof localStorage !== 'undefined') { localStorage.clear() }
282282

283283
config = defineBKTConfig({
284284
apiEndpoint: import.meta.env.VITE_BKT_API_ENDPOINT,
285285
apiKey: import.meta.env.VITE_BKT_API_KEY,
286286
featureTag: 'javascript',
287287
appVersion: '1.2.3',
288-
fetch: window.fetch,
288+
fetch: fetchLike,
289289
})
290290

291291
user = defineBKTUser({

e2e/fetchProvider.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { FetchLike } from '../src/internal/remote/fetch'
2+
3+
let fetchLike: FetchLike
4+
5+
function setFetchProvider(fetch: FetchLike) {
6+
fetchLike = fetch
7+
}
8+
9+
export { setFetchProvider, fetchLike }

e2e/setup.browser.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
import { afterEach } from 'vitest'
3+
import { setFetchProvider } from './fetchProvider'
4+
5+
setFetchProvider(window.fetch)
6+
7+
afterEach(() => {
8+
localStorage.clear()
9+
})

e2e/setup.node.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { setFetchProvider } from './fetchProvider'
2+
3+
setFetchProvider(fetch)

package.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@
1414
"module": "./dist/main.mjs",
1515
"browser": "./dist/main.browser.mjs",
1616
"types": "./dist/main.d.ts",
17+
"react-native": "./dist/main.native.cjs",
1718
"exports": {
1819
".": {
1920
"node": {
2021
"import": "./dist/main.mjs",
2122
"require": "./dist/main.cjs",
2223
"types": "./dist/main.d.ts"
2324
},
25+
"react-native": {
26+
"import": "./dist/main.native.mjs",
27+
"require": "./dist/main.native.cjs",
28+
"types": "./dist/main.d.ts"
29+
},
2430
"default": {
2531
"import": "./dist/main.browser.mjs",
2632
"require": "./dist/main.browser.cjs",
@@ -35,7 +41,9 @@
3541
"test": "pnpm test:browser --run ; pnpm test:node --run",
3642
"test:browser": "vitest --config ./vitest-browser.config.ts --dir test",
3743
"test:node": "vitest --config ./vitest-node.config.ts --dir test",
38-
"test:e2e": "vitest --config ./vitest-e2e.config.ts --dir e2e",
44+
"test:e2e": "pnpm test:e2e:browser --run ; pnpm test:e2e:node --run",
45+
"test:e2e:browser": "vitest --config ./vitest-e2e.config.ts --dir e2e",
46+
"test:e2e:node": "vitest --config ./vitest-e2e-node.config.ts --dir e2e",
3947
"lint": "eslint .",
4048
"lint:fix": "eslint --fix .",
4149
"example:serve": "pnpm exec unbuild && pnpm --filter example serve"

src/BKTConfig.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IllegalArgumentException } from './BKTExceptions'
22
import { BKTStorage, createBKTStorage } from './BKTStorage'
3+
import { IdGenerator } from './internal/IdGenerator'
34
import { FetchLike } from './internal/remote/fetch'
45
import { SDK_VERSION } from './internal/version'
56

@@ -30,6 +31,7 @@ interface RawBKTConfig {
3031
userAgent?: string
3132
fetch?: FetchLike
3233
storageFactory?: <T>(key: string) => BKTStorage<T>
34+
idGenerator?: IdGenerator
3335
}
3436

3537
export interface BKTConfig extends RawBKTConfig {

src/internal/di/PlatformModule.ts

+14
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,17 @@ import { IdGenerator } from '../IdGenerator'
33
export interface PlatformModule {
44
idGenerator(): IdGenerator
55
}
6+
7+
// BasePlatformModule serves as a base implementation of the PlatformModule interface.
8+
// It relies on the BKTConfig to inject an instance of IdGenerator, which must be set before use.
9+
export class BasePlatformModule implements PlatformModule {
10+
protected _idGenerator: IdGenerator
11+
12+
constructor(params: { idGenerator: IdGenerator }) {
13+
this._idGenerator = params.idGenerator
14+
}
15+
16+
idGenerator() {
17+
return this._idGenerator
18+
}
19+
}

src/main.native.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { initializeBKTClientInternal } from './BKTClient'
2+
import { BKTConfig } from './BKTConfig'
3+
import { BKTUser } from './BKTUser'
4+
import { Component, DefaultComponent } from './internal/di/Component'
5+
import { DataModule } from './internal/di/DataModule'
6+
import { InteractorModule } from './internal/di/InteractorModule'
7+
import { BasePlatformModule } from './internal/di/PlatformModule'
8+
import { User } from './internal/model/User'
9+
import { toUser } from './internal/UserHolder'
10+
import { IdGenerator } from './internal/IdGenerator'
11+
12+
export type { BKTConfig } from './BKTConfig'
13+
export { defineBKTConfig } from './BKTConfig'
14+
export type { BKTUser } from './BKTUser'
15+
export { defineBKTUser } from './BKTUser'
16+
export type { BKTClient } from './BKTClient'
17+
export { getBKTClient, destroyBKTClient } from './BKTClient'
18+
export type {
19+
BKTStorage,
20+
BrowserLocalStorage,
21+
InMemoryStorage,
22+
} from './BKTStorage'
23+
export type {
24+
BKTValue,
25+
BKTJsonArray,
26+
BKTJsonObject,
27+
BKTJsonPrimitive,
28+
} from './BKTValue'
29+
export type { BKTEvaluationDetails } from './BKTEvaluationDetails'
30+
31+
// This endpoint is intended for use in React Native - Expo environments.
32+
const createComponent = (config: BKTConfig, user: User): Component => {
33+
const idGenerator = requiredIdGenerator(config)
34+
return new DefaultComponent(
35+
new BasePlatformModule({ idGenerator }),
36+
new DataModule(user, config),
37+
new InteractorModule(),
38+
)
39+
}
40+
41+
export const initializeBKTClient = async (
42+
config: BKTConfig,
43+
user: BKTUser,
44+
timeoutMillis = 5_000,
45+
): Promise<void> => {
46+
// idGenerator is required in the react native environment
47+
const component = createComponent(config, toUser(user))
48+
return initializeBKTClientInternal(component, timeoutMillis)
49+
}
50+
51+
export function requiredIdGenerator(config: BKTConfig): IdGenerator {
52+
if (!config.idGenerator) {
53+
throw new Error('idGenerator is required in this environment')
54+
}
55+
return config.idGenerator
56+
}

src/main.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BKTUser } from './BKTUser'
44
import { Component, DefaultComponent } from './internal/di/Component'
55
import { DataModule } from './internal/di/DataModule'
66
import { InteractorModule } from './internal/di/InteractorModule'
7+
import { BasePlatformModule } from './internal/di/PlatformModule'
78
import { NodePlatformModule } from './internal/di/PlatformModule.node'
89
import { User } from './internal/model/User'
910
import { toUser } from './internal/UserHolder'
@@ -29,7 +30,9 @@ export type { BKTEvaluationDetails } from './BKTEvaluationDetails'
2930

3031
const createNodeComponent = (config: BKTConfig, user: User): Component => {
3132
return new DefaultComponent(
32-
new NodePlatformModule(),
33+
config.idGenerator
34+
? new BasePlatformModule({ idGenerator: config.idGenerator })
35+
: new NodePlatformModule(),
3336
new DataModule(user, config),
3437
new InteractorModule(),
3538
)

test/main.native.spec.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { requiredIdGenerator } from '../src/main.native'
3+
import { defineBKTConfig } from '../src/BKTConfig'
4+
import { IdGenerator } from '../src/internal/IdGenerator'
5+
6+
describe('requiredIdGenerator', () => {
7+
it('should return idGenerator if provided', () => {
8+
const mockIdGenerator: IdGenerator = {
9+
newId: () => 'test-id',
10+
}
11+
const config = defineBKTConfig({
12+
apiKey: 'api-key',
13+
apiEndpoint: 'https://api.bucketeer.io',
14+
featureTag: 'tag',
15+
appVersion: '1.0.0',
16+
idGenerator: mockIdGenerator,
17+
})
18+
// Check the return value instead of just not throwing
19+
expect(requiredIdGenerator(config)).toBe(mockIdGenerator)
20+
})
21+
22+
it('should throw error if idGenerator is not provided', () => {
23+
const config = defineBKTConfig({
24+
apiKey: 'api-key',
25+
apiEndpoint: 'https://api.bucketeer.io',
26+
featureTag: 'tag',
27+
appVersion: '1.0.0',
28+
// idGenerator is intentionally omitted
29+
})
30+
expect(() => requiredIdGenerator(config)).toThrow(
31+
'idGenerator is required in this environment',
32+
)
33+
})
34+
35+
it('should throw error if idGenerator is undefined', () => {
36+
const config = defineBKTConfig({
37+
apiKey: 'api-key',
38+
apiEndpoint: 'https://api.bucketeer.io',
39+
featureTag: 'tag',
40+
appVersion: '1.0.0',
41+
idGenerator: undefined,
42+
})
43+
expect(() => requiredIdGenerator(config)).toThrow(
44+
'idGenerator is required in this environment',
45+
)
46+
})
47+
48+
it('should throw error if idGenerator is null', () => {
49+
const config = defineBKTConfig({
50+
apiKey: 'api-key',
51+
apiEndpoint: 'https://api.bucketeer.io',
52+
featureTag: 'tag',
53+
appVersion: '1.0.0',
54+
idGenerator: null as unknown as IdGenerator, // Cast to satisfy type checking initially
55+
})
56+
expect(() => requiredIdGenerator(config)).toThrow(
57+
'idGenerator is required in this environment',
58+
)
59+
})
60+
})
61+
62+

0 commit comments

Comments
 (0)