@rogatio/cli 6.4.1 → 6.6.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/dist/editor/index.css +84 -5
- package/dist/editor/index.js +188 -58
- package/dist/node/index.js +567 -11
- package/package.json +1 -1
package/dist/editor/index.css
CHANGED
|
@@ -276,13 +276,26 @@
|
|
|
276
276
|
.rogatio-editor [data-editor-command-bar] {
|
|
277
277
|
display: flex;
|
|
278
278
|
flex-wrap: wrap;
|
|
279
|
-
|
|
279
|
+
align-items: center;
|
|
280
|
+
justify-content: space-between;
|
|
281
|
+
gap: 0.75rem 1rem;
|
|
280
282
|
margin-block: 1rem;
|
|
281
283
|
padding: 0.65rem 0.75rem;
|
|
282
284
|
border: 1px solid var(--rogatio-border);
|
|
283
285
|
border-radius: var(--rogatio-radius);
|
|
284
286
|
background: var(--rogatio-surface-raised);
|
|
285
287
|
}
|
|
288
|
+
.rogatio-editor [data-action-cluster] {
|
|
289
|
+
display: flex;
|
|
290
|
+
flex-wrap: wrap;
|
|
291
|
+
align-items: center;
|
|
292
|
+
gap: 0.5rem;
|
|
293
|
+
}
|
|
294
|
+
.rogatio-editor [data-action-cluster=danger] {
|
|
295
|
+
margin-left: 0.15rem;
|
|
296
|
+
padding-left: 0.65rem;
|
|
297
|
+
border-left: 1px solid var(--rogatio-border);
|
|
298
|
+
}
|
|
286
299
|
.rogatio-editor [data-editor-form] {
|
|
287
300
|
display: grid;
|
|
288
301
|
gap: 1rem;
|
|
@@ -385,9 +398,57 @@
|
|
|
385
398
|
justify-content: space-between;
|
|
386
399
|
gap: 0.65rem;
|
|
387
400
|
}
|
|
388
|
-
.rogatio-editor [data-group-
|
|
389
|
-
|
|
390
|
-
|
|
401
|
+
.rogatio-editor [data-group-identity] {
|
|
402
|
+
display: flex;
|
|
403
|
+
flex: 1 1 auto;
|
|
404
|
+
align-items: center;
|
|
405
|
+
gap: 0.35rem;
|
|
406
|
+
min-width: 0;
|
|
407
|
+
}
|
|
408
|
+
.rogatio-editor [data-group-actions] {
|
|
409
|
+
display: flex;
|
|
410
|
+
flex-wrap: wrap;
|
|
411
|
+
align-items: center;
|
|
412
|
+
gap: 0.5rem;
|
|
413
|
+
margin-left: auto;
|
|
414
|
+
}
|
|
415
|
+
.rogatio-editor [data-action-dock] {
|
|
416
|
+
display: grid;
|
|
417
|
+
margin-top: 0.35rem;
|
|
418
|
+
border: 1px solid var(--rogatio-border);
|
|
419
|
+
border-left: 3px solid var(--rogatio-card-rule-edge);
|
|
420
|
+
border-radius: var(--rogatio-radius);
|
|
421
|
+
background: var(--rogatio-surface-inset);
|
|
422
|
+
overflow: hidden;
|
|
423
|
+
}
|
|
424
|
+
.rogatio-editor [data-dock-row] {
|
|
425
|
+
display: grid;
|
|
426
|
+
grid-template-columns: 5.5rem minmax(0, 1fr);
|
|
427
|
+
column-gap: 0.75rem;
|
|
428
|
+
align-items: center;
|
|
429
|
+
padding: 0.65rem 0.85rem;
|
|
430
|
+
}
|
|
431
|
+
.rogatio-editor [data-dock-row] + [data-dock-row] {
|
|
432
|
+
border-top: 1px solid var(--rogatio-border);
|
|
433
|
+
}
|
|
434
|
+
.rogatio-editor [data-dock-label] {
|
|
435
|
+
font-family: var(--rogatio-font-mono);
|
|
436
|
+
font-size: 0.6875rem;
|
|
437
|
+
font-weight: 700;
|
|
438
|
+
letter-spacing: 0.08em;
|
|
439
|
+
text-transform: uppercase;
|
|
440
|
+
color: var(--rogatio-muted);
|
|
441
|
+
}
|
|
442
|
+
.rogatio-editor [data-dock-actions] {
|
|
443
|
+
display: flex;
|
|
444
|
+
flex-wrap: wrap;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: flex-end;
|
|
447
|
+
gap: 0.5rem 0.75rem;
|
|
448
|
+
min-width: 0;
|
|
449
|
+
}
|
|
450
|
+
.rogatio-editor [data-dock-actions] [data-action-cluster=tools] {
|
|
451
|
+
margin-right: auto;
|
|
391
452
|
}
|
|
392
453
|
.rogatio-editor [data-group-heading] h2,
|
|
393
454
|
.rogatio-editor [data-section-heading] h2,
|
|
@@ -605,6 +666,22 @@
|
|
|
605
666
|
.rogatio-editor [data-section-heading] {
|
|
606
667
|
align-items: flex-start;
|
|
607
668
|
}
|
|
669
|
+
.rogatio-editor [data-group-actions] {
|
|
670
|
+
margin-left: 0;
|
|
671
|
+
width: 100%;
|
|
672
|
+
}
|
|
673
|
+
.rogatio-editor [data-dock-row] {
|
|
674
|
+
grid-template-columns: 1fr;
|
|
675
|
+
justify-items: start;
|
|
676
|
+
row-gap: 0.45rem;
|
|
677
|
+
}
|
|
678
|
+
.rogatio-editor [data-dock-actions] {
|
|
679
|
+
justify-content: flex-start;
|
|
680
|
+
width: 100%;
|
|
681
|
+
}
|
|
682
|
+
.rogatio-editor [data-dock-actions] [data-action-cluster=tools] {
|
|
683
|
+
margin-right: 0;
|
|
684
|
+
}
|
|
608
685
|
.rogatio-editor [data-rule-actions] {
|
|
609
686
|
justify-content: flex-start;
|
|
610
687
|
width: 100%;
|
|
@@ -630,7 +707,9 @@
|
|
|
630
707
|
.rogatio-editor fieldset,
|
|
631
708
|
.rogatio-editor [data-rule-card],
|
|
632
709
|
.rogatio-editor [data-group-row],
|
|
633
|
-
.rogatio-editor [data-editor-check-group]
|
|
710
|
+
.rogatio-editor [data-editor-check-group],
|
|
711
|
+
.rogatio-editor [data-action-dock],
|
|
712
|
+
.rogatio-editor [data-action-cluster=danger] {
|
|
634
713
|
border-color: ButtonText;
|
|
635
714
|
background: Canvas;
|
|
636
715
|
}
|
package/dist/editor/index.js
CHANGED
|
@@ -2238,7 +2238,7 @@ var EditorControllerImpl = class {
|
|
|
2238
2238
|
this.commandBar = this.document.createElement("div");
|
|
2239
2239
|
this.commandBar.dataset.editorCommandBar = "true";
|
|
2240
2240
|
this.commandBar.setAttribute("role", "toolbar");
|
|
2241
|
-
this.commandBar.setAttribute("aria-label", "
|
|
2241
|
+
this.commandBar.setAttribute("aria-label", "Project actions");
|
|
2242
2242
|
this.form = this.document.createElement("form");
|
|
2243
2243
|
this.form.dataset.editorForm = "true";
|
|
2244
2244
|
this.form.noValidate = true;
|
|
@@ -3485,7 +3485,7 @@ var EditorControllerImpl = class {
|
|
|
3485
3485
|
const draftGroup = this.draftGroupForSavedId(groupId);
|
|
3486
3486
|
const groupName = displayName(
|
|
3487
3487
|
draftGroup?.name,
|
|
3488
|
-
|
|
3488
|
+
this.host.querySelector("[data-group-heading] h2")?.textContent?.trim() ?? ""
|
|
3489
3489
|
);
|
|
3490
3490
|
this.applyGroupEnablementLabel(button, enabled.has(groupId), groupName);
|
|
3491
3491
|
}
|
|
@@ -3616,6 +3616,7 @@ var EditorControllerImpl = class {
|
|
|
3616
3616
|
} else {
|
|
3617
3617
|
this.renderGroup(this.route.groupId);
|
|
3618
3618
|
}
|
|
3619
|
+
this.renderActionDock();
|
|
3619
3620
|
this.decorateExtensionControls();
|
|
3620
3621
|
this.renderSummary();
|
|
3621
3622
|
this.renderMigrationNotices();
|
|
@@ -3722,24 +3723,185 @@ var EditorControllerImpl = class {
|
|
|
3722
3723
|
renderCommandBar() {
|
|
3723
3724
|
this.commandBar.replaceChildren();
|
|
3724
3725
|
this.commandBar.setAttribute("aria-busy", this.saving ? "true" : "false");
|
|
3726
|
+
this.commandBar.setAttribute("aria-label", "Project actions");
|
|
3727
|
+
const tools = this.document.createElement("div");
|
|
3728
|
+
tools.dataset.actionCluster = "tools";
|
|
3725
3729
|
if (this.aiAssistPanel) {
|
|
3726
|
-
|
|
3730
|
+
tools.append(
|
|
3727
3731
|
this.createCommandButton("AI Assist", "ai-assist", this.saving)
|
|
3728
3732
|
);
|
|
3729
3733
|
}
|
|
3730
|
-
this.
|
|
3731
|
-
|
|
3734
|
+
tools.append(this.createCommandButton("Validate", "validate", this.saving));
|
|
3735
|
+
if (this.route.kind === "project") {
|
|
3736
|
+
tools.append(
|
|
3737
|
+
this.createCommandButton(
|
|
3738
|
+
"Add group",
|
|
3739
|
+
"add-group",
|
|
3740
|
+
this.saving,
|
|
3741
|
+
{},
|
|
3742
|
+
"primary"
|
|
3743
|
+
)
|
|
3744
|
+
);
|
|
3745
|
+
} else if (this.route.kind === "test") {
|
|
3746
|
+
tools.append(
|
|
3747
|
+
this.createCommandButton(
|
|
3748
|
+
"Run test",
|
|
3749
|
+
"test:run",
|
|
3750
|
+
this.saving || this.testRunning,
|
|
3751
|
+
{},
|
|
3752
|
+
"primary"
|
|
3753
|
+
)
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
const commit = this.document.createElement("div");
|
|
3757
|
+
commit.dataset.actionCluster = "commit";
|
|
3758
|
+
commit.append(
|
|
3759
|
+
this.createCommandButton("Cancel", "cancel", this.saving),
|
|
3732
3760
|
this.createCommandButton(
|
|
3733
3761
|
"Save",
|
|
3734
3762
|
"save",
|
|
3735
3763
|
this.saving || !this.isDirty(),
|
|
3736
3764
|
{},
|
|
3737
3765
|
"primary"
|
|
3738
|
-
)
|
|
3739
|
-
this.createCommandButton("Cancel", "cancel", this.saving)
|
|
3766
|
+
)
|
|
3740
3767
|
);
|
|
3741
|
-
|
|
3742
|
-
|
|
3768
|
+
this.commandBar.append(tools, commit);
|
|
3769
|
+
}
|
|
3770
|
+
createGroupEnableButton(savedGroupId, groupName) {
|
|
3771
|
+
const enablement = this.options.groupEnablement;
|
|
3772
|
+
const enableButton = this.document.createElement("button");
|
|
3773
|
+
enableButton.type = "button";
|
|
3774
|
+
enableButton.dataset.groupEnable = "true";
|
|
3775
|
+
enableButton.dataset.groupId = savedGroupId;
|
|
3776
|
+
enableButton.dataset.btn = "primary";
|
|
3777
|
+
this.applyGroupEnablementLabel(
|
|
3778
|
+
enableButton,
|
|
3779
|
+
enablement?.isEnabled(savedGroupId) === true,
|
|
3780
|
+
groupName
|
|
3781
|
+
);
|
|
3782
|
+
enableButton.addEventListener("click", (event) => {
|
|
3783
|
+
event.preventDefault();
|
|
3784
|
+
event.stopPropagation();
|
|
3785
|
+
if (enableButton.disabled || !enablement) return;
|
|
3786
|
+
const next = enablement.isEnabled(savedGroupId) !== true;
|
|
3787
|
+
const peers = Array.from(
|
|
3788
|
+
this.host.querySelectorAll(
|
|
3789
|
+
"button[data-group-enable]"
|
|
3790
|
+
)
|
|
3791
|
+
).filter((peer) => peer.dataset.groupId === savedGroupId);
|
|
3792
|
+
for (const peer of peers) peer.disabled = true;
|
|
3793
|
+
void Promise.resolve(enablement.setEnabled(savedGroupId, next)).finally(
|
|
3794
|
+
() => {
|
|
3795
|
+
for (const peer of peers) {
|
|
3796
|
+
if (peer.isConnected) peer.disabled = false;
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
);
|
|
3800
|
+
});
|
|
3801
|
+
return enableButton;
|
|
3802
|
+
}
|
|
3803
|
+
/** Enable/Disable, Copy, and Remove as one toolbar. Remove stays visually last. */
|
|
3804
|
+
createGroupActionClusters(groupId, groupName) {
|
|
3805
|
+
const actions = this.document.createElement("div");
|
|
3806
|
+
actions.dataset.groupActions = "true";
|
|
3807
|
+
actions.setAttribute("role", "toolbar");
|
|
3808
|
+
actions.setAttribute("aria-label", `Actions for group ${groupName}`);
|
|
3809
|
+
const safe = this.document.createElement("div");
|
|
3810
|
+
safe.dataset.actionCluster = "safe";
|
|
3811
|
+
const group = this.groupById(groupId);
|
|
3812
|
+
const savedGroupId = group ? this.savedGroupId(group) : void 0;
|
|
3813
|
+
if (this.options.groupEnablement && savedGroupId) {
|
|
3814
|
+
safe.append(this.createGroupEnableButton(savedGroupId, groupName));
|
|
3815
|
+
}
|
|
3816
|
+
safe.append(
|
|
3817
|
+
this.createCommandButton("Copy group", "copy-group", this.saving, {
|
|
3818
|
+
groupId
|
|
3819
|
+
})
|
|
3820
|
+
);
|
|
3821
|
+
const danger = this.document.createElement("div");
|
|
3822
|
+
danger.dataset.actionCluster = "danger";
|
|
3823
|
+
danger.append(
|
|
3824
|
+
this.createCommandButton(
|
|
3825
|
+
"Remove group",
|
|
3826
|
+
"remove-group",
|
|
3827
|
+
this.saving,
|
|
3828
|
+
{ groupId },
|
|
3829
|
+
"danger"
|
|
3830
|
+
)
|
|
3831
|
+
);
|
|
3832
|
+
actions.append(safe, danger);
|
|
3833
|
+
return actions;
|
|
3834
|
+
}
|
|
3835
|
+
createProjectActionRow() {
|
|
3836
|
+
const actions = this.document.createElement("div");
|
|
3837
|
+
const tools = this.document.createElement("div");
|
|
3838
|
+
tools.dataset.actionCluster = "tools";
|
|
3839
|
+
if (this.aiAssistPanel) {
|
|
3840
|
+
tools.append(
|
|
3841
|
+
this.createCommandButton("AI Assist", "ai-assist", this.saving)
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
tools.append(this.createCommandButton("Validate", "validate", this.saving));
|
|
3845
|
+
const commit = this.document.createElement("div");
|
|
3846
|
+
commit.dataset.actionCluster = "commit";
|
|
3847
|
+
commit.append(
|
|
3848
|
+
this.createCommandButton("Cancel", "cancel", this.saving),
|
|
3849
|
+
this.createCommandButton(
|
|
3850
|
+
"Save",
|
|
3851
|
+
"save",
|
|
3852
|
+
this.saving || !this.isDirty(),
|
|
3853
|
+
{},
|
|
3854
|
+
"primary"
|
|
3855
|
+
)
|
|
3856
|
+
);
|
|
3857
|
+
actions.append(tools, commit);
|
|
3858
|
+
return actions;
|
|
3859
|
+
}
|
|
3860
|
+
appendDockRow(dock, label, actions) {
|
|
3861
|
+
const row = this.document.createElement("div");
|
|
3862
|
+
row.dataset.dockRow = "true";
|
|
3863
|
+
const labelEl = this.document.createElement("span");
|
|
3864
|
+
labelEl.dataset.dockLabel = "true";
|
|
3865
|
+
labelEl.textContent = label;
|
|
3866
|
+
actions.dataset.dockActions = "true";
|
|
3867
|
+
row.append(labelEl, actions);
|
|
3868
|
+
dock.append(row);
|
|
3869
|
+
}
|
|
3870
|
+
/**
|
|
3871
|
+
* Repeats the actions a long page would otherwise hide: under the last rule,
|
|
3872
|
+
* under the project group list, or under the test results.
|
|
3873
|
+
*/
|
|
3874
|
+
renderActionDock() {
|
|
3875
|
+
const dock = this.document.createElement("section");
|
|
3876
|
+
dock.dataset.actionDock = "end";
|
|
3877
|
+
dock.setAttribute("aria-label", "Repeated actions");
|
|
3878
|
+
if (this.route.kind === "group") {
|
|
3879
|
+
const groupId = this.route.groupId;
|
|
3880
|
+
const group = this.groupById(groupId);
|
|
3881
|
+
if (group) {
|
|
3882
|
+
const groupName = displayName(group.name, "Unnamed group");
|
|
3883
|
+
const addRule = this.document.createElement("div");
|
|
3884
|
+
addRule.dataset.actionCluster = "safe";
|
|
3885
|
+
addRule.append(
|
|
3886
|
+
this.createCommandButton(
|
|
3887
|
+
"Add rule",
|
|
3888
|
+
"add-rule",
|
|
3889
|
+
this.saving,
|
|
3890
|
+
{ groupId },
|
|
3891
|
+
"primary"
|
|
3892
|
+
)
|
|
3893
|
+
);
|
|
3894
|
+
this.appendDockRow(dock, "Rules", addRule);
|
|
3895
|
+
this.appendDockRow(
|
|
3896
|
+
dock,
|
|
3897
|
+
"Group",
|
|
3898
|
+
this.createGroupActionClusters(groupId, groupName)
|
|
3899
|
+
);
|
|
3900
|
+
}
|
|
3901
|
+
} else if (this.route.kind === "project") {
|
|
3902
|
+
const addGroup = this.document.createElement("div");
|
|
3903
|
+
addGroup.dataset.actionCluster = "safe";
|
|
3904
|
+
addGroup.append(
|
|
3743
3905
|
this.createCommandButton(
|
|
3744
3906
|
"Add group",
|
|
3745
3907
|
"add-group",
|
|
@@ -3748,19 +3910,23 @@ var EditorControllerImpl = class {
|
|
|
3748
3910
|
"primary"
|
|
3749
3911
|
)
|
|
3750
3912
|
);
|
|
3751
|
-
|
|
3752
|
-
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3913
|
+
this.appendDockRow(dock, "Groups", addGroup);
|
|
3914
|
+
} else {
|
|
3915
|
+
const run = this.document.createElement("div");
|
|
3916
|
+
run.dataset.actionCluster = "safe";
|
|
3917
|
+
run.append(
|
|
3755
3918
|
this.createCommandButton(
|
|
3756
3919
|
"Run test",
|
|
3757
3920
|
"test:run",
|
|
3758
|
-
this.saving,
|
|
3921
|
+
this.saving || this.testRunning,
|
|
3759
3922
|
{},
|
|
3760
3923
|
"primary"
|
|
3761
3924
|
)
|
|
3762
3925
|
);
|
|
3926
|
+
this.appendDockRow(dock, "Test", run);
|
|
3763
3927
|
}
|
|
3928
|
+
this.appendDockRow(dock, "Project", this.createProjectActionRow());
|
|
3929
|
+
this.form.append(dock);
|
|
3764
3930
|
}
|
|
3765
3931
|
renderProject() {
|
|
3766
3932
|
const heading = this.document.createElement("h2");
|
|
@@ -3841,51 +4007,11 @@ var EditorControllerImpl = class {
|
|
|
3841
4007
|
const groupName = displayName(group.name, "Unnamed group");
|
|
3842
4008
|
const headingRow = this.document.createElement("div");
|
|
3843
4009
|
headingRow.dataset.groupHeading = "true";
|
|
4010
|
+
const identity = this.document.createElement("div");
|
|
4011
|
+
identity.dataset.groupIdentity = "true";
|
|
3844
4012
|
const heading = this.document.createElement("h2");
|
|
3845
|
-
const copyGroup = this.createCommandButton(
|
|
3846
|
-
"Copy group",
|
|
3847
|
-
"copy-group",
|
|
3848
|
-
this.saving,
|
|
3849
|
-
{ groupId }
|
|
3850
|
-
);
|
|
3851
|
-
const removeGroup = this.createCommandButton(
|
|
3852
|
-
"Remove group",
|
|
3853
|
-
"remove-group",
|
|
3854
|
-
this.saving,
|
|
3855
|
-
{ groupId },
|
|
3856
|
-
"danger"
|
|
3857
|
-
);
|
|
3858
|
-
const savedGroupId = this.savedGroupId(group);
|
|
3859
|
-
const enablement = this.options.groupEnablement;
|
|
3860
|
-
const commands = [];
|
|
3861
|
-
if (enablement && savedGroupId) {
|
|
3862
|
-
const enableButton = this.document.createElement("button");
|
|
3863
|
-
enableButton.type = "button";
|
|
3864
|
-
enableButton.dataset.groupEnable = "true";
|
|
3865
|
-
enableButton.dataset.groupId = savedGroupId;
|
|
3866
|
-
enableButton.dataset.btn = "primary";
|
|
3867
|
-
this.applyGroupEnablementLabel(
|
|
3868
|
-
enableButton,
|
|
3869
|
-
enablement.isEnabled(savedGroupId) === true,
|
|
3870
|
-
groupName
|
|
3871
|
-
);
|
|
3872
|
-
enableButton.addEventListener("click", (event) => {
|
|
3873
|
-
event.preventDefault();
|
|
3874
|
-
event.stopPropagation();
|
|
3875
|
-
if (enableButton.disabled) return;
|
|
3876
|
-
const next = enablement.isEnabled(savedGroupId) !== true;
|
|
3877
|
-
enableButton.disabled = true;
|
|
3878
|
-
void Promise.resolve(enablement.setEnabled(savedGroupId, next)).finally(
|
|
3879
|
-
() => {
|
|
3880
|
-
if (enableButton.isConnected) enableButton.disabled = false;
|
|
3881
|
-
}
|
|
3882
|
-
);
|
|
3883
|
-
});
|
|
3884
|
-
commands.push(enableButton);
|
|
3885
|
-
}
|
|
3886
|
-
commands.push(copyGroup, removeGroup);
|
|
3887
4013
|
this.renderNameHeading({
|
|
3888
|
-
row:
|
|
4014
|
+
row: identity,
|
|
3889
4015
|
heading,
|
|
3890
4016
|
headingId: `${this.instanceId}-group-title-${groupIndex}`,
|
|
3891
4017
|
kind: "group",
|
|
@@ -3893,8 +4019,12 @@ var EditorControllerImpl = class {
|
|
|
3893
4019
|
namePath: pointer("groups", groupIndex, "name"),
|
|
3894
4020
|
name: group.name,
|
|
3895
4021
|
fallback: "Unnamed group",
|
|
3896
|
-
commands
|
|
4022
|
+
commands: []
|
|
3897
4023
|
});
|
|
4024
|
+
headingRow.append(
|
|
4025
|
+
identity,
|
|
4026
|
+
this.createGroupActionClusters(groupId, groupName)
|
|
4027
|
+
);
|
|
3898
4028
|
this.form.append(headingRow);
|
|
3899
4029
|
const rulesSection = this.document.createElement("section");
|
|
3900
4030
|
rulesSection.dataset.rulesSection = "true";
|
package/dist/node/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { basename as basename3, dirname as dirname8, resolve as resolve8 } from
|
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7
7
|
|
|
8
8
|
// packages/runtime/dist/node/index.js
|
|
9
|
+
import { isIP } from "node:net";
|
|
9
10
|
import * as fs from "node:fs/promises";
|
|
10
11
|
import * as path from "node:path";
|
|
11
12
|
|
|
@@ -1979,6 +1980,42 @@ function sourceMatches(source, url) {
|
|
|
1979
1980
|
return false;
|
|
1980
1981
|
}
|
|
1981
1982
|
}
|
|
1983
|
+
var PAC_STEER_MARKER = "steer:";
|
|
1984
|
+
function decodePacSteer(entry) {
|
|
1985
|
+
if (!entry.startsWith(PAC_STEER_MARKER)) return null;
|
|
1986
|
+
const value = entry.slice(PAC_STEER_MARKER.length);
|
|
1987
|
+
let url;
|
|
1988
|
+
try {
|
|
1989
|
+
url = new URL(`${value}/`);
|
|
1990
|
+
} catch {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
if (url.username !== "" || url.password !== "" || url.search !== "") {
|
|
1994
|
+
return null;
|
|
1995
|
+
}
|
|
1996
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
1997
|
+
const scheme = url.protocol === "https:" ? "https" : "http";
|
|
1998
|
+
const port = url.port === "" ? void 0 : Number(url.port);
|
|
1999
|
+
if (port !== void 0 && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
2000
|
+
return null;
|
|
2001
|
+
}
|
|
2002
|
+
const rebuilt = port === void 0 ? `${scheme}://${url.hostname}` : `${scheme}://${url.hostname}:${port}`;
|
|
2003
|
+
if (rebuilt !== value || !isLiteralSteerHost(url.hostname)) return null;
|
|
2004
|
+
return port === void 0 ? { scheme, host: url.hostname } : { scheme, host: url.hostname, port };
|
|
2005
|
+
}
|
|
2006
|
+
function isLiteralSteerHost(host) {
|
|
2007
|
+
if (host.length === 0 || host.length > 253) return false;
|
|
2008
|
+
if (host.startsWith(".") || host.endsWith(".") || host.includes("..")) {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
const labels = host.split(".");
|
|
2012
|
+
for (const label2 of labels) {
|
|
2013
|
+
if (label2.length === 0 || label2.length > 63) return false;
|
|
2014
|
+
if (!/^[A-Za-z0-9-]+$/.test(label2)) return false;
|
|
2015
|
+
if (label2.startsWith("-") || label2.endsWith("-")) return false;
|
|
2016
|
+
}
|
|
2017
|
+
return true;
|
|
2018
|
+
}
|
|
1982
2019
|
function sameOrigin(a, b) {
|
|
1983
2020
|
try {
|
|
1984
2021
|
return new URL(a).origin === new URL(b).origin;
|
|
@@ -1998,6 +2035,9 @@ import {
|
|
|
1998
2035
|
} from "node:http";
|
|
1999
2036
|
import { connect } from "node:net";
|
|
2000
2037
|
import { Worker } from "node:worker_threads";
|
|
2038
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
2039
|
+
import { request as httpRequest } from "node:http";
|
|
2040
|
+
import { request as httpsRequest } from "node:https";
|
|
2001
2041
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2002
2042
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2003
2043
|
import { readFile as readFile2, realpath as realpath2, stat } from "node:fs/promises";
|
|
@@ -20701,6 +20741,131 @@ var require_x509_cjs = __commonJS({
|
|
|
20701
20741
|
exports.idX448 = idX448;
|
|
20702
20742
|
}
|
|
20703
20743
|
});
|
|
20744
|
+
function parseIPv4(value) {
|
|
20745
|
+
const parts = value.split(".");
|
|
20746
|
+
if (parts.length !== 4) return null;
|
|
20747
|
+
const numbers = parts.map((part) => {
|
|
20748
|
+
if (!/^(?:0|[1-9]\d*)$/.test(part)) return -1;
|
|
20749
|
+
const number = Number(part);
|
|
20750
|
+
return Number.isInteger(number) && number >= 0 && number <= 255 ? number : -1;
|
|
20751
|
+
});
|
|
20752
|
+
return numbers.some((number) => number < 0) ? null : [numbers[0], numbers[1], numbers[2], numbers[3]];
|
|
20753
|
+
}
|
|
20754
|
+
function parseIPv6(value) {
|
|
20755
|
+
if (value.includes("%")) return null;
|
|
20756
|
+
const halves = value.split("::");
|
|
20757
|
+
if (halves.length > 2) return null;
|
|
20758
|
+
const left = halves[0] === "" ? [] : halves[0]?.split(":") ?? [];
|
|
20759
|
+
const right = halves.length === 2 && halves[1] !== "" ? halves[1]?.split(":") ?? [] : [];
|
|
20760
|
+
const parts = [...left, ...right];
|
|
20761
|
+
const words = [];
|
|
20762
|
+
for (const part of parts) {
|
|
20763
|
+
if (part.includes(".")) {
|
|
20764
|
+
const ipv4 = parseIPv4(part);
|
|
20765
|
+
if (ipv4 === null) return null;
|
|
20766
|
+
words.push(ipv4[0] << 8 | ipv4[1], ipv4[2] << 8 | ipv4[3]);
|
|
20767
|
+
continue;
|
|
20768
|
+
}
|
|
20769
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(part)) return null;
|
|
20770
|
+
words.push(Number.parseInt(part, 16));
|
|
20771
|
+
}
|
|
20772
|
+
if (halves.length === 1) return words.length === 8 ? words : null;
|
|
20773
|
+
if (words.length >= 8) return null;
|
|
20774
|
+
return [
|
|
20775
|
+
...words.slice(0, left.length),
|
|
20776
|
+
...new Array(8 - words.length).fill(0),
|
|
20777
|
+
...words.slice(left.length)
|
|
20778
|
+
];
|
|
20779
|
+
}
|
|
20780
|
+
function ipv4Number(value) {
|
|
20781
|
+
return ((value[0] * 256 + value[1]) * 256 + value[2]) * 256 + value[3] >>> 0;
|
|
20782
|
+
}
|
|
20783
|
+
function inIPv4Range(value, start, end) {
|
|
20784
|
+
return value >= start && value <= end;
|
|
20785
|
+
}
|
|
20786
|
+
function prefixMatches(value, prefix, bits) {
|
|
20787
|
+
let remaining = bits;
|
|
20788
|
+
for (let index = 0; remaining > 0; index += 1) {
|
|
20789
|
+
const compareBits = Math.min(remaining, 16);
|
|
20790
|
+
const mask = compareBits === 16 ? 65535 : 65535 << 16 - compareBits & 65535;
|
|
20791
|
+
if (((value[index] ?? 0) & mask) !== ((prefix[index] ?? 0) & mask))
|
|
20792
|
+
return false;
|
|
20793
|
+
remaining -= compareBits;
|
|
20794
|
+
}
|
|
20795
|
+
return true;
|
|
20796
|
+
}
|
|
20797
|
+
function mappedIPv4(value) {
|
|
20798
|
+
if (value.slice(0, 5).some((word) => word !== 0) || value[5] !== 65535)
|
|
20799
|
+
return null;
|
|
20800
|
+
return [
|
|
20801
|
+
(value[6] ?? 0) >> 8,
|
|
20802
|
+
(value[6] ?? 0) & 255,
|
|
20803
|
+
(value[7] ?? 0) >> 8,
|
|
20804
|
+
(value[7] ?? 0) & 255
|
|
20805
|
+
];
|
|
20806
|
+
}
|
|
20807
|
+
function classifyIPv4(value) {
|
|
20808
|
+
const number = ipv4Number(value);
|
|
20809
|
+
if (inIPv4Range(number, 0, 16777215)) return "unspecified";
|
|
20810
|
+
if (inIPv4Range(number, 167772160, 184549375) || inIPv4Range(number, 2886729728, 2887778303) || inIPv4Range(number, 3232235520, 3232301055))
|
|
20811
|
+
return "private";
|
|
20812
|
+
if (inIPv4Range(number, 2130706432, 2147483647)) return "loopback";
|
|
20813
|
+
if (inIPv4Range(number, 2851995648, 2852061183)) return "link-local";
|
|
20814
|
+
if (inIPv4Range(number, 1681915904, 1686110207)) return "carrier-grade";
|
|
20815
|
+
if (inIPv4Range(number, 3221225472, 3221225727) || inIPv4Range(number, 3221225984, 3221226239) || inIPv4Range(number, 3227017984, 3227018239))
|
|
20816
|
+
return "reserved";
|
|
20817
|
+
if (inIPv4Range(number, 3323068416, 3323199487)) return "benchmarking";
|
|
20818
|
+
if (inIPv4Range(number, 3325256704, 3325256959) || inIPv4Range(number, 3405803776, 3405804031))
|
|
20819
|
+
return "documentation";
|
|
20820
|
+
if (inIPv4Range(number, 3758096384, 4294967295)) return "multicast";
|
|
20821
|
+
return "public";
|
|
20822
|
+
}
|
|
20823
|
+
function classifyIPv6(value) {
|
|
20824
|
+
const mapped = mappedIPv4(value);
|
|
20825
|
+
if (mapped !== null) {
|
|
20826
|
+
return classifyIPv4(mapped);
|
|
20827
|
+
}
|
|
20828
|
+
if (value.every((word) => word === 0)) return "unspecified";
|
|
20829
|
+
if (value.slice(0, 7).every((word) => word === 0) && value[7] === 1)
|
|
20830
|
+
return "loopback";
|
|
20831
|
+
if (prefixMatches(value, [64512], 7)) return "private";
|
|
20832
|
+
if (prefixMatches(value, [65152], 10)) return "link-local";
|
|
20833
|
+
if (prefixMatches(value, [65280], 8)) return "multicast";
|
|
20834
|
+
if (prefixMatches(value, [8193, 3512], 32)) return "documentation";
|
|
20835
|
+
if (prefixMatches(value, [8193, 0], 32)) return "reserved";
|
|
20836
|
+
if (prefixMatches(value, [8193, 2, 0], 48)) return "benchmarking";
|
|
20837
|
+
if (prefixMatches(value, [8193, 16], 28)) return "reserved";
|
|
20838
|
+
if (prefixMatches(value, [100, 65435, 0, 0, 0, 0], 96))
|
|
20839
|
+
return "reserved";
|
|
20840
|
+
if (prefixMatches(value, [256], 8)) return "reserved";
|
|
20841
|
+
return "public";
|
|
20842
|
+
}
|
|
20843
|
+
function classifyAddress(value) {
|
|
20844
|
+
if (typeof value !== "string") return "invalid";
|
|
20845
|
+
const family = isIP(value);
|
|
20846
|
+
if (family === 4) {
|
|
20847
|
+
const parsed = parseIPv4(value);
|
|
20848
|
+
return parsed === null ? "invalid" : classifyIPv4(parsed);
|
|
20849
|
+
}
|
|
20850
|
+
if (family === 6) {
|
|
20851
|
+
const parsed = parseIPv6(value);
|
|
20852
|
+
return parsed === null ? "invalid" : classifyIPv6(parsed);
|
|
20853
|
+
}
|
|
20854
|
+
return "invalid";
|
|
20855
|
+
}
|
|
20856
|
+
function validateResolvedAddresses(addresses, maxAddresses) {
|
|
20857
|
+
if (addresses.length === 0 || addresses.length > maxAddresses) return null;
|
|
20858
|
+
const checked = [];
|
|
20859
|
+
for (const address of addresses) {
|
|
20860
|
+
if (address.family !== 4 && address.family !== 6 || classifyAddress(address.address) !== "public")
|
|
20861
|
+
return null;
|
|
20862
|
+
checked.push({ address: address.address, family: address.family });
|
|
20863
|
+
}
|
|
20864
|
+
checked.sort(
|
|
20865
|
+
(left, right) => left.family - right.family || (left.address < right.address ? -1 : left.address > right.address ? 1 : 0)
|
|
20866
|
+
);
|
|
20867
|
+
return checked;
|
|
20868
|
+
}
|
|
20704
20869
|
var MAX_RETRIES = 2;
|
|
20705
20870
|
var RETRY_DELAY_BASE = 1e3;
|
|
20706
20871
|
var COMPLETE_TIMEOUT = 6e4;
|
|
@@ -22084,11 +22249,183 @@ async function rewriteRequestBody(input, action, captureContext) {
|
|
|
22084
22249
|
}
|
|
22085
22250
|
};
|
|
22086
22251
|
}
|
|
22252
|
+
var defaultResolver = {
|
|
22253
|
+
async lookup(hostname, signal) {
|
|
22254
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
22255
|
+
const addresses = await dnsLookup(hostname, { all: true });
|
|
22256
|
+
return addresses.map((address) => ({
|
|
22257
|
+
address: address.address,
|
|
22258
|
+
family: address.family
|
|
22259
|
+
}));
|
|
22260
|
+
}
|
|
22261
|
+
};
|
|
22262
|
+
function responseHeaders(response) {
|
|
22263
|
+
const headers = [];
|
|
22264
|
+
for (let index = 0; index < response.rawHeaders.length; index += 2) {
|
|
22265
|
+
const name = response.rawHeaders[index];
|
|
22266
|
+
const value = response.rawHeaders[index + 1];
|
|
22267
|
+
if (name !== void 0 && value !== void 0)
|
|
22268
|
+
headers.push([name.toLowerCase(), value]);
|
|
22269
|
+
}
|
|
22270
|
+
return headers;
|
|
22271
|
+
}
|
|
22272
|
+
var defaultTransport = {
|
|
22273
|
+
request(options) {
|
|
22274
|
+
return new Promise((resolve22, reject) => {
|
|
22275
|
+
const lookup = (_hostname, _options, callback) => {
|
|
22276
|
+
callback(null, options.address, options.address.includes(":") ? 6 : 4);
|
|
22277
|
+
};
|
|
22278
|
+
const requestOptions = {
|
|
22279
|
+
hostname: options.hostname,
|
|
22280
|
+
port: options.port,
|
|
22281
|
+
path: options.path,
|
|
22282
|
+
method: options.method,
|
|
22283
|
+
headers: options.headers,
|
|
22284
|
+
agent: false,
|
|
22285
|
+
lookup,
|
|
22286
|
+
signal: options.signal,
|
|
22287
|
+
setHost: false,
|
|
22288
|
+
autoSelectFamily: false
|
|
22289
|
+
};
|
|
22290
|
+
const request = options.protocol === "https:" ? httpsRequest(
|
|
22291
|
+
{ ...requestOptions, servername: options.servername },
|
|
22292
|
+
(response) => {
|
|
22293
|
+
resolve22({
|
|
22294
|
+
status: response.statusCode ?? 0,
|
|
22295
|
+
headers: responseHeaders(response),
|
|
22296
|
+
body: response
|
|
22297
|
+
});
|
|
22298
|
+
}
|
|
22299
|
+
) : httpRequest(requestOptions, (response) => {
|
|
22300
|
+
resolve22({
|
|
22301
|
+
status: response.statusCode ?? 0,
|
|
22302
|
+
headers: responseHeaders(response),
|
|
22303
|
+
body: response
|
|
22304
|
+
});
|
|
22305
|
+
});
|
|
22306
|
+
request.once("error", reject);
|
|
22307
|
+
request.end();
|
|
22308
|
+
});
|
|
22309
|
+
}
|
|
22310
|
+
};
|
|
22311
|
+
function headerValue(headers, name) {
|
|
22312
|
+
return headers.find(([headerName]) => headerName.toLowerCase() === name)?.[1];
|
|
22313
|
+
}
|
|
22314
|
+
function responseHeaderBytes(headers) {
|
|
22315
|
+
let bytes = 0;
|
|
22316
|
+
for (const [name, value] of headers)
|
|
22317
|
+
bytes += Buffer.byteLength(name) + Buffer.byteLength(value) + 4;
|
|
22318
|
+
return bytes;
|
|
22319
|
+
}
|
|
22320
|
+
function combineSignals(signal, timeoutMs) {
|
|
22321
|
+
const controller = new AbortController();
|
|
22322
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
22323
|
+
const abort = () => controller.abort();
|
|
22324
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
22325
|
+
return {
|
|
22326
|
+
signal: controller.signal,
|
|
22327
|
+
dispose: () => {
|
|
22328
|
+
clearTimeout(timer);
|
|
22329
|
+
signal?.removeEventListener("abort", abort);
|
|
22330
|
+
}
|
|
22331
|
+
};
|
|
22332
|
+
}
|
|
22333
|
+
async function fetchAuthorized(operation, options = {}) {
|
|
22334
|
+
if (operation.kind !== "outbound-http" || operation.method !== "GET" && operation.method !== "HEAD")
|
|
22335
|
+
return failure("runtime.unsupported-method");
|
|
22336
|
+
const target = canonicalizeOutboundTarget(operation.target);
|
|
22337
|
+
if (target === null || target !== operation.target)
|
|
22338
|
+
return failure("runtime.invalid-target");
|
|
22339
|
+
const url = new URL(target);
|
|
22340
|
+
const protocol = url.protocol === "https:" ? "https:" : "http:";
|
|
22341
|
+
const port = protocol === "https:" ? 443 : 80;
|
|
22342
|
+
const timeoutMs = options.operationTimeoutMs ?? RUNTIME_LIMITS.operationTimeoutMs;
|
|
22343
|
+
const combined = combineSignals(
|
|
22344
|
+
options.signal,
|
|
22345
|
+
Math.max(1, Math.min(timeoutMs, RUNTIME_LIMITS.operationTimeoutMs))
|
|
22346
|
+
);
|
|
22347
|
+
try {
|
|
22348
|
+
const resolver = options.resolver ?? defaultResolver;
|
|
22349
|
+
const resolved = await resolver.lookup(url.hostname, combined.signal);
|
|
22350
|
+
const addresses = validateResolvedAddresses(
|
|
22351
|
+
resolved,
|
|
22352
|
+
RUNTIME_LIMITS.maxDnsAddresses
|
|
22353
|
+
);
|
|
22354
|
+
if (addresses === null || addresses.length === 0)
|
|
22355
|
+
return failure("runtime.address-denied");
|
|
22356
|
+
const selected = addresses[0];
|
|
22357
|
+
const transport = options.transport ?? defaultTransport;
|
|
22358
|
+
let response;
|
|
22359
|
+
try {
|
|
22360
|
+
response = await transport.request({
|
|
22361
|
+
protocol,
|
|
22362
|
+
hostname: url.hostname,
|
|
22363
|
+
address: selected.address,
|
|
22364
|
+
port,
|
|
22365
|
+
path: `${url.pathname}${url.search}`,
|
|
22366
|
+
method: operation.method,
|
|
22367
|
+
headers: { host: url.host, "accept-encoding": "identity" },
|
|
22368
|
+
...protocol === "https:" ? { servername: url.hostname } : {},
|
|
22369
|
+
signal: combined.signal
|
|
22370
|
+
});
|
|
22371
|
+
} catch {
|
|
22372
|
+
return combined.signal.aborted ? failure("runtime.timeout") : failure("runtime.internal");
|
|
22373
|
+
}
|
|
22374
|
+
if (combined.signal.aborted) return failure("runtime.timeout");
|
|
22375
|
+
if (response.status >= 300 && response.status <= 399)
|
|
22376
|
+
return failure("runtime.redirect-rejected");
|
|
22377
|
+
if (responseHeaderBytes(response.headers) > RUNTIME_LIMITS.maxResponseHeaderBytes)
|
|
22378
|
+
return failure("runtime.size-limit");
|
|
22379
|
+
const encoding = headerValue(response.headers, "content-encoding");
|
|
22380
|
+
if (encoding !== void 0 && encoding.toLowerCase() !== "identity")
|
|
22381
|
+
return failure("runtime.size-limit");
|
|
22382
|
+
const chunks = [];
|
|
22383
|
+
let total = 0;
|
|
22384
|
+
try {
|
|
22385
|
+
for await (const chunk of response.body) {
|
|
22386
|
+
if (combined.signal.aborted) return failure("runtime.timeout");
|
|
22387
|
+
const bytes = chunk instanceof Uint8Array ? Uint8Array.from(chunk) : Uint8Array.from(chunk);
|
|
22388
|
+
total += bytes.byteLength;
|
|
22389
|
+
if (total > RUNTIME_LIMITS.maxResponseBodyBytes)
|
|
22390
|
+
return failure("runtime.size-limit");
|
|
22391
|
+
chunks.push(bytes);
|
|
22392
|
+
}
|
|
22393
|
+
} catch {
|
|
22394
|
+
return combined.signal.aborted ? failure("runtime.timeout") : failure("runtime.internal");
|
|
22395
|
+
}
|
|
22396
|
+
const body = new Uint8Array(total);
|
|
22397
|
+
let offset = 0;
|
|
22398
|
+
for (const chunk of chunks) {
|
|
22399
|
+
body.set(chunk, offset);
|
|
22400
|
+
offset += chunk.byteLength;
|
|
22401
|
+
}
|
|
22402
|
+
return {
|
|
22403
|
+
ok: true,
|
|
22404
|
+
value: { status: response.status, headers: response.headers, body }
|
|
22405
|
+
};
|
|
22406
|
+
} catch {
|
|
22407
|
+
return combined.signal.aborted ? failure("runtime.timeout") : failure("runtime.dns-failed");
|
|
22408
|
+
} finally {
|
|
22409
|
+
combined.dispose();
|
|
22410
|
+
}
|
|
22411
|
+
}
|
|
22087
22412
|
function supportedContentType2(value) {
|
|
22088
22413
|
if (value === void 0) return false;
|
|
22089
22414
|
const mediaType = value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
22090
22415
|
return mediaType.startsWith("text/") || mediaType === "application/json" || mediaType === "application/javascript" || mediaType === "text/javascript" || mediaType === "text/css" || mediaType === "application/xml" || mediaType === "text/xml";
|
|
22091
22416
|
}
|
|
22417
|
+
function responseHeader(headers, name) {
|
|
22418
|
+
return headers.find(([key]) => key.toLowerCase() === name)?.[1];
|
|
22419
|
+
}
|
|
22420
|
+
function responseBodyReplacements(action) {
|
|
22421
|
+
if ("replacements" in action && Array.isArray(action.replacements)) {
|
|
22422
|
+
return action.replacements;
|
|
22423
|
+
}
|
|
22424
|
+
return [];
|
|
22425
|
+
}
|
|
22426
|
+
function isReplaceAction(action) {
|
|
22427
|
+
return "mode" in action && action.mode === "replace";
|
|
22428
|
+
}
|
|
22092
22429
|
async function rewriteResponseBody(input, replacements, replaceAction, captureContext) {
|
|
22093
22430
|
if (!supportedContentType2(input.contentType) || input.contentType === void 0)
|
|
22094
22431
|
return failure("runtime.size-limit");
|
|
@@ -22134,6 +22471,78 @@ async function rewriteResponseBody(input, replacements, replaceAction, captureCo
|
|
|
22134
22471
|
}
|
|
22135
22472
|
};
|
|
22136
22473
|
}
|
|
22474
|
+
function replaceResponseBody(body, contentType, captureContext) {
|
|
22475
|
+
const captures = captureContext === void 0 ? [] : matchUrlCaptures(captureContext.urlRegex, captureContext.url);
|
|
22476
|
+
if (captureContext !== void 0 && captures === null) {
|
|
22477
|
+
return failure("runtime.url-capture-mismatch");
|
|
22478
|
+
}
|
|
22479
|
+
const expanded = captureContext === void 0 ? body : substituteUrlCaptures(body, captures ?? []);
|
|
22480
|
+
const encoded = new TextEncoder().encode(expanded);
|
|
22481
|
+
if (encoded.byteLength > RUNTIME_LIMITS.maxResponseBodyBytes)
|
|
22482
|
+
return failure("runtime.size-limit");
|
|
22483
|
+
return {
|
|
22484
|
+
ok: true,
|
|
22485
|
+
value: {
|
|
22486
|
+
body: encoded,
|
|
22487
|
+
contentType: contentType?.split(";", 1)[0]?.trim() ?? "text/plain"
|
|
22488
|
+
}
|
|
22489
|
+
};
|
|
22490
|
+
}
|
|
22491
|
+
async function fetchAndRewriteAuthorizedResponse(operation, action, options = {}) {
|
|
22492
|
+
if (operation.kind !== "outbound-http" || operation.method !== "GET")
|
|
22493
|
+
return failure("runtime.unsupported-method");
|
|
22494
|
+
const response = await fetchAuthorized(operation, options);
|
|
22495
|
+
if (!response.ok) return response;
|
|
22496
|
+
const contentType = responseHeader(response.value.headers, "content-type");
|
|
22497
|
+
const transformed = isReplaceAction(action) ? replaceResponseBody(
|
|
22498
|
+
action.body,
|
|
22499
|
+
contentType,
|
|
22500
|
+
operation.sourceValue === void 0 ? void 0 : { url: operation.target, urlRegex: operation.sourceValue }
|
|
22501
|
+
) : await rewriteResponseBody(
|
|
22502
|
+
{
|
|
22503
|
+
contentType,
|
|
22504
|
+
contentEncoding: responseHeader(
|
|
22505
|
+
response.value.headers,
|
|
22506
|
+
"content-encoding"
|
|
22507
|
+
),
|
|
22508
|
+
body: response.value.body
|
|
22509
|
+
},
|
|
22510
|
+
responseBodyReplacements(action)
|
|
22511
|
+
);
|
|
22512
|
+
if (!transformed.ok) return transformed;
|
|
22513
|
+
return {
|
|
22514
|
+
ok: true,
|
|
22515
|
+
value: {
|
|
22516
|
+
...transformed.value,
|
|
22517
|
+
status: response.value.status,
|
|
22518
|
+
headers: response.value.headers
|
|
22519
|
+
}
|
|
22520
|
+
};
|
|
22521
|
+
}
|
|
22522
|
+
var RESPONSE_BODY_LISTENER_PREFIX = "/.rogatio/body/";
|
|
22523
|
+
function parseResponseBodyRedirect(rawUrl) {
|
|
22524
|
+
const queryAt = rawUrl.indexOf("?");
|
|
22525
|
+
const path2 = queryAt === -1 ? rawUrl : rawUrl.slice(0, queryAt);
|
|
22526
|
+
const search = queryAt === -1 ? "" : rawUrl.slice(queryAt);
|
|
22527
|
+
if (!path2.startsWith(RESPONSE_BODY_LISTENER_PREFIX)) return null;
|
|
22528
|
+
const rest = path2.slice(RESPONSE_BODY_LISTENER_PREFIX.length);
|
|
22529
|
+
const slash1 = rest.indexOf("/");
|
|
22530
|
+
if (slash1 <= 0) return null;
|
|
22531
|
+
const slash2 = rest.indexOf("/", slash1 + 1);
|
|
22532
|
+
if (slash2 <= slash1 + 1) return null;
|
|
22533
|
+
let ruleId;
|
|
22534
|
+
let digest;
|
|
22535
|
+
try {
|
|
22536
|
+
ruleId = decodeURIComponent(rest.slice(0, slash1));
|
|
22537
|
+
digest = decodeURIComponent(rest.slice(slash1 + 1, slash2));
|
|
22538
|
+
} catch {
|
|
22539
|
+
return null;
|
|
22540
|
+
}
|
|
22541
|
+
if (ruleId.length === 0 || digest.length === 0) return null;
|
|
22542
|
+
const original = rest.slice(slash2 + 1) + search;
|
|
22543
|
+
if (!/^https?:\/\//i.test(original)) return null;
|
|
22544
|
+
return { ruleId, digest, originalUrl: original };
|
|
22545
|
+
}
|
|
22137
22546
|
function originOf(value) {
|
|
22138
22547
|
const direct = normalizeSiteOrigin(value);
|
|
22139
22548
|
if (direct !== null) return direct;
|
|
@@ -22445,6 +22854,9 @@ function writeResponse(res, status, statusMessage, headers, body) {
|
|
|
22445
22854
|
res.writeHead(status, statusMessage, out);
|
|
22446
22855
|
res.end(body);
|
|
22447
22856
|
}
|
|
22857
|
+
function asPresetDigest(value) {
|
|
22858
|
+
return value.startsWith("sha256:") ? value : null;
|
|
22859
|
+
}
|
|
22448
22860
|
function initiatorFromHeaders(headers, targetUrl) {
|
|
22449
22861
|
const origin = headerValue2(headers, "origin");
|
|
22450
22862
|
if (typeof origin === "string" && /^https?:\/\//i.test(origin)) {
|
|
@@ -22487,6 +22899,10 @@ async function startInterceptProxy(options = {}) {
|
|
|
22487
22899
|
async function handleHttp(req, res) {
|
|
22488
22900
|
try {
|
|
22489
22901
|
const targetUrl = req.url ?? "";
|
|
22902
|
+
if (targetUrl.startsWith("/.rogatio/body/")) {
|
|
22903
|
+
await handleResponseBodyRedirect(req, res, targetUrl);
|
|
22904
|
+
return;
|
|
22905
|
+
}
|
|
22490
22906
|
if (!/^https?:\/\//i.test(targetUrl)) {
|
|
22491
22907
|
res.writeHead(400);
|
|
22492
22908
|
res.end();
|
|
@@ -22602,6 +23018,96 @@ async function startInterceptProxy(options = {}) {
|
|
|
22602
23018
|
}
|
|
22603
23019
|
}
|
|
22604
23020
|
}
|
|
23021
|
+
async function handleResponseBodyRedirect(req, res, rawUrl) {
|
|
23022
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
23023
|
+
if (method !== "GET") {
|
|
23024
|
+
res.writeHead(405);
|
|
23025
|
+
res.end();
|
|
23026
|
+
return;
|
|
23027
|
+
}
|
|
23028
|
+
const parsed = parseResponseBodyRedirect(rawUrl);
|
|
23029
|
+
const active = policy;
|
|
23030
|
+
if (parsed === null || active === null || active.presetDigest === void 0) {
|
|
23031
|
+
res.writeHead(parsed === null ? 400 : 403);
|
|
23032
|
+
res.end();
|
|
23033
|
+
return;
|
|
23034
|
+
}
|
|
23035
|
+
const presetDigest = asPresetDigest(active.presetDigest);
|
|
23036
|
+
if (presetDigest === null || parsed.digest !== presetDigest) {
|
|
23037
|
+
res.writeHead(403);
|
|
23038
|
+
res.end();
|
|
23039
|
+
return;
|
|
23040
|
+
}
|
|
23041
|
+
const operation = active.operations.find(
|
|
23042
|
+
(op) => op.kind === "response-body" && op.ruleId === parsed.ruleId
|
|
23043
|
+
);
|
|
23044
|
+
if (operation === void 0 || !sourceMatches(operation.matcher.source, parsed.originalUrl)) {
|
|
23045
|
+
res.writeHead(404);
|
|
23046
|
+
res.end();
|
|
23047
|
+
return;
|
|
23048
|
+
}
|
|
23049
|
+
if (operation.matcher.method !== void 0 && operation.matcher.method !== "GET") {
|
|
23050
|
+
res.writeHead(405);
|
|
23051
|
+
res.end();
|
|
23052
|
+
return;
|
|
23053
|
+
}
|
|
23054
|
+
const target = canonicalizeOutboundTarget(parsed.originalUrl);
|
|
23055
|
+
if (target === null) {
|
|
23056
|
+
res.writeHead(400);
|
|
23057
|
+
res.end();
|
|
23058
|
+
return;
|
|
23059
|
+
}
|
|
23060
|
+
const allowed = revalidateAuthority(active.project, active.operations, {
|
|
23061
|
+
groupId: operation.groupId,
|
|
23062
|
+
ruleId: operation.ruleId,
|
|
23063
|
+
url: parsed.originalUrl,
|
|
23064
|
+
target,
|
|
23065
|
+
method: "GET",
|
|
23066
|
+
initiator: initiatorFromHeaders(req.headers, parsed.originalUrl) ?? new URL(target).origin,
|
|
23067
|
+
resourceType: operation.matcher.resourceTypes[0]
|
|
23068
|
+
});
|
|
23069
|
+
if (!allowed.allowed) {
|
|
23070
|
+
res.writeHead(403);
|
|
23071
|
+
res.end();
|
|
23072
|
+
return;
|
|
23073
|
+
}
|
|
23074
|
+
const fetched = await fetchAndRewriteAuthorizedResponse(
|
|
23075
|
+
{
|
|
23076
|
+
groupId: operation.groupId,
|
|
23077
|
+
ruleId: operation.ruleId,
|
|
23078
|
+
operationId: operation.ruleId,
|
|
23079
|
+
kind: "outbound-http",
|
|
23080
|
+
target,
|
|
23081
|
+
method: "GET",
|
|
23082
|
+
sourceValue: operation.matcher.source.value,
|
|
23083
|
+
presetDigest
|
|
23084
|
+
},
|
|
23085
|
+
operation.responseBody
|
|
23086
|
+
);
|
|
23087
|
+
if (!fetched.ok) {
|
|
23088
|
+
res.writeHead(502);
|
|
23089
|
+
res.end();
|
|
23090
|
+
return;
|
|
23091
|
+
}
|
|
23092
|
+
const headers = {};
|
|
23093
|
+
for (const [name, value] of fetched.value.headers) {
|
|
23094
|
+
const lower = name.toLowerCase();
|
|
23095
|
+
if (lower === "set-cookie" || lower === "cookie" || lower === "authorization" || lower === "proxy-authorization") {
|
|
23096
|
+
continue;
|
|
23097
|
+
}
|
|
23098
|
+
const existing = headers[lower];
|
|
23099
|
+
if (existing === void 0) headers[lower] = value;
|
|
23100
|
+
else if (Array.isArray(existing)) existing.push(value);
|
|
23101
|
+
else headers[lower] = [existing, value];
|
|
23102
|
+
}
|
|
23103
|
+
writeResponse(
|
|
23104
|
+
res,
|
|
23105
|
+
fetched.value.status,
|
|
23106
|
+
fetched.value.status === 200 ? "OK" : "Upstream",
|
|
23107
|
+
headers,
|
|
23108
|
+
Buffer.from(fetched.value.body)
|
|
23109
|
+
);
|
|
23110
|
+
}
|
|
22605
23111
|
function handleConnect(req, clientSocket, head) {
|
|
22606
23112
|
const authority = req.url ?? "";
|
|
22607
23113
|
const [host, portText] = authority.split(":");
|
|
@@ -22646,6 +23152,19 @@ async function startInterceptProxy(options = {}) {
|
|
|
22646
23152
|
}
|
|
22647
23153
|
};
|
|
22648
23154
|
}
|
|
23155
|
+
function steerKey(origin) {
|
|
23156
|
+
const port = origin.port !== void 0 ? `:${origin.port}` : "";
|
|
23157
|
+
return `steer:${origin.scheme}://${origin.host}${port}`;
|
|
23158
|
+
}
|
|
23159
|
+
function pacRouteFromEntry(entry) {
|
|
23160
|
+
const steer = decodePacSteer(entry);
|
|
23161
|
+
if (steer !== null) return { steer };
|
|
23162
|
+
if (entry.includes("://")) {
|
|
23163
|
+
return isExactHttpUrl(entry) ? { url: entry } : null;
|
|
23164
|
+
}
|
|
23165
|
+
if (entry.length === 0) return null;
|
|
23166
|
+
return { hostname: entry };
|
|
23167
|
+
}
|
|
22649
23168
|
function pacCheck(route) {
|
|
22650
23169
|
if (typeof route.url === "string" && isExactHttpUrl(route.url)) {
|
|
22651
23170
|
return {
|
|
@@ -22653,6 +23172,12 @@ function pacCheck(route) {
|
|
|
22653
23172
|
line: `url === ${JSON.stringify(route.url)}`
|
|
22654
23173
|
};
|
|
22655
23174
|
}
|
|
23175
|
+
if (route.steer !== void 0) {
|
|
23176
|
+
return {
|
|
23177
|
+
key: steerKey(route.steer),
|
|
23178
|
+
line: steerPredicate(route.steer)
|
|
23179
|
+
};
|
|
23180
|
+
}
|
|
22656
23181
|
if (typeof route.hostname === "string" && route.hostname.length > 0 && !route.hostname.includes("://")) {
|
|
22657
23182
|
return {
|
|
22658
23183
|
key: `host:${route.hostname}`,
|
|
@@ -22661,6 +23186,17 @@ function pacCheck(route) {
|
|
|
22661
23186
|
}
|
|
22662
23187
|
return null;
|
|
22663
23188
|
}
|
|
23189
|
+
function steerPredicate(origin) {
|
|
23190
|
+
const hostCheck = `host === ${JSON.stringify(origin.host)}`;
|
|
23191
|
+
const base = origin.port !== void 0 ? `${origin.scheme}://${origin.host}:${origin.port}` : `${origin.scheme}://${origin.host}`;
|
|
23192
|
+
if (origin.port !== void 0) {
|
|
23193
|
+
const withSlash = JSON.stringify(`${base}/`);
|
|
23194
|
+
const exact = JSON.stringify(base);
|
|
23195
|
+
const withQuery = JSON.stringify(`${base}?`);
|
|
23196
|
+
return `${hostCheck} && (url.indexOf(${withSlash}) === 0 || url === ${exact} || url.indexOf(${withQuery}) === 0)`;
|
|
23197
|
+
}
|
|
23198
|
+
return `${hostCheck} && url.indexOf(${JSON.stringify(base)}) === 0`;
|
|
23199
|
+
}
|
|
22664
23200
|
function isExactHttpUrl(value) {
|
|
22665
23201
|
try {
|
|
22666
23202
|
const url = new URL(value);
|
|
@@ -22782,6 +23318,7 @@ function unsupportedReasons(capabilities) {
|
|
|
22782
23318
|
function createPlatformInterceptionProvider(adapter) {
|
|
22783
23319
|
let state = "stopped";
|
|
22784
23320
|
let active = false;
|
|
23321
|
+
let pacInstalled = false;
|
|
22785
23322
|
return {
|
|
22786
23323
|
platform: adapter.platform,
|
|
22787
23324
|
detect() {
|
|
@@ -22793,6 +23330,17 @@ function createPlatformInterceptionProvider(adapter) {
|
|
|
22793
23330
|
};
|
|
22794
23331
|
},
|
|
22795
23332
|
async start(activation, pacRoutes) {
|
|
23333
|
+
const decoded = pacRoutes.flatMap((entry) => {
|
|
23334
|
+
const route = pacRouteFromEntry(entry);
|
|
23335
|
+
return route === null ? [] : [route];
|
|
23336
|
+
});
|
|
23337
|
+
if (pacRoutes.length === 0) {
|
|
23338
|
+
const endpoint2 = await adapter.startTlsProxy(activation);
|
|
23339
|
+
active = true;
|
|
23340
|
+
pacInstalled = false;
|
|
23341
|
+
state = "running";
|
|
23342
|
+
return endpoint2;
|
|
23343
|
+
}
|
|
22796
23344
|
const capabilities = adapter.detect();
|
|
22797
23345
|
const reasons = unsupportedReasons(capabilities);
|
|
22798
23346
|
if (!capabilities.supported || reasons.length > 0) {
|
|
@@ -22804,20 +23352,17 @@ function createPlatformInterceptionProvider(adapter) {
|
|
|
22804
23352
|
throw new Error("runtime.platform-unsupported");
|
|
22805
23353
|
}
|
|
22806
23354
|
const endpoint = await adapter.startTlsProxy(activation);
|
|
22807
|
-
const pac = generatePacScript(
|
|
22808
|
-
pacRoutes.map(
|
|
22809
|
-
(entry) => entry.includes("://") ? { url: entry } : { hostname: entry }
|
|
22810
|
-
),
|
|
22811
|
-
endpoint
|
|
22812
|
-
);
|
|
23355
|
+
const pac = generatePacScript(decoded, endpoint);
|
|
22813
23356
|
try {
|
|
22814
23357
|
await adapter.installPac(pac);
|
|
22815
23358
|
active = true;
|
|
23359
|
+
pacInstalled = true;
|
|
22816
23360
|
state = "running";
|
|
22817
23361
|
return endpoint;
|
|
22818
23362
|
} catch (error) {
|
|
22819
23363
|
await adapter.stopTlsProxy();
|
|
22820
23364
|
active = false;
|
|
23365
|
+
pacInstalled = false;
|
|
22821
23366
|
state = "stopped";
|
|
22822
23367
|
throw error;
|
|
22823
23368
|
}
|
|
@@ -22827,9 +23372,10 @@ function createPlatformInterceptionProvider(adapter) {
|
|
|
22827
23372
|
state = state === "unsupported" ? "unsupported" : "stopped";
|
|
22828
23373
|
return;
|
|
22829
23374
|
}
|
|
22830
|
-
await adapter.removePac();
|
|
23375
|
+
if (pacInstalled) await adapter.removePac();
|
|
22831
23376
|
await adapter.stopTlsProxy();
|
|
22832
23377
|
active = false;
|
|
23378
|
+
pacInstalled = false;
|
|
22833
23379
|
state = "stopped";
|
|
22834
23380
|
},
|
|
22835
23381
|
status() {
|
|
@@ -23429,7 +23975,7 @@ function createNativeRuntimeController(options) {
|
|
|
23429
23975
|
return aiConfigInForce;
|
|
23430
23976
|
}
|
|
23431
23977
|
async function bindInterception(current, sessionConfig) {
|
|
23432
|
-
if (sessionConfig.pacRoutes.length === 0) {
|
|
23978
|
+
if (sessionConfig.pacRoutes.length === 0 && sessionConfig.contentListener !== true) {
|
|
23433
23979
|
return {
|
|
23434
23980
|
activation: { ...current, pacRoutes: [] },
|
|
23435
23981
|
session: null,
|
|
@@ -23638,7 +24184,8 @@ function createNativeRuntimeController(options) {
|
|
|
23638
24184
|
preset = normalized.value;
|
|
23639
24185
|
activePolicy = {
|
|
23640
24186
|
project: schemaResult.data,
|
|
23641
|
-
operations: compileResult.operations
|
|
24187
|
+
operations: compileResult.operations,
|
|
24188
|
+
presetDigest: preset.digest
|
|
23642
24189
|
};
|
|
23643
24190
|
const startResult = await this.start();
|
|
23644
24191
|
if (startResult.state !== "running") {
|
|
@@ -23686,6 +24233,7 @@ function createNativeRuntimeController(options) {
|
|
|
23686
24233
|
policyDigest: typeof meta.policyDigest === "string" ? meta.policyDigest : "",
|
|
23687
24234
|
extensionId: typeof meta.extensionId === "string" ? meta.extensionId : "",
|
|
23688
24235
|
pacRoutes: Array.isArray(meta.pacRoutes) ? meta.pacRoutes : [],
|
|
24236
|
+
contentListener: meta.contentListener === true,
|
|
23689
24237
|
targetPolicy: {
|
|
23690
24238
|
public: meta.targetPolicy?.public === true || meta.targetPolicy?.publicAllowed === true,
|
|
23691
24239
|
localOrigins: Array.isArray(meta.targetPolicy?.localOrigins) ? meta.targetPolicy.localOrigins : []
|
|
@@ -24539,7 +25087,11 @@ function createNativeHost(options) {
|
|
|
24539
25087
|
}
|
|
24540
25088
|
const policy = controller.getActivePolicy();
|
|
24541
25089
|
interceptProxy = await startInterceptProxy({
|
|
24542
|
-
policy: policy ? {
|
|
25090
|
+
policy: policy ? {
|
|
25091
|
+
project: policy.project,
|
|
25092
|
+
operations: policy.operations,
|
|
25093
|
+
presetDigest: policy.presetDigest
|
|
25094
|
+
} : null
|
|
24543
25095
|
});
|
|
24544
25096
|
return interceptProxy.endpoint;
|
|
24545
25097
|
},
|
|
@@ -24560,7 +25112,11 @@ function createNativeHost(options) {
|
|
|
24560
25112
|
const endpoint = await platformProvider.start(activation, routes);
|
|
24561
25113
|
const policy = controller.getActivePolicy();
|
|
24562
25114
|
interceptProxy?.setPolicy(
|
|
24563
|
-
policy ? {
|
|
25115
|
+
policy ? {
|
|
25116
|
+
project: policy.project,
|
|
25117
|
+
operations: policy.operations,
|
|
25118
|
+
presetDigest: policy.presetDigest
|
|
25119
|
+
} : null
|
|
24564
25120
|
);
|
|
24565
25121
|
return endpoint;
|
|
24566
25122
|
},
|
package/package.json
CHANGED