Web React
Web React Embedding¶
This guide explains how to embed the InvestSuite SDK in a React web application.
Download the InvestSuite SDK from your Codemagic dashboard¶
The InvestSuite team will provide you with a Codemagic dashboard where you can download (new) versions of the SDK as we release them. Download the latest one and store it in a convenient location where you can easily access it later.
Unzip the "artifacts" file you downloaded from Codemagic. This should give you a folder containing the SDK packages and a fully working example React project.
Example app¶
Each SDK embedding module ships with a functional example app that shows how to use the SDK.
You can find this by unzipping the downloaded file inside the example folder.
In order to use this example app:
1. Navigate to the example folder
2. Run npm install or yarn install
3. Run npm start or yarn start
Install the SDK as a dependency¶
Add the SDK package to your project:
npm install <path-to-sdk>/investsuite-embedding-web-react
Usage of the add-to-app SDK¶
Import the SDK¶
import {
FlutterEmbeddingContext,
FlutterEmbeddingView,
useFlutterEmbeddingContext,
IHandoversToHostService,
StartParams,
Language,
ThemeMode,
// Request/Response types
ProvideAccessTokenRequest,
ProvideAccessTokenResponse,
ProvideAnonymousAccessTokenRequest,
ProvideAnonymousAccessTokenResponse,
ReceiveAnalyticsEventRequest,
ReceiveAnalyticsEventResponse,
ReceiveDebugLogRequest,
ReceiveDebugLogResponse,
ReceiveErrorRequest,
ReceiveErrorResponse,
OnExitRequest,
OnExitResponse,
StartFaqRequest,
StartFaqResponse,
StartOnboardingRequest,
StartOnboardingResponse,
StartFundPortfolioRequest,
StartFundPortfolioResponse,
StartAddMoneyRequest,
StartAddMoneyResponse,
StartAuthorizationRequest,
StartAuthorizationResponse,
StartTransactionSigningRequest,
StartTransactionSigningResponse,
// For communicating with Flutter
ChangeLanguageRequest,
ChangeThemeModeRequest,
ResetRequest,
NavigateToRequest,
HandleNotificationRequest,
InvestSuiteNotificationData,
} from 'investsuite-embedding-web-react';
import { ServerCallContext } from '@nicegram/grpc-web-client';
Implementing the HandoversToHostService¶
Before starting the engine, you need to implement the IHandoversToHostService interface which handles callbacks from Flutter to your app:
const createHandoversToHostService = (
accessToken: string,
onExit: () => void
): IHandoversToHostService => {
return {
provideAccessToken(
_request: ProvideAccessTokenRequest,
_context: ServerCallContext
): Promise<ProvideAccessTokenResponse> {
console.log('provideAccessToken called');
return Promise.resolve(
ProvideAccessTokenResponse.create({ accessToken })
);
},
provideAnonymousAccessToken(
_request: ProvideAnonymousAccessTokenRequest,
_context: ServerCallContext
): Promise<ProvideAnonymousAccessTokenResponse> {
console.log('provideAnonymousAccessToken called');
return Promise.resolve(
ProvideAnonymousAccessTokenResponse.create({ anonymousAccessToken: '' })
);
},
receiveAnalyticsEvent(
request: ReceiveAnalyticsEventRequest,
_context: ServerCallContext
): Promise<ReceiveAnalyticsEventResponse> {
console.log('receiveAnalyticsEvent:', request.name, request.parameters);
// Forward to your analytics service
return Promise.resolve(ReceiveAnalyticsEventResponse.create({}));
},
receiveDebugLog(
request: ReceiveDebugLogRequest,
_context: ServerCallContext
): Promise<ReceiveDebugLogResponse> {
console.log('receiveDebugLog:', request.level, request.message);
return Promise.resolve(ReceiveDebugLogResponse.create({}));
},
receiveError(
request: ReceiveErrorRequest,
_context: ServerCallContext
): Promise<ReceiveErrorResponse> {
console.error('receiveError:', request.errorCode, request.data);
return Promise.resolve(ReceiveErrorResponse.create({}));
},
onExit(
_request: OnExitRequest,
_context: ServerCallContext
): Promise<OnExitResponse> {
console.log('onExit called');
onExit();
return Promise.resolve(OnExitResponse.create({}));
},
startFaq(
request: StartFaqRequest,
_context: ServerCallContext
): Promise<StartFaqResponse> {
console.log('startFaq:', request.module);
alert(`Opening FAQ for module: ${request.module}`);
return Promise.resolve(StartFaqResponse.create({}));
},
startOnboarding(
_request: StartOnboardingRequest,
_context: ServerCallContext
): Promise<StartOnboardingResponse> {
console.log('startOnboarding called');
alert('Starting onboarding flow');
return Promise.resolve(StartOnboardingResponse.create({ success: true }));
},
startFundPortfolio(
request: StartFundPortfolioRequest,
_context: ServerCallContext
): Promise<StartFundPortfolioResponse> {
console.log('startFundPortfolio:', request.portfolioData);
return Promise.resolve(StartFundPortfolioResponse.create({ success: true }));
},
startAddMoney(
request: StartAddMoneyRequest,
_context: ServerCallContext
): Promise<StartAddMoneyResponse> {
console.log('startAddMoney:', request.portfolioData);
return Promise.resolve(StartAddMoneyResponse.create({ success: true }));
},
startAuthorization(
_request: StartAuthorizationRequest,
_context: ServerCallContext
): Promise<StartAuthorizationResponse> {
console.log('startAuthorization called');
return new Promise((resolve) => {
const result = window.confirm('Please authorize this action');
resolve(StartAuthorizationResponse.create({ success: result }));
});
},
startTransactionSigning(
request: StartTransactionSigningRequest,
_context: ServerCallContext
): Promise<StartTransactionSigningResponse> {
console.log('startTransactionSigning:', request.portfolioId, request.amount);
return new Promise((resolve) => {
const result = window.confirm(`Sign transaction for ${request.amount}?`);
resolve(StartTransactionSigningResponse.create({ success: result }));
});
},
};
};
Creating StartParams¶
Create the start parameters with language, theme mode, and environment:
const createStartParams = (): StartParams => {
return StartParams.create({
language: Language.EN, // EN, NL, FR, AR, TR
themeMode: ThemeMode.SYSTEM, // LIGHT, DARK, SYSTEM
environment: 'TST', // "MOCK", "TST", "UAT", "PROD"
});
};
Available Languages:
- Language.EN - English
- Language.NL - Dutch
- Language.FR - French
- Language.AR - Arabic
- Language.TR - Turkish
Available Theme Modes:
- ThemeMode.LIGHT
- ThemeMode.DARK
- ThemeMode.SYSTEM
App Component with FlutterEmbeddingContext¶
Wrap your app with the FlutterEmbeddingContext provider:
import React, { useState, useCallback, useMemo } from 'react';
import {
FlutterEmbeddingContext,
FlutterEmbeddingView,
useFlutterEmbeddingContext,
} from 'investsuite-embedding-web-react';
function App() {
const [accessToken, setAccessToken] = useState('your-access-token');
const [showFlutter, setShowFlutter] = useState(false);
const handleExit = useCallback(() => {
setShowFlutter(false);
}, []);
const handoversToHostService = useMemo(
() => createHandoversToHostService(accessToken, handleExit),
[accessToken, handleExit]
);
const startParams = useMemo(() => createStartParams(), []);
if (!showFlutter) {
return (
<div className="landing-page">
<h1>InvestSuite SDK Demo</h1>
<input
type="text"
value={accessToken}
onChange={(e) => setAccessToken(e.target.value)}
placeholder="Enter access token"
/>
<button onClick={() => setShowFlutter(true)}>
Start InvestSuite SDK
</button>
</div>
);
}
return (
<FlutterEmbeddingContext
flutterAppBundlePath="flutter/main.dart.js"
handoversToHostService={handoversToHostService}
startParams={startParams}
>
<MainContent />
</FlutterEmbeddingContext>
);
}
MainContent Component with FlutterEmbeddingView¶
function MainContent() {
const {
loading,
error,
handoversToFlutterServiceClient,
} = useFlutterEmbeddingContext();
if (loading) {
return <div className="loading">Loading InvestSuite SDK...</div>;
}
if (error) {
return <div className="error">Error: {error.message}</div>;
}
return (
<div className="app-container">
<header className="app-header">
<h1>InvestSuite SDK</h1>
<CommunicationControls client={handoversToFlutterServiceClient} />
</header>
<main className="flutter-container">
<FlutterEmbeddingView />
</main>
</div>
);
}
CommunicationView Component¶
Create controls to communicate with the Flutter SDK:
interface CommunicationControlsProps {
client: HandoversToFlutterServiceClient | null;
}
function CommunicationControls({ client }: CommunicationControlsProps) {
const [selectedLanguage, setSelectedLanguage] = useState<Language>(Language.EN);
const [selectedTheme, setSelectedTheme] = useState<ThemeMode>(ThemeMode.SYSTEM);
const [deeplink, setDeeplink] = useState('/self/portfolio/DEMO/more');
const changeLanguage = async () => {
if (!client) return;
try {
const request = ChangeLanguageRequest.create({ language: selectedLanguage });
await client.changeLanguage(request);
console.log('Language changed successfully');
} catch (error) {
console.error('Error changing language:', error);
}
};
const changeThemeMode = async () => {
if (!client) return;
try {
const request = ChangeThemeModeRequest.create({ themeMode: selectedTheme });
await client.changeThemeMode(request);
console.log('Theme mode changed successfully');
} catch (error) {
console.error('Error changing theme mode:', error);
}
};
const reset = async (clearData: boolean) => {
if (!client) return;
try {
const request = ResetRequest.create({ clearData });
await client.reset(request);
console.log(`Reset called with clearData=${clearData}`);
} catch (error) {
console.error('Error resetting:', error);
}
};
const navigateTo = async () => {
if (!client) return;
try {
const request = NavigateToRequest.create({ deeplink });
await client.navigateTo(request);
console.log(`NavigateTo called with deeplink=${deeplink}`);
} catch (error) {
console.error('Error navigating:', error);
}
};
const handleNotification = async () => {
if (!client) return;
try {
const notificationData = InvestSuiteNotificationData.create({
id: 'notification-123',
title: 'Deposit Received',
body: 'Your deposit has been processed',
type: 'CASH_DEPOSIT_EXECUTED',
module: 'SELF',
createdAt: BigInt(Date.now()),
data: { portfolio_id: 'DEMO' },
});
const request = HandleNotificationRequest.create({ notificationData });
await client.handleNotification(request);
console.log('Notification handled successfully');
} catch (error) {
console.error('Error handling notification:', error);
}
};
return (
<div className="communication-controls">
<div className="control-group">
<label>Language:</label>
<select
value={selectedLanguage}
onChange={(e) => setSelectedLanguage(Number(e.target.value) as Language)}
>
<option value={Language.EN}>English</option>
<option value={Language.NL}>Dutch</option>
<option value={Language.FR}>French</option>
<option value={Language.AR}>Arabic</option>
<option value={Language.TR}>Turkish</option>
</select>
<button onClick={changeLanguage}>Change Language</button>
</div>
<div className="control-group">
<label>Theme:</label>
<select
value={selectedTheme}
onChange={(e) => setSelectedTheme(Number(e.target.value) as ThemeMode)}
>
<option value={ThemeMode.LIGHT}>Light</option>
<option value={ThemeMode.DARK}>Dark</option>
<option value={ThemeMode.SYSTEM}>System</option>
</select>
<button onClick={changeThemeMode}>Change Theme</button>
</div>
<div className="control-group">
<button onClick={() => reset(false)}>Reset (Keep Data)</button>
<button onClick={() => reset(true)}>Reset (Clear Data)</button>
</div>
<div className="control-group">
<label>Deeplink:</label>
<input
type="text"
value={deeplink}
onChange={(e) => setDeeplink(e.target.value)}
/>
<button onClick={navigateTo}>Navigate To</button>
</div>
<div className="control-group">
<button onClick={handleNotification}>Send Test Notification</button>
</div>
</div>
);
}
Configuration¶
FlutterEmbeddingContext Props¶
| Prop | Type | Required | Description |
|---|---|---|---|
flutterAppBundlePath |
string |
Yes | Path to the Flutter web build (main.dart.js). |
handoversToHostService |
IHandoversToHostService |
Yes | Your implementation of the handovers service. |
startParams |
StartParams |
Yes | Initial configuration for the SDK. |
FlutterEmbeddingView Props¶
| Prop | Type | Required | Description |
|---|---|---|---|
style |
CSSProperties |
No | Custom styles for the container. |
className |
string |
No | Custom CSS class name. |
useFlutterEmbeddingContext Hook¶
Returns an object with:
| Property | Type | Description |
|---|---|---|
loading |
boolean |
Whether the SDK is still loading. |
error |
Error \| null |
Any error that occurred during initialization. |
handoversToFlutterServiceClient |
HandoversToFlutterServiceClient \| null |
Client to communicate with Flutter. |
Styling¶
The FlutterEmbeddingView renders an iframe that hosts the Flutter web application. Style it like any other HTML element:
.flutter-container {
width: 100%;
height: calc(100vh - 60px);
border: none;
}