Skip to content

Commit

Permalink
Add support for Google Yolo (#1157)
Browse files Browse the repository at this point in the history
Co-authored-by: Chris Peyer <[email protected]>
Co-authored-by: Honwai Wong <[email protected]>
  • Loading branch information
3 people authored Aug 28, 2023
1 parent 43599e7 commit f56787b
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 5 deletions.
11 changes: 11 additions & 0 deletions libs/blocks/global-navigation/global-navigation.css
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,17 @@ header.global-navigation {
background-color: var(--feds-background-link--hover--light);;
}

#feds-googleLogin {
position: absolute;
top: 100%;
right: 0;
}

[dir = "rtl"] #feds-googleLogin {
left: 0;
right: auto;
}

/* Desktop styles */
@media (min-width: 900px) {
/* General */
Expand Down
53 changes: 53 additions & 0 deletions libs/features/google-login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const GOOGLE_SCRIPT = 'https://accounts.google.com/gsi/client';
const GOOGLE_ID = '530526366930-l874a90ipfkn26naa71r010u8epp39jt.apps.googleusercontent.com';
const PLACEHOLDER = 'feds-googleLogin';
const WRAPPER = 'feds-profile';

const onToken = async (getMetadata, data) => {
let destination;
try {
destination = new URL(getMetadata('google-login-redirect'))?.href;
} catch {
// Do nothing
}

await window.adobeIMS.socialHeadlessSignIn({
provider_id: 'google',
idp_token: data?.credential,
client_id: window.adobeid?.client_id,
scope: window.adobeid?.scope,
}).then(() => {
if (window.DISABLE_PAGE_RELOAD === true) return;
// Existing account
if (destination) {
window.location.assign(destination);
} else {
window.location.reload();
}
}).catch(() => {
// New account
window.adobeIMS.signInWithSocialProvider('google', { redirect_uri: destination || window.location.href });
});
};

export default async function initGoogleLogin(loadIms, getMetadata, loadScript) {
try {
await loadIms();
} catch {
return;
}
if (window.adobeIMS?.isSignedInUser()) return;

await loadScript(GOOGLE_SCRIPT);
const placeholder = document.createElement('div');
placeholder.id = PLACEHOLDER;
document.querySelector(`.${WRAPPER}`)?.append(placeholder);

window.google?.accounts?.id?.initialize({
client_id: GOOGLE_ID,
callback: (data) => onToken(getMetadata, data),
prompt_parent_id: PLACEHOLDER,
cancel_on_tap_outside: false,
});
window.google?.accounts?.id?.prompt();
}
11 changes: 11 additions & 0 deletions libs/scripts/delayed.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export const loadPrivacy = async (getConfig, loadScript) => {
}
});
};

export const loadGoogleLogin = async (getMetadata, loadIms, loadScript) => {
const googleLogin = getMetadata('google-login')?.toLowerCase();
if (googleLogin !== 'on' || window.adobeIMS?.isSignedInUser()) return;

const { default: initGoogleLogin } = await import('../features/google-login.js');
initGoogleLogin(loadIms, getMetadata, loadScript);
};

/**
* Executes everything that happens a lot later, without impacting the user experience.
*/
Expand All @@ -56,10 +65,12 @@ const loadDelayed = ([
getMetadata,
loadScript,
loadStyle,
loadIms,
], DELAY = 3000) => new Promise((resolve) => {
setTimeout(() => {
loadPrivacy(getConfig, loadScript);
loadJarvisChat(getConfig, getMetadata, loadScript, loadStyle);
loadGoogleLogin(getMetadata, loadIms, loadScript);
if (getMetadata('interlinks') === 'on') {
const { locale } = getConfig();
const path = `${locale.contentRoot}/keywords.json`;
Expand Down
2 changes: 1 addition & 1 deletion libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ export async function loadArea(area = document) {
initSidekick();

const { default: delayed } = await import('../scripts/delayed.js');
delayed([getConfig, getMetadata, loadScript, loadStyle]);
delayed([getConfig, getMetadata, loadScript, loadStyle, loadIms]);
}

// Load everything that can be deferred until after all blocks load.
Expand Down
86 changes: 86 additions & 0 deletions test/features/google-login/google-login.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import sinon from 'sinon';
import { expect } from '@esm-bundle/chai';
import { readFile } from '@web/test-runner-commands';
import initGoogleLogin from '../../../libs/features/google-login.js';

describe('Google Login', () => {
let initializeSpy;
let promptSpy;
beforeEach(async () => {
document.body.innerHTML = await readFile({ path: './mocks/google-login.html' });
window.google = window.google || {
accounts: {
id: {
initialize: () => {},
prompt: () => {},
},
},
};
initializeSpy = sinon.spy(window.google.accounts.id, 'initialize');
promptSpy = sinon.spy(window.google.accounts.id, 'prompt');
window.adobeid = {
client_id: 'milo',
scope: 'gnav',
};
});

afterEach(() => {
document.body.innerHTML = '';
initializeSpy.restore();
promptSpy.restore();
delete window.adobeid;
delete window.google;
});

it('should create a placeholder to inject DOM markup', async () => {
await initGoogleLogin(sinon.stub(), sinon.stub(), sinon.stub());
expect(document.getElementById('feds-googleLogin')).to.exist;
});

it('should initialize and render the login element', async () => {
await initGoogleLogin(sinon.stub(), sinon.stub(), sinon.stub());
expect(initializeSpy.called).to.be.true;
expect(promptSpy.called).to.be.true;
expect(initializeSpy.getCall(0).args[0].prompt_parent_id).to.equal('feds-googleLogin');
});

it('should exchange tokens with adobeIMS after login', async () => {
window.adobeIMS = window.adobeIMS || {
socialHeadlessSignIn: () => {},
isSignedInUser: () => false,
signInWithSocialProvider: () => {},
};

// No account
const socialHeadlessSignInStub = sinon.stub(window.adobeIMS, 'socialHeadlessSignIn')
.returns(new Promise((resolve, reject) => { reject(); }));
const signInWithSocialProviderSpy = sinon.spy(window.adobeIMS, 'signInWithSocialProvider');
await initGoogleLogin(sinon.stub(), sinon.stub(), sinon.stub());
let onToken = initializeSpy.getCall(0).args[0].callback;
await onToken(sinon.stub(), sinon.stub());
expect(signInWithSocialProviderSpy.called).to.be.true;

// Existing account
socialHeadlessSignInStub.returns(new Promise((resolve) => { resolve(); }));
await initGoogleLogin(sinon.stub(), sinon.stub(), sinon.stub());
onToken = initializeSpy.getCall(0).args[0].callback;
window.DISABLE_PAGE_RELOAD = true;
signInWithSocialProviderSpy.resetHistory();
await onToken(sinon.stub(), sinon.stub());
expect(signInWithSocialProviderSpy.called).not.to.be.true;
expect(document.getElementById('feds-googleLogin')).to.exist;
signInWithSocialProviderSpy.restore();
socialHeadlessSignInStub.restore();
delete window.DISABLE_PAGE_RELOAD;
});

it('should not initialize if IMS is not ready or user is already logged-in', async () => {
window.adobeIMS = window.adobeIMS || { isSignedInUser: () => {} };
const loggedInStub = sinon.stub(window.adobeIMS, 'isSignedInUser').returns(() => true);
await initGoogleLogin(sinon.stub(), sinon.stub(), sinon.stub());
expect(document.getElementById('feds-googleLogin')).not.to.exist;
await initGoogleLogin(Promise.reject, sinon.stub(), sinon.stub());
expect(document.getElementById('feds-googleLogin')).not.to.exist;
loggedInStub.restore();
});
});
1 change: 1 addition & 0 deletions test/features/google-login/mocks/google-login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="feds-profile"></div>
22 changes: 18 additions & 4 deletions test/scripts/delayed.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import sinon from 'sinon';
import { expect } from '@esm-bundle/chai';
import loadDelayed, { loadPrivacy, loadJarvisChat } from '../../libs/scripts/delayed.js';
import { getMetadata, getConfig, setConfig, loadScript, loadStyle } from '../../libs/utils/utils.js';
import loadDelayed, { loadPrivacy, loadJarvisChat, loadGoogleLogin } from '../../libs/scripts/delayed.js';
import { getMetadata, getConfig, setConfig, loadIms } from '../../libs/utils/utils.js';

describe('Delayed', () => {
const loadScript = sinon.stub().returns(() => new Promise((resolve) => { resolve(); }));
const loadStyle = sinon.stub().returns(() => new Promise((resolve) => { resolve(); }));

it('should load privacy feature', async () => {
expect(loadPrivacy).to.exist;
setConfig({ privacyId: '7a5eb705-95ed-4cc4-a11d-0cc5760e93db' });
Expand All @@ -21,14 +24,25 @@ describe('Delayed', () => {
tag.setAttribute('content', 'on');
document.head.appendChild(tag);
expect(loadJarvisChat).to.exist;
window.AdobeMessagingExperienceClient = { initialize: sinon.stub() };
window.adobePrivacy = { activeCookieGroups: sinon.stub() };
loadJarvisChat(getConfig, getMetadata, loadScript, loadStyle);
document.head.removeChild(tag);
});

it('should load google login feature', async () => {
const tag = document.createElement('meta');
tag.setAttribute('name', 'google-login');
tag.setAttribute('content', 'on');
document.head.appendChild(tag);
expect(loadGoogleLogin).to.exist;
await loadGoogleLogin(getMetadata, loadIms, loadScript);
});

it('should load interlinks logic', async () => {
const clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
document.querySelector('head')?.insertAdjacentHTML('beforeend', '<meta name="interlinks" content="on">');
loadDelayed([getConfig, getMetadata, loadScript, loadStyle]).then((module) => {
loadDelayed([getConfig, getMetadata, loadScript, loadStyle, loadIms]).then((module) => {
expect(module).to.exist;
expect(typeof module === 'object').to.equal(true);
});
Expand All @@ -39,7 +53,7 @@ describe('Delayed', () => {
it('should skip load interlinks logic when metadata is off', async () => {
const clock = sinon.useFakeTimers({ toFake: ['setTimeout'] });
document.head.querySelector('meta[name="interlinks"]')?.remove();
loadDelayed([getConfig, getMetadata, loadScript, loadStyle]).then((module) => {
loadDelayed([getConfig, getMetadata, loadScript, loadStyle, loadIms]).then((module) => {
expect(module == null).to.equal(true);
});
await clock.runAllAsync();
Expand Down

0 comments on commit f56787b

Please sign in to comment.