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