Skip to content

Commit c203c35

Browse files
authored
Plugin Dependencies (#1030)
* Added support for plugin dependencies * Fixed installPlugin.js lint issues * fix issues with installing dependencies * add: confirmation to install dependencies
1 parent 6c09882 commit c203c35

File tree

1 file changed

+174
-7
lines changed

1 file changed

+174
-7
lines changed

src/lib/installPlugin.js

Lines changed: 174 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
1+
import ajax from "@deadlyjack/ajax";
2+
import alert from "dialogs/alert";
3+
import confirm from "dialogs/confirm";
14
import loader from "dialogs/loader";
25
import fsOperation from "fileSystem";
6+
import purchaseListener from "handlers/purchase";
37
import JSZip from "jszip";
48
import Url from "utils/Url";
9+
import helpers from "utils/helpers";
510
import constants from "./constants";
611
import InstallState from "./installState";
712
import loadPlugin from "./loadPlugin";
813

14+
/** @type {import("dialogs/loader").Loader} */
15+
let loaderDialog;
16+
/** @type {Array<() => Promise<void>>} */
17+
let depsLoaders;
18+
919
/**
1020
* Installs a plugin.
1121
* @param {string} id
1222
* @param {string} name
1323
* @param {string} purchaseToken
24+
* @param {boolean} isDependency
1425
*/
15-
export default async function installPlugin(id, name, purchaseToken) {
16-
const title = name || "Plugin";
17-
const loaderDialog = loader.create(title, strings.installing);
26+
export default async function installPlugin(
27+
id,
28+
name,
29+
purchaseToken,
30+
isDependency,
31+
) {
32+
if (!isDependency) {
33+
loaderDialog = loader.create(name || "Plugin", strings.installing);
34+
depsLoaders = [];
35+
}
36+
1837
let pluginDir;
1938
let pluginUrl;
2039
let state;
@@ -43,7 +62,7 @@ export default async function installPlugin(id, name, purchaseToken) {
4362
}
4463

4564
try {
46-
loaderDialog.show();
65+
if (!isDependency) loaderDialog.show();
4766

4867
const plugin = await fsOperation(pluginUrl).readFile(
4968
undefined,
@@ -62,10 +81,39 @@ export default async function installPlugin(id, name, purchaseToken) {
6281
throw new Error(strings["invalid plugin"]);
6382
}
6483

84+
/** @type {{ dependencies: string[] }} */
6585
const pluginJson = JSON.parse(
6686
await zip.files["plugin.json"].async("text"),
6787
);
6888

89+
if (!isDependency && pluginJson.dependencies) {
90+
const manifests = await resolveDepsManifest(pluginJson.dependencies);
91+
92+
let titleText;
93+
if (manifests.length > 1) {
94+
titleText = "Acode wants to install the following dependencies:";
95+
} else {
96+
titleText = "Acode wants to install the following dependency:";
97+
}
98+
99+
const shouldInstall = await confirm(
100+
"Installer Notice",
101+
titleText +
102+
"<br /><br />" +
103+
manifests.map((value) => value.name).join(", "),
104+
true,
105+
);
106+
107+
if (shouldInstall) {
108+
for (const manifest of manifests) {
109+
const hasError = await resolveDep(manifest);
110+
if (hasError) throw new Error(strings.failed);
111+
}
112+
} else {
113+
return;
114+
}
115+
}
116+
69117
if (!pluginDir) {
70118
pluginJson.source = pluginUrl;
71119
id = pluginJson.id;
@@ -110,7 +158,18 @@ export default async function installPlugin(id, name, purchaseToken) {
110158

111159
// Wait for all files to be processed
112160
await Promise.allSettled(promises);
113-
await loadPlugin(id, true);
161+
162+
if (isDependency) {
163+
depsLoaders.push(async () => {
164+
await loadPlugin(id, true);
165+
});
166+
} else {
167+
for (const loader of depsLoaders) {
168+
await loader();
169+
}
170+
await loadPlugin(id, true);
171+
}
172+
114173
await state.save();
115174
deleteRedundantFiles(pluginDir, state);
116175
}
@@ -128,7 +187,9 @@ export default async function installPlugin(id, name, purchaseToken) {
128187
}
129188
throw err;
130189
} finally {
131-
loaderDialog.destroy();
190+
if (!isDependency) {
191+
loaderDialog.destroy();
192+
}
132193
}
133194
}
134195

@@ -160,6 +221,112 @@ async function createFileRecursive(parent, dir) {
160221
await createFileRecursive(newParent, dir);
161222
}
162223
}
224+
225+
/**
226+
* Resolves Dependencies Manifest with given ids.
227+
* @param {string[]} deps dependencies
228+
*/
229+
async function resolveDepsManifest(deps) {
230+
const resolved = [];
231+
for (const dependency of deps) {
232+
const remoteDependency = await fsOperation(
233+
constants.API_BASE,
234+
`plugin/${dependency}`,
235+
)
236+
.readFile("json")
237+
.catch(() => null);
238+
239+
if (!remoteDependency)
240+
throw new Error(`Unknown plugin dependency: ${dependency}`);
241+
242+
const version = await getInstalledPluginVersion(remoteDependency.id);
243+
if (remoteDependency?.version === version) continue;
244+
245+
if (remoteDependency.dependencies) {
246+
const manifests = await resolveDepsManifest(
247+
remoteDependency.dependencies,
248+
);
249+
resolved.push(manifests);
250+
}
251+
252+
resolved.push(remoteDependency);
253+
}
254+
255+
/**
256+
*
257+
* @param {string} id
258+
* @returns {Promise<string>} plugin version
259+
*/
260+
async function getInstalledPluginVersion(id) {
261+
if (await fsOperation(PLUGIN_DIR, id).exists()) {
262+
const plugin = await fsOperation(PLUGIN_DIR, id, "plugin.json").readFile(
263+
"json",
264+
);
265+
return plugin.version;
266+
}
267+
}
268+
269+
return resolved;
270+
}
271+
272+
/** Resolve dependency
273+
* @param {object} manifest
274+
* @returns {Promise<boolean>} has error
275+
*/
276+
async function resolveDep(manifest) {
277+
let purchaseToken;
278+
let product;
279+
let isPaid = false;
280+
281+
isPaid = manifest.price > 0;
282+
[product] = await helpers.promisify(iap.getProducts, [manifest.sku]);
283+
if (product) {
284+
const purchase = await getPurchase(product.productId);
285+
purchaseToken = purchase?.purchaseToken;
286+
}
287+
288+
if (isPaid && !purchaseToken) {
289+
if (!product) throw new Error("Product not found");
290+
const apiStatus = await helpers.checkAPIStatus();
291+
292+
if (!apiStatus) {
293+
alert(strings.error, strings.api_error);
294+
return true;
295+
}
296+
297+
iap.setPurchaseUpdatedListener(...purchaseListener(onpurchase, onerror));
298+
loaderDialog.setMessage(strings["loading..."]);
299+
await helpers.promisify(iap.purchase, product.json);
300+
301+
async function onpurchase(e) {
302+
const purchase = await getPurchase(product.productId);
303+
await ajax.post(Url.join(constants.API_BASE, "plugin/order"), {
304+
data: {
305+
id: manifest.id,
306+
token: purchase?.purchaseToken,
307+
package: BuildInfo.packageName,
308+
},
309+
});
310+
purchaseToken = purchase?.purchaseToken;
311+
}
312+
313+
async function onerror(error) {
314+
throw error;
315+
}
316+
}
317+
318+
loaderDialog.setMessage(
319+
`${strings.installing.replace("...", "")} ${manifest.name}...`,
320+
);
321+
await installPlugin(manifest.id, undefined, purchaseToken, true);
322+
323+
async function getPurchase(sku) {
324+
const purchases = await helpers.promisify(iap.getPurchases);
325+
const purchase = purchases.find((p) => p.productIds.includes(sku));
326+
return purchase;
327+
}
328+
}
329+
163330
/**
164331
*
165332
* @param {string} dir
@@ -181,7 +348,7 @@ async function listFileRecursive(dir, files) {
181348
* @param {Record<string, boolean>} files
182349
*/
183350
async function deleteRedundantFiles(pluginDir, state) {
184-
/** @type string[] */
351+
/** @type {string[]} */
185352
let files = [];
186353
await listFileRecursive(pluginDir, files);
187354

0 commit comments

Comments
 (0)