Skip to content

Core: Fix WebSocket connection for StackBlitz/WebContainers#34281

Merged
ghengeveld merged 1 commit into
nextfrom
fix-stackblitz-websocket
Mar 23, 2026
Merged

Core: Fix WebSocket connection for StackBlitz/WebContainers#34281
ghengeveld merged 1 commit into
nextfrom
fix-stackblitz-websocket

Conversation

@ghengeveld
Copy link
Copy Markdown
Member

@ghengeveld ghengeveld commented Mar 23, 2026

Closes #34226

What I did

  • Detect WebContainer environment using @webcontainer/env
  • Disable origin/host and websocket token validation when running in a WebContainer.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  • Take the StackBlitz sandbox from [Bug]: Deploying to StackBlitz now requires configuring allowed hosts #34226. Confirm it throws a websocket token validation error in console, and the UI notifies that the server is disconnected.
  • Upgrade the sandbox to the canary version below.
  • Restart the Storybook and confirm the websocket validation error no longer occurs, and the notification doesn't appear.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This pull request has been released as version 0.0.0-pr-34281-sha-d910f6e6. Try it out in a new sandbox by running npx storybook@0.0.0-pr-34281-sha-d910f6e6 sandbox or in an existing project with npx storybook@0.0.0-pr-34281-sha-d910f6e6 upgrade.

More information
Published version 0.0.0-pr-34281-sha-d910f6e6
Triggered by @ghengeveld
Repository storybookjs/storybook
Branch fix-stackblitz-websocket
Commit d910f6e6
Datetime Mon Mar 23 15:33:49 UTC 2026 (1774280029)
Workflow run 23445629404

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=34281

Summary by CodeRabbit

  • New Features

    • Added web container environment detection to the application
    • Server channel initialization now adapts its behavior for web container contexts
  • Tests

    • Added comprehensive test coverage for server channel upgrade handling in web container scenarios

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 23, 2026

📝 Walkthrough

Walkthrough

The pull request adds support for detecting web container environments and introduces conditional validation bypass in WebSocket upgrade handlers. The @webcontainer/env package is introduced as a dependency, with isWebContainer() re-exported for use throughout the codebase. When running in a web container, origin and token validation checks are skipped during server channel creation.

Changes

Cohort / File(s) Summary
Dependency Management
code/core/package.json
Added @webcontainer/env dependency pinned to ^1.1.1.
Environment Detection
code/core/src/common/utils/envs.ts
Re-exported isWebContainer from @webcontainer/env package.
Server Channel Options
code/core/src/core-server/utils/get-server-channel.ts
Extended ServerChannelTransportOptions with optional skipValidation flag; updated WebSocket upgrade handler to conditionally bypass origin and token validation checks when flag is enabled.
Build Dev Server
code/core/src/core-server/build-dev.ts
Integrated isWebContainer() detection and passed skipValidation option to getServerChannel() based on environment detection.
Server Channel Tests
code/core/src/core-server/utils/__tests__/server-channel.test.ts
Added test configuration and two new test cases covering upgrade requests without token and with invalid token/origin when validation is skipped.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
code/core/src/core-server/utils/__tests__/server-channel.test.ts (1)

312-366: Good test coverage for the skipValidation behavior.

The tests correctly verify that:

  1. Connections without tokens are accepted when validation is disabled
  2. Connections with invalid origins are accepted when validation is disabled

Both tests follow the established patterns and properly assert that handleUpgrade is called while the socket is not destroyed.

Consider adding a test case for missing origin header with skipValidation: true to mirror the existing "rejects connections without origin header" test, ensuring full parity in coverage.

📝 Optional test case
it('accepts connections without origin header when validation is disabled', () => {
  const server = new EventEmitter() as any as Server;
  const socket = new EventEmitter() as any;
  socket.write = vi.fn();
  socket.destroy = vi.fn();
  const destroySpy = vi.spyOn(socket, 'destroy');
  const handleUpgradeSpy = vi.fn();
  const transport = new ServerChannelTransport(server, webContainerOptions);

  // `@ts-expect-error` (accessing private property)
  transport.socket.handleUpgrade = handleUpgradeSpy;

  const request = {
    url: '/storybook-server-channel',
    headers: {},
  } as any;
  const head = Buffer.from('');

  server.listeners('upgrade')[0](request, socket, head);

  expect(socket.write).not.toHaveBeenCalled();
  expect(destroySpy).not.toHaveBeenCalled();
  expect(handleUpgradeSpy).toHaveBeenCalled();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/core-server/utils/__tests__/server-channel.test.ts` around
lines 312 - 366, Add a test that mirrors the existing "accepts connections
without token/with invalid origin" cases but uses a request with no origin
header to verify skipValidation behavior; create the same setup (EventEmitter
server, socket with write/destroy spies), instantiate ServerChannelTransport
(same webContainerOptions used in the other tests), stub
transport.socket.handleUpgrade and trigger
server.listeners('upgrade')[0](request, socket, head) where request.headers is
an empty object, then assert socket.write was not called, socket.destroy was not
called, and handleUpgrade was called (test title: "accepts connections without
origin header when validation is disabled").
code/core/package.json (1)

237-237: Verify whether an exact version constraint is needed for @webcontainer/env.

The dependency is correctly added to dependencies for runtime use. The package is valid and version 1.1.1 is the latest available. However, consider pinning an exact version (1.1.1) instead of the caret constraint (^1.1.1) if stricter build reproducibility is required across environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/package.json` at line 237, Decide and apply the desired version
constraint for the `@webcontainer/env` dependency in package.json: if you need
strict reproducibility, replace the caret constraint "^1.1.1" with an exact
"1.1.1" for the "@webcontainer/env" entry; otherwise, leave the caret to allow
compatible patch/minor updates. Locate the "@webcontainer/env" key in the
dependencies section of package.json and update its version string accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code/core/package.json`:
- Line 237: Decide and apply the desired version constraint for the
`@webcontainer/env` dependency in package.json: if you need strict
reproducibility, replace the caret constraint "^1.1.1" with an exact "1.1.1" for
the "@webcontainer/env" entry; otherwise, leave the caret to allow compatible
patch/minor updates. Locate the "@webcontainer/env" key in the dependencies
section of package.json and update its version string accordingly.

In `@code/core/src/core-server/utils/__tests__/server-channel.test.ts`:
- Around line 312-366: Add a test that mirrors the existing "accepts connections
without token/with invalid origin" cases but uses a request with no origin
header to verify skipValidation behavior; create the same setup (EventEmitter
server, socket with write/destroy spies), instantiate ServerChannelTransport
(same webContainerOptions used in the other tests), stub
transport.socket.handleUpgrade and trigger
server.listeners('upgrade')[0](request, socket, head) where request.headers is
an empty object, then assert socket.write was not called, socket.destroy was not
called, and handleUpgrade was called (test title: "accepts connections without
origin header when validation is disabled").

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4b1058aa-d232-4eb7-8abb-23aa0369655a

📥 Commits

Reviewing files that changed from the base of the PR and between 9ffa6c7 and d910f6e.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • code/core/package.json
  • code/core/src/common/utils/envs.ts
  • code/core/src/core-server/build-dev.ts
  • code/core/src/core-server/utils/__tests__/server-channel.test.ts
  • code/core/src/core-server/utils/get-server-channel.ts

@ghengeveld ghengeveld added bug patch:yes Bugfix & documentation PR that need to be picked to main branch ci:normal labels Mar 23, 2026
@ghengeveld ghengeveld self-assigned this Mar 23, 2026
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Mar 23, 2026

View your CI Pipeline Execution ↗ for commit d910f6e

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ✅ Succeeded 10m 44s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-23 16:00:03 UTC

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Mar 23, 2026

View your CI Pipeline Execution ↗ for commit d910f6e


☁️ Nx Cloud last updated this comment at 2026-03-23 15:48:02 UTC

@ghengeveld ghengeveld requested review from AriPerkkio and yannbf March 23, 2026 15:48
Comment thread code/core/src/common/utils/envs.ts
Comment thread code/core/src/core-server/utils/get-server-channel.ts
@storybook-app-bot
Copy link
Copy Markdown

Package Benchmarks

Commit: d910f6e, ran on 23 March 2026 at 16:00:24 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 49 50 🚨 +1 🚨
Self size 20.46 MB 20.46 MB 🎉 -780 B 🎉
Dependency size 16.54 MB 16.55 MB 🚨 +10 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 534 534 0
Self size 650 KB 650 KB 0 B
Dependency size 60.20 MB 59.95 MB 🎉 -248 KB 🎉
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 92 92 0
Self size 1.12 MB 1.12 MB 🎉 -36 B 🎉
Dependency size 22.72 MB 22.48 MB 🎉 -248 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 121 121 0
Self size 30 KB 30 KB 0 B
Dependency size 23.79 MB 23.54 MB 🎉 -248 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 82 82 0
Self size 35 KB 35 KB 🚨 +36 B 🚨
Dependency size 20.51 MB 20.26 MB 🎉 -248 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 271 271 0
Self size 24 KB 24 KB 🎉 -12 B 🎉
Dependency size 44.81 MB 44.56 MB 🎉 -248 KB 🎉
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 58 58 0
Self size 1.44 MB 1.19 MB 🎉 -248 KB 🎉
Dependency size 13.21 MB 13.21 MB 🎉 -6 B 🎉
Bundle Size Analyzer Link Link

Copy link
Copy Markdown
Member

@yannbf yannbf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Ari's concerns, otherwise LGTM.

@ghengeveld ghengeveld merged commit a5658d3 into next Mar 23, 2026
132 of 138 checks passed
@ghengeveld ghengeveld deleted the fix-stackblitz-websocket branch March 23, 2026 16:49
valentinpalkovic pushed a commit that referenced this pull request Apr 2, 2026
Core: Fix WebSocket connection for StackBlitz/WebContainers
(cherry picked from commit a5658d3)
@github-actions github-actions Bot mentioned this pull request Apr 2, 2026
19 tasks
@github-actions github-actions Bot added the patch:done Patch/release PRs already cherry-picked to main/release branch label Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Deploying to StackBlitz now requires configuring allowed hosts

3 participants