@orkestrel/browser 0.0.2 → 0.0.5
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/src/core/index.cjs +422 -162
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +11 -2
- package/dist/src/core/index.d.ts +11 -2
- package/dist/src/core/index.js +422 -162
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +588 -411
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +52 -53
- package/dist/src/server/index.d.ts +52 -53
- package/dist/src/server/index.js +586 -410
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -8
package/dist/src/core/index.cjs
CHANGED
|
@@ -46,7 +46,7 @@ var BROWSER_RESULT_LIMIT_SENTINEL_PREFIX = "[[ORKESTREL_BROWSER_RESULT_LIMIT]]";
|
|
|
46
46
|
* throw is recognized only at the message START, not wherever the substring
|
|
47
47
|
* happens to occur.
|
|
48
48
|
*/
|
|
49
|
-
var BROWSER_RESULT_LIMIT_PATTERN = new RegExp(
|
|
49
|
+
var BROWSER_RESULT_LIMIT_PATTERN = new RegExp(`^(?:Uncaught )?Error: \\[\\[ORKESTREL_BROWSER_RESULT_LIMIT\\]\\](\\d+)`);
|
|
50
50
|
/** Poll interval in milliseconds while waiting for a selector to appear. */
|
|
51
51
|
var BROWSER_WAIT_POLL_INTERVAL_MS = 100;
|
|
52
52
|
/**
|
|
@@ -497,9 +497,11 @@ var CDPClient = class {
|
|
|
497
497
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
498
498
|
#sessionSubscriptions = /* @__PURE__ */ new Map();
|
|
499
499
|
#connected = false;
|
|
500
|
+
#active = false;
|
|
500
501
|
#wired = false;
|
|
501
502
|
#timeout;
|
|
502
503
|
#connecting;
|
|
504
|
+
#closing;
|
|
503
505
|
#closeRequested = false;
|
|
504
506
|
constructor(options) {
|
|
505
507
|
this.#transport = options.transport;
|
|
@@ -509,29 +511,16 @@ var CDPClient = class {
|
|
|
509
511
|
return this.#connected;
|
|
510
512
|
}
|
|
511
513
|
async connect() {
|
|
514
|
+
if (this.#closing !== void 0) await this.#closing;
|
|
512
515
|
if (this.#connected) return;
|
|
513
516
|
if (this.#connecting !== void 0) return this.#connecting;
|
|
514
517
|
this.#closeRequested = false;
|
|
515
|
-
const attempt = (
|
|
516
|
-
if (!this.#wired) {
|
|
517
|
-
this.#transport.emitter.on("message", (data) => this.#onMessage(data));
|
|
518
|
-
this.#transport.emitter.on("close", () => this.#onClose());
|
|
519
|
-
this.#transport.emitter.on("error", (error) => this.#onError(error));
|
|
520
|
-
this.#wired = true;
|
|
521
|
-
}
|
|
522
|
-
await this.#transport.start();
|
|
523
|
-
if (this.#closeRequested) {
|
|
524
|
-
this.#closeRequested = false;
|
|
525
|
-
await this.#transport.close();
|
|
526
|
-
throw new CDPConnectionError("CDP client was closed while connecting", { method: "connect" });
|
|
527
|
-
}
|
|
528
|
-
this.#connected = true;
|
|
529
|
-
})();
|
|
518
|
+
const attempt = this.#start();
|
|
530
519
|
this.#connecting = attempt;
|
|
531
520
|
try {
|
|
532
521
|
await attempt;
|
|
533
522
|
} finally {
|
|
534
|
-
this.#connecting = void 0;
|
|
523
|
+
if (this.#connecting === attempt) this.#connecting = void 0;
|
|
535
524
|
}
|
|
536
525
|
}
|
|
537
526
|
/**
|
|
@@ -633,18 +622,56 @@ var CDPClient = class {
|
|
|
633
622
|
* transport is closed deterministically once `start()` resolves.
|
|
634
623
|
*/
|
|
635
624
|
async close() {
|
|
625
|
+
const active = this.#closing;
|
|
626
|
+
if (active !== void 0) {
|
|
627
|
+
await active;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const attempt = this.#stop();
|
|
631
|
+
this.#closing = attempt;
|
|
632
|
+
try {
|
|
633
|
+
await attempt;
|
|
634
|
+
} finally {
|
|
635
|
+
if (this.#closing === attempt) this.#closing = void 0;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async #start() {
|
|
639
|
+
if (!this.#wired) {
|
|
640
|
+
this.#transport.emitter.on("message", (data) => this.#onMessage(data));
|
|
641
|
+
this.#transport.emitter.on("close", () => this.#onClose());
|
|
642
|
+
this.#transport.emitter.on("error", (error) => this.#onError(error));
|
|
643
|
+
this.#wired = true;
|
|
644
|
+
}
|
|
645
|
+
await this.#transport.start();
|
|
646
|
+
this.#active = true;
|
|
647
|
+
if (this.#closeRequested) {
|
|
648
|
+
this.#closeRequested = false;
|
|
649
|
+
try {
|
|
650
|
+
await this.#transport.close();
|
|
651
|
+
} finally {
|
|
652
|
+
this.#active = false;
|
|
653
|
+
}
|
|
654
|
+
throw new CDPConnectionError("CDP client was closed while connecting", { method: "connect" });
|
|
655
|
+
}
|
|
656
|
+
this.#connected = true;
|
|
657
|
+
}
|
|
658
|
+
async #stop() {
|
|
636
659
|
if (this.#connecting !== void 0) {
|
|
637
660
|
this.#closeRequested = true;
|
|
638
661
|
await this.#connecting.catch(() => void 0);
|
|
639
662
|
}
|
|
640
|
-
if (!this.#connected) return;
|
|
663
|
+
if (!this.#connected && !this.#active) return;
|
|
641
664
|
this.#connected = false;
|
|
642
665
|
for (const [id, entry] of this.#pending) {
|
|
643
666
|
clearTimeout(entry.timer);
|
|
644
667
|
entry.reject(new CDPConnectionError("CDP connection closed", { method: entry.method }));
|
|
645
668
|
this.#pending.delete(id);
|
|
646
669
|
}
|
|
647
|
-
|
|
670
|
+
try {
|
|
671
|
+
await this.#transport.close();
|
|
672
|
+
} finally {
|
|
673
|
+
this.#active = false;
|
|
674
|
+
}
|
|
648
675
|
}
|
|
649
676
|
#nextId() {
|
|
650
677
|
this.#messageId += 1;
|
|
@@ -652,6 +679,7 @@ var CDPClient = class {
|
|
|
652
679
|
}
|
|
653
680
|
#onClose() {
|
|
654
681
|
this.#connected = false;
|
|
682
|
+
this.#active = false;
|
|
655
683
|
for (const [id, entry] of this.#pending) {
|
|
656
684
|
clearTimeout(entry.timer);
|
|
657
685
|
entry.reject(new CDPConnectionError("CDP connection closed", { method: entry.method }));
|
|
@@ -659,7 +687,8 @@ var CDPClient = class {
|
|
|
659
687
|
}
|
|
660
688
|
}
|
|
661
689
|
#onError(error) {
|
|
662
|
-
|
|
690
|
+
this.#connected = false;
|
|
691
|
+
for (const [id, entry] of this.#pending) {
|
|
663
692
|
clearTimeout(entry.timer);
|
|
664
693
|
entry.reject(new CDPConnectionError(`CDP connection failed: ${String(error)}`, {
|
|
665
694
|
method: entry.method,
|
|
@@ -699,17 +728,22 @@ var CDPClient = class {
|
|
|
699
728
|
const rawParams = parsed["params"];
|
|
700
729
|
const params = (0, _orkestrel_contract.isRecord)(rawParams) ? rawParams : Object.freeze({});
|
|
701
730
|
const globalHandlers = this.#subscriptions.get(method);
|
|
702
|
-
if (globalHandlers !== void 0)
|
|
731
|
+
if (globalHandlers !== void 0) this.#dispatch(globalHandlers, params);
|
|
703
732
|
const eventSessionId = parsed["sessionId"];
|
|
704
733
|
if ((0, _orkestrel_contract.isString)(eventSessionId)) {
|
|
705
734
|
const sessionMap = this.#sessionSubscriptions.get(eventSessionId);
|
|
706
735
|
if (sessionMap !== void 0) {
|
|
707
736
|
const sessionHandlers = sessionMap.get(method);
|
|
708
|
-
if (sessionHandlers !== void 0)
|
|
737
|
+
if (sessionHandlers !== void 0) this.#dispatch(sessionHandlers, params);
|
|
709
738
|
}
|
|
710
739
|
}
|
|
711
740
|
}
|
|
712
741
|
}
|
|
742
|
+
#dispatch(handlers, params) {
|
|
743
|
+
for (const handler of [...handlers]) try {
|
|
744
|
+
handler(params);
|
|
745
|
+
} catch {}
|
|
746
|
+
}
|
|
713
747
|
};
|
|
714
748
|
//#endregion
|
|
715
749
|
//#region src/core/BrowserCodegen.ts
|
|
@@ -719,11 +753,17 @@ var BrowserCodegen = class {
|
|
|
719
753
|
#actions = [];
|
|
720
754
|
#started = false;
|
|
721
755
|
#destroyed = false;
|
|
756
|
+
#starting;
|
|
757
|
+
#stopping;
|
|
758
|
+
#shutdown;
|
|
722
759
|
#emitter;
|
|
723
760
|
constructor(client, sessionId, options) {
|
|
724
761
|
this.#client = client;
|
|
725
762
|
this.#sessionId = sessionId;
|
|
726
|
-
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
763
|
+
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
764
|
+
on: options?.on,
|
|
765
|
+
error: options?.error
|
|
766
|
+
});
|
|
727
767
|
}
|
|
728
768
|
get emitter() {
|
|
729
769
|
return this.#emitter;
|
|
@@ -732,9 +772,56 @@ var BrowserCodegen = class {
|
|
|
732
772
|
return this.#started;
|
|
733
773
|
}
|
|
734
774
|
async start() {
|
|
775
|
+
if (this.#destroyed) return;
|
|
776
|
+
if (this.#stopping !== void 0) await this.#stopping;
|
|
735
777
|
if (this.#destroyed) return;
|
|
736
778
|
if (this.#started) return;
|
|
737
|
-
|
|
779
|
+
const active = this.#starting;
|
|
780
|
+
if (active !== void 0) {
|
|
781
|
+
await active;
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const attempt = this.#begin();
|
|
785
|
+
this.#starting = attempt;
|
|
786
|
+
try {
|
|
787
|
+
await attempt;
|
|
788
|
+
} finally {
|
|
789
|
+
if (this.#starting === attempt) this.#starting = void 0;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async stop() {
|
|
793
|
+
await this.#starting?.catch(() => void 0);
|
|
794
|
+
if (!this.#started) return this.actions();
|
|
795
|
+
const active = this.#stopping;
|
|
796
|
+
if (active !== void 0) return await active;
|
|
797
|
+
const attempt = this.#end();
|
|
798
|
+
this.#stopping = attempt;
|
|
799
|
+
try {
|
|
800
|
+
return await attempt;
|
|
801
|
+
} finally {
|
|
802
|
+
if (this.#stopping === attempt) this.#stopping = void 0;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
actions() {
|
|
806
|
+
return normalizeCodegenActions(this.#actions);
|
|
807
|
+
}
|
|
808
|
+
script(options) {
|
|
809
|
+
return compileCodegenScript(this.actions(), options);
|
|
810
|
+
}
|
|
811
|
+
clear() {
|
|
812
|
+
this.#actions = [];
|
|
813
|
+
this.#emitter.emit("clear");
|
|
814
|
+
}
|
|
815
|
+
destroy() {
|
|
816
|
+
const active = this.#shutdown;
|
|
817
|
+
if (active !== void 0) return active;
|
|
818
|
+
if (this.#destroyed) return Promise.resolve();
|
|
819
|
+
this.#destroyed = true;
|
|
820
|
+
const shutdown = this.#destroy();
|
|
821
|
+
this.#shutdown = shutdown;
|
|
822
|
+
return shutdown;
|
|
823
|
+
}
|
|
824
|
+
async #begin() {
|
|
738
825
|
this.#client.subscribe("Runtime.bindingCalled", this.#onBindingCalled, this.#sessionId);
|
|
739
826
|
this.#client.subscribe("Page.frameNavigated", this.#onFrameNavigated, this.#sessionId);
|
|
740
827
|
try {
|
|
@@ -745,36 +832,25 @@ var BrowserCodegen = class {
|
|
|
745
832
|
expression: BROWSER_CODEGEN_SOURCE,
|
|
746
833
|
awaitPromise: true
|
|
747
834
|
}, this.#sessionId);
|
|
835
|
+
if (this.#destroyed) {
|
|
836
|
+
this.#unsubscribe();
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
748
839
|
} catch (error) {
|
|
749
|
-
this.#
|
|
750
|
-
this.#client.unsubscribe("Page.frameNavigated", this.#onFrameNavigated, this.#sessionId);
|
|
751
|
-
this.#started = false;
|
|
840
|
+
this.#unsubscribe();
|
|
752
841
|
throw error;
|
|
753
842
|
}
|
|
843
|
+
this.#started = true;
|
|
754
844
|
this.#emitter.emit("start");
|
|
755
845
|
}
|
|
756
|
-
async
|
|
757
|
-
|
|
758
|
-
this.#client.unsubscribe("Runtime.bindingCalled", this.#onBindingCalled, this.#sessionId);
|
|
759
|
-
this.#client.unsubscribe("Page.frameNavigated", this.#onFrameNavigated, this.#sessionId);
|
|
846
|
+
async #end() {
|
|
847
|
+
this.#unsubscribe();
|
|
760
848
|
this.#started = false;
|
|
761
849
|
const snapshot = this.actions();
|
|
762
850
|
this.#emitter.emit("stop", snapshot);
|
|
763
851
|
return snapshot;
|
|
764
852
|
}
|
|
765
|
-
|
|
766
|
-
return normalizeCodegenActions(this.#actions);
|
|
767
|
-
}
|
|
768
|
-
script(options) {
|
|
769
|
-
return compileCodegenScript(this.actions(), options);
|
|
770
|
-
}
|
|
771
|
-
clear() {
|
|
772
|
-
this.#actions = [];
|
|
773
|
-
this.#emitter.emit("clear");
|
|
774
|
-
}
|
|
775
|
-
async destroy() {
|
|
776
|
-
if (this.#destroyed) return;
|
|
777
|
-
this.#destroyed = true;
|
|
853
|
+
async #destroy() {
|
|
778
854
|
try {
|
|
779
855
|
await this.stop();
|
|
780
856
|
} catch {}
|
|
@@ -794,6 +870,10 @@ var BrowserCodegen = class {
|
|
|
794
870
|
this.#actions.push(action);
|
|
795
871
|
this.#emitter.emit("action", action);
|
|
796
872
|
};
|
|
873
|
+
#unsubscribe() {
|
|
874
|
+
this.#client.unsubscribe("Runtime.bindingCalled", this.#onBindingCalled, this.#sessionId);
|
|
875
|
+
this.#client.unsubscribe("Page.frameNavigated", this.#onFrameNavigated, this.#sessionId);
|
|
876
|
+
}
|
|
797
877
|
};
|
|
798
878
|
//#endregion
|
|
799
879
|
//#region src/core/BrowserPage.ts
|
|
@@ -805,16 +885,26 @@ var BrowserPage = class {
|
|
|
805
885
|
#url = "about:blank";
|
|
806
886
|
#closed = false;
|
|
807
887
|
#codegen;
|
|
808
|
-
#
|
|
888
|
+
#codegenStart;
|
|
889
|
+
#navigation;
|
|
890
|
+
#closing;
|
|
891
|
+
#releasing;
|
|
892
|
+
#loadEvent;
|
|
893
|
+
#loadTimer;
|
|
894
|
+
#loadResolve;
|
|
895
|
+
#loadReject;
|
|
896
|
+
#destroyHandler = (params) => {
|
|
897
|
+
if (!(0, _orkestrel_contract.isString)(params["targetId"]) || params["targetId"] !== this.#targetId) return;
|
|
898
|
+
this.#closed = true;
|
|
899
|
+
this.#release().catch(() => void 0);
|
|
900
|
+
};
|
|
901
|
+
#loadHandler = () => this.#resolveLoad();
|
|
809
902
|
constructor(client, targetId, sessionId, writer, url) {
|
|
810
903
|
this.#client = client;
|
|
811
904
|
this.#targetId = targetId;
|
|
812
905
|
this.#sessionId = sessionId;
|
|
813
906
|
this.#writer = writer;
|
|
814
907
|
if (url !== void 0) this.#url = url;
|
|
815
|
-
this.#destroyHandler = (params) => {
|
|
816
|
-
if ((0, _orkestrel_contract.isString)(params["targetId"]) && params["targetId"] === this.#targetId) this.#closed = true;
|
|
817
|
-
};
|
|
818
908
|
this.#client.subscribe("Target.targetDestroyed", this.#destroyHandler);
|
|
819
909
|
}
|
|
820
910
|
get url() {
|
|
@@ -824,46 +914,41 @@ var BrowserPage = class {
|
|
|
824
914
|
return this.#closed;
|
|
825
915
|
}
|
|
826
916
|
async title() {
|
|
917
|
+
this.#assertOpen();
|
|
827
918
|
const result = await this.#evaluate("document.title");
|
|
828
|
-
return
|
|
919
|
+
return this.#requireString(result, "Document title");
|
|
829
920
|
}
|
|
830
921
|
async navigate(url, options) {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
error
|
|
837
|
-
}));
|
|
922
|
+
this.#assertOpen();
|
|
923
|
+
while (this.#navigation !== void 0) await this.#navigation.catch(() => void 0);
|
|
924
|
+
this.#assertOpen();
|
|
925
|
+
const navigation = this.#navigate(url, options);
|
|
926
|
+
this.#navigation = navigation;
|
|
838
927
|
try {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
if (!outcome.ok) throw outcome.error;
|
|
843
|
-
} catch (error) {
|
|
844
|
-
wait.cancel();
|
|
845
|
-
await this.#stopLoading(Math.min(timeout, BROWSER_STOP_LOADING_TIMEOUT_MS));
|
|
846
|
-
throw error;
|
|
928
|
+
await navigation;
|
|
929
|
+
} finally {
|
|
930
|
+
if (this.#navigation === navigation) this.#navigation = void 0;
|
|
847
931
|
}
|
|
848
|
-
const currentUrl = await this.#evaluate("location.href");
|
|
849
|
-
this.#url = typeof currentUrl === "string" ? currentUrl : url;
|
|
850
932
|
}
|
|
851
933
|
async content() {
|
|
934
|
+
this.#assertOpen();
|
|
852
935
|
const [title, html, text, currentUrl] = await Promise.all([
|
|
853
936
|
this.#evaluate("document.title"),
|
|
854
937
|
this.#evaluate(guardEvaluateExpression("document.documentElement.outerHTML", BROWSER_RESULT_LIMIT)),
|
|
855
938
|
this.#evaluate(guardEvaluateExpression("document.body ? document.body.innerText : \"\"", BROWSER_RESULT_LIMIT)),
|
|
856
939
|
this.#evaluate("location.href")
|
|
857
940
|
]);
|
|
858
|
-
|
|
941
|
+
const url = this.#requireString(currentUrl, "Document URL");
|
|
942
|
+
this.#url = url;
|
|
859
943
|
return {
|
|
860
|
-
url
|
|
861
|
-
title:
|
|
862
|
-
html:
|
|
863
|
-
text:
|
|
944
|
+
url,
|
|
945
|
+
title: this.#requireString(title, "Document title"),
|
|
946
|
+
html: this.#requireString(html, "Document HTML"),
|
|
947
|
+
text: this.#requireString(text, "Document text")
|
|
864
948
|
};
|
|
865
949
|
}
|
|
866
950
|
async screenshot(options) {
|
|
951
|
+
this.#assertOpen();
|
|
867
952
|
const format = options?.type ?? "png";
|
|
868
953
|
const params = { format };
|
|
869
954
|
if (options?.quality !== void 0 && format === "jpeg") params["quality"] = options.quality;
|
|
@@ -871,9 +956,9 @@ var BrowserPage = class {
|
|
|
871
956
|
const metrics = await this.#client.send("Page.getLayoutMetrics", void 0, this.#sessionId);
|
|
872
957
|
if ((0, _orkestrel_contract.isRecord)(metrics) && (0, _orkestrel_contract.isRecord)(metrics["contentSize"])) {
|
|
873
958
|
const contentSize = metrics["contentSize"];
|
|
874
|
-
const width =
|
|
875
|
-
const height =
|
|
876
|
-
if (width > 0 && height > 0) {
|
|
959
|
+
const width = contentSize["width"];
|
|
960
|
+
const height = contentSize["height"];
|
|
961
|
+
if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
|
|
877
962
|
params["clip"] = {
|
|
878
963
|
x: 0,
|
|
879
964
|
y: 0,
|
|
@@ -895,11 +980,13 @@ var BrowserPage = class {
|
|
|
895
980
|
};
|
|
896
981
|
}
|
|
897
982
|
async click(selector, options) {
|
|
983
|
+
this.#assertOpen();
|
|
898
984
|
const timeout = options?.timeout ?? 3e4;
|
|
899
985
|
await this.#waitForSelector(selector, timeout);
|
|
900
986
|
await this.#evaluate(`(() => { const el = document.querySelector(${JSON.stringify(selector)}); if (el) { el.scrollIntoView({ block: 'center' }); el.click(); } else { throw new Error('Element not found: ' + ${JSON.stringify(selector)}); } })()`);
|
|
901
987
|
}
|
|
902
988
|
async fill(selector, value, options) {
|
|
989
|
+
this.#assertOpen();
|
|
903
990
|
const timeout = options?.timeout ?? 3e4;
|
|
904
991
|
await this.#waitForSelector(selector, timeout);
|
|
905
992
|
await this.#evaluate(`(() => {
|
|
@@ -917,6 +1004,7 @@ var BrowserPage = class {
|
|
|
917
1004
|
})()`);
|
|
918
1005
|
}
|
|
919
1006
|
async select(selector, values, options) {
|
|
1007
|
+
this.#assertOpen();
|
|
920
1008
|
const timeout = options?.timeout ?? 3e4;
|
|
921
1009
|
await this.#waitForSelector(selector, timeout);
|
|
922
1010
|
const valuesJson = JSON.stringify([...values]);
|
|
@@ -931,9 +1019,11 @@ var BrowserPage = class {
|
|
|
931
1019
|
})()`);
|
|
932
1020
|
}
|
|
933
1021
|
async evaluate(expression) {
|
|
1022
|
+
this.#assertOpen();
|
|
934
1023
|
return this.#evaluate(guardEvaluateExpression(expression, BROWSER_RESULT_LIMIT));
|
|
935
1024
|
}
|
|
936
1025
|
async wait(selector, options) {
|
|
1026
|
+
this.#assertOpen();
|
|
937
1027
|
const timeout = options?.timeout ?? 3e4;
|
|
938
1028
|
await this.#waitForSelector(selector, timeout);
|
|
939
1029
|
}
|
|
@@ -941,6 +1031,7 @@ var BrowserPage = class {
|
|
|
941
1031
|
return (await this.frames()).find((frame) => frame.name === name || frame.url === name);
|
|
942
1032
|
}
|
|
943
1033
|
async frames() {
|
|
1034
|
+
this.#assertOpen();
|
|
944
1035
|
const result = await this.#client.send("Page.getFrameTree", void 0, this.#sessionId);
|
|
945
1036
|
if (!(0, _orkestrel_contract.isRecord)(result) || !(0, _orkestrel_contract.isRecord)(result["frameTree"])) return [];
|
|
946
1037
|
const frames = [];
|
|
@@ -948,15 +1039,88 @@ var BrowserPage = class {
|
|
|
948
1039
|
return frames;
|
|
949
1040
|
}
|
|
950
1041
|
async codegen(options) {
|
|
1042
|
+
this.#assertOpen();
|
|
951
1043
|
if (this.#codegen !== void 0) return this.#codegen;
|
|
1044
|
+
const active = this.#codegenStart;
|
|
1045
|
+
if (active !== void 0) return await active;
|
|
1046
|
+
const attempt = this.#startCodegen(options);
|
|
1047
|
+
this.#codegenStart = attempt;
|
|
1048
|
+
try {
|
|
1049
|
+
return await attempt;
|
|
1050
|
+
} finally {
|
|
1051
|
+
if (this.#codegenStart === attempt) this.#codegenStart = void 0;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
destroy() {
|
|
1055
|
+
const active = this.#closing;
|
|
1056
|
+
if (active !== void 0) return active;
|
|
1057
|
+
this.#closed = true;
|
|
1058
|
+
const closing = this.#destroy();
|
|
1059
|
+
this.#closing = closing;
|
|
1060
|
+
return closing;
|
|
1061
|
+
}
|
|
1062
|
+
close() {
|
|
1063
|
+
const active = this.#closing;
|
|
1064
|
+
if (active !== void 0) return active;
|
|
1065
|
+
if (this.#closed) {
|
|
1066
|
+
const closing = this.#release();
|
|
1067
|
+
this.#closing = closing;
|
|
1068
|
+
return closing;
|
|
1069
|
+
}
|
|
1070
|
+
this.#closed = true;
|
|
1071
|
+
const closing = this.#close();
|
|
1072
|
+
this.#closing = closing;
|
|
1073
|
+
return closing;
|
|
1074
|
+
}
|
|
1075
|
+
async #navigate(url, options) {
|
|
1076
|
+
const timeout = options?.timeout ?? 3e4;
|
|
1077
|
+
const condition = options?.condition ?? "load";
|
|
1078
|
+
const wait = this.#waitForLoadEvent(condition, timeout);
|
|
1079
|
+
wait.catch(() => void 0);
|
|
1080
|
+
try {
|
|
1081
|
+
const result = await this.#client.send("Page.navigate", { url }, this.#sessionId, timeout);
|
|
1082
|
+
if ((0, _orkestrel_contract.isRecord)(result) && (0, _orkestrel_contract.isString)(result["errorText"])) throw new BrowserError(`Navigation failed: ${result["errorText"]}`);
|
|
1083
|
+
await wait;
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
this.#cancelLoad();
|
|
1086
|
+
await this.#stopLoading(Math.min(timeout, BROWSER_STOP_LOADING_TIMEOUT_MS));
|
|
1087
|
+
throw error;
|
|
1088
|
+
}
|
|
1089
|
+
const currentUrl = await this.#evaluate("location.href");
|
|
1090
|
+
this.#url = this.#requireString(currentUrl, "Navigation URL");
|
|
1091
|
+
}
|
|
1092
|
+
async #startCodegen(options) {
|
|
952
1093
|
const codegen = new BrowserCodegen(this.#client, this.#sessionId, options);
|
|
953
1094
|
await codegen.start();
|
|
1095
|
+
if (this.#closed) {
|
|
1096
|
+
await codegen.destroy();
|
|
1097
|
+
throw new BrowserError("Browser page is closed");
|
|
1098
|
+
}
|
|
954
1099
|
this.#codegen = codegen;
|
|
955
1100
|
return codegen;
|
|
956
1101
|
}
|
|
957
|
-
async
|
|
958
|
-
|
|
959
|
-
|
|
1102
|
+
async #destroy() {
|
|
1103
|
+
await this.#release();
|
|
1104
|
+
try {
|
|
1105
|
+
await this.#client.send("Target.detachFromTarget", { sessionId: this.#sessionId });
|
|
1106
|
+
} catch {}
|
|
1107
|
+
}
|
|
1108
|
+
async #close() {
|
|
1109
|
+
await this.#release();
|
|
1110
|
+
try {
|
|
1111
|
+
await this.#client.send("Target.closeTarget", { targetId: this.#targetId });
|
|
1112
|
+
} catch {}
|
|
1113
|
+
}
|
|
1114
|
+
#release() {
|
|
1115
|
+
const active = this.#releasing;
|
|
1116
|
+
if (active !== void 0) return active;
|
|
1117
|
+
const release = this.#releaseResources();
|
|
1118
|
+
this.#releasing = release;
|
|
1119
|
+
return release;
|
|
1120
|
+
}
|
|
1121
|
+
async #releaseResources() {
|
|
1122
|
+
this.#cancelLoad();
|
|
1123
|
+
await this.#codegenStart?.catch(() => void 0);
|
|
960
1124
|
if (this.#codegen !== void 0) {
|
|
961
1125
|
try {
|
|
962
1126
|
await this.#codegen.destroy();
|
|
@@ -964,9 +1128,6 @@ var BrowserPage = class {
|
|
|
964
1128
|
this.#codegen = void 0;
|
|
965
1129
|
}
|
|
966
1130
|
this.#client.unsubscribe("Target.targetDestroyed", this.#destroyHandler);
|
|
967
|
-
try {
|
|
968
|
-
await this.#client.send("Target.closeTarget", { targetId: this.#targetId });
|
|
969
|
-
} catch {}
|
|
970
1131
|
}
|
|
971
1132
|
#flattenFrameTree(node, out) {
|
|
972
1133
|
const frame = node["frame"];
|
|
@@ -1025,35 +1186,43 @@ var BrowserPage = class {
|
|
|
1025
1186
|
}
|
|
1026
1187
|
#waitForLoadEvent(condition, timeout) {
|
|
1027
1188
|
const eventName = condition === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1189
|
+
const deferred = Promise.withResolvers();
|
|
1190
|
+
this.#loadEvent = eventName;
|
|
1191
|
+
this.#loadResolve = deferred.resolve;
|
|
1192
|
+
this.#loadReject = deferred.reject;
|
|
1193
|
+
this.#loadTimer = setTimeout(() => {
|
|
1194
|
+
this.#rejectLoad(new BrowserError(`Navigation timeout after ${timeout}ms`));
|
|
1195
|
+
}, timeout);
|
|
1196
|
+
this.#client.subscribe(eventName, this.#loadHandler, this.#sessionId);
|
|
1197
|
+
return deferred.promise;
|
|
1198
|
+
}
|
|
1199
|
+
#resolveLoad() {
|
|
1200
|
+
const resolve = this.#loadResolve;
|
|
1201
|
+
this.#clearLoad();
|
|
1202
|
+
resolve?.();
|
|
1203
|
+
}
|
|
1204
|
+
#rejectLoad(error) {
|
|
1205
|
+
const reject = this.#loadReject;
|
|
1206
|
+
this.#clearLoad();
|
|
1207
|
+
reject?.(error);
|
|
1208
|
+
}
|
|
1209
|
+
#cancelLoad() {
|
|
1210
|
+
this.#rejectLoad(new BrowserError("Navigation cancelled"));
|
|
1211
|
+
}
|
|
1212
|
+
#clearLoad() {
|
|
1213
|
+
if (this.#loadTimer !== void 0) clearTimeout(this.#loadTimer);
|
|
1214
|
+
if (this.#loadEvent !== void 0) this.#client.unsubscribe(this.#loadEvent, this.#loadHandler, this.#sessionId);
|
|
1215
|
+
this.#loadEvent = void 0;
|
|
1216
|
+
this.#loadTimer = void 0;
|
|
1217
|
+
this.#loadResolve = void 0;
|
|
1218
|
+
this.#loadReject = void 0;
|
|
1219
|
+
}
|
|
1220
|
+
#requireString(value, field) {
|
|
1221
|
+
if (typeof value === "string") return value;
|
|
1222
|
+
throw new BrowserError(`${field} failed: no string value returned`);
|
|
1223
|
+
}
|
|
1224
|
+
#assertOpen() {
|
|
1225
|
+
if (this.#closed) throw new BrowserError("Browser page is closed");
|
|
1057
1226
|
}
|
|
1058
1227
|
};
|
|
1059
1228
|
//#endregion
|
|
@@ -1064,6 +1233,10 @@ var BrowserContext = class {
|
|
|
1064
1233
|
#viewport;
|
|
1065
1234
|
#writer;
|
|
1066
1235
|
#pages = /* @__PURE__ */ new Map();
|
|
1236
|
+
#creating = /* @__PURE__ */ new Set();
|
|
1237
|
+
#syncing;
|
|
1238
|
+
#shutdown;
|
|
1239
|
+
#closed = false;
|
|
1067
1240
|
constructor(client, id, viewport, writer) {
|
|
1068
1241
|
this.#client = client;
|
|
1069
1242
|
this.#id = id;
|
|
@@ -1075,78 +1248,165 @@ var BrowserContext = class {
|
|
|
1075
1248
|
}
|
|
1076
1249
|
page(index) {
|
|
1077
1250
|
const i = index ?? 0;
|
|
1078
|
-
const
|
|
1079
|
-
return i >= 0 && i <
|
|
1251
|
+
const pages = [...this.#pages.values()];
|
|
1252
|
+
return i >= 0 && i < pages.length ? pages[i] : void 0;
|
|
1080
1253
|
}
|
|
1081
1254
|
pages() {
|
|
1082
1255
|
return [...this.#pages.values()];
|
|
1083
1256
|
}
|
|
1084
1257
|
async create(options) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1258
|
+
if (this.#closed) throw new BrowserError("Browser context is closed");
|
|
1259
|
+
const attempt = this.#create(options);
|
|
1260
|
+
this.#creating.add(attempt);
|
|
1261
|
+
try {
|
|
1262
|
+
return await attempt;
|
|
1263
|
+
} finally {
|
|
1264
|
+
this.#creating.delete(attempt);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
async sync(targets) {
|
|
1268
|
+
while (this.#syncing !== void 0) await this.#syncing;
|
|
1269
|
+
if (this.#closed) throw new BrowserError("Browser context is closed");
|
|
1270
|
+
const attempt = this.#sync(targets);
|
|
1271
|
+
this.#syncing = attempt;
|
|
1272
|
+
try {
|
|
1273
|
+
await attempt;
|
|
1274
|
+
} finally {
|
|
1275
|
+
if (this.#syncing === attempt) this.#syncing = void 0;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
destroy() {
|
|
1279
|
+
const active = this.#shutdown;
|
|
1280
|
+
if (active !== void 0) return active;
|
|
1281
|
+
this.#closed = true;
|
|
1282
|
+
const shutdown = this.#destroyResources();
|
|
1283
|
+
this.#shutdown = shutdown;
|
|
1284
|
+
return shutdown;
|
|
1285
|
+
}
|
|
1286
|
+
close() {
|
|
1287
|
+
const active = this.#shutdown;
|
|
1288
|
+
if (active !== void 0) return active;
|
|
1289
|
+
this.#closed = true;
|
|
1290
|
+
const shutdown = this.#closeResources();
|
|
1291
|
+
this.#shutdown = shutdown;
|
|
1292
|
+
return shutdown;
|
|
1293
|
+
}
|
|
1294
|
+
async #create(options) {
|
|
1295
|
+
const result = await this.#client.send("Target.createTarget", {
|
|
1296
|
+
url: "about:blank",
|
|
1297
|
+
...this.#id === void 0 ? {} : { browserContextId: this.#id }
|
|
1298
|
+
});
|
|
1299
|
+
if (!(0, _orkestrel_contract.isRecord)(result) || !(0, _orkestrel_contract.isString)(result["targetId"])) throw new BrowserError("Failed to create new browser target");
|
|
1089
1300
|
const targetId = result["targetId"];
|
|
1090
|
-
|
|
1301
|
+
let page;
|
|
1302
|
+
try {
|
|
1303
|
+
const viewport = options?.viewport ?? this.#viewport;
|
|
1304
|
+
page = await this.#attach(targetId, options?.url ?? "about:blank", viewport);
|
|
1305
|
+
if (options?.url !== void 0 && options.url !== "about:blank") await page.navigate(options.url, { timeout: options.timeout });
|
|
1306
|
+
if (this.#closed) throw new BrowserError("Browser context closed during page creation");
|
|
1307
|
+
this.#pages.set(targetId, page);
|
|
1308
|
+
return page;
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
if (page !== void 0) await page.close();
|
|
1311
|
+
else await this.#closeTarget(targetId);
|
|
1312
|
+
throw error;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
async #sync(targets) {
|
|
1316
|
+
const pageTargets = targets.filter((target) => target.type === "page");
|
|
1317
|
+
const targetIds = new Set(pageTargets.map((target) => target.id));
|
|
1318
|
+
for (const [id, page] of this.#pages) {
|
|
1319
|
+
if (targetIds.has(id)) continue;
|
|
1320
|
+
await page.destroy();
|
|
1321
|
+
this.#pages.delete(id);
|
|
1322
|
+
}
|
|
1323
|
+
for (const target of pageTargets) {
|
|
1324
|
+
if (this.#closed || this.#pages.has(target.id)) continue;
|
|
1325
|
+
try {
|
|
1326
|
+
const page = await this.#reattach(target.id, target.url, this.#viewport);
|
|
1327
|
+
if (this.#closed) {
|
|
1328
|
+
await page.destroy();
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
this.#pages.set(target.id, page);
|
|
1332
|
+
} catch {}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
async #attach(targetId, url, viewport) {
|
|
1336
|
+
let sessionId;
|
|
1337
|
+
try {
|
|
1338
|
+
sessionId = await this.#openSession(targetId);
|
|
1339
|
+
await this.#enableSession(sessionId);
|
|
1340
|
+
if (viewport !== void 0) await this.#applyViewport(sessionId, viewport);
|
|
1341
|
+
return new BrowserPage(this.#client, targetId, sessionId, this.#writer, url);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
if (sessionId !== void 0) await this.#detachSession(sessionId);
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
async #reattach(targetId, url, viewport) {
|
|
1348
|
+
let sessionId;
|
|
1349
|
+
try {
|
|
1350
|
+
sessionId = await this.#openSession(targetId);
|
|
1351
|
+
await this.#enableSession(sessionId);
|
|
1352
|
+
if (viewport !== void 0) await this.#tryViewport(sessionId, viewport);
|
|
1353
|
+
return new BrowserPage(this.#client, targetId, sessionId, this.#writer, url);
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
if (sessionId !== void 0) await this.#detachSession(sessionId);
|
|
1356
|
+
throw error;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
async #destroyResources() {
|
|
1360
|
+
await this.#settle();
|
|
1361
|
+
for (const page of this.#pages.values()) await page.destroy();
|
|
1362
|
+
this.#pages.clear();
|
|
1363
|
+
}
|
|
1364
|
+
async #closeResources() {
|
|
1365
|
+
await this.#settle();
|
|
1366
|
+
for (const page of this.#pages.values()) await page.close();
|
|
1367
|
+
this.#pages.clear();
|
|
1368
|
+
if (this.#id === void 0) return;
|
|
1369
|
+
try {
|
|
1370
|
+
await this.#client.send("Target.disposeBrowserContext", { browserContextId: this.#id });
|
|
1371
|
+
} catch {}
|
|
1372
|
+
}
|
|
1373
|
+
async #settle() {
|
|
1374
|
+
await Promise.allSettled([...this.#creating]);
|
|
1375
|
+
await this.#syncing?.catch(() => void 0);
|
|
1376
|
+
}
|
|
1377
|
+
async #openSession(targetId) {
|
|
1378
|
+
const result = await this.#client.send("Target.attachToTarget", {
|
|
1091
1379
|
targetId,
|
|
1092
1380
|
flatten: true
|
|
1093
1381
|
});
|
|
1094
|
-
if (!(0, _orkestrel_contract.isRecord)(
|
|
1095
|
-
|
|
1382
|
+
if (!(0, _orkestrel_contract.isRecord)(result) || !(0, _orkestrel_contract.isString)(result["sessionId"])) throw new BrowserError("Failed to attach to browser target");
|
|
1383
|
+
return result["sessionId"];
|
|
1384
|
+
}
|
|
1385
|
+
async #enableSession(sessionId) {
|
|
1096
1386
|
await this.#client.send("Page.enable", void 0, sessionId);
|
|
1097
1387
|
await this.#client.send("Runtime.enable", void 0, sessionId);
|
|
1098
|
-
|
|
1099
|
-
|
|
1388
|
+
}
|
|
1389
|
+
async #applyViewport(sessionId, viewport) {
|
|
1390
|
+
await this.#client.send("Emulation.setDeviceMetricsOverride", {
|
|
1100
1391
|
width: viewport.width,
|
|
1101
1392
|
height: viewport.height,
|
|
1102
1393
|
deviceScaleFactor: 1,
|
|
1103
1394
|
mobile: false
|
|
1104
1395
|
}, sessionId);
|
|
1105
|
-
const page = new BrowserPage(this.#client, targetId, sessionId, this.#writer, options?.url);
|
|
1106
|
-
if (options?.url !== void 0 && options.url !== "about:blank") await page.navigate(options.url, { timeout: options.timeout });
|
|
1107
|
-
this.#pages.set(targetId, page);
|
|
1108
|
-
return page;
|
|
1109
1396
|
}
|
|
1110
|
-
async
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if (targetIds.has(id)) continue;
|
|
1115
|
-
try {
|
|
1116
|
-
await page.close();
|
|
1117
|
-
} catch {}
|
|
1118
|
-
this.#pages.delete(id);
|
|
1119
|
-
}
|
|
1120
|
-
for (const target of pageTargets) {
|
|
1121
|
-
if (this.#pages.has(target.id)) continue;
|
|
1122
|
-
try {
|
|
1123
|
-
const attachResult = await this.#client.send("Target.attachToTarget", {
|
|
1124
|
-
targetId: target.id,
|
|
1125
|
-
flatten: true
|
|
1126
|
-
});
|
|
1127
|
-
if (!(0, _orkestrel_contract.isRecord)(attachResult) || !(0, _orkestrel_contract.isString)(attachResult["sessionId"])) continue;
|
|
1128
|
-
const sessionId = attachResult["sessionId"];
|
|
1129
|
-
await this.#client.send("Page.enable", void 0, sessionId);
|
|
1130
|
-
await this.#client.send("Runtime.enable", void 0, sessionId);
|
|
1131
|
-
if (this.#viewport !== void 0) try {
|
|
1132
|
-
await this.#client.send("Emulation.setDeviceMetricsOverride", {
|
|
1133
|
-
width: this.#viewport.width,
|
|
1134
|
-
height: this.#viewport.height,
|
|
1135
|
-
deviceScaleFactor: 1,
|
|
1136
|
-
mobile: false
|
|
1137
|
-
}, sessionId);
|
|
1138
|
-
} catch {}
|
|
1139
|
-
this.#pages.set(target.id, new BrowserPage(this.#client, target.id, sessionId, this.#writer, target.url));
|
|
1140
|
-
} catch {}
|
|
1141
|
-
}
|
|
1397
|
+
async #tryViewport(sessionId, viewport) {
|
|
1398
|
+
try {
|
|
1399
|
+
await this.#applyViewport(sessionId, viewport);
|
|
1400
|
+
} catch {}
|
|
1142
1401
|
}
|
|
1143
|
-
async
|
|
1144
|
-
|
|
1145
|
-
await
|
|
1402
|
+
async #closeTarget(targetId) {
|
|
1403
|
+
try {
|
|
1404
|
+
await this.#client.send("Target.closeTarget", { targetId });
|
|
1146
1405
|
} catch {}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1406
|
+
}
|
|
1407
|
+
async #detachSession(sessionId) {
|
|
1408
|
+
try {
|
|
1409
|
+
await this.#client.send("Target.detachFromTarget", { sessionId });
|
|
1150
1410
|
} catch {}
|
|
1151
1411
|
}
|
|
1152
1412
|
};
|