@pipelab/plugin-construct 1.0.0-beta.22 → 1.0.0-beta.26
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/assets/listeners.ts +144 -0
- package/dist/assets/script.ts +72 -190
- package/dist/index.cjs +265 -134
- package/dist/index.mjs +205 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
|
|
3
|
+
export const registerInstallButtonListener = (page: Page, log: typeof console.log) => {
|
|
4
|
+
const installDialog = page.locator("#addonConfirmInstallDialog");
|
|
5
|
+
const installBtn = installDialog.locator(".okButton");
|
|
6
|
+
installBtn
|
|
7
|
+
.waitFor({
|
|
8
|
+
timeout: 0,
|
|
9
|
+
})
|
|
10
|
+
.then(async () => {
|
|
11
|
+
await installBtn.click();
|
|
12
|
+
log("installBtn clicked");
|
|
13
|
+
registerInstallButtonListener(page, log);
|
|
14
|
+
})
|
|
15
|
+
.catch(async (e) => {
|
|
16
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
17
|
+
log("installBtn.click() failed", e.message);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const registerSaveLoginExpiredistener = (page: Page, log: typeof console.log) => {
|
|
22
|
+
const installDialog = page.locator("#confirmDialog");
|
|
23
|
+
const cancelBtn = installDialog.locator(".cancelConfirmButton");
|
|
24
|
+
cancelBtn
|
|
25
|
+
.waitFor({
|
|
26
|
+
timeout: 0,
|
|
27
|
+
})
|
|
28
|
+
.then(async () => {
|
|
29
|
+
await cancelBtn.click();
|
|
30
|
+
log("cancelBtn clicked");
|
|
31
|
+
registerSaveLoginExpiredistener(page, log);
|
|
32
|
+
})
|
|
33
|
+
.catch(async (e) => {
|
|
34
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
35
|
+
log("cancelBtn.click() failed", e.message);
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const registerWebglErrorListener = (page: Page, log: typeof console.log) => {
|
|
40
|
+
const okDialog = page.locator("#okDialog");
|
|
41
|
+
const webglErrorButton = okDialog.locator(".okButton");
|
|
42
|
+
webglErrorButton
|
|
43
|
+
.waitFor({
|
|
44
|
+
timeout: 0,
|
|
45
|
+
})
|
|
46
|
+
.then(async () => {
|
|
47
|
+
const text = await okDialog.allInnerTexts();
|
|
48
|
+
|
|
49
|
+
if (text.join().toLowerCase().includes("webgl")) {
|
|
50
|
+
await webglErrorButton.click();
|
|
51
|
+
log("webglErrorButton clicked");
|
|
52
|
+
registerWebglErrorListener(page, log);
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
.catch(async (e) => {
|
|
56
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
57
|
+
log("webglErrorButton.click() failed", e.message);
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const registerDeprecatedFeatures = (page: Page, log: typeof console.log) => {
|
|
62
|
+
const deprecatedFeaturesDialog = page.locator("#deprecatedFeaturesDialog");
|
|
63
|
+
const okButton = deprecatedFeaturesDialog.locator(".okButton");
|
|
64
|
+
okButton
|
|
65
|
+
.waitFor({
|
|
66
|
+
timeout: 0,
|
|
67
|
+
})
|
|
68
|
+
.then(async () => {
|
|
69
|
+
await okButton.click();
|
|
70
|
+
log("okButton clicked");
|
|
71
|
+
registerDeprecatedFeatures(page, log);
|
|
72
|
+
})
|
|
73
|
+
.catch(async (e) => {
|
|
74
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
75
|
+
log("deprecatedFeatures.okButton.click() failed", e.message);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const registerWelcomeToConstructListener = (page: Page, log: typeof console.log) => {
|
|
80
|
+
const welcomeTourDialog = page.locator("#welcomeTourDialog");
|
|
81
|
+
const okButton = welcomeTourDialog.locator(".noThanksLink");
|
|
82
|
+
okButton
|
|
83
|
+
.waitFor({
|
|
84
|
+
timeout: 0,
|
|
85
|
+
})
|
|
86
|
+
.then(async () => {
|
|
87
|
+
await okButton.click();
|
|
88
|
+
log("okButton clicked");
|
|
89
|
+
})
|
|
90
|
+
.catch(async (e) => {
|
|
91
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
92
|
+
log("welcomeTour.okButton.click() failed", e.message);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const registerMissingAddonErrorListener = (page: Page, log: typeof console.log) => {
|
|
97
|
+
const okDialog = page.locator("#missingAddonsDialog");
|
|
98
|
+
const webglErrorButton = okDialog.locator(".okButton");
|
|
99
|
+
webglErrorButton
|
|
100
|
+
.waitFor({
|
|
101
|
+
timeout: 0,
|
|
102
|
+
})
|
|
103
|
+
.then(async () => {
|
|
104
|
+
throw new Error("Missing addon. You should bundle addons with your project");
|
|
105
|
+
})
|
|
106
|
+
.catch(async (e) => {
|
|
107
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
108
|
+
log("missingAddon.okButton.waitFor() failed", e.message);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const registerNewVersionAvailableListener = (page: Page, log: typeof console.log) => {
|
|
113
|
+
const newVersionAvailableDialog = page.locator("#confirmDialog");
|
|
114
|
+
const cancelButton = newVersionAvailableDialog.locator(".cancelConfirmButton");
|
|
115
|
+
cancelButton
|
|
116
|
+
.waitFor({
|
|
117
|
+
timeout: 0,
|
|
118
|
+
})
|
|
119
|
+
.then(async () => {
|
|
120
|
+
await cancelButton.click();
|
|
121
|
+
log("cancelButton clicked");
|
|
122
|
+
registerNewVersionAvailableListener(page, log);
|
|
123
|
+
})
|
|
124
|
+
.catch(async (e) => {
|
|
125
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
126
|
+
log("cancelButton.click() failed", e.message);
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const registerNotNowListener = (page: Page, log: typeof console.log) => {
|
|
131
|
+
const notNowBtn = page.getByText("Not now");
|
|
132
|
+
notNowBtn
|
|
133
|
+
.waitFor({
|
|
134
|
+
timeout: 0,
|
|
135
|
+
})
|
|
136
|
+
.then(async () => {
|
|
137
|
+
await notNowBtn.click();
|
|
138
|
+
log("notNowBtn clicked");
|
|
139
|
+
})
|
|
140
|
+
.catch(async (e) => {
|
|
141
|
+
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
142
|
+
log("notNowBtn.click() failed", e.message);
|
|
143
|
+
});
|
|
144
|
+
};
|
package/dist/assets/script.ts
CHANGED
|
@@ -1,145 +1,15 @@
|
|
|
1
1
|
import { Page } from "playwright";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.then(async () => {
|
|
14
|
-
await installBtn.click();
|
|
15
|
-
log("installBtn clicked");
|
|
16
|
-
registerInstallButtonListener(page, log);
|
|
17
|
-
})
|
|
18
|
-
.catch(async (e) => {
|
|
19
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
20
|
-
log("installBtn.click() failed", e.message);
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const registerSaveLoginExpiredistener = (page: Page, log: typeof console.log) => {
|
|
25
|
-
// as soon as it appear, without blocking flow
|
|
26
|
-
// accept installing plugins
|
|
27
|
-
const installDialog = page.locator("#confirmDialog");
|
|
28
|
-
const cancelBtn = installDialog.locator(".cancelConfirmButton");
|
|
29
|
-
cancelBtn
|
|
30
|
-
.waitFor({
|
|
31
|
-
timeout: 0,
|
|
32
|
-
})
|
|
33
|
-
.then(async () => {
|
|
34
|
-
await cancelBtn.click();
|
|
35
|
-
log("cancelBtn clicked");
|
|
36
|
-
registerSaveLoginExpiredistener(page, log);
|
|
37
|
-
})
|
|
38
|
-
.catch(async (e) => {
|
|
39
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
40
|
-
log("cancelBtn.click() failed", e.message);
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const registerWebglErrorListener = (page: Page, log: typeof console.log) => {
|
|
45
|
-
// as soon as it appear, without blocking flow
|
|
46
|
-
// ignore webgl error
|
|
47
|
-
const okDialog = page.locator("#okDialog");
|
|
48
|
-
const webglErrorButton = okDialog.locator(".okButton");
|
|
49
|
-
webglErrorButton
|
|
50
|
-
.waitFor({
|
|
51
|
-
timeout: 0,
|
|
52
|
-
})
|
|
53
|
-
.then(async () => {
|
|
54
|
-
const text = await okDialog.allInnerTexts();
|
|
55
|
-
|
|
56
|
-
if (text.join().toLowerCase().includes("webgl")) {
|
|
57
|
-
await webglErrorButton.click();
|
|
58
|
-
log("webglErrorButton clicked");
|
|
59
|
-
registerWebglErrorListener(page, log);
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
.catch(async (e) => {
|
|
63
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
64
|
-
log("webglErrorButton.click() failed", e.message);
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
const registerDeprecatedFeatures = (page: Page, log: typeof console.log) => {
|
|
68
|
-
// as soon as it appear, without blocking flow
|
|
69
|
-
// ignore deprecated feature
|
|
70
|
-
const deprecatedFeaturesDialog = page.locator("#deprecatedFeaturesDialog");
|
|
71
|
-
const okButton = deprecatedFeaturesDialog.locator(".okButton");
|
|
72
|
-
okButton
|
|
73
|
-
.waitFor({
|
|
74
|
-
timeout: 0,
|
|
75
|
-
})
|
|
76
|
-
.then(async () => {
|
|
77
|
-
await okButton.click();
|
|
78
|
-
log("okButton clicked");
|
|
79
|
-
registerDeprecatedFeatures(page, log);
|
|
80
|
-
})
|
|
81
|
-
.catch(async (e) => {
|
|
82
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
83
|
-
log("deprecatedFeatures.okButton.click() failed", e.message);
|
|
84
|
-
});
|
|
85
|
-
};
|
|
86
|
-
const registerWelcomeToConstructListener = (page: Page, log: typeof console.log) => {
|
|
87
|
-
// as soon as it appear, without blocking flow
|
|
88
|
-
// ignore deprecated feature
|
|
89
|
-
const welcomeTourDialog = page.locator("#welcomeTourDialog");
|
|
90
|
-
const okButton = welcomeTourDialog.locator(".noThanksLink");
|
|
91
|
-
okButton
|
|
92
|
-
.waitFor({
|
|
93
|
-
timeout: 0,
|
|
94
|
-
})
|
|
95
|
-
.then(async () => {
|
|
96
|
-
await okButton.click();
|
|
97
|
-
log("okButton clicked");
|
|
98
|
-
// registerWelcomeToConstructListener(page, log); // usually only once
|
|
99
|
-
})
|
|
100
|
-
.catch(async (e) => {
|
|
101
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
102
|
-
log("welcomeTour.okButton.click() failed", e.message);
|
|
103
|
-
});
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const registerMissingAddonErrorListener = (page: Page, log: typeof console.log) => {
|
|
107
|
-
// as soon as it appear, without blocking flow
|
|
108
|
-
// ignore missing addon and throw
|
|
109
|
-
const okDialog = page.locator("#missingAddonsDialog");
|
|
110
|
-
const webglErrorButton = okDialog.locator(".okButton");
|
|
111
|
-
webglErrorButton
|
|
112
|
-
.waitFor({
|
|
113
|
-
timeout: 0,
|
|
114
|
-
})
|
|
115
|
-
.then(async () => {
|
|
116
|
-
throw new Error("Missing addon. You should bundle addons with your project");
|
|
117
|
-
})
|
|
118
|
-
.catch(async (e) => {
|
|
119
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
120
|
-
log("missingAddon.okButton.waitFor() failed", e.message);
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const registerNewVersionAvailableListener = (page: Page, log: typeof console.log) => {
|
|
125
|
-
// as soon as it appear, without blocking flow
|
|
126
|
-
// ignore new version available and throw
|
|
127
|
-
const newVersionAvailableDialog = page.locator("#confirmDialog");
|
|
128
|
-
const cancelButton = newVersionAvailableDialog.locator(".cancelConfirmButton");
|
|
129
|
-
cancelButton
|
|
130
|
-
.waitFor({
|
|
131
|
-
timeout: 0,
|
|
132
|
-
})
|
|
133
|
-
.then(async () => {
|
|
134
|
-
await cancelButton.click();
|
|
135
|
-
log("cancelButton clicked");
|
|
136
|
-
registerNewVersionAvailableListener(page, log);
|
|
137
|
-
})
|
|
138
|
-
.catch(async (e) => {
|
|
139
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
140
|
-
log("cancelButton.click() failed", e.message);
|
|
141
|
-
});
|
|
142
|
-
};
|
|
3
|
+
import {
|
|
4
|
+
registerInstallButtonListener,
|
|
5
|
+
registerSaveLoginExpiredistener,
|
|
6
|
+
registerWebglErrorListener,
|
|
7
|
+
registerDeprecatedFeatures,
|
|
8
|
+
registerWelcomeToConstructListener,
|
|
9
|
+
registerMissingAddonErrorListener,
|
|
10
|
+
registerNewVersionAvailableListener,
|
|
11
|
+
registerNotNowListener,
|
|
12
|
+
} from "./listeners.js";
|
|
143
13
|
|
|
144
14
|
export const script = async (
|
|
145
15
|
page: Page,
|
|
@@ -150,64 +20,71 @@ export const script = async (
|
|
|
150
20
|
version: string | undefined,
|
|
151
21
|
downloadDir: string,
|
|
152
22
|
) => {
|
|
23
|
+
if (username && password) {
|
|
24
|
+
log("Directly authenticating via Construct 3 account API...");
|
|
25
|
+
const formData = new FormData();
|
|
26
|
+
formData.append("username", username);
|
|
27
|
+
formData.append("password", password);
|
|
28
|
+
formData.append("productType", "games");
|
|
29
|
+
|
|
30
|
+
const res = await fetch("https://account.construct.net/login.json", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: formData,
|
|
33
|
+
});
|
|
34
|
+
const json = (await res.json()) as any;
|
|
35
|
+
if (json.request.status !== "ok") {
|
|
36
|
+
throw new Error(json.request.errorMessage || "Invalid credentials");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { userID, token } = json.response;
|
|
40
|
+
log("API login successful, injecting credentials into browser context...");
|
|
41
|
+
|
|
42
|
+
// Navigate to account.construct.net to set the origin context for IndexedDB
|
|
43
|
+
await page.goto("https://account.construct.net/");
|
|
44
|
+
|
|
45
|
+
// Inject token into IndexedDB
|
|
46
|
+
await page.evaluate(
|
|
47
|
+
async ({ userID, token }) => {
|
|
48
|
+
return new Promise<void>((resolve, reject) => {
|
|
49
|
+
const request = indexedDB.open("localforage", 1);
|
|
50
|
+
request.onerror = () => reject(new Error("Failed to open DB"));
|
|
51
|
+
request.onsuccess = (e: any) => {
|
|
52
|
+
const db = e.target.result;
|
|
53
|
+
try {
|
|
54
|
+
const transaction = db.transaction(["keyvaluepairs"], "readwrite");
|
|
55
|
+
const store = transaction.objectStore("keyvaluepairs");
|
|
56
|
+
const putRequest = store.put({ userID, token }, "login-data");
|
|
57
|
+
putRequest.onsuccess = () => resolve();
|
|
58
|
+
putRequest.onerror = () => reject(new Error("Failed to put item"));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
reject(err);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
request.onupgradeneeded = (e: any) => {
|
|
64
|
+
const db = e.target.result;
|
|
65
|
+
db.createObjectStore("keyvaluepairs");
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
{ userID, token },
|
|
70
|
+
);
|
|
71
|
+
log("Credentials injected successfully.");
|
|
72
|
+
}
|
|
73
|
+
|
|
153
74
|
let url = "https://editor.construct.net/";
|
|
154
75
|
if (version) {
|
|
155
76
|
url += version;
|
|
156
77
|
}
|
|
157
78
|
log("Navigating to URL", url);
|
|
158
|
-
// const serviceWorkerPromise = page.waitForEvent("serviceworker");
|
|
159
79
|
await page.goto(url);
|
|
160
80
|
log("after navigating");
|
|
161
81
|
|
|
162
|
-
// const serviceworker = await serviceWorkerPromise;
|
|
163
82
|
registerWelcomeToConstructListener(page, log);
|
|
164
83
|
registerNewVersionAvailableListener(page, log);
|
|
165
|
-
|
|
166
|
-
// as soon as it appear, without blocking flow
|
|
167
|
-
// ignore asking for update
|
|
168
|
-
const notNowBtn = page.getByText("Not now");
|
|
169
|
-
notNowBtn
|
|
170
|
-
.waitFor({
|
|
171
|
-
timeout: 0,
|
|
172
|
-
})
|
|
173
|
-
.then(async () => {
|
|
174
|
-
return notNowBtn.click();
|
|
175
|
-
})
|
|
176
|
-
.then(() => {
|
|
177
|
-
log("notNowBtn clicked");
|
|
178
|
-
})
|
|
179
|
-
.catch(async (e) => {
|
|
180
|
-
if (e.message.includes("Target page, context or browser has been closed")) return;
|
|
181
|
-
log("notNowBtn.click() failed", e.message);
|
|
182
|
-
});
|
|
84
|
+
registerNotNowListener(page, log);
|
|
183
85
|
|
|
184
86
|
log("after event");
|
|
185
87
|
|
|
186
|
-
await page.waitForTimeout(2000);
|
|
187
|
-
log("after wait");
|
|
188
|
-
|
|
189
|
-
if (username && password) {
|
|
190
|
-
log("Authenticating");
|
|
191
|
-
await page.getByTitle("User account").locator("ui-icon").click();
|
|
192
|
-
await page.getByRole("menuitem", { name: "Log in" }).locator("span").click();
|
|
193
|
-
await page.frameLocator("#loginDialog iframe").getByLabel("Username").fill(username);
|
|
194
|
-
await page.frameLocator("#loginDialog iframe").getByLabel("Password").fill(password);
|
|
195
|
-
|
|
196
|
-
const tokenPromise = page.waitForResponse(/https:\/\/account.*\.construct\.net\/login.json/i);
|
|
197
|
-
|
|
198
|
-
await page.frameLocator("#loginDialog iframe").getByRole("button", { name: "Log in" }).click();
|
|
199
|
-
|
|
200
|
-
const response = await tokenPromise;
|
|
201
|
-
const jsonResponse = await response.json();
|
|
202
|
-
|
|
203
|
-
if (jsonResponse.request.status === "error") {
|
|
204
|
-
await page.close();
|
|
205
|
-
|
|
206
|
-
throw new Error("Invalid credentials");
|
|
207
|
-
}
|
|
208
|
-
log("Authenticated");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
88
|
await page.waitForTimeout(2000);
|
|
212
89
|
|
|
213
90
|
const [fileChooser] = await Promise.all([
|
|
@@ -235,10 +112,15 @@ export const script = async (
|
|
|
235
112
|
log("Got loading progress dialog");
|
|
236
113
|
|
|
237
114
|
const progressInterval = setInterval(async () => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
115
|
+
try {
|
|
116
|
+
const text = await progessBar.getAttribute("value", { timeout: 100 });
|
|
117
|
+
if (text === null) return;
|
|
118
|
+
const textAsNumber = parseFloat(text);
|
|
119
|
+
const finalText = Number.isNaN(textAsNumber) ? 0 : textAsNumber;
|
|
120
|
+
log("progress", `${finalText * 100}%`);
|
|
121
|
+
} catch {
|
|
122
|
+
clearInterval(progressInterval);
|
|
123
|
+
}
|
|
242
124
|
}, 500);
|
|
243
125
|
|
|
244
126
|
registerInstallButtonListener(page, log);
|
|
@@ -253,7 +135,7 @@ export const script = async (
|
|
|
253
135
|
timeout: 0,
|
|
254
136
|
});
|
|
255
137
|
log("Got progress dialog to disapear");
|
|
256
|
-
|
|
138
|
+
clearInterval(progressInterval);
|
|
257
139
|
|
|
258
140
|
await page.getByRole("button", { name: "Menu" }).click();
|
|
259
141
|
await page.getByRole("menuitem", { name: "Project" }).click();
|