forked from TrevorSundberg/puppeteer-in-electron
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
154 lines (137 loc) · 5.01 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import getPort from "get-port";
import http from "http";
import retry from "async-retry";
import uuid from "uuid";
type App = import("electron").App;
type BrowserWindow = import("electron").BrowserWindow;
type BrowserView = import("electron").BrowserView;
type puppeteer = typeof import("puppeteer-core");
type Browser = import("puppeteer-core").Browser;
const readJson = async (port: string): Promise<any> => new Promise((resolve, reject) => {
let json = "";
const request = http.request(
{
host: "127.0.0.1",
path: "/json/version",
port
},
(response) => {
response.on("error", reject);
response.on("data", (chunk: Buffer) => {
json += chunk.toString();
});
response.on("end", () => resolve(JSON.parse(json)));
}
);
request.on("error", reject);
request.end();
});
/**
* Initialize the electron app to accept puppeteer/DevTools connections.
* Must be called at startup before the electron app is ready.
* @param {App} app The app imported from electron.
* @param {number} port Port to host the DevTools websocket connection.
*/
export const initialize = async (app: App, port: number = 0) => {
if (!app) {
throw new Error("The parameter 'app' was not passed in. " +
"This may indicate that you are running in node rather than electron.");
}
if (app.isReady()) {
throw new Error("Must be called at startup before the electron app is ready.");
}
if (port < 0 || port > 65535) {
throw new Error(`Invalid port ${port}.`);
}
if (app.commandLine.getSwitchValue("remote-debugging-port")) {
throw new Error("The electron application is already listening on a port. Double `initialize`?");
}
const actualPort = port === 0 ? await getPort() : port;
app.commandLine.appendSwitch(
"remote-debugging-port",
`${actualPort}`
);
app.commandLine.appendSwitch(
"remote-debugging-address",
"127.0.0.1"
);
const electronMajor = parseInt(
app.getVersion().split(".")[0],
10
);
// NetworkService crashes in electron 6.
if (electronMajor >= 7) {
app.commandLine.appendSwitch(
"enable-features",
"NetworkService"
);
}
};
/**
* Connects puppeteer to the electron app. Must call {@link initialize} before connecting.
* When connecting multiple times, you use the same port.
* @param {App} app The app imported from electron.
* @param {puppeteer} puppeteer The imported puppeteer namespace.
* @returns {Promise<Browser>} An object containing the puppeteer browser, the port, and json received from DevTools.
*/
export const connect = async (app: App, puppeteer: puppeteer) => {
if (!puppeteer) {
throw new Error("The parameter 'puppeteer' was not passed in.");
}
const port = app.commandLine.getSwitchValue("remote-debugging-port");
if (!port) {
throw new Error("The electron application was not setup to listen on a port. Was `initialize` called at startup?");
}
await app.whenReady;
const json = await retry(() => readJson(port));
const browser = await puppeteer.connect({
browserWSEndpoint: json.webSocketDebuggerUrl,
defaultViewport: null
});
return browser;
};
/**
* Given a BrowserWindow, find the corresponding puppeteer Page. It is undefined if external operations
* occur on the page whilst we are attempting to find it. A url/file must be loaded on the window for it to be found.
* If no url is loaded, the parameter 'allowBlankNavigate' allows us to load "about:blank" first.
* @param {Browser} browser The puppeteer browser instance obtained from calling |connect|.
* @param {BrowserWindow} window The browser window for which we want to find the corresponding puppeteer Page.
* @param {boolean} allowBlankNavigate If no url is loaded, allow us to load "about:blank" so that we may find the Page.
* @returns {Promise<Page>} The page that corresponds with the BrowserWindow.
*/
export const getPage = async (
browser: Browser,
window: BrowserWindow | BrowserView,
allowBlankNavigate: boolean = true
) => {
if (!browser) {
throw new Error("The parameter 'browser' was not passed in.");
}
if (!window) {
throw new Error("The parameter 'window' was not passed in.");
}
if (window.webContents.getURL() === "") {
if (allowBlankNavigate) {
await window.webContents.loadURL("about:blank");
} else {
throw new Error("In order to get the puppeteer Page, we must be able " +
"to execute JavaScript which requires the window having loaded a URL.");
}
}
const guid = uuid.v4();
await window.webContents.executeJavaScript(`window.puppeteer = "${guid}"`);
const pages = await browser.pages();
const guids = await Promise.all(pages.map((testPage) => testPage.evaluate("window.puppeteer")));
const index = guids.findIndex((testGuid) => testGuid === guid);
await window.webContents.executeJavaScript("delete window.puppeteer");
const page = pages[index];
if (!page) {
throw new Error("Unable to find puppeteer Page from BrowserWindow. Please report this.");
}
return page;
};
export default {
connect,
getPage,
initialize
};