@pipelab/plugin-construct 1.0.0-beta.20 → 1.0.0-beta.23

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,46 +20,68 @@ 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(async ({ userID, token }) => {
47
+ return new Promise<void>((resolve, reject) => {
48
+ const request = indexedDB.open("localforage", 1);
49
+ request.onerror = () => reject(new Error("Failed to open DB"));
50
+ request.onsuccess = (e: any) => {
51
+ const db = e.target.result;
52
+ try {
53
+ const transaction = db.transaction(["keyvaluepairs"], "readwrite");
54
+ const store = transaction.objectStore("keyvaluepairs");
55
+ const putRequest = store.put({ userID, token }, "login-data");
56
+ putRequest.onsuccess = () => resolve();
57
+ putRequest.onerror = () => reject(new Error("Failed to put item"));
58
+ } catch (err) {
59
+ reject(err);
60
+ }
61
+ };
62
+ request.onupgradeneeded = (e: any) => {
63
+ const db = e.target.result;
64
+ db.createObjectStore("keyvaluepairs");
65
+ };
66
+ });
67
+ }, { userID, token });
68
+ log("Credentials injected successfully.");
69
+ }
70
+
153
71
  let url = "https://editor.construct.net/";
154
72
  if (version) {
155
73
  url += version;
156
74
  }
157
75
  log("Navigating to URL", url);
158
- // const serviceWorkerPromise = page.waitForEvent("serviceworker");
159
76
  await page.goto(url);
160
77
  log("after navigating");
161
78
 
162
- // const serviceworker = await serviceWorkerPromise;
163
79
  registerWelcomeToConstructListener(page, log);
164
80
  registerNewVersionAvailableListener(page, log);
81
+ registerNotNowListener(page, log);
165
82
 
166
83
  log("after event");
167
84
 
168
- await page.waitForTimeout(2000);
169
- log("after wait");
170
-
171
- if (username && password) {
172
- log("Authenticating");
173
- await page.getByTitle("User account").locator("ui-icon").click();
174
- await page.getByRole("menuitem", { name: "Log in" }).locator("span").click();
175
- await page.frameLocator("#loginDialog iframe").getByLabel("Username").fill(username);
176
- await page.frameLocator("#loginDialog iframe").getByLabel("Password").fill(password);
177
-
178
- const tokenPromise = page.waitForResponse(/https:\/\/account.*\.construct\.net\/login.json/i);
179
-
180
- await page.frameLocator("#loginDialog iframe").getByRole("button", { name: "Log in" }).click();
181
-
182
- const response = await tokenPromise;
183
- const jsonResponse = await response.json();
184
-
185
- if (jsonResponse.request.status === "error") {
186
- await page.close();
187
-
188
- throw new Error("Invalid credentials");
189
- }
190
- log("Authenticated");
191
- }
192
-
193
85
  await page.waitForTimeout(2000);
194
86
 
195
87
  const [fileChooser] = await Promise.all([
@@ -223,24 +115,6 @@ export const script = async (
223
115
  log("progress", `${finalText * 100}%`);
224
116
  }, 500);
225
117
 
226
- // as soon as it appear, without blocking flow
227
- // ignore asking for update
228
- const notNowBtn = page.getByText("Not now");
229
- notNowBtn
230
- .waitFor({
231
- timeout: 0,
232
- })
233
- .then(async () => {
234
- return notNowBtn.click();
235
- })
236
- .then(() => {
237
- log("notNowBtn clicked");
238
- })
239
- .catch(async (e) => {
240
- if (e.message.includes("Target page, context or browser has been closed")) return;
241
- log("notNowBtn.click() failed", e.message);
242
- });
243
-
244
118
  registerInstallButtonListener(page, log);
245
119
  registerWebglErrorListener(page, log);
246
120
  registerMissingAddonErrorListener(page, log);
package/dist/index.cjs CHANGED
@@ -151604,7 +151604,7 @@ const zipFolder = async (from, to, log) => {
151604
151604
  });
151605
151605
  };
151606
151606
  //#endregion
151607
- //#region src/assets/script.ts
151607
+ //#region src/assets/listeners.ts
151608
151608
  const registerInstallButtonListener = (page, log) => {
151609
151609
  const installBtn = page.locator("#addonConfirmInstallDialog").locator(".okButton");
151610
151610
  installBtn.waitFor({ timeout: 0 }).then(async () => {
@@ -151681,7 +151681,60 @@ const registerNewVersionAvailableListener = (page, log) => {
151681
151681
  log("cancelButton.click() failed", e.message);
151682
151682
  });
151683
151683
  };
151684
+ const registerNotNowListener = (page, log) => {
151685
+ const notNowBtn = page.getByText("Not now");
151686
+ notNowBtn.waitFor({ timeout: 0 }).then(async () => {
151687
+ await notNowBtn.click();
151688
+ log("notNowBtn clicked");
151689
+ }).catch(async (e) => {
151690
+ if (e.message.includes("Target page, context or browser has been closed")) return;
151691
+ log("notNowBtn.click() failed", e.message);
151692
+ });
151693
+ };
151694
+ //#endregion
151695
+ //#region src/assets/script.ts
151684
151696
  const script = async (page, log, filePath, username, password, version, downloadDir) => {
151697
+ if (username && password) {
151698
+ log("Directly authenticating via Construct 3 account API...");
151699
+ const formData = new FormData();
151700
+ formData.append("username", username);
151701
+ formData.append("password", password);
151702
+ formData.append("productType", "games");
151703
+ const json = await (await fetch("https://account.construct.net/login.json", {
151704
+ method: "POST",
151705
+ body: formData
151706
+ })).json();
151707
+ if (json.request.status !== "ok") throw new Error(json.request.errorMessage || "Invalid credentials");
151708
+ const { userID, token } = json.response;
151709
+ log("API login successful, injecting credentials into browser context...");
151710
+ await page.goto("https://account.construct.net/");
151711
+ await page.evaluate(async ({ userID, token }) => {
151712
+ return new Promise((resolve, reject) => {
151713
+ const request = indexedDB.open("localforage", 1);
151714
+ request.onerror = () => reject(/* @__PURE__ */ new Error("Failed to open DB"));
151715
+ request.onsuccess = (e) => {
151716
+ const db = e.target.result;
151717
+ try {
151718
+ const putRequest = db.transaction(["keyvaluepairs"], "readwrite").objectStore("keyvaluepairs").put({
151719
+ userID,
151720
+ token
151721
+ }, "login-data");
151722
+ putRequest.onsuccess = () => resolve();
151723
+ putRequest.onerror = () => reject(/* @__PURE__ */ new Error("Failed to put item"));
151724
+ } catch (err) {
151725
+ reject(err);
151726
+ }
151727
+ };
151728
+ request.onupgradeneeded = (e) => {
151729
+ e.target.result.createObjectStore("keyvaluepairs");
151730
+ };
151731
+ });
151732
+ }, {
151733
+ userID,
151734
+ token
151735
+ });
151736
+ log("Credentials injected successfully.");
151737
+ }
151685
151738
  let url = "https://editor.construct.net/";
151686
151739
  if (version) url += version;
151687
151740
  log("Navigating to URL", url);
@@ -151689,24 +151742,9 @@ const script = async (page, log, filePath, username, password, version, download
151689
151742
  log("after navigating");
151690
151743
  registerWelcomeToConstructListener(page, log);
151691
151744
  registerNewVersionAvailableListener(page, log);
151745
+ registerNotNowListener(page, log);
151692
151746
  log("after event");
151693
151747
  await page.waitForTimeout(2e3);
151694
- log("after wait");
151695
- if (username && password) {
151696
- log("Authenticating");
151697
- await page.getByTitle("User account").locator("ui-icon").click();
151698
- await page.getByRole("menuitem", { name: "Log in" }).locator("span").click();
151699
- await page.frameLocator("#loginDialog iframe").getByLabel("Username").fill(username);
151700
- await page.frameLocator("#loginDialog iframe").getByLabel("Password").fill(password);
151701
- const tokenPromise = page.waitForResponse(/https:\/\/account.*\.construct\.net\/login.json/i);
151702
- await page.frameLocator("#loginDialog iframe").getByRole("button", { name: "Log in" }).click();
151703
- if ((await (await tokenPromise).json()).request.status === "error") {
151704
- await page.close();
151705
- throw new Error("Invalid credentials");
151706
- }
151707
- log("Authenticated");
151708
- }
151709
- await page.waitForTimeout(2e3);
151710
151748
  const [fileChooser] = await Promise.all([page.waitForEvent("filechooser"), page.keyboard.press("ControlOrMeta+O")]);
151711
151749
  log("filechooser");
151712
151750
  console.log("filePath", filePath);
@@ -151722,15 +151760,6 @@ const script = async (page, log, filePath, username, password, version, download
151722
151760
  const textAsNumber = parseFloat(text);
151723
151761
  log("progress", `${(Number.isNaN(textAsNumber) ? 0 : textAsNumber) * 100}%`);
151724
151762
  }, 500);
151725
- const notNowBtn = page.getByText("Not now");
151726
- notNowBtn.waitFor({ timeout: 0 }).then(async () => {
151727
- return notNowBtn.click();
151728
- }).then(() => {
151729
- log("notNowBtn clicked");
151730
- }).catch(async (e) => {
151731
- if (e.message.includes("Target page, context or browser has been closed")) return;
151732
- log("notNowBtn.click() failed", e.message);
151733
- });
151734
151763
  registerInstallButtonListener(page, log);
151735
151764
  registerWebglErrorListener(page, log);
151736
151765
  registerMissingAddonErrorListener(page, log);
@@ -151772,7 +151801,7 @@ const { LOCALAPPDATA, XDG_CONFIG_HOME } = process.env;
151772
151801
  const isCI = process.env.CI === "true";
151773
151802
  let baseProfile;
151774
151803
  if (platform === "win32") baseProfile = (0, node_path.join)(LOCALAPPDATA ?? "", "Google", "Chrome", "User Data");
151775
- else if (platform === "linux") baseProfile = (0, node_path.join)(XDG_CONFIG_HOME ?? "", "google-chrome");
151804
+ else if (platform === "linux") baseProfile = (0, node_path.join)(XDG_CONFIG_HOME && XDG_CONFIG_HOME.trim() !== "" ? XDG_CONFIG_HOME : (0, node_path.join)((0, node_os.homedir)(), ".config"), "google-chrome");
151776
151805
  else if (platform === "darwin") baseProfile = (0, node_path.join)((0, node_os.homedir)(), "Library", "Application Support", "Google", "Chrome");
151777
151806
  const sharedParams = {
151778
151807
  username: createStringParam("", {
@@ -151867,7 +151896,14 @@ const exportc3p = async (file, { cwd, log, inputs, setOutput, paths, abortSignal
151867
151896
  await (0, node_fs_promises.mkdir)(customProfile, { recursive: true });
151868
151897
  const indexedDbPathSource = (0, node_path.join)(newInputs.customProfile, "Default", "IndexedDB");
151869
151898
  const indexedDbPathDestination = (0, node_path.join)(customProfile, "Default", "IndexedDB");
151870
- for (const p of ["https_editor.construct.net_0.indexeddb.blob", "https_editor.construct.net_0.indexeddb.leveldb"]) await (0, node_fs_promises.cp)((0, node_path.join)(indexedDbPathSource, p), (0, node_path.join)(indexedDbPathDestination, p), { recursive: true });
151899
+ await (0, node_fs_promises.mkdir)(indexedDbPathDestination, { recursive: true });
151900
+ for (const p of ["https_editor.construct.net_0.indexeddb.blob", "https_editor.construct.net_0.indexeddb.leveldb"]) {
151901
+ const from = (0, node_path.join)(indexedDbPathSource, p);
151902
+ const to = (0, node_path.join)(indexedDbPathDestination, p);
151903
+ try {
151904
+ await (0, node_fs_promises.cp)(from, to, { recursive: true });
151905
+ } catch (e) {}
151906
+ }
151871
151907
  browserContext = await browserInstance.launchPersistentContext(customProfile, {
151872
151908
  headless,
151873
151909
  locale: "en-US",