Skip to content

Commit ae760d9

Browse files
author
Ajit Kumar
committed
Refactor plugin installation and purchase handling
- Enhanced HandleIntent function to validate plugin installation requests. - Modified getLoggedInUser method to return a Promise. - Removed IAP_AVAILABLE from config and adjusted related logic. - Improved main.js to handle app installation source and define appInstallSource. - Refactored plugin page to streamline plugin installation and purchase logic. - Added external purchase handling in plugin view and buttons. - Updated IAP plugin to track availability status. - Enhanced ListItem component to manage plugin installation and purchase flow. - Introduced shouldAllowExternalPurchase helper function for better purchase logic.
1 parent 01dfe2a commit ae760d9

12 files changed

Lines changed: 275 additions & 2124 deletions

File tree

package-lock.json

Lines changed: 77 additions & 1978 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
"com.foxdebug.acode.rk.plugin.plugincontext": {},
4242
"cordova-plugin-system": {},
4343
"com.foxdebug.acode.rk.auth": {},
44-
"cordova-plugin-iap": {},
45-
"com.foxdebug.acode.rk.exec.proot": {}
44+
"com.foxdebug.acode.rk.exec.proot": {},
45+
"cordova-plugin-iap": {}
4646
},
4747
"platforms": [
4848
"android"
@@ -151,7 +151,6 @@
151151
"acorn": "^8.15.0",
152152
"autosize": "^6.0.1",
153153
"codemirror": "^6.0.2",
154-
"cordova": "13.0.0",
155154
"core-js": "^3.47.0",
156155
"dayjs": "^1.11.19",
157156
"dompurify": "^3.4.0",

src/handlers/intent.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@ export default async function HandleIntent(intent = {}) {
3535

3636
if (defaultPrevented) return;
3737

38-
if (module === "plugin") {
38+
if (module === "plugin" && action === "install") {
3939
const { default: Plugin } = await import("pages/plugin");
40+
41+
if (!value || !/^([a-z0-9\.]+)$/.test(value)) {
42+
return;
43+
}
44+
4045
const installed = await fsOperation(PLUGIN_DIR, value).exists();
4146
Plugin({ id: value, installed, install: action === "install" });
4247
}

src/lib/auth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class AuthService {
100100

101101
/**
102102
*
103-
* @returns {User}
103+
* @returns {Promise<User>}
104104
*/
105105
async getLoggedInUser() {
106106
if (loggedInUser) return loggedInUser;

src/lib/config.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ const config = {
3939
INSTAGRAM_URL: "https://www.instagram.com/foxbiz.io/",
4040
FOXBIZ_URL: "https://foxbiz.io",
4141

42-
//assume playstore build until proven otherwise
43-
IAP_AVAILABLE: true,
44-
4542
get HAS_PRO() {
4643
return hasPro;
4744
},
@@ -51,17 +48,4 @@ const config = {
5148
},
5249
};
5350

54-
55-
cordova.exec((installer) => {
56-
config.IAP_AVAILABLE =
57-
typeof iap !== "undefined" &&
58-
installer != null &&
59-
installer !== "null" &&
60-
installer === "com.android.vending";
61-
},
62-
(error) => {
63-
console.error(error);
64-
config.IAP_AVAILABLE = typeof iap !== "undefined";
65-
}, 'System', 'getInstaller', []);
66-
6751
export default config;

src/main.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import $_fileMenu from "views/file-menu.hbs";
6666
import $_menu from "views/menu.hbs";
6767
import auth, { loginEvents } from "./lib/auth";
6868

69+
const INSTALL_SOURCE_PLAY = "com.android.vending";
6970
const previousVersionCode = Number.parseInt(localStorage.versionCode, 10);
7071

7172
window.onload = Main;
@@ -195,6 +196,25 @@ async function onDeviceReady() {
195196

196197
startAd();
197198

199+
let installSource = INSTALL_SOURCE_PLAY;
200+
201+
try {
202+
installSource = await helpers.promisify(system.getInstaller);
203+
} catch (error) {
204+
console.error(error);
205+
}
206+
207+
Object.defineProperty(window, "appInstallSource", {
208+
get() {
209+
return installSource;
210+
},
211+
set() {
212+
console.warn("appInstallSource is readonly");
213+
},
214+
configurable: false,
215+
enumerable: false,
216+
});
217+
198218
try {
199219
await helpers.promisify(iap.startConnection).catch((e) => {
200220
window.log("error", "connection error");

src/pages/plugin/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
function plugin({ id, installed, install }, onInstall, onUninstall) {
1+
function plugin({ id, install }, onInstall, onUninstall) {
22
import(/* webpackChunkName: "plugins" */ "./plugin").then((res) => {
33
const Plugin = res.default;
4-
Plugin(id, installed, onInstall, onUninstall, install);
4+
Plugin(id, onInstall, onUninstall, install);
55
});
66
}
77

src/pages/plugin/plugin.js

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import fsOperation from "fileSystem";
33
import Page from "components/page";
44
import alert from "dialogs/alert";
55
import loader from "dialogs/loader";
6+
import { addIntentHandler, removeIntentHandler } from "handlers/intent";
67
import purchaseListener from "handlers/purchase";
78
import actionStack from "lib/actionStack";
8-
import auth from "lib/auth";
9+
import auth, { loginEvents } from "lib/auth";
910
import config from "lib/config";
1011
import installPlugin from "lib/installPlugin";
1112
import InstallState from "lib/installState";
@@ -19,21 +20,19 @@ import markdownItTaskLists from "markdown-it-task-lists";
1920
import { highlightCodeBlock, initHighlighting } from "utils/codeHighlight";
2021
import helpers from "utils/helpers";
2122
import Url from "utils/Url";
22-
import view from "./plugin.view.js";
23+
import view, { cleanups } from "./plugin.view.js";
2324

2425
let $lastPluginPage;
2526

2627
/**
2728
* Plugin page
2829
* @param {string} id
29-
* @param {boolean} installed
3030
* @param {() => void} [onInstall]
3131
* @param {() => void} [onUninstall]
3232
* @param {boolean} [installOnRender]
3333
*/
3434
export default async function PluginInclude(
3535
id,
36-
installed,
3736
onInstall,
3837
onUninstall,
3938
installOnRender,
@@ -42,8 +41,8 @@ export default async function PluginInclude(
4241
$lastPluginPage.hide();
4342
}
4443

45-
installed = typeof installed !== "boolean" ? installed === "true" : installed;
4644
const $page = Page(strings["plugin"]);
45+
let installed = await fsOperation(PLUGIN_DIR, id).exists();
4746
let plugin = {};
4847
let currentVersion = "";
4948
let purchased = false;
@@ -56,6 +55,8 @@ export default async function PluginInclude(
5655
let $settingsIcon;
5756
let minVersionCode = -1;
5857
let isSupported = true;
58+
let refundHandlerSet = false;
59+
let purchaseHandlerSet = false;
5960

6061
actionStack.push({
6162
id: "plugin",
@@ -68,6 +69,11 @@ export default async function PluginInclude(
6869
loader.removeTitleLoader();
6970
cancelled = true;
7071
$lastPluginPage = null;
72+
cleanups.forEach((cleanup) => {
73+
try {
74+
cleanup();
75+
} catch {}
76+
});
7177
};
7278

7379
$lastPluginPage = $page;
@@ -169,7 +175,11 @@ export default async function PluginInclude(
169175
remotePlugin.supported_editor,
170176
);
171177

172-
if (!purchased && (await helpers.checkAPIStatus())) {
178+
if (
179+
iap.isIapAvailable() &&
180+
!purchased &&
181+
(await helpers.checkAPIStatus())
182+
) {
173183
try {
174184
[product] = await helpers.promisify(iap.getProducts, [
175185
remotePlugin.sku,
@@ -252,6 +262,45 @@ export default async function PluginInclude(
252262
const $button = e.target;
253263
const oldText = $button.textContent;
254264

265+
try {
266+
if (helpers.shouldAllowExternalPurchase()) {
267+
CustomTabs.open(
268+
`${config.BASE_URL}/plugin/${id}?callback=app`,
269+
{ showTitle: true },
270+
() => {},
271+
() => {},
272+
);
273+
if (!purchaseHandlerSet) {
274+
purchaseHandlerSet = true;
275+
const handler = async ({ module, action, value }) => {
276+
if (module === "plugin" && action === "purchased" && value === id) {
277+
loader.show();
278+
279+
try {
280+
const pluginData = await fsOperation(
281+
config.API_BASE,
282+
`plugin/${id}`,
283+
).readFile("json");
284+
285+
purchased = pluginData.owned;
286+
render();
287+
} catch (error) {
288+
window.log(error);
289+
helpers.error(error);
290+
}
291+
292+
loader.hide();
293+
purchaseHandlerSet = false;
294+
removeIntentHandler(handler);
295+
}
296+
};
297+
addIntentHandler(handler);
298+
cleanups.push(() => removeIntentHandler(handler));
299+
}
300+
return;
301+
}
302+
} catch (error) {}
303+
255304
try {
256305
if (!product) throw new Error("Product not found");
257306
const apiStatus = await helpers.checkAPIStatus();
@@ -296,6 +345,38 @@ export default async function PluginInclude(
296345
async function refund(e) {
297346
const $button = e.target;
298347
const oldText = $button.textContent;
348+
349+
if (helpers.shouldAllowExternalPurchase()) {
350+
CustomTabs.open(
351+
`${config.BASE_URL}/plugin/${id}?callback=app`,
352+
{ showTitle: true },
353+
() => {},
354+
() => {},
355+
);
356+
357+
if (!refundHandlerSet) {
358+
refundHandlerSet = true;
359+
const handler = ({ module, action, value }) => {
360+
if (module === "plugin" && action === "uninstall" && value === id) {
361+
purchased = false;
362+
363+
if (installed) {
364+
uninstall();
365+
} else {
366+
render();
367+
}
368+
369+
refundHandlerSet = false;
370+
removeIntentHandler(handler);
371+
}
372+
};
373+
374+
addIntentHandler(handler);
375+
cleanups.push(() => removeIntentHandler(handler));
376+
}
377+
return;
378+
}
379+
299380
try {
300381
if (!product) throw new Error("Product not found");
301382
$button.textContent = strings["loading..."];

src/pages/plugin/plugin.view.js

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import alert from "dialogs/alert";
99
import DOMPurify from "dompurify";
1010
import Ref from "html-tag-js/ref";
1111
import actionStack from "lib/actionStack";
12-
import auth from "lib/auth";
12+
import auth, { loginEvents } from "lib/auth";
1313
import config from "lib/config";
1414
import helpers from "utils/helpers";
1515
import Url from "utils/Url";
@@ -43,6 +43,8 @@ dayjs.updateLocale("en", {
4343
},
4444
});
4545

46+
export const cleanups = [];
47+
4648
export default (props) => {
4749
const {
4850
id,
@@ -271,43 +273,24 @@ function handleTabClick(e) {
271273
document.getElementById(tabId).classList.add("active");
272274
}
273275

274-
function Buttons({
275-
id,
276-
name,
277-
isPaid,
278-
installed,
279-
update,
280-
install,
281-
uninstall,
282-
purchased,
283-
price,
284-
buy,
285-
minVersionCode,
286-
isSupported = true,
287-
}) {
288-
async function openPluginWebsite() {
289-
try {
290-
const user = await auth.getLoggedInUser();
291-
if (!user) {
292-
CustomTabs.open(
293-
`${config.BASE_URL}/login?redirect=app`,
294-
{ showTitle: true },
295-
() => {},
296-
() => {},
297-
);
298-
return;
299-
}
276+
async function Buttons(props) {
277+
const {
278+
id,
279+
name,
280+
isPaid,
281+
installed,
282+
update,
283+
install,
284+
uninstall,
285+
purchased,
286+
price,
287+
buy,
288+
minVersionCode,
289+
isSupported = true,
290+
} = props;
291+
292+
console.log("buttons", { installed });
300293

301-
CustomTabs.open(
302-
`${config.BASE_URL}/plugin/${id}`,
303-
{ showTitle: true },
304-
() => {},
305-
() => {},
306-
);
307-
} catch (e) {
308-
console.error(e);
309-
}
310-
}
311294
if (!isSupported) {
312295
return (
313296
<div
@@ -371,15 +354,34 @@ function Buttons({
371354
);
372355
}
373356

374-
if (!config.IAP_AVAILABLE && isPaid && !purchased && price) {
357+
const user = await auth.getLoggedInUser();
358+
if (isPaid && helpers.shouldAllowExternalPurchase() && !user) {
359+
const buttonRef = Ref();
375360
return (
376361
<button
377-
data-type="buy"
362+
ref={buttonRef}
363+
data-type="info"
378364
className="btn btn-install"
379-
onclick={openPluginWebsite}
365+
onclick={() => {
366+
CustomTabs.open(
367+
`${config.BASE_URL}/login?redirect=app`,
368+
{ showTitle: true },
369+
() => {},
370+
() => {},
371+
);
372+
373+
const onLogin = async () => {
374+
loginEvents.off(onLogin);
375+
376+
const newButton = await Buttons(props);
377+
buttonRef.el.replaceWith(newButton);
378+
};
379+
loginEvents.on(onLogin);
380+
cleanups.push(() => loginEvents.off(onLogin));
381+
}}
380382
>
381-
<i className="icon open_in_browser"></i>
382-
{price}
383+
<i className="icon user-round"></i>
384+
{strings.login}
383385
</button>
384386
);
385387
}

0 commit comments

Comments
 (0)