sobey-monitor-sdk 1.1.8 → 1.1.10

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/dist/index.cjs.js CHANGED
@@ -768,12 +768,93 @@ function installHttpErrorHandler() {
768
768
  * 检查是否是 SDK 自身的请求
769
769
  */
770
770
  function isSdkRequest(url) {
771
+ // 检查是否匹配常见的 SDK 上报路径
772
+ if (url.includes('/monitor/api/report') ||
773
+ url.includes('/monitor/api/beacon') ||
774
+ url.includes('/monitor/api/config')) {
775
+ return true;
776
+ }
777
+ // 配置已初始化时,检查 dsn
778
+ if (config.isInitialized()) {
779
+ const cfg = config.get();
780
+ if (cfg.dsn && url.includes(cfg.dsn)) {
781
+ return true;
782
+ }
783
+ }
784
+ return false;
785
+ }
786
+ /**
787
+ * 获取嵌套对象的字段值
788
+ * @param obj 对象
789
+ * @param path 字段路径,如 "status" 或 "data.code"
790
+ */
791
+ function getNestedValue(obj, path) {
792
+ if (!obj || typeof obj !== 'object')
793
+ return undefined;
794
+ const keys = path.split('.');
795
+ let value = obj;
796
+ for (const key of keys) {
797
+ if (value === null || value === undefined)
798
+ return undefined;
799
+ value = value[key];
800
+ }
801
+ return value;
802
+ }
803
+ /**
804
+ * 检查单条规则是否匹配
805
+ */
806
+ function matchRule(data, rule) {
807
+ const value = getNestedValue(data, rule.field);
808
+ switch (rule.operator) {
809
+ case 'exists':
810
+ return value !== undefined && value !== null;
811
+ case 'eq':
812
+ return value === rule.value;
813
+ case 'ne':
814
+ return value !== rule.value;
815
+ case 'gt':
816
+ return typeof value === 'number' && typeof rule.value === 'number' && value > rule.value;
817
+ case 'gte':
818
+ return typeof value === 'number' && typeof rule.value === 'number' && value >= rule.value;
819
+ case 'lt':
820
+ return typeof value === 'number' && typeof rule.value === 'number' && value < rule.value;
821
+ case 'lte':
822
+ return typeof value === 'number' && typeof rule.value === 'number' && value <= rule.value;
823
+ default:
824
+ return false;
825
+ }
826
+ }
827
+ /**
828
+ * 检测业务错误
829
+ * @param responseBody 响应体字符串
830
+ * @returns 是否为业务错误
831
+ */
832
+ function detectBusinessError(responseBody) {
833
+ if (!responseBody)
834
+ return false;
771
835
  if (!config.isInitialized())
772
836
  return false;
773
837
  const cfg = config.get();
774
- if (!cfg.dsn)
838
+ const rules = cfg.error?.businessErrorRules;
839
+ if (!rules || rules.length === 0)
840
+ return false;
841
+ try {
842
+ const data = JSON.parse(responseBody);
843
+ // 任一规则匹配时返回 true
844
+ for (const rule of rules) {
845
+ if (matchRule(data, rule)) {
846
+ if (cfg.debug) {
847
+ console.log('[Monitor] Business error detected by rule:', rule, 'data:', data);
848
+ }
849
+ return true;
850
+ }
851
+ }
775
852
  return false;
776
- return url.includes(cfg.dsn);
853
+ }
854
+ catch {
855
+ // JSON 解析失败,不是业务错误
856
+ return false;
857
+ }
777
858
  }
778
859
  function interceptXHR() {
779
860
  XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
@@ -826,6 +907,8 @@ function interceptXHR() {
826
907
  },
827
908
  });
828
909
  }
910
+ const responseBody = this.responseText?.substring(0, 1000);
911
+ // HTTP 错误或业务错误
829
912
  if (status === 0 || status >= 400) {
830
913
  reportHttpError({
831
914
  method: monitorData.method,
@@ -833,9 +916,20 @@ function interceptXHR() {
833
916
  status,
834
917
  duration,
835
918
  requestBody: monitorData.requestBody,
836
- responseBody: this.responseText?.substring(0, 1000), // 限制长度
919
+ responseBody,
837
920
  });
838
921
  }
922
+ else if (status >= 200 && status < 300 && detectBusinessError(responseBody)) {
923
+ // HTTP 200 但检测到业务错误
924
+ reportHttpError({
925
+ method: monitorData.method,
926
+ url: monitorData.url,
927
+ status,
928
+ duration,
929
+ requestBody: monitorData.requestBody,
930
+ responseBody,
931
+ }, true);
932
+ }
839
933
  });
840
934
  return originalXHRSend.call(this, body);
841
935
  };
@@ -877,8 +971,10 @@ function interceptFetch() {
877
971
  },
878
972
  });
879
973
  }
880
- if (!response.ok) {
881
- // 克隆响应以读取 body
974
+ // 检查是否配置了业务错误规则
975
+ const hasBusinessRules = cfg?.error?.businessErrorRules && cfg.error.businessErrorRules.length > 0;
976
+ // 只有在请求失败或配置了业务规则时才读取响应体
977
+ if (!response.ok || hasBusinessRules) {
882
978
  const cloned = response.clone();
883
979
  let responseBody;
884
980
  try {
@@ -886,14 +982,28 @@ function interceptFetch() {
886
982
  responseBody = responseBody.substring(0, 1000);
887
983
  }
888
984
  catch { }
889
- reportHttpError({
890
- method,
891
- url,
892
- status,
893
- duration,
894
- requestBody,
895
- responseBody,
896
- });
985
+ if (!response.ok) {
986
+ // HTTP 错误
987
+ reportHttpError({
988
+ method,
989
+ url,
990
+ status,
991
+ duration,
992
+ requestBody,
993
+ responseBody,
994
+ });
995
+ }
996
+ else if (hasBusinessRules && detectBusinessError(responseBody)) {
997
+ // HTTP 200 但检测到业务错误
998
+ reportHttpError({
999
+ method,
1000
+ url,
1001
+ status,
1002
+ duration,
1003
+ requestBody,
1004
+ responseBody,
1005
+ }, true);
1006
+ }
897
1007
  }
898
1008
  return response;
899
1009
  }
@@ -930,10 +1040,13 @@ function interceptFetch() {
930
1040
  /**
931
1041
  * 上报 HTTP 错误
932
1042
  */
933
- function reportHttpError(httpInfo) {
1043
+ function reportHttpError(httpInfo, isBusinessError = false) {
1044
+ const message = isBusinessError
1045
+ ? `Business Error ${httpInfo.method} ${httpInfo.url}`
1046
+ : `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`;
934
1047
  const errorData = {
935
1048
  type: 'http_error',
936
- message: `HTTP ${httpInfo.status} ${httpInfo.method} ${httpInfo.url}`,
1049
+ message,
937
1050
  httpInfo,
938
1051
  };
939
1052
  reporter.reportError(errorData);