network-speed-js 1.0.5 → 1.0.6

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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # Network Speed SDK
2
-
3
1
  <div align="center">
4
2
 
3
+ # Network Speed SDK
4
+
5
5
  一个基于 **Performance API** 的现代化网速测试 SDK,支持内外网自动检测、资源监听和完整的 TS 类型支持。
6
6
 
7
7
  **框架无关 · 开箱即用 · 准确可靠**
@@ -13,8 +13,12 @@
13
13
 
14
14
  ---
15
15
 
16
+ <div align="center">
17
+
16
18
  ## 🎯 核心亮点
17
19
 
20
+ </div>
21
+
18
22
  ### 从 Axios 到 Performance API 的技术升级
19
23
 
20
24
  本项目从 **v0.x(Axios 拦截器方案)** 完全重构为 **v1.0(Performance API 方案)**,实现了测速准确度和功能的质的飞跃。
@@ -33,8 +37,12 @@
33
37
 
34
38
  ---
35
39
 
40
+ <div align="center">
41
+
36
42
  ## ✨ 特性
37
43
 
44
+ </div>
45
+
38
46
  - 🚀 **基于 Performance API** - 使用浏览器原生 API,准确测量真实下载速度
39
47
  - 🔄 **内外网自动检测** - 智能切换内网/外网测速资源
40
48
  - 📊 **完整的性能数据** - 提供速度、耗时、传输大小等详细信息
@@ -43,14 +51,22 @@
43
51
  - 🌐 **框架无关** - 可用于 Vue、React、Angular 或原生 JavaScript 项目
44
52
  - 📦 **轻量级** - 零依赖,体积小巧
45
53
 
54
+ <div align="center">
55
+
46
56
  ## 📦 安装
47
57
 
58
+ </div>
59
+
48
60
  ```bash
49
61
  npm install network-speed-js
50
62
  ```
51
63
 
64
+ <div align="center">
65
+
52
66
  ## 🚀 快速开始
53
67
 
68
+ </div>
69
+
54
70
  ### 原生 JavaScript / TypeScript
55
71
 
56
72
  **方式一:使用图片资源测速(推荐,默认模式,无需CORS)**
@@ -173,8 +189,12 @@ export class SpeedTestComponent {
173
189
  }
174
190
  ```
175
191
 
192
+ <div align="center">
193
+
176
194
  ## 📖 API 文档
177
195
 
196
+ </div>
197
+
178
198
  ### NetworkSpeedSDK
179
199
 
180
200
  #### 构造函数
@@ -331,8 +351,12 @@ interface ResourceSpeedInfo {
331
351
  }
332
352
  ```
333
353
 
354
+ <div align="center">
355
+
334
356
  ## 💡 使用示例
335
357
 
358
+ </div>
359
+
336
360
  ### 1. 使用图片资源测速(默认,推荐)
337
361
 
338
362
  ```typescript
@@ -479,8 +503,12 @@ const monitor = new NetworkMonitor();
479
503
  setInterval(() => monitor.monitor(), 60000); // 每分钟检测
480
504
  ```
481
505
 
506
+ <div align="center">
507
+
482
508
  ## 🎨 框架集成
483
509
 
510
+ </div>
511
+
484
512
  ### Vue 3 自定义 Hook
485
513
 
486
514
  ```typescript
@@ -556,8 +584,12 @@ export function useNetworkSpeed(options = {}) {
556
584
  }
557
585
  ```
558
586
 
587
+ <div align="center">
588
+
559
589
  ## ⚙️ 配置指南
560
590
 
591
+ </div>
592
+
561
593
  ### 测速资源准备
562
594
 
563
595
  #### 服务端配置(Nginx)
@@ -641,8 +673,12 @@ dd if=/dev/urandom of=speed-test.bin bs=1024 count=500
641
673
  }
642
674
  ```
643
675
 
676
+ <div align="center">
677
+
644
678
  ## 🔍 核心概念
645
679
 
680
+ </div>
681
+
646
682
  ### Performance API 是什么?
647
683
 
648
684
  Performance API 是浏览器提供的原生性能监控接口,可以精确测量资源加载的各个阶段耗时。
@@ -664,8 +700,12 @@ Performance API 是浏览器提供的原生性能监控接口,可以精确测
664
700
  2. 如果内网资源超时或失败,自动切换到外网资源
665
701
  3. 根据成功的资源判断当前网络环境
666
702
 
703
+ <div align="center">
704
+
667
705
  ## 📊 Performance API vs 其他方案
668
706
 
707
+ </div>
708
+
669
709
  | 方案 | 准确度 | 可控性 | 复杂度 | 推荐 |
670
710
  |------|--------|--------|--------|------|
671
711
  | Performance API | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ✅ 主力 |
@@ -704,8 +744,12 @@ const size = entry.transferSize; // 真实传输字节数
704
744
  - ✅ 可识别缓存命中
705
745
  - ✅ 提供完整的加载时序
706
746
 
747
+ <div align="center">
748
+
707
749
  ## ❓ 常见问题
708
750
 
751
+ </div>
752
+
709
753
  ### Q1: 测速结果不准确?
710
754
 
711
755
  **可能原因:**
@@ -780,8 +824,12 @@ if (connection) {
780
824
  }
781
825
  ```
782
826
 
827
+ <div align="center">
828
+
783
829
  ## 🚀 性能优化
784
830
 
831
+ </div>
832
+
785
833
  ### 1. 避免频繁测速
786
834
 
787
835
  ```typescript
@@ -845,8 +893,12 @@ async function testSpeed() {
845
893
  }
846
894
  ```
847
895
 
896
+ <div align="center">
897
+
848
898
  ## ⚠️ 注意事项
849
899
 
900
+ </div>
901
+
850
902
  ### Performance API 能做什么
851
903
 
852
904
  ✅ 基于真实资源加载评估下载速度
@@ -861,8 +913,12 @@ async function testSpeed() {
861
913
  ❌ 测量 RTT 抖动
862
914
  ❌ 脱离真实请求独立测速
863
915
 
916
+ <div align="center">
917
+
864
918
  ## 🤝 贡献
865
919
 
920
+ </div>
921
+
866
922
  欢迎提交 Issue 和 Pull Request!
867
923
 
868
924
  ### 本地开发
@@ -901,12 +957,20 @@ network-speed-js/
901
957
  └── README.md # 项目文档
902
958
  ```
903
959
 
960
+ <div align="center">
961
+
904
962
  ## 📄 License
905
963
 
964
+ </div>
965
+
906
966
  MIT License
907
967
 
968
+ <div align="center">
969
+
908
970
  ## 🔗 相关链接
909
971
 
972
+ </div>
973
+
910
974
  - [更新日志 (CHANGELOG)](./CHANGELOG.md)
911
975
  - [Performance API 文档](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API)
912
976
  - [PerformanceResourceTiming](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming)
@@ -1 +1 @@
1
- {"version":3,"file":"speed-tester.d.ts","sourceRoot":"","sources":["../../src/core/speed-tester.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,QAAQ,CAAoC;gBAExC,OAAO,EAAE,gBAAgB;IAyBrC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC;IAQtC;;OAEG;YACW,kBAAkB;IAehC;;OAEG;IACH,OAAO,CAAC,aAAa;IA0DrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAqBrB;;OAEG;IACH,OAAO,CAAC,aAAa;IA4BrB;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,GAAG,MAAM,IAAI;IAkBnF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAIxD;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
1
+ {"version":3,"file":"speed-tester.d.ts","sourceRoot":"","sources":["../../src/core/speed-tester.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AAGjB;;GAEG;AACH,UAAU,iBAAiB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,QAAQ,CAAoC;gBAExC,OAAO,EAAE,gBAAgB;IAgDrC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC;IAQtC;;OAEG;YACW,kBAAkB;IAehC;;OAEG;IACH,OAAO,CAAC,aAAa;IA0DrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAqBrB;;OAEG;IACH,OAAO,CAAC,aAAa;IA4BrB;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,wBAAwB,GAAG,MAAM,IAAI;IAkBnF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAIxD;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
@@ -2,16 +2,16 @@ function h(t) {
2
2
  const e = t.responseEnd - t.responseStart;
3
3
  if (e <= 0 || t.transferSize === 0)
4
4
  return null;
5
- const s = t.transferSize * 8 / e / 1e3, n = t.transferSize / e;
5
+ const r = t.transferSize * 8 / e / 1e3, n = t.transferSize / e;
6
6
  return {
7
7
  name: t.name,
8
- speedMbps: Number(s.toFixed(2)),
8
+ speedMbps: Number(r.toFixed(2)),
9
9
  speedKBps: Number(n.toFixed(2)),
10
10
  downloadTime: Number(e.toFixed(2)),
11
11
  transferSize: t.transferSize
12
12
  };
13
13
  }
14
- function f() {
14
+ function d() {
15
15
  return performance.getEntriesByType("resource").filter(
16
16
  (e) => e instanceof PerformanceResourceTiming && e.transferSize > 0
17
17
  ).map(h).filter((e) => e !== null);
@@ -24,25 +24,40 @@ function p(t) {
24
24
  function w(t, e = { fast: 10, medium: 2 }) {
25
25
  return t >= e.fast ? "fast" : t >= e.medium ? "medium" : t > 0 ? "slow" : "unknown";
26
26
  }
27
- class a {
27
+ class l {
28
28
  options;
29
29
  observer = null;
30
30
  constructor(e) {
31
- "useFetch" in e && e.useFetch ? this.options = {
32
- intranetUrl: e.intranetUrl || "",
33
- internetUrl: e.internetUrl,
34
- timeout: e.timeout || 1e4,
35
- autoDetect: e.autoDetect ?? !0,
36
- thresholds: e.thresholds || { fast: 10, medium: 2 },
37
- useFetch: !0
38
- } : this.options = {
39
- intranetUrl: "intranetImageUrl" in e && e.intranetImageUrl || "",
40
- internetUrl: "internetImageUrl" in e ? e.internetImageUrl : "",
41
- timeout: e.timeout || 1e4,
42
- autoDetect: e.autoDetect ?? !0,
43
- thresholds: e.thresholds || { fast: 10, medium: 2 },
44
- useFetch: !1
45
- };
31
+ if ("useFetch" in e && e.useFetch) {
32
+ if (!e.internetUrl)
33
+ throw new Error("Fetch模式必须提供 internetUrl 参数");
34
+ if (!e.internetUrl.startsWith("http://") && !e.internetUrl.startsWith("https://"))
35
+ throw new Error("internetUrl 必须是完整的HTTP/HTTPS URL");
36
+ this.options = {
37
+ intranetUrl: e.intranetUrl || "",
38
+ internetUrl: e.internetUrl,
39
+ timeout: e.timeout || 1e4,
40
+ autoDetect: e.autoDetect ?? !0,
41
+ thresholds: e.thresholds || { fast: 10, medium: 2 },
42
+ useFetch: !0
43
+ };
44
+ } else {
45
+ const r = "internetImageUrl" in e ? e.internetImageUrl : "";
46
+ if (!r)
47
+ throw new Error("图片模式必须提供 internetImageUrl 参数");
48
+ if (!r.startsWith("http://") && !r.startsWith("https://"))
49
+ throw new Error("internetImageUrl 必须是完整的HTTP/HTTPS URL");
50
+ this.options = {
51
+ intranetUrl: "intranetImageUrl" in e && e.intranetImageUrl || "",
52
+ internetUrl: r,
53
+ timeout: e.timeout || 1e4,
54
+ autoDetect: e.autoDetect ?? !0,
55
+ thresholds: e.thresholds || { fast: 10, medium: 2 },
56
+ useFetch: !1
57
+ };
58
+ }
59
+ if (this.options.intranetUrl && !this.options.intranetUrl.startsWith("http://") && !this.options.intranetUrl.startsWith("https://"))
60
+ throw new Error("intranetUrl/intranetImageUrl 必须是完整的HTTP/HTTPS URL");
46
61
  }
47
62
  /**
48
63
  * 执行测速
@@ -65,74 +80,74 @@ class a {
65
80
  /**
66
81
  * 测试单个URL
67
82
  */
68
- testSingleUrl(e, s) {
69
- return new Promise((n, o) => {
70
- const r = `${e}?t=${Date.now()}`;
71
- p(r);
72
- const c = new PerformanceObserver((m) => {
73
- for (const u of m.getEntries())
74
- if (u.entryType === "resource" && u.name.includes(e)) {
75
- const i = h(u);
76
- if (i) {
77
- const d = {
78
- speedMbps: i.speedMbps,
79
- speedKBps: i.speedKBps,
83
+ testSingleUrl(e, r) {
84
+ return new Promise((n, i) => {
85
+ const s = `${e}?t=${Date.now()}`;
86
+ p(s);
87
+ const c = new PerformanceObserver((f) => {
88
+ for (const a of f.getEntries())
89
+ if (a.entryType === "resource" && a.name.includes(e)) {
90
+ const o = h(a);
91
+ if (o) {
92
+ const m = {
93
+ speedMbps: o.speedMbps,
94
+ speedKBps: o.speedKBps,
80
95
  networkType: w(
81
- i.speedMbps,
96
+ o.speedMbps,
82
97
  this.options.thresholds
83
98
  ),
84
- isIntranet: s,
85
- duration: i.downloadTime,
86
- transferSize: i.transferSize,
99
+ isIntranet: r,
100
+ duration: o.downloadTime,
101
+ transferSize: o.transferSize,
87
102
  resourceUrl: e
88
103
  };
89
- c.disconnect(), n(d);
104
+ c.disconnect(), n(m);
90
105
  }
91
106
  }
92
107
  });
93
108
  c.observe({ entryTypes: ["resource"] });
94
- const l = setTimeout(() => {
95
- c.disconnect(), o(new Error(`测速超时: ${e}`));
109
+ const u = setTimeout(() => {
110
+ c.disconnect(), i(new Error(`测速超时: ${e}`));
96
111
  }, this.options.timeout);
97
- this.options.useFetch ? this.loadWithFetch(r, l, c, o) : this.loadWithImage(r, l, c, o);
112
+ this.options.useFetch ? this.loadWithFetch(s, u, c, i) : this.loadWithImage(s, u, c, i);
98
113
  });
99
114
  }
100
115
  /**
101
116
  * 使用 Image 对象加载资源(默认方式,不受跨域限制)
102
117
  */
103
- loadWithImage(e, s, n, o) {
104
- const r = new Image();
105
- r.onload = () => {
106
- clearTimeout(s);
107
- }, r.onerror = () => {
108
- clearTimeout(s), n.disconnect(), o(new Error(`图片加载失败: ${e}`));
109
- }, r.src = e;
118
+ loadWithImage(e, r, n, i) {
119
+ const s = new Image();
120
+ s.onload = () => {
121
+ clearTimeout(r);
122
+ }, s.onerror = () => {
123
+ clearTimeout(r), n.disconnect(), i(new Error(`图片加载失败: ${e}`));
124
+ }, s.src = e;
110
125
  }
111
126
  /**
112
127
  * 使用 fetch API 加载资源(需要 CORS 支持)
113
128
  */
114
- loadWithFetch(e, s, n, o) {
129
+ loadWithFetch(e, r, n, i) {
115
130
  fetch(e, {
116
131
  method: "GET",
117
132
  cache: "no-store"
118
133
  // 禁用缓存
119
- }).then((r) => {
120
- if (!r.ok)
121
- throw new Error(`HTTP error! status: ${r.status}`);
122
- return r.blob();
134
+ }).then((s) => {
135
+ if (!s.ok)
136
+ throw new Error(`HTTP error! status: ${s.status}`);
137
+ return s.blob();
123
138
  }).then(() => {
124
- clearTimeout(s);
125
- }).catch((r) => {
126
- clearTimeout(s), n.disconnect(), o(new Error(`资源加载失败: ${r.message}`));
139
+ clearTimeout(r);
140
+ }).catch((s) => {
141
+ clearTimeout(r), n.disconnect(), i(new Error(`资源加载失败: ${s.message}`));
127
142
  });
128
143
  }
129
144
  /**
130
145
  * 监听特定资源的性能数据
131
146
  */
132
- observeResource(e, s) {
133
- const n = new PerformanceObserver((o) => {
134
- for (const r of o.getEntries())
135
- r.entryType === "resource" && r.name.includes(e) && s(r);
147
+ observeResource(e, r) {
148
+ const n = new PerformanceObserver((i) => {
149
+ for (const s of i.getEntries())
150
+ s.entryType === "resource" && s.name.includes(e) && r(s);
136
151
  });
137
152
  return n.observe({ entryTypes: ["resource"] }), () => n.disconnect();
138
153
  }
@@ -149,7 +164,7 @@ class a {
149
164
  this.observer && (this.observer.disconnect(), this.observer = null);
150
165
  }
151
166
  }
152
- class S {
167
+ class U {
153
168
  tester = null;
154
169
  /**
155
170
  * 创建SDK实例
@@ -174,7 +189,7 @@ class S {
174
189
  * const speeds = sdk.getAllResourcesSpeeds();
175
190
  */
176
191
  constructor(e) {
177
- e && (this.tester = new a(e));
192
+ e && (this.tester = new l(e));
178
193
  }
179
194
  /**
180
195
  * 执行网速测试
@@ -196,7 +211,7 @@ class S {
196
211
  * @returns 资源速度信息数组
197
212
  */
198
213
  getAllResourcesSpeeds() {
199
- return f();
214
+ return d();
200
215
  }
201
216
  /**
202
217
  * 监听特定资源的性能数据
@@ -204,17 +219,17 @@ class S {
204
219
  * @param callback 回调函数
205
220
  * @returns 停止监听的函数
206
221
  */
207
- observeResource(e, s) {
208
- return this.tester || (this.tester = new a({
222
+ observeResource(e, r) {
223
+ return this.tester || (this.tester = new l({
209
224
  internetImageUrl: ""
210
- })), this.tester.observeResource(e, s);
225
+ })), this.tester.observeResource(e, r);
211
226
  }
212
227
  /**
213
228
  * 更新配置
214
229
  * @param options 新的配置选项
215
230
  */
216
231
  updateOptions(e) {
217
- this.tester = new a(e);
232
+ this.tester = new l(e);
218
233
  }
219
234
  /**
220
235
  * 销毁SDK实例
@@ -223,13 +238,13 @@ class S {
223
238
  this.tester && (this.tester.destroy(), this.tester = null);
224
239
  }
225
240
  }
226
- function U(t) {
227
- return new S(t);
241
+ function g(t) {
242
+ return new U(t);
228
243
  }
229
244
  export {
230
- S as NetworkSpeedSDK,
245
+ U as NetworkSpeedSDK,
231
246
  h as calcSpeedByResource,
232
- U as createNetworkSpeedSDK,
247
+ g as createNetworkSpeedSDK,
233
248
  w as evaluateNetworkType,
234
- f as getAllResourcesSpeeds
249
+ d as getAllResourcesSpeeds
235
250
  };
@@ -1,5 +1,5 @@
1
- (function(n,c){typeof exports=="object"&&typeof module<"u"?c(exports):typeof define=="function"&&define.amd?define(["exports"],c):(n=typeof globalThis<"u"?globalThis:n||self,c(n.NetworkSpeedJS={}))})(this,(function(n){"use strict";function c(t){const e=t.responseEnd-t.responseStart;if(e<=0||t.transferSize===0)return null;const s=t.transferSize*8/e/1e3,o=t.transferSize/e;return{name:t.name,speedMbps:Number(s.toFixed(2)),speedKBps:Number(o.toFixed(2)),downloadTime:Number(e.toFixed(2)),transferSize:t.transferSize}}function h(){return performance.getEntriesByType("resource").filter(e=>e instanceof PerformanceResourceTiming&&e.transferSize>0).map(c).filter(e=>e!==null)}function S(t){performance.getEntriesByName(t).forEach(()=>{performance.clearResourceTimings()})}function f(t,e={fast:10,medium:2}){return t>=e.fast?"fast":t>=e.medium?"medium":t>0?"slow":"unknown"}class l{options;observer=null;constructor(e){"useFetch"in e&&e.useFetch?this.options={intranetUrl:e.intranetUrl||"",internetUrl:e.internetUrl,timeout:e.timeout||1e4,autoDetect:e.autoDetect??!0,thresholds:e.thresholds||{fast:10,medium:2},useFetch:!0}:this.options={intranetUrl:"intranetImageUrl"in e&&e.intranetImageUrl||"",internetUrl:"internetImageUrl"in e?e.internetImageUrl:"",timeout:e.timeout||1e4,autoDetect:e.autoDetect??!0,thresholds:e.thresholds||{fast:10,medium:2},useFetch:!1}}async test(){return this.options.autoDetect?this.testWithAutoDetect():this.testSingleUrl(this.options.internetUrl,!1)}async testWithAutoDetect(){if(this.options.intranetUrl)try{return await this.testSingleUrl(this.options.intranetUrl,!0)}catch{console.log("内网测速失败,切换到外网测速")}return this.testSingleUrl(this.options.internetUrl,!1)}testSingleUrl(e,s){return new Promise((o,i)=>{const r=`${e}?t=${Date.now()}`;S(r);const a=new PerformanceObserver(g=>{for(const d of g.getEntries())if(d.entryType==="resource"&&d.name.includes(e)){const u=c(d);if(u){const y={speedMbps:u.speedMbps,speedKBps:u.speedKBps,networkType:f(u.speedMbps,this.options.thresholds),isIntranet:s,duration:u.downloadTime,transferSize:u.transferSize,resourceUrl:e};a.disconnect(),o(y)}}});a.observe({entryTypes:["resource"]});const p=setTimeout(()=>{a.disconnect(),i(new Error(`测速超时: ${e}`))},this.options.timeout);this.options.useFetch?this.loadWithFetch(r,p,a,i):this.loadWithImage(r,p,a,i)})}loadWithImage(e,s,o,i){const r=new Image;r.onload=()=>{clearTimeout(s)},r.onerror=()=>{clearTimeout(s),o.disconnect(),i(new Error(`图片加载失败: ${e}`))},r.src=e}loadWithFetch(e,s,o,i){fetch(e,{method:"GET",cache:"no-store"}).then(r=>{if(!r.ok)throw new Error(`HTTP error! status: ${r.status}`);return r.blob()}).then(()=>{clearTimeout(s)}).catch(r=>{clearTimeout(s),o.disconnect(),i(new Error(`资源加载失败: ${r.message}`))})}observeResource(e,s){const o=new PerformanceObserver(i=>{for(const r of i.getEntries())r.entryType==="resource"&&r.name.includes(e)&&s(r)});return o.observe({entryTypes:["resource"]}),()=>o.disconnect()}updateOptions(e){this.options={...this.options,...e}}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null)}}class m{tester=null;constructor(e){e&&(this.tester=new l(e))}async test(){if(!this.tester)throw new Error(`SDK未配置,请在构造函数中传入配置选项。
1
+ (function(n,c){typeof exports=="object"&&typeof module<"u"?c(exports):typeof define=="function"&&define.amd?define(["exports"],c):(n=typeof globalThis<"u"?globalThis:n||self,c(n.NetworkSpeedJS={}))})(this,(function(n){"use strict";function c(t){const e=t.responseEnd-t.responseStart;if(e<=0||t.transferSize===0)return null;const r=t.transferSize*8/e/1e3,i=t.transferSize/e;return{name:t.name,speedMbps:Number(r.toFixed(2)),speedKBps:Number(i.toFixed(2)),downloadTime:Number(e.toFixed(2)),transferSize:t.transferSize}}function f(){return performance.getEntriesByType("resource").filter(e=>e instanceof PerformanceResourceTiming&&e.transferSize>0).map(c).filter(e=>e!==null)}function w(t){performance.getEntriesByName(t).forEach(()=>{performance.clearResourceTimings()})}function d(t,e={fast:10,medium:2}){return t>=e.fast?"fast":t>=e.medium?"medium":t>0?"slow":"unknown"}class l{options;observer=null;constructor(e){if("useFetch"in e&&e.useFetch){if(!e.internetUrl)throw new Error("Fetch模式必须提供 internetUrl 参数");if(!e.internetUrl.startsWith("http://")&&!e.internetUrl.startsWith("https://"))throw new Error("internetUrl 必须是完整的HTTP/HTTPS URL");this.options={intranetUrl:e.intranetUrl||"",internetUrl:e.internetUrl,timeout:e.timeout||1e4,autoDetect:e.autoDetect??!0,thresholds:e.thresholds||{fast:10,medium:2},useFetch:!0}}else{const r="internetImageUrl"in e?e.internetImageUrl:"";if(!r)throw new Error("图片模式必须提供 internetImageUrl 参数");if(!r.startsWith("http://")&&!r.startsWith("https://"))throw new Error("internetImageUrl 必须是完整的HTTP/HTTPS URL");this.options={intranetUrl:"intranetImageUrl"in e&&e.intranetImageUrl||"",internetUrl:r,timeout:e.timeout||1e4,autoDetect:e.autoDetect??!0,thresholds:e.thresholds||{fast:10,medium:2},useFetch:!1}}if(this.options.intranetUrl&&!this.options.intranetUrl.startsWith("http://")&&!this.options.intranetUrl.startsWith("https://"))throw new Error("intranetUrl/intranetImageUrl 必须是完整的HTTP/HTTPS URL")}async test(){return this.options.autoDetect?this.testWithAutoDetect():this.testSingleUrl(this.options.internetUrl,!1)}async testWithAutoDetect(){if(this.options.intranetUrl)try{return await this.testSingleUrl(this.options.intranetUrl,!0)}catch{console.log("内网测速失败,切换到外网测速")}return this.testSingleUrl(this.options.internetUrl,!1)}testSingleUrl(e,r){return new Promise((i,o)=>{const s=`${e}?t=${Date.now()}`;w(s);const u=new PerformanceObserver(U=>{for(const h of U.getEntries())if(h.entryType==="resource"&&h.name.includes(e)){const a=c(h);if(a){const T={speedMbps:a.speedMbps,speedKBps:a.speedKBps,networkType:d(a.speedMbps,this.options.thresholds),isIntranet:r,duration:a.downloadTime,transferSize:a.transferSize,resourceUrl:e};u.disconnect(),i(T)}}});u.observe({entryTypes:["resource"]});const p=setTimeout(()=>{u.disconnect(),o(new Error(`测速超时: ${e}`))},this.options.timeout);this.options.useFetch?this.loadWithFetch(s,p,u,o):this.loadWithImage(s,p,u,o)})}loadWithImage(e,r,i,o){const s=new Image;s.onload=()=>{clearTimeout(r)},s.onerror=()=>{clearTimeout(r),i.disconnect(),o(new Error(`图片加载失败: ${e}`))},s.src=e}loadWithFetch(e,r,i,o){fetch(e,{method:"GET",cache:"no-store"}).then(s=>{if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);return s.blob()}).then(()=>{clearTimeout(r)}).catch(s=>{clearTimeout(r),i.disconnect(),o(new Error(`资源加载失败: ${s.message}`))})}observeResource(e,r){const i=new PerformanceObserver(o=>{for(const s of o.getEntries())s.entryType==="resource"&&s.name.includes(e)&&r(s)});return i.observe({entryTypes:["resource"]}),()=>i.disconnect()}updateOptions(e){this.options={...this.options,...e}}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null)}}class m{tester=null;constructor(e){e&&(this.tester=new l(e))}async test(){if(!this.tester)throw new Error(`SDK未配置,请在构造函数中传入配置选项。
2
2
  示例:
3
3
  new NetworkSpeedSDK({ internetImageUrl: "https://cdn.example.com/test.jpg" })
4
4
  或:
5
- new NetworkSpeedSDK({ internetUrl: "https://cdn.example.com/test.bin", useFetch: true })`);return this.tester.test()}getAllResourcesSpeeds(){return h()}observeResource(e,s){return this.tester||(this.tester=new l({internetImageUrl:""})),this.tester.observeResource(e,s)}updateOptions(e){this.tester=new l(e)}destroy(){this.tester&&(this.tester.destroy(),this.tester=null)}}function w(t){return new m(t)}n.NetworkSpeedSDK=m,n.calcSpeedByResource=c,n.createNetworkSpeedSDK=w,n.evaluateNetworkType=f,n.getAllResourcesSpeeds=h,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
5
+ new NetworkSpeedSDK({ internetUrl: "https://cdn.example.com/test.bin", useFetch: true })`);return this.tester.test()}getAllResourcesSpeeds(){return f()}observeResource(e,r){return this.tester||(this.tester=new l({internetImageUrl:""})),this.tester.observeResource(e,r)}updateOptions(e){this.tester=new l(e)}destroy(){this.tester&&(this.tester.destroy(),this.tester=null)}}function S(t){return new m(t)}n.NetworkSpeedSDK=m,n.calcSpeedByResource=c,n.createNetworkSpeedSDK=S,n.evaluateNetworkType=d,n.getAllResourcesSpeeds=f,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "network-speed-js",
3
3
  "private": false,
4
4
  "description": "A framework-agnostic network speed testing SDK based on Performance API with intranet/internet auto-detection support",
5
- "version": "1.0.5",
5
+ "version": "1.0.6",
6
6
  "author": "Sunny-117",
7
7
  "license": "MIT",
8
8
  "keywords": [