-
-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathindex.js
More file actions
1650 lines (1453 loc) · 56.8 KB
/
index.js
File metadata and controls
1650 lines (1453 loc) · 56.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(function (win) {
'use strict';
// 检查是否已经加载过
if (win.__crossRequestLoaded) {
return;
}
win.__crossRequestLoaded = true;
const isYapiContext = () => {
try {
return !!(win.document && win.document.getElementById('yapi'));
} catch (e) {
return false;
}
};
const toAbsoluteUrl = (inputUrl) => {
if (typeof inputUrl !== 'string') return inputUrl;
const url = inputUrl.trim();
if (!url) return url;
// 已经是绝对 URL(含协议)
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
return url;
}
// protocol-relative URL: //example.com/path
if (url.startsWith('//')) {
return (win.location && win.location.protocol ? win.location.protocol : 'https:') + url;
}
try {
return new URL(
url,
win.location && win.location.href ? win.location.href : undefined
).toString();
} catch (e) {
return url;
}
};
// 静默模式:非 YApi 页面默认静默(避免误拦截导致页面加载失败)
// 允许外部显式强制静默:window.__crossRequestSilentMode = true
const isSilentMode = win.__crossRequestSilentMode === true ? true : !isYapiContext();
// 静默模式下不输出调试日志
const debugLog = isSilentMode ? () => {} : console.log.bind(console);
debugLog('[Index] index.js 脚本开始执行(' + (isSilentMode ? '静默' : '完整') + '模式)');
// 使用提取的 helpers(由 content-script.js 预先加载)
// 提供内联 fallback 确保扩展不会因为 helper 加载失败而崩溃
const helpers = win.CrossRequestHelpers || {};
const resolveYapiUrlTemplate = (inputUrl) => {
if (!isYapiContext()) return inputUrl;
if (typeof inputUrl !== 'string') return inputUrl;
if (!inputUrl.includes('{')) return inputUrl;
if (!win.document) return inputUrl;
// 仅在接口测试(运行)面板存在时处理,避免影响 YApi 本身的内部请求
const postmanRoot = win.document.querySelector('.interface-test.postman');
if (!postmanRoot) return inputUrl;
const extract =
helpers.extractUrlPlaceholders ||
function (url) {
const re = /\{([^}]+)\}/g;
const seen = new Set();
const out = [];
let m;
while ((m = re.exec(url))) {
const name = String(m[1] || '').trim();
if (!name || seen.has(name)) continue;
seen.add(name);
out.push(name);
}
return out;
};
const names = extract(inputUrl);
if (!names.length) return inputUrl;
const values = {};
const findValueInput = (keyInput) => {
if (!keyInput) return null;
const row =
keyInput.closest('.ant-row') ||
keyInput.closest('.key-value-wrap') ||
keyInput.parentElement ||
postmanRoot;
const rowInputs = Array.from((row || postmanRoot).querySelectorAll('input'));
const idx = rowInputs.indexOf(keyInput);
const isCandidate = (el) => {
if (!el || el === keyInput) return false;
if (el.disabled) return false;
if (el.type === 'hidden') return false;
if (el.classList && el.classList.contains('ant-input-disabled')) return false;
return true;
};
if (idx >= 0) {
const after = rowInputs.slice(idx + 1).filter(isCandidate);
if (after.length) return after[0];
}
const any = rowInputs.filter(isCandidate);
return any.length ? any[0] : null;
};
const keyInputs = Array.from(postmanRoot.querySelectorAll('input[disabled]'));
names.forEach((name) => {
const keyInput = keyInputs.find((el) => (el.value || '').trim() === name);
if (!keyInput) return;
const valueInput = findValueInput(keyInput);
const value = valueInput && valueInput.value != null ? String(valueInput.value).trim() : '';
if (value) values[name] = value;
});
if (!Object.keys(values).length) return inputUrl;
const apply =
helpers.applyUrlPlaceholders ||
function (url, map) {
return url.replace(/\{([^}]+)\}/g, (match, rawName) => {
const name = String(rawName || '').trim();
if (!name) return match;
if (!Object.prototype.hasOwnProperty.call(map, name)) return match;
const v = map[name];
if (v === undefined || v === null) return match;
return String(v);
});
};
return apply(inputUrl, values);
};
const buildFixedHeaderStorageKey = () => {
if (helpers.buildFixedHeaderStorageKey) return helpers.buildFixedHeaderStorageKey();
const host =
win.location && win.location.host
? win.location.host
: win.location && win.location.hostname
? win.location.hostname
: 'unknown';
return `__crm_fixed_headers_${host}`;
};
const normalizeHeaderEntries = (input) => {
if (helpers.normalizeHeaderEntries) return helpers.normalizeHeaderEntries(input);
let rawList = [];
if (!input) return [];
if (Array.isArray(input)) {
rawList = input;
} else if (typeof input === 'object') {
if (Array.isArray(input.headers)) {
rawList = input.headers;
} else {
rawList = Object.entries(input).map(([key, value]) => ({ key, value }));
}
} else {
return [];
}
const entries = [];
rawList.forEach((item) => {
if (!item) return;
if (Array.isArray(item)) {
if (item.length < 2) return;
entries.push({ key: item[0], value: item[1] });
return;
}
if (typeof item === 'object') {
const key = item.key || item.name || item.header || '';
let value = '';
if (Object.prototype.hasOwnProperty.call(item, 'value')) {
value = item.value;
} else if (Object.prototype.hasOwnProperty.call(item, 'val')) {
value = item.val;
}
entries.push({ key, value });
}
});
return entries
.map(({ key, value }) => ({
key: String(key == null ? '' : key).trim(),
value: value == null ? '' : String(value)
}))
.filter((entry) => entry.key);
};
const readFixedHeaderEntries = () => {
const storageKey = buildFixedHeaderStorageKey();
if (!storageKey) return [];
try {
const raw = win.localStorage ? win.localStorage.getItem(storageKey) : '';
if (!raw) return [];
return normalizeHeaderEntries(JSON.parse(raw));
} catch (e) {
return [];
}
};
const mergeFixedHeaders = (headers, entries) => {
if (helpers.mergeFixedHeaders) {
return helpers.mergeFixedHeaders(headers, entries, { preferExisting: true });
}
const base =
headers && typeof headers === 'object' && !Array.isArray(headers) ? { ...headers } : {};
const normalized = normalizeHeaderEntries(entries);
if (!normalized.length) return base;
const existingKeys = new Set(Object.keys(base).map((key) => key.toLowerCase()));
normalized.forEach(({ key, value }) => {
const lower = key.toLowerCase();
if (existingKeys.has(lower)) return;
base[key] = value;
existingKeys.add(lower);
});
return base;
};
const applyFixedHeaders = (headers) => {
const fixedEntries = readFixedHeaderEntries();
if (!fixedEntries.length) {
return headers || {};
}
return mergeFixedHeaders(headers, fixedEntries);
};
// Fallback: bodyToString
if (!helpers.bodyToString) {
console.warn('[Index] bodyToString helper 未加载,使用内联 fallback');
helpers.bodyToString = function (body) {
if (body === undefined || body === null) {
return '';
}
if (typeof body === 'object') {
return JSON.stringify(body);
}
if (typeof body === 'string') {
return body;
}
return String(body);
};
}
// Fallback: buildQueryString
if (!helpers.buildQueryString) {
console.warn('[Index] buildQueryString helper 未加载,使用内联 fallback');
helpers.buildQueryString = function (params) {
if (!params || typeof params !== 'object') {
return '';
}
const pairs = [];
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = params[key];
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
value.forEach((item) => {
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(item)}`);
});
} else if (typeof value === 'object') {
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(value))}`);
} else {
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
}
}
}
}
return pairs.length > 0 ? pairs.join('&') : '';
};
}
// Fallback: safeLogResponse
if (!helpers.safeLogResponse) {
console.warn('[Index] safeLogResponse helper 未加载,使用内联 fallback');
helpers.safeLogResponse = function (originalBody, options) {
const opts = options || {};
const maxBytes = typeof opts.maxBytes === 'number' ? opts.maxBytes : 10 * 1024;
const headChars = typeof opts.headChars === 'number' ? opts.headChars : 512;
const tailChars = typeof opts.tailChars === 'number' ? opts.tailChars : 512;
function toText(value) {
if (value == null) {
return '';
}
if (typeof value === 'string') {
return value;
}
try {
return JSON.stringify(value);
} catch (e) {
return String(value);
}
}
const text = toText(originalBody);
let byteLength;
if (typeof TextEncoder !== 'undefined') {
byteLength = new TextEncoder().encode(text).length;
} else {
byteLength = text.length * 2;
}
if (byteLength <= maxBytes) {
return originalBody;
}
return {
truncated: true,
size: byteLength + ' bytes',
head: text.slice(0, headChars),
tail: tailChars > 0 ? text.slice(-tailChars) : '',
hint: '响应体过大,已截断显示'
};
};
}
// 创建跨域请求的 API
const CrossRequestAPI = {
// 请求计数器
requestId: 0,
// 待处理的请求
pendingRequests: new Map(),
// 发送跨域请求
async request(options) {
const id = `request-${++this.requestId}`;
const promise = new Promise((resolve, reject) => {
// 保存回调
this.pendingRequests.set(id, { resolve, reject, cleanup: null });
});
// 规范化 method 为大写,确保大小写不敏感的比较
const method = (options.method || 'GET').toUpperCase();
// 兼容 YMFE/cross-request PR #7 的旧版文件上传参数:
// - options.files: { fieldName: inputElementId }
// - options.file: inputElementId(单文件 raw body)
let data = options.data || options.body;
if (helpers.buildMultipartBodyFromLegacyFiles && (options.files || options.file)) {
try {
const multipart = helpers.buildMultipartBodyFromLegacyFiles(
data,
options.files,
options.file
);
if (multipart) {
data = multipart;
}
} catch (e) {
console.warn('[Index] 旧版 files/file 参数转换失败:', e.message);
}
}
let url = options.url;
let body = data;
// YApi 运行面板:将 /path/{param} 用当前输入值替换,避免请求失败/生成错误 cURL
url = resolveYapiUrlTemplate(url);
// 对于 GET/HEAD 请求,将参数转换为查询字符串附加到 URL
if ((method === 'GET' || method === 'HEAD') && data) {
const queryString =
typeof data === 'object' ? helpers.buildQueryString(data) : String(data);
if (queryString) {
url = url + (url.includes('?') ? '&' : '?') + queryString;
}
body = undefined; // GET/HEAD 请求不应该有 body
}
// 背景页 fetch 需要绝对 URL:非 YApi 页面常见使用相对路径(如 /api/...)
url = toAbsoluteUrl(url);
// 支持 FormData/File/Blob 的序列化(Issue #14)
if (body !== undefined && helpers.serializeRequestBody) {
try {
body = await helpers.serializeRequestBody(body);
} catch (e) {
console.warn('[Index] FormData 序列化失败,降级为原始 body:', e.message);
}
}
// 创建请求数据
const requestData = {
id,
url,
method,
headers: applyFixedHeaders(options.headers || {}),
body,
timeout: options.timeout || 30000
};
const pending = this.pendingRequests.get(id);
const tryPortTransport = () => {
if (typeof window === 'undefined' || typeof window.postMessage !== 'function') return false;
if (typeof MessageChannel === 'undefined') return false;
const channel = new MessageChannel();
const responsePort = channel.port1;
const requestPort = channel.port2;
const cleanup = () => {
try {
responsePort.onmessage = null;
responsePort.onmessageerror = null;
responsePort.close();
} catch (e) {
void e;
}
};
if (pending) {
pending.cleanup = cleanup;
}
responsePort.onmessage = (evt) => {
const msg = evt && evt.data ? evt.data : null;
if (!msg || msg.id !== id) return;
if (msg.type === 'y-request-response') {
this.handleResponse({ detail: { id: msg.id, response: msg.response } });
} else if (msg.type === 'y-request-error') {
this.handleError({ detail: { id: msg.id, error: msg.error } });
}
};
responsePort.onmessageerror = () => {
this.handleError({ detail: { id, error: '消息通道错误' } });
};
// 兼容部分浏览器需要显式 start()
try {
responsePort.start && responsePort.start();
} catch (e) {
void e;
}
const targetOrigin =
window.location && window.location.origin && window.location.origin !== 'null'
? window.location.origin
: '*';
try {
window.postMessage(
{ __crossRequestMaster: true, type: 'cross-request-port', request: requestData },
targetOrigin,
[requestPort]
);
return true;
} catch (e) {
cleanup();
if (pending) pending.cleanup = null;
return false;
}
};
const sentViaPort = tryPortTransport();
if (!sentViaPort) {
// 将请求数据编码并插入到 DOM(fallback)
const container = document.createElement('div');
container.id = `y-request-${id}`;
container.style.display = 'none';
container.textContent = btoa(encodeURIComponent(JSON.stringify(requestData)));
(document.body || document.documentElement).appendChild(container);
}
// 设置超时
setTimeout(() => {
if (this.pendingRequests.has(id)) {
const pending = this.pendingRequests.get(id);
const timeoutResponse = {
status: 0,
statusText: '请求超时',
headers: {},
data: { error: '请求超时' },
body: JSON.stringify({ error: '请求超时' }),
ok: false,
isError: true
};
if (pending && typeof pending.cleanup === 'function') {
pending.cleanup();
}
pending.resolve(timeoutResponse);
this.pendingRequests.delete(id);
}
}, requestData.timeout);
return promise;
},
// 处理响应
handleResponse(event) {
const { id, response } = event.detail;
const pending = this.pendingRequests.get(id);
if (pending) {
if (typeof pending.cleanup === 'function') {
pending.cleanup();
}
// 使用 response-handler helper 处理响应(如果可用)
if (helpers.processBackgroundResponse) {
// 使用提取的生产函数
const processed = helpers.processBackgroundResponse(response);
pending.resolve(processed);
this.pendingRequests.delete(id);
return;
}
// Fallback: 内联实现(如果 helper 未加载)
console.warn('[Index] processBackgroundResponse helper 未加载,使用 fallback');
// 确保 response 对象存在
if (!response) {
console.error('[Index] 收到空响应');
pending.resolve({
status: 0,
statusText: 'No Response',
headers: {},
data: {},
body: ''
});
this.pendingRequests.delete(id);
return;
}
// 处理响应体,为 YApi 提供正确的数据格式
const headers = response.headers || {};
const contentType = headers['content-type'] || '';
const looksLikeJsonString = (val) => {
if (typeof val !== 'string') return false;
const trimmed = val.trim();
return trimmed.startsWith('{') || trimmed.startsWith('[');
};
const hasBodyProp = Object.prototype.hasOwnProperty.call(response, 'body');
const hasBodyParsedProp = Object.prototype.hasOwnProperty.call(response, 'bodyParsed');
const hasDataProp = Object.prototype.hasOwnProperty.call(response, 'data');
let parsedData;
// 优先使用 background.js 提供的解析结果,必要时重新解析字符串
if (hasBodyParsedProp) {
parsedData = response.bodyParsed;
debugLog('[Index] 使用 bodyParsed 作为解析结果');
} else if (hasDataProp && response.data !== undefined) {
parsedData = response.data;
debugLog('[Index] 使用 response.data 作为解析结果');
} else if (
(contentType.includes('application/json') || looksLikeJsonString(response.body)) &&
hasBodyProp &&
response.body != null
) {
if (typeof response.body === 'object' && response.body !== null) {
parsedData = response.body;
debugLog('[Index] response.body 已是对象,直接使用:', {
type: typeof response.body,
isArray: Array.isArray(response.body),
value: helpers.safeLogResponse(response.body)
});
} else if (typeof response.body === 'string') {
try {
parsedData = JSON.parse(response.body);
debugLog('[Index] 为 YApi 解析 JSON 成功:', {
originalType: typeof response.body,
parsedType: typeof parsedData,
isObject: parsedData && typeof parsedData === 'object',
value: parsedData
});
} catch (e) {
console.warn('[Index] JSON 解析失败,使用原始响应:', e.message);
parsedData = {
error: 'JSON解析失败',
raw: response.body
};
}
} else {
parsedData = response.body;
debugLog('[Index] response.body 是标量值,直接使用:', {
type: typeof response.body,
value: helpers.safeLogResponse(response.body)
});
}
} else if (hasBodyProp && (response.body === undefined || response.body === null)) {
parsedData = {};
} else if (hasBodyProp) {
parsedData = response.body;
} else {
parsedData = {};
}
// 确保 body 始终是字符串格式(用于向后兼容)
const bodySource = hasBodyProp
? response.body
: hasBodyParsedProp
? response.bodyParsed
: '';
const bodyString = helpers.bodyToString(bodySource);
const processedResponse = {
status: response.status || 0,
statusText: response.statusText || 'OK', // 确保有默认值
headers,
data: parsedData === undefined ? {} : parsedData, // 只有 undefined 才用 {},保留 null/0/false/"" 等所有合法值
body: bodyString // 保留原始字符串格式
};
if (hasBodyParsedProp) {
processedResponse.bodyParsed = response.bodyParsed;
}
pending.resolve(processedResponse);
this.pendingRequests.delete(id);
}
},
// 处理错误
handleError(event) {
const { id, error } = event.detail;
const pending = this.pendingRequests.get(id);
if (pending) {
if (typeof pending.cleanup === 'function') {
pending.cleanup();
}
pending.reject(new Error(error));
this.pendingRequests.delete(id);
}
}
};
// 监听响应事件
document.addEventListener('y-request-response', (event) => {
CrossRequestAPI.handleResponse(event);
});
document.addEventListener('y-request-error', (event) => {
CrossRequestAPI.handleError(event);
});
// YApi 兼容的 crossRequest 方法
function createCrossRequestMethod() {
return function (options) {
debugLog('[Index] YApi crossRequest 被调用:', options?.url);
// 处理 YApi 参数格式
if (typeof options === 'string') {
options = { url: options };
}
// 准备请求数据
const requestData = {
url: resolveYapiUrlTemplate(options.url),
method: options.method || options.type || 'GET',
headers: options.headers || {},
data: options.data || options.body,
timeout: options.timeout || 30000
};
requestData.headers = applyFixedHeaders(requestData.headers);
// 注意:fetch 不允许设置 User-Agent/Cookie/Host/Origin 等受限请求头,
// 在扩展中强行注入通常不会生效,且可能导致兼容性问题,因此不默认注入。
// 只为非 GET/HEAD 请求添加 Content-Type(有数据时)
if (requestData.data && requestData.method !== 'GET' && requestData.method !== 'HEAD') {
const hasContentType =
!!requestData.headers['Content-Type'] || !!requestData.headers['content-type'];
const isFormLike =
(typeof FormData !== 'undefined' && requestData.data instanceof FormData) ||
(typeof Blob !== 'undefined' && requestData.data instanceof Blob) ||
(typeof File !== 'undefined' && requestData.data instanceof File) ||
(typeof URLSearchParams !== 'undefined' && requestData.data instanceof URLSearchParams) ||
(typeof ArrayBuffer !== 'undefined' && requestData.data instanceof ArrayBuffer) ||
(typeof DataView !== 'undefined' && requestData.data instanceof DataView);
// 让浏览器为 FormData/Blob 设置 multipart 边界,避免破坏文件上传
if (!hasContentType && !isFormLike) {
if (typeof requestData.data === 'object') {
requestData.headers['Content-Type'] = 'application/json';
} else {
requestData.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
}
}
// 添加常见的请求头
const hasAccept = !!requestData.headers['Accept'] || !!requestData.headers['accept'];
if (!hasAccept) {
requestData.headers['Accept'] = 'application/json, text/plain, */*';
}
// 注意:不要从页面读取 document.cookie 并拼到请求头里(Cookie 受限且域不匹配)。
debugLog('[Index] 捕获的请求数据:', requestData);
// 只在非静默模式下显示 cURL 命令
if (!isSilentMode) {
showCurlCommand(requestData);
}
// 发送请求
const promise = CrossRequestAPI.request(requestData);
// YApi 期望的回调格式
promise
.then((response) => {
debugLog('[Index] YApi 请求成功,状态:', response.status);
// 检查是否是错误响应
if (response.isError) {
// 这是一个网络错误或其他错误
const errorMsg = response.statusText || '请求失败';
if (!isSilentMode) {
createErrorDisplay(errorMsg);
}
if (options.error) {
// 构建错误响应体
let errorBody;
// 检查 body 是否已经是对象
if (typeof response.body === 'object' && response.body !== null) {
errorBody = response.body;
} else if (typeof response.body === 'string' && response.body !== '') {
// 非空字符串才尝试解析(空字符串不是合法的 JSON)
try {
errorBody = JSON.parse(response.body);
} catch (e) {
errorBody = {
data: {
success: false,
error: errorMsg,
message: errorMsg,
code: 'NETWORK_ERROR'
}
};
}
} else {
errorBody = {
data: {
success: false,
error: errorMsg,
message: errorMsg,
code: 'NETWORK_ERROR'
}
};
}
const errorHeader = response.headers || { 'content-type': 'application/json' };
const errorData = {
res: {
body:
response.body != null
? helpers.bodyToString(response.body)
: JSON.stringify(errorBody),
header: errorHeader,
status: response.status || 0, // 保留原始状态码,如果没有则用 0
statusText: response.statusText || 'Network Error',
success: false // res 里面也需要 success
},
status: response.status || 0, // 保留原始状态码,如果没有则用 0
statusText: response.statusText || 'Network Error',
success: false // 顶层的 success 字段
};
debugLog('[Index] 处理 isError 响应,调用 error 回调');
options.error(errorBody, errorHeader, errorData);
return;
}
// 如果没有错误回调,继续执行 success 回调,让 YApi 处理错误
debugLog('[Index] 没有 error 回调,将错误传递给 success 回调');
}
// 检查HTTP状态码
if (response.status && response.status >= 400) {
let errorMsg = `HTTP ${response.status}`;
switch (response.status) {
case 400:
errorMsg = '请求参数错误 (400)';
break;
case 401:
errorMsg = '未授权,请检查认证信息 (401)';
break;
case 403:
errorMsg = '访问被拒绝 (403)';
break;
case 404:
errorMsg = '请求的资源不存在 (404)';
break;
case 500:
errorMsg = '服务器内部错误 (500)';
break;
case 502:
errorMsg = '网关错误 (502)';
break;
case 503:
errorMsg = '服务暂时不可用 (503)';
break;
}
// 显示错误提示(仅非静默模式)
if (!isSilentMode) {
createErrorDisplay(errorMsg);
}
}
if (options.success) {
// 使用 response-handler helper 构建 YApi 回调参数(如果可用)
let yapiRes, yapiHeader, yapiData;
if (helpers.buildYapiCallbackParams) {
// 使用提取的生产函数
const params = helpers.buildYapiCallbackParams(response);
yapiRes = params.yapiRes;
yapiHeader = params.yapiHeader;
yapiData = params.yapiData;
} else {
// Fallback: 内联实现(如果 helper 未加载)
console.warn('[Index] buildYapiCallbackParams helper 未加载,使用 fallback');
// 根据 YApi postmanLib.js 源码,构建期望的数据结构
// YApi 期望第一个参数是响应内容(字符串或对象)
// 优先使用已经解析好的 response.data,如果不存在再使用 response.body/bodyParsed
const headers = response.headers || {};
const contentType = headers['content-type'] || '';
const looksLikeJsonString = (val) => {
if (typeof val !== 'string') return false;
const trimmed = val.trim();
return trimmed.startsWith('{') || trimmed.startsWith('[');
};
const hasBodyProp = Object.prototype.hasOwnProperty.call(response, 'body');
const hasBodyParsedProp = Object.prototype.hasOwnProperty.call(
response,
'bodyParsed'
);
const hasDataProp = Object.prototype.hasOwnProperty.call(response, 'data');
if (contentType.includes('application/json') || looksLikeJsonString(response.body)) {
if (hasDataProp && response.data !== undefined) {
yapiRes = response.data;
debugLog('[Index] 使用 response.data 构建 yapiRes');
} else if (hasBodyParsedProp) {
yapiRes = response.bodyParsed;
debugLog('[Index] 使用 bodyParsed 构建 yapiRes');
} else if (hasBodyProp && response.body != null) {
if (typeof response.body === 'object' && response.body !== null) {
yapiRes = response.body;
debugLog('[Index] body 已是对象,直接使用');
} else if (typeof response.body === 'string') {
try {
yapiRes = JSON.parse(response.body);
debugLog('[Index] 从 body 重新解析 JSON 成功');
} catch (e) {
console.warn('[Index] JSON 解析失败,使用原始响应:', e.message);
yapiRes = response.body;
}
} else {
yapiRes = response.body;
}
}
if (yapiRes === undefined) {
yapiRes = {};
}
} else {
if (hasBodyProp) {
yapiRes = response.body != null ? response.body : '';
} else if (hasBodyParsedProp) {
yapiRes = response.bodyParsed != null ? response.bodyParsed : '';
} else {
yapiRes = '';
}
}
yapiHeader = headers; // 响应头
const bodySource = hasBodyProp
? response.body
: hasBodyParsedProp
? response.bodyParsed
: '';
const yapiBodyString = helpers.bodyToString(bodySource); // 确保 body 为字符串格式
yapiData = {
res: {
body: yapiBodyString, // 原始响应体字符串
header: headers, // 响应头
status: response.status || 0, // 状态码
statusText: response.statusText || 'OK',
success: true // 成功响应也需要 success
},
// 额外的顶层属性
status: response.status || 0,
statusText: response.statusText || 'OK',
success: true // 顶层的 success 字段
};
}
debugLog('[Index] 准备调用 YApi success 回调');
try {
// YApi 期望的回调参数:success(res, header, data)
options.success(yapiRes, yapiHeader, yapiData);
} catch (callbackError) {
console.error('[Index] YApi success 回调执行出错:', callbackError);
// 尝试简化的格式
try {
debugLog('[Index] 尝试简化格式...');
options.success(response.data, response.headers, response);
} catch (secondError) {
console.error('[Index] 简化格式也失败:', secondError);
}
}
}
})
.catch((error) => {
// 处理 promise rejection
debugLog('[Index] Promise rejected:', error.message);
// 显示错误提示(仅非静默模式)
const errorMsg = error.message || '请求失败';
if (!isSilentMode) {
createErrorDisplay(errorMsg);
}
if (options.error) {
// 与成功响应使用相同的参数结构
// 网络错误时没有响应体
const errorBody = undefined;
const errorHeader = {
'content-type': 'application/json'
};
// 使用 503 表示服务不可用
const errorData = {
res: {
body: errorBody, // 空字符串,因为网络错误没有响应体
header: errorHeader,
status: 503, // 503 Service Unavailable
statusText: 'Service Unavailable',
success: false // res 里面也需要 success
},
status: 503, // 503 Service Unavailable
statusText: 'Service Unavailable',
success: false // 顶层的 success 字段
};
debugLog('[Index] 调用 error 回调');
// 使用与 success 相同的三个参数
options.error(errorBody, errorHeader, errorData);
} else if (options.success) {
// 如果没有错误回调,调用 success 回调但传递错误信息
// YApi 可能会检查第一个参数来判断是否有错误
// 网络错误时没有响应体
const errorBody = '';
const errorHeader = {
'content-type': 'application/json'
};
// 使用 503 表示服务不可用
const errorData = {
res: {
body: errorBody, // 空字符串,因为网络错误没有响应体
header: errorHeader,
status: 503, // 503 Service Unavailable
statusText: 'Service Unavailable',
success: false // res 里面也需要 success
},
status: 503, // 503 Service Unavailable
statusText: 'Service Unavailable',
success: false // 顶层的 success 字段
};
debugLog('[Index] 使用 success 回调传递错误');
options.success(errorBody, errorHeader, errorData);
}
});
return promise;
};
}
// 复制到剪贴板的现代函数
async function copyToClipboard(text) {
try {
// 优先使用现代 Clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);