@capgo/capacitor-network-diagnostics 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CapgoCapacitorNetworkDiagnostics.podspec +17 -0
  2. package/LICENSE +373 -0
  3. package/Package.swift +28 -0
  4. package/README.md +467 -0
  5. package/android/build.gradle +59 -0
  6. package/android/src/main/AndroidManifest.xml +4 -0
  7. package/android/src/main/java/app/capgo/networkdiagnostics/NetworkDiagnostics.java +681 -0
  8. package/android/src/main/java/app/capgo/networkdiagnostics/NetworkDiagnosticsPlugin.java +141 -0
  9. package/android/src/main/res/.gitkeep +0 -0
  10. package/dist/docs.json +961 -0
  11. package/dist/esm/definitions.d.ts +276 -0
  12. package/dist/esm/definitions.js +2 -0
  13. package/dist/esm/definitions.js.map +1 -0
  14. package/dist/esm/index.d.ts +4 -0
  15. package/dist/esm/index.js +7 -0
  16. package/dist/esm/index.js.map +1 -0
  17. package/dist/esm/web.d.ts +24 -0
  18. package/dist/esm/web.js +388 -0
  19. package/dist/esm/web.js.map +1 -0
  20. package/dist/plugin.cjs.js +402 -0
  21. package/dist/plugin.cjs.js.map +1 -0
  22. package/dist/plugin.js +405 -0
  23. package/dist/plugin.js.map +1 -0
  24. package/ios/Sources/NetworkDiagnosticsPlugin/NetworkDiagnostics+Download.swift +71 -0
  25. package/ios/Sources/NetworkDiagnosticsPlugin/NetworkDiagnostics+PacketLoss.swift +91 -0
  26. package/ios/Sources/NetworkDiagnosticsPlugin/NetworkDiagnostics+Run.swift +163 -0
  27. package/ios/Sources/NetworkDiagnosticsPlugin/NetworkDiagnostics+Utils.swift +202 -0
  28. package/ios/Sources/NetworkDiagnosticsPlugin/NetworkDiagnostics.swift +151 -0
  29. package/ios/Sources/NetworkDiagnosticsPlugin/NetworkDiagnosticsPlugin.swift +139 -0
  30. package/ios/Tests/NetworkDiagnosticsPluginTests/NetworkDiagnosticsTests.swift +11 -0
  31. package/package.json +92 -0
@@ -0,0 +1,388 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export class NetworkDiagnosticsWeb extends WebPlugin {
3
+ async getNetworkStatus() {
4
+ var _a, _b, _c;
5
+ const nav = navigator;
6
+ const connection = (_b = (_a = nav.connection) !== null && _a !== void 0 ? _a : nav.mozConnection) !== null && _b !== void 0 ? _b : nav.webkitConnection;
7
+ const connectionType = this.mapConnectionType((_c = connection === null || connection === void 0 ? void 0 : connection.type) !== null && _c !== void 0 ? _c : connection === null || connection === void 0 ? void 0 : connection.effectiveType);
8
+ const connected = navigator.onLine;
9
+ const details = {};
10
+ if (connection === null || connection === void 0 ? void 0 : connection.effectiveType) {
11
+ details.effectiveType = connection.effectiveType;
12
+ }
13
+ if (typeof (connection === null || connection === void 0 ? void 0 : connection.downlink) === 'number') {
14
+ details.downlinkMbps = connection.downlink;
15
+ }
16
+ if (typeof (connection === null || connection === void 0 ? void 0 : connection.rtt) === 'number') {
17
+ details.rttMs = connection.rtt;
18
+ }
19
+ return {
20
+ connected,
21
+ connectionType,
22
+ constrained: connection === null || connection === void 0 ? void 0 : connection.saveData,
23
+ details,
24
+ expensive: false,
25
+ internetReachable: connected,
26
+ };
27
+ }
28
+ async testUrl(options) {
29
+ const started = performance.now();
30
+ const method = this.normalizeMethod(options.method);
31
+ const controller = new AbortController();
32
+ const timeout = window.setTimeout(() => controller.abort(), this.timeout(options.timeoutMs, 10000));
33
+ try {
34
+ const response = await fetch(options.url, {
35
+ method,
36
+ redirect: options.followRedirects === false ? 'manual' : 'follow',
37
+ signal: controller.signal,
38
+ });
39
+ return {
40
+ durationMs: this.elapsed(started),
41
+ finalUrl: response.url || options.url,
42
+ method,
43
+ ok: response.status >= 200 && response.status < 400,
44
+ reachable: true,
45
+ statusCode: response.status,
46
+ url: options.url,
47
+ };
48
+ }
49
+ catch (error) {
50
+ return {
51
+ durationMs: this.elapsed(started),
52
+ errorCode: this.errorCode(error),
53
+ errorMessage: this.errorMessage(error),
54
+ method,
55
+ ok: false,
56
+ reachable: false,
57
+ url: options.url,
58
+ };
59
+ }
60
+ finally {
61
+ window.clearTimeout(timeout);
62
+ }
63
+ }
64
+ async testPort(options) {
65
+ return {
66
+ durationMs: 0,
67
+ errorCode: 'UNSUPPORTED_WEB',
68
+ errorMessage: 'Browsers cannot open raw TCP sockets. Use iOS or Android for native port diagnostics.',
69
+ host: options.host,
70
+ open: false,
71
+ port: options.port,
72
+ };
73
+ }
74
+ async testWebSocket(options) {
75
+ const started = performance.now();
76
+ return new Promise((resolve) => {
77
+ let settled = false;
78
+ let socket;
79
+ const timeout = window.setTimeout(() => {
80
+ if (settled) {
81
+ return;
82
+ }
83
+ settled = true;
84
+ socket === null || socket === void 0 ? void 0 : socket.close();
85
+ resolve({
86
+ durationMs: this.elapsed(started),
87
+ errorCode: 'TIMEOUT',
88
+ errorMessage: 'WebSocket handshake timed out',
89
+ open: false,
90
+ url: options.url,
91
+ });
92
+ }, this.timeout(options.timeoutMs, 10000));
93
+ const finish = (result) => {
94
+ if (settled) {
95
+ return;
96
+ }
97
+ settled = true;
98
+ window.clearTimeout(timeout);
99
+ socket === null || socket === void 0 ? void 0 : socket.close();
100
+ resolve(result);
101
+ };
102
+ try {
103
+ socket = new WebSocket(options.url);
104
+ socket.onopen = () => {
105
+ finish({
106
+ durationMs: this.elapsed(started),
107
+ open: true,
108
+ protocol: socket === null || socket === void 0 ? void 0 : socket.protocol,
109
+ url: options.url,
110
+ });
111
+ };
112
+ socket.onerror = () => {
113
+ finish({
114
+ durationMs: this.elapsed(started),
115
+ errorCode: 'WEBSOCKET_ERROR',
116
+ errorMessage: 'WebSocket handshake failed',
117
+ open: false,
118
+ url: options.url,
119
+ });
120
+ };
121
+ }
122
+ catch (error) {
123
+ finish({
124
+ durationMs: this.elapsed(started),
125
+ errorCode: this.errorCode(error),
126
+ errorMessage: this.errorMessage(error),
127
+ open: false,
128
+ url: options.url,
129
+ });
130
+ }
131
+ });
132
+ }
133
+ async testDownloadSpeed(options) {
134
+ const started = performance.now();
135
+ const maxBytes = this.positiveNumber(options.maxBytes, 5 * 1024 * 1024);
136
+ const controller = new AbortController();
137
+ const timeout = window.setTimeout(() => controller.abort(), this.timeout(options.timeoutMs, 30000));
138
+ let bytesDownloaded = 0;
139
+ let statusCode;
140
+ try {
141
+ const response = await fetch(options.url, {
142
+ method: 'GET',
143
+ signal: controller.signal,
144
+ });
145
+ statusCode = response.status;
146
+ if (response.body) {
147
+ const reader = response.body.getReader();
148
+ while (bytesDownloaded < maxBytes) {
149
+ const read = await reader.read();
150
+ if (read.done) {
151
+ break;
152
+ }
153
+ bytesDownloaded += read.value.byteLength;
154
+ }
155
+ await reader.cancel().catch(() => undefined);
156
+ }
157
+ else {
158
+ const buffer = await response.arrayBuffer();
159
+ bytesDownloaded = Math.min(buffer.byteLength, maxBytes);
160
+ }
161
+ const durationMs = Math.max(this.elapsed(started), 1);
162
+ const bytesPerSecond = bytesDownloaded / (durationMs / 1000);
163
+ return {
164
+ bytesDownloaded,
165
+ bytesPerSecond,
166
+ durationMs,
167
+ mbps: (bytesPerSecond * 8) / 1000000,
168
+ ok: response.ok,
169
+ statusCode,
170
+ url: options.url,
171
+ };
172
+ }
173
+ catch (error) {
174
+ const durationMs = Math.max(this.elapsed(started), 1);
175
+ const bytesPerSecond = bytesDownloaded / (durationMs / 1000);
176
+ return {
177
+ bytesDownloaded,
178
+ bytesPerSecond,
179
+ durationMs,
180
+ errorCode: this.errorCode(error),
181
+ errorMessage: this.errorMessage(error),
182
+ mbps: (bytesPerSecond * 8) / 1000000,
183
+ ok: false,
184
+ statusCode,
185
+ url: options.url,
186
+ };
187
+ }
188
+ finally {
189
+ window.clearTimeout(timeout);
190
+ }
191
+ }
192
+ async testPacketLoss(options) {
193
+ var _a;
194
+ const mode = this.packetLossMode(options);
195
+ const count = Math.max(1, Math.floor(this.positiveNumber(options.count, 10)));
196
+ const intervalMs = this.positiveNumber(options.intervalMs, 250);
197
+ const latencies = [];
198
+ let received = 0;
199
+ let lastErrorCode;
200
+ let lastErrorMessage;
201
+ if (mode === 'tcp') {
202
+ return {
203
+ errorCode: 'UNSUPPORTED_WEB',
204
+ errorMessage: 'Browsers cannot open raw TCP sockets. Use iOS or Android for native packet loss diagnostics.',
205
+ lost: count,
206
+ lossPercent: 100,
207
+ mode,
208
+ received: 0,
209
+ sent: count,
210
+ target: this.packetLossTarget(options, mode),
211
+ };
212
+ }
213
+ for (let index = 0; index < count; index++) {
214
+ const result = await this.testUrl({
215
+ method: 'HEAD',
216
+ timeoutMs: options.timeoutMs,
217
+ url: (_a = options.url) !== null && _a !== void 0 ? _a : '',
218
+ });
219
+ if (result.reachable) {
220
+ received += 1;
221
+ latencies.push(result.durationMs);
222
+ }
223
+ else {
224
+ lastErrorCode = result.errorCode;
225
+ lastErrorMessage = result.errorMessage;
226
+ }
227
+ if (index < count - 1) {
228
+ await this.sleep(intervalMs);
229
+ }
230
+ }
231
+ return this.packetLossResult(mode, this.packetLossTarget(options, mode), count, received, latencies, lastErrorCode, lastErrorMessage);
232
+ }
233
+ async runDiagnostics(options = {}) {
234
+ var _a, _b, _c;
235
+ const status = await this.getNetworkStatus();
236
+ const urls = [];
237
+ const ports = [];
238
+ const websockets = [];
239
+ for (const url of (_a = options.urls) !== null && _a !== void 0 ? _a : []) {
240
+ urls.push(await this.testUrl(url));
241
+ }
242
+ for (const port of (_b = options.ports) !== null && _b !== void 0 ? _b : []) {
243
+ ports.push(await this.testPort(port));
244
+ }
245
+ for (const websocket of (_c = options.websockets) !== null && _c !== void 0 ? _c : []) {
246
+ websockets.push(await this.testWebSocket(websocket));
247
+ }
248
+ const download = options.download ? await this.testDownloadSpeed(options.download) : undefined;
249
+ const packetLoss = options.packetLoss ? await this.testPacketLoss(options.packetLoss) : undefined;
250
+ return {
251
+ download,
252
+ issues: this.buildIssues(status, urls, ports, websockets, download, packetLoss),
253
+ packetLoss,
254
+ ports,
255
+ status,
256
+ urls,
257
+ websockets,
258
+ };
259
+ }
260
+ async getPluginVersion() {
261
+ return {
262
+ version: 'web',
263
+ };
264
+ }
265
+ buildIssues(status, urls, ports, websockets, download, packetLoss) {
266
+ const issues = [];
267
+ if (!status.connected) {
268
+ issues.push('No active network connection');
269
+ }
270
+ else if (!status.internetReachable) {
271
+ issues.push('Network is connected but internet reachability is not confirmed');
272
+ }
273
+ for (const result of urls) {
274
+ if (!result.reachable) {
275
+ issues.push(`URL unreachable: ${result.url}`);
276
+ }
277
+ else if (!result.ok) {
278
+ issues.push(`URL returned non-success status: ${result.url}`);
279
+ }
280
+ }
281
+ for (const result of ports) {
282
+ if (!result.open) {
283
+ issues.push(`TCP port blocked or unreachable: ${result.host}:${result.port}`);
284
+ }
285
+ }
286
+ for (const result of websockets) {
287
+ if (!result.open) {
288
+ issues.push(`WebSocket failed: ${result.url}`);
289
+ }
290
+ }
291
+ if (download && !download.ok) {
292
+ issues.push(`Download speed test failed: ${download.url}`);
293
+ }
294
+ if (packetLoss && packetLoss.lossPercent > 0) {
295
+ issues.push(`Packet loss detected: ${packetLoss.lossPercent}% to ${packetLoss.target}`);
296
+ }
297
+ return issues;
298
+ }
299
+ elapsed(started) {
300
+ return Math.round(performance.now() - started);
301
+ }
302
+ errorCode(error) {
303
+ if (error instanceof DOMException && error.name === 'AbortError') {
304
+ return 'TIMEOUT';
305
+ }
306
+ if (error instanceof Error && error.name) {
307
+ return error.name;
308
+ }
309
+ return 'ERROR';
310
+ }
311
+ errorMessage(error) {
312
+ if (error instanceof Error && error.message) {
313
+ return error.message;
314
+ }
315
+ return String(error);
316
+ }
317
+ mapConnectionType(type) {
318
+ if (!type) {
319
+ return navigator.onLine ? 'unknown' : 'none';
320
+ }
321
+ if (type === 'wifi') {
322
+ return 'wifi';
323
+ }
324
+ if (type === 'cellular' ||
325
+ type.includes('2g') ||
326
+ type.includes('3g') ||
327
+ type.includes('4g') ||
328
+ type.includes('5g')) {
329
+ return 'cellular';
330
+ }
331
+ if (type === 'ethernet') {
332
+ return 'ethernet';
333
+ }
334
+ if (type === 'none') {
335
+ return 'none';
336
+ }
337
+ return 'unknown';
338
+ }
339
+ normalizeMethod(method) {
340
+ return method === 'GET' ? 'GET' : 'HEAD';
341
+ }
342
+ packetLossMode(options) {
343
+ if (options.mode) {
344
+ return options.mode;
345
+ }
346
+ return options.host && options.port ? 'tcp' : 'http';
347
+ }
348
+ packetLossResult(mode, target, sent, received, latencies, errorCode, errorMessage) {
349
+ const lost = sent - received;
350
+ const result = {
351
+ lost,
352
+ lossPercent: (lost / sent) * 100,
353
+ mode,
354
+ received,
355
+ sent,
356
+ target,
357
+ };
358
+ if (latencies.length > 0) {
359
+ result.averageLatencyMs = latencies.reduce((sum, latency) => sum + latency, 0) / latencies.length;
360
+ result.minLatencyMs = Math.min(...latencies);
361
+ result.maxLatencyMs = Math.max(...latencies);
362
+ }
363
+ if (errorCode) {
364
+ result.errorCode = errorCode;
365
+ }
366
+ if (errorMessage) {
367
+ result.errorMessage = errorMessage;
368
+ }
369
+ return result;
370
+ }
371
+ packetLossTarget(options, mode) {
372
+ var _a, _b, _c;
373
+ if (mode === 'http') {
374
+ return (_a = options.url) !== null && _a !== void 0 ? _a : '';
375
+ }
376
+ return `${(_b = options.host) !== null && _b !== void 0 ? _b : ''}:${(_c = options.port) !== null && _c !== void 0 ? _c : 0}`;
377
+ }
378
+ positiveNumber(value, fallback) {
379
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
380
+ }
381
+ sleep(ms) {
382
+ return new Promise((resolve) => window.setTimeout(resolve, ms));
383
+ }
384
+ timeout(value, fallback) {
385
+ return Math.floor(this.positiveNumber(value, fallback));
386
+ }
387
+ }
388
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type {\n ConnectionType,\n DownloadSpeedTestOptions,\n DownloadSpeedTestResult,\n NetworkDiagnosticsPlugin,\n NetworkStatusResult,\n PacketLossMode,\n PacketLossTestOptions,\n PacketLossTestResult,\n PluginVersionResult,\n PortTestOptions,\n PortTestResult,\n RunDiagnosticsOptions,\n RunDiagnosticsResult,\n UrlTestMethod,\n UrlTestOptions,\n UrlTestResult,\n WebSocketTestOptions,\n WebSocketTestResult,\n} from './definitions';\n\ninterface NavigatorConnection {\n effectiveType?: string;\n type?: string;\n downlink?: number;\n rtt?: number;\n saveData?: boolean;\n}\n\ninterface NavigatorWithConnection extends Navigator {\n connection?: NavigatorConnection;\n mozConnection?: NavigatorConnection;\n webkitConnection?: NavigatorConnection;\n}\n\nexport class NetworkDiagnosticsWeb extends WebPlugin implements NetworkDiagnosticsPlugin {\n async getNetworkStatus(): Promise<NetworkStatusResult> {\n const nav = navigator as NavigatorWithConnection;\n const connection = nav.connection ?? nav.mozConnection ?? nav.webkitConnection;\n const connectionType = this.mapConnectionType(connection?.type ?? connection?.effectiveType);\n const connected = navigator.onLine;\n const details: Record<string, string | number | boolean> = {};\n\n if (connection?.effectiveType) {\n details.effectiveType = connection.effectiveType;\n }\n if (typeof connection?.downlink === 'number') {\n details.downlinkMbps = connection.downlink;\n }\n if (typeof connection?.rtt === 'number') {\n details.rttMs = connection.rtt;\n }\n\n return {\n connected,\n connectionType,\n constrained: connection?.saveData,\n details,\n expensive: false,\n internetReachable: connected,\n };\n }\n\n async testUrl(options: UrlTestOptions): Promise<UrlTestResult> {\n const started = performance.now();\n const method = this.normalizeMethod(options.method);\n const controller = new AbortController();\n const timeout = window.setTimeout(() => controller.abort(), this.timeout(options.timeoutMs, 10000));\n\n try {\n const response = await fetch(options.url, {\n method,\n redirect: options.followRedirects === false ? 'manual' : 'follow',\n signal: controller.signal,\n });\n\n return {\n durationMs: this.elapsed(started),\n finalUrl: response.url || options.url,\n method,\n ok: response.status >= 200 && response.status < 400,\n reachable: true,\n statusCode: response.status,\n url: options.url,\n };\n } catch (error) {\n return {\n durationMs: this.elapsed(started),\n errorCode: this.errorCode(error),\n errorMessage: this.errorMessage(error),\n method,\n ok: false,\n reachable: false,\n url: options.url,\n };\n } finally {\n window.clearTimeout(timeout);\n }\n }\n\n async testPort(options: PortTestOptions): Promise<PortTestResult> {\n return {\n durationMs: 0,\n errorCode: 'UNSUPPORTED_WEB',\n errorMessage: 'Browsers cannot open raw TCP sockets. Use iOS or Android for native port diagnostics.',\n host: options.host,\n open: false,\n port: options.port,\n };\n }\n\n async testWebSocket(options: WebSocketTestOptions): Promise<WebSocketTestResult> {\n const started = performance.now();\n\n return new Promise<WebSocketTestResult>((resolve) => {\n let settled = false;\n let socket: WebSocket | undefined;\n const timeout = window.setTimeout(\n () => {\n if (settled) {\n return;\n }\n settled = true;\n socket?.close();\n resolve({\n durationMs: this.elapsed(started),\n errorCode: 'TIMEOUT',\n errorMessage: 'WebSocket handshake timed out',\n open: false,\n url: options.url,\n });\n },\n this.timeout(options.timeoutMs, 10000),\n );\n\n const finish = (result: WebSocketTestResult): void => {\n if (settled) {\n return;\n }\n settled = true;\n window.clearTimeout(timeout);\n socket?.close();\n resolve(result);\n };\n\n try {\n socket = new WebSocket(options.url);\n socket.onopen = () => {\n finish({\n durationMs: this.elapsed(started),\n open: true,\n protocol: socket?.protocol,\n url: options.url,\n });\n };\n socket.onerror = () => {\n finish({\n durationMs: this.elapsed(started),\n errorCode: 'WEBSOCKET_ERROR',\n errorMessage: 'WebSocket handshake failed',\n open: false,\n url: options.url,\n });\n };\n } catch (error) {\n finish({\n durationMs: this.elapsed(started),\n errorCode: this.errorCode(error),\n errorMessage: this.errorMessage(error),\n open: false,\n url: options.url,\n });\n }\n });\n }\n\n async testDownloadSpeed(options: DownloadSpeedTestOptions): Promise<DownloadSpeedTestResult> {\n const started = performance.now();\n const maxBytes = this.positiveNumber(options.maxBytes, 5 * 1024 * 1024);\n const controller = new AbortController();\n const timeout = window.setTimeout(() => controller.abort(), this.timeout(options.timeoutMs, 30000));\n let bytesDownloaded = 0;\n let statusCode: number | undefined;\n\n try {\n const response = await fetch(options.url, {\n method: 'GET',\n signal: controller.signal,\n });\n statusCode = response.status;\n\n if (response.body) {\n const reader = response.body.getReader();\n while (bytesDownloaded < maxBytes) {\n const read = await reader.read();\n if (read.done) {\n break;\n }\n bytesDownloaded += read.value.byteLength;\n }\n await reader.cancel().catch(() => undefined);\n } else {\n const buffer = await response.arrayBuffer();\n bytesDownloaded = Math.min(buffer.byteLength, maxBytes);\n }\n\n const durationMs = Math.max(this.elapsed(started), 1);\n const bytesPerSecond = bytesDownloaded / (durationMs / 1000);\n\n return {\n bytesDownloaded,\n bytesPerSecond,\n durationMs,\n mbps: (bytesPerSecond * 8) / 1_000_000,\n ok: response.ok,\n statusCode,\n url: options.url,\n };\n } catch (error) {\n const durationMs = Math.max(this.elapsed(started), 1);\n const bytesPerSecond = bytesDownloaded / (durationMs / 1000);\n\n return {\n bytesDownloaded,\n bytesPerSecond,\n durationMs,\n errorCode: this.errorCode(error),\n errorMessage: this.errorMessage(error),\n mbps: (bytesPerSecond * 8) / 1_000_000,\n ok: false,\n statusCode,\n url: options.url,\n };\n } finally {\n window.clearTimeout(timeout);\n }\n }\n\n async testPacketLoss(options: PacketLossTestOptions): Promise<PacketLossTestResult> {\n const mode = this.packetLossMode(options);\n const count = Math.max(1, Math.floor(this.positiveNumber(options.count, 10)));\n const intervalMs = this.positiveNumber(options.intervalMs, 250);\n const latencies: number[] = [];\n let received = 0;\n let lastErrorCode: string | undefined;\n let lastErrorMessage: string | undefined;\n\n if (mode === 'tcp') {\n return {\n errorCode: 'UNSUPPORTED_WEB',\n errorMessage: 'Browsers cannot open raw TCP sockets. Use iOS or Android for native packet loss diagnostics.',\n lost: count,\n lossPercent: 100,\n mode,\n received: 0,\n sent: count,\n target: this.packetLossTarget(options, mode),\n };\n }\n\n for (let index = 0; index < count; index++) {\n const result = await this.testUrl({\n method: 'HEAD',\n timeoutMs: options.timeoutMs,\n url: options.url ?? '',\n });\n if (result.reachable) {\n received += 1;\n latencies.push(result.durationMs);\n } else {\n lastErrorCode = result.errorCode;\n lastErrorMessage = result.errorMessage;\n }\n if (index < count - 1) {\n await this.sleep(intervalMs);\n }\n }\n\n return this.packetLossResult(\n mode,\n this.packetLossTarget(options, mode),\n count,\n received,\n latencies,\n lastErrorCode,\n lastErrorMessage,\n );\n }\n\n async runDiagnostics(options: RunDiagnosticsOptions = {}): Promise<RunDiagnosticsResult> {\n const status = await this.getNetworkStatus();\n const urls: UrlTestResult[] = [];\n const ports: PortTestResult[] = [];\n const websockets: WebSocketTestResult[] = [];\n\n for (const url of options.urls ?? []) {\n urls.push(await this.testUrl(url));\n }\n for (const port of options.ports ?? []) {\n ports.push(await this.testPort(port));\n }\n for (const websocket of options.websockets ?? []) {\n websockets.push(await this.testWebSocket(websocket));\n }\n\n const download = options.download ? await this.testDownloadSpeed(options.download) : undefined;\n const packetLoss = options.packetLoss ? await this.testPacketLoss(options.packetLoss) : undefined;\n\n return {\n download,\n issues: this.buildIssues(status, urls, ports, websockets, download, packetLoss),\n packetLoss,\n ports,\n status,\n urls,\n websockets,\n };\n }\n\n async getPluginVersion(): Promise<PluginVersionResult> {\n return {\n version: 'web',\n };\n }\n\n private buildIssues(\n status: NetworkStatusResult,\n urls: UrlTestResult[],\n ports: PortTestResult[],\n websockets: WebSocketTestResult[],\n download?: DownloadSpeedTestResult,\n packetLoss?: PacketLossTestResult,\n ): string[] {\n const issues: string[] = [];\n\n if (!status.connected) {\n issues.push('No active network connection');\n } else if (!status.internetReachable) {\n issues.push('Network is connected but internet reachability is not confirmed');\n }\n\n for (const result of urls) {\n if (!result.reachable) {\n issues.push(`URL unreachable: ${result.url}`);\n } else if (!result.ok) {\n issues.push(`URL returned non-success status: ${result.url}`);\n }\n }\n\n for (const result of ports) {\n if (!result.open) {\n issues.push(`TCP port blocked or unreachable: ${result.host}:${result.port}`);\n }\n }\n\n for (const result of websockets) {\n if (!result.open) {\n issues.push(`WebSocket failed: ${result.url}`);\n }\n }\n\n if (download && !download.ok) {\n issues.push(`Download speed test failed: ${download.url}`);\n }\n\n if (packetLoss && packetLoss.lossPercent > 0) {\n issues.push(`Packet loss detected: ${packetLoss.lossPercent}% to ${packetLoss.target}`);\n }\n\n return issues;\n }\n\n private elapsed(started: number): number {\n return Math.round(performance.now() - started);\n }\n\n private errorCode(error: unknown): string {\n if (error instanceof DOMException && error.name === 'AbortError') {\n return 'TIMEOUT';\n }\n if (error instanceof Error && error.name) {\n return error.name;\n }\n return 'ERROR';\n }\n\n private errorMessage(error: unknown): string {\n if (error instanceof Error && error.message) {\n return error.message;\n }\n return String(error);\n }\n\n private mapConnectionType(type?: string): ConnectionType {\n if (!type) {\n return navigator.onLine ? 'unknown' : 'none';\n }\n\n if (type === 'wifi') {\n return 'wifi';\n }\n if (\n type === 'cellular' ||\n type.includes('2g') ||\n type.includes('3g') ||\n type.includes('4g') ||\n type.includes('5g')\n ) {\n return 'cellular';\n }\n if (type === 'ethernet') {\n return 'ethernet';\n }\n if (type === 'none') {\n return 'none';\n }\n\n return 'unknown';\n }\n\n private normalizeMethod(method?: UrlTestMethod): UrlTestMethod {\n return method === 'GET' ? 'GET' : 'HEAD';\n }\n\n private packetLossMode(options: PacketLossTestOptions): PacketLossMode {\n if (options.mode) {\n return options.mode;\n }\n return options.host && options.port ? 'tcp' : 'http';\n }\n\n private packetLossResult(\n mode: PacketLossMode,\n target: string,\n sent: number,\n received: number,\n latencies: number[],\n errorCode?: string,\n errorMessage?: string,\n ): PacketLossTestResult {\n const lost = sent - received;\n const result: PacketLossTestResult = {\n lost,\n lossPercent: (lost / sent) * 100,\n mode,\n received,\n sent,\n target,\n };\n\n if (latencies.length > 0) {\n result.averageLatencyMs = latencies.reduce((sum, latency) => sum + latency, 0) / latencies.length;\n result.minLatencyMs = Math.min(...latencies);\n result.maxLatencyMs = Math.max(...latencies);\n }\n if (errorCode) {\n result.errorCode = errorCode;\n }\n if (errorMessage) {\n result.errorMessage = errorMessage;\n }\n\n return result;\n }\n\n private packetLossTarget(options: PacketLossTestOptions, mode: PacketLossMode): string {\n if (mode === 'http') {\n return options.url ?? '';\n }\n return `${options.host ?? ''}:${options.port ?? 0}`;\n }\n\n private positiveNumber(value: number | undefined, fallback: number): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => window.setTimeout(resolve, ms));\n }\n\n private timeout(value: number | undefined, fallback: number): number {\n return Math.floor(this.positiveNumber(value, fallback));\n }\n}\n"]}