Skip to content

Commit

Permalink
Add JWT support (#754)
Browse files Browse the repository at this point in the history
Add JWT support which is enabled when useJWT is true in global config

For iframe experiences, add iframe-jwt.js (along with prod and staging versions) which provide an `initAnswersFrameJWT` function. The answers experience will load once `initAnswersFrameJWT` is called.

For non-iframe experiences, `initAnswersJWT` must be called rather than `initAnswersFrameJWT`

The next PR will ensure that the apiKey from the jambo injected data will not appear in the output of any builds.

This PR does not include JWT overlay support.

J=SLAP-1118
TEST=manual

Test iframe-jwt.js and see that the page doesn't load until initAnswersFrame is ran. Test supplying valid and invalid tokens. Test navigating between pages and refreshing the page. Test that if the answers experience with `useJWT=true` is loaded directly (not through the iframe), and error message appears in the console. Smoke test the non-iframe experience and the the overlay integration with `useJWT=false`.  Test JWT locally and on SGS. Test in a cross-domain manner with a local test site which points to an answers experience on SGS which is running this branch. On SGS, test iframe-jwt-prod.js and iframe-jwt-staging.js

Test JWT on non-iframe experiences.
  • Loading branch information
cea2aj authored May 7, 2021
1 parent 1c9fa6d commit abea750
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 7 deletions.
1 change: 1 addition & 0 deletions global_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// "apiKey": "<REPLACE ME>", // The answers api key found on the experiences page. This will be provided automatically by the Yext CI system
// "experienceVersion": "<REPLACE ME>", // the Answers Experience version to use for API requests. This will be provided automatically by the Yext CI system
// "businessId": "<REPLACE ME>", // The business ID of the account. This will be provided automatically by the Yext CI system
// "useJWT": true, // Whether or not to enable JWT. If true, the apiKey will be ignored and initAnswersJWT(token) or initAnswersFrameJWT(token) must be called.
"logo": "", // The link to the logo for open graph meta tag - og:image.
"favicon": "",
"googleTagManagerName": "dataLayer", // The name of your Google Tag Manager data layer
Expand Down
11 changes: 10 additions & 1 deletion layouts/html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@
window.iframeLoaded = new Promise(resolve => {
iframeLoadedResolve = resolve;
});
window.answersJWT = new Promise(resolve => {
window.initAnswersJWT = resolve;
});
window.iFrameResizer = {
onReady: function() {
window.parentIFrame.sendMessage(JSON.stringify({
Expand All @@ -132,7 +135,13 @@
}));
iframeLoadedResolve();
},
onMessage: window.isOverlay ? window.Overlay.onMessage : function() {},
onMessage: (message) => {
if (message.token) {
initAnswersJWT(message.token);
return;
}
window.isOverlay && window.Overlay.onMessage(message);
}
};
{{/babel}}
</script>
Expand Down
30 changes: 27 additions & 3 deletions script/core.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
const IS_STAGING = HitchhikerJS.isStaging(JAMBO_INJECTED_DATA?.pages?.stagingDomains || []);
const injectedConfig = {
experienceVersion: IS_STAGING ? 'STAGING' : 'PRODUCTION',
apiKey: HitchhikerJS.getInjectedProp('{{{global_config.experienceKey}}}', ['apiKey']),
{{#unless global_config.useJWT}}
apiKey: HitchhikerJS.getInjectedProp('{{{global_config.experienceKey}}}', ['apiKey']),
{{/unless}}
{{#with env.JAMBO_INJECTED_DATA}}
{{#if businessId}}businessId: "{{businessId}}",{{/if}}
{{/with}}
Expand All @@ -28,10 +30,32 @@
{{/if}}
{{/with}}
};
{{#if global_config.useJWT}}
const jwtNotProvidedTimeout = setTimeout(() => {
console.warn(
'A JWT has not been received within 5 seconds of page load, and "useJWT" is set to true.\n' +
'Load the experience by calling initAnswersJWT(token).'
);
}, 5000);
window.answersJWT.then(token => {
clearTimeout(jwtNotProvidedTimeout);
initAnswersInstance({
...injectedConfig,
...userConfig,
apiKey: token
});
});
{{else}}
initAnswersInstance({
...injectedConfig,
...userConfig
});
{{/if}}
}
function initAnswersInstance (config) {
ANSWERS.init({
templateBundle: TemplateBundle.default,
...injectedConfig,
...userConfig,
...config,
querySource: window.isOverlay ? 'OVERLAY' : 'STANDARD',
onStateChange: (objParams, stringParams, replaceHistory) => {
if ('parentIFrame' in window) {
Expand Down
7 changes: 6 additions & 1 deletion static/js/iframe-common.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require('iframe-resizer');

export function generateIFrame(domain, queryParam, urlParam) {
export function generateIFrame(domain, queryParam, urlParam, token) {
var isLocalHost = window.location.host.split(':')[0] === 'localhost';
var containerEl = document.querySelector('#answers-container');
var iframe = document.createElement('iframe');
Expand Down Expand Up @@ -82,6 +82,11 @@ export function generateIFrame(domain, queryParam, urlParam) {
// For dynamic iFrame resizing
iFrameResize({
checkOrigin: false,
onInit: function(iframe) {
token && iframe.iFrameResizer.sendMessage({
token: token
});
},
onMessage: function(messageData) {
const message = JSON.parse(messageData.message);
if (message.action === "paginate") {
Expand Down
11 changes: 11 additions & 0 deletions static/js/iframe-jwt-prod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generateIFrame } from './iframe-common';
import InjectedData from './models/InjectedData';
import getJwtNotProvidedTimeout from './utils/getJwtNotProvidedTimeout';

const prodDomain = new InjectedData().getProdDomain();
const jwtNotProvidedTimeout = getJwtNotProvidedTimeout();

window.initAnswersFrameJWT = function (token) {
clearTimeout(jwtNotProvidedTimeout);
generateIFrame(prodDomain, null, null, token);
}
11 changes: 11 additions & 0 deletions static/js/iframe-jwt-staging.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generateIFrame } from './iframe-common';
import InjectedData from './models/InjectedData';
import getJwtNotProvidedTimeout from './utils/getJwtNotProvidedTimeout';

const stagingDomain = new InjectedData().getStagingDomain();
const jwtNotProvidedTimeout = getJwtNotProvidedTimeout();

window.initAnswersFrameJWT = function (token) {
clearTimeout(jwtNotProvidedTimeout);
generateIFrame(stagingDomain, null, null, token);
}
11 changes: 11 additions & 0 deletions static/js/iframe-jwt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generateIFrame } from './iframe-common';
import InjectedData from './models/InjectedData';
import getJwtNotProvidedTimeout from './utils/getJwtNotProvidedTimeout';

const domain = new InjectedData().getDomain();
const jwtNotProvidedTimeout = getJwtNotProvidedTimeout();

window.initAnswersFrameJWT = function (token) {
clearTimeout(jwtNotProvidedTimeout);
generateIFrame(domain, null, null, token);
}
8 changes: 8 additions & 0 deletions static/js/utils/getJwtNotProvidedTimeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function getJwtNotProvidedTimeout () {
return setTimeout(() => {
console.warn(
'A JWT has not been received within 5 seconds of page load, and "useJWT" is set to true.\n' +
'Load the experience by calling initAnswersFrameJWT(token).'
);
}, 5000);
}
3 changes: 3 additions & 0 deletions static/webpack-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ module.exports = function () {
'HitchhikerJS': `./${jamboConfig.dirs.output}/static/entry.js`,
'HitchhikerCSS': `./${jamboConfig.dirs.output}/static/css-entry.js`,
'iframe': `./${jamboConfig.dirs.output}/static/js/iframe.js`,
'iframe-jwt': `./${jamboConfig.dirs.output}/static/js/iframe-jwt.js`,
'iframe-jwt-prod': `./${jamboConfig.dirs.output}/static/js/iframe-jwt-prod.js`,
'iframe-jwt-staging': `./${jamboConfig.dirs.output}/static/js/iframe-jwt-staging.js`,
'answers': `./${jamboConfig.dirs.output}/static/js/iframe.js`,
'overlay-button': `./${jamboConfig.dirs.output}/static/js/overlay/button-frame/entry.js`,
'overlay': `./${jamboConfig.dirs.output}/static/js/overlay/parent-frame/yxtanswersoverlay.js`,
Expand Down
1 change: 1 addition & 0 deletions test-site/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
!config/index.json
!pages/index.html.hbs
!public/iframe_test.html
!public/iframe_jwt_test.html
!public/overlay.html
public/
config/
Expand Down
5 changes: 3 additions & 2 deletions test-site/jambo.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"directanswercards"
],
"preservedFiles": [
"public/overlay.html",
"public/iframe_test.html"
"public/iframe_test.html",
"public/iframe_jwt_test.html",
"public/overlay.html"
]
},
"defaultTheme": "answers-hitchhiker-theme"
Expand Down
1 change: 1 addition & 0 deletions test-site/public/iframe_jwt_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html><head><meta name="viewport" content="initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,width=device-width"></head><body><div id="answers-container"></div><script src="iframe-jwt.js"></script></body></html>

0 comments on commit abea750

Please sign in to comment.