Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove null-origin iframe messenger #2713

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 3 additions & 36 deletions packages/web-worker/README.md
Expand Up @@ -37,33 +37,19 @@ When the resulting function is called, it will return a worker "instance". This
```ts
const worker = createWorker();

// Assuming worker was:
// export function hello(name) {
// return `Hello, ${name}`;
// }

const result = await worker.hello('world'); // 'Hello, world'
const result = await worker.hello('world');
```

Note that more complex workers are allowed; they can export multiple functions, including default exports, and it can accept complex argument types [with some restrictions](#limitations):

```ts
const worker = createWorker();

// Assuming worker was:
// export default function hello(name) {
// return `Hello, ${name}`;
// }
//
// export function getName(user) {
// return user.getDisplayName();
// }

const result = await worker.default(
await worker.getName({
getDisplayName: () => 'Tobi',
}),
); // 'Hello, Tobi'
);
```

##### Terminating the worker
Expand Down Expand Up @@ -175,24 +161,6 @@ const worker = createWorker({
});
```

###### `createIframeWorkerMessenger()`

The `createIframeWorkerMessenger` is provided to make it easy to create a worker that is not treated as same-origin to the parent page. This function will, for each worker, create an iframe with a restrictive `sandbox` attribute and an anonymous origin, and will force that iframe to create a worker. It then passes a `MessagePort` through to the worker as the `postMessage` interface to use so that messages go directly between the worker and the original page.

```ts
import {
createWorkerFactory,
createIframeWorkerMessenger,
} from '@shopify/web-worker';

const createWorker = createWorkerFactory(() => import('./worker'));
const worker = createWorker({
createMessenger: createIframeWorkerMessenger,
});
```

Note that this mechanism will always fail CORS checks on requests from the worker code unless the requested resource has the `Allow-Access-Control-Origin` header set to `*`.

##### Naming the worker file

By default, worker files created using `createWorkerFactory` are given incrementing IDs as the file name. This strategy is generally less than ideal for long-term caching, as the name of the file depends on the order in which it was encountered during the build. For long-term caching, it is better to provide a static name for the worker file. This can be done by supplying the [`webpackChunkName` magic comment](https://webpack.js.org/api/module-methods/#magic-comments) before your import:
Expand Down Expand Up @@ -252,8 +220,7 @@ createWorkerFactory(workerStuff);
When webpack attempts to process this import, it sees the loader syntax, and passes the worker script to this package’s [custom webpack loader](src/webpack-parts/loader.ts). This loader does most of the heavy lifting. It creates an in-memory module (using information it pulls off the `WebWorkerPlugin` instance it finds in the webpack compiler) that exposes the worker API using the `expose()` function from this library:

```ts
// This is the imaginary module the loader creates and compiles
import {expose} from '@shopify/web-worker';
import {value expose} from '@shopify/web-worker';
import * as api from './worker';
expose(api);
```
Expand Down
2 changes: 0 additions & 2 deletions packages/web-worker/src/index.ts
Expand Up @@ -13,7 +13,5 @@ export type {
} from './create';
export {
createWorkerMessenger,
createIframeWorkerMessenger,
createNullOriginIframeMessenger,
createRemoteOriginIframeMessengerFactory,
} from './messenger';
1 change: 0 additions & 1 deletion packages/web-worker/src/messenger/iframe/index.ts
@@ -1,2 +1 @@
export {createNullOriginIframeMessenger} from './null-origin';
export {createRemoteOriginIframeMessengerFactory} from './remote-origin';
29 changes: 0 additions & 29 deletions packages/web-worker/src/messenger/iframe/null-origin.ts

This file was deleted.

6 changes: 1 addition & 5 deletions packages/web-worker/src/messenger/index.ts
@@ -1,6 +1,2 @@
export {createWorkerMessenger} from './worker';
export {
createNullOriginIframeMessenger as createIframeWorkerMessenger,
createNullOriginIframeMessenger,
createRemoteOriginIframeMessengerFactory,
} from './iframe';
export {createRemoteOriginIframeMessengerFactory} from './iframe';
66 changes: 0 additions & 66 deletions packages/web-worker/src/tests/e2e.test.ts
Expand Up @@ -1061,72 +1061,6 @@ describe('web-worker', () => {
expect(textContent).toBe(`${greetingPrefix}${greetingTarget}`);
});
});

it('can create a worker that communicates via an iframe without same-origin credentials', async () => {
const cookie = 'MY_COOKIE';
const noCookiesResult = 'NoCookies';
const cookiesResult = 'Cookies';
const testId = 'WorkerResult';

await withContext('no-same-origin', async (context) => {
const {workspace, browser, server} = context;

const path = '/app/ping';

server.use((ctx, next) => {
if (ctx.originalUrl === path) {
ctx.type = 'text';
ctx.body = ctx.cookies.get(cookie) ? cookiesResult : noCookiesResult;
ctx.set('Access-Control-Allow-Origin', '*');
return;
}

return next();
});

await workspace.write(
mainFile,
`
import {createWorkerFactory, createIframeWorkerMessenger} from '@shopify/web-worker';
const worker = createWorkerFactory(() => import('./worker'))({
createMessenger: createIframeWorkerMessenger,
});
(async () => {
document.cookie = ${JSON.stringify(cookie)} + '=1;SameSite=Lax';
const result = await worker.default();
const element = document.createElement('div');
element.setAttribute('id', ${JSON.stringify(testId)});
element.textContent = result;
document.body.appendChild(element);
})();
`,
);

await workspace.write(
workerFile,
`
export default async function run() {
const result = await fetch(${JSON.stringify(
server.url(path),
)}, {method: 'GET'});
return result.text();
}
`,
);

await runWebpack(context);

const page = await browser.go();
const workerElement = await page.waitForSelector(`#${testId}`);
const textContent = await workerElement!.evaluate(
(element) => element.innerHTML,
);

expect(textContent).toBe(noCookiesResult);
});
});
});

// Workers are generated from a blob URL that just importScripts() the actual
Expand Down