Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
"com.foxdebug.acode.rk.customtabs": {},
"com.foxdebug.acode.rk.plugin.plugincontext": {},
"cordova-plugin-system": {},
"com.foxdebug.acode.rk.auth": {},
"com.foxdebug.acode.rk.exec.proot": {},
"cordova-plugin-iap": {}
"com.foxdebug.acode.rk.auth": {}
},
"platforms": [
"android"
Expand Down Expand Up @@ -72,7 +70,6 @@
"babel-loader": "^10.0.0",
"com.foxdebug.acode.rk.auth": "file:src/plugins/auth",
"com.foxdebug.acode.rk.customtabs": "file:src/plugins/custom-tabs",
"com.foxdebug.acode.rk.exec.proot": "file:src/plugins/proot",
"com.foxdebug.acode.rk.exec.terminal": "file:src/plugins/terminal",
"com.foxdebug.acode.rk.plugin.plugincontext": "file:src/plugins/pluginContext",
"cordova-android": "^15.0.0",
Expand All @@ -83,7 +80,6 @@
"cordova-plugin-device": "^2.1.0",
"cordova-plugin-file": "^8.1.3",
"cordova-plugin-ftp": "file:src/plugins/ftp",
"cordova-plugin-iap": "file:src/plugins/iap",
"cordova-plugin-sdcard": "file:src/plugins/sdcard",
"cordova-plugin-server": "file:src/plugins/server",
"cordova-plugin-sftp": "file:src/plugins/sftp",
Expand Down
22 changes: 22 additions & 0 deletions src/pages/plugin/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import alert from "dialogs/alert";
import loader from "dialogs/loader";
import purchaseListener from "handlers/purchase";
import actionStack from "lib/actionStack";
import auth from "lib/auth";
import config from "lib/config";
import installPlugin from "lib/installPlugin";
import InstallState from "lib/installState";
Expand Down Expand Up @@ -252,6 +253,27 @@ export default async function PluginInclude(
const oldText = $button.textContent;

try {
if (!config.IAP_AVAILABLE) {
const user = await auth.getLoggedInUser();
if (!user) {
CustomTabs.open(
`${config.BASE_URL}/login?redirect=app`,
{ showTitle: true },
() => {},
() => {},
);
return;
}

CustomTabs.open(
`${config.BASE_URL}/plugin/${plugin.id}`,
{ showTitle: true },
() => {},
() => {},
);
return;
}
Comment on lines +256 to +275
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dead code: !config.IAP_AVAILABLE block in buy() is never reached

When !config.IAP_AVAILABLE, plugin.view.js's Buttons component renders the buy button with onclick={openPluginWebsite} (a local function), not with the buy prop. So this guard block inside buy() will never execute from the plugin detail page. The redirect logic for this scenario lives entirely in openPluginWebsite; this copy adds confusion without adding safety.


if (!product) throw new Error("Product not found");
const apiStatus = await helpers.checkAPIStatus();

Expand Down
34 changes: 34 additions & 0 deletions src/pages/plugin/plugin.view.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import alert from "dialogs/alert";
import DOMPurify from "dompurify";
import Ref from "html-tag-js/ref";
import actionStack from "lib/actionStack";
import auth from "lib/auth";
import config from "lib/config";
import helpers from "utils/helpers";
import Url from "utils/Url";
Expand Down Expand Up @@ -271,6 +272,7 @@ function handleTabClick(e) {
}

function Buttons({
id,
name,
isPaid,
installed,
Expand All @@ -283,6 +285,25 @@ function Buttons({
minVersionCode,
isSupported = true,
}) {
async function openPluginWebsite() {
const user = await auth.getLoggedInUser();
if (!user) {
CustomTabs.open(
`${config.BASE_URL}/login?redirect=app`,
{ showTitle: true },
() => {},
() => {},
);
return;
}

CustomTabs.open(
`${config.BASE_URL}/plugin/${id}`,
{ showTitle: true },
() => {},
() => {},
);
}
Comment on lines +288 to +306
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unhandled promise rejection in openPluginWebsite

openPluginWebsite is an async function called directly from an onclick handler, but it has no try/catch. If auth.getLoggedInUser() rejects (e.g. network error, token parse failure), the rejection is silently swallowed by the browser and the user sees nothing. Add a try/catch and surface the error the same way the rest of the file does (helpers.error or alert).

if (!isSupported) {
return (
<div
Expand Down Expand Up @@ -346,6 +367,19 @@ function Buttons({
);
}

if (!config.IAP_AVAILABLE && isPaid && !purchased && price) {
return (
<button
data-type="buy"
className="btn btn-install"
onclick={openPluginWebsite}
>
<i className="icon open_in_browser"></i>
{price}
</button>
);
}
Comment on lines +370 to +381
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No loading/disabled state on the web-redirect buy button

openPluginWebsite performs an async auth.getLoggedInUser() call before opening the browser tab, but the button is never disabled during that window. Multiple rapid taps will queue multiple CustomTabs.open calls, potentially opening several browser tabs. Setting button.disabled = true at the start of openPluginWebsite (or mirroring how the IAP buy path sets loading text) would prevent this.


if (isPaid && !purchased && price) {
return (
<button data-type="buy" className="btn btn-install" onclick={buy}>
Expand Down
1 change: 1 addition & 0 deletions src/pages/plugins/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export default function Item({
className="list-item"
data-action="open"
data-installed={(!!installed).toString()}
data-paid={(price > 0).toString()}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 data-paid attribute is written but never read

The attribute is set on the list item element but no code in the repository reads or selects by data-paid. If this is intended for future use or CSS styling, a brief comment would help clarify intent; otherwise it adds unnecessary noise to the DOM.

style={enabled === false ? { opacity: 0.6 } : {}}
>
<div className="plugin-header">
Expand Down
23 changes: 22 additions & 1 deletion src/sidebarApps/extensions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import alert from "dialogs/alert";
import prompt from "dialogs/prompt";
import select from "dialogs/select";
import purchaseListener from "handlers/purchase";
import auth from "lib/auth";
import config from "lib/config";
import InstallState from "lib/installState";
import loadPlugin from "lib/loadPlugin";
Expand Down Expand Up @@ -772,6 +773,27 @@ function ListItem({ icon, name, id, version, downloads, installed, source }) {
});

const isPaid = remotePlugin.price > 0;
if (isPaid && !config.IAP_AVAILABLE) {
const user = await auth.getLoggedInUser();
if (!user) {
CustomTabs.open(
`${config.BASE_URL}/login?redirect=app`,
{ showTitle: true },
() => {},
() => {},
);
return;
}

CustomTabs.open(
`${config.BASE_URL}/plugin/${remotePlugin.id}`,
{ showTitle: true },
() => {},
() => {},
);
return;
}
Comment on lines 775 to +795
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Purchased plugins always redirected to web when IAP is unavailable

When isPaid && !config.IAP_AVAILABLE, the code unconditionally redirects to the web without first checking remotePlugin.owned. In contrast, plugin.js sets purchased = remotePlugin.owned and only shows the buy flow for unpurchased plugins. A user who already paid for a plugin via the web redirect will hit this branch on every install attempt and be sent back to the web again rather than being allowed to install — they have no way to complete the install from the sidebar.

Consider checking remotePlugin.owned (or an equivalent field) before redirecting, as plugin.js does.


if (isPaid) {
[product] = await helpers.promisify(iap.getProducts, [
remotePlugin.sku,
Expand All @@ -781,7 +803,6 @@ function ListItem({ icon, name, id, version, downloads, installed, source }) {
purchaseToken = purchase?.purchaseToken;
}
}

if (isPaid && !purchaseToken) {
if (!product) throw new Error("Product not found");
const apiStatus = await helpers.checkAPIStatus();
Expand Down