Skip to content

Commit

Permalink
refactor: Use utils for determining LD init
Browse files Browse the repository at this point in the history
  • Loading branch information
Joel Anton committed May 9, 2024
1 parent e0bff8a commit 6aded3a
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 22 deletions.
4 changes: 2 additions & 2 deletions libs/shared-web/src/hooks/useFeatureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export const useFeatureFlags = (organization?: IOrganizationEntity) => {
const ldClient = useLDClient();

useEffect(() => {
if (!checkShouldUseLaunchDarkly() || !organization?._id) {
if (!checkShouldUseLaunchDarkly() || !organization?._id || !ldClient) {
return;
}

ldClient?.identify({
ldClient.identify({
kind: 'organization',
key: organization._id,
name: organization.name,
Expand Down
97 changes: 77 additions & 20 deletions libs/shared-web/src/providers/LaunchDarklyProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IOrganizationEntity } from '@novu/shared';
import { IOrganizationEntity, IUserEntity } from '@novu/shared';
import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk';
import { PropsWithChildren, ReactNode, useEffect, useRef, useState } from 'react';
import { PropsWithChildren, ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { LAUNCH_DARKLY_CLIENT_SIDE_ID } from '../config';
import { useFeatureFlags } from '../hooks';
import { checkShouldUseLaunchDarkly } from '../utils';
import { useAuthContext } from './AuthProvider';
import { useAuthContext, UserContext } from './AuthProvider';

/** A provider with children required */
type GenericLDProvider = Awaited<ReturnType<typeof asyncWithLDProvider>>;
Expand Down Expand Up @@ -33,37 +33,53 @@ export const LaunchDarklyProvider: React.FC<PropsWithChildren<ILaunchDarklyProvi
if (!authContext) {
throw new Error('LaunchDarklyProvider must be used within <AuthProvider>!');
}
const { currentOrganization, isLoggedIn } = authContext;
const { currentOrganization, isLoggedIn, isUserLoading, currentUser } = authContext;

const { shouldWaitForLd, doesNeedOrg } = useMemo(() => checkShouldInitializeLaunchDarkly(authContext), [authContext]);

useEffect(() => {
// no need to fetch if LD is disabled or there isn't an org to query against
if (!checkShouldUseLaunchDarkly() || !currentOrganization) {
if (!shouldWaitForLd) {
return;
}

const fetchLDProvider = async () => {
LDProvider.current = await asyncWithLDProvider({
clientSideID: LAUNCH_DARKLY_CLIENT_SIDE_ID,
context: {
kind: 'organization',
key: currentOrganization._id,
name: currentOrganization.name,
},
reactOptions: {
useCamelCaseFlagKeys: false,
},
});
setIsLDReady(true);
try {
LDProvider.current = await asyncWithLDProvider({
clientSideID: LAUNCH_DARKLY_CLIENT_SIDE_ID,
reactOptions: {
useCamelCaseFlagKeys: false,
},
// determine which context to use based on if an organization is available
context: currentOrganization
? {
kind: 'organization',
key: currentOrganization._id,
name: currentOrganization.name,
}
: {
/**
* When user is not authenticated, assigns an id to them to ensure consistent results.
* https://docs.launchdarkly.com/sdk/features/anonymous#javascript
*/
kind: 'user',
anonymous: true,
},
});
} catch (err: unknown) {
// FIXME: what should we do here since we don't have logging?
} finally {
setIsLDReady(true);
}
};

fetchLDProvider();
}, [setIsLDReady, currentOrganization]);
}, [setIsLDReady, shouldWaitForLd, currentOrganization]);

/**
* For self-hosted, LD will not be enabled, so do not block initialization.
* Must not show the fallback if the user isn't logged-in to avoid issues with un-authenticated routes (i.e. login).
*/
if (checkShouldUseLaunchDarkly() && isLoggedIn && !isLDReady) {
if ((shouldWaitForLd || (doesNeedOrg && !currentOrganization)) && !isLDReady) {
return <>{fallbackDisplay}</>;
}

Expand All @@ -82,3 +98,44 @@ function LaunchDarklyClientWrapper({ children, org }: PropsWithChildren<{ org?:

return <>{children}</>;
}

function checkShouldInitializeLaunchDarkly(userCtx: UserContext): { shouldWaitForLd: boolean; doesNeedOrg?: boolean } {
const { isLoggedIn, currentOrganization } = userCtx;

if (!checkShouldUseLaunchDarkly()) {
return { shouldWaitForLd: false };
}

// enable feature flags for unauthenticated areas of the app
if (!isLoggedIn) {
return { shouldWaitForLd: true };
}

// user must be loaded -- a user can have `isLoggedIn` true when `currentUser` is undefined
// eslint-disable-next-line
// if (!currentUser) {
// return { shouldWaitForLd: false };
// }

// allow LD to load when the user is created but still in onboarding
const isUserFullyRegistered = checkIsUserFullyRegistered(userCtx);
if (!isUserFullyRegistered) {
return { shouldWaitForLd: true };
}

// if a user is fully on-boarded, but no organization has loaded, we must wait for the organization to initialize the client.
return { shouldWaitForLd: !!currentOrganization, doesNeedOrg: true };
}

/**
* Determine if a user is fully-registered; if not, they're still in onboarding.
*/
function checkIsUserFullyRegistered(userCtx: UserContext): boolean {
/*
* Determine if the user has completed registration based on if they have an associated orgId.
* Use jobTitle as a back-up
*/
const isUserFullyRegistered = !!userCtx.jwtPayload?.organizationId || !!userCtx.currentUser?.jobTitle;

return isUserFullyRegistered;
}

0 comments on commit 6aded3a

Please sign in to comment.