Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

react-shopify-app-route-propagator: stop using iframe name #1151

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
],
"devDependencies": {
"@babel/core": "^7.5.5",
"@shopify/app-bridge": "^0.7.3",
"@shopify/app-bridge": "^1.2.0",
"@types/enzyme": "^3.1.10",
"@types/enzyme-adapter-react-16": "^1.0.3",
"@types/faker": "^4.1.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-shopify-app-route-propagator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"homepage": "https://github.com/Shopify/quilt/blob/master/packages/react-shopify-app-route-propagator/README.md",
"peerDependencies": {
"@shopify/app-bridge": "^1.0.0",
"@shopify/app-bridge": "^1.2.0",
"react": ">=16.8.0 <17.0.0",
"react-dom": ">=16.8.0 <17.0.0"
},
Expand Down
2 changes: 0 additions & 2 deletions packages/react-shopify-app-route-propagator/src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ export function getTopWindow() {
export function getOrigin() {
return location.origin;
}

export const MODAL_IFRAME_NAME = 'app-modal-iframe';
51 changes: 29 additions & 22 deletions packages/react-shopify-app-route-propagator/src/hook.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
import {useMemo, useEffect} from 'react';
import {ClientApplication} from '@shopify/app-bridge';
import {Context, ClientApplication} from '@shopify/app-bridge';
import {History} from '@shopify/app-bridge/actions';
import {LocationOrHref} from './types';

import {
getSelfWindow,
getTopWindow,
getOrigin,
MODAL_IFRAME_NAME,
} from './globals';
import {getSelfWindow, getTopWindow, getOrigin} from './globals';

export default function useRoutePropagation(
app: ClientApplication<any>,
location: LocationOrHref,
) {
const history = useMemo(() => History.create(app), [app]);

useEffect(() => {
const selfWindow = getSelfWindow();
const topWindow = getTopWindow();
useEffect(
() => {
async function updateHistory() {
const selfWindow = getSelfWindow();
const topWindow = getTopWindow();

const renderedInTheTopWindow = selfWindow === topWindow;
const renderedInAModal = selfWindow.name === MODAL_IFRAME_NAME;
const renderedInTheTopWindow = selfWindow === topWindow;

if (renderedInTheTopWindow || renderedInAModal) {
return;
}
const renderedAsMainApp = await app
.getState('context')
.then((context: Context) => {
return context === Context.Main;
});

const normalizedLocation = getNormalizedURL(location);
if (renderedInTheTopWindow || !renderedAsMainApp) {
return;
}

/*
const normalizedLocation = getNormalizedURL(location);

/*
We delete this param that ends up unnecassarily stuck on
the iframe due to oauth when propagating up.
*/
normalizedLocation.searchParams.delete('hmac');
normalizedLocation.searchParams.delete('hmac');

const {pathname, search, hash} = normalizedLocation;
const locationStr = `${pathname}${search}${hash}`;

const {pathname, search, hash} = normalizedLocation;
const locationStr = `${pathname}${search}${hash}`;
history.dispatch(History.Action.REPLACE, locationStr);
}

history.dispatch(History.Action.REPLACE, locationStr);
}, [history, location]);
updateHistory();
},
[history, location],
);
}

function getNormalizedURL(location: LocationOrHref) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import {ClientApplication} from '@shopify/app-bridge';
import * as React from 'react';
import {Context, ClientApplication} from '@shopify/app-bridge';
import {History as AppBridgeHistory} from '@shopify/app-bridge/actions';
import {act} from 'react-dom/test-utils';
import {mount} from '@shopify/react-testing';
import {MODAL_IFRAME_NAME} from '../globals';
import RoutePropagator from '../route-propagator';

jest.mock('../globals', () => {
Expand All @@ -25,7 +25,9 @@ describe('@shopify/react-shopify-app-route-propagator', () => {
name: '',
};

const mockApp = {} as ClientApplication<any>;
const mockApp = ({
getState: jest.fn().mockReturnValue(Promise.resolve(Context.Main)),
} as unknown) as ClientApplication<any>;

const appBridgeHistoryMock = {
dispatch: jest.fn(),
Expand All @@ -40,10 +42,11 @@ describe('@shopify/react-shopify-app-route-propagator', () => {
getSelfWindow.mockImplementation(() => selfWindow);
});

it('dispatch a replace action on mount', () => {
it('dispatch a replace action on mount', async () => {
const path = '/settings';

mount(<RoutePropagator location={path} app={mockApp} />);
await act((async () => {
await mount(<RoutePropagator location={path} app={mockApp} />);
}) as any);

expect(appBridgeHistoryMock.dispatch).toHaveBeenCalledTimes(1);
expect(appBridgeHistoryMock.dispatch).toHaveBeenCalledWith(
Expand All @@ -52,11 +55,15 @@ describe('@shopify/react-shopify-app-route-propagator', () => {
);
});

it('dispatch a replace action when the location updates', () => {
it('dispatch a replace action when the location updates', async () => {
const firstPath = '/settings';
const propagator = mount(
<RoutePropagator location={firstPath} app={mockApp} />,
);
let propagator;

await act((async () => {
propagator = await mount(
<RoutePropagator location={firstPath} app={mockApp} />,
);
}) as any);

expect(appBridgeHistoryMock.dispatch).toHaveBeenCalledTimes(1);
expect(appBridgeHistoryMock.dispatch).toHaveBeenLastCalledWith(
Expand All @@ -65,6 +72,11 @@ describe('@shopify/react-shopify-app-route-propagator', () => {
);

const secondPath = '/foo';

await act((async () => {
await propagator.setProps({location: secondPath});
}) as any);

propagator.setProps({location: secondPath});

expect(appBridgeHistoryMock.dispatch).toHaveBeenCalledTimes(2);
Expand All @@ -74,66 +86,84 @@ describe('@shopify/react-shopify-app-route-propagator', () => {
);
});

it('does not dispatch a replace action when the location updates but the value stay the same', () => {
it('does not dispatch a replace action when the location updates but the value stay the same', async () => {
const firstPath = '/settings';
const propagator = mount(
<RoutePropagator location={firstPath} app={mockApp} />,
);
let propagator;
await act((async () => {
propagator = await mount(
<RoutePropagator location={firstPath} app={mockApp} />,
);
}) as any);

expect(appBridgeHistoryMock.dispatch).toHaveBeenCalledTimes(1);
expect(appBridgeHistoryMock.dispatch).toHaveBeenLastCalledWith(
AppBridgeHistory.Action.REPLACE,
firstPath,
);

propagator.setProps({location: firstPath});
await act((async () => {
await propagator.setProps({location: firstPath});
}) as any);

expect(appBridgeHistoryMock.dispatch).toHaveBeenCalledTimes(1);
});

describe('when window is window.top', () => {
it('does not dispatch a replace action on mount', () => {
it('does not dispatch a replace action on mount', async () => {
getSelfWindow.mockImplementation(() => topWindow);

mount(<RoutePropagator location="/settings" app={mockApp} />);
await act((async () => {
await mount(<RoutePropagator location="/settings" app={mockApp} />);
}) as any);

expect(appBridgeHistoryMock.dispatch).not.toHaveBeenCalled();
});

it('does not dispatch a replace action when the location updates', () => {
it('does not dispatch a replace action when the location updates', async () => {
getSelfWindow.mockImplementation(() => topWindow);

const propagator = mount(
<RoutePropagator location="/settings" app={mockApp} />,
);
let propagator;
await act((async () => {
propagator = await mount(
<RoutePropagator location="/settings" app={mockApp} />,
);
}) as any);

const path = '/foo';
propagator.setProps({location: path});

await act((async () => {
await propagator.setProps({location: path});
}) as any);

expect(appBridgeHistoryMock.dispatch).not.toHaveBeenCalled();
});
});

describe('when window is an iframe', () => {
it('does not dispatch a replace action on mount', () => {
getSelfWindow.mockImplementation(() => ({
name: MODAL_IFRAME_NAME,
}));
mount(<RoutePropagator location="/settings" app={mockApp} />);
describe('when app is not a main app', () => {
const modalApp = ({
getState: jest.fn().mockReturnValue(Promise.resolve(Context.Modal)),
} as unknown) as ClientApplication<any>;

it('does not dispatch a replace action on mount', async () => {
await act((async () => {
await mount(<RoutePropagator location="/settings" app={modalApp} />);
}) as any);

expect(appBridgeHistoryMock.dispatch).not.toHaveBeenCalled();
});

it('does not dispatch a replace action when the location updates', () => {
getSelfWindow.mockImplementation(() => ({
name: MODAL_IFRAME_NAME,
}));
it('does not dispatch a replace action when the location updates', async () => {
let propagator;

const propagator = mount(
<RoutePropagator location="/settings" app={mockApp} />,
);
await act((async () => {
propagator = await mount(
<RoutePropagator location="/settings" app={modalApp} />,
);
}) as any);

propagator.setProps({location: '/foo'});
await act((async () => {
await propagator.setProps({location: '/foo'});
}) as any);

expect(appBridgeHistoryMock.dispatch).not.toHaveBeenCalled();
});
Expand Down
13 changes: 9 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1088,10 +1088,10 @@
dependencies:
"@shopify/app-bridge" "^1.8.0"

"@shopify/app-bridge@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@shopify/app-bridge/-/app-bridge-0.7.3.tgz#abadfa2ef6293f630764bc5ddca31750caf00f1c"
integrity sha512-xDkYvVvN8Gxy3JZduoT4Ohrl6fAuW+wEfhfL+qPzysu0LuJZ7CKgVCdEazPmtcI+V4GQPQLYLzcbCn3mWFU8dg==
"@shopify/app-bridge@^1.2.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@shopify/app-bridge/-/app-bridge-1.9.0.tgz#8908e1112cc452a084978ddbcebb4adce7e743b8"
integrity sha512-GVvBLpSMnyZkLRlxBC/Up+8tw26aOLaSWapfLurzd5AwKV69nUkiqWbibtUbzKay2zyRMiYZhjNrES28jtiFcg==

"@shopify/app-bridge@^1.8.0":
version "1.8.0"
Expand Down Expand Up @@ -11378,6 +11378,11 @@ yargs@^8.0.2:
y18n "^3.2.1"
yargs-parser "^7.0.0"

yarn@^1.10.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.19.1.tgz#14b92410dd1ba5bab87a12b4a3d807f4569bea97"
integrity sha512-gBnfbL9rYY05Gt0cjJhs/siqQXHYlZalTjK3nXn2QO20xbkIFPob+LlH44ML47GcR4VU9/2dYck1BWFM0Javxw==

yauzl@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
Expand Down