Skip to content

Isolate projects sharing a Service Worker registration #45

Closed

Description

@kazupon

Summary

  • Goal: Run independent projects in different tabs under the same origin and Service Worker scope.
  • Current concern: The Service Worker uses a shared virtual filesystem and a single current Web Worker RPC/port, even when different host clients connect.
  • Request: Isolate project files, transform requests, and HMR routing by runtime owner while allowing previews of one project to share that project's state.
  • Scope: Correct ownership and routing under one registration. No separate origin or Service Worker registration per project should be required.
  • Verification status: This is a source-based architecture concern; the two-tab scenario below has not been reproduced in a browser during this review.

Details

Shared state observations, required isolation, and a two-tab acceptance scenario

Current implementation

Reviewed at 03ea7fb. The Service Worker bootstrap uses one imported filesystem and subscriber; incoming V_FS_* messages are applied without selecting a project owner.

The server stores one workerRpc and one swWwPort. A connection supplies a client ID for its acknowledgement, but the active transform/HMR connection is not stored per project.

Per-preview session IDs within one runtime do not by themselves establish isolation between separate runtime owners. Please validate and support that boundary explicitly.

Requested behavior

  • Allow two host tabs to use the same registration and identical virtual paths with different file contents.
  • Route filesystem mutations, document requests, transforms, and HMR messages to the correct owning project. Include requests initiated by preview iframes and their assets.
  • Let multiple preview sessions of one project share its files and updates, while keeping independent projects separate.
  • Removing previews or disposing one owner must not replace or remove another owner's files, ports, or subscriptions.
  • Define how request ownership is established and validated. Do not rely only on an arbitrary preview ID or whichever Web Worker connected most recently.

Acceptance criteria

  • Open two tabs with /index.html and /src/main.ts at the same virtual paths but visibly different content. Initialize them in both orders.
  • Update, add, and delete files in either tab; reload both previews and confirm each sees only its own project.
  • Deliver delayed transform responses and HMR events while both tabs edit; responses stay with their owners.
  • Close or dispose one owner; the other continues loading assets, editing, and reloading.
  • Multiple sessions belonging to one owner still share updates as intended.

Start with the browser regression scenario above, then implement the required routing/state separation. The choice of internal project identity and request transport remains an implementation decision.

Activity

  1. kazupon commented on Oct 1, 2026

    @kazupon
    OwnerAuthor

    Carrying over acceptance criterion 5 of #43, which #60 did not cover:

    Disposing one instance leaves a second client using the same Service Worker registration operational.

    This issue already lists the same scenario ("Close or dispose one owner; the other continues loading assets, editing, and reloading"), so it is tracked here.

    Since #60, the problem looks like this:

    • The Service Worker keeps a single connection to the most recently connected Web Worker (swWwPort and workerRpc).
    • Suppose the disposed instance's Web Worker is that connection. Then other clients that share the registration send their requests to the terminated Worker until another instance connects, and those requests fail after the 30-second RPC timeout.
    • Preview HMR ports that were forwarded to that Worker stop working as well.
  2. kazupon commented on Oct 1, 2026

    @kazupon
    OwnerAuthor

    Carrying over the part of the acceptance criteria of #46 that #72 did not cover:

    Cover two clients sharing the registration and disposal while recovery is pending; late recovery must not revive a disposed owner.

    #72 covers disposal while a recovery is pending. The two-client part depends on the owner separation that this issue asks for, so it is tracked here. The same goes for this request in #46: "Recovery for one client must not replace another client's project."

    Since #72, a restart of the Service Worker process is handled like this:

    • When listen() completes, the Service Worker sends V_SW_INSTANCE_STARTED with a new instance ID to every window client it controls (service-worker-core.ts).
    • Each runtime that knows another ID restores its own project. It sends its copy of the files with V_FS_INIT, then connects its Web Worker channel again (index.ts).
    • The restarted Service Worker holds requests within the base path until it has received V_FS_INIT and a Web Worker channel, for up to 10 seconds (service-worker-core.ts).

    When two clients share the registration:

    • Both restore into the one shared virtual filesystem.
    • Each connection replaces swWwPort and workerRpc.
    • The client that restores last wins, as with ready().
    • The held requests are released as soon as the files and the channel of any client have arrived.

    Notes for the separation:

    • V_WW_CONNECT_PORT now carries a runtimeId, created when ready() starts (index.ts). It marks the owner of a channel. The Service Worker does not use it yet.
    • The V_FS_* messages carry no owner yet. This issue can decide how to route them: an owner field on each message, or a port per owner.
    • V_WW_CONNECT_PORT_ACK does not say which connection it acknowledges, and the Service Worker sends it to the page. If two runtimes in one page connect at the same time, each could take the other's acknowledgement.

    One more acceptance criterion for this issue:

    • Restart the Service Worker process while two clients share the registration. For example, use ServiceWorker.stopAllWorkers of the DevTools Protocol, as in restart.integration-test.ts. Afterwards, each client's previews load that client's latest files, and the recovery of one client does not replace the project of the other.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions