Skip to content

Commit

Permalink
Add cross-origin isolation flag (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamVerschueren authored Jul 2, 2024
1 parent b55124d commit 008665a
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 18 deletions.
18 changes: 9 additions & 9 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"printWidth": 100,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"endOfLine": "lf",
"semi": true,
"arrowParens": "always",
"bracketSpacing": true,
"trailingComma": "es5"
"printWidth": 100,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"endOfLine": "lf",
"semi": true,
"arrowParens": "always",
"bracketSpacing": true,
"trailingComma": "es5"
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# @stackblitz/sdk changelog

## v1.10.0 (2024-05-03)

- Added support for `organization` in `ProjectOptions`

## v1.9.0 (2023-04-04)
Expand Down
3 changes: 2 additions & 1 deletion examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
<body>
<h1>StackBlitz SDK Examples</h1>
<ul>
<li><a href="/examples/open-embed-project-id/">Open and embed a StackBlitz project</a></li>
<li><a href="/examples/open-embed-project-id/">Open and embed a StackBlitz EngineBlock project</a></li>
<li><a href="/examples/open-embed-webcontainer/">Open and embed a StackBlitz WebContainer project</a></li>
<li><a href="/examples/open-embed-github-project/">Open and embed a GitHub repo</a></li>
<li><a href="/examples/embed-project-vm/">Control an embedded project with the SDK</a></li>
</ul>
Expand Down
2 changes: 1 addition & 1 deletion examples/open-embed-project-id/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function openProject() {
});
}

// This replaces the HTML element with
// This replaces the HTML element with
// the id of "embed" with https://stackblitz.com/edit/css-custom-prop-color-values embedded in an iframe.
function embedProject() {
sdk.embedProjectId('embed', 'css-custom-prop-color-values', {
Expand Down
26 changes: 26 additions & 0 deletions examples/open-embed-webcontainer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>StackBlitz SDK - Open and embed a StackBlitz WebContainer project</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<script type="module" src="./index.ts"></script>
</head>
<body>
<header>
<h1>Open and embed a StackBlitz WebContainer project</h1>
<div>
<label>
<input type="checkbox" name="corp" /> Cross-Origin Isolation
</label>
</div>
<nav>
<button type="button" name="embed-project">Embed project</button>
<button type="button" name="open-project">Open project</button>
</nav>
</header>
<div id="embed">
<p>Embed will go here</p>
</div>
</body>
</html>
53 changes: 53 additions & 0 deletions examples/open-embed-webcontainer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sdk from '@stackblitz/sdk';

import './styles.css';

// This opens https://stackblitz.com/edit/node
// in the current window with the Preview pane
function openProject() {
sdk.openProjectId('node', {
newWindow: false,
view: 'preview',
});
}

// This replaces the HTML element with
// the id of "embed" with https://stackblitz.com/edit/node embedded in an iframe.
function embedProject() {
sdk.embedProjectId('embed', 'node', {
openFile: 'index.ts',
crossOriginIsolated: true,
});
}

function toggleCorp(event: Event) {
const queryParams = new URLSearchParams(window.location.search);
const isChecked = (event.target as any)?.checked;

if (isChecked) {
if (!queryParams.has('corp') || queryParams.get('corp') !== '1') {
queryParams.set('corp', '1');
}
} else {
queryParams.delete('corp');
}

window.location.search = queryParams.toString();
}

function setup() {
const embedButton = document.querySelector('[name=embed-project]') as HTMLButtonElement;
const openButton = document.querySelector('[name=open-project]') as HTMLButtonElement;
const corpCheckbox = document.querySelector('[name=corp]') as HTMLInputElement;

embedButton.addEventListener('click', embedProject);
openButton.addEventListener('click', openProject);
corpCheckbox.addEventListener('change', toggleCorp);

// mark the checkbox checked if the corp param is already set
const queryParams = new URLSearchParams(window.location.search);

corpCheckbox.checked = queryParams.get('corp') === '1';
}

setup();
9 changes: 9 additions & 0 deletions examples/open-embed-webcontainer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "sdk-example-open-embed-webcontainer",
"description": "Demo of the StackBlitz SDK's methods for opening & embedding an existing StackBlitz WebContainer project",
"version": "0.0.0",
"private": true,
"dependencies": {
"@stackblitz/sdk": "^1.8.1"
}
}
53 changes: 53 additions & 0 deletions examples/open-embed-webcontainer/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
html {
height: 100%;
text-align: center;
font-family: system-ui, sans-serif;
color: black;
background-color: white;
}

body {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
}

h1 {
margin: 1rem;
font-size: 1.25rem;
}

nav {
margin: 1rem;
font-size: 0.9rem;
}

button {
margin: 0.2em;
padding: 0.2em 0.5em;
font-size: inherit;
font-family: inherit;
}

#embed {
display: flex;
flex: 1 1 60%;
flex-direction: column;
justify-content: center;
overflow: hidden;
width: 100%;
height: auto;
margin: 0;
border: 0;
}

#embed > p {
width: min(300px, 100%);
margin: 2rem auto;
padding: 4rem 1rem;
border: dashed 2px #ccc;
border-radius: 0.5em;
font-size: 85%;
color: #777;
}
17 changes: 17 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function replaceAndEmbed(
frame.className = target.className;
}
setFrameDimensions(frame, options);
setFrameAllowList(target, frame, options);
target.replaceWith(frame);
}

Expand Down Expand Up @@ -81,3 +82,19 @@ function setFrameDimensions(frame: HTMLIFrameElement, options: EmbedOptions = {}
frame.setAttribute('style', 'width:100%;');
}
}

function setFrameAllowList(
target: HTMLElement & { allow?: string },
frame: HTMLIFrameElement,
options: EmbedOptions = {}
) {
const allowList = target.allow?.split(';')?.map((key) => key.trim()) ?? [];

if (options.crossOriginIsolated && !allowList.includes('cross-origin-isolated')) {
allowList.push('cross-origin-isolated');
}

if (allowList.length > 0) {
frame.allow = allowList.join('; ');
}
}
8 changes: 7 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export interface ProjectOptions {
organization?: {
provider: 'github';
name: string;
}
};
/**
* Show the sidebar as open or closed on page load.
*
Expand Down Expand Up @@ -205,6 +205,12 @@ export interface EmbedOptions extends ProjectOptions {
* Hide the preview URL in embeds.
*/
hideNavigation?: boolean;
/**
* Load the project with the proper cross-origin isolation headers.
*
* @see https://blog.stackblitz.com/posts/cross-browser-with-coop-coep/
*/
crossOriginIsolated?: boolean;
}

export type OpenFileOption = string | string[];
Expand Down
7 changes: 5 additions & 2 deletions src/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type ParamName =
| 'view'
| 'zenMode'
| 'orgName'
| 'orgProvider';
| 'orgProvider'
| 'corp';

export const generators: Record<keyof ParamOptions, (value: any) => string> = {
clickToLoad: (value: ParamOptions['clickToLoad']) => trueParam('ctl', value),
Expand All @@ -56,7 +57,9 @@ export const generators: Record<keyof ParamOptions, (value: any) => string> = {
theme: (value: ParamOptions['theme']) => enumParam('theme', value, UI_THEMES),
view: (value: ParamOptions['view']) => enumParam('view', value, UI_VIEWS),
zenMode: (value: ParamOptions['zenMode']) => trueParam('zenMode', value),
organization: (value: ParamOptions['organization']) => `${stringParams('orgName', value?.name)}&${stringParams('orgProvider', value?.provider)}`,
organization: (value: ParamOptions['organization']) =>
`${stringParams('orgName', value?.name)}&${stringParams('orgProvider', value?.provider)}`,
crossOriginIsolated: (value: ParamOptions['crossOriginIsolated']) => trueParam('corp', value),
};

export function buildParams(options: ParamOptions = {}): string {
Expand Down
11 changes: 8 additions & 3 deletions test/unit/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ describe('embedUrl', () => {
});

test('turns config into URL query parameters', () => {
expect(embedUrl('/edit/test', { clickToLoad: true, openFile: 'index.js', theme: 'dark' })).toBe(
'https://stackblitz.com/edit/test?embed=1&ctl=1&file=index.js&theme=dark'
);
expect(
embedUrl('/edit/test', {
clickToLoad: true,
openFile: 'index.js',
theme: 'dark',
crossOriginIsolated: true,
})
).toBe('https://stackblitz.com/edit/test?embed=1&ctl=1&file=index.js&theme=dark&corp=1');
});

test('allows removing the embed=1 query parameter', () => {
Expand Down
5 changes: 4 additions & 1 deletion test/unit/params.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ describe('buildParams', () => {
theme: 'default',
view: 'default',
zenMode: false,
crossOriginIsolated: false,
};
// Check that we are testing all options
expect(Object.keys(options).sort()).toStrictEqual(Object.keys(generators).sort());
Expand All @@ -108,14 +109,15 @@ describe('buildParams', () => {
hideExplorer: true,
hideNavigation: true,
openFile: ['src/index.js,src/styles.css', 'package.json'],
organization: {name: 'stackblitz', provider: 'github'},
organization: { name: 'stackblitz', provider: 'github' },
showSidebar: true,
sidebarView: 'search',
startScript: 'dev:serve',
terminalHeight: 50,
theme: 'light',
view: 'preview',
zenMode: true,
crossOriginIsolated: true,
};
// Check that we are testing all options
expect(Object.keys(options).sort()).toStrictEqual(Object.keys(generators).sort());
Expand All @@ -140,6 +142,7 @@ describe('buildParams', () => {
'theme=light',
'view=preview',
'zenMode=1',
'corp=1',
].sort()
);
});
Expand Down
14 changes: 14 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ReplacePlugin from '@rollup/plugin-replace';
import bodyParser from 'body-parser';
import fs from 'node:fs/promises';
import path from 'node:path';
import querystring from 'node:querystring';
import type { Plugin, UserConfig, ViteDevServer } from 'vite';
import { defineConfig } from 'vite';
import TSConfigPaths from 'vite-tsconfig-paths';
Expand Down Expand Up @@ -139,6 +140,19 @@ function configureServer(server: ViteDevServer) {
next();
}
});

server.middlewares.use('/examples', async (req, res, next) => {
if (req.url.includes('?')) {
const query = querystring.parse(req.url.split('?').pop());

if (query.corp === '1') {
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
}
}

next();
});
}

function getProjectDataString(req: any): string {
Expand Down

0 comments on commit 008665a

Please sign in to comment.