capacitor-network-metrics-sdk
v1.0.42
Published
Capacitor plugin for network metrics measurement (Android + iOS)
Maintainers
Readme
capacitor-network-metrics-sdk
Capacitor plugin for comprehensive network quality measurement on Android and iOS. Thin bridge over:
- Android: android-network-metrics-sdk via JitPack
- iOS: ios-network-metrics-sdk via SPM
Install
npm install capacitor-network-metrics-sdk
npx cap syncAndroid setup
android/build.gradle — add JitPack
repositories {
maven { url 'https://jitpack.io' }
}android/app/src/main/AndroidManifest.xml — permissions
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Background work -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />Register plugin in MainActivity.java
import com.kevindupas.networkmetricssdk.NetworkMetricsSdkPlugin;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
registerPlugin(NetworkMetricsSdkPlugin.class);
super.onCreate(savedInstanceState);
}
}iOS setup
Info.plist — required keys
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used to tag network measurements with GPS coordinates.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Used to tag background network measurements with GPS coordinates.</string>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.networkmetrics.refresh</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>Usage
import { NetworkMetricsSdk } from 'capacitor-network-metrics-sdk';
// Initialize once (e.g. in app startup)
await NetworkMetricsSdk.initialize({
backendUrl: 'https://your-backend.com/api/measurements',
authHeader: 'Bearer YOUR_TOKEN',
intervalMinutes: 15,
enableSpeed: true,
enablePacketLoss: true,
enableStreaming: true,
enableSocialLatency: true,
enableDns: true,
enableWebBrowsing: true,
udpHost: 'your-udp-server.com',
udpPort: 5005,
tcpPort: 5006,
remoteConfigUrl: 'https://your-backend.com/api/config/targets', // optional
});
// Trigger a measurement immediately
await NetworkMetricsSdk.measureNow();
// Read last stored result
const { json, timestamp } = await NetworkMetricsSdk.getLastResult();
if (json) {
const record = JSON.parse(json);
console.log('Download:', record.speed?.downloadMbps, 'Mbps');
console.log('MOS:', record.mos);
}API
initialize(options)
Starts the SDK and schedules periodic background measurements.
| Option | Type | Default | Description |
|---|---|---|---|
| backendUrl | string | required | POST endpoint for measurements |
| authHeader | string | — | Authorization header |
| intervalMinutes | number | 15 | Background interval |
| enableSpeed | boolean | true | Speed test |
| enablePacketLoss | boolean | true | Packet loss |
| enableStreaming | boolean | true | HLS simulation |
| enableSocialLatency | boolean | true | Social platform TTFB |
| enableDns | boolean | true | DNS timing |
| enableWebBrowsing | boolean | true | Web phase timing |
| udpHost | string | "" | UDP echo server |
| udpPort | number | 5005 | UDP port |
| tcpPort | number | 5006 | TCP fallback port |
| remoteConfigUrl | string | — | Remote web targets URL (1h cache) |
measureNow()
Triggers an immediate one-shot measurement cycle. Returns as soon as the measurement is queued (non-blocking).
getLastResult()
Returns the last stored measurement.
{ json: string | null, timestamp: number }
// timestamp = ms since epoch, 0 if no result yetgetGnssSatellites() (Android only)
Passive snapshot of GNSS satellites currently visible (GPS, GLONASS, Galileo, BeiDou, QZSS, SBAS, IRNSS). Registers a GnssStatus.Callback on first call and reuses it.
interface GnssSatellite {
svid: number;
constellation: 'GPS' | 'GLO' | 'GAL' | 'BDS' | 'QZS' | 'SBAS' | 'IRN' | 'OTH';
azimuth: number; // degrees, 0 = north, clockwise
elevation: number; // degrees above horizon
cn0DbHz: number; // carrier-to-noise density
usedInFix: boolean;
}
interface GnssSatellitesSnapshot {
satellites: GnssSatellite[];
inView: number;
usedInFix: number;
avgCn0DbHz: number;
}Requires ACCESS_FINE_LOCATION at runtime and location services enabled. Returns empty when permission missing.
getNeighborCells() (Android only)
Snapshot of all visible cells (registered + neighbors) per TelephonyManager.getAllCellInfo().
interface NeighborCell {
technology: 'LTE' | 'NR' | 'WCDMA' | 'GSM';
isRegistered: boolean;
mcc: string | null;
mnc: string | null;
tac: number | null; // LTE/NR: tac; WCDMA/GSM: lac
ci: number | null; // LTE/WCDMA/GSM cell id
nci: number | null; // NR 5G cell id (64-bit)
pci: number | null; // physical cell id (LTE/NR)
psc: number | null; // WCDMA primary scrambling code
arfcn: number | null; // EARFCN/NRARFCN/UARFCN/GSM-ARFCN
rsrp: number | null;
rsrq: number | null;
sinr: number | null;
asuLevel: number | null;
dbm: number | null;
timestampNanos: number;
}
interface NeighborCellsSnapshot {
cells: NeighborCell[];
count: number;
}Requires ACCESS_FINE_LOCATION + READ_PHONE_STATE. Returns { cells: [], count: 0 } if missing.
Payload reference
Full payload documented in android-network-metrics-sdk/PAYLOAD_REFERENCE.md.
Key fields:
{
"testId": "uuid",
"deviceId": "platform-device-id",
"timestamp": "2026-04-19T10:00:00Z",
"speed": { "downloadMbps": 12.4, "uploadMbps": 5.1, "latencyMs": 28, "jitterMs": 3.2 },
"udpPacketLoss": { "lossPercent": 1.0, "method": "UDP" },
"radio": { "rsrp": -85, "networkGeneration": "4G", "nrMode": null },
"network": { "isp": "Safaricom", "cfColo": "NBO", "isLocallyServed": true },
"device": { "model": "Pixel 8", "thermalStatus": "NONE" },
"scores": { "streaming": { "score": 4, "label": "Good" } },
"mos": 4.2
}Platform feature matrix
| Feature | Android | iOS | |---|:---:|:---:| | Speed DL/UL/Latency/Jitter | ✅ | ✅ | | Packet loss UDP/TCP | ✅ | ✅ | | HLS streaming | ✅ | ✅ FG / ⚠️ BG | | Social latency | ✅ | ✅ | | DNS timing | ✅ | ✅ | | Web browsing phases | ✅ | ✅ | | GPS location | ✅ | ✅ | | ISP / ASN / CF PoP | ✅ | ✅ | | Device info / battery / thermal | ✅ | ✅ | | RAM usage | ✅ | ✅ | | CPU load % | ✅ | ❌ | | MCC / MNC / Operator | ✅ | ❌ deprecated | | RSRP / RSRQ / Cell ID | ✅ | ❌ private API | | Neighbouring cells | ✅ | ❌ private API | | 5G NSA/SA | ✅ | ❌ no public API | | VoLTE / VoNR | ✅ | ❌ no public API | | Background guaranteed | ✅ WorkManager | ⚠️ BGAppRefresh | | MOS G.107 + QoS scores | ✅ | ✅ |
Changelog
v1.0.40
- Feat: LTE Timing Advance —
radio.timingAdvance(raw units,nullwhen unavailable; distance ≈ TA × 78.07 m, conversion left to consumers) - Feat: dual-SIM — new
radioPerSim[]array ongetRadioSnapshot()and on the cycle payload, one entry per active subscription withsubscriptionId,slotIndex,carrierName, and full radio block. Legacy top-levelradio(default data SIM) is preserved unchanged - Feat: Android SDK v1.0.20 —
RadioMeasurement.measurePerSim(), TA read onCellSignalStrengthLte
v1.0.28
- Perf: Android SDK v1.0.17 — Cloudflare/Ookla-grade methodology. HTTP/2 + ConnectionPool keep-alive (drops ping handshake overhead on cellular), 2 s TCP slow-start warmup excluded from throughput, outlier-trimmed latency + stddev jitter, 25 MB download / 1 MB upload chunks. Loaded latency now probed concurrently during the download stream (true bufferbloat measurement) instead of on an idle post-transfer connection
v1.0.27
- Feat: SPEED phase payload —
measurementProgressSPEED event now carriesdownloadMbps,uploadMbps,latencyMs,jitterMs,loadedLatencyMs,serverName,serverLocationso consumers can display ping/jitter/server as soon as the speed test finishes, without waiting for the full cycle
v1.0.26
- Feat: live speed progress events —
SPEED_DOWNLOAD_PROGRESS/SPEED_UPLOAD_PROGRESSphases onmeasurementProgress, each carryingmbps(instantaneous throughput sampled every 400 ms during the speed phase) - Feat: Android SDK v1.0.15 — Worker wires
onDownloadProgress/onUploadProgresscallbacks intoSpeedMeasurement
v1.0.25
- Feat:
measurementProgressevent — emitted after each phase (SPEED, PACKET_LOSS, STREAMING, SOCIAL_LATENCY, DNS, WEB_BROWSING, RADIO, NETWORK, DEVICE, GEO, COMPLETE) - Feat:
streamingUrlparam ininitialize()— custom HLS URL for streaming measurement - Feat: Android SDK v1.0.14 —
socialTargets+streamingUrlin remote config - Feat:
notifyListeners("measurementProgress", { phase })on Android
v1.0.24
- Fix: bump ios-network-metrics-sdk to v1.0.19 — filter CTCarrier placeholder "--" on iOS 16+
v1.0.23
- Feat: bump ios-network-metrics-sdk to v1.0.18 — MCC/MNC/operator (iOS 14–16.3), loaded latency under download
v1.0.22
- Fix: bump ios-network-metrics-sdk to v1.0.17 — parallel chunk download loop, no delegate, no race
v1.0.21
- Fix: bump ios-network-metrics-sdk to v1.0.16 — download speed:
URLSessionDataDelegate.didReceivefor in-memory chunk accumulation (disk write flush delay was causing 0 Mbps)
v1.0.20
- Fix: bump ios-network-metrics-sdk to v1.0.15 — download speed:
URLSessionDownloadTask+didWriteDatadelegate (real chunk counts, no byte loop)
v1.0.19
- Fix: bump ios-network-metrics-sdk to v1.0.14 — download speed:
URLSessionDataDelegatechunk counting
v1.0.18
- Fix: bump ios-network-metrics-sdk to v1.0.13 — minimum iOS target bumped to 15.0
v1.0.17
- Fix: bump ios-network-metrics-sdk to v1.0.12 — download speed ~0 Mbps fixed: use
URLSession.bytes(from:)streaming.
v1.0.16
- Fix: bump ios-network-metrics-sdk to v1.0.11 — download speed concurrent threads via
Task.detached.
v1.0.15
- Fix: bump ios-network-metrics-sdk to v1.0.10 — remove all
withTaskGroupin measurements +os_logdebug at each step.
v1.0.14
- Fix: bump ios-network-metrics-sdk to v1.0.9 —
CLLocationManagermain thread crash +DispatchSemaphoreblocking Swift cooperative thread pool.
v1.0.13
- Fix: bump ios-network-metrics-sdk to v1.0.8 — Swift runtime
async letheap corruption (swift#75501). Sequentialawait+Task.detached.
v1.0.12
- Fix: bump ios-network-metrics-sdk to v1.0.7 —
DispatchQueue.main.syncdeadlock on main thread replaced withMainActor.run.
v1.0.11
- Fix: bump ios-network-metrics-sdk to v1.0.6 (CI workflow fix + all UIDevice main thread fixes)
v1.0.10
- Fix: bump ios-network-metrics-sdk to v1.0.3 (SIGABRT crash fix —
UIDevice.identifierForVendormoved toMainActor.runinrunCycle)
v1.0.9
- Fix: bump ios-network-metrics-sdk to v1.0.2 (SIGABRT crash fix on
measureNow()— UIDevice battery access on main thread)
v1.0.8
- Fix: bump ios-network-metrics-sdk to v1.0.1 (BGTaskScheduler crash fix)
- iOS:
AppDelegatemust callNetworkMetricsSdk.shared.registerForBackgroundTask()indidFinishLaunching— see iOS Setup in README
v1.0.7
- Feat: add
speedDownloadDurationMs,speedUploadDurationMs,speedThreadCounttoinitialize()API - Fix: pass all speed tuning params through to Android Builder and iOS Config
v1.0.6
- Fix: Android
intervalMinutes→intervalMsconversion (Builder API mismatch) - Fix: Android
initialize()→init()+start()(correct SDK API)
v1.0.5
- Fix: Android SDK dependency bumped to v1.0.13
v1.0.4
- Fix: Android SDK dependency bumped to v1.0.13 (fixes VoLTE/VoNR reflection compilation)
v1.0.3
- Fix: add root
Package.swiftfor Capacitor SPM auto-detection on iOS
v1.0.2
- Fix: tsconfig ES2022 + moduleResolution bundler (fixes dynamic import error in CI)
- Build script: remove docgen dependency
- Add CI + release GitHub Actions workflows (npm publish on tag)
v1.0.1
- Fix: TypeScript build configuration
v1.0.0
- Initial release — Android + iOS
- Full API:
initialize,measureNow,getLastResult - Android: delegates to android-network-metrics-sdk v1.0.12
- iOS: delegates to ios-network-metrics-sdk v1.0.0
