Skip to content

Commit 6da7e9b

Browse files
committed
refactor: initialization of Pega logic in Embedded sample
1 parent 5e3b7eb commit 6da7e9b

File tree

4 files changed

+205
-97
lines changed

4 files changed

+205
-97
lines changed

packages/react-sdk-components/src/samples/Embedded/MainScreen/index.tsx

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { useEffect, useMemo, useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { getSdkConfig } from '@pega/auth/lib/sdk-auth-manager';
33
import { makeStyles } from '@mui/styles';
44

5-
import StoreContext from '../../../bridge/Context/StoreContext';
6-
import createPConnectComponent from '../../../bridge/react_pconnect';
5+
import { usePegaAuth } from '../context/PegaAuthProvider';
6+
import { usePega } from '../context/PegaReadyContext';
77

88
import ShoppingOptionCard from '../ShoppingOptionCard';
99
import ResolutionScreen from '../ResolutionScreen';
@@ -105,45 +105,35 @@ const useStyles = makeStyles(theme => ({
105105
pegaForm: {}
106106
}));
107107

108-
function RootComponent(props) {
109-
const PegaConnectObj = createPConnectComponent();
110-
const thePConnObj = <PegaConnectObj {...props} />;
108+
export default function MainScreen() {
109+
const { isAuthenticated } = usePegaAuth();
110+
const { isPegaReady, PegaContainer } = usePega();
111111

112-
/**
113-
* NOTE: For Embedded mode, we add in displayOnlyFA to our React context
114-
* so it is available to any component that may need it.
115-
* VRS: Attempted to remove displayOnlyFA but it presently handles various components which
116-
* SDK does not yet support, so all those need to be fixed up before it can be removed.
117-
* To be done in a future sprint.
118-
*/
119-
const contextValue = useMemo(() => {
120-
return { store: PCore.getStore(), displayOnlyFA: true };
121-
}, [PCore.getStore()]);
122-
123-
return <StoreContext.Provider value={contextValue}>{thePConnObj}</StoreContext.Provider>;
124-
}
125-
126-
export default function MainScreen(props) {
127112
const classes = useStyles();
113+
128114
const [showPega, setShowPega] = useState(false);
129115
const [showLandingPage, setShowLandingPage] = useState(true);
130116
const [showResolution, setShowResolution] = useState(false);
131117

132118
useEffect(() => {
133-
// Subscribe to the EVENT_CANCEL event to handle the assignment cancellation
134-
PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, () => cancelAssignment(), 'cancelAssignment');
135-
// Subscribe to the END_OF_ASSIGNMENT_PROCESSING event to handle assignment completion
136-
PCore.getPubSubUtils().subscribe(
137-
PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING,
138-
() => assignmentFinished(),
139-
'endOfAssignmentProcessing'
140-
);
119+
if (isPegaReady) {
120+
// Subscribe to the EVENT_CANCEL event to handle the assignment cancellation
121+
PCore.getPubSubUtils().subscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, () => cancelAssignment(), 'cancelAssignment');
122+
123+
// Subscribe to the END_OF_ASSIGNMENT_PROCESSING event to handle assignment completion
124+
PCore.getPubSubUtils().subscribe(
125+
PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING,
126+
() => assignmentFinished(),
127+
'endOfAssignmentProcessing'
128+
);
129+
}
130+
141131
return () => {
142132
// unsubscribe to the events
143133
PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.EVENT_CANCEL, 'cancelAssignment');
144134
PCore.getPubSubUtils().unsubscribe(PCore.getConstants().PUB_SUB_EVENTS.CASE_EVENTS.END_OF_ASSIGNMENT_PROCESSING, 'endOfAssignmentProcessing');
145135
};
146-
}, []);
136+
}, [isPegaReady]);
147137

148138
const cancelAssignment = () => {
149139
setShowLandingPage(true);
@@ -245,13 +235,15 @@ export default function MainScreen(props) {
245235
return (
246236
<div className={classes.pegaViewContainer}>
247237
<div className={classes.pegaForm} id='pega-part-of-page'>
248-
<RootComponent {...props} />
238+
<PegaContainer />
249239
<br />
250240
</div>
251241
</div>
252242
);
253243
}
254244

245+
if (!isAuthenticated) return <div>Loading...</div>;
246+
255247
return (
256248
<div className={classes.appContainer}>
257249
{showLandingPage && renderLandingPage()}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { createContext, useContext, useEffect, useState } from 'react';
2+
import { getSdkConfig, loginIfNecessary, sdkSetAuthHeader, sdkSetCustomTokenParamsCB } from '@pega/auth/lib/sdk-auth-manager';
3+
4+
interface AuthContextType {
5+
isAuthenticated: boolean;
6+
}
7+
8+
const UserAuthContext = createContext<AuthContextType | undefined>(undefined);
9+
10+
const PegaAuthProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
11+
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
12+
13+
const initialize = async () => {
14+
try {
15+
// Add event listener for when logged in and constellation bootstrap is loaded
16+
document.addEventListener('SdkConstellationReady', () => {
17+
setIsAuthenticated(true);
18+
});
19+
20+
// Initialize authentication settings
21+
await initializeAuthentication();
22+
23+
// this function will handle login process, and SdkConstellationReady event will be fired once PCore is ready
24+
loginIfNecessary({ appName: 'embedded', mainRedirect: false });
25+
} catch (error) {
26+
console.error('Something went wrong while login', error);
27+
}
28+
};
29+
30+
useEffect(() => {
31+
initialize();
32+
}, []);
33+
34+
return <UserAuthContext.Provider value={{ isAuthenticated }}>{children}</UserAuthContext.Provider>;
35+
};
36+
37+
export default PegaAuthProvider;
38+
39+
export const usePegaAuth = (): AuthContextType => {
40+
const context = useContext(UserAuthContext);
41+
if (context === undefined) {
42+
throw new Error('useAuth must be used within an AuthProvider');
43+
}
44+
return context;
45+
};
46+
47+
async function initializeAuthentication() {
48+
const { authConfig } = await getSdkConfig();
49+
50+
if ((authConfig.mashupGrantType === 'none' || !authConfig.mashupClientId) && authConfig.customAuthType === 'Basic') {
51+
// Service package to use custom auth with Basic
52+
const sB64 = window.btoa(`${authConfig.mashupUserIdentifier}:${window.atob(authConfig.mashupPassword)}`);
53+
sdkSetAuthHeader(`Basic ${sB64}`);
54+
}
55+
56+
if ((authConfig.mashupGrantType === 'none' || !authConfig.mashupClientId) && authConfig.customAuthType === 'BasicTO') {
57+
const now = new Date();
58+
const expTime = new Date(now.getTime() + 5 * 60 * 1000);
59+
let sISOTime = `${expTime.toISOString().split('.')[0]}Z`;
60+
const regex = /[-:]/g;
61+
sISOTime = sISOTime.replace(regex, '');
62+
// Service package to use custom auth with Basic
63+
const sB64 = window.btoa(`${authConfig.mashupUserIdentifier}:${window.atob(authConfig.mashupPassword)}:${sISOTime}`);
64+
sdkSetAuthHeader(`Basic ${sB64}`);
65+
}
66+
67+
if (authConfig.mashupGrantType === 'customBearer' && authConfig.customAuthType === 'CustomIdentifier') {
68+
// Use custom bearer with specific custom parameter to set the desired operator via
69+
// a userIdentifier property. (Caution: highly insecure...being used for simple demonstration)
70+
sdkSetCustomTokenParamsCB(() => {
71+
return { userIdentifier: authConfig.mashupUserIdentifier };
72+
});
73+
}
74+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
2+
3+
import StoreContext from '../../../bridge/Context/StoreContext';
4+
import createPConnectComponent from '../../../bridge/react_pconnect';
5+
import { getSdkComponentMap } from '../../../bridge/helpers/sdk_component_map';
6+
7+
import localSdkComponentMap from '../../../../sdk-local-component-map';
8+
9+
import { usePegaAuth } from './PegaAuthProvider';
10+
11+
function RootComponent(props) {
12+
const PegaConnectObj = createPConnectComponent();
13+
const thePConnObj = <PegaConnectObj {...props} />;
14+
15+
/**
16+
* NOTE: For Embedded mode, we add in displayOnlyFA to our React context
17+
* so it is available to any component that may need it.
18+
* VRS: Attempted to remove displayOnlyFA but it presently handles various components which
19+
* SDK does not yet support, so all those need to be fixed up before it can be removed.
20+
* To be done in a future sprint.
21+
*/
22+
const contextValue = useMemo(() => {
23+
return { store: PCore.getStore(), displayOnlyFA: true };
24+
}, [PCore.getStore()]);
25+
26+
return <StoreContext.Provider value={contextValue}>{thePConnObj}</StoreContext.Provider>;
27+
}
28+
29+
interface PegaContextProps {
30+
isPegaReady: boolean;
31+
rootPConnect?: typeof PConnect; // Function to get Pega Connect object, if available
32+
PegaContainer: React.FC;
33+
}
34+
35+
declare const myLoadMashup: any;
36+
37+
const PegaContext = createContext<PegaContextProps | undefined>(undefined);
38+
39+
export const PegaReadyProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
40+
const { isAuthenticated } = usePegaAuth();
41+
const [isPegaReady, setIsPegaReady] = useState<boolean>(false);
42+
const [rootProps, setRootProps] = useState<{
43+
getPConnect?: () => typeof PConnect;
44+
[key: string]: any;
45+
}>({});
46+
47+
const startMashup = async () => {
48+
try {
49+
PCore.onPCoreReady(async renderObj => {
50+
console.log(`PCore ready!`);
51+
52+
const theComponentMap = await getSdkComponentMap(localSdkComponentMap);
53+
console.log(`SdkComponentMap initialized`, theComponentMap);
54+
55+
const { props } = renderObj;
56+
setRootProps(props);
57+
setIsPegaReady(true);
58+
});
59+
60+
// load the Mashup and handle the onPCoreEntry response that establishes the
61+
// top level Pega root element (likely a RootContainer)
62+
myLoadMashup('pega-root', false); // this is defined in bootstrap shell that's been loaded already
63+
} catch (error) {
64+
console.error('Error loading pega:', error);
65+
}
66+
};
67+
68+
/**
69+
* Start the mashup once authenticated
70+
* This ensures that the Pega environment is ready for use
71+
*/
72+
useEffect(() => {
73+
if (isAuthenticated) {
74+
startMashup();
75+
}
76+
}, [isAuthenticated]);
77+
78+
// Memoize the root PConnect function to avoid unnecessary re-renders
79+
const rootPConnect = useMemo(() => {
80+
if (rootProps && rootProps?.getPConnect) {
81+
return rootProps.getPConnect();
82+
}
83+
84+
return undefined;
85+
}, [rootProps]);
86+
87+
const PegaContainer = () => (isPegaReady ? <RootComponent {...rootProps} /> : null);
88+
89+
return <PegaContext.Provider value={{ isPegaReady, rootPConnect, PegaContainer }}>{children}</PegaContext.Provider>;
90+
};
91+
92+
export const usePega = () => {
93+
const context = useContext(PegaContext);
94+
if (!context) {
95+
throw new Error('usePega must be used within a PegaProvider');
96+
}
97+
return context;
98+
};

packages/react-sdk-components/src/samples/Embedded/index.tsx

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,26 @@
1-
import { useEffect, useState } from 'react';
21
import { CssBaseline, StyledEngineProvider, ThemeProvider } from '@mui/material';
3-
import { getSdkConfig, loginIfNecessary } from '@pega/auth/lib/sdk-auth-manager';
42

5-
import { getSdkComponentMap } from '../../bridge/helpers/sdk_component_map';
6-
import { compareSdkPCoreVersions } from '../../components/helpers/versionHelpers';
3+
import PegaAuthProvider from './context/PegaAuthProvider';
4+
import { PegaReadyProvider } from './context/PegaReadyContext';
75

86
import Header from './Header';
97
import MainScreen from './MainScreen';
10-
import localSdkComponentMap from '../../../sdk-local-component-map';
11-
import { initializeAuthentication } from './utils';
128
import { theme } from '../../theme';
139
import './styles.css';
1410

15-
declare const myLoadMashup: any;
16-
1711
export default function Embedded() {
18-
const [isLoggedIn, setIsLoggedIn] = useState(false);
19-
const [rootProps, setRootProps] = useState({});
20-
21-
useEffect(() => {
22-
initialize();
23-
}, []);
24-
25-
const initialize = async () => {
26-
try {
27-
// Add event listener for when logged in and constellation bootstrap is loaded
28-
document.addEventListener('SdkConstellationReady', () => handleSdkConstellationReady());
29-
30-
const { authConfig } = await getSdkConfig();
31-
initializeAuthentication(authConfig);
32-
33-
// this function will handle login process, and SdkConstellationReady event will be fired once PCore is ready
34-
loginIfNecessary({ appName: 'embedded', mainRedirect: false });
35-
} catch (error) {
36-
console.error('Something went wrong while login', error);
37-
}
38-
};
39-
40-
const initializeRootContainerProps = renderObj => {
41-
const { props } = renderObj;
42-
43-
setRootProps(props);
44-
};
45-
46-
const startMashup = () => {
47-
PCore.onPCoreReady(async renderObj => {
48-
// Check that we're seeing the PCore version we expect
49-
compareSdkPCoreVersions();
50-
51-
await getSdkComponentMap(localSdkComponentMap);
52-
53-
console.log(`SdkComponentMap initialized`);
54-
55-
// Don't call initializeRootContainerProps until SdkComponentMap is fully initialized
56-
initializeRootContainerProps(renderObj);
57-
});
58-
59-
myLoadMashup('pega-root', false); // this is defined in bootstrap shell that's been loaded already
60-
};
61-
62-
const handleSdkConstellationReady = () => {
63-
setIsLoggedIn(true);
64-
65-
startMashup();
66-
};
67-
6812
return (
6913
<StyledEngineProvider injectFirst>
7014
<ThemeProvider theme={theme}>
7115
<CssBaseline />
72-
{isLoggedIn ? (
73-
<>
74-
<Header />
75-
<MainScreen {...rootProps} />
76-
</>
77-
) : (
78-
<div>Loading...</div>
79-
)}
16+
<PegaAuthProvider>
17+
<PegaReadyProvider>
18+
<>
19+
<Header />
20+
<MainScreen />
21+
</>
22+
</PegaReadyProvider>
23+
</PegaAuthProvider>
8024
</ThemeProvider>
8125
</StyledEngineProvider>
8226
);

0 commit comments

Comments
 (0)