Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: React Native SSE Support #1012

Merged
merged 11 commits into from
Dec 13, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ describe('User Hashing and Bucketing', () => {
],
}

// run 100,000 times to get a good distribution
for (let i = 0; i < 100000; i++) {
// run 200,000 times to get a good distribution
for (let i = 0; i < 200000; i++) {
const user_id = uuid.v4()
const { bucketingHash } = generateBoundedHashes(
user_id,
Expand Down
4 changes: 2 additions & 2 deletions lib/shared/bucketing/__tests__/bucketing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ describe('User Hashing and Bucketing', () => {
],
}

// run 100,000 times to get a good distribution
times(100000, () => {
// run 200,000 times to get a good distribution
times(200000, () => {
const user_id = uuid.v4()
const { bucketingHash } = generateBoundedHashes(
user_id,
Expand Down
1 change: 1 addition & 0 deletions lib/shared/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './utils'
export * from './types/ConfigSource'
export * from './types/UserError'
export * from './types/variableKeys'
export * from './types/SSETypes'
16 changes: 16 additions & 0 deletions lib/shared/types/src/types/SSETypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { DVCLogger } from '../logger'

export interface SSEConnectionInterface {
updateURL(url: string): void
isConnected(): boolean
reopen(): void
close(): void
}

export interface SSEConnectionConstructor {
new (
url: string,
onMessage: (message: unknown) => void,
logger: DVCLogger,
): SSEConnectionInterface
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"react-native": "0.72.4",
"react-native-device-info": "^8.7.0",
"react-native-get-random-values": "^1.7.2",
"react-native-sse": "^1.2.1",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "0.13.7",
"server-only": "^0.0.1",
Expand Down
14 changes: 9 additions & 5 deletions sdk/js/src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@ import { checkParamDefined } from './utils'
import { EventEmitter } from './EventEmitter'
import type {
BucketedUserConfig,
InferredVariableType,
VariableDefinitions,
VariableTypeAlias,
} from '@devcycle/types'
import { getVariableTypeFromValue } from '@devcycle/types'
import { ConfigRequestConsolidator } from './ConfigRequestConsolidator'
import { dvcDefaultLogger } from './logger'
import type { DVCLogger } from '@devcycle/types'
import type {
DVCLogger,
SSEConnectionInterface,
SSEConnectionConstructor,
} from '@devcycle/types'
import { StreamingConnection } from './StreamingConnection'

type variableUpdatedHandler = (
Expand Down Expand Up @@ -89,7 +92,7 @@ export class DevCycleClient<
private eventQueue?: EventQueue<Variables, CustomData>
private requestConsolidator: ConfigRequestConsolidator
eventEmitter: EventEmitter
private streamingConnection?: StreamingConnection
private streamingConnection?: SSEConnectionInterface
private pageVisibilityHandler?: () => void
private inactivityHandlerId?: number
private windowMessageHandler?: (event: MessageEvent) => void
Expand Down Expand Up @@ -747,10 +750,11 @@ export class DevCycleClient<

// Update the streaming connection URL if it has changed (for ex. if the current user has targeting overrides)
if (config?.sse?.url) {
// construct the streamingConnection if necessary
if (!this.streamingConnection) {
if (!this.options.disableRealtimeUpdates) {
this.streamingConnection = new StreamingConnection(
const SSEConnectionClass =
this.options.sseConnectionClass || StreamingConnection
this.streamingConnection = new SSEConnectionClass(
config.sse.url,
this.onSSEMessage.bind(this),
this.logger,
Expand Down
4 changes: 2 additions & 2 deletions sdk/js/src/StreamingConnection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DVCLogger } from '@devcycle/types'
import type { DVCLogger, SSEConnectionInterface } from '@devcycle/types'

export class StreamingConnection {
export class StreamingConnection implements SSEConnectionInterface {
private connection?: EventSource

constructor(
Expand Down
5 changes: 5 additions & 0 deletions sdk/js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
BucketedUserConfig,
VariableKey,
InferredVariableType,
SSEConnectionConstructor,
} from '@devcycle/types'
export { UserError } from '@devcycle/types'

Expand Down Expand Up @@ -81,6 +82,10 @@ export interface DevCycleOptions {
* Used to know if we are running in a React Native environment.
*/
reactNative?: boolean
/**
* Custom SSE connection class to use for the SDK.
*/
sseConnectionClass?: SSEConnectionConstructor
/**
* Disable Realtime Update and their SSE connection.
*/
Expand Down
4 changes: 3 additions & 1 deletion sdk/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
"dependencies": {
"@devcycle/js-client-sdk": "^1.32.2",
"@devcycle/react-client-sdk": "^1.30.2",
"@devcycle/types": "^1.19.2",
"@react-native-async-storage/async-storage": "^1.17.11",
"react-native-device-info": "^8.7.0",
"react-native-get-random-values": "^1.7.2"
"react-native-get-random-values": "^1.7.2",
"react-native-sse": "^1.2.1"
Copy link
Contributor

Choose a reason for hiding this comment

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

note: this will require docs update as we need to install the other packages separately

Copy link
Member Author

Choose a reason for hiding this comment

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

Why wouldn't it be installed from the dependency? seems to work in the E2E test app.

Copy link
Contributor

Choose a reason for hiding this comment

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

im not 100 percent sure, but we have that explicit in the docs and in the past when i have tried to install, required me to add those dependencies have not tested recently tbh

},
"peerDependencies": {
"react": ">=17.0.2",
Expand Down
2 changes: 2 additions & 0 deletions sdk/react-native/src/DevCycleProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import { DevCycleProvider as ReactDVCProvider } from '@devcycle/react-client-sdk'
import ReactNativeStore from './ReactNativeCacheStore'
import { ReactNativeSSEConnection } from './ReactNativeSSEConnection'

type PropsType = Parameters<typeof ReactDVCProvider>[0]

Expand All @@ -24,6 +25,7 @@ export const getReactNativeConfig = (
...config.options,
sdkPlatform: 'react-native',
reactNative: true,
sseConnectionClass: ReactNativeSSEConnection,
},
}
if (!config.options?.storage) {
Expand Down
70 changes: 70 additions & 0 deletions sdk/react-native/src/ReactNativeSSEConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import EventSource from 'react-native-sse'
import type { DVCLogger, SSEConnectionInterface } from '@devcycle/types'

export class ReactNativeSSEConnection implements SSEConnectionInterface {
private connection?: EventSource
private isConnectionOpen = false

constructor(
private url: string,
private onMessage: (message: unknown) => void,
private logger: DVCLogger,
) {
this.openConnection()
}

public updateURL(url: string): void {
this.close()
this.url = url
this.openConnection()
}

private openConnection() {
this.connection = new EventSource(this.url, {
debug: false,
// start connection immediately
timeoutBeforeConnection: 0,
// disable request timeout so connections are kept open
timeout: 0,
// enable withCredentials so we can send cookies
withCredentials: true,
})

this.connection.addEventListener('message', (event) => {
this.logger.debug(`ReactNativeSSEConnection message. ${event.data}`)
this.onMessage(event.data)
})

this.connection.addEventListener('error', (error) => {
this.logger.error(
`ReactNativeSSEConnection error. ${
(error as any)?.message || JSON.stringify(error)
}`,
)
})

this.connection.addEventListener('open', () => {
this.logger.debug('ReactNativeSSEConnection opened')
this.isConnectionOpen = true
})
this.connection.addEventListener('close', () => {
this.logger.debug('ReactNativeSSEConnection closed')
this.isConnectionOpen = false
})
}

isConnected(): boolean {
return this.isConnectionOpen
}

reopen(): void {
if (!this.isConnected()) {
this.close()
this.openConnection()
}
}

close(): void {
this.connection?.close()
}
}
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4790,9 +4790,11 @@ __metadata:
dependencies:
"@devcycle/js-client-sdk": ^1.32.2
"@devcycle/react-client-sdk": ^1.30.2
"@devcycle/types": ^1.19.2
"@react-native-async-storage/async-storage": ^1.17.11
react-native-device-info: ^8.7.0
react-native-get-random-values: ^1.7.2
react-native-sse: ^1.2.1
peerDependencies:
react: ">=17.0.2"
react-native: ">=0.68.0"
Expand Down Expand Up @@ -15420,6 +15422,7 @@ __metadata:
react-native-config: 1.5.0
react-native-device-info: ^8.7.0
react-native-get-random-values: ^1.7.2
react-native-sse: ^1.2.1
react-native-svg: 13.9.0
react-native-svg-transformer: ^1.0.0
react-refresh: ^0.10.0
Expand Down Expand Up @@ -27439,6 +27442,13 @@ __metadata:
languageName: node
linkType: hard

"react-native-sse@npm:^1.2.1":
version: 1.2.1
resolution: "react-native-sse@npm:1.2.1"
checksum: 424910fa1bcc6643a7e9f628f2710bf185c862b63375ea6ec4b8eecfb5b714055480757ea1250ce6cdae66c2785f6a64432cdf9833e0ec0411b59f9791f6cce8
languageName: node
linkType: hard

"react-native-svg-transformer@npm:^1.0.0":
version: 1.0.0
resolution: "react-native-svg-transformer@npm:1.0.0"
Expand Down
Loading