[Personal-WP] Add direct Personal WP desktop access#3758
Open
akirk wants to merge 38 commits into
Open
Conversation
aa0319e to
07016be
Compare
927b078 to
cb50804
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Implements “Personal WP desktop access” via a signaling-only PHP relay plus a direct browser-to-browser WebRTC DataChannel tunnel, enabling desktop viewing/control of the phone-hosted Playground runtime.
Changes:
- Add a service-worker “desktop relay” request bridge for
/scope:default/*traffic. - Add phone (host) and desktop (guest) tunnel implementations using WebRTC DataChannels, including approval + backup download over the tunnel.
- Add a minimal PHP/MySQL relay for session rendezvous + signaling (with Apache rewrite routing).
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.base.json | Adds a TS path alias for importing the remote service worker entry. |
| packages/playground/remote/src/lib/desktop-relay.ts | Implements service-worker-side mapping, probing, and request forwarding to the desktop client. |
| packages/playground/remote/src/lib/desktop-relay.spec.ts | Adds unit tests for header/body serialization helpers used by the desktop relay. |
| packages/playground/remote/service-worker.ts | Wires desktop relay messaging + fetch interception into the remote service worker. |
| packages/playground/personal-wp/vite.config.ts | Configures worker bundling so the imported service worker builds to sw.js. |
| packages/playground/personal-wp/src/main.tsx | Routes /connect and ?share= viewer flows outside the normal app shell. |
| packages/playground/personal-wp/src/lib/desktop-access-tunnel-utils.ts | Adds shared helpers for attempt scoping, verification codes, filenames, and ICE candidate buffering. |
| packages/playground/personal-wp/src/lib/desktop-access-service.ts | Adds a thin app-facing service wrapper to start/stop/approve desktop access. |
| packages/playground/personal-wp/src/lib/desktop-access-direct-tunnel.ts | Implements WebRTC signaling + DataChannel HTTP tunneling and backup transfer (host + guest). |
| packages/playground/personal-wp/src/lib/desktop-access-direct-tunnel.spec.ts | Adds unit tests for desktop access helper utilities (attempt scoping, codes, filenames, ICE buffering). |
| packages/playground/personal-wp/src/components/site-manager/site-info-panel/style.module.css | Adds UI styles for desktop access controls + diagnostics within Site Tools. |
| packages/playground/personal-wp/src/components/site-manager/site-info-panel/index.tsx | Adds the “Use on desktop” section, start/stop/share/copy, and approval UI. |
| packages/playground/personal-wp/src/components/desktop-access-viewer/style.module.css | Adds desktop viewer layout styles, status/diagnostics UI, and overlays. |
| packages/playground/personal-wp/src/components/desktop-access-viewer/index.tsx | Implements the desktop viewer: SW registration, request bridging, WebRTC guest, and backup download. |
| packages/playground/personal-wp/src/components/desktop-access-connect/style.module.css | Adds styling for the /connect access-code entry page. |
| packages/playground/personal-wp/src/components/desktop-access-connect/index.tsx | Implements /connect access-code resolution and route detection helpers. |
| packages/playground/personal-wp/src/components/desktop-access-connect/index.spec.ts | Adds tests for /connect route + code normalization/formatting helpers. |
| packages/playground/personal-wp/public/relay.php | Adds the signaling-only PHP relay backed by normalized MySQL tables. |
| packages/playground/personal-wp/.htaccess | Routes /relay/* requests to relay.php. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+4
to
+5
| * The phone keeps running the real Playground runtime. Desktop browsers only | ||
| * send HTTP requests through the relay and render the responses. |
Comment on lines
+240
to
+245
| const desktopRelayMapping = getDesktopRelayMapping(scope); | ||
| if (desktopRelayMapping) { | ||
| return event.respondWith( | ||
| handleDesktopRelayRequest(event, desktopRelayMapping) | ||
| ); | ||
| } |
Comment on lines
+627
to
+635
| private queueDataChannelMessage(data: unknown, attemptId: string): void { | ||
| if (typeof data !== 'string' || !this.dataChannel) { | ||
| return; | ||
| } | ||
| const request = JSON.parse(data) as DataChannelHostMessage; | ||
| if (isDataChannelControlMessage(request)) { | ||
| this.handleDataChannelControlMessage(request, attemptId); | ||
| return; | ||
| } |
Comment on lines
+1539
to
+1549
| channel.onmessage = (event) => { | ||
| if (typeof event.data !== 'string') { | ||
| return; | ||
| } | ||
| const response = JSON.parse(event.data) as DataChannelGuestMessage; | ||
| if ( | ||
| isDataChannelControlMessage(response) && | ||
| response.attemptId !== attemptId | ||
| ) { | ||
| return; | ||
| } |
Comment on lines
+814
to
+820
| await this.sendDataChannelResponse({ | ||
| type: 'response', | ||
| requestId: request.requestId, | ||
| status: phpResponse.httpStatusCode, | ||
| headers: responseHeaders, | ||
| body: uint8ArrayToBase64(phpResponse.bytes), | ||
| }); |
| ) | ||
| VALUES (?, ?, ?, ?, ?, ?)' | ||
| ); | ||
| $stmt->bind_param('sssssi', $sessionId, $from, $to, $type, $encodedData, $now); |
Comment on lines
+347
to
+353
| function normalizeVerificationCode(value: string): string { | ||
| return value.replace(/\D+/g, '').slice(0, 2); | ||
| } | ||
|
|
||
| function formatVerificationCode(value: string): string { | ||
| return normalizeVerificationCode(value); | ||
| } |
Comment on lines
+6
to
+7
| // @ts-ignore | ||
| import serviceWorkerPath from '@wp-playground/remote/service-worker?worker&url'; |
| * Phone-side direct tunnel. The relay is only used to exchange WebRTC | ||
| * signaling messages; WordPress HTTP requests are handled over the data channel. | ||
| */ | ||
| export class DirectTunnelHost { |
| return fallback; | ||
| } | ||
|
|
||
| export class DirectTunnelGuest { |
07a1cc4 to
9a8b364
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Demo
mywp-connect.mp4
What It Does
This is a more privacy friendly alternative to #3746. It keeps the same product goal: let someone open the Personal WP instance running on their phone from a desktop browser while the phone remains nearby and connected.
The main difference is transport. #3746 proxies WordPress HTTP requests and responses through the PHP relay. This PR uses the PHP relay only for rendezvous and WebRTC signaling, then sends WordPress HTTP traffic over a direct browser-to-browser WebRTC DataChannel.
Related PRs:
Important Implementation Notes
seqvalues are only used as polling cursors/order markers.SIGNAL_RETENTION_MS, not modulo slots or a retained sequence tail.User Flow
On the phone:
Use on desktopsection, start desktop access./connectplus a six-digit access code.On the desktop:
/connecton the same Personal WP deployment.42.Allow, or press Enter on the phone keyboard.Download backupin the desktop viewer header to request and download a zip backup from the running phone site over the WebRTC tunnel.While connected, the desktop page renders the WordPress instance that is still running on the phone. The visible desktop URL is normalized to
/connectwhile connecting, then cleaned up to paths like/connect/wp-admin/once the iframe navigates so the user can tell where they are without seeing the long session id. These are currently transient viewer URLs; reload is not expected to restore the session.Architecture
The PHP relay is only a rendezvous/signaling service. It creates short-lived sessions, resolves six-digit access codes, records host/guest heartbeats, and stores small WebRTC signaling messages while the browser peers connect.
It does not expose a
/relay/:session/request/*HTTP proxy in this branch.The request path is:
/scope:default/....playgroundClient.request(...).Responsefor the iframe.So WordPress request/response bodies are intended to stay out of PHP and MySQL in this approach.
Relay Storage
The relay now uses three normalized MySQL tables instead of a session-level JSON blob.
mywp_desktop_access_sessionsStores session metadata and the phone host heartbeat:
mywp_desktop_access_signalsStores ordered WebRTC/control messages addressed from one peer to the other:
seqis only the delivery cursor/order marker for polling. Cleanup is time-based: old signal rows are deleted afterSIGNAL_RETENTION_MSinstead of keeping a sequence-number tail.mywp_desktop_access_guestsStores desktop viewer heartbeats:
The relay expects these tables to already exist. It does not create or migrate
database schema at runtime.
Pairing and Approval
The six-digit access code is only the rendezvous step. It lets a desktop find the current phone session, but it is not enough to access WordPress.
After the WebRTC channel opens:
This is meant to protect against brute-forcing the six-digit rendezvous code. A guessed code can reach the approval gate, but the user still has to confirm the desktop from the phone.
Desktop Viewer Details
The desktop viewer registers the Playground service worker at
/sw.jswith root scope and verifies that/scope:default/is controlled before loading the iframe.The iframe starts at
about:blankand is only navigated once the service worker and DataChannel are ready. This avoids accidentally booting a local Playground instance in the desktop iframe.The viewer preserves the loaded iframe during reconnects. Reconnect state is shown in the header/debug UI instead of blocking the already-loaded page.
A compact
Debugdisclosure in the header shows relay diagnostics such as:The status pill also exposes the same diagnostics via its
titleattribute.Difference From #3746
#3746:
This PR:
trunk.TunnelHostHTTP polling implementation.Privacy and Data Handling
This approach reduces what the relay sees. MySQL temporarily stores:
It should not store WordPress HTTP bodies, cookies, admin requests, HTML, CSS, JS, media, form submissions, or backup zip contents. The desktop and phone exchange those request/response payloads and desktop-requested backup chunks directly over the WebRTC DataChannel.
Server Configuration
The relay reads DB configuration from these env/server variables, in order:
PLAYGROUND_RELAY_DB_HOST,MYWP_DB_HOST,DB_HOSTPLAYGROUND_RELAY_DB_USER,MYWP_DB_USER,DB_USERPLAYGROUND_RELAY_DB_PASSWORD,MYWP_DB_PASSWORD,DB_PASSWORDPLAYGROUND_RELAY_DB_NAME,MYWP_DB_NAME,DB_NAMEPLAYGROUND_RELAY_DB_PORT,MYWP_DB_PORT,DB_PORTPLAYGROUND_RELAY_PUBLIC_BASE_URLmay be set to force generated desktop share URLs.Caveats
This is a prototype.
The WebRTC peer connection currently uses no STUN or TURN servers:
That keeps the first version focused on same-network/direct connectivity. It may fail on networks where the browsers cannot discover a usable direct route. Adding STUN would likely improve direct connection setup. Adding TURN would improve reliability further, but TURN would relay encrypted WebRTC traffic and change the privacy tradeoff.
The phone tab must stay open. The desktop is a viewer/control surface for the running phone Playground runtime.
The clean desktop URL, such as
/connect/wp-admin/, is currently cosmetic/transient. Reloading it is not expected to restore the desktop session.Installing apps via postMessage from the desktop viewer is still reported as unsupported for now.
Validation
Ran during this branch:
php -l packages/playground/personal-wp/public/relay.phpnpm exec nx typecheck playground-personal-wpnpm exec nx typecheck playground-remotegit diff --checkI did not run the full build or deployment scripts.