Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ node_modules
dist
coverage
.project
.vscode
build
package-lock.json
7 changes: 6 additions & 1 deletion AppExamples/CleverDeal.React/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@symphony-ui/uitoolkit-components": "^3.5.0",
"@tanstack/react-table": "^8.21.3",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^13.5.0",
Expand Down Expand Up @@ -53,6 +54,10 @@
]
},
"devDependencies": {
"@types/react-tabs": "^2.3.4"
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/react-tabs": "^2.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.16"
}
}
6 changes: 6 additions & 0 deletions AppExamples/CleverDeal.React/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
}
26 changes: 26 additions & 0 deletions AppExamples/CleverDeal.React/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,32 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Clever Deal 2.0</title>
<!--
Suppress cross-origin "Script error." events from the Symphony ECP SDK
iframe. Must run before bundle.js so our listener is registered first
and can call stopImmediatePropagation before CRA's overlay handler.
-->
<script>
(function () {
if (!document.getElementById('symphony-ecm')) {
return;
}

window.addEventListener(
'error',
function (e) {
if (
(e.message === 'Script error.' || e.message === 'Script error') &&
e.filename === ''
) {
e.stopImmediatePropagation();
e.preventDefault();
}
},
true,
);
})();
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
318 changes: 318 additions & 0 deletions AppExamples/CleverDeal.React/public/wealth-client-chat-host.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Wealth Client Chat Host</title>
<style>
html,
body,
#wealth-client-chat {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background: #fbfcfe;
}

iframe {
border: 0;
}
</style>
</head>
<body>
<div id="wealth-client-chat"></div>
<script>
(function () {
const DEFAULT_ORIGIN = 'corporate.symphony.com';
const DEFAULT_PARTNER_ID = 'symphony_internal_BYC-XXX';
const CONTAINER_ID = 'wealth-client-chat';
const ALLOWED_APPS = 'com.symphony.zoom,com.symphony.teams,salesforce2-app,com.symphony.sfs.admin-app';
const query = new URLSearchParams(window.location.search);
const ecpOrigin = query.get('ecpOrigin') || DEFAULT_ORIGIN;
const partnerId = query.get('partnerId');
const mode = query.get('mode') || 'light';
const theme = query.get('theme');
const podUrl = 'https://' + ecpOrigin;
const initialStreamId = query.get('streamId');
let activeStreamId = initialStreamId;
let frame = null;
let isClientReady = false;
let actionId = 0;
const pendingSendActions = {};

function debug(message, context) {
if (context) {
console.debug('[WealthClientHost] ' + message, context);
return;
}

console.debug('[WealthClientHost] ' + message);
}

function warn(message, context) {
if (context) {
console.warn('[WealthClientHost] ' + message, context);
return;
}

console.warn('[WealthClientHost] ' + message);
}

function error(message, context) {
if (context) {
console.error('[WealthClientHost] ' + message, context);
return;
}

console.error('[WealthClientHost] ' + message);
}

function postToParent(type, payload) {
debug('Posting host event to parent.', { type: type, payload: payload || {} });
window.parent.postMessage(
{
source: 'wealth-client-chat-host',
type: type,
payload: payload || {},
},
window.location.origin,
);
}

function buildFrameUrl(streamId) {
const url = new URL('https://' + ecpOrigin + '/embed/index.html');
url.searchParams.set('streamId', streamId);
url.searchParams.set('module', 'room');
url.searchParams.set('mode', mode);
url.searchParams.set('condensed', 'false');
url.searchParams.set('showTitle', 'false');
url.searchParams.set('canAddPeople', 'true');
url.searchParams.set('ecpLoginPopup', 'true');
url.searchParams.set('allowedApps', ALLOWED_APPS);
url.searchParams.set('sound', 'false');
url.searchParams.set('showInfo', 'false');
url.searchParams.set('showMembers', 'false');
url.searchParams.set('symphonyLogo', 'false');
url.searchParams.set('sdkOrigin', window.location.origin);
url.searchParams.set('embed', 'true');
if (theme) {
url.searchParams.set('theme', theme);
}
if (partnerId) {
url.searchParams.set('partnerId', partnerId);
} else if (ecpOrigin !== 'st3.dev.symphony.com') {
url.searchParams.set('partnerId', DEFAULT_PARTNER_ID);
}
debug('Built client chat frame URL.', {
streamId: streamId,
ecpOrigin: ecpOrigin,
mode: mode,
hasTheme: Boolean(theme),
url: url.toString(),
});
return url.toString();
}

function ensureFrame() {
if (frame) {
return frame;
}

frame = document.createElement('iframe');
frame.setAttribute('title', 'Wealth Client Chat Frame');
frame.setAttribute('style', 'width:100%;height:100%;border:0;');
frame.addEventListener('load', function () {
debug('Client chat iframe load event fired.', {
activeStreamId: activeStreamId,
src: frame ? frame.src : null,
});
});
document.getElementById(CONTAINER_ID).replaceChildren(frame);
debug('Created client chat iframe container.', { containerId: CONTAINER_ID });
return frame;
}

function openStream(streamId) {
if (!streamId) {
debug('Skipped openStream because stream id was empty.');
return;
}

if (activeStreamId === streamId && frame) {
debug('Skipped iframe navigation because stream is already active.', {
streamId: streamId,
});
if (isClientReady) {
postToParent('ready', { streamId: activeStreamId });
}
return;
}

activeStreamId = streamId;
isClientReady = false;
debug('Opening client chat stream in iframe.', {
streamId: streamId,
previousStreamId: frame ? frame.getAttribute('data-stream-id') : null,
});
ensureFrame().setAttribute('data-stream-id', streamId);
ensureFrame().src = buildFrameUrl(streamId);
}

function postEcpMessage(eventType, payload) {
const targetFrame = ensureFrame();
if (!targetFrame.contentWindow) {
throw new Error('Client chat iframe window is not available.');
}

debug('Posting message to embedded ECP frame.', {
eventType: eventType,
payload: payload,
targetOrigin: podUrl,
});
targetFrame.contentWindow.postMessage({ eventType: eventType, payload: payload }, podUrl);
}

function sendMessageToStream(requestId, documentName, messagePayload, streamId) {
const targetStreamId = streamId || activeStreamId;
if (!targetStreamId) {
throw new Error('Client chat stream id is missing.');
}

if (!isClientReady) {
throw new Error('Client chat is not ready yet.');
}

const actionRequestId = 'send-message-' + ++actionId;
pendingSendActions[actionRequestId] = {
requestId: requestId,
documentName: documentName,
streamId: targetStreamId,
};

debug('Sending document share request to embedded client chat.', {
requestId: requestId,
actionRequestId: actionRequestId,
documentName: documentName,
streamId: targetStreamId,
messagePayload: messagePayload,
});

postEcpMessage('sdk-action', {
name: 'send-message',
id: actionRequestId,
params: {
message: messagePayload,
options: {
mode: 'blast',
streamIds: [targetStreamId],
},
},
});
}

window.addEventListener('message', function (event) {
if (event.origin === podUrl) {
const ecpData = event.data || {};
debug('Received embedded ECP event.', ecpData);

if (ecpData.eventType === 'clientReady') {
isClientReady = true;
postToParent('ready', { streamId: activeStreamId });
return;
}

if (ecpData.eventType === 'sdk-resolve') {
const payload = ecpData.payload || {};
const pendingAction = pendingSendActions[payload.id];
if (!pendingAction) {
return;
}

delete pendingSendActions[payload.id];

if (payload.data && payload.data.error) {
error('Embedded ECP send-message failed.', {
actionRequestId: payload.id,
requestId: pendingAction.requestId,
documentName: pendingAction.documentName,
error: payload.data.error,
});
postToParent('share-error', {
requestId: pendingAction.requestId,
documentName: pendingAction.documentName,
message: payload.data.error.message || 'Unable to share document to client chat.',
});
return;
}

debug('Embedded ECP send-message succeeded.', {
actionRequestId: payload.id,
requestId: pendingAction.requestId,
documentName: pendingAction.documentName,
streamId: pendingAction.streamId,
});
postToParent('share-success', {
requestId: pendingAction.requestId,
documentName: pendingAction.documentName,
streamId: pendingAction.streamId,
});
return;
}

return;
}

if (event.origin !== window.location.origin) {
return;
}

const data = event.data || {};
if (data.source !== 'wealth-client-chat-parent') {
return;
}

debug('Received parent message.', {
type: data.type,
payload: data.payload || {},
});

if (data.type === 'open-stream') {
openStream(data.payload && data.payload.streamId);
return;
}

if (data.type === 'send-message') {
try {
sendMessageToStream(
data.payload && data.payload.requestId,
data.payload && data.payload.documentName,
data.payload && data.payload.message,
data.payload && data.payload.streamId,
);
} catch (sendError) {
const message = sendError instanceof Error ? sendError.message : 'Unable to share document to client chat.';
warn('Unable to forward share request to embedded client chat.', {
error: message,
payload: data.payload || {},
});
postToParent('share-error', {
requestId: data.payload && data.payload.requestId,
documentName: data.payload && data.payload.documentName,
message: message,
});
}
}
});

debug('Initializing wealth client chat host.', {
ecpOrigin: ecpOrigin,
initialStreamId: initialStreamId,
partnerId: partnerId || DEFAULT_PARTNER_ID,
});
openStream(initialStreamId);
})();
</script>
</body>
</html>
Loading