@rogatio/cli 5.8.3 → 5.9.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/README.md +2 -2
- package/dist/editor/index.js +659 -693
- package/dist/node/index.js +229 -264
- package/package.json +1 -1
package/dist/editor/index.js
CHANGED
|
@@ -27,6 +27,7 @@ var LIMITS = Object.freeze({
|
|
|
27
27
|
maxMockDelayMs: 3e4,
|
|
28
28
|
maxMockFilePathLength: 2048,
|
|
29
29
|
maxResponseBodyReplacements: 64,
|
|
30
|
+
maxResponseBodyBytes: 4 * 1024 * 1024,
|
|
30
31
|
maxResponseBodyPatternLength: 2048,
|
|
31
32
|
maxResponseBodyReplacementLength: 4096,
|
|
32
33
|
maxRequestBodyBytes: 4 * 1024 * 1024,
|
|
@@ -213,6 +214,21 @@ var HTTP_METHODS = Object.freeze([
|
|
|
213
214
|
"CONNECT",
|
|
214
215
|
"TRACE"
|
|
215
216
|
]);
|
|
217
|
+
function hasLoneSurrogate(value) {
|
|
218
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
219
|
+
const code = value.charCodeAt(i);
|
|
220
|
+
if (code >= 55296 && code <= 56319) {
|
|
221
|
+
if (i + 1 >= value.length) return true;
|
|
222
|
+
const next = value.charCodeAt(i + 1);
|
|
223
|
+
if (next < 56320 || next > 57343) return true;
|
|
224
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
225
|
+
if (i === 0) return true;
|
|
226
|
+
const prev = value.charCodeAt(i - 1);
|
|
227
|
+
if (prev < 55296 || prev > 56319) return true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
216
232
|
|
|
217
233
|
// packages/editor/src/ai-assist-panel.ts
|
|
218
234
|
function createAIAssistPanel(_container, _editor, onApply, onClose) {
|
|
@@ -561,307 +577,261 @@ function createAIAssistPanel(_container, _editor, onApply, onClose) {
|
|
|
561
577
|
};
|
|
562
578
|
}
|
|
563
579
|
|
|
564
|
-
// packages/editor/src/rule-types/
|
|
565
|
-
function
|
|
566
|
-
|
|
580
|
+
// packages/editor/src/rule-types/dom.ts
|
|
581
|
+
function normalizeOption(option) {
|
|
582
|
+
if (typeof option === "string") {
|
|
583
|
+
return { value: option, label: option };
|
|
584
|
+
}
|
|
585
|
+
return option;
|
|
567
586
|
}
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
587
|
+
function createSelect(document2, options, value, onChange) {
|
|
588
|
+
const select = document2.createElement("select");
|
|
589
|
+
for (const option of options) {
|
|
590
|
+
const { value: optionValue, label } = normalizeOption(option);
|
|
591
|
+
const opt = document2.createElement("option");
|
|
592
|
+
opt.value = optionValue;
|
|
593
|
+
opt.textContent = label;
|
|
594
|
+
if (optionValue === value) opt.selected = true;
|
|
595
|
+
select.append(opt);
|
|
573
596
|
}
|
|
574
|
-
|
|
597
|
+
select.addEventListener("change", () => onChange(select.value));
|
|
598
|
+
return select;
|
|
575
599
|
}
|
|
576
|
-
function
|
|
577
|
-
|
|
578
|
-
|
|
600
|
+
function createTextInput(document2, value, onChange, maxLength) {
|
|
601
|
+
const input = document2.createElement("input");
|
|
602
|
+
input.type = "text";
|
|
603
|
+
input.value = value;
|
|
604
|
+
if (maxLength !== void 0) input.maxLength = maxLength;
|
|
605
|
+
input.addEventListener("input", () => onChange(input.value));
|
|
606
|
+
return input;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// packages/editor/src/rule-types/header.ts
|
|
610
|
+
var FORBIDDEN_REQUEST_HEADERS2 = Object.freeze([
|
|
611
|
+
"accept-charset",
|
|
612
|
+
"accept-encoding",
|
|
613
|
+
"access-control-request-headers",
|
|
614
|
+
"access-control-request-method",
|
|
615
|
+
"connection",
|
|
616
|
+
"content-length",
|
|
617
|
+
"cookie",
|
|
618
|
+
"cookie2",
|
|
619
|
+
"date",
|
|
620
|
+
"dnt",
|
|
621
|
+
"expect",
|
|
622
|
+
"host",
|
|
623
|
+
"keep-alive",
|
|
624
|
+
"origin",
|
|
625
|
+
"proxy-authenticate",
|
|
626
|
+
"proxy-authorization",
|
|
627
|
+
"te",
|
|
628
|
+
"trailer",
|
|
629
|
+
"transfer-encoding",
|
|
630
|
+
"upgrade",
|
|
631
|
+
"via"
|
|
632
|
+
]);
|
|
633
|
+
var FORBIDDEN_RESPONSE_HEADERS2 = Object.freeze([
|
|
634
|
+
"connection",
|
|
635
|
+
"content-encoding",
|
|
636
|
+
"content-length",
|
|
637
|
+
"date",
|
|
638
|
+
"keep-alive",
|
|
639
|
+
"proxy-authenticate",
|
|
640
|
+
"proxy-authorization",
|
|
641
|
+
"set-cookie",
|
|
642
|
+
"set-cookie2",
|
|
643
|
+
"te",
|
|
644
|
+
"trailer",
|
|
645
|
+
"transfer-encoding",
|
|
646
|
+
"upgrade",
|
|
647
|
+
"via"
|
|
648
|
+
]);
|
|
649
|
+
var FORBIDDEN_REQUEST_PREFIXES2 = Object.freeze(["proxy-", "sec-"]);
|
|
650
|
+
function isForbiddenHeader(name, direction) {
|
|
651
|
+
const normalized = name.toLowerCase();
|
|
652
|
+
const forbidden = direction === "request" ? FORBIDDEN_REQUEST_HEADERS2 : FORBIDDEN_RESPONSE_HEADERS2;
|
|
653
|
+
return forbidden.includes(normalized) || direction === "request" && FORBIDDEN_REQUEST_PREFIXES2.some(
|
|
654
|
+
(prefix) => normalized.startsWith(prefix)
|
|
579
655
|
);
|
|
580
656
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
657
|
+
var HEADER_DIRECTIONS = [
|
|
658
|
+
"request",
|
|
659
|
+
"response"
|
|
660
|
+
];
|
|
661
|
+
var HEADER_OPERATIONS = [
|
|
662
|
+
"set",
|
|
663
|
+
"append",
|
|
664
|
+
"remove"
|
|
665
|
+
];
|
|
666
|
+
var HEADER_EDITOR_OPERATIONS = Object.freeze(["set", "remove"]);
|
|
667
|
+
function operationOptions(current) {
|
|
668
|
+
if (current === "append") {
|
|
669
|
+
return ["set", "append", "remove"];
|
|
585
670
|
}
|
|
586
|
-
return
|
|
671
|
+
return HEADER_EDITOR_OPERATIONS;
|
|
587
672
|
}
|
|
588
|
-
function
|
|
673
|
+
function createHeaderRuleType() {
|
|
589
674
|
return {
|
|
590
|
-
id: "
|
|
591
|
-
label: "
|
|
592
|
-
actionField: "mock",
|
|
675
|
+
id: "header",
|
|
676
|
+
label: "Header",
|
|
593
677
|
matches(rule) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
return
|
|
678
|
+
return rule.type === "header";
|
|
679
|
+
},
|
|
680
|
+
defaultFields() {
|
|
681
|
+
return {
|
|
682
|
+
headerDirection: "request",
|
|
683
|
+
headerOperation: "set",
|
|
684
|
+
headerName: "",
|
|
685
|
+
headerValue: ""
|
|
686
|
+
};
|
|
687
|
+
},
|
|
688
|
+
mount(context) {
|
|
689
|
+
const document2 = context.document;
|
|
690
|
+
const fieldset = document2.createElement("fieldset");
|
|
691
|
+
const legend = document2.createElement("legend");
|
|
692
|
+
legend.textContent = "Header rule";
|
|
693
|
+
fieldset.append(legend);
|
|
694
|
+
const directionLabel = document2.createElement("label");
|
|
695
|
+
directionLabel.textContent = "Direction";
|
|
696
|
+
const directionValue = context.getField("headerDirection") ?? "request";
|
|
697
|
+
const directionSelect = createSelect(
|
|
698
|
+
document2,
|
|
699
|
+
HEADER_DIRECTIONS,
|
|
700
|
+
directionValue,
|
|
701
|
+
(value) => {
|
|
702
|
+
context.setField("headerDirection", value);
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
context.registerControl("/headerDirection", directionSelect);
|
|
706
|
+
directionLabel.append(directionSelect);
|
|
707
|
+
fieldset.append(directionLabel);
|
|
708
|
+
const operationLabel = document2.createElement("label");
|
|
709
|
+
operationLabel.textContent = "Operation";
|
|
710
|
+
let operationValue = context.getField("headerOperation") ?? "set";
|
|
711
|
+
const valueLabel = document2.createElement("label");
|
|
712
|
+
valueLabel.textContent = "Header value";
|
|
713
|
+
const syncValueVisibility = () => {
|
|
714
|
+
const showValue = operationValue === "set" || operationValue === "append";
|
|
715
|
+
valueLabel.hidden = !showValue;
|
|
716
|
+
};
|
|
717
|
+
const operationSelect = createSelect(
|
|
718
|
+
document2,
|
|
719
|
+
operationOptions(operationValue),
|
|
720
|
+
operationValue,
|
|
721
|
+
(value) => {
|
|
722
|
+
operationValue = value;
|
|
723
|
+
context.setField("headerOperation", value);
|
|
724
|
+
if (value === "remove") {
|
|
725
|
+
context.deleteField("headerValue");
|
|
726
|
+
}
|
|
727
|
+
syncValueVisibility();
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
context.registerControl("/headerOperation", operationSelect);
|
|
731
|
+
operationLabel.append(operationSelect);
|
|
732
|
+
fieldset.append(operationLabel);
|
|
733
|
+
const nameLabel = document2.createElement("label");
|
|
734
|
+
nameLabel.textContent = "Header name";
|
|
735
|
+
const nameValue = context.getField("headerName") ?? "";
|
|
736
|
+
const nameInput = createTextInput(
|
|
737
|
+
document2,
|
|
738
|
+
nameValue,
|
|
739
|
+
(value) => {
|
|
740
|
+
context.setField("headerName", value);
|
|
741
|
+
},
|
|
742
|
+
256
|
|
743
|
+
);
|
|
744
|
+
context.registerControl("/headerName", nameInput);
|
|
745
|
+
nameLabel.append(nameInput);
|
|
746
|
+
fieldset.append(nameLabel);
|
|
747
|
+
const valueValue = context.getField("headerValue") ?? "";
|
|
748
|
+
const valueInput = createTextInput(
|
|
749
|
+
document2,
|
|
750
|
+
valueValue,
|
|
751
|
+
(value) => {
|
|
752
|
+
context.setField("headerValue", value);
|
|
753
|
+
},
|
|
754
|
+
4096
|
|
755
|
+
);
|
|
756
|
+
context.registerControl("/headerValue", valueInput);
|
|
757
|
+
valueLabel.append(valueInput);
|
|
758
|
+
fieldset.append(valueLabel);
|
|
759
|
+
syncValueVisibility();
|
|
760
|
+
context.container.append(fieldset);
|
|
761
|
+
return {
|
|
762
|
+
destroy() {
|
|
763
|
+
directionSelect.removeEventListener("change", () => {
|
|
764
|
+
});
|
|
765
|
+
operationSelect.removeEventListener("change", () => {
|
|
766
|
+
});
|
|
767
|
+
nameInput.removeEventListener("input", () => {
|
|
768
|
+
});
|
|
769
|
+
valueInput.removeEventListener("input", () => {
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
};
|
|
598
773
|
},
|
|
599
774
|
validate(rule, rulePath) {
|
|
600
|
-
const mock = asMockAction(rule.mock);
|
|
601
|
-
if (mock === void 0) return [];
|
|
602
775
|
const diagnostics = [];
|
|
603
|
-
const
|
|
604
|
-
|
|
776
|
+
const direction = rule.headerDirection;
|
|
777
|
+
const operation = rule.headerOperation;
|
|
778
|
+
const headerName = rule.headerName;
|
|
779
|
+
const headerValue = rule.headerValue;
|
|
780
|
+
if (!direction || !HEADER_DIRECTIONS.includes(direction)) {
|
|
605
781
|
diagnostics.push({
|
|
606
|
-
code: "
|
|
782
|
+
code: "schema.invalid-value",
|
|
607
783
|
severity: "error",
|
|
608
|
-
path: `${rulePath}/
|
|
609
|
-
message:
|
|
784
|
+
path: `${rulePath}/headerDirection`,
|
|
785
|
+
message: 'Direction must be "request" or "response".'
|
|
610
786
|
});
|
|
611
787
|
}
|
|
612
|
-
|
|
613
|
-
const hasFile = typeof mock.file === "string";
|
|
614
|
-
if (hasBody === hasFile) {
|
|
788
|
+
if (!operation || !HEADER_OPERATIONS.includes(operation)) {
|
|
615
789
|
diagnostics.push({
|
|
616
|
-
code: "
|
|
790
|
+
code: "schema.invalid-value",
|
|
617
791
|
severity: "error",
|
|
618
|
-
path: `${rulePath}/
|
|
619
|
-
message:
|
|
792
|
+
path: `${rulePath}/headerOperation`,
|
|
793
|
+
message: 'Operation must be "set", "append", or "remove".'
|
|
620
794
|
});
|
|
621
|
-
} else if (hasBody) {
|
|
622
|
-
const body = mock.body;
|
|
623
|
-
if (body.length > LIMITS.maxMockInlineBodyLength) {
|
|
624
|
-
diagnostics.push({
|
|
625
|
-
code: "editor.mock-body-too-long",
|
|
626
|
-
severity: "error",
|
|
627
|
-
path: `${rulePath}/mock/body`,
|
|
628
|
-
message: `Mock inline body must be at most ${LIMITS.maxMockInlineBodyLength} characters.`
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
} else if (hasFile) {
|
|
632
|
-
const file = mock.file;
|
|
633
|
-
if (file.length === 0 || file.length > LIMITS.maxMockFilePathLength) {
|
|
634
|
-
diagnostics.push({
|
|
635
|
-
code: "editor.mock-file-path",
|
|
636
|
-
severity: "error",
|
|
637
|
-
path: `${rulePath}/mock/file`,
|
|
638
|
-
message: `Mock file path must be 1-${LIMITS.maxMockFilePathLength} characters.`
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
795
|
}
|
|
642
|
-
if (
|
|
796
|
+
if (typeof headerName !== "string" || headerName.length === 0 || headerName.length > 256) {
|
|
643
797
|
diagnostics.push({
|
|
644
|
-
code: "
|
|
798
|
+
code: "schema.out-of-range",
|
|
645
799
|
severity: "error",
|
|
646
|
-
path: `${rulePath}/
|
|
647
|
-
message:
|
|
800
|
+
path: `${rulePath}/headerName`,
|
|
801
|
+
message: "Header name must be 1-256 characters."
|
|
648
802
|
});
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
path: `${rulePath}/mock/headers`,
|
|
656
|
-
message: `A mock rule may define at most ${LIMITS.maxMockHeadersPerRule} headers.`
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
mock.headers.forEach((header, index) => {
|
|
660
|
-
const namePath = `${rulePath}/mock/headers/${index}/name`;
|
|
661
|
-
const valuePath = `${rulePath}/mock/headers/${index}/value`;
|
|
662
|
-
if (typeof header?.name !== "string" || header.name.length === 0 || header.name.length > LIMITS.maxMockHeaderNameLength) {
|
|
663
|
-
diagnostics.push({
|
|
664
|
-
code: "editor.mock-header-name",
|
|
665
|
-
severity: "error",
|
|
666
|
-
path: namePath,
|
|
667
|
-
message: `Mock header name must be 1-${LIMITS.maxMockHeaderNameLength} characters.`
|
|
668
|
-
});
|
|
669
|
-
} else if (hasControlOrColon(header.name)) {
|
|
670
|
-
diagnostics.push({
|
|
671
|
-
code: "editor.mock-header-name",
|
|
672
|
-
severity: "error",
|
|
673
|
-
path: namePath,
|
|
674
|
-
message: "Mock header name must not contain control characters or a colon."
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
if (typeof header?.value !== "string" || header.value.length > LIMITS.maxMockHeaderValueLength) {
|
|
678
|
-
diagnostics.push({
|
|
679
|
-
code: "editor.mock-header-value",
|
|
680
|
-
severity: "error",
|
|
681
|
-
path: valuePath,
|
|
682
|
-
message: `Mock header value must be at most ${LIMITS.maxMockHeaderValueLength} characters.`
|
|
683
|
-
});
|
|
684
|
-
}
|
|
803
|
+
} else if (direction && isForbiddenHeader(headerName, direction)) {
|
|
804
|
+
diagnostics.push({
|
|
805
|
+
code: "extension.forbidden-header",
|
|
806
|
+
severity: "error",
|
|
807
|
+
path: `${rulePath}/headerName`,
|
|
808
|
+
message: `Header "${headerName}" is forbidden for ${direction} headers.`
|
|
685
809
|
});
|
|
686
810
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
context.setField("mock", next);
|
|
694
|
-
};
|
|
695
|
-
const render = () => {
|
|
696
|
-
const current = getCurrent();
|
|
697
|
-
container.replaceChildren();
|
|
698
|
-
if (current === void 0) return;
|
|
699
|
-
const statusLabel = document2.createElement("label");
|
|
700
|
-
statusLabel.textContent = "Status";
|
|
701
|
-
const statusInput = document2.createElement("input");
|
|
702
|
-
statusInput.type = "number";
|
|
703
|
-
statusInput.min = String(LIMITS.minMockStatus);
|
|
704
|
-
statusInput.max = String(LIMITS.maxMockStatus);
|
|
705
|
-
statusInput.step = "1";
|
|
706
|
-
statusInput.value = String(current.status);
|
|
707
|
-
statusInput.dataset.editorField = "true";
|
|
708
|
-
statusInput.addEventListener("input", () => {
|
|
709
|
-
const c = getCurrent();
|
|
710
|
-
if (c === void 0) return;
|
|
711
|
-
setAction({ ...c, status: Number(statusInput.value) });
|
|
811
|
+
if ((operation === "set" || operation === "append") && typeof headerValue !== "string") {
|
|
812
|
+
diagnostics.push({
|
|
813
|
+
code: "schema.required",
|
|
814
|
+
severity: "error",
|
|
815
|
+
path: `${rulePath}/headerValue`,
|
|
816
|
+
message: "Header value is required for set and append operations."
|
|
712
817
|
});
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
delayInput.min = "0";
|
|
720
|
-
delayInput.max = String(LIMITS.maxMockDelayMs);
|
|
721
|
-
delayInput.step = "1";
|
|
722
|
-
delayInput.value = current.delayMs === void 0 ? "" : String(current.delayMs);
|
|
723
|
-
delayInput.dataset.editorField = "true";
|
|
724
|
-
delayInput.addEventListener("input", () => {
|
|
725
|
-
const c = getCurrent();
|
|
726
|
-
if (c === void 0) return;
|
|
727
|
-
const raw = delayInput.value;
|
|
728
|
-
const next = raw === "" ? { ...c, delayMs: void 0 } : { ...c, delayMs: Number(raw) };
|
|
729
|
-
setAction(next);
|
|
818
|
+
} else if ((operation === "set" || operation === "append") && typeof headerValue === "string" && headerValue.length > 4096) {
|
|
819
|
+
diagnostics.push({
|
|
820
|
+
code: "schema.out-of-range",
|
|
821
|
+
severity: "error",
|
|
822
|
+
path: `${rulePath}/headerValue`,
|
|
823
|
+
message: "Header value must be at most 4096 characters."
|
|
730
824
|
});
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
bodyOption.value = "body";
|
|
739
|
-
bodyOption.textContent = "Inline body";
|
|
740
|
-
const fileOption = document2.createElement("option");
|
|
741
|
-
fileOption.value = "file";
|
|
742
|
-
fileOption.textContent = "File snapshot";
|
|
743
|
-
sourceSelect.append(bodyOption, fileOption);
|
|
744
|
-
sourceSelect.value = typeof current.file === "string" ? "file" : "body";
|
|
745
|
-
context.registerControl("/mock/body-source", sourceSelect);
|
|
746
|
-
const bodyArea = document2.createElement("textarea");
|
|
747
|
-
bodyArea.value = typeof current.body === "string" ? current.body : "";
|
|
748
|
-
bodyArea.dataset.editorField = "true";
|
|
749
|
-
bodyArea.addEventListener("input", () => {
|
|
750
|
-
const c = getCurrent();
|
|
751
|
-
if (c === void 0) return;
|
|
752
|
-
setAction({ ...c, body: bodyArea.value, file: void 0 });
|
|
825
|
+
}
|
|
826
|
+
if (operation === "remove" && headerValue !== void 0) {
|
|
827
|
+
diagnostics.push({
|
|
828
|
+
code: "schema.unexpected",
|
|
829
|
+
severity: "error",
|
|
830
|
+
path: `${rulePath}/headerValue`,
|
|
831
|
+
message: "Header value must not be provided for remove operation."
|
|
753
832
|
});
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
fileLabel.textContent = "File path";
|
|
757
|
-
const fileInput = document2.createElement("input");
|
|
758
|
-
fileInput.type = "text";
|
|
759
|
-
fileInput.value = typeof current.file === "string" ? current.file : "";
|
|
760
|
-
fileInput.dataset.editorField = "true";
|
|
761
|
-
fileInput.addEventListener("input", () => {
|
|
762
|
-
const c = getCurrent();
|
|
763
|
-
if (c === void 0) return;
|
|
764
|
-
setAction({ ...c, body: void 0, file: fileInput.value });
|
|
765
|
-
});
|
|
766
|
-
context.registerControl("/mock/file", fileInput);
|
|
767
|
-
fileLabel.append(fileInput);
|
|
768
|
-
const syncSource = () => {
|
|
769
|
-
const useFile = sourceSelect.value === "file";
|
|
770
|
-
bodyArea.style.display = useFile ? "none" : "";
|
|
771
|
-
fileLabel.style.display = useFile ? "" : "none";
|
|
772
|
-
};
|
|
773
|
-
sourceSelect.addEventListener("change", syncSource);
|
|
774
|
-
syncSource();
|
|
775
|
-
const headersFieldset = document2.createElement("fieldset");
|
|
776
|
-
const headersLegend = document2.createElement("legend");
|
|
777
|
-
headersLegend.textContent = "Response headers";
|
|
778
|
-
headersFieldset.append(headersLegend);
|
|
779
|
-
const renderHeaders = () => {
|
|
780
|
-
const c = getCurrent();
|
|
781
|
-
if (c === void 0) return;
|
|
782
|
-
headersFieldset.querySelectorAll("[data-mock-header-row]").forEach((row) => {
|
|
783
|
-
row.remove();
|
|
784
|
-
});
|
|
785
|
-
const headers = c.headers ?? [];
|
|
786
|
-
headers.forEach((header, index) => {
|
|
787
|
-
const row = document2.createElement("div");
|
|
788
|
-
row.dataset.mockHeaderRow = String(index);
|
|
789
|
-
const nameLabel = document2.createElement("label");
|
|
790
|
-
nameLabel.textContent = "Name";
|
|
791
|
-
const nameInput = document2.createElement("input");
|
|
792
|
-
nameInput.type = "text";
|
|
793
|
-
nameInput.value = header.name;
|
|
794
|
-
nameInput.dataset.editorField = "true";
|
|
795
|
-
nameInput.addEventListener("input", () => {
|
|
796
|
-
const cc = getCurrent();
|
|
797
|
-
if (cc === void 0) return;
|
|
798
|
-
const next = (cc.headers ?? []).map(
|
|
799
|
-
(h, i) => i === index ? { ...h, name: nameInput.value } : h
|
|
800
|
-
);
|
|
801
|
-
setAction({ ...cc, headers: next });
|
|
802
|
-
});
|
|
803
|
-
context.registerControl(`/mock/headers/${index}/name`, nameInput);
|
|
804
|
-
nameLabel.append(nameInput);
|
|
805
|
-
const valueLabel = document2.createElement("label");
|
|
806
|
-
valueLabel.textContent = "Value";
|
|
807
|
-
const valueInput = document2.createElement("input");
|
|
808
|
-
valueInput.type = "text";
|
|
809
|
-
valueInput.value = header.value;
|
|
810
|
-
valueInput.dataset.editorField = "true";
|
|
811
|
-
valueInput.addEventListener("input", () => {
|
|
812
|
-
const cc = getCurrent();
|
|
813
|
-
if (cc === void 0) return;
|
|
814
|
-
const next = (cc.headers ?? []).map(
|
|
815
|
-
(h, i) => i === index ? { ...h, value: valueInput.value } : h
|
|
816
|
-
);
|
|
817
|
-
setAction({ ...cc, headers: next });
|
|
818
|
-
});
|
|
819
|
-
context.registerControl(`/mock/headers/${index}/value`, valueInput);
|
|
820
|
-
valueLabel.append(valueInput);
|
|
821
|
-
const remove = document2.createElement("button");
|
|
822
|
-
remove.type = "button";
|
|
823
|
-
remove.textContent = "Remove";
|
|
824
|
-
remove.dataset.editorField = "true";
|
|
825
|
-
remove.addEventListener("click", () => {
|
|
826
|
-
const cc = getCurrent();
|
|
827
|
-
if (cc === void 0) return;
|
|
828
|
-
const next = (cc.headers ?? []).filter((_, i) => i !== index);
|
|
829
|
-
setAction({ ...cc, headers: next });
|
|
830
|
-
});
|
|
831
|
-
row.append(nameLabel, valueLabel, remove);
|
|
832
|
-
headersFieldset.append(row);
|
|
833
|
-
});
|
|
834
|
-
const add = document2.createElement("button");
|
|
835
|
-
add.type = "button";
|
|
836
|
-
add.textContent = "Add header";
|
|
837
|
-
add.dataset.editorField = "true";
|
|
838
|
-
add.dataset.mockAddHeader = "true";
|
|
839
|
-
add.addEventListener("click", () => {
|
|
840
|
-
const cc = getCurrent();
|
|
841
|
-
if (cc === void 0) return;
|
|
842
|
-
setAction({
|
|
843
|
-
...cc,
|
|
844
|
-
headers: [...cc.headers ?? [], { name: "", value: "" }]
|
|
845
|
-
});
|
|
846
|
-
});
|
|
847
|
-
headersFieldset.append(add);
|
|
848
|
-
};
|
|
849
|
-
renderHeaders();
|
|
850
|
-
container.append(
|
|
851
|
-
statusLabel,
|
|
852
|
-
delayLabel,
|
|
853
|
-
sourceLabel,
|
|
854
|
-
bodyArea,
|
|
855
|
-
fileLabel,
|
|
856
|
-
headersFieldset
|
|
857
|
-
);
|
|
858
|
-
};
|
|
859
|
-
render();
|
|
860
|
-
return { destroy() {
|
|
861
|
-
} };
|
|
862
|
-
},
|
|
863
|
-
defaultAction() {
|
|
864
|
-
return { status: 200, body: "" };
|
|
833
|
+
}
|
|
834
|
+
return diagnostics;
|
|
865
835
|
}
|
|
866
836
|
};
|
|
867
837
|
}
|
|
@@ -870,17 +840,33 @@ function createMockRuleType() {
|
|
|
870
840
|
var MAX_QUERY_NAME_LENGTH = 256;
|
|
871
841
|
var MAX_QUERY_VALUE_LENGTH = 2048;
|
|
872
842
|
var MAX_QUERY_PARAMS = 64;
|
|
873
|
-
|
|
843
|
+
var QUERY_OPERATIONS = Object.freeze(["set", "remove"]);
|
|
844
|
+
function isRecord(value) {
|
|
874
845
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
875
846
|
}
|
|
876
847
|
function asQueryAction(value) {
|
|
877
|
-
if (!
|
|
848
|
+
if (!isRecord(value)) return void 0;
|
|
878
849
|
if (value.type !== "query") return void 0;
|
|
879
850
|
const params = value.params;
|
|
880
851
|
if (!Array.isArray(params)) return void 0;
|
|
881
852
|
return value;
|
|
882
853
|
}
|
|
883
|
-
function
|
|
854
|
+
function paramOperation(param) {
|
|
855
|
+
return param.operation ?? "set";
|
|
856
|
+
}
|
|
857
|
+
function buildSetParam(name, value) {
|
|
858
|
+
return { name, operation: "set", value };
|
|
859
|
+
}
|
|
860
|
+
function buildRemoveParam(name) {
|
|
861
|
+
return { name, operation: "remove" };
|
|
862
|
+
}
|
|
863
|
+
function normalizeParam(param) {
|
|
864
|
+
if (paramOperation(param) === "remove") {
|
|
865
|
+
return buildRemoveParam(param.name);
|
|
866
|
+
}
|
|
867
|
+
return buildSetParam(param.name, param.value ?? "");
|
|
868
|
+
}
|
|
869
|
+
function stable(diagnostics) {
|
|
884
870
|
return [...diagnostics].sort(
|
|
885
871
|
(a, b) => a.path === b.path ? a.code.localeCompare(b.code) : a.path.localeCompare(b.path)
|
|
886
872
|
);
|
|
@@ -889,7 +875,7 @@ var queryRuleType = {
|
|
|
889
875
|
id: "query",
|
|
890
876
|
label: "Query parameters",
|
|
891
877
|
matches(rule) {
|
|
892
|
-
return
|
|
878
|
+
return rule.type === "query";
|
|
893
879
|
},
|
|
894
880
|
validate(rule, rulePath) {
|
|
895
881
|
const action = asQueryAction(rule.action);
|
|
@@ -902,7 +888,7 @@ var queryRuleType = {
|
|
|
902
888
|
path: `${rulePath}/action/params`,
|
|
903
889
|
message: "Query rules need at least one parameter."
|
|
904
890
|
});
|
|
905
|
-
return
|
|
891
|
+
return stable(diagnostics);
|
|
906
892
|
}
|
|
907
893
|
if (action.params.length > MAX_QUERY_PARAMS) {
|
|
908
894
|
diagnostics.push({
|
|
@@ -916,6 +902,7 @@ var queryRuleType = {
|
|
|
916
902
|
action.params.forEach((param, index) => {
|
|
917
903
|
const namePath = `${rulePath}/action/params/${index}/name`;
|
|
918
904
|
const valuePath = `${rulePath}/action/params/${index}/value`;
|
|
905
|
+
const operation = paramOperation(param);
|
|
919
906
|
if (typeof param?.name !== "string" || param.name.length === 0) {
|
|
920
907
|
diagnostics.push({
|
|
921
908
|
code: "editor.query-param-name-required",
|
|
@@ -931,19 +918,28 @@ var queryRuleType = {
|
|
|
931
918
|
message: "Query parameter name is too long."
|
|
932
919
|
});
|
|
933
920
|
}
|
|
934
|
-
if (
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
921
|
+
if (operation === "set") {
|
|
922
|
+
if (typeof param?.value !== "string" || param.value.length === 0) {
|
|
923
|
+
diagnostics.push({
|
|
924
|
+
code: "editor.query-param-value-required",
|
|
925
|
+
severity: "error",
|
|
926
|
+
path: valuePath,
|
|
927
|
+
message: "Query parameter value is required."
|
|
928
|
+
});
|
|
929
|
+
} else if (param.value.length > MAX_QUERY_VALUE_LENGTH) {
|
|
930
|
+
diagnostics.push({
|
|
931
|
+
code: "editor.query-param-value-too-long",
|
|
932
|
+
severity: "error",
|
|
933
|
+
path: valuePath,
|
|
934
|
+
message: "Query parameter value is too long."
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
} else if (param?.value !== void 0) {
|
|
942
938
|
diagnostics.push({
|
|
943
|
-
code: "
|
|
939
|
+
code: "schema.unexpected",
|
|
944
940
|
severity: "error",
|
|
945
941
|
path: valuePath,
|
|
946
|
-
message: "Query parameter value
|
|
942
|
+
message: "Query parameter value must not be provided for remove operation."
|
|
947
943
|
});
|
|
948
944
|
}
|
|
949
945
|
if (typeof param?.name === "string" && seen.has(param.name)) {
|
|
@@ -956,7 +952,7 @@ var queryRuleType = {
|
|
|
956
952
|
}
|
|
957
953
|
if (typeof param?.name === "string") seen.add(param.name);
|
|
958
954
|
});
|
|
959
|
-
return
|
|
955
|
+
return stable(diagnostics);
|
|
960
956
|
},
|
|
961
957
|
mount(context) {
|
|
962
958
|
const { document: document2, container } = context;
|
|
@@ -971,6 +967,7 @@ var queryRuleType = {
|
|
|
971
967
|
current.params.forEach((param, index) => {
|
|
972
968
|
const row = document2.createElement("div");
|
|
973
969
|
row.dataset.queryParamRow = String(index);
|
|
970
|
+
const operation = paramOperation(param);
|
|
974
971
|
const nameLabel = document2.createElement("label");
|
|
975
972
|
nameLabel.textContent = "Name";
|
|
976
973
|
const nameInput = document2.createElement("input");
|
|
@@ -980,29 +977,57 @@ var queryRuleType = {
|
|
|
980
977
|
nameInput.addEventListener("input", () => {
|
|
981
978
|
const c = getCurrent();
|
|
982
979
|
if (c === void 0) return;
|
|
983
|
-
const params = c.params.map(
|
|
984
|
-
(
|
|
985
|
-
|
|
980
|
+
const params = c.params.map((p, i) => {
|
|
981
|
+
if (i !== index) return p;
|
|
982
|
+
return normalizeParam({ ...p, name: nameInput.value });
|
|
983
|
+
});
|
|
986
984
|
setAction({ type: "query", params });
|
|
987
985
|
});
|
|
988
986
|
context.registerControl(`/action/params/${index}/name`, nameInput);
|
|
989
987
|
nameLabel.append(nameInput);
|
|
988
|
+
const operationLabel = document2.createElement("label");
|
|
989
|
+
operationLabel.textContent = "Operation";
|
|
990
|
+
const operationSelect = createSelect(
|
|
991
|
+
document2,
|
|
992
|
+
QUERY_OPERATIONS,
|
|
993
|
+
operation,
|
|
994
|
+
(value) => {
|
|
995
|
+
const c = getCurrent();
|
|
996
|
+
if (c === void 0) return;
|
|
997
|
+
const params = c.params.map((p, i) => {
|
|
998
|
+
if (i !== index) return p;
|
|
999
|
+
if (value === "remove") {
|
|
1000
|
+
return buildRemoveParam(p.name);
|
|
1001
|
+
}
|
|
1002
|
+
return buildSetParam(p.name, p.value ?? "");
|
|
1003
|
+
});
|
|
1004
|
+
setAction({ type: "query", params });
|
|
1005
|
+
render();
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
operationSelect.dataset.queryParamOperation = String(index);
|
|
1009
|
+
context.registerControl(
|
|
1010
|
+
`/action/params/${index}/operation`,
|
|
1011
|
+
operationSelect
|
|
1012
|
+
);
|
|
1013
|
+
operationLabel.append(operationSelect);
|
|
990
1014
|
const valueLabel = document2.createElement("label");
|
|
991
1015
|
valueLabel.textContent = "Value";
|
|
992
1016
|
const valueInput = document2.createElement("input");
|
|
993
1017
|
valueInput.type = "text";
|
|
994
|
-
valueInput.value = param.value;
|
|
1018
|
+
valueInput.value = param.value ?? "";
|
|
995
1019
|
valueInput.dataset.editorField = "true";
|
|
996
1020
|
valueInput.addEventListener("input", () => {
|
|
997
1021
|
const c = getCurrent();
|
|
998
1022
|
if (c === void 0) return;
|
|
999
1023
|
const params = c.params.map(
|
|
1000
|
-
(p, i) => i === index ?
|
|
1024
|
+
(p, i) => i === index ? buildSetParam(p.name, valueInput.value) : p
|
|
1001
1025
|
);
|
|
1002
1026
|
setAction({ type: "query", params });
|
|
1003
1027
|
});
|
|
1004
1028
|
context.registerControl(`/action/params/${index}/value`, valueInput);
|
|
1005
1029
|
valueLabel.append(valueInput);
|
|
1030
|
+
valueLabel.hidden = operation === "remove";
|
|
1006
1031
|
const remove = document2.createElement("button");
|
|
1007
1032
|
remove.type = "button";
|
|
1008
1033
|
remove.textContent = "Remove";
|
|
@@ -1012,8 +1037,9 @@ var queryRuleType = {
|
|
|
1012
1037
|
if (c === void 0) return;
|
|
1013
1038
|
const params = c.params.filter((_, i) => i !== index);
|
|
1014
1039
|
setAction({ type: "query", params });
|
|
1040
|
+
render();
|
|
1015
1041
|
});
|
|
1016
|
-
row.append(nameLabel, valueLabel, remove);
|
|
1042
|
+
row.append(nameLabel, operationLabel, valueLabel, remove);
|
|
1017
1043
|
container.append(row);
|
|
1018
1044
|
});
|
|
1019
1045
|
const add = document2.createElement("button");
|
|
@@ -1025,8 +1051,9 @@ var queryRuleType = {
|
|
|
1025
1051
|
if (c === void 0) return;
|
|
1026
1052
|
setAction({
|
|
1027
1053
|
type: "query",
|
|
1028
|
-
params: [...c.params,
|
|
1054
|
+
params: [...c.params, buildSetParam("", "")]
|
|
1029
1055
|
});
|
|
1056
|
+
render();
|
|
1030
1057
|
});
|
|
1031
1058
|
container.append(add);
|
|
1032
1059
|
};
|
|
@@ -1035,34 +1062,71 @@ var queryRuleType = {
|
|
|
1035
1062
|
} };
|
|
1036
1063
|
},
|
|
1037
1064
|
defaultAction() {
|
|
1038
|
-
return {
|
|
1065
|
+
return {
|
|
1066
|
+
type: "query",
|
|
1067
|
+
params: [buildSetParam("", "")]
|
|
1068
|
+
};
|
|
1039
1069
|
}
|
|
1040
1070
|
};
|
|
1041
1071
|
|
|
1072
|
+
// packages/editor/src/rule-types/redirect.ts
|
|
1073
|
+
function createRedirectRuleType() {
|
|
1074
|
+
return {
|
|
1075
|
+
id: "redirect",
|
|
1076
|
+
label: "Redirect",
|
|
1077
|
+
actionField: "redirect",
|
|
1078
|
+
matches(rule) {
|
|
1079
|
+
return rule.type === "redirect";
|
|
1080
|
+
},
|
|
1081
|
+
defaultAction() {
|
|
1082
|
+
return { destination: "" };
|
|
1083
|
+
},
|
|
1084
|
+
mount(context) {
|
|
1085
|
+
const document2 = context.document;
|
|
1086
|
+
const label = document2.createElement("label");
|
|
1087
|
+
label.textContent = "Redirect destination URL";
|
|
1088
|
+
const input = document2.createElement("input");
|
|
1089
|
+
input.type = "text";
|
|
1090
|
+
input.maxLength = 2048;
|
|
1091
|
+
const existing = context.getField("redirect.destination");
|
|
1092
|
+
input.value = typeof existing === "string" ? existing : "";
|
|
1093
|
+
input.addEventListener("input", () => {
|
|
1094
|
+
context.setField("redirect.destination", input.value);
|
|
1095
|
+
});
|
|
1096
|
+
context.registerControl("/redirect/destination", input);
|
|
1097
|
+
label.append(input);
|
|
1098
|
+
context.container.append(label);
|
|
1099
|
+
return {
|
|
1100
|
+
destroy() {
|
|
1101
|
+
input.removeEventListener("input", () => {
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
},
|
|
1106
|
+
validate(rule, rulePath) {
|
|
1107
|
+
const redirect = rule.redirect;
|
|
1108
|
+
const destination = redirect !== null && typeof redirect === "object" && typeof redirect.destination === "string" ? redirect.destination : "";
|
|
1109
|
+
const urlRegex = typeof rule.urlRegex === "string" ? rule.urlRegex : "";
|
|
1110
|
+
const issues = validateRedirectDestination(destination, urlRegex);
|
|
1111
|
+
return issues.map((issue) => ({
|
|
1112
|
+
code: `schema.${issue.code}`,
|
|
1113
|
+
severity: "error",
|
|
1114
|
+
path: `${rulePath}/redirect/destination`,
|
|
1115
|
+
message: issue.message
|
|
1116
|
+
}));
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1042
1121
|
// packages/editor/src/rule-types/request-body.ts
|
|
1043
|
-
function
|
|
1122
|
+
function isRecord2(value) {
|
|
1044
1123
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1045
1124
|
}
|
|
1046
1125
|
function requestBodyOf(value) {
|
|
1047
|
-
if (!
|
|
1126
|
+
if (!isRecord2(value) || !("mode" in value)) return void 0;
|
|
1048
1127
|
return value;
|
|
1049
1128
|
}
|
|
1050
|
-
function
|
|
1051
|
-
for (let i = 0; i < value.length; i += 1) {
|
|
1052
|
-
const code = value.charCodeAt(i);
|
|
1053
|
-
if (code >= 55296 && code <= 56319) {
|
|
1054
|
-
if (i + 1 >= value.length) return true;
|
|
1055
|
-
const next = value.charCodeAt(i + 1);
|
|
1056
|
-
if (next < 56320 || next > 57343) return true;
|
|
1057
|
-
} else if (code >= 56320 && code <= 57343) {
|
|
1058
|
-
if (i === 0) return true;
|
|
1059
|
-
const prev = value.charCodeAt(i - 1);
|
|
1060
|
-
if (prev < 55296 || prev > 56319) return true;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
return false;
|
|
1064
|
-
}
|
|
1065
|
-
function stable3(values) {
|
|
1129
|
+
function stable2(values) {
|
|
1066
1130
|
return [...values].sort(
|
|
1067
1131
|
(left, right) => left.path.localeCompare(right.path) || left.code.localeCompare(right.code)
|
|
1068
1132
|
);
|
|
@@ -1178,7 +1242,7 @@ function createRequestBodyRuleType() {
|
|
|
1178
1242
|
message: 'Request-body rules require exactly one resource type: "xmlhttprequest".'
|
|
1179
1243
|
});
|
|
1180
1244
|
}
|
|
1181
|
-
return
|
|
1245
|
+
return stable2(diagnostics);
|
|
1182
1246
|
},
|
|
1183
1247
|
mount(context) {
|
|
1184
1248
|
const { document: document2, container } = context;
|
|
@@ -1186,26 +1250,25 @@ function createRequestBodyRuleType() {
|
|
|
1186
1250
|
const current = requestBodyOf(context.getField("requestBody"));
|
|
1187
1251
|
container.replaceChildren();
|
|
1188
1252
|
if (current === void 0) return;
|
|
1189
|
-
const modeSelect =
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1253
|
+
const modeSelect = createSelect(
|
|
1254
|
+
document2,
|
|
1255
|
+
[
|
|
1256
|
+
{ value: "replace", label: "Replace body" },
|
|
1257
|
+
{ value: "regex", label: "Regex replace" }
|
|
1258
|
+
],
|
|
1259
|
+
current.mode,
|
|
1260
|
+
(mode) => {
|
|
1261
|
+
const next = { ...current, mode };
|
|
1262
|
+
if (mode === "replace") {
|
|
1263
|
+
delete next.pattern;
|
|
1264
|
+
delete next.replacement;
|
|
1265
|
+
} else {
|
|
1266
|
+
delete next.body;
|
|
1267
|
+
}
|
|
1268
|
+
context.setField("requestBody", next);
|
|
1269
|
+
render();
|
|
1205
1270
|
}
|
|
1206
|
-
|
|
1207
|
-
render();
|
|
1208
|
-
});
|
|
1271
|
+
);
|
|
1209
1272
|
context.registerControl("/requestBody/mode", modeSelect);
|
|
1210
1273
|
container.append(modeSelect);
|
|
1211
1274
|
if (current.mode === "replace") {
|
|
@@ -1252,71 +1315,135 @@ function createRequestBodyRuleType() {
|
|
|
1252
1315
|
}
|
|
1253
1316
|
|
|
1254
1317
|
// packages/editor/src/rule-types/response-body.ts
|
|
1255
|
-
function
|
|
1318
|
+
function isRecord3(value) {
|
|
1256
1319
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1257
1320
|
}
|
|
1258
|
-
function
|
|
1259
|
-
if (!
|
|
1260
|
-
|
|
1321
|
+
function responseBodyOf(value) {
|
|
1322
|
+
if (!isRecord3(value)) return void 0;
|
|
1323
|
+
if (value.mode === "replace") {
|
|
1324
|
+
return { mode: "replace", body: value.body };
|
|
1325
|
+
}
|
|
1326
|
+
if (value.mode === "regex" && Array.isArray(value.replacements)) {
|
|
1327
|
+
return {
|
|
1328
|
+
mode: "regex",
|
|
1329
|
+
replacements: value.replacements
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
if (Array.isArray(value.replacements)) {
|
|
1333
|
+
return {
|
|
1334
|
+
mode: "regex",
|
|
1335
|
+
replacements: value.replacements
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
return void 0;
|
|
1261
1339
|
}
|
|
1262
|
-
function
|
|
1340
|
+
function stable3(values) {
|
|
1263
1341
|
return [...values].sort(
|
|
1264
1342
|
(left, right) => left.path.localeCompare(right.path) || left.code.localeCompare(right.code)
|
|
1265
1343
|
);
|
|
1266
1344
|
}
|
|
1345
|
+
function validateRegexReplacements(replacements, rulePath) {
|
|
1346
|
+
const diagnostics = [];
|
|
1347
|
+
if (replacements.length === 0 || replacements.length > LIMITS.maxResponseBodyReplacements) {
|
|
1348
|
+
diagnostics.push({
|
|
1349
|
+
code: "editor.response-body-replacements",
|
|
1350
|
+
severity: "error",
|
|
1351
|
+
path: `${rulePath}/responseBody/replacements`,
|
|
1352
|
+
message: `Response-body rules need 1-${LIMITS.maxResponseBodyReplacements} replacements.`
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
replacements.forEach((entry, index) => {
|
|
1356
|
+
const patternPath = `${rulePath}/responseBody/replacements/${index}/pattern`;
|
|
1357
|
+
const replacementPath = `${rulePath}/responseBody/replacements/${index}/replacement`;
|
|
1358
|
+
try {
|
|
1359
|
+
if (typeof entry?.pattern !== "string" || entry.pattern.length === 0 || entry.pattern.length > LIMITS.maxResponseBodyPatternLength)
|
|
1360
|
+
throw new Error();
|
|
1361
|
+
new RegExp(entry.pattern, "u");
|
|
1362
|
+
} catch {
|
|
1363
|
+
diagnostics.push({
|
|
1364
|
+
code: "editor.response-body-pattern",
|
|
1365
|
+
severity: "error",
|
|
1366
|
+
path: patternPath,
|
|
1367
|
+
message: "Replacement pattern must be a valid bounded regular expression."
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
if (typeof entry?.replacement !== "string" || entry.replacement.length > LIMITS.maxResponseBodyReplacementLength) {
|
|
1371
|
+
diagnostics.push({
|
|
1372
|
+
code: "editor.response-body-replacement",
|
|
1373
|
+
severity: "error",
|
|
1374
|
+
path: replacementPath,
|
|
1375
|
+
message: "Replacement text exceeds the permitted size."
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
return diagnostics;
|
|
1380
|
+
}
|
|
1267
1381
|
function createResponseBodyRuleType() {
|
|
1268
1382
|
return {
|
|
1269
1383
|
id: "response-body",
|
|
1270
|
-
label: "Response body
|
|
1384
|
+
label: "Response body",
|
|
1271
1385
|
actionField: "responseBody",
|
|
1272
1386
|
matches(rule) {
|
|
1273
|
-
return rule.type === "response-body" ||
|
|
1387
|
+
return rule.type === "response-body" || responseBodyOf(rule.responseBody) !== void 0;
|
|
1274
1388
|
},
|
|
1275
1389
|
validate(rule, rulePath) {
|
|
1276
|
-
const
|
|
1277
|
-
if (
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1390
|
+
const raw = rule.responseBody;
|
|
1391
|
+
if (isRecord3(raw) && "mode" in raw && raw.mode !== "replace" && raw.mode !== "regex") {
|
|
1392
|
+
return stable3([
|
|
1393
|
+
{
|
|
1394
|
+
code: "editor.response-body-mode",
|
|
1395
|
+
severity: "error",
|
|
1396
|
+
path: `${rulePath}/responseBody/mode`,
|
|
1397
|
+
message: 'responseBody.mode must be "replace" or "regex"'
|
|
1398
|
+
}
|
|
1399
|
+
]);
|
|
1286
1400
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1401
|
+
const action = responseBodyOf(raw);
|
|
1402
|
+
if (action === void 0) {
|
|
1403
|
+
if (isRecord3(raw) && raw.mode === "regex") {
|
|
1404
|
+
return stable3(validateRegexReplacements([], rulePath));
|
|
1405
|
+
}
|
|
1406
|
+
return [];
|
|
1407
|
+
}
|
|
1408
|
+
const diagnostics = [];
|
|
1409
|
+
if (action.mode === "replace") {
|
|
1410
|
+
if (typeof action.body !== "string") {
|
|
1295
1411
|
diagnostics.push({
|
|
1296
|
-
code: "editor.response-body-
|
|
1412
|
+
code: "editor.response-body-replace-body",
|
|
1297
1413
|
severity: "error",
|
|
1298
|
-
path:
|
|
1299
|
-
message: "
|
|
1414
|
+
path: `${rulePath}/responseBody/body`,
|
|
1415
|
+
message: "Replace mode requires a body string."
|
|
1300
1416
|
});
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1417
|
+
} else if (action.body.length > LIMITS.maxResponseBodyBytes) {
|
|
1418
|
+
diagnostics.push({
|
|
1419
|
+
code: "editor.response-body-replace-body",
|
|
1420
|
+
severity: "error",
|
|
1421
|
+
path: `${rulePath}/responseBody/body`,
|
|
1422
|
+
message: `Replace body exceeds the maximum size of ${LIMITS.maxResponseBodyBytes} bytes.`
|
|
1423
|
+
});
|
|
1424
|
+
} else if (hasLoneSurrogate(action.body)) {
|
|
1303
1425
|
diagnostics.push({
|
|
1304
|
-
code: "editor.response-body-
|
|
1426
|
+
code: "editor.response-body-lone-surrogate",
|
|
1305
1427
|
severity: "error",
|
|
1306
|
-
path:
|
|
1307
|
-
message: "
|
|
1428
|
+
path: `${rulePath}/responseBody/body`,
|
|
1429
|
+
message: "Replace body must not contain lone UTF-16 surrogates."
|
|
1308
1430
|
});
|
|
1309
1431
|
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1432
|
+
} else {
|
|
1433
|
+
diagnostics.push(
|
|
1434
|
+
...validateRegexReplacements(action.replacements, rulePath)
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
return stable3(diagnostics);
|
|
1312
1438
|
},
|
|
1313
1439
|
mount(context) {
|
|
1314
1440
|
const { document: document2, container } = context;
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1441
|
+
const currentReplacements = () => {
|
|
1442
|
+
const action = responseBodyOf(context.getField("responseBody"));
|
|
1443
|
+
return action?.mode === "regex" ? action.replacements : [];
|
|
1444
|
+
};
|
|
1445
|
+
const renderRegexRows = (onUpdate) => {
|
|
1446
|
+
currentReplacements().forEach((entry, index) => {
|
|
1320
1447
|
const row = document2.createElement("div");
|
|
1321
1448
|
row.dataset.responseBodyReplacement = String(index);
|
|
1322
1449
|
const pattern = document2.createElement("input");
|
|
@@ -1324,11 +1451,9 @@ function createResponseBodyRuleType() {
|
|
|
1324
1451
|
pattern.value = entry.pattern;
|
|
1325
1452
|
pattern.placeholder = "Pattern";
|
|
1326
1453
|
pattern.addEventListener("input", () => {
|
|
1327
|
-
const next = [
|
|
1328
|
-
...replacementsOf(context.getField("responseBody")) ?? []
|
|
1329
|
-
];
|
|
1454
|
+
const next = [...currentReplacements()];
|
|
1330
1455
|
next[index] = { ...next[index], pattern: pattern.value };
|
|
1331
|
-
|
|
1456
|
+
onUpdate(next);
|
|
1332
1457
|
});
|
|
1333
1458
|
context.registerControl(
|
|
1334
1459
|
`/responseBody/replacements/${index}/pattern`,
|
|
@@ -1339,11 +1464,9 @@ function createResponseBodyRuleType() {
|
|
|
1339
1464
|
replacement.value = entry.replacement;
|
|
1340
1465
|
replacement.placeholder = "Replacement";
|
|
1341
1466
|
replacement.addEventListener("input", () => {
|
|
1342
|
-
const next = [
|
|
1343
|
-
...replacementsOf(context.getField("responseBody")) ?? []
|
|
1344
|
-
];
|
|
1467
|
+
const next = [...currentReplacements()];
|
|
1345
1468
|
next[index] = { ...next[index], replacement: replacement.value };
|
|
1346
|
-
|
|
1469
|
+
onUpdate(next);
|
|
1347
1470
|
});
|
|
1348
1471
|
context.registerControl(
|
|
1349
1472
|
`/responseBody/replacements/${index}/replacement`,
|
|
@@ -1353,8 +1476,11 @@ function createResponseBodyRuleType() {
|
|
|
1353
1476
|
remove.type = "button";
|
|
1354
1477
|
remove.textContent = "Remove replacement";
|
|
1355
1478
|
remove.addEventListener("click", () => {
|
|
1356
|
-
|
|
1357
|
-
|
|
1479
|
+
onUpdate(
|
|
1480
|
+
currentReplacements().filter(
|
|
1481
|
+
(_, itemIndex) => itemIndex !== index
|
|
1482
|
+
)
|
|
1483
|
+
);
|
|
1358
1484
|
render();
|
|
1359
1485
|
});
|
|
1360
1486
|
row.append(pattern, replacement, remove);
|
|
@@ -1364,29 +1490,81 @@ function createResponseBodyRuleType() {
|
|
|
1364
1490
|
add.type = "button";
|
|
1365
1491
|
add.textContent = "Add replacement";
|
|
1366
1492
|
add.addEventListener("click", () => {
|
|
1367
|
-
|
|
1368
|
-
...
|
|
1493
|
+
onUpdate([
|
|
1494
|
+
...currentReplacements(),
|
|
1369
1495
|
{ pattern: "", replacement: "" }
|
|
1370
|
-
];
|
|
1371
|
-
context.setField("responseBody", { replacements: next });
|
|
1496
|
+
]);
|
|
1372
1497
|
render();
|
|
1373
1498
|
});
|
|
1374
1499
|
container.append(add);
|
|
1375
1500
|
};
|
|
1501
|
+
const render = () => {
|
|
1502
|
+
const current = responseBodyOf(context.getField("responseBody"));
|
|
1503
|
+
container.replaceChildren();
|
|
1504
|
+
if (current === void 0) return;
|
|
1505
|
+
const modeSelect = createSelect(
|
|
1506
|
+
document2,
|
|
1507
|
+
[
|
|
1508
|
+
{ value: "replace", label: "Replace body" },
|
|
1509
|
+
{ value: "regex", label: "Regex rewrite" }
|
|
1510
|
+
],
|
|
1511
|
+
current.mode,
|
|
1512
|
+
(mode) => {
|
|
1513
|
+
if (mode === "replace") {
|
|
1514
|
+
context.setField("responseBody", { mode: "replace", body: "" });
|
|
1515
|
+
} else {
|
|
1516
|
+
context.setField("responseBody", {
|
|
1517
|
+
mode: "regex",
|
|
1518
|
+
replacements: [{ pattern: "", replacement: "" }]
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
render();
|
|
1522
|
+
}
|
|
1523
|
+
);
|
|
1524
|
+
context.registerControl("/responseBody/mode", modeSelect);
|
|
1525
|
+
container.append(modeSelect);
|
|
1526
|
+
if (current.mode === "replace") {
|
|
1527
|
+
const bodyInput = document2.createElement("textarea");
|
|
1528
|
+
bodyInput.value = current.body ?? "";
|
|
1529
|
+
bodyInput.placeholder = "Replacement body";
|
|
1530
|
+
bodyInput.addEventListener("input", () => {
|
|
1531
|
+
context.setField("responseBody", {
|
|
1532
|
+
mode: "replace",
|
|
1533
|
+
body: bodyInput.value
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
context.registerControl("/responseBody/body", bodyInput);
|
|
1537
|
+
container.append(bodyInput);
|
|
1538
|
+
} else {
|
|
1539
|
+
const stored = context.getField("responseBody");
|
|
1540
|
+
const writeRegex = (replacements) => {
|
|
1541
|
+
if (isRecord3(stored) && !("mode" in stored) && Array.isArray(stored.replacements)) {
|
|
1542
|
+
context.setField("responseBody", { replacements });
|
|
1543
|
+
} else {
|
|
1544
|
+
context.setField("responseBody", {
|
|
1545
|
+
mode: "regex",
|
|
1546
|
+
replacements
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
renderRegexRows(writeRegex);
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1376
1553
|
render();
|
|
1377
1554
|
return { destroy() {
|
|
1378
1555
|
} };
|
|
1379
1556
|
},
|
|
1380
1557
|
defaultAction() {
|
|
1381
|
-
return {
|
|
1558
|
+
return { mode: "replace", body: "" };
|
|
1382
1559
|
}
|
|
1383
1560
|
};
|
|
1384
1561
|
}
|
|
1385
1562
|
|
|
1386
1563
|
// packages/editor/src/rule-types/index.ts
|
|
1387
1564
|
var builtInRuleTypes = Object.freeze([
|
|
1565
|
+
createHeaderRuleType(),
|
|
1566
|
+
createRedirectRuleType(),
|
|
1388
1567
|
queryRuleType,
|
|
1389
|
-
createMockRuleType(),
|
|
1390
1568
|
createResponseBodyRuleType(),
|
|
1391
1569
|
createRequestBodyRuleType()
|
|
1392
1570
|
]);
|
|
@@ -1472,7 +1650,7 @@ var FORBIDDEN_EXTENSION_FIELDS = /* @__PURE__ */ new Set([
|
|
|
1472
1650
|
var MAX_SNAPSHOT_ARRAY_LENGTH = 4096;
|
|
1473
1651
|
var F2_MAX_URL_REGEX_LENGTH2 = 2048;
|
|
1474
1652
|
var editorInstanceCount = 0;
|
|
1475
|
-
function
|
|
1653
|
+
function isRecord4(value) {
|
|
1476
1654
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1477
1655
|
}
|
|
1478
1656
|
function snapshotOwnData(value, ancestors = /* @__PURE__ */ new WeakSet()) {
|
|
@@ -1551,11 +1729,11 @@ function freezeSnapshot(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
1551
1729
|
return value;
|
|
1552
1730
|
}
|
|
1553
1731
|
function asDraftProject(value) {
|
|
1554
|
-
if (!
|
|
1732
|
+
if (!isRecord4(value) || !Array.isArray(value.groups)) return void 0;
|
|
1555
1733
|
for (const group of value.groups) {
|
|
1556
|
-
if (!
|
|
1734
|
+
if (!isRecord4(group) || !Array.isArray(group.rules)) return void 0;
|
|
1557
1735
|
for (const rule of group.rules) {
|
|
1558
|
-
if (!
|
|
1736
|
+
if (!isRecord4(rule)) return void 0;
|
|
1559
1737
|
}
|
|
1560
1738
|
}
|
|
1561
1739
|
return value;
|
|
@@ -1586,7 +1764,7 @@ function valueAtPath(root, path) {
|
|
|
1586
1764
|
if (index === void 0 || !Object.hasOwn(current, index))
|
|
1587
1765
|
return void 0;
|
|
1588
1766
|
current = current[index];
|
|
1589
|
-
} else if (
|
|
1767
|
+
} else if (isRecord4(current) && Object.hasOwn(current, segment)) {
|
|
1590
1768
|
current = current[segment];
|
|
1591
1769
|
} else {
|
|
1592
1770
|
return void 0;
|
|
@@ -1594,6 +1772,25 @@ function valueAtPath(root, path) {
|
|
|
1594
1772
|
}
|
|
1595
1773
|
return current;
|
|
1596
1774
|
}
|
|
1775
|
+
var MATCHER_CREATABLE_FIELDS = ["description", "method", "type"];
|
|
1776
|
+
var ACTION_PAYLOAD_FIELDS = [
|
|
1777
|
+
"redirect",
|
|
1778
|
+
"action",
|
|
1779
|
+
"requestBody",
|
|
1780
|
+
"responseBody",
|
|
1781
|
+
"headerDirection",
|
|
1782
|
+
"headerOperation",
|
|
1783
|
+
"headerName",
|
|
1784
|
+
"headerValue"
|
|
1785
|
+
];
|
|
1786
|
+
var CREATABLE_RULE_FIELDS = /* @__PURE__ */ new Set([
|
|
1787
|
+
...MATCHER_CREATABLE_FIELDS,
|
|
1788
|
+
...ACTION_PAYLOAD_FIELDS,
|
|
1789
|
+
"redactSensitiveInLogs"
|
|
1790
|
+
]);
|
|
1791
|
+
function isBodyRuleType(type) {
|
|
1792
|
+
return type === "request-body" || type === "response-body";
|
|
1793
|
+
}
|
|
1597
1794
|
function setValueAtPath(root, path, value) {
|
|
1598
1795
|
const segments = decodePointer(path);
|
|
1599
1796
|
if (!segments || segments.length === 0) return false;
|
|
@@ -1606,7 +1803,7 @@ function setValueAtPath(root, path, value) {
|
|
|
1606
1803
|
return false;
|
|
1607
1804
|
}
|
|
1608
1805
|
current = current[childIndex];
|
|
1609
|
-
} else if (
|
|
1806
|
+
} else if (isRecord4(current) && Object.hasOwn(current, segment)) {
|
|
1610
1807
|
current = current[segment];
|
|
1611
1808
|
} else {
|
|
1612
1809
|
return false;
|
|
@@ -1620,8 +1817,8 @@ function setValueAtPath(root, path, value) {
|
|
|
1620
1817
|
current[index] = value;
|
|
1621
1818
|
return true;
|
|
1622
1819
|
}
|
|
1623
|
-
if (!
|
|
1624
|
-
if (!Object.hasOwn(current, finalSegment) && finalSegment
|
|
1820
|
+
if (!isRecord4(current)) return false;
|
|
1821
|
+
if (!Object.hasOwn(current, finalSegment) && !CREATABLE_RULE_FIELDS.has(finalSegment)) {
|
|
1625
1822
|
return false;
|
|
1626
1823
|
}
|
|
1627
1824
|
if (Object.is(current[finalSegment], value)) return false;
|
|
@@ -1639,7 +1836,7 @@ function deleteValueAtPath(root, path) {
|
|
|
1639
1836
|
const parentPath = pointer(...segments.slice(0, -1));
|
|
1640
1837
|
const parent = valueAtPath(root, parentPath);
|
|
1641
1838
|
const key = segments[segments.length - 1];
|
|
1642
|
-
if (!
|
|
1839
|
+
if (!isRecord4(parent) || !Object.hasOwn(parent, key)) return false;
|
|
1643
1840
|
return delete parent[key];
|
|
1644
1841
|
}
|
|
1645
1842
|
function safeText(value, fallback = "") {
|
|
@@ -1675,7 +1872,7 @@ function normalizeDiagnostics(value) {
|
|
|
1675
1872
|
}
|
|
1676
1873
|
const diagnostics = [];
|
|
1677
1874
|
for (const item of value) {
|
|
1678
|
-
if (!
|
|
1875
|
+
if (!isRecord4(item)) continue;
|
|
1679
1876
|
const code = item.code;
|
|
1680
1877
|
const path = item.path;
|
|
1681
1878
|
const message = item.message;
|
|
@@ -1688,12 +1885,11 @@ function normalizeDiagnostics(value) {
|
|
|
1688
1885
|
function isValidExtensionName(name) {
|
|
1689
1886
|
return name.length > 0 && !COMMON_RULE_FIELDS.has(name) && !FORBIDDEN_EXTENSION_FIELDS.has(name) && !hasControl(name);
|
|
1690
1887
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
if (!isRecord5(rule)) return false;
|
|
1888
|
+
function clearActionFields(rule, keep, keepFields) {
|
|
1889
|
+
if (!isRecord4(rule)) return false;
|
|
1694
1890
|
let changed = false;
|
|
1695
|
-
for (const field of
|
|
1696
|
-
if (field === keep) continue;
|
|
1891
|
+
for (const field of ACTION_PAYLOAD_FIELDS) {
|
|
1892
|
+
if (field === keep || keepFields?.has(field)) continue;
|
|
1697
1893
|
if (Object.hasOwn(rule, field)) {
|
|
1698
1894
|
delete rule[field];
|
|
1699
1895
|
changed = true;
|
|
@@ -1706,9 +1902,9 @@ function extensionFieldParent(rule, name) {
|
|
|
1706
1902
|
if (segments.length === 0) return void 0;
|
|
1707
1903
|
let current = rule;
|
|
1708
1904
|
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
1709
|
-
if (!
|
|
1905
|
+
if (!isRecord4(current)) return void 0;
|
|
1710
1906
|
const next = current[segments[index]];
|
|
1711
|
-
if (!
|
|
1907
|
+
if (!isRecord4(next)) {
|
|
1712
1908
|
const created = {};
|
|
1713
1909
|
Object.defineProperty(current, segments[index], {
|
|
1714
1910
|
configurable: true,
|
|
@@ -1721,7 +1917,7 @@ function extensionFieldParent(rule, name) {
|
|
|
1721
1917
|
current = next;
|
|
1722
1918
|
}
|
|
1723
1919
|
}
|
|
1724
|
-
if (!
|
|
1920
|
+
if (!isRecord4(current)) return void 0;
|
|
1725
1921
|
return { parent: current, key: segments[segments.length - 1] };
|
|
1726
1922
|
}
|
|
1727
1923
|
function toSearchText(value) {
|
|
@@ -2009,6 +2205,14 @@ var EditorControllerImpl = class {
|
|
|
2009
2205
|
return;
|
|
2010
2206
|
}
|
|
2011
2207
|
const path = target.dataset.path;
|
|
2208
|
+
if (path && target instanceof HTMLInputElement && target.type === "checkbox" && !this.extensionControls.has(path)) {
|
|
2209
|
+
const changed = setValueAtPath(this.draft, path, target.checked);
|
|
2210
|
+
if (changed) {
|
|
2211
|
+
this.markChanged();
|
|
2212
|
+
this.render();
|
|
2213
|
+
}
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2012
2216
|
if (!path || this.extensionControls.has(path)) return;
|
|
2013
2217
|
if (this.updateCommonField(path, target.value)) this.render();
|
|
2014
2218
|
};
|
|
@@ -2283,7 +2487,7 @@ var EditorControllerImpl = class {
|
|
|
2283
2487
|
const ruleContainer = valueAtPath(this.draft, ruleContainerPath);
|
|
2284
2488
|
clearActionFields(
|
|
2285
2489
|
ruleContainer,
|
|
2286
|
-
rawValue === "redirect"
|
|
2490
|
+
rawValue === "redirect" ? rawValue : void 0
|
|
2287
2491
|
);
|
|
2288
2492
|
this.markChanged();
|
|
2289
2493
|
}
|
|
@@ -2319,16 +2523,30 @@ var EditorControllerImpl = class {
|
|
|
2319
2523
|
return;
|
|
2320
2524
|
}
|
|
2321
2525
|
const extension = this.extensions.find((entry) => entry.id === typeId);
|
|
2322
|
-
if (!extension?.defaultAction) return;
|
|
2323
|
-
const actionField = extension.actionField ?? "action";
|
|
2526
|
+
if (!extension?.defaultAction && !extension?.defaultFields) return;
|
|
2324
2527
|
const changedType = setValueAtPath(this.draft, `${rulePath}/type`, typeId);
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2528
|
+
let changedPayload = false;
|
|
2529
|
+
let keep;
|
|
2530
|
+
let keepFields;
|
|
2531
|
+
if (extension.defaultFields) {
|
|
2532
|
+
const fields = extension.defaultFields();
|
|
2533
|
+
keepFields = new Set(Object.keys(fields));
|
|
2534
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
2535
|
+
if (setValueAtPath(this.draft, `${rulePath}/${key}`, value)) {
|
|
2536
|
+
changedPayload = true;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
} else {
|
|
2540
|
+
const defaultAction = extension.defaultAction;
|
|
2541
|
+
if (defaultAction) {
|
|
2542
|
+
keep = extension.actionField ?? "action";
|
|
2543
|
+
if (setValueAtPath(this.draft, `${rulePath}/${keep}`, defaultAction())) {
|
|
2544
|
+
changedPayload = true;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
const cleared = clearActionFields(ruleContainer, keep, keepFields);
|
|
2549
|
+
if (changedType || changedPayload || cleared) {
|
|
2332
2550
|
this.markChanged();
|
|
2333
2551
|
this.render();
|
|
2334
2552
|
}
|
|
@@ -2580,7 +2798,7 @@ var EditorControllerImpl = class {
|
|
|
2580
2798
|
if (ruleProposal.method) {
|
|
2581
2799
|
rule.method = ruleProposal.method;
|
|
2582
2800
|
}
|
|
2583
|
-
const actionField = ruleProposal.kind === "redirect"
|
|
2801
|
+
const actionField = ruleProposal.kind === "redirect" ? ruleProposal.kind : "action";
|
|
2584
2802
|
rule[actionField] = ruleProposal.action;
|
|
2585
2803
|
group.rules.push(rule);
|
|
2586
2804
|
this.markChanged();
|
|
@@ -3377,6 +3595,25 @@ var EditorControllerImpl = class {
|
|
|
3377
3595
|
this.renderField(matcherGrid, "Method", `${rulePath}/method`, method);
|
|
3378
3596
|
matcherFields.append(matcherGrid);
|
|
3379
3597
|
card.append(matcherFields);
|
|
3598
|
+
if (!isBodyRuleType(rule.type)) {
|
|
3599
|
+
const redactFieldset = this.document.createElement("fieldset");
|
|
3600
|
+
const redactLegend = this.document.createElement("legend");
|
|
3601
|
+
redactLegend.textContent = "Match logging";
|
|
3602
|
+
redactFieldset.append(redactLegend);
|
|
3603
|
+
const redactGrid = this.document.createElement("div");
|
|
3604
|
+
redactGrid.dataset.editorFields = "true";
|
|
3605
|
+
const redactCheckbox = this.document.createElement("input");
|
|
3606
|
+
redactCheckbox.type = "checkbox";
|
|
3607
|
+
redactCheckbox.checked = rule.redactSensitiveInLogs === true;
|
|
3608
|
+
this.renderField(
|
|
3609
|
+
redactGrid,
|
|
3610
|
+
"Redact sensitive fields in logs",
|
|
3611
|
+
`${rulePath}/redactSensitiveInLogs`,
|
|
3612
|
+
redactCheckbox
|
|
3613
|
+
);
|
|
3614
|
+
redactFieldset.append(redactGrid);
|
|
3615
|
+
card.append(redactFieldset);
|
|
3616
|
+
}
|
|
3380
3617
|
if (this.extensions.length > 0) {
|
|
3381
3618
|
const currentType = this.extensions.find(
|
|
3382
3619
|
(e) => e.matches(rule)
|
|
@@ -3862,10 +4099,10 @@ var EditorControllerImpl = class {
|
|
|
3862
4099
|
}
|
|
3863
4100
|
};
|
|
3864
4101
|
function isSaveSuccess(value) {
|
|
3865
|
-
return
|
|
4102
|
+
return isRecord4(value) && value.ok === true;
|
|
3866
4103
|
}
|
|
3867
4104
|
function saveFailureDiagnostic(value) {
|
|
3868
|
-
if (!
|
|
4105
|
+
if (!isRecord4(value)) {
|
|
3869
4106
|
return diagnostic(
|
|
3870
4107
|
"editor.save-failed",
|
|
3871
4108
|
"",
|
|
@@ -3912,282 +4149,11 @@ function createEditor(options) {
|
|
|
3912
4149
|
const extensions = normalizeExtensions(options.ruleTypes);
|
|
3913
4150
|
return new EditorControllerImpl(options, project, extensions);
|
|
3914
4151
|
}
|
|
3915
|
-
|
|
3916
|
-
// packages/editor/src/rule-types/header.ts
|
|
3917
|
-
var FORBIDDEN_REQUEST_HEADERS2 = Object.freeze([
|
|
3918
|
-
"accept-charset",
|
|
3919
|
-
"accept-encoding",
|
|
3920
|
-
"access-control-request-headers",
|
|
3921
|
-
"access-control-request-method",
|
|
3922
|
-
"connection",
|
|
3923
|
-
"content-length",
|
|
3924
|
-
"cookie",
|
|
3925
|
-
"cookie2",
|
|
3926
|
-
"date",
|
|
3927
|
-
"dnt",
|
|
3928
|
-
"expect",
|
|
3929
|
-
"host",
|
|
3930
|
-
"keep-alive",
|
|
3931
|
-
"origin",
|
|
3932
|
-
"proxy-authenticate",
|
|
3933
|
-
"proxy-authorization",
|
|
3934
|
-
"te",
|
|
3935
|
-
"trailer",
|
|
3936
|
-
"transfer-encoding",
|
|
3937
|
-
"upgrade",
|
|
3938
|
-
"via"
|
|
3939
|
-
]);
|
|
3940
|
-
var FORBIDDEN_RESPONSE_HEADERS2 = Object.freeze([
|
|
3941
|
-
"connection",
|
|
3942
|
-
"content-encoding",
|
|
3943
|
-
"content-length",
|
|
3944
|
-
"date",
|
|
3945
|
-
"keep-alive",
|
|
3946
|
-
"proxy-authenticate",
|
|
3947
|
-
"proxy-authorization",
|
|
3948
|
-
"set-cookie",
|
|
3949
|
-
"set-cookie2",
|
|
3950
|
-
"te",
|
|
3951
|
-
"trailer",
|
|
3952
|
-
"transfer-encoding",
|
|
3953
|
-
"upgrade",
|
|
3954
|
-
"via"
|
|
3955
|
-
]);
|
|
3956
|
-
var FORBIDDEN_REQUEST_PREFIXES2 = Object.freeze(["proxy-", "sec-"]);
|
|
3957
|
-
function isForbiddenHeader(name, direction) {
|
|
3958
|
-
const normalized = name.toLowerCase();
|
|
3959
|
-
const forbidden = direction === "request" ? FORBIDDEN_REQUEST_HEADERS2 : FORBIDDEN_RESPONSE_HEADERS2;
|
|
3960
|
-
return forbidden.includes(normalized) || direction === "request" && FORBIDDEN_REQUEST_PREFIXES2.some(
|
|
3961
|
-
(prefix) => normalized.startsWith(prefix)
|
|
3962
|
-
);
|
|
3963
|
-
}
|
|
3964
|
-
var HEADER_DIRECTIONS = [
|
|
3965
|
-
"request",
|
|
3966
|
-
"response"
|
|
3967
|
-
];
|
|
3968
|
-
var HEADER_OPERATIONS = [
|
|
3969
|
-
"set",
|
|
3970
|
-
"append",
|
|
3971
|
-
"remove"
|
|
3972
|
-
];
|
|
3973
|
-
function createSelect(document2, options, value, onChange) {
|
|
3974
|
-
const select = document2.createElement("select");
|
|
3975
|
-
for (const option of options) {
|
|
3976
|
-
const opt = document2.createElement("option");
|
|
3977
|
-
opt.value = option;
|
|
3978
|
-
opt.textContent = option;
|
|
3979
|
-
if (option === value) opt.selected = true;
|
|
3980
|
-
select.append(opt);
|
|
3981
|
-
}
|
|
3982
|
-
select.addEventListener("change", () => onChange(select.value));
|
|
3983
|
-
return select;
|
|
3984
|
-
}
|
|
3985
|
-
function createInput(document2, value, onChange, maxLength) {
|
|
3986
|
-
const input = document2.createElement("input");
|
|
3987
|
-
input.type = "text";
|
|
3988
|
-
input.value = value;
|
|
3989
|
-
if (maxLength !== void 0) input.maxLength = maxLength;
|
|
3990
|
-
input.addEventListener("input", () => onChange(input.value));
|
|
3991
|
-
return input;
|
|
3992
|
-
}
|
|
3993
|
-
function createHeaderRuleType() {
|
|
3994
|
-
return {
|
|
3995
|
-
id: "header",
|
|
3996
|
-
label: "Header",
|
|
3997
|
-
matches(rule) {
|
|
3998
|
-
return rule.type === "header";
|
|
3999
|
-
},
|
|
4000
|
-
mount(context) {
|
|
4001
|
-
const document2 = context.document;
|
|
4002
|
-
const fieldset = document2.createElement("fieldset");
|
|
4003
|
-
const legend = document2.createElement("legend");
|
|
4004
|
-
legend.textContent = "Header rule";
|
|
4005
|
-
fieldset.append(legend);
|
|
4006
|
-
const directionLabel = document2.createElement("label");
|
|
4007
|
-
directionLabel.textContent = "Direction";
|
|
4008
|
-
const directionValue = context.getField("headerDirection") ?? "request";
|
|
4009
|
-
const directionSelect = createSelect(
|
|
4010
|
-
document2,
|
|
4011
|
-
HEADER_DIRECTIONS,
|
|
4012
|
-
directionValue,
|
|
4013
|
-
(value) => {
|
|
4014
|
-
context.setField("headerDirection", value);
|
|
4015
|
-
}
|
|
4016
|
-
);
|
|
4017
|
-
context.registerControl("/headerDirection", directionSelect);
|
|
4018
|
-
directionLabel.append(directionSelect);
|
|
4019
|
-
fieldset.append(directionLabel);
|
|
4020
|
-
const operationLabel = document2.createElement("label");
|
|
4021
|
-
operationLabel.textContent = "Operation";
|
|
4022
|
-
const operationValue = context.getField("headerOperation") ?? "set";
|
|
4023
|
-
const operationSelect = createSelect(
|
|
4024
|
-
document2,
|
|
4025
|
-
HEADER_OPERATIONS,
|
|
4026
|
-
operationValue,
|
|
4027
|
-
(value) => {
|
|
4028
|
-
context.setField("headerOperation", value);
|
|
4029
|
-
}
|
|
4030
|
-
);
|
|
4031
|
-
context.registerControl("/headerOperation", operationSelect);
|
|
4032
|
-
operationLabel.append(operationSelect);
|
|
4033
|
-
fieldset.append(operationLabel);
|
|
4034
|
-
const nameLabel = document2.createElement("label");
|
|
4035
|
-
nameLabel.textContent = "Header name";
|
|
4036
|
-
const nameValue = context.getField("headerName") ?? "";
|
|
4037
|
-
const nameInput = createInput(
|
|
4038
|
-
document2,
|
|
4039
|
-
nameValue,
|
|
4040
|
-
(value) => {
|
|
4041
|
-
context.setField("headerName", value);
|
|
4042
|
-
},
|
|
4043
|
-
256
|
|
4044
|
-
);
|
|
4045
|
-
context.registerControl("/headerName", nameInput);
|
|
4046
|
-
nameLabel.append(nameInput);
|
|
4047
|
-
fieldset.append(nameLabel);
|
|
4048
|
-
const valueLabel = document2.createElement("label");
|
|
4049
|
-
valueLabel.textContent = "Header value";
|
|
4050
|
-
const valueValue = context.getField("headerValue") ?? "";
|
|
4051
|
-
const valueInput = createInput(
|
|
4052
|
-
document2,
|
|
4053
|
-
valueValue,
|
|
4054
|
-
(value) => {
|
|
4055
|
-
context.setField("headerValue", value);
|
|
4056
|
-
},
|
|
4057
|
-
4096
|
|
4058
|
-
);
|
|
4059
|
-
context.registerControl("/headerValue", valueInput);
|
|
4060
|
-
valueLabel.append(valueInput);
|
|
4061
|
-
fieldset.append(valueLabel);
|
|
4062
|
-
context.container.append(fieldset);
|
|
4063
|
-
return {
|
|
4064
|
-
destroy() {
|
|
4065
|
-
directionSelect.removeEventListener("change", () => {
|
|
4066
|
-
});
|
|
4067
|
-
operationSelect.removeEventListener("change", () => {
|
|
4068
|
-
});
|
|
4069
|
-
nameInput.removeEventListener("input", () => {
|
|
4070
|
-
});
|
|
4071
|
-
valueInput.removeEventListener("input", () => {
|
|
4072
|
-
});
|
|
4073
|
-
}
|
|
4074
|
-
};
|
|
4075
|
-
},
|
|
4076
|
-
validate(rule, rulePath) {
|
|
4077
|
-
const diagnostics = [];
|
|
4078
|
-
const direction = rule.headerDirection;
|
|
4079
|
-
const operation = rule.headerOperation;
|
|
4080
|
-
const headerName = rule.headerName;
|
|
4081
|
-
const headerValue = rule.headerValue;
|
|
4082
|
-
if (!direction || !HEADER_DIRECTIONS.includes(direction)) {
|
|
4083
|
-
diagnostics.push({
|
|
4084
|
-
code: "schema.invalid-value",
|
|
4085
|
-
severity: "error",
|
|
4086
|
-
path: `${rulePath}/headerDirection`,
|
|
4087
|
-
message: 'Direction must be "request" or "response".'
|
|
4088
|
-
});
|
|
4089
|
-
}
|
|
4090
|
-
if (!operation || !HEADER_OPERATIONS.includes(operation)) {
|
|
4091
|
-
diagnostics.push({
|
|
4092
|
-
code: "schema.invalid-value",
|
|
4093
|
-
severity: "error",
|
|
4094
|
-
path: `${rulePath}/headerOperation`,
|
|
4095
|
-
message: 'Operation must be "set", "append", or "remove".'
|
|
4096
|
-
});
|
|
4097
|
-
}
|
|
4098
|
-
if (typeof headerName !== "string" || headerName.length === 0 || headerName.length > 256) {
|
|
4099
|
-
diagnostics.push({
|
|
4100
|
-
code: "schema.out-of-range",
|
|
4101
|
-
severity: "error",
|
|
4102
|
-
path: `${rulePath}/headerName`,
|
|
4103
|
-
message: "Header name must be 1-256 characters."
|
|
4104
|
-
});
|
|
4105
|
-
} else if (direction && isForbiddenHeader(headerName, direction)) {
|
|
4106
|
-
diagnostics.push({
|
|
4107
|
-
code: "extension.forbidden-header",
|
|
4108
|
-
severity: "error",
|
|
4109
|
-
path: `${rulePath}/headerName`,
|
|
4110
|
-
message: `Header "${headerName}" is forbidden for ${direction} headers.`
|
|
4111
|
-
});
|
|
4112
|
-
}
|
|
4113
|
-
if ((operation === "set" || operation === "append") && typeof headerValue !== "string") {
|
|
4114
|
-
diagnostics.push({
|
|
4115
|
-
code: "schema.required",
|
|
4116
|
-
severity: "error",
|
|
4117
|
-
path: `${rulePath}/headerValue`,
|
|
4118
|
-
message: "Header value is required for set and append operations."
|
|
4119
|
-
});
|
|
4120
|
-
} else if ((operation === "set" || operation === "append") && typeof headerValue === "string" && headerValue.length > 4096) {
|
|
4121
|
-
diagnostics.push({
|
|
4122
|
-
code: "schema.out-of-range",
|
|
4123
|
-
severity: "error",
|
|
4124
|
-
path: `${rulePath}/headerValue`,
|
|
4125
|
-
message: "Header value must be at most 4096 characters."
|
|
4126
|
-
});
|
|
4127
|
-
}
|
|
4128
|
-
if (operation === "remove" && headerValue !== void 0) {
|
|
4129
|
-
diagnostics.push({
|
|
4130
|
-
code: "schema.unexpected",
|
|
4131
|
-
severity: "error",
|
|
4132
|
-
path: `${rulePath}/headerValue`,
|
|
4133
|
-
message: "Header value must not be provided for remove operation."
|
|
4134
|
-
});
|
|
4135
|
-
}
|
|
4136
|
-
return diagnostics;
|
|
4137
|
-
}
|
|
4138
|
-
};
|
|
4139
|
-
}
|
|
4140
|
-
|
|
4141
|
-
// packages/editor/src/rule-types/redirect.ts
|
|
4142
|
-
function createRedirectRuleType() {
|
|
4143
|
-
return {
|
|
4144
|
-
id: "redirect",
|
|
4145
|
-
label: "Redirect",
|
|
4146
|
-
matches(rule) {
|
|
4147
|
-
return rule.type === "redirect";
|
|
4148
|
-
},
|
|
4149
|
-
mount(context) {
|
|
4150
|
-
const document2 = context.document;
|
|
4151
|
-
const label = document2.createElement("label");
|
|
4152
|
-
label.textContent = "Redirect destination URL";
|
|
4153
|
-
const input = document2.createElement("input");
|
|
4154
|
-
input.type = "text";
|
|
4155
|
-
input.maxLength = 2048;
|
|
4156
|
-
const existing = context.getField("redirect.destination");
|
|
4157
|
-
input.value = typeof existing === "string" ? existing : "";
|
|
4158
|
-
input.addEventListener("input", () => {
|
|
4159
|
-
context.setField("redirect.destination", input.value);
|
|
4160
|
-
});
|
|
4161
|
-
context.registerControl("/redirect/destination", input);
|
|
4162
|
-
label.append(input);
|
|
4163
|
-
context.container.append(label);
|
|
4164
|
-
return {
|
|
4165
|
-
destroy() {
|
|
4166
|
-
input.removeEventListener("input", () => {
|
|
4167
|
-
});
|
|
4168
|
-
}
|
|
4169
|
-
};
|
|
4170
|
-
},
|
|
4171
|
-
validate(rule, rulePath) {
|
|
4172
|
-
const redirect = rule.redirect;
|
|
4173
|
-
const destination = redirect !== null && typeof redirect === "object" && typeof redirect.destination === "string" ? redirect.destination : "";
|
|
4174
|
-
const urlRegex = typeof rule.urlRegex === "string" ? rule.urlRegex : "";
|
|
4175
|
-
const issues = validateRedirectDestination(destination, urlRegex);
|
|
4176
|
-
return issues.map((issue) => ({
|
|
4177
|
-
code: `schema.${issue.code}`,
|
|
4178
|
-
severity: "error",
|
|
4179
|
-
path: `${rulePath}/redirect/destination`,
|
|
4180
|
-
message: issue.message
|
|
4181
|
-
}));
|
|
4182
|
-
}
|
|
4183
|
-
};
|
|
4184
|
-
}
|
|
4185
4152
|
export {
|
|
4186
4153
|
EditorInitializationError,
|
|
4187
4154
|
builtInRuleTypes,
|
|
4188
4155
|
createEditor,
|
|
4189
4156
|
createHeaderRuleType,
|
|
4190
|
-
createMockRuleType,
|
|
4191
4157
|
createRedirectRuleType,
|
|
4192
4158
|
createRequestBodyRuleType,
|
|
4193
4159
|
createResponseBodyRuleType,
|