diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ddd1861e66db4..7114c33cea0fd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -966,7 +966,7 @@ jobs: uses: actions/dependency-review-action@v4.3.2 with: allow-licenses: Apache-2.0, 0BSD, BSD-2-Clause, BSD-3-Clause, CC0-1.0, ISC, MIT, MIT-0, MPL-2.0 - allow-dependencies-licenses: "pkg:golang/github.com/coder/wgtunnel@0.1.13-0.20240522110300-ade90dfb2da0, pkg:npm/pako@1.0.11, pkg:npm/caniuse-lite@1.0.30001639" + allow-dependencies-licenses: "pkg:golang/github.com/coder/wgtunnel@0.1.13-0.20240522110300-ade90dfb2da0, pkg:npm/pako@1.0.11, pkg:npm/caniuse-lite@1.0.30001639, pkg:githubactions/alwaysmeticulous/report-diffs-action/cloud-compute" license-check: true vulnerability-check: false - name: "Report" @@ -992,3 +992,23 @@ jobs: fi done echo "No incompatible licenses detected" + meticulous: + runs-on: ubuntu-latest + steps: + - name: "Checkout Repository" + uses: actions/checkout@v4 + - name: Setup Node + uses: ./.github/actions/setup-node + - name: Build + working-directory: ./site + run: pnpm build + - name: Serve + working-directory: ./site + run: | + pnpm vite preview & + sleep 5 + - name: Run Meticulous tests + uses: alwaysmeticulous/report-diffs-action/cloud-compute@v1 + with: + api-token: ${{ secrets.METICULOUS_API_TOKEN }} + app-url: "http://127.0.0.1:4173/" diff --git a/coderd/httpmw/csp.go b/coderd/httpmw/csp.go index 0862a0cd7cb2a..99d22acf6df6c 100644 --- a/coderd/httpmw/csp.go +++ b/coderd/httpmw/csp.go @@ -59,7 +59,7 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H cspDirectiveConnectSrc: {"'self'"}, cspDirectiveChildSrc: {"'self'"}, // https://github.com/suren-atoyan/monaco-react/issues/168 - cspDirectiveScriptSrc: {"'self'"}, + cspDirectiveScriptSrc: {"'self' "}, cspDirectiveStyleSrc: {"'self' 'unsafe-inline'"}, // data: is used by monaco editor on FE for Syntax Highlight cspDirectiveFontSrc: {"'self' data:"}, @@ -88,6 +88,11 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H if telemetry { // If telemetry is enabled, we report to coder.com. cspSrcs.Append(cspDirectiveConnectSrc, "https://coder.com") + // These are necessary to allow meticulous to collect sampling to + // improve our testing. Only remove these if we're no longer using + // their services. + cspSrcs.Append(cspDirectiveConnectSrc, meticulousConnectSrc...) + cspSrcs.Append(cspDirectiveScriptSrc, meticulousScriptSrc...) } // This extra connect-src addition is required to support old webkit @@ -131,3 +136,8 @@ func CSPHeaders(telemetry bool, websocketHosts func() []string) func(next http.H }) } } + +var ( + meticulousConnectSrc = []string{"https://cognito-identity.us-west-2.amazonaws.com", "https://user-events-v3.s3-accelerate.amazonaws.com", "*.sentry.io"} + meticulousScriptSrc = []string{"https://snippet.meticulous.ai", "https://browser.sentry-cdn.com"} +) diff --git a/site/package.json b/site/package.json index b0f3b2254e712..4704ebb0a7adb 100644 --- a/site/package.json +++ b/site/package.json @@ -30,6 +30,7 @@ "deadcode": "ts-prune | grep -v \".stories\\|.config\\|e2e\\|__mocks__\\|used in module\\|testHelpers\\|typesGenerated\" || echo \"No deadcode found.\"" }, "dependencies": { + "@alwaysmeticulous/recorder-loader": "2.137.0", "@emoji-mart/data": "1.2.1", "@emoji-mart/react": "1.1.1", "@emotion/css": "11.11.2", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index fbdee02e28702..2b7e0cf81d9a7 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -9,6 +9,9 @@ overrides: semver: 7.6.2 dependencies: + '@alwaysmeticulous/recorder-loader': + specifier: 2.137.0 + version: 2.137.0 '@emoji-mart/data': specifier: 1.2.1 version: 1.2.1 @@ -456,6 +459,10 @@ packages: resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} dev: true + /@alwaysmeticulous/recorder-loader@2.137.0: + resolution: {integrity: sha512-ux/xGYCNsOe8BzquEg7k7YSNJiw/0Sg2Pd/7fppYiVr5xEefpPeIhh3qwuupZgx6sB2t5KpKQdodNWVmGeyh/w==} + dev: false + /@ampproject/remapping@2.3.0: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1936,7 +1943,7 @@ packages: resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} engines: {node: '>=6.9.0'} dependencies: - regenerator-runtime: 0.14.1 + regenerator-runtime: 0.14.0 dev: true /@babel/runtime@7.24.7: @@ -5289,7 +5296,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.23.2 '@types/aria-query': 5.0.3 aria-query: 5.1.3 chalk: 4.1.2 @@ -11972,7 +11979,7 @@ packages: peerDependencies: react: '>=16.13.1' dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.22.6 react: 18.3.1 dev: true @@ -12288,6 +12295,10 @@ packages: /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: true + /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} diff --git a/site/src/index.tsx b/site/src/index.tsx index 8604ff268655d..489d747a3c0a6 100644 --- a/site/src/index.tsx +++ b/site/src/index.tsx @@ -1,3 +1,4 @@ +import { tryLoadAndStartRecorder } from "@alwaysmeticulous/recorder-loader"; import { createRoot } from "react-dom/client"; import { App } from "./App"; @@ -12,5 +13,30 @@ const element = document.getElementById("root"); if (element === null) { throw new Error("root element is null"); } + const root = createRoot(element); -root.render(); +async function startApp() { + // Record all sessions on localhost, staging stacks and preview URLs + if (isInternal()) { + // Start the Meticulous recorder before you initialise your app. + // Note: all errors are caught and logged, so no need to surround with try/catch + await tryLoadAndStartRecorder({ + projectId: "Y4uHy1qs0B660xxUdrkLPkazUMPr6OuTqYEnShaR", + isProduction: false, + }); + } + + root.render(); +} + +function isInternal() { + return ( + window.location.hostname.indexOf("dev.coder.com") > -1 || + window.location.hostname.indexOf("localhost") > -1 || + window.location.hostname.indexOf("127.0.0.1") > -1 + ); +} + +startApp().catch((error) => { + console.error(error); +});