Skip to content
Merged
42 changes: 42 additions & 0 deletions packages/sdk/react-native/src/RNStateDetector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AppState, AppStateStatus } from 'react-native';

import { ApplicationState, NetworkState, StateDetector } from './platform/ConnectionManager';

function translateAppState(state: AppStateStatus): ApplicationState {
switch (state) {
case 'active':
return ApplicationState.Foreground;
case 'inactive':
case 'background':
case 'extension':
default:
return ApplicationState.Background;
}
}

export default class RNStateDetector implements StateDetector {
private applicationStateListener?: (state: ApplicationState) => void;
private networkStateListener?: (state: NetworkState) => void;

constructor() {
AppState.addEventListener('change', (state: AppStateStatus) => {
this.applicationStateListener?.(translateAppState(state));
});
}

setApplicationStateListener(fn: (state: ApplicationState) => void): void {
this.applicationStateListener = fn;
// When you listen provide the current state immediately.
this.applicationStateListener(translateAppState(AppState.currentState));
}

setNetworkStateListener(fn: (state: NetworkState) => void): void {
this.networkStateListener = fn;
// Not implemented.
}

stopListening(): void {
this.applicationStateListener = undefined;
this.networkStateListener = undefined;
}
}
47 changes: 47 additions & 0 deletions packages/sdk/react-native/src/ReactNativeLDClient.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
/* eslint-disable max-classes-per-file */
import {
AutoEnvAttributes,
base64UrlEncode,
BasicLogger,
ConnectionMode,
internal,
LDClientImpl,
type LDContext,
type LDOptions,
} from '@launchdarkly/js-client-sdk-common';

import createPlatform from './platform';
import { ConnectionDestination, ConnectionManager } from './platform/ConnectionManager';
import RNStateDetector from './RNStateDetector';

/**
* The React Native LaunchDarkly client. Instantiate this class to create an
Expand All @@ -24,6 +28,7 @@ import createPlatform from './platform';
* ```
*/
export default class ReactNativeLDClient extends LDClientImpl {
private connectionManager: ConnectionManager;
/**
* Creates an instance of the LaunchDarkly client.
*
Expand Down Expand Up @@ -57,9 +62,51 @@ export default class ReactNativeLDClient extends LDClientImpl {
{ ...options, logger },
internalOptions,
);

const destination: ConnectionDestination = {
setNetworkAvailability: (available: boolean) => {
this.setNetworkAvailability(available);
},
setEventSendingEnabled: (enabled: boolean, flush: boolean) => {
this.setEventSendingEnabled(enabled, flush);
},
setConnectionMode: async (mode: ConnectionMode) => {
// Pass the connection mode to the base implementation.
// The RN implementation will pass the connection mode through the connection manager.
this.baseSetConnectionMode(mode);
},
};

const initialConnectionMode = options.initialConnectionMode ?? 'streaming';
this.connectionManager = new ConnectionManager(
logger,
{
initialConnectionMode,
// TODO: Add the ability to configure connection management.
// This is not yet added as the RN SDK needs package specific configuration added.
automaticNetworkHandling: true,
automaticBackgroundHandling: true,
runInBackground: false,
},
destination,
new RNStateDetector(),
);
}

private baseSetConnectionMode(mode: ConnectionMode) {
// Jest had problems with calls to super from nested arrow functions, so this method proxies the call.
super.setConnectionMode(mode);
}

override createStreamUriPath(context: LDContext) {
return `/meval/${base64UrlEncode(JSON.stringify(context), this.platform.encoding!)}`;
}

override async setConnectionMode(mode: ConnectionMode): Promise<void> {
// Set the connection mode before setting offline, in case there is any mode transition work
// such as flushing on entering the background.
this.connectionManager.setConnectionMode(mode);
// For now the data source connection and the event processing state are connected.
this.connectionManager.setOffline(mode === 'offline');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ function mockDestination(): ConnectionDestination {
setNetworkAvailability: jest.fn(),
setEventSendingEnabled: jest.fn(),
setConnectionMode: jest.fn(),
flush: jest.fn(),
};
}

Expand Down
7 changes: 3 additions & 4 deletions packages/sdk/react-native/src/platform/ConnectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export interface ConnectionDestination {
setNetworkAvailability(available: boolean): void;
setEventSendingEnabled(enabled: boolean, flush: boolean): void;
setConnectionMode(mode: ConnectionMode): Promise<void>;
flush(): Promise<void>;
}

export interface StateDetector {
Expand Down Expand Up @@ -121,6 +120,8 @@ export class ConnectionManager {
private setForegroundAvailable(): void {
if (this.offline) {
this.destination.setConnectionMode('offline');
// Don't attempt to flush. If the user wants to flush when entering offline
// mode, then they can do that directly.
this.destination.setEventSendingEnabled(false, false);
return;
}
Expand All @@ -132,11 +133,9 @@ export class ConnectionManager {
}

private setBackgroundAvailable(): void {
this.destination.flush();

if (!this.config.runInBackground) {
this.destination.setConnectionMode('offline');
this.destination.setEventSendingEnabled(false, false);
this.destination.setEventSendingEnabled(false, true);
return;
}

Expand Down
6 changes: 1 addition & 5 deletions packages/sdk/react-native/src/platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@ import AsyncStorage from './ConditionalAsyncStorage';
import PlatformCrypto from './crypto';

export class PlatformRequests implements Requests {
eventSource?: RNEventSource<EventName>;

constructor(private readonly logger: LDLogger) {}

createEventSource(url: string, eventSourceInitDict: EventSourceInitDict): EventSource {
this.eventSource = new RNEventSource<EventName>(url, {
return new RNEventSource<EventName>(url, {
headers: eventSourceInitDict.headers,
retryAndHandleError: eventSourceInitDict.errorFilter,
logger: this.logger,
});

return this.eventSource;
}

fetch(url: string, options?: Options): Promise<Response> {
Expand Down
1 change: 0 additions & 1 deletion packages/sdk/react-native/src/provider/LDProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import ReactNativeLDClient from '../ReactNativeLDClient';
import LDProvider from './LDProvider';
import setupListeners from './setupListeners';

jest.mock('../provider/useAppState');
jest.mock('../ReactNativeLDClient');
jest.mock('./setupListeners');

Expand Down
3 changes: 0 additions & 3 deletions packages/sdk/react-native/src/provider/LDProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React, { PropsWithChildren, useEffect, useState } from 'react';
import ReactNativeLDClient from '../ReactNativeLDClient';
import { Provider, ReactContext } from './reactContext';
import setupListeners from './setupListeners';
import useAppState from './useAppState';

type LDProps = {
client: ReactNativeLDClient;
Expand All @@ -26,8 +25,6 @@ const LDProvider = ({ client, children }: PropsWithChildren<LDProps>) => {
setupListeners(client, setState);
}, []);

useAppState(client);
Copy link
Member Author

Choose a reason for hiding this comment

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

Another problem with this is that the overall desired connection mode would fight with the connection mode determined by the "app state".

If you tell the client to be offline, then you don't expect a foreground transition to set it back online, for example.


return <Provider value={state}>{children}</Provider>;
};

Expand Down
114 changes: 0 additions & 114 deletions packages/sdk/react-native/src/provider/useAppState.test.ts

This file was deleted.

58 changes: 0 additions & 58 deletions packages/sdk/react-native/src/provider/useAppState.ts

This file was deleted.

Loading