@letsscrapedata/controller 0.0.56 → 0.0.58

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/index.js CHANGED
@@ -423,6 +423,9 @@ var PlaywrightPage = class extends EventEmitter {
423
423
  const localStorageStr = await page.evaluate(() => JSON.stringify(window.localStorage));
424
424
  const localStorageObj = JSON.parse(localStorageStr);
425
425
  const localStorageItems = Object.keys(localStorageObj).map((name) => ({ name, value: localStorageObj[name] }));
426
+ if (localStorageItems.length === 0) {
427
+ return [];
428
+ }
426
429
  const url = new URL(page.url());
427
430
  return [{ origin: url.origin, localStorage: localStorageItems }];
428
431
  }
@@ -2207,6 +2210,9 @@ var PuppeteerPage = class extends EventEmitter4 {
2207
2210
  const localStorageStr = await page.evaluate(() => JSON.stringify(window.localStorage));
2208
2211
  const localStorageObj = JSON.parse(localStorageStr);
2209
2212
  const localStorageItems = Object.keys(localStorageObj).map((name) => ({ name, value: localStorageObj[name] }));
2213
+ if (localStorageItems.length === 0) {
2214
+ return [];
2215
+ }
2210
2216
  const url = new URL(page.url());
2211
2217
  return [{ origin: url.origin, localStorage: localStorageItems }];
2212
2218
  }
@@ -3960,6 +3966,7 @@ import os from "os";
3960
3966
  import puppeteer from "puppeteer";
3961
3967
  import playwright, { request as apiRequestInPlaywright } from "playwright";
3962
3968
  import patchright from "patchright";
3969
+ import { Camoufox, launchServer } from "camoufox-js";
3963
3970
 
3964
3971
  // src/patchright/browser.ts
3965
3972
  import EventEmitter10 from "events";
@@ -4004,12 +4011,12 @@ var PatchrightElement = class _PatchrightElement {
4004
4011
  return {};
4005
4012
  }
4006
4013
  }
4007
- async evaluate(func, args) {
4014
+ async evaluate(func, args, isolated = true) {
4008
4015
  try {
4009
4016
  const frame = this.#frame;
4010
4017
  ;
4011
4018
  if (typeof frame.parentFrame === "function") {
4012
- return await frame.evaluate(func, args);
4019
+ return await frame.evaluate(func, args, !!isolated);
4013
4020
  } else {
4014
4021
  const locator = this.#frame.owner();
4015
4022
  return await locator.evaluate(func, args);
@@ -4339,6 +4346,9 @@ var PatchrightPage = class extends EventEmitter8 {
4339
4346
  const localStorageStr = await page.evaluate(() => JSON.stringify(window.localStorage));
4340
4347
  const localStorageObj = JSON.parse(localStorageStr);
4341
4348
  const localStorageItems = Object.keys(localStorageObj).map((name) => ({ name, value: localStorageObj[name] }));
4349
+ if (localStorageItems.length === 0) {
4350
+ return [];
4351
+ }
4342
4352
  const url = new URL(page.url());
4343
4353
  return [{ origin: url.origin, localStorage: localStorageItems }];
4344
4354
  }
@@ -4662,18 +4672,17 @@ var PatchrightPage = class extends EventEmitter8 {
4662
4672
  const height = await this.#page.evaluate(() => document.documentElement.scrollHeight);
4663
4673
  return height;
4664
4674
  }
4665
- async evaluate(func, args) {
4675
+ async evaluate(func, args, isolated = true) {
4666
4676
  if (!this.#page) {
4667
4677
  throw new Error("No valid page");
4668
4678
  }
4669
- return this.#page.evaluate(func, args);
4679
+ return this.#page.evaluate(func, args, !!isolated);
4670
4680
  }
4671
- async exposeFunction(name, callbackFunction) {
4681
+ async exposeFunction(_name, _callbackFunction) {
4672
4682
  if (!this.#page) {
4673
4683
  throw new Error("No valid page");
4674
4684
  }
4675
- await this.#page.exposeFunction(name, callbackFunction);
4676
- return;
4685
+ throw new Error(`Patchright does not support page.exposeFunction to prevent detection`);
4677
4686
  }
4678
4687
  async findElement(selectorOrXpath, iframeOptions = []) {
4679
4688
  if (!this.#page) {
@@ -5763,147 +5772,1952 @@ var PatchrightBrowser = class _PatchrightBrowser extends EventEmitter10 {
5763
5772
  };
5764
5773
 
5765
5774
  // src/controller/controller.ts
5766
- import { getPidsListeningOnPort, unreachable as unreachable7 } from "@letsscrapedata/utils";
5767
- var LsdBrowserController = class _LsdBrowserController {
5768
- static #forbidConstructor = false;
5769
- #puppeteer;
5770
- #playwrightBrowserTypes;
5771
- #patchrightBrowserTypes;
5772
- #nextBrowserIdx;
5773
- /**
5774
- * Possible values are 'aix', 'darwin', 'freebsd','linux', 'openbsd', 'sunos', and 'win32'.
5775
- */
5776
- #osPlatform;
5777
- constructor() {
5778
- if (_LsdBrowserController.#forbidConstructor) {
5779
- throw new Error("Only one LsdBrowserController instance can be created!");
5775
+ import { getPidsListeningOnPort, unreachable as unreachable9 } from "@letsscrapedata/utils";
5776
+
5777
+ // src/camoufox/browser.ts
5778
+ import EventEmitter13 from "events";
5779
+ import { getCurrentUnixTime as getCurrentUnixTime12, getPerformanceOfPidTree as getPerformanceOfPidTree4 } from "@letsscrapedata/utils";
5780
+
5781
+ // src/camoufox/context.ts
5782
+ import EventEmitter12 from "events";
5783
+ import { getCurrentUnixTime as getCurrentUnixTime11, sleep as sleep4 } from "@letsscrapedata/utils";
5784
+
5785
+ // src/camoufox/page.ts
5786
+ import EventEmitter11 from "events";
5787
+ import { getCurrentUnixTime as getCurrentUnixTime10, unreachable as unreachable8 } from "@letsscrapedata/utils";
5788
+
5789
+ // src/camoufox/element.ts
5790
+ import { unreachable as unreachable7 } from "@letsscrapedata/utils";
5791
+ var CamoufoxElement = class _CamoufoxElement {
5792
+ #frame;
5793
+ #locator;
5794
+ constructor(locator, frame) {
5795
+ if (!frame.locator || !locator.click) {
5796
+ throw new Error("Invalid paras in new CamoufoxElement");
5780
5797
  }
5781
- this.#puppeteer = puppeteer;
5782
- this.#playwrightBrowserTypes = {
5783
- chromium: playwright.chromium,
5784
- firefox: playwright.firefox,
5785
- webkit: playwright.webkit
5786
- };
5787
- this.#patchrightBrowserTypes = {
5788
- chromium: patchright.chromium,
5789
- firefox: patchright.firefox,
5790
- webkit: patchright.webkit
5791
- };
5792
- this.#osPlatform = os.platform();
5793
- this.#nextBrowserIdx = 1;
5794
- _LsdBrowserController.#forbidConstructor = true;
5798
+ this.#frame = frame;
5799
+ this.#locator = locator;
5795
5800
  }
5796
- #playwrightBrowserType(browserType, connectFlag = false) {
5797
- if (browserType === "chromium") {
5798
- return this.#playwrightBrowserTypes.chromium;
5799
- } else if (connectFlag) {
5800
- throw new Error(`playwright only can connect to chromium browser, not support ${browserType} browser`);
5801
- } else if (browserType === "firefox") {
5802
- return this.#playwrightBrowserTypes.firefox;
5803
- } else if (browserType === "webkit") {
5804
- return this.#playwrightBrowserTypes.webkit;
5805
- } else {
5806
- throw new Error(`Invalid playwright browserType ${browserType}`);
5807
- }
5801
+ async attribute(attributeName) {
5802
+ const attributeValue = await this.#locator.getAttribute(attributeName);
5803
+ return attributeValue ? attributeValue : "";
5808
5804
  }
5809
- #patchrightBrowserType(browserType, connectFlag = false) {
5810
- if (browserType === "chromium") {
5811
- return this.#patchrightBrowserTypes.chromium;
5812
- } else if (connectFlag) {
5813
- throw new Error(`patchright only can connect to chromium browser, not support ${browserType} browser`);
5814
- } else if (browserType === "firefox") {
5815
- return this.#patchrightBrowserTypes.firefox;
5816
- } else if (browserType === "webkit") {
5817
- return this.#patchrightBrowserTypes.webkit;
5818
- } else {
5819
- throw new Error(`Invalid patchright browserType ${browserType}`);
5805
+ async attributeNames() {
5806
+ const names = await this.#locator.evaluate((node) => node.getAttributeNames());
5807
+ return names;
5808
+ }
5809
+ async boundingBox() {
5810
+ return await this.#locator.boundingBox();
5811
+ }
5812
+ async dataset() {
5813
+ try {
5814
+ const dataset = await this.#locator.evaluate((node) => node.dataset);
5815
+ return dataset;
5816
+ } catch (err) {
5817
+ return {};
5820
5818
  }
5821
5819
  }
5822
- #puppeteerProduct(browserType) {
5823
- if (browserType === "chromium") {
5824
- return "chrome";
5825
- } else {
5826
- throw new Error(`Invalid puppeteer product ${browserType}`);
5820
+ async evaluate(func, args, isolated = true) {
5821
+ try {
5822
+ const frame = this.#frame;
5823
+ ;
5824
+ if (typeof frame.parentFrame === "function") {
5825
+ if (typeof func === "string" && !isolated) {
5826
+ return await frame.evaluate(`mw:${func}`, args);
5827
+ } else {
5828
+ return await frame.evaluate(func, args);
5829
+ }
5830
+ } else {
5831
+ const locator = this.#frame.owner();
5832
+ if (typeof func === "string" && !isolated) {
5833
+ return await locator.evaluate(`mw:${func}`, args);
5834
+ } else {
5835
+ return await locator.evaluate(func, args);
5836
+ }
5837
+ }
5838
+ } catch (err) {
5839
+ logerr(err);
5840
+ return "";
5827
5841
  }
5828
5842
  }
5829
- setBrowserPlugin(browserControllerType, browserType, plugin) {
5830
- if (browserControllerType === "puppeteer") {
5831
- if (!PuppeteerBrowser.doesSupport(browserType)) {
5832
- throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
5843
+ /*
5844
+ async #getChildFrame(parentFrame: Frame, iframeOption: IframeOption): Promise<Frame | null> {
5845
+ if (!parentFrame) {
5846
+ throw new Error("Invalid parent frame");
5833
5847
  }
5834
- this.#puppeteer = plugin;
5835
- } else if (browserControllerType === "playwright") {
5836
- if (!PlaywrightBrowser.doesSupport(browserType)) {
5837
- throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
5848
+
5849
+ let { src = "" } = iframeOption;
5850
+ if (!src) {
5851
+ throw new Error("Invalid src in IframeOption");
5838
5852
  }
5839
- switch (browserType) {
5840
- case "chromium":
5841
- case "firefox":
5842
- case "webkit":
5843
- this.#playwrightBrowserTypes[browserType] = plugin;
5844
- break;
5845
- default:
5846
- unreachable7(browserType);
5853
+
5854
+ // src: use childFrames()
5855
+ const childFrames = parentFrame.childFrames();
5856
+ for (const childFrame of childFrames) {
5857
+ const url = childFrame.url();
5858
+ if (typeof src === "string") {
5859
+ // src: string
5860
+ if (url.startsWith(src)) {
5861
+ return childFrame;
5862
+ } else if (url.toLowerCase().startsWith(src)) {
5863
+ return childFrame;
5864
+ }
5865
+ } else {
5866
+ // src: RegExp
5867
+ if (url.match(src)) {
5868
+ return childFrame;
5869
+ }
5870
+ }
5847
5871
  }
5848
- } else if (browserControllerType === "patchright") {
5849
- if (!PatchrightBrowser.doesSupport(browserType)) {
5850
- throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
5872
+
5873
+ return null;
5874
+ }
5875
+ */
5876
+ async #getChildFrameLocator(parent, iframeOption) {
5877
+ return parent.frameLocator(getIframeSelector(iframeOption));
5878
+ }
5879
+ async #getDescendantFrame(parent, iframeOptions) {
5880
+ try {
5881
+ if (iframeOptions.length <= 0) {
5882
+ return null;
5851
5883
  }
5852
- switch (browserType) {
5853
- case "chromium":
5854
- case "firefox":
5855
- case "webkit":
5856
- this.#patchrightBrowserTypes[browserType] = plugin;
5857
- break;
5858
- default:
5859
- unreachable7(browserType);
5884
+ let frameLocator = parent.frameLocator(getIframeSelector(iframeOptions[0]));
5885
+ for (const iframeOption of iframeOptions.slice(1)) {
5886
+ if (!frameLocator) {
5887
+ return null;
5888
+ }
5889
+ frameLocator = await this.#getChildFrameLocator(frameLocator, iframeOption);
5860
5890
  }
5861
- } else {
5862
- unreachable7(browserControllerType);
5891
+ return frameLocator;
5892
+ } catch (err) {
5893
+ throw new Error(`No child iframe: ${JSON.stringify(iframeOptions)}`);
5863
5894
  }
5864
- return true;
5865
5895
  }
5866
- async launch(browserControllerType, browserType, options) {
5867
- let {
5868
- closeFreePagesIntervalSeconds = 300,
5869
- maxBrowserContextsPerBrowser = 10,
5870
- maxPagesPerBrowserContext = 20,
5871
- maxPageFreeSeconds = 900,
5872
- maxViewportOfNewPage = true,
5873
- proxy = null,
5874
- timeout = 3e4,
5875
- args = [],
5876
- executablePath = "",
5877
- maxWindowSize = true,
5878
- headless = true,
5879
- minBrowserContexts = 1,
5880
- // incognito
5881
- proxyPerBrowserContext = false,
5882
- userDataDir = "",
5883
- userAgent = ""
5884
- } = options ? options : {};
5885
- let browserPid = 0;
5886
- const incognito = typeof options?.incognito === "boolean" ? options.incognito : browserControllerType === "puppeteer" ? false : true;
5887
- const actOptions = { closeFreePagesIntervalSeconds, maxBrowserContextsPerBrowser, maxPagesPerBrowserContext, maxPageFreeSeconds, maxViewportOfNewPage, proxy, timeout, args, executablePath, maxWindowSize, headless, minBrowserContexts, incognito, proxyPerBrowserContext, userDataDir, userAgent };
5888
- let idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--incoginto"));
5889
- if (idx >= 0) {
5890
- logwarn(`Please use options.incognito instead when launching new browser.`);
5891
- args.splice(idx, 1);
5896
+ async #findElementHandles(selector, absolute = false, iframeOptions = []) {
5897
+ let parent = absolute ? this.#frame : this.#locator;
5898
+ let frame = this.#frame;
5899
+ const retObj = { frame, locators: [] };
5900
+ if (iframeOptions.length > 0) {
5901
+ const childFrame = await this.#getDescendantFrame(frame, iframeOptions);
5902
+ if (!childFrame) {
5903
+ return retObj;
5904
+ }
5905
+ retObj.frame = childFrame;
5906
+ parent = childFrame;
5892
5907
  }
5893
- idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--proxy-server"));
5894
- if (idx >= 0) {
5895
- logwarn(`Please use options.proxy instead when launching new browser.`);
5896
- args.splice(idx, 1);
5908
+ try {
5909
+ let locators = [];
5910
+ if (selector.startsWith("./") || selector.startsWith("/") || selector.startsWith("..")) {
5911
+ locators = await parent.locator(`xpath=${selector}`).all();
5912
+ } else {
5913
+ if (selector !== ".") {
5914
+ locators = await parent.locator(selector).all();
5915
+ } else {
5916
+ locators = [this.#locator];
5917
+ }
5918
+ }
5919
+ retObj.locators = locators;
5920
+ return retObj;
5921
+ } catch (err) {
5922
+ loginfo(err);
5923
+ return retObj;
5897
5924
  }
5898
- idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--user-data-dir"));
5899
- if (idx >= 0) {
5900
- logwarn(`Please use options.userDataDir instead when launching new browser.`);
5901
- args.splice(idx, 1);
5925
+ }
5926
+ async findElement(selectorOrXpath, iframeOptions = [], absolute = false) {
5927
+ const selectors = typeof selectorOrXpath === "string" ? [selectorOrXpath] : selectorOrXpath;
5928
+ if (!Array.isArray(selectors)) {
5929
+ throw new Error(`Invalid selectorOrXpath ${selectorOrXpath} in findElement`);
5902
5930
  }
5903
- idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--start-maximized"));
5904
- if (idx >= 0) {
5905
- logwarn(`Please use options.maxWindowSize instead when launching new browser.`);
5906
- args.splice(idx, 1);
5931
+ for (const selector of selectors) {
5932
+ const { frame, locators } = await this.#findElementHandles(selector, absolute, iframeOptions);
5933
+ if (locators.length > 0) {
5934
+ const playwrightElement = new _CamoufoxElement(locators[0], frame);
5935
+ return playwrightElement;
5936
+ }
5937
+ }
5938
+ return null;
5939
+ }
5940
+ async findElements(selectorOrXpath, iframeOptions = [], absolute = false) {
5941
+ const selectors = typeof selectorOrXpath === "string" ? [selectorOrXpath] : selectorOrXpath;
5942
+ if (!Array.isArray(selectors)) {
5943
+ throw new Error(`Invalid selectorOrXpath ${selectorOrXpath} in findElements`);
5944
+ }
5945
+ for (const selector of selectors) {
5946
+ const { frame, locators } = await this.#findElementHandles(selector, absolute, iframeOptions);
5947
+ if (locators.length > 0) {
5948
+ const playwrightElements = locators.map((locator) => new _CamoufoxElement(locator, frame));
5949
+ return playwrightElements;
5950
+ }
5951
+ }
5952
+ return [];
5953
+ }
5954
+ async hasAttribute(attributeName) {
5955
+ const hasFlag = await this.#locator.evaluate((node, attr) => node.hasAttribute(attr), attributeName);
5956
+ return hasFlag;
5957
+ }
5958
+ async innerHtml() {
5959
+ const html = await this.#locator.innerHTML();
5960
+ return html;
5961
+ }
5962
+ async innerText(onlyChild = false) {
5963
+ let text = "";
5964
+ if (onlyChild) {
5965
+ text = await this.#locator.evaluate((node) => {
5966
+ let child = node.firstChild;
5967
+ let texts = [];
5968
+ while (child) {
5969
+ if (child.nodeType == 3) {
5970
+ texts.push(child.data);
5971
+ }
5972
+ child = child.nextSibling;
5973
+ }
5974
+ return texts.join(" ");
5975
+ });
5976
+ } else {
5977
+ text = await this.#locator.innerText();
5978
+ }
5979
+ return text;
5980
+ }
5981
+ async outerHtml() {
5982
+ const html = await this.#locator.evaluate((node) => node.outerHTML);
5983
+ return html;
5984
+ }
5985
+ async textContent() {
5986
+ const text = await this.#locator.textContent();
5987
+ return text ? text : "";
5988
+ }
5989
+ async click(options = {}) {
5990
+ const { button, clickCount: count, delay, position: offset, clickType = "click" } = options;
5991
+ const actOptions = { button, count, delay, offset };
5992
+ if (clickType === "click") {
5993
+ await this.#locator.click(actOptions);
5994
+ } else if (clickType === "evaluate") {
5995
+ await this.#locator.evaluate(async (ev) => await ev.click());
5996
+ } else {
5997
+ unreachable7(clickType);
5998
+ }
5999
+ return true;
6000
+ }
6001
+ async focus() {
6002
+ await this.#locator.focus();
6003
+ return true;
6004
+ }
6005
+ async hover() {
6006
+ await this.#locator.hover();
6007
+ return true;
6008
+ }
6009
+ async input(value, options = {}) {
6010
+ const { delay = 0, replace = false, enter = false } = options;
6011
+ if (replace) {
6012
+ await this.#locator.click({ button: "left", clickCount: 3 });
6013
+ }
6014
+ if (delay > 0) {
6015
+ await this.#locator.fill(value);
6016
+ } else {
6017
+ await this.#locator.fill(value);
6018
+ }
6019
+ if (enter) {
6020
+ await this.#locator.press("Enter");
6021
+ }
6022
+ return true;
6023
+ }
6024
+ async press(key, options = {}) {
6025
+ await this.#locator.press(key, options);
6026
+ return true;
6027
+ }
6028
+ async screenshot(options) {
6029
+ return await this.#locator.screenshot(options);
6030
+ }
6031
+ async scrollIntoView() {
6032
+ await this.#locator.scrollIntoViewIfNeeded();
6033
+ return true;
6034
+ }
6035
+ async select(options) {
6036
+ const { type, values = [], labels = [], indexes = [] } = options;
6037
+ switch (type) {
6038
+ case "value":
6039
+ if (values.length > 0) {
6040
+ await this.#locator.selectOption(values);
6041
+ }
6042
+ break;
6043
+ case "label":
6044
+ if (labels.length > 0) {
6045
+ await this.#locator.selectOption(labels.map((label) => {
6046
+ return { label };
6047
+ }));
6048
+ }
6049
+ break;
6050
+ case "index":
6051
+ if (indexes.length > 0) {
6052
+ const indexValues = await this.#locator.evaluate(
6053
+ (node, indexes2) => {
6054
+ const options2 = node.options;
6055
+ const len = options2.length;
6056
+ const vals = [];
6057
+ for (const index of indexes2.filter((i) => i >= 0 && i < len)) {
6058
+ vals.push(options2[index].value);
6059
+ }
6060
+ return vals;
6061
+ },
6062
+ indexes
6063
+ );
6064
+ if (indexValues.length > 0) {
6065
+ await this.#locator.selectOption(indexValues);
6066
+ }
6067
+ }
6068
+ break;
6069
+ default:
6070
+ unreachable7(type);
6071
+ }
6072
+ return true;
6073
+ }
6074
+ async setAttribute(attributeName, newValue) {
6075
+ await this.#locator.evaluate((node, argvs) => {
6076
+ node.setAttribute(argvs[0], argvs[1]);
6077
+ }, [attributeName, newValue]);
6078
+ return true;
6079
+ }
6080
+ _origElement() {
6081
+ return this.#locator;
6082
+ }
6083
+ };
6084
+
6085
+ // src/camoufox/page.ts
6086
+ var CamoufoxPage = class extends EventEmitter11 {
6087
+ #lsdBrowserContext;
6088
+ #page;
6089
+ #status;
6090
+ #pageId;
6091
+ #closeWhenFree;
6092
+ #resquestInterceptionOptions;
6093
+ #responseInterceptionOptions;
6094
+ #client;
6095
+ #responseCb;
6096
+ #hasValidUrl(page) {
6097
+ const url = page.url();
6098
+ return url.toLowerCase().startsWith("http");
6099
+ }
6100
+ async #clearCookies(page) {
6101
+ if (!this.#hasValidUrl(page)) {
6102
+ throw new Error("Please open related url before clearing cookies");
6103
+ }
6104
+ const browserContext = this.#lsdBrowserContext._origBrowserContext();
6105
+ if (!browserContext) {
6106
+ throw new Error(`Invalid LsdBrowserContext`);
6107
+ }
6108
+ const cookieItems = await this.#getCookies(page);
6109
+ const domainSet = new Set(cookieItems.map((c) => c.domain));
6110
+ if (domainSet.size !== 1) {
6111
+ logwarn(`Domains in clearCookies: ${Array.from(domainSet.values())}`);
6112
+ }
6113
+ for (const domain of domainSet.values()) {
6114
+ await browserContext.clearCookies({ domain });
6115
+ }
6116
+ return true;
6117
+ }
6118
+ async #getCookies(page) {
6119
+ if (!this.#hasValidUrl(page)) {
6120
+ throw new Error("Please open related url before getting cookies");
6121
+ }
6122
+ const browserContext = this.#lsdBrowserContext._origBrowserContext();
6123
+ if (!browserContext) {
6124
+ throw new Error(`Invalid LsdBrowserContext`);
6125
+ }
6126
+ const url = page.url();
6127
+ const origCookies = await browserContext.cookies(url);
6128
+ const cookies = origCookies.map((origCookie) => {
6129
+ const { name, value, domain, path, expires, httpOnly, secure, sameSite = "Lax" } = origCookie;
6130
+ return { name, value, domain, path, expires, httpOnly, secure, sameSite };
6131
+ });
6132
+ return cookies;
6133
+ }
6134
+ async #setCookies(page, cookies) {
6135
+ if (!page) {
6136
+ throw new Error("No valid page");
6137
+ }
6138
+ if (Array.isArray(cookies) && cookies.length > 0 && cookies.every((c) => typeof c.name === "string")) {
6139
+ const browserContext = this.#lsdBrowserContext._origBrowserContext();
6140
+ if (!browserContext) {
6141
+ throw new Error(`Invalid LsdBrowserContext`);
6142
+ }
6143
+ await browserContext.addCookies(cookies);
6144
+ return true;
6145
+ } else {
6146
+ return false;
6147
+ }
6148
+ }
6149
+ async #clearLocalStorage(page) {
6150
+ if (!this.#hasValidUrl(page)) {
6151
+ throw new Error("Please open related url before clearing localStorage");
6152
+ }
6153
+ await page.evaluate(() => window.localStorage.clear());
6154
+ return true;
6155
+ }
6156
+ async #getLocalStorage(page) {
6157
+ if (!this.#hasValidUrl(page)) {
6158
+ throw new Error("Please open related url before getting localStorage");
6159
+ }
6160
+ const localStorageStr = await page.evaluate(() => JSON.stringify(window.localStorage));
6161
+ const localStorageObj = JSON.parse(localStorageStr);
6162
+ const localStorageItems = Object.keys(localStorageObj).map((name) => ({ name, value: localStorageObj[name] }));
6163
+ if (localStorageItems.length === 0) {
6164
+ return [];
6165
+ }
6166
+ const url = new URL(page.url());
6167
+ return [{ origin: url.origin, localStorage: localStorageItems }];
6168
+ }
6169
+ async #setLocalStorage(page, localStorageItems) {
6170
+ if (!this.#hasValidUrl(page)) {
6171
+ throw new Error("Please open related url before setting localStorage");
6172
+ }
6173
+ await page.evaluate((items) => {
6174
+ for (const item of items) {
6175
+ window.localStorage.setItem(item.name, item.value);
6176
+ }
6177
+ }, localStorageItems);
6178
+ return true;
6179
+ }
6180
+ async #clearIndexedDB(page) {
6181
+ if (!this.#hasValidUrl(page)) {
6182
+ throw new Error("Please open related url before clearing indexedDB");
6183
+ }
6184
+ await page.evaluate(async () => {
6185
+ for (const db of await indexedDB.databases?.() || []) {
6186
+ if (db.name)
6187
+ indexedDB.deleteDatabase(db.name);
6188
+ }
6189
+ });
6190
+ return true;
6191
+ }
6192
+ /*
6193
+ async #getChildFrame(parentFrame: Frame, iframeOption: IframeOption): Promise<Frame | null> {
6194
+ if (!parentFrame) {
6195
+ throw new Error("Invalid parent frame");
6196
+ }
6197
+
6198
+ let { src = "" } = iframeOption;
6199
+ if (!src) {
6200
+ throw new Error("Invalid src in IframeOption");
6201
+ }
6202
+
6203
+ // src: use childFrames()
6204
+ const childFrames = parentFrame.childFrames();
6205
+ for (const childFrame of childFrames) {
6206
+ const url = childFrame.url();
6207
+ if (typeof src === "string") {
6208
+ // src: string
6209
+ if (url.startsWith(src)) {
6210
+ return childFrame;
6211
+ } else if (url.toLowerCase().startsWith(src)) {
6212
+ return childFrame;
6213
+ }
6214
+ } else {
6215
+ // src: RegExp
6216
+ if (url.match(src)) {
6217
+ return childFrame;
6218
+ }
6219
+ }
6220
+ }
6221
+
6222
+ return null;
6223
+ }
6224
+ */
6225
+ async #findDescendantFrame(src, id) {
6226
+ if (!this.#page) {
6227
+ throw new Error("No valid page");
6228
+ }
6229
+ const frames = this.#page.frames();
6230
+ for (const frame of frames) {
6231
+ const url = frame.url();
6232
+ if (typeof src === "string" && src) {
6233
+ if (url.startsWith(src)) {
6234
+ return frame;
6235
+ } else if (url.toLowerCase().startsWith(src)) {
6236
+ return frame;
6237
+ }
6238
+ } else if (src instanceof RegExp) {
6239
+ if (url.match(src)) {
6240
+ return frame;
6241
+ }
6242
+ } else if (id) {
6243
+ const element = await frame.frameElement();
6244
+ if (element) {
6245
+ const frameId = await frame.evaluate(([ele, attr]) => ele.getAttribute(attr), [element, "id"]);
6246
+ if (frameId === id) {
6247
+ return frame;
6248
+ }
6249
+ }
6250
+ }
6251
+ }
6252
+ return null;
6253
+ }
6254
+ async #getChildFrameLocator(parent, iframeOption) {
6255
+ return parent.frameLocator(getIframeSelector(iframeOption));
6256
+ }
6257
+ async #getDescendantFrame(mainFrame, iframeOptions) {
6258
+ try {
6259
+ if (iframeOptions.length <= 0) {
6260
+ return null;
6261
+ }
6262
+ if (iframeOptions.length === 1 && !iframeOptions[0].selector) {
6263
+ const { src = "", id = "" } = iframeOptions[0];
6264
+ const frame = await this.#findDescendantFrame(src, id);
6265
+ return frame;
6266
+ } else {
6267
+ let frameLocator = mainFrame.frameLocator(getIframeSelector(iframeOptions[0]));
6268
+ for (const iframeOption of iframeOptions.slice(1)) {
6269
+ if (!frameLocator) {
6270
+ return null;
6271
+ }
6272
+ frameLocator = await this.#getChildFrameLocator(frameLocator, iframeOption);
6273
+ }
6274
+ return frameLocator;
6275
+ }
6276
+ } catch (err) {
6277
+ throw new Error(`No child iframe: ${JSON.stringify(iframeOptions)}`);
6278
+ }
6279
+ }
6280
+ async #findElementHandles(selector, iframeOptions = []) {
6281
+ if (!this.#page) {
6282
+ throw new Error("No valid page");
6283
+ }
6284
+ let frame = this.#page.mainFrame();
6285
+ const retObj = { frame, locators: [] };
6286
+ if (iframeOptions.length > 0) {
6287
+ frame = await this.#getDescendantFrame(frame, iframeOptions);
6288
+ if (!frame) {
6289
+ return retObj;
6290
+ }
6291
+ retObj.frame = frame;
6292
+ }
6293
+ try {
6294
+ let locators = [];
6295
+ if (selector.startsWith("./") || selector.startsWith("/") || selector.startsWith("..")) {
6296
+ locators = await frame.locator(`xpath=${selector}`).all();
6297
+ } else {
6298
+ if (selector !== ".") {
6299
+ locators = await frame.locator(selector).all();
6300
+ } else {
6301
+ throw new Error("Cannot use selector '.' on page");
6302
+ }
6303
+ }
6304
+ retObj.locators = locators;
6305
+ return retObj;
6306
+ } catch (err) {
6307
+ loginfo(err);
6308
+ return retObj;
6309
+ }
6310
+ }
6311
+ #addPageOn() {
6312
+ if (!this.#page) {
6313
+ throw new Error("No valid page");
6314
+ }
6315
+ const page = this.#page;
6316
+ const pageId = this.#pageId;
6317
+ page.on("close", async () => {
6318
+ loginfo(`##browser ${pageId} closed`);
6319
+ if (!page.pageInfo) {
6320
+ logerr(`Logic error in page.on("close")`);
6321
+ }
6322
+ this.emit("pageClose");
6323
+ this.#lsdBrowserContext.emit("pageClose", this);
6324
+ });
6325
+ page.on("popup", (p) => {
6326
+ if (p) {
6327
+ let evtData = null;
6328
+ const pageInfo = p.pageInfo;
6329
+ let popupPageId = "page";
6330
+ if (pageInfo) {
6331
+ const { browserIdx, browserContextIdx, pageIdx } = pageInfo;
6332
+ popupPageId = `page-${browserIdx}-${browserContextIdx}-${pageIdx}`;
6333
+ pageInfo.openType = "popup";
6334
+ evtData = this.browserContext().page(pageIdx);
6335
+ if (evtData && page.pageInfo?.taskId) {
6336
+ pageInfo.relatedId = page.pageInfo.taskId;
6337
+ }
6338
+ } else {
6339
+ logerr(`##browser ${pageId} has popup without page.pageInfo`);
6340
+ }
6341
+ loginfo(`##browser ${pageId} has popup ${popupPageId}`);
6342
+ this.emit("pagePopup", evtData);
6343
+ } else {
6344
+ logerr(`##browser ${pageId} has popup page with null page`);
6345
+ }
6346
+ });
6347
+ }
6348
+ constructor(browserContext, page, pageInfo) {
6349
+ if (!browserContext.pages || !page?.goto) {
6350
+ throw new Error("Invalid paras in new LsdPage");
6351
+ }
6352
+ super();
6353
+ this.#lsdBrowserContext = browserContext;
6354
+ this.#page = page;
6355
+ this.#status = "free";
6356
+ const currentTime = getCurrentUnixTime10();
6357
+ const { browserIdx = 0, browserContextIdx = 0, pageIdx = 0, openType = "other", openTime = currentTime, lastStatusUpdateTime = currentTime, taskId = 0, relatedId = 0, misc = {} } = pageInfo ? pageInfo : {};
6358
+ this.#page.pageInfo = { browserIdx, browserContextIdx, pageIdx, openType, openTime, lastStatusUpdateTime, taskId, relatedId, misc };
6359
+ this.#pageId = `page-${browserIdx}-${browserContextIdx}-${pageIdx}`;
6360
+ this.#closeWhenFree = false;
6361
+ this.#resquestInterceptionOptions = [];
6362
+ this.#responseInterceptionOptions = [];
6363
+ this.#client = null;
6364
+ this.#responseCb = null;
6365
+ this.#addPageOn();
6366
+ }
6367
+ async addPreloadScript() {
6368
+ throw new Error(`Camoufox does not support page.addPreloadScript to prevent detection`);
6369
+ }
6370
+ async addScriptTag(_options) {
6371
+ throw new Error(`Camoufox does not support page.addPreloadScript to prevent detection`);
6372
+ }
6373
+ apiContext() {
6374
+ return this.browserContext().apiContext();
6375
+ }
6376
+ async bringToFront() {
6377
+ if (!this.#page) {
6378
+ throw new Error("No valid page");
6379
+ }
6380
+ await this.#page.bringToFront();
6381
+ return true;
6382
+ }
6383
+ browserContext() {
6384
+ return this.#lsdBrowserContext;
6385
+ }
6386
+ async clearCookies() {
6387
+ if (!this.#page) {
6388
+ throw new Error("No valid page");
6389
+ }
6390
+ return await this.#clearCookies(this.#page);
6391
+ }
6392
+ async clearLocalStorage() {
6393
+ if (!this.#page) {
6394
+ throw new Error("No valid page");
6395
+ }
6396
+ return await this.#clearLocalStorage(this.#page);
6397
+ }
6398
+ async clearRequestInterceptions() {
6399
+ if (!this.#page) {
6400
+ throw new Error("No valid page");
6401
+ }
6402
+ await this.#page.unrouteAll();
6403
+ return true;
6404
+ }
6405
+ async clearResponseInterceptions() {
6406
+ if (!this.#page) {
6407
+ throw new Error("No valid page");
6408
+ }
6409
+ try {
6410
+ if (this.#responseInterceptionOptions.length > 0) {
6411
+ if (this.#responseCb) {
6412
+ this.#page.removeListener("response", this.#responseCb);
6413
+ }
6414
+ this.#responseInterceptionOptions = [];
6415
+ }
6416
+ return true;
6417
+ } catch (err) {
6418
+ logerr(err);
6419
+ return false;
6420
+ }
6421
+ }
6422
+ async clearStateData() {
6423
+ if (!this.#page) {
6424
+ throw new Error("No valid page");
6425
+ }
6426
+ await this.#clearCookies(this.#page);
6427
+ await this.#clearIndexedDB(this.#page);
6428
+ return await this.#clearLocalStorage(this.#page);
6429
+ }
6430
+ async close() {
6431
+ if (this.#status === "closed") {
6432
+ logwarn(`Page ${this.#pageId} is already closed.`);
6433
+ return true;
6434
+ } else if (this.#status === "busy") {
6435
+ throw new Error(`Page ${this.#pageId} cannot be closed because it is busy.`);
6436
+ }
6437
+ if (!this.#page) {
6438
+ throw new Error("No valid page");
6439
+ }
6440
+ await this.#page.close();
6441
+ this.#page = null;
6442
+ this.#status = "closed";
6443
+ return true;
6444
+ }
6445
+ closeWhenFree() {
6446
+ return this.#closeWhenFree;
6447
+ }
6448
+ async content(iframeOptions = []) {
6449
+ if (!this.#page) {
6450
+ throw new Error("No valid page");
6451
+ }
6452
+ let content = "";
6453
+ if (iframeOptions.length > 0) {
6454
+ const frameLocator = await this.#getDescendantFrame(this.#page.mainFrame(), iframeOptions);
6455
+ if (frameLocator) {
6456
+ content = await frameLocator.locator(":root").evaluate(() => document.documentElement.outerHTML);
6457
+ }
6458
+ } else {
6459
+ content = await this.#page.content();
6460
+ }
6461
+ return content;
6462
+ }
6463
+ async cookies() {
6464
+ if (!this.#page) {
6465
+ throw new Error("No valid page");
6466
+ }
6467
+ return this.#getCookies(this.#page);
6468
+ }
6469
+ async documentHeight() {
6470
+ if (!this.#page) {
6471
+ throw new Error("No valid page");
6472
+ }
6473
+ const height = await this.#page.evaluate(() => document.documentElement.scrollHeight);
6474
+ return height;
6475
+ }
6476
+ async evaluate(func, args, isolated = true) {
6477
+ if (!this.#page) {
6478
+ throw new Error("No valid page");
6479
+ }
6480
+ if (typeof func === "string" && !isolated) {
6481
+ return this.#page.evaluate(`mw:${func}`, args);
6482
+ } else {
6483
+ return this.#page.evaluate(func, args);
6484
+ }
6485
+ }
6486
+ async exposeFunction(_name, _callbackFunction) {
6487
+ throw new Error(`Camoufox does not support page.exposeFunction to prevent detection`);
6488
+ }
6489
+ async findElement(selectorOrXpath, iframeOptions = []) {
6490
+ if (!this.#page) {
6491
+ throw new Error("No valid page");
6492
+ }
6493
+ const selectors = typeof selectorOrXpath === "string" ? [selectorOrXpath] : selectorOrXpath;
6494
+ if (!Array.isArray(selectors)) {
6495
+ throw new Error(`Invalid selectorOrXpath ${selectorOrXpath} in findElement`);
6496
+ }
6497
+ for (const selector of selectors) {
6498
+ const { frame, locators } = await this.#findElementHandles(selector, iframeOptions);
6499
+ if (locators.length > 0) {
6500
+ const playwrightElement = new CamoufoxElement(locators[0], frame);
6501
+ return playwrightElement;
6502
+ }
6503
+ }
6504
+ return null;
6505
+ }
6506
+ async findElements(selectorOrXpath, iframeOptions = []) {
6507
+ if (!this.#page) {
6508
+ throw new Error("No valid page");
6509
+ }
6510
+ const selectors = typeof selectorOrXpath === "string" ? [selectorOrXpath] : selectorOrXpath;
6511
+ if (!Array.isArray(selectors)) {
6512
+ throw new Error(`Invalid selectorOrXpath ${selectorOrXpath} in findElements`);
6513
+ }
6514
+ for (const selector of selectors) {
6515
+ const { frame, locators } = await this.#findElementHandles(selector, iframeOptions);
6516
+ if (locators.length > 0) {
6517
+ const playwrightElements = locators.map((locator) => new CamoufoxElement(locator, frame));
6518
+ return playwrightElements;
6519
+ }
6520
+ }
6521
+ return [];
6522
+ }
6523
+ async free() {
6524
+ if (this.#status === "free") {
6525
+ logwarn(`Page ${this.#pageId} is already free.`);
6526
+ }
6527
+ this.#status = "free";
6528
+ await this.clearRequestInterceptions();
6529
+ await this.clearResponseInterceptions();
6530
+ return true;
6531
+ }
6532
+ #getWaitUntil(origWaitUntil) {
6533
+ if (origWaitUntil === "networkidle0" || origWaitUntil === "networkidle2") {
6534
+ return "networkidle";
6535
+ } else {
6536
+ return origWaitUntil;
6537
+ }
6538
+ }
6539
+ async goto(url, options) {
6540
+ if (!this.#page) {
6541
+ throw new Error("No valid page");
6542
+ }
6543
+ if (options) {
6544
+ const { referer, timeout, waitUntil = "load" } = options;
6545
+ const newOptions = {};
6546
+ if (referer) {
6547
+ newOptions.referer = referer;
6548
+ }
6549
+ if (timeout) {
6550
+ newOptions.timeout = timeout;
6551
+ }
6552
+ newOptions.waitUntil = this.#getWaitUntil(waitUntil);
6553
+ await this.#page.goto(url, newOptions);
6554
+ } else {
6555
+ await this.#page.goto(url);
6556
+ }
6557
+ return true;
6558
+ }
6559
+ id() {
6560
+ return this.#pageId;
6561
+ }
6562
+ isFree() {
6563
+ return this.#status === "free";
6564
+ }
6565
+ async localStroage() {
6566
+ if (!this.#page) {
6567
+ throw new Error("No valid page");
6568
+ }
6569
+ return this.#getLocalStorage(this.#page);
6570
+ }
6571
+ load() {
6572
+ throw new Error("Not supported in CamoufoxPage.");
6573
+ }
6574
+ mainFrame() {
6575
+ if (!this.#page) {
6576
+ throw new Error("No valid page");
6577
+ }
6578
+ return this.#page.mainFrame();
6579
+ }
6580
+ async maximizeViewport() {
6581
+ const height = await this.pageHeight();
6582
+ const width = await this.pageWidth();
6583
+ return await this.setViewportSize({ height, width });
6584
+ }
6585
+ async mouseClick(x, y, options) {
6586
+ if (!this.#page) {
6587
+ throw new Error("No valid page");
6588
+ }
6589
+ await this.#page.mouse.click(x, y, options);
6590
+ return true;
6591
+ }
6592
+ async mouseDown() {
6593
+ if (!this.#page) {
6594
+ throw new Error("No valid page");
6595
+ }
6596
+ await this.#page.mouse.down();
6597
+ return true;
6598
+ }
6599
+ async mouseMove(x, y) {
6600
+ if (!this.#page) {
6601
+ throw new Error("No valid page");
6602
+ }
6603
+ await this.#page.mouse.move(x, y);
6604
+ return true;
6605
+ }
6606
+ async mouseUp() {
6607
+ if (!this.#page) {
6608
+ throw new Error("No valid page");
6609
+ }
6610
+ await this.#page.mouse.up();
6611
+ return true;
6612
+ }
6613
+ async mouseWheel(deltaX = 0, deltaY = 0) {
6614
+ if (!this.#page) {
6615
+ throw new Error("No valid page");
6616
+ }
6617
+ await this.#page.mouse.wheel(deltaX, deltaY);
6618
+ return true;
6619
+ }
6620
+ async pageHeight() {
6621
+ if (!this.#page) {
6622
+ throw new Error("No valid page");
6623
+ }
6624
+ const bodyHeight = await this.#page.evaluate(() => document.body.scrollHeight);
6625
+ const documentHeight = await this.#page.evaluate(() => document.documentElement.scrollHeight);
6626
+ const windowHeight = await this.#page.evaluate(() => window.outerHeight);
6627
+ const pageHeight = Math.max(bodyHeight, documentHeight, windowHeight);
6628
+ return pageHeight;
6629
+ }
6630
+ pageInfo() {
6631
+ if (!this.#page) {
6632
+ throw new Error("No valid page");
6633
+ }
6634
+ return Object.assign({}, this.#page.pageInfo);
6635
+ }
6636
+ async pageWidth() {
6637
+ if (!this.#page) {
6638
+ throw new Error("No valid page");
6639
+ }
6640
+ const offsetWidth = await this.#page.evaluate(() => document.documentElement.offsetWidth);
6641
+ const windowWidth = await this.#page.evaluate(() => window.outerWidth);
6642
+ const pageWidth = Math.max(offsetWidth, windowWidth);
6643
+ return pageWidth;
6644
+ }
6645
+ async pdf(options) {
6646
+ if (!this.#page) {
6647
+ throw new Error("No valid page");
6648
+ }
6649
+ const buffer = await this.#page.pdf(options);
6650
+ return buffer;
6651
+ }
6652
+ async reload() {
6653
+ if (!this.#page) {
6654
+ throw new Error("No valid page");
6655
+ }
6656
+ try {
6657
+ await this.#page.reload();
6658
+ return true;
6659
+ } catch (err) {
6660
+ loginfo(err);
6661
+ return false;
6662
+ }
6663
+ }
6664
+ async screenshot(options) {
6665
+ if (!this.#page) {
6666
+ throw new Error("No valid page");
6667
+ }
6668
+ return await this.#page.screenshot(options);
6669
+ }
6670
+ async scrollBy(x, y) {
6671
+ if (!this.#page) {
6672
+ throw new Error("No valid page");
6673
+ }
6674
+ await this.#page.evaluate(
6675
+ ([x2, y2]) => {
6676
+ window.scrollBy(x2, y2);
6677
+ },
6678
+ [x, y]
6679
+ );
6680
+ return true;
6681
+ }
6682
+ async scrollTo(x, y) {
6683
+ if (!this.#page) {
6684
+ throw new Error("No valid page");
6685
+ }
6686
+ await this.#page.evaluate(
6687
+ ([x2, y2]) => {
6688
+ window.scrollTo(x2, y2);
6689
+ },
6690
+ [x, y]
6691
+ );
6692
+ return true;
6693
+ }
6694
+ async sendCDPMessage(method, params = null, detach = true) {
6695
+ if (!this.#client) {
6696
+ const origContext = this.browserContext()._origBrowserContext();
6697
+ if (!origContext) {
6698
+ throw new Error(`Invalid playwright browserContext`);
6699
+ }
6700
+ this.#client = await origContext.newCDPSession(this.#page);
6701
+ }
6702
+ if (!this.#client) {
6703
+ throw new Error("No valid CDP session to send message");
6704
+ }
6705
+ const response = params ? await this.#client.send(method, params) : await this.#client.send(method);
6706
+ if (detach) {
6707
+ await this.#client.detach();
6708
+ this.#client = null;
6709
+ }
6710
+ return response;
6711
+ }
6712
+ setCloseWhenFree(closeWhenFree) {
6713
+ this.#closeWhenFree = closeWhenFree;
6714
+ return true;
6715
+ }
6716
+ async setCookies(cookies) {
6717
+ if (!this.#page) {
6718
+ throw new Error("No valid page");
6719
+ }
6720
+ return await this.#setCookies(this.#page, cookies);
6721
+ }
6722
+ async setExtraHTTPHeaders(headers) {
6723
+ if (!this.#page) {
6724
+ throw new Error("No valid page");
6725
+ }
6726
+ await this.#page.setExtraHTTPHeaders(headers);
6727
+ return true;
6728
+ }
6729
+ async setLocalStroage(localStorageItems) {
6730
+ if (!this.#page) {
6731
+ throw new Error("No valid page");
6732
+ }
6733
+ return await this.#setLocalStorage(this.#page, localStorageItems);
6734
+ }
6735
+ setPageInfo(pageInfo) {
6736
+ if (!this.#page?.pageInfo) {
6737
+ throw new Error("No valid page or pageInfo");
6738
+ }
6739
+ if (!pageInfo) {
6740
+ throw new Error("Invalid paras in setPageInfo");
6741
+ }
6742
+ const actPageInfo = this.#page.pageInfo;
6743
+ const { lastStatusUpdateTime, taskId, relatedId, misc } = pageInfo;
6744
+ if (typeof lastStatusUpdateTime === "number") {
6745
+ actPageInfo.lastStatusUpdateTime = lastStatusUpdateTime;
6746
+ }
6747
+ if (typeof taskId === "number") {
6748
+ actPageInfo.taskId = taskId;
6749
+ }
6750
+ if (typeof relatedId === "number") {
6751
+ actPageInfo.relatedId = relatedId;
6752
+ }
6753
+ if (misc && typeof misc === "object") {
6754
+ for (const key of Object.keys(misc)) {
6755
+ actPageInfo.misc[key] = misc[key];
6756
+ }
6757
+ }
6758
+ return true;
6759
+ }
6760
+ #checkRequestMatch(request, requestMatch) {
6761
+ try {
6762
+ if (!request) {
6763
+ return false;
6764
+ }
6765
+ const { methods, postData, resourceTypes, url } = requestMatch;
6766
+ if (methods && !methods.includes(request.method().toUpperCase())) {
6767
+ return false;
6768
+ }
6769
+ if (resourceTypes && !resourceTypes.includes(request.resourceType())) {
6770
+ return false;
6771
+ }
6772
+ if (url && !request.url().match(url)) {
6773
+ return false;
6774
+ }
6775
+ const origData = request.postData();
6776
+ const data = origData ? origData : "";
6777
+ if (postData && !data.match(postData)) {
6778
+ return false;
6779
+ }
6780
+ return true;
6781
+ } catch (err) {
6782
+ logerr(err);
6783
+ return false;
6784
+ }
6785
+ }
6786
+ async setRequestInterception(options) {
6787
+ if (!this.#page) {
6788
+ throw new Error("No valid page");
6789
+ }
6790
+ const actOptions = Array.isArray(options) ? options : [options];
6791
+ if (actOptions.length <= 0) {
6792
+ logwarn("Invalid paras in setRequestInterception");
6793
+ return false;
6794
+ }
6795
+ const firstRequestInterception = this.#resquestInterceptionOptions.length <= 0;
6796
+ for (const option of actOptions) {
6797
+ switch (option.action) {
6798
+ case "abort":
6799
+ case "fulfill":
6800
+ this.#resquestInterceptionOptions.push(option);
6801
+ break;
6802
+ default:
6803
+ unreachable8(option.action);
6804
+ }
6805
+ }
6806
+ if (firstRequestInterception && this.#resquestInterceptionOptions.length > 0) {
6807
+ this.#page.route("**", async (route) => {
6808
+ try {
6809
+ for (const option of actOptions) {
6810
+ const { requestMatch, action, fulfill } = option;
6811
+ const request = route.request();
6812
+ const matchedFlag = !requestMatch || this.#checkRequestMatch(request, requestMatch);
6813
+ if (matchedFlag) {
6814
+ switch (action) {
6815
+ case "abort":
6816
+ await route.abort();
6817
+ break;
6818
+ case "fulfill":
6819
+ const body = fulfill ? fulfill : `<html><body><h1>${request.url()}</h1></body></html>`;
6820
+ route.fulfill({
6821
+ status: 200,
6822
+ // contentType: "text/html; charset=utf-8", // "text/plain",
6823
+ body
6824
+ });
6825
+ break;
6826
+ default:
6827
+ unreachable8(action);
6828
+ }
6829
+ return true;
6830
+ } else {
6831
+ }
6832
+ }
6833
+ await route.continue();
6834
+ return true;
6835
+ } catch (err) {
6836
+ logerr(err);
6837
+ return false;
6838
+ }
6839
+ });
6840
+ }
6841
+ return true;
6842
+ }
6843
+ async #responseListener(response) {
6844
+ try {
6845
+ const pageUrl = this.#page ? this.#page.url() : "";
6846
+ if (!response.ok()) {
6847
+ return;
6848
+ }
6849
+ const request = response.request();
6850
+ if (!request) {
6851
+ return;
6852
+ }
6853
+ for (const option of this.#responseInterceptionOptions) {
6854
+ const { requestMatch, responseMatch, responseItems, handler, handlerOptions = {} } = option;
6855
+ let matchedFlag = !requestMatch || this.#checkRequestMatch(request, requestMatch);
6856
+ if (matchedFlag && responseMatch) {
6857
+ const { minLength, maxLength } = responseMatch;
6858
+ const text = await response.text();
6859
+ const len = text.length;
6860
+ if (minLength && minLength > 0 && len < minLength || maxLength && maxLength > 0 && len > maxLength) {
6861
+ matchedFlag = false;
6862
+ }
6863
+ }
6864
+ if (!matchedFlag) {
6865
+ continue;
6866
+ }
6867
+ if (Array.isArray(responseItems)) {
6868
+ const requestMethod = request.method();
6869
+ const requestUrl = request.url();
6870
+ const reqData2 = request.postData();
6871
+ const requestData = reqData2 ? reqData2 : "";
6872
+ const responseData = await response.text();
6873
+ responseItems.push({
6874
+ pageUrl,
6875
+ requestMethod,
6876
+ requestUrl,
6877
+ requestData,
6878
+ responseData
6879
+ });
6880
+ loginfo(`##browser cache matched response: ${requestUrl}`);
6881
+ }
6882
+ if (typeof handler === "function") {
6883
+ const pageData = { pageUrl, cookies: "" };
6884
+ await handler(response, handlerOptions, pageData);
6885
+ }
6886
+ }
6887
+ return;
6888
+ } catch (err) {
6889
+ logerr(err);
6890
+ return;
6891
+ }
6892
+ }
6893
+ async setResponseInterception(options) {
6894
+ if (!this.#page) {
6895
+ throw new Error("No valid page");
6896
+ }
6897
+ const actOptions = Array.isArray(options) ? options : [options];
6898
+ if (actOptions.length <= 0) {
6899
+ logwarn("Invalid paras in setResponseInterception");
6900
+ return false;
6901
+ }
6902
+ const firstResponseInterception = this.#responseInterceptionOptions.length <= 0;
6903
+ for (const option of actOptions) {
6904
+ if (option?.responseItems || option?.handler) {
6905
+ this.#responseInterceptionOptions.push(option);
6906
+ } else {
6907
+ throw new Error(`Invalid ResponseInterceptionOption`);
6908
+ }
6909
+ }
6910
+ if (firstResponseInterception && this.#responseInterceptionOptions.length > 0) {
6911
+ this.#responseCb = this.#responseListener.bind(this);
6912
+ this.#page.on("response", this.#responseCb);
6913
+ }
6914
+ return true;
6915
+ }
6916
+ async setStateData(stateData) {
6917
+ return await this.#lsdBrowserContext.setStateData(stateData);
6918
+ }
6919
+ async setUserAgent(userAgent) {
6920
+ if (userAgent) {
6921
+ throw new Error(`Camoufox does not support page.setUserAgent by now`);
6922
+ }
6923
+ return false;
6924
+ }
6925
+ async setViewportSize(viewPortSize) {
6926
+ if (!this.#page) {
6927
+ throw new Error("No valid page");
6928
+ }
6929
+ await this.#page.setViewportSize(viewPortSize);
6930
+ return true;
6931
+ }
6932
+ async stateData() {
6933
+ if (!this.#page) {
6934
+ throw new Error("No valid page");
6935
+ }
6936
+ const cookies = await this.#getCookies(this.#page);
6937
+ const localStorage = await this.#getLocalStorage(this.#page);
6938
+ return { cookies, localStorage };
6939
+ }
6940
+ status() {
6941
+ return this.#status;
6942
+ }
6943
+ async title() {
6944
+ if (!this.#page) {
6945
+ throw new Error("No valid page");
6946
+ }
6947
+ return await this.#page.title();
6948
+ }
6949
+ url() {
6950
+ if (!this.#page) {
6951
+ throw new Error("No valid page");
6952
+ }
6953
+ return this.#page.url();
6954
+ }
6955
+ use() {
6956
+ if (this.#status === "busy") {
6957
+ throw new Error(`Page ${this.#pageId} is already busy!!!`);
6958
+ }
6959
+ this.#status = "busy";
6960
+ return true;
6961
+ }
6962
+ async waitForElement(selector, options = {}) {
6963
+ if (!this.#page) {
6964
+ throw new Error("No valid page");
6965
+ }
6966
+ const locator = this.#page.locator(selector);
6967
+ const { timeout = 3e4, state = "visible" } = options;
6968
+ await locator.waitFor({ state, timeout });
6969
+ return true;
6970
+ }
6971
+ async waitForNavigation(options) {
6972
+ if (!this.#page) {
6973
+ throw new Error("No valid page");
6974
+ }
6975
+ const { url = "", timeout = 3e4, waitUntil = "load" } = options;
6976
+ const newWaitUntil = this.#getWaitUntil(waitUntil);
6977
+ if (url) {
6978
+ await this.#page.waitForURL(url, { timeout, waitUntil: newWaitUntil });
6979
+ } else if (newWaitUntil === "commit") {
6980
+ throw new Error("commit is not supported in CamoufoxPage.waitForNavigation");
6981
+ } else {
6982
+ await this.#page.waitForLoadState(newWaitUntil, { timeout });
6983
+ }
6984
+ return true;
6985
+ }
6986
+ async windowMember(keys) {
6987
+ if (!this.#page) {
6988
+ throw new Error("No valid page");
6989
+ }
6990
+ if (!this.#page || !Array.isArray(keys) || keys.length <= 0 || keys.length > 20) {
6991
+ return "";
6992
+ }
6993
+ const content = await this.#page.evaluate(
6994
+ (keys2) => {
6995
+ let retObj = window;
6996
+ for (const key of keys2) {
6997
+ if (!key) {
6998
+ break;
6999
+ } else if (typeof retObj !== "object" || !retObj) {
7000
+ return "";
7001
+ } else {
7002
+ retObj = retObj[key];
7003
+ }
7004
+ }
7005
+ if (typeof retObj === "string") {
7006
+ return retObj;
7007
+ } else if (typeof retObj === "number") {
7008
+ return String(retObj);
7009
+ } else if (typeof retObj === "boolean") {
7010
+ return String(Number(retObj));
7011
+ } else if (!retObj) {
7012
+ return "";
7013
+ } else if (typeof retObj === "object") {
7014
+ try {
7015
+ return JSON.stringify(retObj);
7016
+ } catch (err) {
7017
+ return "";
7018
+ }
7019
+ } else if (typeof retObj === "bigint") {
7020
+ return String(retObj);
7021
+ } else {
7022
+ return "";
7023
+ }
7024
+ },
7025
+ keys
7026
+ );
7027
+ return content;
7028
+ }
7029
+ _origPage() {
7030
+ return this.#page;
7031
+ }
7032
+ };
7033
+
7034
+ // src/camoufox/api.ts
7035
+ var CamoufoxApiContext = class {
7036
+ #apiRequestContext;
7037
+ #status;
7038
+ constructor(apiRequestContext) {
7039
+ this.#apiRequestContext = apiRequestContext;
7040
+ this.#status = "normal";
7041
+ }
7042
+ async fetch(url, options = {}) {
7043
+ if (this.#status !== "normal") {
7044
+ throw new Error(`ApiContext has already been destroyed`);
7045
+ }
7046
+ const apiResponse = await this.#apiRequestContext.fetch(url, options);
7047
+ const headers = apiResponse.headers();
7048
+ const status = apiResponse.status();
7049
+ const statusText = apiResponse.statusText();
7050
+ const text = await apiResponse.text();
7051
+ const responseUrl = apiResponse.url();
7052
+ return { headers, status, statusText, text, url: responseUrl };
7053
+ }
7054
+ async stateData() {
7055
+ if (this.#status !== "normal") {
7056
+ throw new Error(`ApiContext has already been destroyed`);
7057
+ }
7058
+ const storageState = await this.#apiRequestContext.storageState();
7059
+ const { cookies, origins: localStorage } = storageState;
7060
+ return { cookies, localStorage };
7061
+ }
7062
+ async destroy() {
7063
+ await this.#apiRequestContext.dispose();
7064
+ this.#status = "destroyed";
7065
+ return true;
7066
+ }
7067
+ };
7068
+
7069
+ // src/camoufox/context.ts
7070
+ var CamoufoxBrowserContext = class extends EventEmitter12 {
7071
+ #lsdBrowser;
7072
+ #browserIdx;
7073
+ #browserContextIdx;
7074
+ #browserContext;
7075
+ #browserContextCreationMethod;
7076
+ #apiContext;
7077
+ #createTime;
7078
+ #lastStatusUpdateTime;
7079
+ #status;
7080
+ #incognito;
7081
+ #proxy;
7082
+ #maxPagesPerBrowserContext;
7083
+ #maxPageFreeSeconds;
7084
+ #maxViewportOfNewPage;
7085
+ #lsdPages;
7086
+ #nextPageIdx;
7087
+ #gettingPage;
7088
+ async #initPages() {
7089
+ if (!this.#browserContext) {
7090
+ throw new Error("Invalid browserContext");
7091
+ }
7092
+ const pages = this.#browserContext.pages();
7093
+ const openType = this.#lsdBrowser.browserCreationMethod();
7094
+ const lastStatusUpdateTime = getCurrentUnixTime11();
7095
+ for (const page of pages) {
7096
+ const pageInfo = { browserIdx: this.#browserIdx, browserContextIdx: this.#browserContextIdx, pageIdx: this.#nextPageIdx++, openType, openTime: this.#createTime, lastStatusUpdateTime, taskId: 0, relatedId: 0, misc: {} };
7097
+ const lsdPage = new CamoufoxPage(this, page, pageInfo);
7098
+ if (this.#maxViewportOfNewPage) {
7099
+ await lsdPage.maximizeViewport();
7100
+ }
7101
+ this.#lsdPages.push(lsdPage);
7102
+ loginfo(`##browser ${lsdPage.id()} ${openType}ed`);
7103
+ }
7104
+ }
7105
+ constructor(lsdBrowser, browserContext, browserContextCreationMethod, incognito = false, proxy = null, browserIdx = 0, browserContextIdx = 0, maxPagesPerBrowserContext = 20, maxPageFreeSeconds = 0, maxViewportOfNewPage = true) {
7106
+ if (!lsdBrowser || typeof lsdBrowser.browserContexts !== "function") {
7107
+ throw new Error(`Invalid lsdBrowser parameter`);
7108
+ }
7109
+ if (!browserContext || typeof browserContext.setOffline !== "function") {
7110
+ throw new Error(`Invalid playwright browserContext parameter`);
7111
+ }
7112
+ super();
7113
+ this.#lsdBrowser = lsdBrowser;
7114
+ this.#browserIdx = browserIdx;
7115
+ this.#browserContextIdx = browserContextIdx;
7116
+ this.#browserContext = browserContext;
7117
+ this.#browserContextCreationMethod = browserContextCreationMethod;
7118
+ const apiRequestContext = browserContext.request;
7119
+ this.#apiContext = new CamoufoxApiContext(apiRequestContext);
7120
+ const currentTime = getCurrentUnixTime11();
7121
+ this.#createTime = currentTime;
7122
+ this.#lastStatusUpdateTime = currentTime;
7123
+ this.#status = "free";
7124
+ this.#incognito = incognito === false ? false : true;
7125
+ this.#proxy = proxy?.proxyUrl ? proxy : null;
7126
+ this.#maxPagesPerBrowserContext = maxPagesPerBrowserContext;
7127
+ this.#maxPageFreeSeconds = maxPageFreeSeconds;
7128
+ this.#maxViewportOfNewPage = maxViewportOfNewPage;
7129
+ this.#lsdPages = [];
7130
+ this.#nextPageIdx = 1;
7131
+ this.#gettingPage = false;
7132
+ this.#initPages();
7133
+ browserContext.on("page", async (page) => {
7134
+ const pageInfo = page.pageInfo;
7135
+ if (pageInfo) {
7136
+ const { browserIdx: browserIdx2, browserContextIdx: browserContextIdx2, pageIdx } = pageInfo;
7137
+ logwarn(`##browser page-${browserIdx2}-${browserContextIdx2}-${pageIdx} has been already created`);
7138
+ } else {
7139
+ const currentTime2 = getCurrentUnixTime11();
7140
+ const pageInfo2 = { browserIdx: this.#browserIdx, browserContextIdx: this.#browserContextIdx, pageIdx: this.#nextPageIdx++, openType: "other", openTime: currentTime2, lastStatusUpdateTime: currentTime2, taskId: 0, relatedId: 0, misc: {} };
7141
+ const lsdPage = new CamoufoxPage(this, page, pageInfo2);
7142
+ if (this.#maxViewportOfNewPage) {
7143
+ await lsdPage.maximizeViewport();
7144
+ }
7145
+ this.#lsdPages.push(lsdPage);
7146
+ loginfo(`##page ${lsdPage.id()} created`);
7147
+ }
7148
+ });
7149
+ browserContext.on("close", (bc) => {
7150
+ if (browserContext !== bc) {
7151
+ logerr(`##browser different browserContext in browserContext.on("close")`);
7152
+ }
7153
+ this.#lsdBrowser.emit("browserContextClose", this);
7154
+ });
7155
+ this.on("pageClose", (lsdPage) => {
7156
+ if (!(lsdPage instanceof CamoufoxPage)) {
7157
+ logerr(`Invalid data in LsdBrowserContext.on("pageClose)`);
7158
+ return;
7159
+ }
7160
+ const idx = this.#lsdPages.findIndex((p) => p === lsdPage);
7161
+ if (idx < 0) {
7162
+ logerr(`Invalid lsdPage in LsdBrowserContext.on("pageClose)`);
7163
+ return;
7164
+ }
7165
+ this.#lsdPages.splice(idx, 1);
7166
+ return;
7167
+ });
7168
+ }
7169
+ apiContext() {
7170
+ return this.#apiContext;
7171
+ }
7172
+ browser() {
7173
+ return this.#lsdBrowser;
7174
+ }
7175
+ async close() {
7176
+ if (this.#browserContext) {
7177
+ this.#status = "closed";
7178
+ this.#lastStatusUpdateTime = getCurrentUnixTime11();
7179
+ loginfo(`browserContext ${this.id()} closed at ${this.#lastStatusUpdateTime}`);
7180
+ await this.#browserContext.close();
7181
+ }
7182
+ return true;
7183
+ }
7184
+ async #tryToGetGettingLock() {
7185
+ let i = 0;
7186
+ for (i = 0; i < 50; i++) {
7187
+ if (!this.#gettingPage) {
7188
+ this.#gettingPage = true;
7189
+ return true;
7190
+ } else {
7191
+ await sleep4(200);
7192
+ }
7193
+ }
7194
+ logwarn(`Cannot get the gettingLock.`);
7195
+ return false;
7196
+ }
7197
+ #freeGettingLock() {
7198
+ if (!this.#gettingPage) {
7199
+ logwarn(`Getting lock is already free now.`);
7200
+ }
7201
+ this.#gettingPage = false;
7202
+ }
7203
+ async closeFreePages(maxPageFreeSeconds = 0) {
7204
+ if (maxPageFreeSeconds <= 0) {
7205
+ maxPageFreeSeconds = this.#maxPageFreeSeconds;
7206
+ }
7207
+ if (maxPageFreeSeconds <= 0) {
7208
+ logwarn(`Please set valid maxPageFreeSeconds to close free pages`);
7209
+ return false;
7210
+ }
7211
+ const gotLock = await this.#tryToGetGettingLock();
7212
+ if (!gotLock) {
7213
+ return false;
7214
+ }
7215
+ try {
7216
+ const maxUpdateTime = getCurrentUnixTime11() - this.#maxPageFreeSeconds;
7217
+ let freePages = this.#lsdPages.filter((p) => p.isFree() && p.pageInfo().lastStatusUpdateTime < maxUpdateTime);
7218
+ if (freePages.length === this.#lsdPages.length) {
7219
+ freePages = freePages.slice(1);
7220
+ }
7221
+ for (const lsdPage of freePages) {
7222
+ await lsdPage.close();
7223
+ }
7224
+ this.#freeGettingLock();
7225
+ return true;
7226
+ } catch (err) {
7227
+ logerr(err);
7228
+ this.#freeGettingLock();
7229
+ return false;
7230
+ }
7231
+ }
7232
+ creationMethod() {
7233
+ return this.#browserContextCreationMethod;
7234
+ }
7235
+ doesMeetBrowserContextRequirements(browserContextRequirements) {
7236
+ if (!this.#lsdBrowser.doesMeetBrowserContextRequirements(browserContextRequirements)) {
7237
+ return false;
7238
+ }
7239
+ const { browserIncognitos: incognitos } = browserContextRequirements;
7240
+ return incognitos.length === 0 || incognitos.includes(this.#incognito);
7241
+ }
7242
+ async getPage(always = false) {
7243
+ if (!this.#browserContext) {
7244
+ throw new Error("Invalid browserContext");
7245
+ }
7246
+ const gotLock = await this.#tryToGetGettingLock();
7247
+ if (!gotLock) {
7248
+ return null;
7249
+ }
7250
+ try {
7251
+ let lsdPage = this.#lsdPages.find((p) => p.isFree());
7252
+ if (lsdPage) {
7253
+ lsdPage.use();
7254
+ this.#freeGettingLock();
7255
+ return lsdPage;
7256
+ }
7257
+ if (this.#lsdPages.length >= this.#maxPagesPerBrowserContext && !always) {
7258
+ this.#freeGettingLock();
7259
+ return null;
7260
+ }
7261
+ const page = await this.#browserContext.newPage();
7262
+ await sleep4(2e3);
7263
+ const pageInfo = page.pageInfo;
7264
+ if (!pageInfo) {
7265
+ throw new Error(`Logic error in getPage`);
7266
+ } else {
7267
+ pageInfo.openType = "newpage";
7268
+ }
7269
+ lsdPage = this.#lsdPages.find((p) => p.isFree());
7270
+ if (lsdPage) {
7271
+ lsdPage.use();
7272
+ this.#freeGettingLock();
7273
+ return lsdPage;
7274
+ } else {
7275
+ this.#freeGettingLock();
7276
+ return null;
7277
+ }
7278
+ } catch (err) {
7279
+ logerr(err);
7280
+ this.#freeGettingLock();
7281
+ return null;
7282
+ }
7283
+ }
7284
+ free(clearStateData = false) {
7285
+ if (this.#status === "busy") {
7286
+ this.#status = "free";
7287
+ this.#lastStatusUpdateTime = getCurrentUnixTime11();
7288
+ if (clearStateData) {
7289
+ }
7290
+ return true;
7291
+ } else {
7292
+ return false;
7293
+ }
7294
+ }
7295
+ hasFreePage(pageNum = 1) {
7296
+ if (this.#maxPagesPerBrowserContext - this.#lsdPages.length > pageNum) {
7297
+ return true;
7298
+ } else if (this.#maxPagesPerBrowserContext - this.#lsdPages.filter((p) => !p.isFree()).length > pageNum) {
7299
+ return true;
7300
+ } else {
7301
+ return false;
7302
+ }
7303
+ }
7304
+ id() {
7305
+ return `browserContext-${this.#browserIdx}-${this.#browserContextIdx}`;
7306
+ }
7307
+ isFree() {
7308
+ return this.#status === "free";
7309
+ }
7310
+ isIncognito() {
7311
+ return this.#incognito;
7312
+ }
7313
+ page(pageIdx) {
7314
+ const lsdPage = this.#lsdPages.find((p) => p.pageInfo().pageIdx === pageIdx);
7315
+ return lsdPage ? lsdPage : null;
7316
+ }
7317
+ pages() {
7318
+ return this.#lsdPages;
7319
+ }
7320
+ proxy() {
7321
+ return this.#proxy;
7322
+ }
7323
+ async setStateData(stateData) {
7324
+ if (!this.#browserContext) {
7325
+ throw new Error("No valid browserContext");
7326
+ }
7327
+ try {
7328
+ const { cookies, localStorage: localStorageOrigins } = stateData;
7329
+ const page = await this.getPage();
7330
+ if (!page) {
7331
+ return false;
7332
+ }
7333
+ const origPage = page._origPage();
7334
+ if (cookies.length > 0) {
7335
+ await this.#browserContext.addCookies(cookies);
7336
+ }
7337
+ if (localStorageOrigins.length > 0) {
7338
+ await origPage.route("**/*", async (route) => {
7339
+ const request = route.request();
7340
+ await route.fulfill({
7341
+ status: 200,
7342
+ // contentType: "text/html; charset=utf-8", // "text/plain",
7343
+ body: `<html><body><h1>${request.url()}</h1></body></html>`
7344
+ });
7345
+ });
7346
+ for (const localStorageOrigin of localStorageOrigins) {
7347
+ const { origin, localStorage } = localStorageOrigin;
7348
+ await origPage.goto(origin);
7349
+ await origPage.evaluate((localStorageItems) => {
7350
+ for (const item of localStorageItems) {
7351
+ window.localStorage.setItem(item.name, item.value);
7352
+ }
7353
+ }, localStorage);
7354
+ }
7355
+ }
7356
+ await sleep4(2e3);
7357
+ await origPage.unrouteAll();
7358
+ await page.free();
7359
+ return true;
7360
+ } catch (err) {
7361
+ logerr(err);
7362
+ return false;
7363
+ }
7364
+ }
7365
+ status() {
7366
+ return this.#status;
7367
+ }
7368
+ use() {
7369
+ if (this.#status === "free") {
7370
+ this.#status = "busy";
7371
+ this.#lastStatusUpdateTime = getCurrentUnixTime11();
7372
+ return true;
7373
+ } else {
7374
+ return false;
7375
+ }
7376
+ }
7377
+ _origBrowserContext() {
7378
+ return this.#browserContext;
7379
+ }
7380
+ };
7381
+
7382
+ // src/camoufox/browser.ts
7383
+ var CamoufoxBrowser = class _CamoufoxBrowser extends EventEmitter13 {
7384
+ static #supportedBrowserTypes = ["firefox"];
7385
+ static doesSupport(browserType) {
7386
+ return _CamoufoxBrowser.#supportedBrowserTypes.includes(browserType);
7387
+ }
7388
+ #browser;
7389
+ #browserIdx;
7390
+ #pid;
7391
+ #createTime;
7392
+ #lsdBrowserContexts;
7393
+ #browserControllerType;
7394
+ #browserType;
7395
+ #browserCreationMethod;
7396
+ #headless;
7397
+ #options;
7398
+ #proxy;
7399
+ /**
7400
+ * launch: actual path of executable app
7401
+ * connect: ""
7402
+ */
7403
+ #executablePath;
7404
+ #nextBrowserContextIdx;
7405
+ #closeFreePagesIntervalId;
7406
+ #maxBrowserContextsPerBrowser() {
7407
+ return this.#options.maxBrowserContextsPerBrowser ? this.#options.maxBrowserContextsPerBrowser : 10;
7408
+ }
7409
+ #maxPagesPerBrowserContext() {
7410
+ return this.#options.maxPagesPerBrowserContext ? this.#options.maxPagesPerBrowserContext : 20;
7411
+ }
7412
+ #maxPageFreeSeconds() {
7413
+ return this.#options.maxPageFreeSeconds ? this.#options.maxPageFreeSeconds : 900;
7414
+ }
7415
+ // constructor: called only by LsdBrowserController.launch/connect
7416
+ constructor(browser, browserType, browserCreateMethod, options, browserIdx = 0, pid = 0) {
7417
+ if (!browser || typeof browser.contexts !== "function") {
7418
+ throw new Error(`Invalid playwright browser parameter`);
7419
+ }
7420
+ super();
7421
+ const { closeFreePagesIntervalSeconds = 300, maxPageFreeSeconds = 900, maxViewportOfNewPage = true, headless = false, executablePath = "" } = options;
7422
+ this.#browser = browser;
7423
+ this.#browserIdx = browserIdx;
7424
+ this.#pid = pid;
7425
+ this.#createTime = getCurrentUnixTime12();
7426
+ this.#lsdBrowserContexts = [];
7427
+ this.#browserControllerType = "playwright";
7428
+ this.#browserType = browserType;
7429
+ if (!_CamoufoxBrowser.#supportedBrowserTypes.includes(browserType)) {
7430
+ throw new Error(`Browser controller ${this.#browserControllerType} doesnot support browserType ${browserType}`);
7431
+ }
7432
+ this.#browserCreationMethod = browserCreateMethod;
7433
+ this.#headless = headless;
7434
+ this.#proxy = options?.proxy ? Object.assign({}, options.proxy) : null;
7435
+ this.#options = Object.assign({}, options, { closeFreePagesIntervalSeconds, maxPageFreeSeconds, maxViewportOfNewPage, headless, executablePath, proxy: this.#proxy });
7436
+ this.#executablePath = executablePath;
7437
+ this.#nextBrowserContextIdx = 1;
7438
+ this.#closeFreePagesIntervalId = null;
7439
+ loginfo(`##browser ${this.id()} ${this.#browserCreationMethod}ed by ${this.#browserControllerType}`);
7440
+ const browserContexts = browser.contexts();
7441
+ if (browserContexts.length > 0) {
7442
+ logwarn(`There are ${browserContexts.length} new browserContexts when playwright launches new browser`);
7443
+ }
7444
+ const incognito = typeof options?.incognito === "boolean" ? options.incognito : true;
7445
+ for (const browserContext of browserContexts) {
7446
+ const lsdBrowserContext = new CamoufoxBrowserContext(this, browserContext, "launch", incognito, this.#proxy, this.#browserIdx, this.#nextBrowserContextIdx++, this.#maxPagesPerBrowserContext(), this.#maxPageFreeSeconds(), maxViewportOfNewPage);
7447
+ this.#lsdBrowserContexts.push(lsdBrowserContext);
7448
+ loginfo(`##browserContext ${lsdBrowserContext.id()} ${this.#browserCreationMethod}ed`);
7449
+ }
7450
+ browser.on("disconnected", () => {
7451
+ loginfo(`##browser ${this.id()} disconnected`);
7452
+ if (this.#lsdBrowserContexts.length > 0) {
7453
+ logerr(`${this.id()} has browserContexts when disconnected`);
7454
+ }
7455
+ });
7456
+ this.on("browserContextClose", (lsdBrowserContext) => {
7457
+ if (!(lsdBrowserContext instanceof CamoufoxBrowserContext)) {
7458
+ logerr(`Invalid data in LsdBrowser.on("browserContextClose)`);
7459
+ return;
7460
+ }
7461
+ const idx = this.#lsdBrowserContexts.findIndex((bc) => bc === lsdBrowserContext);
7462
+ if (idx < 0) {
7463
+ logerr(`Invalid lsdBrowserContext in LsdBrowser.on("browserContextClose)`);
7464
+ return;
7465
+ }
7466
+ loginfo(`##browserContext ${lsdBrowserContext.id()} closed
7467
+ `);
7468
+ this.#lsdBrowserContexts.splice(idx, 1);
7469
+ if (this.#lsdBrowserContexts.length === 0) {
7470
+ loginfo(`##browser ${this.id()} has no browserContexts now`);
7471
+ }
7472
+ return;
7473
+ });
7474
+ if (closeFreePagesIntervalSeconds > 0 && maxPageFreeSeconds > 0) {
7475
+ this.#closeFreePagesIntervalId = setInterval(async () => {
7476
+ await this.#closeFreePagesHandler();
7477
+ }, closeFreePagesIntervalSeconds * 1e3);
7478
+ }
7479
+ }
7480
+ async #closeFreePagesHandler() {
7481
+ for (const lsdBrowserContext of this.#lsdBrowserContexts) {
7482
+ await lsdBrowserContext.closeFreePages();
7483
+ }
7484
+ }
7485
+ async newBrowserContext(options) {
7486
+ if (this.#lsdBrowserContexts.length >= this.#maxBrowserContextsPerBrowser()) {
7487
+ logwarn(`##browser ${this.id()} can not create more new browserContext`);
7488
+ return null;
7489
+ }
7490
+ const browserContextOptions = {};
7491
+ if (this.#options.maxWindowSize) {
7492
+ browserContextOptions.viewport = null;
7493
+ }
7494
+ const proxy = options?.proxy ? Object.assign({}, options.proxy) : this.#proxy;
7495
+ if (proxy) {
7496
+ const { proxyUrl: server, username, password } = proxy;
7497
+ browserContextOptions.proxy = { server, username, password };
7498
+ }
7499
+ if (options?.userAgent) {
7500
+ browserContextOptions.userAgent = options.userAgent;
7501
+ }
7502
+ const browserContext = await this.#browser.newContext(browserContextOptions);
7503
+ const { maxViewportOfNewPage = this.#options.maxViewportOfNewPage } = options ? options : {};
7504
+ const lsdBrowserContext = new CamoufoxBrowserContext(this, browserContext, "new", true, proxy, this.#browserIdx, this.#nextBrowserContextIdx++, this.#maxPagesPerBrowserContext(), this.#maxPageFreeSeconds(), maxViewportOfNewPage);
7505
+ this.#lsdBrowserContexts.push(lsdBrowserContext);
7506
+ loginfo(`##browser ${lsdBrowserContext.id()} created`);
7507
+ return lsdBrowserContext;
7508
+ }
7509
+ async close() {
7510
+ if (this.#closeFreePagesIntervalId) {
7511
+ clearInterval(this.#closeFreePagesIntervalId);
7512
+ }
7513
+ for (const lsdBrowserContext of this.#lsdBrowserContexts) {
7514
+ await lsdBrowserContext.close();
7515
+ }
7516
+ await this.#browser.close();
7517
+ return true;
7518
+ }
7519
+ browserContexts() {
7520
+ return this.#lsdBrowserContexts;
7521
+ }
7522
+ browserControllerType() {
7523
+ return this.#browserControllerType;
7524
+ }
7525
+ browserCreationMethod() {
7526
+ return this.#browserCreationMethod;
7527
+ }
7528
+ browserType() {
7529
+ return this.#browserType;
7530
+ }
7531
+ createTime() {
7532
+ return this.#createTime;
7533
+ }
7534
+ doesMeetBrowserContextRequirements(browserContextRequirements) {
7535
+ const { browserControllerTypes, browserTypes, browserHeadlesses } = browserContextRequirements;
7536
+ return (browserControllerTypes.length === 0 || browserControllerTypes.includes(this.#browserControllerType)) && (browserTypes.length === 0 || browserTypes.includes(this.#browserType)) && (browserHeadlesses.length === 0 || browserHeadlesses.includes(this.#headless));
7537
+ }
7538
+ executablePath() {
7539
+ return this.#executablePath;
7540
+ }
7541
+ id() {
7542
+ return `browser-${this.#browserType}-${this.#browserIdx}`;
7543
+ }
7544
+ isConnected() {
7545
+ return this.#browser.isConnected();
7546
+ }
7547
+ isHeadless() {
7548
+ return this.#headless;
7549
+ }
7550
+ options() {
7551
+ return this.#options;
7552
+ }
7553
+ pid() {
7554
+ return this.#pid;
7555
+ }
7556
+ async pidUsage() {
7557
+ if (this.#pid > 0) {
7558
+ const usage = await getPerformanceOfPidTree4(this.#pid, "MB");
7559
+ return usage;
7560
+ } else {
7561
+ return { cpu: 0, memory: 0 };
7562
+ }
7563
+ }
7564
+ proxy() {
7565
+ return this.#proxy;
7566
+ }
7567
+ async version() {
7568
+ const version = await this.#browser.version();
7569
+ return version;
7570
+ }
7571
+ _origBrowser() {
7572
+ return this.#browser;
7573
+ }
7574
+ };
7575
+
7576
+ // src/controller/controller.ts
7577
+ var LsdBrowserController = class _LsdBrowserController {
7578
+ static #forbidConstructor = false;
7579
+ #puppeteer;
7580
+ #playwrightBrowserTypes;
7581
+ #patchrightBrowserTypes;
7582
+ #nextBrowserIdx;
7583
+ /**
7584
+ * Possible values are 'aix', 'darwin', 'freebsd','linux', 'openbsd', 'sunos', and 'win32'.
7585
+ */
7586
+ #osPlatform;
7587
+ constructor() {
7588
+ if (_LsdBrowserController.#forbidConstructor) {
7589
+ throw new Error("Only one LsdBrowserController instance can be created!");
7590
+ }
7591
+ this.#puppeteer = puppeteer;
7592
+ this.#playwrightBrowserTypes = {
7593
+ chromium: playwright.chromium,
7594
+ firefox: playwright.firefox,
7595
+ webkit: playwright.webkit
7596
+ };
7597
+ this.#patchrightBrowserTypes = {
7598
+ chromium: patchright.chromium,
7599
+ firefox: patchright.firefox,
7600
+ webkit: patchright.webkit
7601
+ };
7602
+ this.#osPlatform = os.platform();
7603
+ this.#nextBrowserIdx = 1;
7604
+ _LsdBrowserController.#forbidConstructor = true;
7605
+ }
7606
+ #playwrightBrowserType(browserType, connectFlag = false) {
7607
+ if (browserType === "chromium") {
7608
+ return this.#playwrightBrowserTypes.chromium;
7609
+ } else if (connectFlag) {
7610
+ throw new Error(`playwright only can connect to chromium browser, not support ${browserType} browser`);
7611
+ } else if (browserType === "firefox") {
7612
+ return this.#playwrightBrowserTypes.firefox;
7613
+ } else if (browserType === "webkit") {
7614
+ return this.#playwrightBrowserTypes.webkit;
7615
+ } else {
7616
+ throw new Error(`Invalid playwright browserType ${browserType}`);
7617
+ }
7618
+ }
7619
+ #patchrightBrowserType(browserType, connectFlag = false) {
7620
+ if (browserType === "chromium") {
7621
+ return this.#patchrightBrowserTypes.chromium;
7622
+ } else if (connectFlag) {
7623
+ throw new Error(`patchright only can connect to chromium browser, not support ${browserType} browser`);
7624
+ } else if (browserType === "firefox") {
7625
+ return this.#patchrightBrowserTypes.firefox;
7626
+ } else if (browserType === "webkit") {
7627
+ return this.#patchrightBrowserTypes.webkit;
7628
+ } else {
7629
+ throw new Error(`Invalid patchright browserType ${browserType}`);
7630
+ }
7631
+ }
7632
+ #puppeteerProduct(browserType) {
7633
+ if (browserType === "chromium") {
7634
+ return "chrome";
7635
+ } else {
7636
+ throw new Error(`Invalid puppeteer product ${browserType}`);
7637
+ }
7638
+ }
7639
+ setBrowserPlugin(browserControllerType, browserType, plugin) {
7640
+ if (browserControllerType === "puppeteer") {
7641
+ if (!PuppeteerBrowser.doesSupport(browserType)) {
7642
+ throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
7643
+ }
7644
+ this.#puppeteer = plugin;
7645
+ } else if (browserControllerType === "playwright") {
7646
+ if (!PlaywrightBrowser.doesSupport(browserType)) {
7647
+ throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
7648
+ }
7649
+ switch (browserType) {
7650
+ case "chromium":
7651
+ case "firefox":
7652
+ case "webkit":
7653
+ this.#playwrightBrowserTypes[browserType] = plugin;
7654
+ break;
7655
+ default:
7656
+ unreachable9(browserType);
7657
+ }
7658
+ } else if (browserControllerType === "patchright") {
7659
+ if (!PatchrightBrowser.doesSupport(browserType)) {
7660
+ throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
7661
+ }
7662
+ switch (browserType) {
7663
+ case "chromium":
7664
+ case "firefox":
7665
+ case "webkit":
7666
+ this.#patchrightBrowserTypes[browserType] = plugin;
7667
+ break;
7668
+ default:
7669
+ unreachable9(browserType);
7670
+ }
7671
+ } else if (browserControllerType === "camoufox") {
7672
+ if (!CamoufoxBrowser.doesSupport(browserType)) {
7673
+ throw new Error(`BrowserControllerType ${browserControllerType} doesnot support browserType ${browserType}`);
7674
+ }
7675
+ } else {
7676
+ unreachable9(browserControllerType);
7677
+ }
7678
+ return true;
7679
+ }
7680
+ async launch(browserControllerType, browserType, options) {
7681
+ let {
7682
+ closeFreePagesIntervalSeconds = 300,
7683
+ maxBrowserContextsPerBrowser = 10,
7684
+ maxPagesPerBrowserContext = 20,
7685
+ maxPageFreeSeconds = 900,
7686
+ maxViewportOfNewPage = true,
7687
+ proxy = null,
7688
+ timeout = 3e4,
7689
+ args = [],
7690
+ executablePath = "",
7691
+ maxWindowSize = true,
7692
+ headless = true,
7693
+ minBrowserContexts = 1,
7694
+ // incognito
7695
+ proxyPerBrowserContext = false,
7696
+ userDataDir = "",
7697
+ userAgent = ""
7698
+ } = options ? options : {};
7699
+ let browserPid = 0;
7700
+ const incognito = typeof options?.incognito === "boolean" ? options.incognito : browserControllerType === "puppeteer" ? false : true;
7701
+ const actOptions = { closeFreePagesIntervalSeconds, maxBrowserContextsPerBrowser, maxPagesPerBrowserContext, maxPageFreeSeconds, maxViewportOfNewPage, proxy, timeout, args, executablePath, maxWindowSize, headless, minBrowserContexts, incognito, proxyPerBrowserContext, userDataDir, userAgent };
7702
+ let idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--incoginto"));
7703
+ if (idx >= 0) {
7704
+ logwarn(`Please use options.incognito instead when launching new browser.`);
7705
+ args.splice(idx, 1);
7706
+ }
7707
+ idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--proxy-server"));
7708
+ if (idx >= 0) {
7709
+ logwarn(`Please use options.proxy instead when launching new browser.`);
7710
+ args.splice(idx, 1);
7711
+ }
7712
+ idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--user-data-dir"));
7713
+ if (idx >= 0) {
7714
+ logwarn(`Please use options.userDataDir instead when launching new browser.`);
7715
+ args.splice(idx, 1);
7716
+ }
7717
+ idx = args.findIndex((arg) => arg.toLowerCase().startsWith("--start-maximized"));
7718
+ if (idx >= 0) {
7719
+ logwarn(`Please use options.maxWindowSize instead when launching new browser.`);
7720
+ args.splice(idx, 1);
5907
7721
  }
5908
7722
  let lsdBrowser;
5909
7723
  if (browserControllerType === "playwright") {
@@ -6025,8 +7839,52 @@ var LsdBrowserController = class _LsdBrowserController {
6025
7839
  browserPid = browserProcess.pid;
6026
7840
  }
6027
7841
  lsdBrowser = new PuppeteerBrowser(browser, browserType, "launch", actOptions, this.#nextBrowserIdx++, browserPid);
7842
+ } else if (browserControllerType === "camoufox") {
7843
+ const launchOptions = { headless, timeout };
7844
+ if (executablePath) {
7845
+ launchOptions.executable_path = executablePath;
7846
+ }
7847
+ if (maxWindowSize) {
7848
+ args.push("--start-maximized");
7849
+ }
7850
+ if (proxy?.proxyUrl && proxy.proxyUrl !== "local") {
7851
+ const { proxyUrl: server, username, password } = proxy;
7852
+ launchOptions.proxy = { server, username, password };
7853
+ }
7854
+ if (userDataDir) {
7855
+ launchOptions.user_data_dir = userDataDir;
7856
+ launchOptions.persistent_context = true;
7857
+ }
7858
+ if (args.length > 0) {
7859
+ launchOptions.args = args;
7860
+ }
7861
+ if (typeof options.geoip === "boolean" || typeof options.geoip === "string") {
7862
+ launchOptions.geoip = options.geoip;
7863
+ actOptions.geoip = options.geoip;
7864
+ }
7865
+ if (typeof options.mainWorldEval === "boolean") {
7866
+ launchOptions.main_world_eval = options.mainWorldEval;
7867
+ actOptions.mainWorldEval = options.mainWorldEval;
7868
+ }
7869
+ if (Array.isArray(options.addons) && options.addons.length > 0 && options.addons.every((addon) => typeof addon === "string")) {
7870
+ launchOptions.addons = options.addons;
7871
+ actOptions.addons = options.addons;
7872
+ }
7873
+ if (options.launchMethod === "launchServer") {
7874
+ const browserServer = await launchServer(launchOptions);
7875
+ const process = browserServer.process();
7876
+ if (process?.pid) {
7877
+ browserPid = process.pid;
7878
+ }
7879
+ const wsEndpoint = browserServer.wsEndpoint();
7880
+ const browser = await playwright.firefox.connect(wsEndpoint);
7881
+ lsdBrowser = new CamoufoxBrowser(browser, browserType, "launch", actOptions, this.#nextBrowserIdx++, browserPid);
7882
+ } else {
7883
+ const browser = await Camoufox(launchOptions);
7884
+ lsdBrowser = new CamoufoxBrowser(browser, browserType, "launch", actOptions, this.#nextBrowserIdx++, browserPid);
7885
+ }
6028
7886
  } else {
6029
- throw new Error(`Invalid browserControllerType: ${browserControllerType} in launch`);
7887
+ unreachable9(browserControllerType);
6030
7888
  }
6031
7889
  for (let i = lsdBrowser.browserContexts().length; i < minBrowserContexts; i++) {
6032
7890
  await lsdBrowser.newBrowserContext();
@@ -6066,7 +7924,7 @@ var LsdBrowserController = class _LsdBrowserController {
6066
7924
  const browser = await playwrightBrowserType.connectOverCDP(browserUrl);
6067
7925
  const lsdBrowser = new PlaywrightBrowser(browser, browserType, "connect", actOptions, this.#nextBrowserIdx++, browserPid);
6068
7926
  return lsdBrowser;
6069
- } else if (browserControllerType === "patchright") {
7927
+ } else if (browserControllerType === "patchright" || browserControllerType === "camoufox") {
6070
7928
  const patchrightBrowserType = this.#patchrightBrowserType(browserType, true);
6071
7929
  const browser = await patchrightBrowserType.connectOverCDP(browserUrl);
6072
7930
  const lsdBrowser = new PatchrightBrowser(browser, browserType, "connect", actOptions, this.#nextBrowserIdx++, browserPid);
@@ -6078,7 +7936,7 @@ var LsdBrowserController = class _LsdBrowserController {
6078
7936
  ;
6079
7937
  return lsdBrowser;
6080
7938
  } else {
6081
- throw new Error(`Invalid browserControllerType: ${browserControllerType} in connect`);
7939
+ unreachable9(browserControllerType);
6082
7940
  }
6083
7941
  }
6084
7942
  async newApiContext(options = {}) {