Skip to content

Commit

Permalink
Merge pull request #63 from Shopify/sfr-token
Browse files Browse the repository at this point in the history
Add support for SFR Identity authentication
  • Loading branch information
macournoyer authored Oct 2, 2020
2 parents 42fd178 + 6714ae4 commit 2600b96
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 57 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Change Log


## v1.1.0 (Oct 2, 2020)
* [#63](https://github.com/Shopify/shopify-theme-inspector/pull/63) Add storefront-renderer support

## v1.0.7 (June 4, 2020)
* [#54](https://github.com/Shopify/shopify-theme-inspector/pull/54) Add shopify employee scope

Expand Down
71 changes: 43 additions & 28 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {env} from './env';
import {env, RenderBackend} from './env';
import {isDev, Oauth2} from './utils';

const DEVTOOLS_SCOPE = 'https://api.shopify.com/auth/shop.storefront.devtools';
const COLLABORATORS_SCOPE =
'https://api.shopify.com/auth/partners.collaborator-relationships.readonly';
let shopifyEmployee = false;
let renderBackend = RenderBackend.StorefrontRenderer;

function getOauth2Client(origin: string) {
const identityDomain = isDev(origin)
Expand All @@ -13,19 +13,18 @@ function getOauth2Client(origin: string) {
const clientId = isDev(origin)
? env.DEV_OAUTH2_CLIENT_ID
: env.OAUTH2_CLIENT_ID;
const subjectId = isDev(origin)
? env.DEV_OAUTH2_SUBJECT_ID
: env.OAUTH2_SUBJECT_ID;
const clientAuthParams = [
[
'scope',
`openid profile ${
shopifyEmployee === true ? 'employee' : ''
} ${DEVTOOLS_SCOPE} ${COLLABORATORS_SCOPE}`,
} ${Object.values(env.DEVTOOLS_SCOPE).join(' ')} ${COLLABORATORS_SCOPE}`,
],
];

return new Oauth2(clientId, subjectId, identityDomain, {clientAuthParams});
return new Oauth2(clientId, identityDomain, {
clientAuthParams,
});
}

// Change icon from colored to greyscale depending on whether or not Shopify has
Expand All @@ -48,12 +47,17 @@ function setIconAndPopup(active: string, tabId: number) {
chrome.pageAction.show(tabId);
}

function getSubjectId(oauth: Oauth2, origin: string) {
if (isDev(origin)) {
return oauth.fetchClientId(env.DEV_OAUTH2_SUBJECT_NAME[renderBackend]);
}
return Promise.resolve(env.OAUTH2_SUBJECT_ID[renderBackend]);
}

chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
if (type !== 'signOut') return false;

const oauth2 = getOauth2Client(origin);

oauth2
getOauth2Client(origin)
.logoutUser()
.then(() => {
sendResponse();
Expand Down Expand Up @@ -85,6 +89,9 @@ chrome.runtime.onMessage.addListener((event, sender) => {
chrome.runtime.onMessage.addListener((event, sender) => {
if (sender.tab && sender.tab.id && event.type === 'detect-shopify') {
setIconAndPopup(event.hasDetectedShopify, sender.tab.id);
renderBackend = event.isCore
? RenderBackend.Core
: RenderBackend.StorefrontRenderer;
}
});

Expand All @@ -95,9 +102,7 @@ chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
return false;
}

const oauth2 = getOauth2Client(origin);

oauth2
getOauth2Client(origin)
.authenticate()
.then(() => {
sendResponse({success: true});
Expand All @@ -111,26 +116,31 @@ chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
});

// Listen for 'request-core-access-token' event and respond to the messenger
// with a valid Shopify Core access token. This may trigger a login popup window
// if needed.
// with a valid access token. This may trigger a login popup window if needed.
chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
if (type !== 'request-core-access-token') {
return false;
}

const oauth2 = getOauth2Client(origin);
const params = [
[
'scope',
`${
shopifyEmployee === true ? 'employee' : ''
} ${DEVTOOLS_SCOPE} ${COLLABORATORS_SCOPE}`,
`${shopifyEmployee === true ? 'employee' : ''} ${
env.DEVTOOLS_SCOPE[renderBackend]
} ${COLLABORATORS_SCOPE}`,
],
];
const destination = `${origin}/admin`;

oauth2
.getSubjectAccessToken(destination, params)
// SFR does not need a destination.
const destination =
renderBackend === RenderBackend.Core ? `${origin}/admin` : '';

const oauth = getOauth2Client(origin);

getSubjectId(oauth, origin)
.then(subjectId => {
return oauth.getSubjectAccessToken(destination, subjectId, params);
})
.then(token => {
sendResponse({token});
})
Expand All @@ -146,9 +156,7 @@ chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
if (type !== 'request-user-name') return false;

const oauth2 = getOauth2Client(origin);

oauth2
getOauth2Client(origin)
.getUserInfo()
.then(userInfo => {
const name = userInfo.given_name;
Expand All @@ -166,9 +174,7 @@ chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {
if (type !== 'request-auth-status') return false;

const oauth2 = getOauth2Client(origin);

oauth2
getOauth2Client(origin)
.hasValidClientToken()
.then(isLoggedIn => {
sendResponse({isLoggedIn});
Expand All @@ -179,3 +185,12 @@ chrome.runtime.onMessage.addListener(({type, origin}, _, sendResponse) => {

return true;
});

chrome.runtime.onMessage.addListener(({type}, _, sendResponse) => {
if (type !== 'request-rendering-backend') return false;

const name = renderBackend.toString();
sendResponse({name});

return true;
});
41 changes: 27 additions & 14 deletions src/detectShopify.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
// Use regex on document to test for a shopify site
// Look for a DOM script element that contains
// "Shopify.shop ="
// This is auto-generated from content_for_header
const findShopifyScript = Array.from(
document.querySelectorAll('script'),
).filter(script => {
return /Shopify\.shop =/.test(script.textContent || '');
});
// Use regex on document to test for a Shopify site
// Look for a DOM script element that contains `Shopify.shop =`.
// And look for `BOOMR.application = "core"`, only present on core to detect the
// back-end that rendered this page.
// Both are generated by {{content_for_header}}.
let hasDetectedShopify = false;
let isCore = false;

if (findShopifyScript.length) {
chrome.runtime.sendMessage({
type: 'detect-shopify',
hasDetectedShopify: true,
});
const scripts = document.querySelectorAll('script');
for (let i = 0; i < scripts.length; i++) {
// Short-circuit if we have found everything we need
if (isCore && hasDetectedShopify) break;

const content = scripts[i].textContent;
if (typeof content === 'string') {
if (/Shopify\.shop\s*=/.test(content)) {
hasDetectedShopify = true;
}
if (/BOOMR\.application\s*=\s*"core"/.test(content)) {
isCore = true;
}
}
}

chrome.runtime.sendMessage({
type: 'detect-shopify',
hasDetectedShopify,
isCore,
});

if (document.location.search.includes('shopify_employee')) {
chrome.runtime.sendMessage({
type: 'detect-shopify-employee',
Expand Down
1 change: 1 addition & 0 deletions src/devtools.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h2>This page cannot be profiled</h2>
<div data-flamegraph-wrapper class="hide">
<div data-flamegraph-container class="chart"></div>
<p data-total-time class="total-time">Total time to render liquid: <b>-</b></p>
<p data-rendering-backend class="rendering-backend">Rendering back-end: <b>-</b></p>

<div data-detailed-info class="detailed-info">
<p data-partial>File: <b>-</b></p>
Expand Down
18 changes: 18 additions & 0 deletions src/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import LiquidFlamegraph from './components/liquid-flamegraph';
import {
getProfileData,
setTotalTime,
setRenderingBackend,
getBrowserTheme,
emptyHTMLNode,
} from './utils';
Expand Down Expand Up @@ -90,6 +91,20 @@ function getInspectedWindowURL(): Promise<URL> {
});
}

function getRenderingBackend(): Promise<string> {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{type: 'request-rendering-backend'},
({name, error}) => {
if (error) {
return reject(error);
}
return resolve(name);
},
);
});
}

async function refreshPanel() {
emptyHTMLNode(document.querySelector(selectors.initialMessage));
document
Expand Down Expand Up @@ -117,10 +132,13 @@ async function refreshPanel() {
url,
);

const renderingBackend = await getRenderingBackend();

// All events happening here are synchronous. The set timeout is for UI
// purposes so that timing information gets displayed after the flamegraph is shown.
setTimeout(function() {
setTotalTime(profile.value);
setRenderingBackend(renderingBackend);
}, 300);

document
Expand Down
24 changes: 20 additions & 4 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
export enum RenderBackend {
Core = 'core',
StorefrontRenderer = 'storefront-renderer',
}

export const env = {
OAUTH2_DOMAIN: 'accounts.shopify.com',
DEV_OAUTH2_DOMAIN: 'identity.myshopify.io',
OAUTH2_CLIENT_ID: 'ff2a91a2-6854-449e-a37d-c03bcd181126',
DEV_OAUTH2_CLIENT_ID: '1d7f695c-42e2-493a-a6dc-be12d4117d58',
OAUTH2_SUBJECT_ID:
'7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c',
DEV_OAUTH2_SUBJECT_ID:
'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52',
OAUTH2_SUBJECT_ID: {
[RenderBackend.Core]:
'7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c',
[RenderBackend.StorefrontRenderer]: 'ee139b3d-5861-4d45-b387-1bc3ada7811c',
},
DEV_OAUTH2_SUBJECT_NAME: {
[RenderBackend.Core]: 'shopify-development',
[RenderBackend.StorefrontRenderer]: 'storefront-renderer-development',
},
DEVTOOLS_SCOPE: {
[RenderBackend.Core]:
'https://api.shopify.com/auth/shop.storefront.devtools',
[RenderBackend.StorefrontRenderer]:
'https://api.shopify.com/auth/shop.storefront-renderer.devtools',
},
OAUTH_LOCAL_STORAGE_KEY: 'shopifyDevToolsAuthResults',
};
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Shopify Theme Inspector for Chrome",
"version": "1.0.7",
"version": "1.1.0",
"description": "Profile and debug Liquid template on your Shopify store",
"devtools_page": "devtools.html",
"permissions": ["storage", "identity", "activeTab"],
Expand Down
2 changes: 1 addition & 1 deletion src/styles/devtools.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ body {
margin: 20px;
}

.total-time {
.total-time, .rendering-backend {
margin: 20px;
font-size: 13px;
}
Expand Down
4 changes: 4 additions & 0 deletions src/utils/domHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export function setTotalTime(totalTime: number) {
updateInfoText('[data-total-time]', `${Math.trunc(totalTime * 1000)}ms`);
}

export function setRenderingBackend(backend: string) {
updateInfoText('[data-rendering-backend]', backend);
}

export function formatNodeTime(nodeTime: number) {
const nodeTimeMs = Math.trunc(nodeTime * 1000);
if (nodeTimeMs > 0) {
Expand Down
5 changes: 4 additions & 1 deletion src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const IS_CHROME = navigator.userAgent.indexOf('Firefox') < 0;

export function isDev(origin: string): boolean {
return origin.includes('shop1.myshopify');
return (
origin.includes('shop1.myshopify') ||
origin.includes('shop1-fast.myshopify')
);
}

export function getThemeId() {
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {getProfileData} from './getProfileData';
export {isDev, getThemeId, getBrowserTheme} from './helpers';
export {
setTotalTime,
setRenderingBackend,
formatNodeTime,
emptyHTMLNode,
updateInfoText,
Expand Down
Loading

0 comments on commit 2600b96

Please sign in to comment.