SpeedOf.Me API - Electron Integration

This example demonstrates how to integrate the SpeedOf.Me speed test API into an Electron desktop application.

How It Works

Electron is unique because it runs a full Chromium browser, so once the page is loaded the SpeedOf.Me API works directly in the renderer process, with no WebView wrapper or JavaScript bridge.

The catch is where that page comes from. The engine validates the page that embeds api.js against the domain registered on your account, and it reads that from the page's own URL. A page opened with loadFile() has a file:// URL and therefore no host, so it can never match and every test fails with error 1002 (Domain Mismatch).

So you host speedtest.html on your own registered domain and point the window at that https URL, which is what main.js does.

Files

Prerequisites

Quick Start

1. Install Dependencies

npm install

2. Configure API Credentials

In speedtest.html:

SomApi.account = "YOUR_API_KEY";
SomApi.domainName = "your-domain.com";

3. Publish the Renderer and Point the App at It

Upload speedtest.html to your registered domain, then set the URL in main.js:

mainWindow.loadURL('https://your-domain.com/speedtest.html');

4. Run the App

npm start

5. Build for Distribution

npm run build

Architecture

Electron App
   ├── Main Process (main.js)
   │       └── BrowserWindow
   │               └── preload.js (context bridge)
   │
   └── Renderer Process (speedtest.html, loaded over https
           │                from your registered domain)
           └── SpeedOf.Me API (direct)
                   ▼
           Results displayed directly in DOM
           + Optional IPC to main process

Unlike other desktop examples, Electron doesn't need a WebView wrapper because the renderer process IS a browser. It does still need the page to come from your registered domain over https, for the reason given above.

Content Security Policy

<meta http-equiv="Content-Security-Policy" content="
    default-src 'self';
    script-src 'self' 'unsafe-inline' https://speedof.me;
    frame-src https://speedof.me;
    style-src 'self' 'unsafe-inline';
">

frame-src is the directive that matters. api.js runs the test inside a hidden iframe on speedof.me, so without it default-src 'self' blocks the engine and every test fails with error 2006. There is no connect-src entry for the transfers because they happen inside that iframe, on its own origin, where this page's CSP does not apply.

IPC Communication (Optional)

Preload:

contextBridge.exposeInMainWorld('electronAPI', {
    onTestCompleted: (result) => {
        ipcRenderer.send('speed-test-completed', result);
    }
});

Main:

ipcMain.on('speed-test-completed', (event, result) => {
    console.log('Speed test completed:', result);
});

Progress Events

onProgress fires for three phases: latency, then download, then upload.

progress.type          // 'download', 'upload' or 'latency'
progress.pass          // pass number, '' during the latency phase
progress.percentDone   // 0-100
progress.currentSpeed  // Mbps, only with progress.verbose, '' otherwise
progress.maxSpeed      // Mbps, only with progress.verbose, '' otherwise
progress.latency       // ms, on the final latency event only, '' otherwise
progress.jitter        // ms, on the final latency event only, '' otherwise

Set SomApi.config.progress.verbose = true or the speed fields arrive as empty strings. They stay empty on latency events even when verbose is on, so guard before formatting them.

Error Codes

In an Electron app 2006 is the CSP symptom: a frame-src that does not allow https://speedof.me stops the engine iframe loading, so check that directive first. 2005 is a different failure, because the validate request is issued from inside that iframe, on its own origin, where this page's CSP does not apply. A 2005 with status 0 means something on the device blocked it; any other status is a real HTTP response. 1002 means the renderer was loaded from a file:// URL instead of your registered domain.

Building for Production

# macOS
npm run build -- --mac

# Windows
npm run build -- --win

# Linux (outputs .deb, the linux target in package.json)
npm run build -- --linux

Production Considerations

  1. API Key Security: Consider fetching from your backend
  2. Auto-Updates: Use electron-updater
  3. Code Signing: Sign your app for distribution (Apple/Windows certificates)
  4. Menu Bar: Add proper application menu

Electron-Specific Features

System Tray:

const { Tray } = require('electron');
const tray = new Tray('/path/to/icon.png');

Notifications:

new Notification({
    title: 'Speed Test Complete',
    body: `Download: ${result.download} Mbps`
}).show();

Troubleshooting

Links