SpeedOf.Me API - React Integration

This example demonstrates how to integrate the SpeedOf.Me speed test API with React.

Live Demo

A working example built on CDN builds of React and Babel is linked from this folder's page. Opening the source file directly will not run a test: the API validates the page URL against your registered domain, so serve your copy over http or https from that domain.

Prerequisites

Quick Start

Option 1: CDN (for quick prototyping)

The index.html file shows how to use React with CDN builds. This is great for demos but not recommended for production.

Option 2: Create React App / Vite (recommended for production)

  1. Create a new React project:
    npm create vite@latest my-speedtest -- --template react
    cd my-speedtest
    npm install
  2. Add the API script to index.html:
    <script src="https://speedof.me/api/api.js"></script>
  3. Create the SpeedTest hook (src/hooks/useSpeedTest.js):
    import { useState, useEffect, useCallback } from 'react';
    
    export function useSpeedTest({ apiKey, domain, sustainTime = 6 }) {
        const [status, setStatus] = useState('idle');
        const [result, setResult] = useState(null);
        const [progress, setProgress] = useState(null);
        const [error, setError] = useState(null);
    
        useEffect(() => {
            window.SomApi.account = apiKey;
            window.SomApi.domainName = domain;
            window.SomApi.config.sustainTime = sustainTime;
    
            window.SomApi.onTestCompleted = (testResult) => {
                setResult(testResult);
                setProgress(null);
                setStatus('completed');
            };
    
            window.SomApi.onProgress = setProgress;
    
            window.SomApi.onError = (err) => {
                setError(err);
                setStatus('error');
            };
        }, [apiKey, domain, sustainTime]);
    
        const startTest = useCallback(() => {
            setStatus('running');
            setResult(null);
            setError(null);
            window.SomApi.startTest();
        }, []);
    
        return { status, result, progress, error, startTest };
    }
  4. Use the hook in your component:
    import { useSpeedTest } from './hooks/useSpeedTest';
    
    function SpeedTest() {
        const { status, result, progress, error, startTest } = useSpeedTest({
            apiKey: 'YOUR_API_KEY',
            domain: 'your-domain.com'
        });
    
        return (
            <div>
                <button onClick={startTest} disabled={status === 'running'}>
                    {status === 'running' ? 'Testing...' : 'Start Test'}
                </button>
                {result && (
                    <div>
                        <p>Download: {result.download} Mbps</p>
                        <p>Upload: {result.upload} Mbps</p>
                    </div>
                )}
            </div>
        );
    }

Configuration

// Required
window.SomApi.account = 'YOUR_API_KEY';
window.SomApi.domainName = 'your-domain.com';

// Optional
window.SomApi.config.sustainTime = 6;        // 1-8 seconds (higher = more accurate)
window.SomApi.config.testServerEnabled = true;
window.SomApi.config.userInfoEnabled = true;
window.SomApi.config.latencyTestEnabled = true;
window.SomApi.config.uploadTestEnabled = true;
window.SomApi.config.progress.enabled = true;   // fire onProgress during the test
window.SomApi.config.progress.verbose = true;   // add currentSpeed and maxSpeed to it

Handling Results

The onTestCompleted callback receives a result object:

{
    download: 150.5,      // Download speed in Mbps
    upload: 25.3,         // Upload speed in Mbps ('' if uploadTestEnabled is false)
    maxDownload: 162.0,   // Peak download speed in Mbps
    maxUpload: 27.1,      // Peak upload speed in Mbps ('' if uploadTestEnabled is false)
    latency: 12,          // Latency in ms ('' if latencyTestEnabled is false)
    jitter: 3,            // Jitter in ms, whole number ('' if latencyTestEnabled is false)
    testServer: 'Los Angeles 3', // Test server location ('' if testServerEnabled is false)
    ip_address: '...',    // Client IP ('0.0.0.0' if userInfoEnabled is false)
    hostname: '...',     // Client hostname ('' if userInfoEnabled is false)
    userAgent: '...',    // Browser user agent string
    testDate: '...'      // Test timestamp, ISO 8601
}

Every field is always present. Disabling a test blanks its fields rather than removing them.

Progress Updates

The onProgress callback receives progress data during the test:

{
    type: 'download',     // 'latency', 'download' or 'upload'
    percentDone: 75,      // 0-100
    pass: 3,              // Current test pass ('' during the latency phase)
    currentSpeed: 145.2,  // Current speed in Mbps, verbose mode only: '' unless
                          // config.progress.verbose is true, and always ''
                          // on latency events
    maxSpeed: 162.0,      // Peak speed in Mbps, same rule as currentSpeed
    latency: '',          // Latency in ms, only on the final latency event
                          // (type 'latency' at percentDone 100), '' otherwise
    jitter: ''           // Jitter in ms, same rule as latency
}

Guard currentSpeed before formatting it, because the latency phase carries '':

const speed = typeof progress.currentSpeed === 'number'
    ? `${progress.currentSpeed.toFixed(1)} Mbps`
    : '';

Error Handling

The onError callback receives an error object:

{
    code: 1001,
    message: 'User data error: Invalid Account'
}

Error codes:

Code 2006, and 2005 with status 0, mean the request never left the browser, which is almost always an ad blocker, a content blocker or a privacy extension on the visitor's device. The one thing to check on your own side is a frame-src Content-Security-Policy: it must allow https://speedof.me, or the engine iframe can never load. A 2005 carrying any other status is a real HTTP response, so treat it as a server-side failure.

Production Considerations

  1. API Key Security: Consider using environment variables and a backend proxy to avoid exposing your API key in client-side code.
  2. Loading State: Show a loading indicator while the API script loads.
  3. Cleanup: If your component unmounts during a test, handle gracefully.
  4. Mobile Considerations: Speed tests use significant bandwidth. Consider warning users on mobile data.

TypeScript

For TypeScript projects, declare the SomApi global in src/types/speedofme.d.ts:

// Every field is always present. A disabled sub-test sends an empty string
// rather than omitting the key, which is what the unions below encode.
interface SpeedTestResult {
    download: number;
    upload: number | '';       // '' when uploadTestEnabled is false
    maxDownload: number;
    maxUpload: number | '';    // '' when uploadTestEnabled is false
    latency: number | '';      // '' when latencyTestEnabled is false
    jitter: number | '';       // '' when latencyTestEnabled is false
    testServer: string;         // '' when testServerEnabled is false
    ip_address: string;         // '0.0.0.0' when userInfoEnabled is false
    hostname: string;           // '' when userInfoEnabled is false
    userAgent: string;
    testDate: string;           // ISO 8601
}

interface SpeedTestProgress {
    type: 'download' | 'upload' | 'latency';
    pass: number | '';         // '' during the latency phase
    percentDone: number;        // 0-100
    currentSpeed: number | ''; // Mbps, verbose only, always '' on latency events
    maxSpeed: number | '';     // Mbps, verbose only, '' until the max exceeds 0
    latency: number | '';      // ms, on the final latency event only
    jitter: number | '';       // ms, on the final latency event only
}

interface SpeedTestError {
    code: number;
    message: string;
}

interface SomApiConfig {
    sustainTime: number;
    maxTestPass: number;
    testTimeout: number;
    testServerEnabled: boolean;
    userInfoEnabled: boolean;
    latencyTestEnabled: boolean;
    uploadTestEnabled: boolean;
    progress: { enabled: boolean; verbose: boolean };
}

interface SomApi {
    account: string;
    domainName: string;
    config: SomApiConfig;
    startTest: () => void;
    abortTest: () => void;
    onTestCompleted: (result: SpeedTestResult) => void;
    onProgress: (progress: SpeedTestProgress) => void;
    onError: (error: SpeedTestError) => void;
}

declare global {
    interface Window {
        SomApi: SomApi;
    }
}

// Required: `declare global` is only legal in a module, so a file with no
// other top-level import or export needs this line (TS2669).
export {};

Links