@bobfrankston/msgview
v1.0.139
Published
Cross-platform HTML message display using Electron
Downloads
3,325
Maintainers
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/msgviewFor global CLI usage:
npm install -g @bobfrankston/msgviewUsage
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 -helpAPI
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 messageWindow 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,100API 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,1API 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 3API 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 50API 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-sandboxflag 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' >> ~/.bashrcmacOS
- ✅ 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:
- SSH with X11 forwarding:
ssh -X user@host(Linux/macOS only) - Use in CI/CD with xvfb: Virtual framebuffer for testing
xvfb-run msgview "Test message" - 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% # WindowsSamples
Sample HTML files are included in the samples/ directory:
custom-button.html- Simple custom button example with styled layoutform-example.html- Complete form with validation and stylingfull-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.jsProject 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.mdTypeScript Configuration
This project follows Bob Frankston's TypeScript standards:
- ES modules with NodeNext resolution
- Co-located
.ts,.js,.d.ts, and.js.mapfiles - No separate
src/anddist/directories - Strict mode with
strictNullChecks: false - LF line endings
License
ISC
Author
Bob Frankston
