Skip to content

Commit

Permalink
feat: Ability to add custom scripts to Livechat bundle (#31230)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Aleman <[email protected]>
  • Loading branch information
cabaceira and KevLehman authored Jan 24, 2024
1 parent c41b29e commit 718a7c0
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 4 deletions.
38 changes: 34 additions & 4 deletions apps/meteor/app/livechat/server/livechat.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
import url from 'url';

import jsdom from 'jsdom';
import mem from 'mem';
import { WebApp } from 'meteor/webapp';

import { settings } from '../../settings/server';
import { addServerUrlToIndex } from '../lib/Assets';

const indexHtmlWithServerURL = addServerUrlToIndex((await Assets.getTextAsync('livechat/index.html')) || '');

function parseExtraAttributes(widgetData: string): string {
const liveChatAdditionalScripts = settings.get<string>('Livechat_AdditionalWidgetScripts');
const additionalClass = settings.get<string>('Livechat_WidgetLayoutClasses');

if (liveChatAdditionalScripts == null || additionalClass == null) {
return widgetData;
}

const domParser = new jsdom.JSDOM(widgetData);
const doc = domParser.window.document;
const head = doc.querySelector('head');
const body = doc.querySelector('body');

liveChatAdditionalScripts.split(',').forEach((script) => {
const scriptElement = doc.createElement('script');
scriptElement.src = script;
body?.appendChild(scriptElement);
});

additionalClass.split(',').forEach((css) => {
const linkElement = doc.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = css;
head?.appendChild(linkElement);
});

return doc.documentElement.innerHTML;
}

const memoizedParseExtraAttributes = mem(parseExtraAttributes, { maxAge: process.env.TEST_MODE === 'true' ? 1 : 60000 });

WebApp.connectHandlers.use('/livechat', (req, res, next) => {
if (!req.url) {
return next();
Expand All @@ -18,24 +51,21 @@ WebApp.connectHandlers.use('/livechat', (req, res, next) => {
}

res.setHeader('content-type', 'text/html; charset=utf-8');

const domainWhiteListSetting = settings.get<string>('Livechat_AllowedDomainsList');
let domainWhiteList = [];
if (req.headers.referer && domainWhiteListSetting.trim()) {
domainWhiteList = domainWhiteListSetting.split(',').map((domain) => domain.trim());

const referer = url.parse(req.headers.referer);
if (referer.host && !domainWhiteList.includes(referer.host)) {
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
return next();
}

res.setHeader('Content-Security-Policy', `frame-ancestors ${referer.protocol}//${referer.host}`);
} else {
// TODO need to remove inline scripts from this route to be able to enable CSP here as well
res.removeHeader('Content-Security-Policy');
}

res.write(indexHtmlWithServerURL);
res.write(memoizedParseExtraAttributes(indexHtmlWithServerURL));
res.end();
});
26 changes: 26 additions & 0 deletions apps/meteor/ee/app/livechat-enterprise/server/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,32 @@ export const createSettings = async (): Promise<void> => {
});
});

await settingsRegistry.add('Livechat_AdditionalWidgetScripts', '', {
type: 'string',
group: 'Omnichannel',
section: 'Livechat',
enterprise: true,
invalidValue: '',
multiline: true,
i18nLabel: 'Livechat_AdditionalWidgetScripts',
i18nDescription: 'Livechat_AdditionalWidgetScripts_Description',
enableQuery: [omnichannelEnabledQuery],
modules: ['livechat-enterprise'],
});

await settingsRegistry.add('Livechat_WidgetLayoutClasses', '', {
type: 'string',
group: 'Omnichannel',
section: 'Livechat',
enterprise: true,
invalidValue: '',
multiline: true,
i18nLabel: 'Livechat_WidgetLayoutClasses',
i18nDescription: 'Livechat_WidgetLayoutClasses_Description',
enableQuery: [omnichannelEnabledQuery],
modules: ['livechat-enterprise'],
});

await settingsRegistry.add('Omnichannel_contact_manager_routing', true, {
type: 'boolean',
group: 'Omnichannel',
Expand Down
4 changes: 4 additions & 0 deletions apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3102,6 +3102,10 @@
"List_of_Direct_Messages": "List of Direct Messages",
"List_view": "List View",
"Livechat": "Livechat",
"Livechat_AdditionalWidgetScripts": "Livechat Widget Additional Scripts",
"Livechat_AdditionalWidgetScripts_Description": "Use this setting to add additional JS scripts to the widget bundle. You can add a list of scripts by separating them by commas, for example: `https://yourUrl/customScript1.js,https://yourUrl/customScript2.js`",
"Livechat_WidgetLayoutClasses": "Livechat Widget Additional CSS",
"Livechat_WidgetLayoutClasses_Description": "Use this setting to add additional CSS to the widget bundle. You can add a list of CSS files by separating them by commas, for example: `https://yourUrl/customFile1.css,https://yourUrl/customFile2.css`",
"Livechat_abandoned_rooms_action": "How to handle Visitor Abandonment",
"Livechat_abandoned_rooms_closed_custom_message": "Custom message when room is automatically closed by visitor inactivity",
"Livechat_agents": "Omnichannel agents",
Expand Down
30 changes: 30 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,4 +703,34 @@ describe('LIVECHAT - Utils', function () {
expect(body.messages[0]).to.have.property('t');
});
});

(IS_EE ? describe : describe.skip)('[EE] livechat widget', () => {
it('should include additional css when provided via Livechat_WidgetLayoutClasses setting', async () => {
await updateSetting('Livechat_WidgetLayoutClasses', 'http://my.css.com/my.css');
const x = await request.get('/livechat').expect(200);

expect(x.text.includes('http://my.css.com/my.css')).to.be.true;
});

it('should remove additional css when setting Livechat_WidgetLayoutClasses is empty', async () => {
await updateSetting('Livechat_WidgetLayoutClasses', '');
const x = await request.get('/livechat').expect(200);

expect(x.text.includes('http://my.css.com/my.css')).to.be.false;
});

it('should include additional js when provided via Livechat_AdditionalWidgetScripts setting', async () => {
await updateSetting('Livechat_AdditionalWidgetScripts', 'http://my.js.com/my.js');
const x = await request.get('/livechat').expect(200);

expect(x.text.includes('http://my.js.com/my.js')).to.be.true;
});

it('should remove additional js when setting Livechat_AdditionalWidgetScripts is empty', async () => {
await updateSetting('Livechat_AdditionalWidgetScripts', '');
const x = await request.get('/livechat').expect(200);

expect(x.text.includes('http://my.js.com/my.js')).to.be.false;
});
});
});

0 comments on commit 718a7c0

Please sign in to comment.