@gctrack/network 0.1.0
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/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/index.cjs +91 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yinuo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @gctrack/network
|
|
2
|
+
|
|
3
|
+
接口监控 —— 劫持 `fetch` 与 `XMLHttpRequest`,自动产出 `api_performance` / `api_error` 事件,并标记慢接口。flyio 底层走 XHR,启用后会被自动覆盖。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @gctrack/network @gctrack/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 用法
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createTracker } from '@gctrack/core'
|
|
15
|
+
import { createNetworkMonitor } from '@gctrack/network'
|
|
16
|
+
|
|
17
|
+
const tracker = createTracker({ reportUrl: '...', appKey: '...' })
|
|
18
|
+
const net = createNetworkMonitor(tracker, {
|
|
19
|
+
ignoreUrls: [/\/health$/, '/log'], // 忽略健康检查/日志(上报地址自身已自动忽略)
|
|
20
|
+
slowThreshold: 3000, // 慢接口阈值 ms,命中标 data.slow
|
|
21
|
+
})
|
|
22
|
+
net.start()
|
|
23
|
+
// net.destroy() 还原原始 fetch / xhr
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
MIT (c) yinuo
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _gctrack_protocol = require("@gctrack/protocol");
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const ORIGINAL_FETCH = typeof fetch !== "undefined" ? fetch.bind(globalThis) : void 0;
|
|
5
|
+
const ORIGINAL_OPEN = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype.open : void 0;
|
|
6
|
+
const ORIGINAL_SEND = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype.send : void 0;
|
|
7
|
+
function createNetworkMonitor(tracker, options = {}) {
|
|
8
|
+
const { ignoreUrls = [], slowThreshold = 3e3 } = options;
|
|
9
|
+
let installed = false;
|
|
10
|
+
const isIgnored = (url) => {
|
|
11
|
+
const reportUrl = tracker.getConfig?.()?.reportUrl;
|
|
12
|
+
if (reportUrl && url.includes(reportUrl)) return true;
|
|
13
|
+
return ignoreUrls.some((p) => typeof p === "string" ? url.includes(p) : p.test(url));
|
|
14
|
+
};
|
|
15
|
+
const report = (data) => {
|
|
16
|
+
const isError = data.status === 0 || data.status >= 400;
|
|
17
|
+
tracker.track(isError ? _gctrack_protocol.EventType.ApiError : _gctrack_protocol.EventType.ApiPerformance, {
|
|
18
|
+
...data,
|
|
19
|
+
slow: data.duration >= slowThreshold
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const patchFetch = () => {
|
|
23
|
+
if (typeof fetch === "undefined" || !ORIGINAL_FETCH) return;
|
|
24
|
+
const patched = async (input, init) => {
|
|
25
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
26
|
+
const method = (init?.method || "GET").toUpperCase();
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
try {
|
|
29
|
+
const res = await ORIGINAL_FETCH(input, init);
|
|
30
|
+
if (!isIgnored(url)) report({
|
|
31
|
+
url,
|
|
32
|
+
method,
|
|
33
|
+
status: res.status,
|
|
34
|
+
duration: Date.now() - start
|
|
35
|
+
});
|
|
36
|
+
return res;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (!isIgnored(url)) report({
|
|
39
|
+
url,
|
|
40
|
+
method,
|
|
41
|
+
status: 0,
|
|
42
|
+
duration: Date.now() - start,
|
|
43
|
+
reason: err?.message || "fetch_error"
|
|
44
|
+
});
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
globalThis.fetch = patched;
|
|
49
|
+
};
|
|
50
|
+
const patchXhr = () => {
|
|
51
|
+
if (typeof XMLHttpRequest === "undefined" || !ORIGINAL_OPEN || !ORIGINAL_SEND) return;
|
|
52
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
53
|
+
this.__gctrack_method = (method || "GET").toUpperCase();
|
|
54
|
+
this.__gctrack_url = url;
|
|
55
|
+
return ORIGINAL_OPEN.apply(this, arguments);
|
|
56
|
+
};
|
|
57
|
+
XMLHttpRequest.prototype.send = function() {
|
|
58
|
+
this.__gctrack_start = Date.now();
|
|
59
|
+
this.addEventListener("loadend", () => {
|
|
60
|
+
const url = this.__gctrack_url;
|
|
61
|
+
if (!url || isIgnored(url)) return;
|
|
62
|
+
report({
|
|
63
|
+
url,
|
|
64
|
+
method: this.__gctrack_method || "GET",
|
|
65
|
+
status: this.status,
|
|
66
|
+
duration: Date.now() - (this.__gctrack_start || Date.now())
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
return ORIGINAL_SEND.apply(this, arguments);
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
start() {
|
|
74
|
+
if (installed) return;
|
|
75
|
+
installed = true;
|
|
76
|
+
patchFetch();
|
|
77
|
+
patchXhr();
|
|
78
|
+
},
|
|
79
|
+
destroy() {
|
|
80
|
+
if (!installed) return;
|
|
81
|
+
installed = false;
|
|
82
|
+
if (ORIGINAL_FETCH) globalThis.fetch = ORIGINAL_FETCH;
|
|
83
|
+
if (ORIGINAL_OPEN) XMLHttpRequest.prototype.open = ORIGINAL_OPEN;
|
|
84
|
+
if (ORIGINAL_SEND) XMLHttpRequest.prototype.send = ORIGINAL_SEND;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
exports.createNetworkMonitor = createNetworkMonitor;
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["EventType"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @gctrack/network —— 接口监控\n *\n * 劫持 fetch 与 XMLHttpRequest,自动产出 api_performance / api_error 事件。\n * flyio 底层走 XHR,会被自动覆盖;启用本包后业务层 request 封装无需重复埋点。\n */\nimport type { Tracker } from '@gctrack/core'\nimport { EventType, type ApiEventData } from '@gctrack/protocol'\n\nexport interface NetworkMonitorOptions {\n /** 忽略的 URL(字符串包含匹配或正则);上报地址自身始终被忽略以防循环 */\n ignoreUrls?: Array<string | RegExp>\n /** 慢接口阈值(ms),超过仅用于 data.slow 标记,不影响事件类型 */\n slowThreshold?: number\n}\n\nexport interface NetworkMonitor {\n start: () => void\n destroy: () => void\n}\n\nconst ORIGINAL_FETCH = typeof fetch !== 'undefined' ? fetch.bind(globalThis) : undefined\nconst ORIGINAL_OPEN =\n typeof XMLHttpRequest !== 'undefined' ? XMLHttpRequest.prototype.open : undefined\nconst ORIGINAL_SEND =\n typeof XMLHttpRequest !== 'undefined' ? XMLHttpRequest.prototype.send : undefined\n\nexport function createNetworkMonitor(\n tracker: Tracker,\n options: NetworkMonitorOptions = {},\n): NetworkMonitor {\n const { ignoreUrls = [], slowThreshold = 3000 } = options\n let installed = false\n\n const isIgnored = (url: string): boolean => {\n const reportUrl = (tracker as any).getConfig?.()?.reportUrl\n if (reportUrl && url.includes(reportUrl)) return true\n return ignoreUrls.some((p) =>\n typeof p === 'string' ? url.includes(p) : p.test(url),\n )\n }\n\n const report = (data: ApiEventData): void => {\n const isError = data.status === 0 || data.status >= 400\n tracker.track(isError ? EventType.ApiError : EventType.ApiPerformance, {\n ...data,\n slow: data.duration >= slowThreshold,\n })\n }\n\n const patchFetch = (): void => {\n if (typeof fetch === 'undefined' || !ORIGINAL_FETCH) return\n const patched = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.href\n : input.url\n const method = (init?.method || 'GET').toUpperCase()\n const start = Date.now()\n try {\n const res = await ORIGINAL_FETCH(input as RequestInfo, init)\n if (!isIgnored(url)) {\n report({ url, method, status: res.status, duration: Date.now() - start })\n }\n return res\n } catch (err) {\n if (!isIgnored(url)) {\n report({\n url,\n method,\n status: 0,\n duration: Date.now() - start,\n reason: (err as Error)?.message || 'fetch_error',\n })\n }\n throw err\n }\n }\n ;(globalThis as any).fetch = patched\n }\n\n const patchXhr = (): void => {\n if (typeof XMLHttpRequest === 'undefined' || !ORIGINAL_OPEN || !ORIGINAL_SEND) return\n XMLHttpRequest.prototype.open = function (\n this: XMLHttpRequest & Record<string, any>,\n method: string,\n url: string,\n ) {\n this.__gctrack_method = (method || 'GET').toUpperCase()\n this.__gctrack_url = url\n return ORIGINAL_OPEN.apply(this, arguments as any)\n }\n XMLHttpRequest.prototype.send = function (\n this: XMLHttpRequest & Record<string, any>,\n ) {\n this.__gctrack_start = Date.now()\n this.addEventListener('loadend', () => {\n const url = this.__gctrack_url\n if (!url || isIgnored(url)) return\n report({\n url,\n method: this.__gctrack_method || 'GET',\n status: this.status,\n duration: Date.now() - (this.__gctrack_start || Date.now()),\n })\n })\n return ORIGINAL_SEND.apply(this, arguments as any)\n }\n }\n\n return {\n start() {\n if (installed) return\n installed = true\n patchFetch()\n patchXhr()\n },\n destroy() {\n if (!installed) return\n installed = false\n if (ORIGINAL_FETCH) (globalThis as any).fetch = ORIGINAL_FETCH\n if (ORIGINAL_OPEN) XMLHttpRequest.prototype.open = ORIGINAL_OPEN\n if (ORIGINAL_SEND) XMLHttpRequest.prototype.send = ORIGINAL_SEND\n },\n }\n}\n"],"mappings":";;;AAqBA,MAAM,iBAAiB,OAAO,UAAU,cAAc,MAAM,KAAK,WAAW,GAAG,KAAA;AAC/E,MAAM,gBACJ,OAAO,mBAAmB,cAAc,eAAe,UAAU,OAAO,KAAA;AAC1E,MAAM,gBACJ,OAAO,mBAAmB,cAAc,eAAe,UAAU,OAAO,KAAA;AAE1E,SAAgB,qBACd,SACA,UAAiC,EAAE,EACnB;CAChB,MAAM,EAAE,aAAa,EAAE,EAAE,gBAAgB,QAAS;CAClD,IAAI,YAAY;CAEhB,MAAM,aAAa,QAAyB;EAC1C,MAAM,YAAa,QAAgB,aAAa,EAAE;EAClD,IAAI,aAAa,IAAI,SAAS,UAAU,EAAE,OAAO;EACjD,OAAO,WAAW,MAAM,MACtB,OAAO,MAAM,WAAW,IAAI,SAAS,EAAE,GAAG,EAAE,KAAK,IAAI,CACtD;;CAGH,MAAM,UAAU,SAA6B;EAC3C,MAAM,UAAU,KAAK,WAAW,KAAK,KAAK,UAAU;EACpD,QAAQ,MAAM,UAAUA,kBAAAA,UAAU,WAAWA,kBAAAA,UAAU,gBAAgB;GACrE,GAAG;GACH,MAAM,KAAK,YAAY;GACxB,CAAC;;CAGJ,MAAM,mBAAyB;EAC7B,IAAI,OAAO,UAAU,eAAe,CAAC,gBAAgB;EACrD,MAAM,UAAU,OACd,OACA,SACsB;GACtB,MAAM,MACJ,OAAO,UAAU,WACb,QACA,iBAAiB,MACf,MAAM,OACN,MAAM;GACd,MAAM,UAAU,MAAM,UAAU,OAAO,aAAa;GACpD,MAAM,QAAQ,KAAK,KAAK;GACxB,IAAI;IACF,MAAM,MAAM,MAAM,eAAe,OAAsB,KAAK;IAC5D,IAAI,CAAC,UAAU,IAAI,EACjB,OAAO;KAAE;KAAK;KAAQ,QAAQ,IAAI;KAAQ,UAAU,KAAK,KAAK,GAAG;KAAO,CAAC;IAE3E,OAAO;YACA,KAAK;IACZ,IAAI,CAAC,UAAU,IAAI,EACjB,OAAO;KACL;KACA;KACA,QAAQ;KACR,UAAU,KAAK,KAAK,GAAG;KACvB,QAAS,KAAe,WAAW;KACpC,CAAC;IAEJ,MAAM;;;EAGT,WAAoB,QAAQ;;CAG/B,MAAM,iBAAuB;EAC3B,IAAI,OAAO,mBAAmB,eAAe,CAAC,iBAAiB,CAAC,eAAe;EAC/E,eAAe,UAAU,OAAO,SAE9B,QACA,KACA;GACA,KAAK,oBAAoB,UAAU,OAAO,aAAa;GACvD,KAAK,gBAAgB;GACrB,OAAO,cAAc,MAAM,MAAM,UAAiB;;EAEpD,eAAe,UAAU,OAAO,WAE9B;GACA,KAAK,kBAAkB,KAAK,KAAK;GACjC,KAAK,iBAAiB,iBAAiB;IACrC,MAAM,MAAM,KAAK;IACjB,IAAI,CAAC,OAAO,UAAU,IAAI,EAAE;IAC5B,OAAO;KACL;KACA,QAAQ,KAAK,oBAAoB;KACjC,QAAQ,KAAK;KACb,UAAU,KAAK,KAAK,IAAI,KAAK,mBAAmB,KAAK,KAAK;KAC3D,CAAC;KACF;GACF,OAAO,cAAc,MAAM,MAAM,UAAiB;;;CAItD,OAAO;EACL,QAAQ;GACN,IAAI,WAAW;GACf,YAAY;GACZ,YAAY;GACZ,UAAU;;EAEZ,UAAU;GACR,IAAI,CAAC,WAAW;GAChB,YAAY;GACZ,IAAI,gBAAgB,WAAoB,QAAQ;GAChD,IAAI,eAAe,eAAe,UAAU,OAAO;GACnD,IAAI,eAAe,eAAe,UAAU,OAAO;;EAEtD"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Tracker } from "@gctrack/core";
|
|
2
|
+
export interface NetworkMonitorOptions {
|
|
3
|
+
ignoreUrls?: Array<string | RegExp>;
|
|
4
|
+
slowThreshold?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface NetworkMonitor {
|
|
7
|
+
start: () => void;
|
|
8
|
+
destroy: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createNetworkMonitor(tracker: Tracker, options?: NetworkMonitorOptions): NetworkMonitor;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Tracker } from "@gctrack/core";
|
|
2
|
+
export interface NetworkMonitorOptions {
|
|
3
|
+
ignoreUrls?: Array<string | RegExp>;
|
|
4
|
+
slowThreshold?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface NetworkMonitor {
|
|
7
|
+
start: () => void;
|
|
8
|
+
destroy: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createNetworkMonitor(tracker: Tracker, options?: NetworkMonitorOptions): NetworkMonitor;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { EventType } from "@gctrack/protocol";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const ORIGINAL_FETCH = typeof fetch !== "undefined" ? fetch.bind(globalThis) : void 0;
|
|
5
|
+
const ORIGINAL_OPEN = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype.open : void 0;
|
|
6
|
+
const ORIGINAL_SEND = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype.send : void 0;
|
|
7
|
+
function createNetworkMonitor(tracker, options = {}) {
|
|
8
|
+
const { ignoreUrls = [], slowThreshold = 3e3 } = options;
|
|
9
|
+
let installed = false;
|
|
10
|
+
const isIgnored = (url) => {
|
|
11
|
+
const reportUrl = tracker.getConfig?.()?.reportUrl;
|
|
12
|
+
if (reportUrl && url.includes(reportUrl)) return true;
|
|
13
|
+
return ignoreUrls.some((p) => typeof p === "string" ? url.includes(p) : p.test(url));
|
|
14
|
+
};
|
|
15
|
+
const report = (data) => {
|
|
16
|
+
const isError = data.status === 0 || data.status >= 400;
|
|
17
|
+
tracker.track(isError ? EventType.ApiError : EventType.ApiPerformance, {
|
|
18
|
+
...data,
|
|
19
|
+
slow: data.duration >= slowThreshold
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const patchFetch = () => {
|
|
23
|
+
if (typeof fetch === "undefined" || !ORIGINAL_FETCH) return;
|
|
24
|
+
const patched = async (input, init) => {
|
|
25
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
26
|
+
const method = (init?.method || "GET").toUpperCase();
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
try {
|
|
29
|
+
const res = await ORIGINAL_FETCH(input, init);
|
|
30
|
+
if (!isIgnored(url)) report({
|
|
31
|
+
url,
|
|
32
|
+
method,
|
|
33
|
+
status: res.status,
|
|
34
|
+
duration: Date.now() - start
|
|
35
|
+
});
|
|
36
|
+
return res;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (!isIgnored(url)) report({
|
|
39
|
+
url,
|
|
40
|
+
method,
|
|
41
|
+
status: 0,
|
|
42
|
+
duration: Date.now() - start,
|
|
43
|
+
reason: err?.message || "fetch_error"
|
|
44
|
+
});
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
globalThis.fetch = patched;
|
|
49
|
+
};
|
|
50
|
+
const patchXhr = () => {
|
|
51
|
+
if (typeof XMLHttpRequest === "undefined" || !ORIGINAL_OPEN || !ORIGINAL_SEND) return;
|
|
52
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
53
|
+
this.__gctrack_method = (method || "GET").toUpperCase();
|
|
54
|
+
this.__gctrack_url = url;
|
|
55
|
+
return ORIGINAL_OPEN.apply(this, arguments);
|
|
56
|
+
};
|
|
57
|
+
XMLHttpRequest.prototype.send = function() {
|
|
58
|
+
this.__gctrack_start = Date.now();
|
|
59
|
+
this.addEventListener("loadend", () => {
|
|
60
|
+
const url = this.__gctrack_url;
|
|
61
|
+
if (!url || isIgnored(url)) return;
|
|
62
|
+
report({
|
|
63
|
+
url,
|
|
64
|
+
method: this.__gctrack_method || "GET",
|
|
65
|
+
status: this.status,
|
|
66
|
+
duration: Date.now() - (this.__gctrack_start || Date.now())
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
return ORIGINAL_SEND.apply(this, arguments);
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
start() {
|
|
74
|
+
if (installed) return;
|
|
75
|
+
installed = true;
|
|
76
|
+
patchFetch();
|
|
77
|
+
patchXhr();
|
|
78
|
+
},
|
|
79
|
+
destroy() {
|
|
80
|
+
if (!installed) return;
|
|
81
|
+
installed = false;
|
|
82
|
+
if (ORIGINAL_FETCH) globalThis.fetch = ORIGINAL_FETCH;
|
|
83
|
+
if (ORIGINAL_OPEN) XMLHttpRequest.prototype.open = ORIGINAL_OPEN;
|
|
84
|
+
if (ORIGINAL_SEND) XMLHttpRequest.prototype.send = ORIGINAL_SEND;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { createNetworkMonitor };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @gctrack/network —— 接口监控\n *\n * 劫持 fetch 与 XMLHttpRequest,自动产出 api_performance / api_error 事件。\n * flyio 底层走 XHR,会被自动覆盖;启用本包后业务层 request 封装无需重复埋点。\n */\nimport type { Tracker } from '@gctrack/core'\nimport { EventType, type ApiEventData } from '@gctrack/protocol'\n\nexport interface NetworkMonitorOptions {\n /** 忽略的 URL(字符串包含匹配或正则);上报地址自身始终被忽略以防循环 */\n ignoreUrls?: Array<string | RegExp>\n /** 慢接口阈值(ms),超过仅用于 data.slow 标记,不影响事件类型 */\n slowThreshold?: number\n}\n\nexport interface NetworkMonitor {\n start: () => void\n destroy: () => void\n}\n\nconst ORIGINAL_FETCH = typeof fetch !== 'undefined' ? fetch.bind(globalThis) : undefined\nconst ORIGINAL_OPEN =\n typeof XMLHttpRequest !== 'undefined' ? XMLHttpRequest.prototype.open : undefined\nconst ORIGINAL_SEND =\n typeof XMLHttpRequest !== 'undefined' ? XMLHttpRequest.prototype.send : undefined\n\nexport function createNetworkMonitor(\n tracker: Tracker,\n options: NetworkMonitorOptions = {},\n): NetworkMonitor {\n const { ignoreUrls = [], slowThreshold = 3000 } = options\n let installed = false\n\n const isIgnored = (url: string): boolean => {\n const reportUrl = (tracker as any).getConfig?.()?.reportUrl\n if (reportUrl && url.includes(reportUrl)) return true\n return ignoreUrls.some((p) =>\n typeof p === 'string' ? url.includes(p) : p.test(url),\n )\n }\n\n const report = (data: ApiEventData): void => {\n const isError = data.status === 0 || data.status >= 400\n tracker.track(isError ? EventType.ApiError : EventType.ApiPerformance, {\n ...data,\n slow: data.duration >= slowThreshold,\n })\n }\n\n const patchFetch = (): void => {\n if (typeof fetch === 'undefined' || !ORIGINAL_FETCH) return\n const patched = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.href\n : input.url\n const method = (init?.method || 'GET').toUpperCase()\n const start = Date.now()\n try {\n const res = await ORIGINAL_FETCH(input as RequestInfo, init)\n if (!isIgnored(url)) {\n report({ url, method, status: res.status, duration: Date.now() - start })\n }\n return res\n } catch (err) {\n if (!isIgnored(url)) {\n report({\n url,\n method,\n status: 0,\n duration: Date.now() - start,\n reason: (err as Error)?.message || 'fetch_error',\n })\n }\n throw err\n }\n }\n ;(globalThis as any).fetch = patched\n }\n\n const patchXhr = (): void => {\n if (typeof XMLHttpRequest === 'undefined' || !ORIGINAL_OPEN || !ORIGINAL_SEND) return\n XMLHttpRequest.prototype.open = function (\n this: XMLHttpRequest & Record<string, any>,\n method: string,\n url: string,\n ) {\n this.__gctrack_method = (method || 'GET').toUpperCase()\n this.__gctrack_url = url\n return ORIGINAL_OPEN.apply(this, arguments as any)\n }\n XMLHttpRequest.prototype.send = function (\n this: XMLHttpRequest & Record<string, any>,\n ) {\n this.__gctrack_start = Date.now()\n this.addEventListener('loadend', () => {\n const url = this.__gctrack_url\n if (!url || isIgnored(url)) return\n report({\n url,\n method: this.__gctrack_method || 'GET',\n status: this.status,\n duration: Date.now() - (this.__gctrack_start || Date.now()),\n })\n })\n return ORIGINAL_SEND.apply(this, arguments as any)\n }\n }\n\n return {\n start() {\n if (installed) return\n installed = true\n patchFetch()\n patchXhr()\n },\n destroy() {\n if (!installed) return\n installed = false\n if (ORIGINAL_FETCH) (globalThis as any).fetch = ORIGINAL_FETCH\n if (ORIGINAL_OPEN) XMLHttpRequest.prototype.open = ORIGINAL_OPEN\n if (ORIGINAL_SEND) XMLHttpRequest.prototype.send = ORIGINAL_SEND\n },\n }\n}\n"],"mappings":";;;AAqBA,MAAM,iBAAiB,OAAO,UAAU,cAAc,MAAM,KAAK,WAAW,GAAG,KAAA;AAC/E,MAAM,gBACJ,OAAO,mBAAmB,cAAc,eAAe,UAAU,OAAO,KAAA;AAC1E,MAAM,gBACJ,OAAO,mBAAmB,cAAc,eAAe,UAAU,OAAO,KAAA;AAE1E,SAAgB,qBACd,SACA,UAAiC,EAAE,EACnB;CAChB,MAAM,EAAE,aAAa,EAAE,EAAE,gBAAgB,QAAS;CAClD,IAAI,YAAY;CAEhB,MAAM,aAAa,QAAyB;EAC1C,MAAM,YAAa,QAAgB,aAAa,EAAE;EAClD,IAAI,aAAa,IAAI,SAAS,UAAU,EAAE,OAAO;EACjD,OAAO,WAAW,MAAM,MACtB,OAAO,MAAM,WAAW,IAAI,SAAS,EAAE,GAAG,EAAE,KAAK,IAAI,CACtD;;CAGH,MAAM,UAAU,SAA6B;EAC3C,MAAM,UAAU,KAAK,WAAW,KAAK,KAAK,UAAU;EACpD,QAAQ,MAAM,UAAU,UAAU,WAAW,UAAU,gBAAgB;GACrE,GAAG;GACH,MAAM,KAAK,YAAY;GACxB,CAAC;;CAGJ,MAAM,mBAAyB;EAC7B,IAAI,OAAO,UAAU,eAAe,CAAC,gBAAgB;EACrD,MAAM,UAAU,OACd,OACA,SACsB;GACtB,MAAM,MACJ,OAAO,UAAU,WACb,QACA,iBAAiB,MACf,MAAM,OACN,MAAM;GACd,MAAM,UAAU,MAAM,UAAU,OAAO,aAAa;GACpD,MAAM,QAAQ,KAAK,KAAK;GACxB,IAAI;IACF,MAAM,MAAM,MAAM,eAAe,OAAsB,KAAK;IAC5D,IAAI,CAAC,UAAU,IAAI,EACjB,OAAO;KAAE;KAAK;KAAQ,QAAQ,IAAI;KAAQ,UAAU,KAAK,KAAK,GAAG;KAAO,CAAC;IAE3E,OAAO;YACA,KAAK;IACZ,IAAI,CAAC,UAAU,IAAI,EACjB,OAAO;KACL;KACA;KACA,QAAQ;KACR,UAAU,KAAK,KAAK,GAAG;KACvB,QAAS,KAAe,WAAW;KACpC,CAAC;IAEJ,MAAM;;;EAGT,WAAoB,QAAQ;;CAG/B,MAAM,iBAAuB;EAC3B,IAAI,OAAO,mBAAmB,eAAe,CAAC,iBAAiB,CAAC,eAAe;EAC/E,eAAe,UAAU,OAAO,SAE9B,QACA,KACA;GACA,KAAK,oBAAoB,UAAU,OAAO,aAAa;GACvD,KAAK,gBAAgB;GACrB,OAAO,cAAc,MAAM,MAAM,UAAiB;;EAEpD,eAAe,UAAU,OAAO,WAE9B;GACA,KAAK,kBAAkB,KAAK,KAAK;GACjC,KAAK,iBAAiB,iBAAiB;IACrC,MAAM,MAAM,KAAK;IACjB,IAAI,CAAC,OAAO,UAAU,IAAI,EAAE;IAC5B,OAAO;KACL;KACA,QAAQ,KAAK,oBAAoB;KACjC,QAAQ,KAAK;KACb,UAAU,KAAK,KAAK,IAAI,KAAK,mBAAmB,KAAK,KAAK;KAC3D,CAAC;KACF;GACF,OAAO,cAAc,MAAM,MAAM,UAAiB;;;CAItD,OAAO;EACL,QAAQ;GACN,IAAI,WAAW;GACf,YAAY;GACZ,YAAY;GACZ,UAAU;;EAEZ,UAAU;GACR,IAAI,CAAC,WAAW;GAChB,YAAY;GACZ,IAAI,gBAAgB,WAAoB,QAAQ;GAChD,IAAI,eAAe,eAAe,UAAU,OAAO;GACnD,IAAI,eAAe,eAAe,UAAU,OAAO;;EAEtD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gctrack/network",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GCTrack 接口监控 —— fetch / XMLHttpRequest 劫持,产出 api_performance / api_error",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"monitor",
|
|
21
|
+
"tracking",
|
|
22
|
+
"api",
|
|
23
|
+
"fetch",
|
|
24
|
+
"xhr"
|
|
25
|
+
],
|
|
26
|
+
"author": "yinuo",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@gctrack/protocol": "0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@gctrack/core": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"dev": "tsdown --watch"
|
|
40
|
+
}
|
|
41
|
+
}
|