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

chore: rn sdk use localstorage for bootstrapping #326

Merged
merged 42 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
85c41f8
chore: Added Storage api.
yusinto Dec 5, 2023
6c7f556
chore: replace connecting with initializing
yusinto Dec 11, 2023
7bf3327
chore: added type LDFlagChangeset. Fix storage set signature.
yusinto Dec 11, 2023
d95df72
chore: added fast-deep-equal. removed unused api methods.
yusinto Dec 11, 2023
e2f3cb4
chore: added skeleton to sync cached and put flags.
yusinto Dec 11, 2023
bc4e3de
chore: implement storage and put sync logic
yusinto Dec 11, 2023
63e6d3c
fix: export platform.Storage.
yusinto Dec 11, 2023
682e3ea
chore: implemented rn storage platform.
yusinto Dec 11, 2023
269b02f
chore: add local copy of fast-deep-equal. add storage tests.
yusinto Dec 13, 2023
8493374
fix: fixed hanging unit tests due to emits not being awaited. added m…
yusinto Dec 13, 2023
470a3fc
chore: added community rn async storage package.
yusinto Dec 13, 2023
460507a
fix: added internalOptions to configure events and diagnostic endpoin…
yusinto Dec 14, 2023
6a62c6a
fix: make internalOptions optional and fixed unit tests. removed redu…
yusinto Dec 14, 2023
ed4bb3a
chore: add unit test for internal options.
yusinto Dec 14, 2023
9f5eb03
fix: fix bug where change event emits wrong flag values. added versio…
yusinto Dec 15, 2023
b281b38
fix: when syncing only update memory cache and storage if there's cha…
yusinto Dec 15, 2023
78074fa
chore: added more patch unit tests.
yusinto Dec 15, 2023
34ad378
chore: added delete unit tests.
yusinto Dec 15, 2023
371351c
Update LDClientImpl.ts
yusinto Dec 15, 2023
30f5ace
chore: pretty print json debug output.
yusinto Dec 15, 2023
92361aa
fix: sse patch now updates react context. improved logging. TODO: fix…
yusinto Dec 16, 2023
78dc729
chore: added TODO comment.
yusinto Dec 16, 2023
7283b39
fix: remove unnecessary event listeners to fix infinite loops on errors.
yusinto Dec 18, 2023
ad0f124
chore: catch identify errors.
yusinto Dec 18, 2023
80cc003
chore: improved example.
yusinto Dec 18, 2023
6791a37
Update fetchUtils.test.ts
yusinto Dec 18, 2023
a0f916a
chore: improve npm commands.
yusinto Dec 18, 2023
8c19529
chore: remove async-storage dep from package.json
yusinto Dec 18, 2023
02f5c36
chore: added comments on how to install async-storage.
yusinto Dec 18, 2023
42c3bc6
chore: remove redundant ts-ignore
yusinto Dec 18, 2023
acd16db
chore: improved logging, cleaned up comments, added comments, removed…
yusinto Dec 19, 2023
87d09ba
chore: improve logging.
yusinto Dec 19, 2023
713221e
chore: localstorage pr feedback (#327)
yusinto Dec 20, 2023
87ca38b
chore: include example/yarn.lock because the example app is its own i…
yusinto Dec 20, 2023
c849f8c
Update .gitignore
yusinto Dec 20, 2023
55e664c
chore: improve readme and npm commands for dev.
yusinto Dec 21, 2023
f35bada
chore: replace process.nextTick with setTimeout.
yusinto Dec 21, 2023
597e439
chore: remove .only.
yusinto Dec 21, 2023
eb61ced
chore: further improve readme. include instructions for .env.
yusinto Dec 22, 2023
58f72c1
fix: stream delete command should only delete lower versions, not equal.
yusinto Dec 22, 2023
d8a4cbb
chore: implement tombstoning (#328)
yusinto Dec 22, 2023
382e519
Merge branch 'main' into yus/sc-225809/use-localstorage-for-bootstrap…
yusinto Dec 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/sdk/react-native/example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { LDProvider, ReactNativeLDClient } from '@launchdarkly/react-native-clie
import Welcome from './src/welcome';

const featureClient = new ReactNativeLDClient(MOBILE_KEY);
const context = { kind: 'user', key: 'test-user-1' };

const App = () => {
return (
<LDProvider client={featureClient} context={context}>
<LDProvider client={featureClient}>
<Welcome />
</LDProvider>
);
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/react-native/example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"clean": "expo prebuild --clean && yarn cache clean && rm -rf node_modules && rm -rf .expo"
},
"dependencies": {
"@react-native-async-storage/async-storage": "1.18.2",
"expo": "~49.0.16",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "~1.7.1",
Expand Down
70 changes: 52 additions & 18 deletions packages/sdk/react-native/example/src/welcome.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
import { Button, StyleSheet, Text, View } from 'react-native';
import { useState } from 'react';
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';

import {
useBoolVariation,
useLDClient,
useLDDataSourceStatus,
} from '@launchdarkly/react-native-client-sdk';
import { useBoolVariation, useLDClient } from '@launchdarkly/react-native-client-sdk';

export default function Welcome() {
const { error, status } = useLDDataSourceStatus();
const flag = useBoolVariation('dev-test-flag', false);
const [flagKey, setFlagKey] = useState('dev-test-flag');
const [userKey, setUserKey] = useState('');
const flagValue = useBoolVariation(flagKey, false);
const ldc = useLDClient();

const login = () => {
ldc.identify({ kind: 'user', key: 'test-user-2' });
const onIdentify = () => {
ldc
.identify({ kind: 'user', key: userKey })
.catch((e: any) => console.error(`error identifying ${userKey}: ${e}`));
};

return (
<View style={styles.container}>
<Text>Welcome to LaunchDarkly</Text>
<Text>status: {status ?? 'not connected'}</Text>
{error ? <Text>error: {error.message}</Text> : null}
<Text>devTestFlag: {`${flag}`}</Text>
<Text>
{flagKey}: {`${flagValue}`}
</Text>
<Text>context: {JSON.stringify(ldc.getContext())}</Text>
<Button title="Login" onPress={login} />
<TextInput
style={styles.input}
autoCapitalize="none"
onChangeText={setUserKey}
onSubmitEditing={onIdentify}
value={userKey}
/>
<TouchableOpacity onPress={onIdentify} style={styles.buttonContainer}>
<Text style={styles.buttonText}>identify</Text>
</TouchableOpacity>
<TextInput
style={styles.input}
autoCapitalize="none"
onChangeText={setFlagKey}
value={flagKey}
/>
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>get flag value</Text>
</TouchableOpacity>
</View>
);
}
Expand All @@ -33,9 +51,25 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 60,
height: 60,
marginVertical: 20,
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
buttonContainer: {
elevation: 8,
backgroundColor: '#009688',
borderRadius: 10,
paddingVertical: 10,
paddingHorizontal: 12,
marginBottom: 20,
},
buttonText: {
fontSize: 18,
color: '#fff',
fontWeight: 'bold',
alignSelf: 'center',
textTransform: 'uppercase',
},
});
12 changes: 8 additions & 4 deletions packages/sdk/react-native/link-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ declare -a examples=(example)
for example in "${examples[@]}"
do
echo "===== Linking to $example"
MODULES_DIR=$example/node_modules
SDK_DIR=$MODULES_DIR/@launchdarkly/react-native-client-sdk
COMMON_DIR="$SDK_DIR"/node_modules/@launchdarkly/js-sdk-common
CLIENT_COMMON_DIR="$SDK_DIR"/node_modules/@launchdarkly/js-client-sdk-common
MODULES_DIR="$example"/node_modules
SDK_DIR="$MODULES_DIR"/@launchdarkly/react-native-client-sdk
SDK_DIR_MODULES="$SDK_DIR"/node_modules
SDK_LD_DIR="$SDK_DIR_MODULES"/@launchdarkly
COMMON_DIR="$SDK_LD_DIR"/js-sdk-common
CLIENT_COMMON_DIR="$SDK_LD_DIR"/js-client-sdk-common

mkdir -p "$MODULES_DIR"
rm -rf "$SDK_DIR"
Expand All @@ -22,6 +24,7 @@ do
rsync -aq src "$SDK_DIR"
rsync -aq package.json "$SDK_DIR"
rsync -aq LICENSE "$SDK_DIR"
rsync -aq node_modules/@react-native-async-storage "$SDK_DIR"/node_modules
rsync -aq node_modules/base64-js "$SDK_DIR"/node_modules
rsync -aq node_modules/event-target-shim "$SDK_DIR"/node_modules

Expand All @@ -33,4 +36,5 @@ do
rsync -aq ../../shared/sdk-client/dist "$CLIENT_COMMON_DIR"
rsync -aq ../../shared/sdk-client/src "$CLIENT_COMMON_DIR"
rsync -aq ../../shared/sdk-client/package.json "$CLIENT_COMMON_DIR"

done
1 change: 1 addition & 0 deletions packages/sdk/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
},
"dependencies": {
"@launchdarkly/js-client-sdk-common": "0.0.1",
"@react-native-async-storage/async-storage": "^1.21.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need this now since AsyncStorage is removed in 0.73.

"base64-js": "^1.5.1",
"event-target-shim": "^6.0.2"
},
Expand Down
10 changes: 9 additions & 1 deletion packages/sdk/react-native/src/ReactNativeLDClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@ describe('ReactNativeLDClient', () => {
ldc = new ReactNativeLDClient('mob-test', { sendEvents: false });
});

test('constructor', () => {
test('constructing a new client', () => {
expect(ldc.sdkKey).toEqual('mob-test');
expect(ldc.config.serviceEndpoints).toEqual({
analyticsEventPath: '/mobile',
diagnosticEventPath: '/mobile/events/diagnostic',
events: 'https://events.launchdarkly.com',
includeAuthorizationHeader: true,
polling: 'https://sdk.launchdarkly.com',
streaming: 'https://clientstream.launchdarkly.com',
});
});

test('createStreamUriPath', () => {
Expand Down
15 changes: 11 additions & 4 deletions packages/sdk/react-native/src/ReactNativeLDClient.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import {
base64UrlEncode,
BasicLogger,
internal,
LDClientImpl,
type LDContext,
type LDOptions,
} from '@launchdarkly/js-client-sdk-common';

import platform from './platform';
import createPlatform from './platform';

export default class ReactNativeLDClient extends LDClientImpl {
constructor(sdkKey: string, options: LDOptions = {}) {
const logger =
options.logger ??
new BasicLogger({
level: 'info',
level: 'debug',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I intentionally left this as debug for the alpha.

// eslint-disable-next-line no-console
destination: console.log,
});
super(sdkKey, platform, { ...options, logger });

const internalOptions: internal.LDInternalOptions = {
analyticsEventPath: `/mobile`,
diagnosticEventPath: `/mobile/events/diagnostic`,
};

super(sdkKey, createPlatform(logger), { ...options, logger }, internalOptions);
}

override createStreamUriPath(context: LDContext) {
return `/meval/${base64UrlEncode(JSON.stringify(context), platform.encoding!)}`;
return `/meval/${base64UrlEncode(JSON.stringify(context), this.platform.encoding!)}`;
}
}
3 changes: 1 addition & 2 deletions packages/sdk/react-native/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import useLDClient from './useLDClient';
import useLDDataSourceStatus from './useLDDataSourceStatus';

export * from './variation';

export { useLDDataSourceStatus, useLDClient };
export { useLDClient };
10 changes: 0 additions & 10 deletions packages/sdk/react-native/src/hooks/useLDDataSourceStatus.ts
Copy link
Contributor Author

@yusinto yusinto Dec 18, 2023

Choose a reason for hiding this comment

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

I used this for debugging and now it's no longer required.

This file was deleted.

38 changes: 35 additions & 3 deletions packages/sdk/react-native/src/platform.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* eslint-disable max-classes-per-file */
import AsyncStorage from '@react-native-async-storage/async-storage';

// @ts-ignore
import type {
Crypto,
Encoding,
Expand All @@ -8,12 +11,14 @@ import type {
Hasher,
Hmac,
Info,
LDLogger,
Options,
Platform,
PlatformData,
Requests,
Response,
SdkData,
Storage,
} from '@launchdarkly/js-client-sdk-common';

import { name, version } from '../package.json';
Expand Down Expand Up @@ -68,11 +73,38 @@ class PlatformCrypto implements Crypto {
return uuidv4();
}
}
const platform: Platform = {

class PlatformStorage implements Storage {
constructor(private readonly logger: LDLogger) {}
async clear(key: string): Promise<void> {
await AsyncStorage.removeItem(key);
}

async get(key: string): Promise<string | null> {
try {
const value = await AsyncStorage.getItem(key);
return value ?? null;
} catch (error) {
this.logger.debug(`Error getting AsyncStorage key: ${key}, error: ${error}`);
return null;
}
}

async set(key: string, value: string): Promise<void> {
try {
await AsyncStorage.setItem(key, value);
} catch (error) {
this.logger.debug(`Error saving AsyncStorage key: ${key}, value: ${value}, error: ${error}`);
}
}
}

const createPlatform = (logger: LDLogger): Platform => ({
crypto: new PlatformCrypto(),
info: new PlatformInfo(),
requests: new PlatformRequests(),
encoding: new PlatformEncoding(),
};
storage: new PlatformStorage(logger),
});

export default platform;
export default createPlatform;
2 changes: 1 addition & 1 deletion packages/sdk/react-native/src/provider/LDProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type LDProps = {
};

const LDProvider = ({ client, context, children }: PropsWithChildren<LDProps>) => {
const [state, setState] = useState<ReactContext>({ client, context, dataSource: {} });
const [state, setState] = useState<ReactContext>({ client });

useEffect(() => {
setupListeners(client, setState);
Expand Down
8 changes: 1 addition & 7 deletions packages/sdk/react-native/src/provider/reactContext.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { createContext } from 'react';

import { LDClient, LDContext } from '@launchdarkly/js-client-sdk-common';
import { LDClient } from '@launchdarkly/js-client-sdk-common';

export type ReactContext = {
client: LDClient;
context?: LDContext;
dataSource: {
status?: 'connecting' | 'ready' | 'error';
error?: Error;
};
};

export const context = createContext<ReactContext>({
client: {} as any,
dataSource: {},
});

const { Provider, Consumer } = context;
Expand Down
14 changes: 4 additions & 10 deletions packages/sdk/react-native/src/provider/setupListeners.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import type { Dispatch, SetStateAction } from 'react';

import { LDContext } from '@launchdarkly/js-client-sdk-common';

import ReactNativeLDClient from '../ReactNativeLDClient';
import { ReactContext } from './reactContext';

const setupListeners = (
client: ReactNativeLDClient,
setState: Dispatch<SetStateAction<ReactContext>>,
) => {
client.on('connecting', (c: LDContext) => {
setState({ client, context: c, dataSource: { status: 'connecting' } });
});

client.on('ready', (c: LDContext) => {
setState({ client, context: c, dataSource: { status: 'ready' } });
client.on('ready', () => {
setState({ client });
});

client.on('error', (c: LDContext, e: any) => {
setState({ client, context: c, dataSource: { status: 'error', error: e } });
client.on('change', () => {
setState({ client });
});
};

Expand Down
8 changes: 8 additions & 0 deletions packages/shared/common/src/api/data/LDFlagChangeset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LDFlagValue } from './LDFlagValue';

export interface LDFlagChangeset {
[key: string]: {
current?: LDFlagValue;
previous?: LDFlagValue;
};
}
1 change: 1 addition & 0 deletions packages/shared/common/src/api/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './LDEvaluationDetail';
export * from './LDEvaluationReason';
export * from './LDFlagSet';
export * from './LDFlagValue';
export * from './LDFlagChangeset';
7 changes: 7 additions & 0 deletions packages/shared/common/src/api/platform/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Encoding } from './Encoding';
import { Filesystem } from './Filesystem';
import { Info } from './Info';
import { Requests } from './Requests';
import { Storage } from './Storage';

export interface Platform {
/**
Expand Down Expand Up @@ -31,4 +32,10 @@ export interface Platform {
* The interface for performing http/https requests.
*/
requests: Requests;

/**
* The interface for session specific storage object. If the platform does not
* support local storage access, this may be undefined.
*/
storage?: Storage;
}
5 changes: 5 additions & 0 deletions packages/shared/common/src/api/platform/Storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Storage {
get: (key: string) => Promise<string | null>;
set: (key: string, value: string) => Promise<void>;
clear: (key: string) => Promise<void>;
}
1 change: 1 addition & 0 deletions packages/shared/common/src/api/platform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './Info';
export * from './Platform';
export * from './Requests';
export * from './EventSource';
export * from './Storage';
4 changes: 2 additions & 2 deletions packages/shared/common/src/utils/clone.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function clone(obj: any) {
return JSON.parse(JSON.stringify(obj));
export default function clone<T>(obj: any) {
return JSON.parse(JSON.stringify(obj)) as T;
}
Loading