-
Notifications
You must be signed in to change notification settings - Fork 145
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
@W-14016114: Move Storefront Preview Set up to the SDK (#1430)
* Move storefront preview set up into the SDK
- Loading branch information
Showing
7 changed files
with
285 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
packages/pwa-kit-react-sdk/src/ssr/universal/components/storefront-preview/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) 2023, Salesforce, Inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
import React, {useEffect} from 'react' | ||
import PropTypes from 'prop-types' | ||
import {Helmet} from 'react-helmet' | ||
import {detectStorefrontPreview, getClientScript} from './utils' | ||
|
||
/** | ||
* | ||
* @param {boolean} enabled - flag to turn on/off Storefront Preview feature | ||
* @param {{function():string}} getToken - a STOREFRONT_PREVIEW customised function that fetches token of storefront | ||
*/ | ||
export const StorefrontPreview = ({enabled = true, getToken}) => { | ||
let isHostTrusted | ||
useEffect(() => { | ||
if (enabled && isHostTrusted) { | ||
window.STOREFRONT_PREVIEW = { | ||
...window.STOREFRONT_PREVIEW, | ||
getToken | ||
} | ||
} | ||
}, [enabled, getToken]) | ||
if (!enabled) { | ||
return null | ||
} | ||
// We only want to run this function when enabled is on | ||
isHostTrusted = detectStorefrontPreview() | ||
return isHostTrusted ? ( | ||
<Helmet> | ||
<script | ||
id="storefront_preview" | ||
src={getClientScript()} | ||
async | ||
type="text/javascript" | ||
></script> | ||
</Helmet> | ||
) : null | ||
} | ||
|
||
StorefrontPreview.propTypes = { | ||
enabled: PropTypes.bool, | ||
getToken: PropTypes.func | ||
} |
82 changes: 82 additions & 0 deletions
82
packages/pwa-kit-react-sdk/src/ssr/universal/components/storefront-preview/index.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright (c) 2023, Salesforce, Inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
import React from 'react' | ||
import {render, waitFor} from '@testing-library/react' | ||
import {StorefrontPreview} from './index' | ||
import {detectStorefrontPreview} from './utils' | ||
import {Helmet} from 'react-helmet' | ||
|
||
jest.mock('./utils', () => { | ||
const origin = jest.requireActual('./utils') | ||
return { | ||
...origin, | ||
detectStorefrontPreview: jest.fn() | ||
} | ||
}) | ||
describe('Storefront Preview Component', function () { | ||
const oldWindow = window | ||
|
||
beforeEach(() => { | ||
// eslint-disable-next-line | ||
window = {...oldWindow} | ||
}) | ||
|
||
afterEach(() => { | ||
// eslint-disable-next-line | ||
window = oldWindow | ||
}) | ||
test('not renders nothing when enabled is off', async () => { | ||
render(<StorefrontPreview enabled={false} />) | ||
const helmet = Helmet.peek() | ||
await waitFor(() => { | ||
expect(helmet).toBeUndefined() | ||
}) | ||
}) | ||
test('renders script tag when enabled is on', async () => { | ||
detectStorefrontPreview.mockReturnValue(true) | ||
|
||
render(<StorefrontPreview enabled={true} />) | ||
// this will return all the markup assigned to helmet | ||
// which will get rendered inside head. | ||
const helmet = Helmet.peek() | ||
await waitFor(() => { | ||
expect(helmet.scriptTags[0].src).toBe( | ||
'https://runtime.commercecloud.com/cc/b2c/preview/preview.client.js' | ||
) | ||
expect(helmet.scriptTags[0].async).toBe(true) | ||
expect(helmet.scriptTags[0].type).toBe('text/javascript') | ||
}) | ||
}) | ||
|
||
test('renders script tag when window.STOREFRONT_PREVIEW.enabled is on', async () => { | ||
delete window.STOREFRONT_PREVIEW | ||
window.STOREFRONT_PREVIEW = {} | ||
window.STOREFRONT_PREVIEW.enabled = true | ||
detectStorefrontPreview.mockReturnValue(true) | ||
|
||
render(<StorefrontPreview />) | ||
// this will return all the markup assigned to helmet | ||
// which will get rendered inside head. | ||
const helmet = Helmet.peek() | ||
await waitFor(() => { | ||
expect(helmet.scriptTags[0].src).toBe( | ||
'https://runtime.commercecloud.com/cc/b2c/preview/preview.client.js' | ||
) | ||
expect(helmet.scriptTags[0].async).toBe(true) | ||
expect(helmet.scriptTags[0].type).toBe('text/javascript') | ||
}) | ||
}) | ||
|
||
test('getToken is defined in window.STOREFRONT_PREVIEW when it is defined', async () => { | ||
window.STOREFRONT_PREVIEW = {} | ||
window.STOREFRONT_PREVIEW.enabled = true | ||
detectStorefrontPreview.mockReturnValue(true) | ||
|
||
render(<StorefrontPreview getToken={() => 'my-token'} />) | ||
expect(window.STOREFRONT_PREVIEW.getToken).toBeDefined() | ||
}) | ||
}) |
48 changes: 48 additions & 0 deletions
48
packages/pwa-kit-react-sdk/src/ssr/universal/components/storefront-preview/utils.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) 2023, Salesforce, Inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
/** Origins that are allowed to run Storefront Preview. */ | ||
const TRUSTED_ORIGINS = [ | ||
'https://runtime.commercecloud.com', | ||
'https://runtime-admin-staging.mobify-storefront.com', | ||
'https://runtime-admin-preview.mobify-storefront.com' | ||
] | ||
|
||
/** Origin used for local Runtime Admin. */ | ||
const DEVELOPMENT_ORIGIN = 'http://localhost:4000' | ||
|
||
/** Detects whether the storefront is running in an iframe. */ | ||
const detectInIframe = () => typeof window !== 'undefined' && window.parent !== window.self | ||
|
||
/** Gets the parent origin when running in an iframe. */ | ||
export const getParentOrigin = () => { | ||
if (detectInIframe()) { | ||
if (window.location.ancestorOrigins) return window.location.ancestorOrigins[0] | ||
// ancestorOrigins does not exist in Firefox, so we use referrer as a fallback | ||
if (document.referrer) return new URL(document.referrer).origin | ||
} | ||
} | ||
|
||
const isParentOriginTrusted = (parentOrigin) => { | ||
return window.location.hostname === 'localhost' | ||
? parentOrigin === DEVELOPMENT_ORIGIN // Development | ||
: TRUSTED_ORIGINS.includes(parentOrigin) // Production | ||
} | ||
|
||
/** Detects whether the storefront is running in an iframe as part of Storefront Preview. */ | ||
export const detectStorefrontPreview = () => { | ||
const parentOrigin = getParentOrigin() | ||
return Boolean(parentOrigin) && isParentOriginTrusted(parentOrigin) | ||
} | ||
|
||
/** Returns the URL to load the Storefront Preview client script from the parent origin. */ | ||
export const getClientScript = () => { | ||
const parentOrigin = getParentOrigin() ?? 'https://runtime.commercecloud.com' | ||
return parentOrigin === DEVELOPMENT_ORIGIN | ||
? `${parentOrigin}/mobify/bundle/development/static/storefront-preview.js` | ||
: `${parentOrigin}/cc/b2c/preview/preview.client.js` | ||
} |
91 changes: 91 additions & 0 deletions
91
packages/pwa-kit-react-sdk/src/ssr/universal/components/storefront-preview/utils.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright (c) 2023, Salesforce, Inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
import {getClientScript, getParentOrigin, detectStorefrontPreview} from './utils' | ||
|
||
describe('getClientScript', function () { | ||
const oldWindow = window | ||
|
||
beforeEach(() => { | ||
// eslint-disable-next-line | ||
window = {...oldWindow} | ||
}) | ||
|
||
afterEach(() => { | ||
// eslint-disable-next-line | ||
window = oldWindow | ||
}) | ||
test('returns client script src with prod origin', () => { | ||
const src = getClientScript() | ||
expect(src).toBe('https://runtime.commercecloud.com/cc/b2c/preview/preview.client.js') | ||
}) | ||
|
||
test('returns client script src with localhost origin', () => { | ||
// Delete the real properties from window so we can mock them | ||
delete window.parent | ||
|
||
window.parent = {} | ||
window.location.ancestorOrigins = ['http://localhost:4000'] | ||
const src = getClientScript() | ||
|
||
expect(src).toBe( | ||
'http://localhost:4000/mobify/bundle/development/static/storefront-preview.js' | ||
) | ||
}) | ||
}) | ||
|
||
describe('getParentOrigin', function () { | ||
test('returns origin from ancestorOrigins', () => { | ||
// Delete the real properties from window so we can mock them | ||
delete window.parent | ||
|
||
window.parent = {} | ||
const localHostOrigin = 'http://localhost:4000' | ||
window.location.ancestorOrigins = [localHostOrigin] | ||
const origin = getParentOrigin() | ||
|
||
expect(origin).toBe(localHostOrigin) | ||
}) | ||
test('returns origin from document.referrer', () => { | ||
// Delete the real properties from window so we can mock them | ||
delete window.parent | ||
|
||
window.parent = {} | ||
delete window.location.ancestorOrigins | ||
const localHostOrigin = 'http://localhost:4000' | ||
jest.spyOn(document, 'referrer', 'get').mockReturnValue(localHostOrigin) | ||
const origin = getParentOrigin() | ||
|
||
expect(origin).toBe(localHostOrigin) | ||
}) | ||
}) | ||
describe('detectStorefrontPreview', function () { | ||
test('returns true for trusted origin', () => { | ||
// Delete the real properties from window so we can mock them | ||
delete window.parent | ||
delete window.location | ||
|
||
window.parent = {} | ||
window.location = {} | ||
window.location.hostname = 'localhost' | ||
const localHostOrigin = 'http://localhost:4000' | ||
window.location.ancestorOrigins = [localHostOrigin] | ||
expect(detectStorefrontPreview()).toBe(true) | ||
}) | ||
test('returns false for non-trusted origin', () => { | ||
// Delete the real properties from window so we can mock them | ||
delete window.parent | ||
delete window.location | ||
|
||
window.parent = {} | ||
window.location = {} | ||
window.location.hostname = 'localhost' | ||
const localHostOrigin = 'http://localhost:4000' | ||
window.location.ancestorOrigins = [localHostOrigin] | ||
expect(detectStorefrontPreview()).toBe(true) | ||
}) | ||
}) |
12 changes: 12 additions & 0 deletions
12
packages/pwa-kit-react-sdk/src/storefront-preview/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* Copyright (c) 2023, Salesforce, Inc. | ||
* All rights reserved. | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
/** | ||
* Create alias import path for StorefrontPreview component | ||
* to indicate this is a first-class feature | ||
*/ | ||
import {StorefrontPreview} from '../ssr/universal/components/storefront-preview' | ||
export default StorefrontPreview |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters