Search code examples
visual-studio-codevscode-extensionsvscode-remote

Why does localhost not work inside my VS Code's webviews when connected to remote workspaces?


I have a simple extension that uses VS Code's webview api to load content from a server that my extension spawns. It does this using an iframe that points to localhost:

import * as vscode from 'vscode';
import * as express from 'express';

const PORT = 3000;

export function activate(context: vscode.ExtensionContext) {

    // Spawn simple server
    const app = express();
    app.get('/', (_req, res) => res.send('Hello VS Code!'));
    app.listen(PORT)

    context.subscriptions.push(
        vscode.commands.registerCommand('myExtension.startPreview', () => {
            const panel = vscode.window.createWebviewPanel('myExtension.preview', 'Preview', vscode.ViewColumn.One,
                {
                    enableScripts: true
                });

            panel.webview.html = `<!DOCTYPE html>
            <html lang="en"">
            <head>
                <meta charset="UTF-8">
                <title>Preview</title>
                <style>
                    html { width: 100%; height: 100%; min-height: 100%; display: flex; }
                    body { flex: 1; display: flex; }
                    iframe { flex: 1; border: none; background: white; }
                </style>
            </head>
            <body>
                <iframe src="http://localhost:${PORT}"></iframe>
            </body>
            </html>`
        }));
}

This works fine when the extension runs locally, but when I try running my extension in a remote workspace the iframe is empty:

Empty localhost iframe when in a remote workspace

Why is this happening and how do I fix it?


Solution

  • This is expected when using localhost inside a webview in a remote workspaces. The root cause is that webviews are loaded on the users's local machine, while your extension is running on the remote machine:

    Localhost in the webview resolves to the local machine

    There are two possible fixes for this:

    Mark your extension as a UI Extension so that it is always run on the local machine.

    You can make your extension a UI Extension if it does not need to read files from the workspace or use scripts/tools that would only be installed with the workspace. UI extension's are run on the user's local machine, so the server it spawns is accessible using localhost

    Just add "extensionKind": "ui" to your extension's package.json to do this.

    Configure portMapping for your webview

    The VS Code port mapping API allows you to map a localhost port used inside your webview to an arbitrary port on the machine where your extension is running. This even works if your extension is running remotely.

    The port mapping API was added in VS Code 1.34. To use it, pass in a portMapping object when you create the webview panel:

    const panel = vscode.window.createWebviewPanel('myExtension.preview', 'Preview', vscode.ViewColumn.One,
        {
            enableScripts: true,
            // This maps localhost:3000 in the webview to the express server port on the remote host.
            portMapping: [
                { webviewPort: PORT, extensionHostPort: PORT}
            ]
        });
    

    If your webview uses localhost for any reason, it is a good idea to set portMapping so that it can be properly run anywhere. In practice, you should also consider randomizing which port your server runs on. With port mapping, you don't have change the webview's html for this:

    { webviewPort: 3000, extensionHostPort: RANDOM_PORT_SERVER_WAS_STARTED_ON }
    

    See the VS Code docs for more details