This example demonstrates how to integrate the SpeedOf.Me speed test API into an Electron desktop application.
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.
speedtest.html from that domain over httpsnpm install
In speedtest.html:
SomApi.account = "YOUR_API_KEY";
SomApi.domainName = "your-domain.com";
Upload speedtest.html to your registered domain, then set the URL in main.js:
mainWindow.loadURL('https://your-domain.com/speedtest.html');
npm start
npm run build
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.
<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.
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);
});
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.
1001 Invalid Account: the API key is wrong or inactive1002 Domain Mismatch: the page URL does not match your registered domain2001 Test Error2002 Invalid server response2003 Request timeout2004 Test timeout: the test did not finish within config.testTimeout2005 Speed test could not start (status N)2006 Speed test engine did not loadIn 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.
# macOS
npm run build -- --mac
# Windows
npm run build -- --win
# Linux (outputs .deb, the linux target in package.json)
npm run build -- --linux
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();
frame-src https://speedof.me in particularspeedtest.html on your registered domain and load it with loadURL()contextIsolation: true