@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.
@@ -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
+ };
@@ -1,145 +1,15 @@
1
1
  import { Page } from "playwright";
2
2
  import { join } from "node:path";
3
-
4
- const registerInstallButtonListener = (page: Page, log: typeof console.log) => {
5
- // as soon as it appear, without blocking flow
6
- // accept installing plugins
7
- const installDialog = page.locator("#addonConfirmInstallDialog");
8
- const installBtn = installDialog.locator(".okButton");
9
- installBtn
10
- .waitFor({
11
- timeout: 0,
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
- const text = await progessBar.getAttribute("value");
239
- const textAsNumber = parseFloat(text);
240
- const finalText = Number.isNaN(textAsNumber) ? 0 : textAsNumber;
241
- log("progress", `${finalText * 100}%`);
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
- clearTimeout(progressInterval);
138
+ clearInterval(progressInterval);
257
139
 
258
140
  await page.getByRole("button", { name: "Menu" }).click();
259
141
  await page.getByRole("menuitem", { name: "Project" }).click();