75 |
setUrl(e.target.value)}
81 | onPressEnter={useCallback(() => handleAddRule(url, setActiveKey, rule, setRule), [url])}
82 | placeholder="URL must contain 'http://' or 'https://' "
83 | />
84 |
85 |
86 | }
90 | onClick={() => handleAddRule(url, setActiveKey, rule, setRule)}
91 | disabled={!status}
92 | >
93 | Rule
94 |
95 | }
100 | onClick={() => handleSave(rule)}
101 | disabled={!status}
102 | />
103 |
104 |
setStatus(e)}
109 | />
110 |
111 |
112 | {status && (
113 |
}
117 | className="site-collapse-custom-collapse list-group"
118 | onChange={(key: string | string[]) => setActiveKey(key)}
119 | >
120 | {
121 | rule.map((ruleBox: ruleBox) => (
122 |
125 |
126 |
setUrl(ruleBox.url)}>
127 | {ruleBox.url}
128 |
129 |
{
134 | e.stopPropagation()
135 | handleRuleOnoff(ruleBox.indexes, setRule, rule, checked)
136 | }}
137 | />
138 |
139 | >
140 | }
141 | key={ruleBox.indexes}
142 | className="site-collapse-custom-panel"
143 | >
144 | {/* toolbar */}
145 |
148 |
153 | delete the all rule under this URL ?
154 |
162 | >
163 | }
164 | trigger="hover"
165 | >
166 |
167 |
168 |
handleAddRuleItem(ruleBox.indexes, setRule, rule)} />
169 |
170 | {/* ruleItem */}
171 | {ruleBox.rule.map((item, index) => (
172 |
173 |
174 | handleDeleteRuleItem(ruleBox.indexes, setRule, rule, index)} />
175 | {
182 | e.stopPropagation()
183 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'onoff', checked)
184 | }}
185 | />
186 |
187 |
188 | {
193 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'remarks', e.target.value)
194 | }}
195 | />
196 |
207 | {
220 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'change', e.target.value)
221 | }}
222 | />
223 |
224 |
225 |
226 | {item.type === 'api' && (
227 |
242 | )}
243 | {
248 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'value', e.target.value)
249 | }}
250 | />
251 |
252 |
253 | {!!ruleToJson(item.value) && (
254 |
{
262 | handleJSONEditorChange(ruleBox.indexes, setRule, rule, index, e)
263 | }}
264 | onAdd={e => {
265 | handleJSONEditorChange(ruleBox.indexes, setRule, rule, index, e)
266 | }}
267 | onDelete={e => {
268 | handleJSONEditorChange(ruleBox.indexes, setRule, rule, index, e)
269 | }}
270 | displayDataTypes={false}
271 | />
272 | )}
273 |
274 |
275 | ))}
276 |
277 | ))
278 | }
279 |
280 | )}
281 |
282 |
setEasterEgg(easterEgg + 1)}
285 | >
286 | {easterEgg ? ': >' : ':)'}
287 |
288 | {easterEgg >= 3 && (
289 |
290 | )}
291 |
292 | );
293 | })
294 |
295 | /**
296 | * 标定缓存数据结构
297 | *
298 | * [{
299 | * indexes:URLMD5,
300 | * url:'https://github.com/name-q',
301 | * onoff: *on* | off
302 | * rule:[
303 | * {
304 | * type: api#匹配单个精准接口 | regular#正则匹配多个接口|path匹配多个包含路径的接口,
305 | * method?: POST | GET | PUT | DELETE | HEAD | OPTIONS | *ALL* | api单个接口规则时出现的选项
306 | * change: /name-q/xxx | *name-q | /name-q
307 | * value: 改变后的值 或路径
308 | * onoff: *on* | off
309 | * },
310 | * ]
311 | * },...]
312 | *
313 | * **/
314 |
315 | type ruleItemKey = 'type' | 'method' | 'value' | 'change' | 'onoff' | 'remarks'
316 | interface ruleItem {
317 | type: 'api' | 'regular' | 'path';
318 | method?: 'POST' | 'GET' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'ALL';
319 | value: any;
320 | change: string;
321 | onoff: boolean;
322 | remarks: '';
323 | }
324 | interface ruleBox {
325 | indexes: string;
326 | url: string;
327 | rule: ruleItem[];
328 | onoff: boolean;
329 | }
330 |
331 | /**
332 | * 添加规则
333 | * url - 输入的网址
334 | * setActiveKey - 默认开启的块(仅新增规则使用 不做缓存)
335 | * rule - 现有的规则数据
336 | * setRule - 设置规则的方法
337 | * */
338 | const handleAddRule = (url: string, setActiveKey: Function, rule: ruleBox[], setRule: Function) => {
339 | try {
340 | if (!url) return message.info('URL not entered')
341 | // 是否为网址
342 | const reg = /(http|https):\/\/([\w.]+\/?)\S*/;
343 | if (!reg.test(url)) return message.warning("URL must contain 'http://' or 'https://'");
344 | // 解析出标准网址
345 | const standardUrl = (urlx: string): any => {
346 | urlx = urlx.endsWith('/') ? urlx.substring(0, urlx.length - 1) : urlx
347 | return urlx.endsWith('/') ? standardUrl(urlx) : urlx
348 | }
349 | // 获取标准网址的MD5
350 | let urlMD5: string = getMD5(standardUrl(url.split('?')[0]))
351 | let existence = false
352 | if (rule.length) rule.map(i => { if (i.indexes === urlMD5) { existence = true } })
353 | // 如果此规则存在则置顶并打开
354 | if (existence) {
355 | let item: ruleBox = {
356 | indexes: urlMD5,
357 | url,
358 | rule: [{
359 | type: 'api',
360 | value: '',
361 | change: '',
362 | onoff: true,
363 | method: 'ALL',
364 | remarks: ''
365 | }],
366 | onoff: true
367 | }
368 | rule = rule.filter(i => { if (i.indexes === urlMD5) { item = i; return false } else { return true } })
369 | item.url = url
370 | rule.unshift(item)
371 | } else {
372 | // 如果此规则不存在则新增
373 | rule.unshift({
374 | indexes: urlMD5,
375 | url,
376 | rule: [{
377 | type: 'api',
378 | value: '',
379 | change: '',
380 | onoff: true,
381 | method: 'ALL',
382 | remarks: ''
383 | }],
384 | onoff: true
385 | })
386 | }
387 | // 载入缓存 并默认打开
388 | setRule(rule)
389 | setActiveKey([urlMD5])
390 | } catch (error: any) {
391 | message.error(error)
392 | }
393 | }
394 |
395 | const handleSave = (rule: ruleBox[]) => {
396 | window.electron.ipcRenderer.sendMessage("fs", ['setCache', rule])
397 | window.electron.ipcRenderer.once('fs', (arg) => {
398 | // console.log(arg)
399 | });
400 | }
401 |
402 | const getMD5 = (d: string) => {
403 | if (!d) throw 'getMd5() -- undefined';
404 | let d_ok = d.toString()
405 | return crypto.MD5(d_ok).toString()
406 | }
407 |
408 | // {0:{a:1},1:{b:2}} Format Data --> [{a:1},{b:2}]
409 | const formatData = (object: any) => {
410 | let arr: any = []
411 | for (const key in object) {
412 | if (Object.prototype.hasOwnProperty.call(object, key)) {
413 | let item: ruleBox = object[key]
414 | // @ts-ignore
415 | item.rule = item.rule.map(i => JSON.parse(i))
416 | arr.push(item)
417 | }
418 | }
419 | return arr
420 | }
421 |
422 | const handleAddRuleItem = (indexes: string, setRule: Function, rule: ruleBox[]) => {
423 | rule.map(i => {
424 | if (i.indexes === indexes) {
425 | i.rule.unshift({
426 | type: 'api',
427 | value: '',
428 | change: '',
429 | onoff: true,
430 | method: 'ALL',
431 | remarks: ''
432 | })
433 | }
434 | })
435 | setRule([...rule])
436 | }
437 |
438 | const handleDeleteRuleItem = (indexes: string, setRule: Function, rule: ruleBox[], index: number) => {
439 | rule.map(i => {
440 | if (i.indexes === indexes) {
441 | i.rule = i.rule.filter((item, itemIndex) => itemIndex !== index)
442 | }
443 | })
444 | rule = rule.filter(i => i.rule.length)
445 | setRule([...rule])
446 | }
447 |
448 | const handleDeleteRule = (indexes: string, setRule: Function, rule: ruleBox[], setActiveKey: Function) => {
449 | rule = rule.filter(i => i.indexes !== indexes)
450 | setRule([...rule])
451 | setActiveKey(rule.length ? [rule[0].indexes] : [])
452 | }
453 |
454 | const handleRuleOnoff = (indexes: string, setRule: Function, rule: ruleBox[], checked: boolean) => {
455 | rule.map(i => {
456 | if (i.indexes === indexes) {
457 | i.onoff = checked
458 | }
459 | })
460 | setRule([...rule])
461 | }
462 |
463 | const handleRuleItemChange = (indexes: string, setRule: Function, rule: ruleBox[], index: number, key: ruleItemKey, value: string | boolean) => {
464 | rule.map(i => {
465 | if (i.indexes === indexes) {
466 | i.rule.map((item: ruleItem, itemIndex) => {
467 | if (itemIndex === index) {
468 | // @ts-ignore
469 | item[key] = value
470 | }
471 | })
472 | }
473 | })
474 | setRule([...rule])
475 | }
476 |
477 | const ruleToJson = (value: any) => {
478 | try {
479 | value = JSON.parse(value)
480 | if (value && Object.prototype.toString.call(value) === '[object Object]') {
481 | return value
482 | } else {
483 | return false
484 | }
485 | } catch {
486 | return false
487 | }
488 | }
489 |
490 | const handleJSONEditorChange = (indexes: string, setRule: Function, rule: ruleBox[], index: number, { updated_src }: any) => {
491 | let value = updated_src ? JSON.stringify(updated_src) : ''
492 | rule.map(i => {
493 | if (i.indexes === indexes) {
494 | i.rule.map((item: ruleItem, itemIndex) => {
495 | if (itemIndex === index) {
496 | // @ts-ignore
497 | item['value'] = value
498 | }
499 | })
500 | }
501 | })
502 | setRule([...rule])
503 | }
504 |
505 | // 打开内部浏览器并应用规则
506 | const handleIntermediator = (rule: ruleBox[], url: string) => {
507 | if (!url) return message.info('URL not entered')
508 | // 是否为网址
509 | const reg = /(http|https):\/\/([\w.]+\/?)\S*/;
510 | if (!reg.test(url)) return message.warning("URL must contain 'http://' or 'https://'");
511 | // 解析出标准网址
512 | const standardUrl = (urlx: string): any => {
513 | urlx = urlx.endsWith('/') ? urlx.substring(0, urlx.length - 1) : urlx
514 | return urlx.endsWith('/') ? standardUrl(urlx) : urlx
515 | }
516 | // 获取标准网址的MD5
517 | let urlMD5: string = getMD5(standardUrl(url.split('?')[0]))
518 | // 过滤出属于这个网址的规则
519 | rule = rule.filter(i => i.indexes === urlMD5)
520 |
521 | window.electron.ipcRenderer.sendMessage("intermediator", [rule, url])
522 | window.electron.ipcRenderer.once('intermediator', (arg: any) => {
523 | message.success(arg)
524 | });
525 | }
526 |
527 | export default function App() {
528 | return (
529 |