Skip to content

Commit

Permalink
Add COOP+COEP+CORP headers (#6597)
Browse files Browse the repository at this point in the history
  • Loading branch information
somebody1234 authored May 10, 2023
1 parent 0d9186b commit 9597dd3
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 20 deletions.
4 changes: 4 additions & 0 deletions app/ide-desktop/lib/client/src/bin/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as mime from 'mime-types'
import * as portfinder from 'portfinder'
import createServer from 'create-servers'

import * as common from 'enso-common'
import * as contentConfig from 'enso-content-config'

import * as paths from '../paths'
Expand Down Expand Up @@ -105,6 +106,9 @@ export class Server {
resource === '/preload.cjs.map'
? `${paths.APP_PATH}${resource}`
: `${this.config.dir}${resource}`
for (const [header, value] of common.COOP_COEP_CORP_HEADERS) {
response.setHeader(header, value)
}
fs.readFile(resourceFile, (err, data) => {
if (err) {
logger.error(`Resource '${resource}' not found.`)
Expand Down
13 changes: 12 additions & 1 deletion app/ide-desktop/lib/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/** @file This module contains metadata about the product and distribution.
/** @file This module contains metadata about the product and distribution,
* and various other constants that are needed in multiple sibling packages.
*
* Code in this package is used by two or more sibling packages of this package. The code is defined
* here when it is not possible for a sibling package to own that code without introducing a
Expand All @@ -12,3 +13,13 @@ export const DEEP_LINK_SCHEME = 'enso'

/** Name of the product. */
export const PRODUCT_NAME = 'Enso'

/** COOP, COEP, and CORP headers: https://web.dev/coop-coep/
*
* These are required to increase the resolution of `performance.now()` timers,
* making profiling a lot more accurate and consistent. */
export const COOP_COEP_CORP_HEADERS: [header: string, value: string][] = [
['Cross-Origin-Embedder-Policy', 'require-corp'],
['Cross-Origin-Opener-Policy', 'same-origin'],
['Cross-Origin-Resource-Policy', 'same-origin'],
]
6 changes: 5 additions & 1 deletion app/ide-desktop/lib/content/esbuild-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import esbuildPluginYaml from 'esbuild-plugin-yaml'
import * as utils from '../../utils'
import BUILD_INFO from '../../build.json' assert { type: 'json' }

export const THIS_PATH = pathModule.resolve(pathModule.dirname(url.fileURLToPath(import.meta.url)))
// =================
// === Constants ===
// =================

const THIS_PATH = pathModule.resolve(pathModule.dirname(url.fileURLToPath(import.meta.url)))

// =============================
// === Environment variables ===
Expand Down
4 changes: 4 additions & 0 deletions app/ide-desktop/lib/content/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const logger = app.log.logger
const ESBUILD_PATH = '/esbuild'
/** SSE event indicating a build has finished. */
const ESBUILD_EVENT_NAME = 'change'
/** Path to the service worker that resolves all extensionless paths to `/index.html`.
* This service worker is required for client-side routing to work when doing local development. */
const SERVICE_WORKER_PATH = '/serviceWorker.js'
/** One second in milliseconds. */
const SECOND = 1000
/** Time in seconds after which a `fetchTimeout` ends. */
Expand All @@ -33,6 +36,7 @@ if (IS_DEV_MODE) {
new EventSource(ESBUILD_PATH).addEventListener(ESBUILD_EVENT_NAME, () => {
location.reload()
})
void navigator.serviceWorker.register(SERVICE_WORKER_PATH)
}

// =============
Expand Down
32 changes: 32 additions & 0 deletions app/ide-desktop/lib/content/src/serviceWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/** @file A service worker that redirects paths without extensions to `/index.html`.
* This is required for paths like `/login`, which are handled by client-side routing,
* to work when developing locally on `localhost:8080`. */
// Bring globals and interfaces specific to Web Workers into scope.
/// <reference lib="WebWorker" />
import * as common from 'enso-common'

// =====================
// === Fetch handler ===
// =====================

// We `declare` a variable here because Service Workers have a different global scope.
// eslint-disable-next-line no-restricted-syntax
declare const self: ServiceWorkerGlobalScope

self.addEventListener('fetch', event => {
const url = new URL(event.request.url)
if (url.hostname === 'localhost' && url.pathname !== '/esbuild') {
event.respondWith(
fetch(event.request.url).then(response => {
const clonedResponse = new Response(response.body, response)
for (const [header, value] of common.COOP_COEP_CORP_HEADERS) {
clonedResponse.headers.set(header, value)
}
return clonedResponse
})
)
return
} else {
return false
}
})
8 changes: 8 additions & 0 deletions app/ide-desktop/lib/content/watch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/** @file File watch and compile service. */
import * as path from 'node:path'
import * as url from 'node:url'

import * as esbuild from 'esbuild'
import * as portfinder from 'portfinder'
import chalk from 'chalk'
Expand All @@ -12,6 +15,7 @@ import * as dashboardBundler from '../dashboard/esbuild-config'

const PORT = 8080
const HTTP_STATUS_OK = 200
const THIS_PATH = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)))

// ===============
// === Watcher ===
Expand All @@ -29,6 +33,10 @@ async function watch() {
...bundler.argumentsFromEnv(),
devMode: true,
})
opts.entryPoints.push({
in: path.resolve(THIS_PATH, 'src', 'serviceWorker.ts'),
out: 'serviceWorker',
})
const builder = await esbuild.context(opts)
await builder.watch()
await builder.serve({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,16 @@ export const COMPUTER_ICON = (
</svg>
)

/** An icon representing a user without a profile picture. */
export const DEFAULT_USER_ICON = (
<svg height={32} width={32} viewBox="2 2 20 20" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 20a10 10 0 0 1 6 -18 10 10 0 0 1 6 18 6 6 0 0 0 -4 -5 4.3 4.3 0 0 0 -2 -8 4.3 4.3 0 0 0 -2 8 6 6 0 0 0 -4 5"
fill="#888888"
/>
</svg>
)

export interface StopIconProps {
className?: string
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,10 +471,7 @@ function Dashboard(props: DashboardProps) {
key={user.user.organization_id}
permissions={PERMISSION[user.permission]}
>
<img
className="rounded-full h-6"
src="https://faces-img.xcdn.link/image-lorem-face-4742.jpg"
/>
{svg.DEFAULT_USER_ICON}
</PermissionDisplay>
))}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,15 @@ function TopBar(props: TopBarProps) {
</a>
{/* User profile and menu. */}
<div className="transform">
<img
src="https://faces-img.xcdn.link/image-lorem-face-4742.jpg"
className="rounded-full w-8 h-8 bg-cover cursor-pointer"
<div
onClick={event => {
event.stopPropagation()
setUserMenuVisible(!userMenuVisible)
}}
/>
className="rounded-full w-8 h-8 bg-cover cursor-pointer"
>
{svg.DEFAULT_USER_ICON}
</div>
</div>
</div>
)
Expand Down
27 changes: 17 additions & 10 deletions app/ide-desktop/lib/dashboard/src/serviceWorker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/** @file A service worker that redirects paths without extensions to `/index.html`.
* This is only used in the cloud frontend. */
* This is required for paths like `/login`, which are handled by client-side routing,
* to work when developing locally on `localhost:8081`. */
// Bring globals and interfaces specific to Web Workers into scope.
/// <reference lib="WebWorker" />
import * as common from 'enso-common'

// =====================
// === Fetch handler ===
Expand All @@ -12,17 +15,21 @@ declare const self: ServiceWorkerGlobalScope

self.addEventListener('fetch', event => {
const url = new URL(event.request.url)
if (
url.hostname === 'localhost' &&
/\/[^.]+$/.test(event.request.url) &&
url.pathname !== '/esbuild'
) {
event.respondWith(fetch('/index.html'))
if (url.hostname === 'localhost' && url.pathname !== '/esbuild') {
const responsePromise = /\/[^.]+$/.test(event.request.url)
? fetch('/index.html')
: fetch(event.request.url)
event.respondWith(
responsePromise.then(response => {
const clonedResponse = new Response(response.body, response)
for (const [header, value] of common.COOP_COEP_CORP_HEADERS) {
clonedResponse.headers.set(header, value)
}
return clonedResponse
})
)
return
} else {
return false
}
})

// Required for TypeScript to consider it a module, instead of in window scope.
export {}

0 comments on commit 9597dd3

Please sign in to comment.