npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@bobfrankston/msgview

v1.0.139

Published

Cross-platform HTML message display using Electron

Downloads

3,325

Readme

@bobfrankston/msgview

Cross-platform HTML message display using Electron. Can be used as both a command-line tool and a library in your Node.js/Electron applications.

Display custom dialogs, forms, and web pages with full HTML/CSS/JavaScript support. Perfect for CLI tools that need user interaction or Electron apps needing flexible dialogs.

msgview vs msger

This package is the Electron-based implementation. There's also @bobfrankston/msger - a Rust/wry-based alternative with the same API.

| Aspect | msgview (this) | msger | |--------|----------------|-------| | Startup | ~2-3 seconds | ~50-200ms | | Size | ~200MB | ~5-10MB | | Technology | Electron | Rust + wry (WebView2/webkit2gtk) | | Pi/Linux | ✅ Perfect | ❌ Rendering issues | | Windows/WSL | ✅ Works | ✅ Works |

When to use msgview:

  • Running on Raspberry Pi or Linux ARM
  • Need reliable cross-platform rendering
  • Already using Electron in your project

When to use msger:

  • Speed is critical (CLI tools, automation)
  • Binary size matters
  • Windows/WSL environment

Both tools have identical CLI flags and API options. A platform app API (window.msgapi) for file system access and window manipulation is planned for both.

See the TODO.md for feature comparison and roadmap.

Installation

npm install @bobfrankston/msgview

For global CLI usage:

npm install -g @bobfrankston/msgview

Usage

As a Library (Recommended)

import { app } from 'electron';
import { showMessageBox } from '@bobfrankston/msgview';

app.whenReady().then(async () => {
    const result = await showMessageBox({
        title: 'Confirm Action',
        message: 'Are you sure you want to proceed?',
        buttons: ['Cancel', 'OK']
    });

    console.log('User clicked:', result.button);

    if (result.value) {
        console.log('User entered:', result.value);
    }
});

As a CLI Tool

# Simple message
msgview "Hello, World!"

# Message with custom buttons
msgview "Save changes?" -buttons Yes No Cancel

# With HTML formatting
msgview -html "<h1>Welcome</h1><p>Please provide your <strong>full name</strong></p>"

# Display a web page or local HTML file
msgview -url "https://example.com"
msgview -url "./path/to/page.html"

# Full page mode (complete HTML document)
msgview -full -html "<!DOCTYPE html><html>...<script>window.electronAPI.sendResult({button:'OK'})</script></html>"

# With input field
msgview "Enter your name:" -input

# Custom window size
msgview -url "https://example.com" -size 1024,768

# Window position
msgview -url "https://example.com" -pos 100,200

# Position on specific screen (Windows only, screen index 1)
msgview -url "https://example.com" -pos 100,200,1

# Initial zoom at 125%
msgview -url "https://example.com" -zoom 125

# Large window with 150% zoom for better readability
msgview -url "https://example.com" -size 1280,800 -zoom 150

# Auto-close after 5 seconds
msgview "This will close automatically" -timeout 5

# Detached mode (launch and continue)
msgview "Processing..." -detach

# Get help
msgview -help

API

showMessageBox(options: MessageBoxOptions): Promise<MessageBoxResult>

Display a message box dialog and wait for user response.

MessageBoxOptions

interface MessageBoxOptions {
    title?: string;          // Window title (default: "msgview v1.0.x")
    message?: string;        // Plain text message (optional if url or html is provided)
    html?: string;           // HTML content to display
    url?: string;            // Load external URL or file path
    fullPage?: boolean;      // Display HTML as complete page (not wrapped in template)
    size?: {                 // Window size in pixels
        width: number;       // Window width (default: 600, 1024 for URL mode)
        height: number;      // Window height (default: 400, 768 for URL mode)
    };
    pos?: {                  // Window position
        x: number;           // X coordinate in pixels
        y: number;           // Y coordinate in pixels
        screen?: number;     // Optional screen index (0-based, Windows only)
    };
    zoom?: number;           // Initial zoom percentage (100=100%, 150=150%, 50=50%, etc.)
    buttons?: string[];      // Button labels (default: ["OK"])
    defaultValue?: string;   // Default input value
    allowInput?: boolean;    // Show input field (default: false)
    devTools?: boolean;      // Open Chrome DevTools (default: false)
    timeout?: number;        // Auto-close after specified seconds
}

MessageBoxResult

interface MessageBoxResult {
    button: string;                // Label of clicked button (or 'error', 'closed', 'dismissed')
    value?: string;                // Input value (if allowInput was true)
    form?: Record<string, any>;    // Form data (if HTML contains forms)
    closed?: boolean;              // True if window was closed via X button
    dismissed?: boolean;           // True if dismissed via ESC key
    error?: {                      // Present when button === 'error' (load failures)
        code: number;              // Chromium error code (e.g., -102 for connection refused)
        description: string;       // Technical error description
        url: string;               // URL that failed to load
    };
}

Examples

Simple Confirmation Dialog

const result = await showMessageBox({
    title: 'Delete File',
    message: 'Are you sure you want to delete this file?',
    buttons: ['Cancel', 'Delete']
});

if (result.button === 'Delete') {
    // Perform deletion
}

Input Dialog

const result = await showMessageBox({
    title: 'Enter Name',
    message: 'Please enter your name:',
    allowInput: true,
    defaultValue: 'John Doe',
    buttons: ['Cancel', 'OK']
});

if (result.button === 'OK') {
    console.log('Name entered:', result.value);
}

HTML Content

const result = await showMessageBox({
    title: 'Welcome',
    message: 'Welcome message',
    html: `
        <div style="font-family: sans-serif;">
            <h2 style="color: #2563eb;">Welcome!</h2>
            <p>This supports <strong>HTML</strong> content.</p>
            <ul>
                <li>Rich formatting</li>
                <li>Custom styles</li>
                <li>Any HTML elements</li>
            </ul>
        </div>
    `,
    size: { width: 700, height: 500 },
    buttons: ['Close']
});

Custom Buttons

const result = await showMessageBox({
    title: 'Save Changes',
    message: 'Do you want to save your changes?',
    buttons: ['Don\'t Save', 'Cancel', 'Save']
});

switch (result.button) {
    case 'Save':
        // Save changes
        break;
    case 'Don\'t Save':
        // Discard changes
        break;
    case 'Cancel':
        // Do nothing
        break;
}

Display External URL or Local File

// Load a web page
const result = await showMessageBox({
    message: '',  // Not used in URL mode
    url: 'https://example.com',
    size: { width: 1024, height: 768 },
    buttons: ['Close']
});

// Load a local HTML file (path auto-converts to file:// URL)
const result = await showMessageBox({
    message: '',
    url: './docs/help.html',
    size: { width: 800, height: 600 }
});

// Position window at specific location
const result = await showMessageBox({
    url: 'https://example.com',
    size: { width: 1024, height: 768 },
    pos: { x: 100, y: 200 }
});

// Position on specific screen (Windows only)
const result = await showMessageBox({
    url: 'https://example.com',
    size: { width: 1024, height: 768 },
    pos: { x: 100, y: 200, screen: 1 }  // Screen index 1
});

Full Page Mode (Complete HTML Document)

Use fullPage: true to display a complete HTML document without the msgview template wrapper. Perfect for programmatically generated UIs.

const htmlContent = `
<!DOCTYPE html>
<html>
<head>
    <title>Custom Form</title>
    <style>
        body { font-family: Arial; padding: 40px; }
        button { padding: 10px 20px; margin: 5px; }
    </style>
</head>
<body>
    <h1>User Registration</h1>
    <form id="myForm">
        <input type="text" name="username" placeholder="Username" required>
        <input type="email" name="email" placeholder="Email" required>
        <button type="submit">Submit</button>
    </form>
    <script>
        document.getElementById('myForm').onsubmit = async (e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            const data = Object.fromEntries(formData);
            await window.electronAPI.sendResult({
                button: 'Submit',
                form: data
            });
        };
    </script>
</body>
</html>
`;

const result = await showMessageBox({
    message: '',  // Not used in fullPage mode
    html: htmlContent,
    fullPage: true,
    width: 600,
    height: 400
});

console.log('Form data:', result.form);
// { username: 'john', email: '[email protected]' }

Custom HTML with Buttons

When using custom HTML (via -html, -full, or -url), you can create buttons that return results using the window.electronAPI.sendResult() function:

const result = await showMessageBox({
    message: '',
    html: `
        <div style="padding: 20px;">
            <h2>Choose an option</h2>
            <button onclick="window.electronAPI.sendResult({button: 'Option1', value: 'data1'})">
                Option 1
            </button>
            <button onclick="window.electronAPI.sendResult({button: 'Option2', value: 'data2'})">
                Option 2
            </button>
        </div>
    `,
    buttons: []  // Don't show default buttons
});

console.log('User selected:', result.button, result.value);

Important: When using custom HTML buttons, the electronAPI is exposed via the preload script. Your buttons must call window.electronAPI.sendResult(result) to close the dialog and return data.

Features

  • ✅ Cross-platform (Windows, macOS, Linux/WSL)
  • ✅ TypeScript support with full type definitions
  • ✅ HTML content support with custom styling
  • ✅ Load external URLs and local HTML files
  • ✅ Full page mode for complete HTML documents
  • ✅ Customizable buttons with custom handlers
  • ✅ Input field and form support
  • ✅ Form data collection and validation
  • ✅ Modern, clean UI with responsive design
  • ✅ Promise-based async API
  • ✅ ESC key to dismiss (returns {button: 'dismissed'})
  • ✅ Detached mode for non-blocking displays
  • ✅ Window positioning with multi-screen support (Windows)
  • ✅ Auto-close timeout support
  • ✅ Zoom controls (Ctrl +/- or Ctrl+MouseWheel)
  • ✅ Right-click context menu
  • ✅ Can be used as library or CLI tool

CLI Options

Options:
  -message <text>         Specify message text (use quotes for multi-word)
  -title <text>           Set window title
  -html <html>            Display HTML formatted message
  -url <url>              Load and display a web page or file
  -full                   Display -html as complete page (not wrapped in template)
  -buttons <label...>     Specify button labels (e.g., -buttons Yes No Cancel)
  -ok                     Add OK button
  -cancel                 Add Cancel button
  -input                  Include an input field
  -timeout <seconds>      Auto-close after specified seconds
  -size <width,height>    Set window size (e.g., -size 800,600)
  -pos <x,y[,s]>          Set window position (e.g., -pos 100,200 or -pos 100,200,1 for screen index 1)
  -zoom <percent>         Set initial zoom percentage (100=100%, 150=150%, 50=50%)
  -detach                 Launch viewer without waiting for it to close
  -dev                    Show demo message with dev tools
  -help, -?, --help       Show this help message

Window Positioning

Position windows at specific screen coordinates using the pos option (API) or -pos flag (CLI).

Basic Positioning

CLI examples:

# Position at screen coordinates (100, 200)
msgview -message "Hello" -pos 100,200

# Position a web viewer
msgview -url "https://example.com" -pos 300,100

API example:

const result = await showMessageBox({
    message: 'Positioned window',
    pos: { x: 100, y: 200 }
});

Multi-Screen Positioning (Windows Only)

On Windows, you can specify a screen index to position relative to a specific monitor:

CLI examples:

# Position at (100, 200) on screen index 0 (primary monitor)
msgview -message "Hello" -pos 100,200,0

# Position at (100, 200) on screen index 1 (secondary monitor)
msgview -message "Hello" -pos 100,200,1

API example:

const result = await showMessageBox({
    message: 'On secondary monitor',
    pos: { x: 100, y: 200, screen: 1 }  // Screen index 1
});

Note: Screen indices are 0-based. If an invalid screen index is specified, the coordinates are used as-is and a warning is logged.

Auto-Close Timeout

Automatically close the message box after a specified number of seconds using the timeout option (API) or -timeout flag (CLI). When a timeout occurs, the result will have button: 'timeout'.

CLI examples:

# Auto-close after 5 seconds
msgview "This will close in 5 seconds" -timeout 5

# Notification that auto-dismisses
msgview "Build completed successfully!" -timeout 3

API example:

const result = await showMessageBox({
    message: 'Auto-closing notification',
    timeout: 5  // Close after 5 seconds
});

if (result.button === 'timeout') {
    console.log('Message box timed out');
}

Zoom Control

Initial Zoom Percentage

Set the initial zoom when opening a window using the -zoom flag (CLI) or zoom option (API). Values are simple percentages:

  • 100 = 100% (default, normal size)
  • 150 = 150% (1.5x larger)
  • 200 = 200% (2x larger)
  • 75 = 75% (smaller)
  • 50 = 50% (half size)

CLI examples:

# 125% zoom for better readability
msgview -url "https://example.com" -zoom 125

# 75% zoom to see more content
msgview -url "https://example.com" -zoom 75

# 200% zoom for presentations
msgview -url "./slides.html" -zoom 200 -size 1920,1080

# 50% zoom to see entire page
msgview -url "./diagram.html" -zoom 50

API example:

const result = await showMessageBox({
    message: '',
    url: 'https://example.com',
    zoom: 125,  // Start at 125% zoom
    size: { width: 1280, height: 800 }
});

Runtime Zoom Controls

Users can always adjust zoom during runtime:

  • Ctrl + or Ctrl = - Zoom in
  • Ctrl - - Zoom out
  • Ctrl 0 - Reset to default (100%)
  • Ctrl + Mouse Wheel - Zoom in/out

Custom HTML Integration

window.electronAPI

When using custom HTML (via -html, -full, or -url), a special API is available:

// Send result back to the caller
await window.electronAPI.sendResult({
    button: 'ButtonName',     // Required: button identifier
    value: 'optional data',   // Optional: single value
    form: {...}              // Optional: form data object
});

Examples

Simple button:

<button onclick="window.electronAPI.sendResult({button: 'OK'})">OK</button>

Button with data:

<button onclick="window.electronAPI.sendResult({button: 'Submit', value: document.getElementById('name').value})">
    Submit
</button>

Form submission:

<form id="myForm">
    <input name="username" required>
    <input name="email" type="email" required>
    <button type="submit">Submit</button>
</form>
<script>
    document.getElementById('myForm').onsubmit = async (e) => {
        e.preventDefault();
        const data = Object.fromEntries(new FormData(e.target));
        await window.electronAPI.sendResult({ button: 'Submit', form: data });
    };
</script>

ESC Key Support

Add ESC key support to your custom HTML:

document.addEventListener('keydown', async (event) => {
    if (event.key === 'Escape') {
        await window.electronAPI.sendResult({ button: 'dismissed', dismissed: true });
    }
});

Platform Support & Limitations

Windows

  • ✅ Fully supported on Windows 10/11
  • ✅ Works in local console sessions
  • Not supported over SSH/Remote Desktop - requires a graphical display session
  • The tool detects SSH sessions and exits with a clear error message

Linux / WSL

  • ✅ Supported on Linux with X11 display
  • ✅ WSL2 with WSLg (Windows 11) - works out of the box
  • ✅ WSL with X server (VcXsrv, Xming) - requires DISPLAY environment variable
  • ⚠️ Runs with --no-sandbox flag automatically (required for WSL/root)
  • ❌ Headless servers without X11 - not supported

WSL Setup:

# WSL2 on Windows 11 - no setup needed (uses WSLg)

# WSL on Windows 10 - requires X server
# 1. Install VcXsrv or Xming on Windows
# 2. Start X server
# 3. Set DISPLAY variable:
export DISPLAY=:0
# or in ~/.bashrc:
echo 'export DISPLAY=:0' >> ~/.bashrc

macOS

  • ✅ Fully supported on macOS 10.13+

Headless Environments (SSH, Docker, CI/CD)

msgview requires a graphical display and will not work in headless environments:

# Running over SSH will fail with:
Error: msgview requires a graphical display.
This appears to be a headless environment (SSH session or no display).
Please run msgview from a local console or with X11 forwarding enabled.

Workarounds:

  1. SSH with X11 forwarding: ssh -X user@host (Linux/macOS only)
  2. Use in CI/CD with xvfb: Virtual framebuffer for testing
    xvfb-run msgview "Test message"
  3. Alternative: Use a different approach for headless automation (e.g., web-based UI, REST API)

GPU/Graphics Requirements

  • Requires DirectX 11 (Windows) or OpenGL (Linux/macOS)
  • Hardware or software GPU acceleration
  • May fail in VMs without proper graphics drivers

Known Issues

-title with -url

When using -title with -url, the custom title is set initially but will be overridden by the web page's <title> tag once it loads. If you need a persistent custom title, modify the HTML page itself.

Workaround: Omit -title and let the web page set its own title via the <title> tag.

Error Handling & Exit Codes

Exit Codes

When used as a CLI tool, msgview exits with the following codes:

  • 0 - Success (user clicked a button normally)
  • 1 - Initialization error (headless environment, GPU failure, etc.)
  • 2 - Load error (failed to load URL or file)
  • 3 - Dismissed (user pressed ESC or closed window)

Load Errors

When a URL or file fails to load, msgview returns a JSON error result:

{
  "button": "error",
  "value": "Load failed: Connection refused - server not responding",
  "error": {
    "code": -102,
    "description": "ERR_CONNECTION_REFUSED",
    "url": "http://localhost:9299"
  }
}

Common error codes:

  • -102 - Connection refused (server not running)
  • -105 - DNS lookup failed (hostname not found)
  • -106 - Internet disconnected
  • -118 - Connection timed out
  • -6 - File not found

Example handling:

const result = await showMessageBox({
    message: '',
    url: 'http://localhost:9299'
});

if (result.button === 'error') {
    console.error('Failed to load:', result.value);
    console.error('Error code:', result.error?.code);
    console.error('URL:', result.error?.url);
    // Handle error appropriately
}

CLI usage:

msgview -url "http://localhost:9299"
# Output:
# {
#   "button": "error",
#   "value": "Load failed: Connection refused - server not responding",
#   "error": { "code": -102, "description": "ERR_CONNECTION_REFUSED", "url": "http://localhost:9299" }
# }
# Exit code: 2

# Check exit code in shell
echo $?  # Linux/macOS
echo %ERRORLEVEL%  # Windows

Samples

Sample HTML files are included in the samples/ directory:

  • custom-button.html - Simple custom button example with styled layout
  • form-example.html - Complete form with validation and styling
  • full-page-example.html - Demonstrates full page mode with header/footer

Usage:

msgview -url "./samples/custom-button.html"
msgview -url "./samples/form-example.html"

Or programmatically:

import { showMessageBox } from '@bobfrankston/msgview';
import { readFileSync } from 'fs';

const html = readFileSync('./samples/full-page-example.html', 'utf-8');
const result = await showMessageBox({
    message: '',
    html,
    fullPage: true,
    width: 800,
    height: 600
});

Development

# Install dependencies
npm install

# Build TypeScript
npm run build

# Run standalone demo
npm start

# Run examples
npm run build && node example-usage.js

Project Structure

msgview-electron/
├── index.ts          # Library API
├── main.ts           # Electron main process & CLI entry
├── preload.ts        # IPC bridge
├── renderer.ts       # Frontend logic
├── index.html        # UI template
├── example-usage.ts  # Usage examples
├── package.json
├── tsconfig.json
└── README.md

TypeScript Configuration

This project follows Bob Frankston's TypeScript standards:

  • ES modules with NodeNext resolution
  • Co-located .ts, .js, .d.ts, and .js.map files
  • No separate src/ and dist/ directories
  • Strict mode with strictNullChecks: false
  • LF line endings

License

ISC

Author

Bob Frankston