From bcfb12b3e38254bf1f8e4b0e809a90d572340018 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 15:00:23 +0530 Subject: [PATCH 01/16] feat: native api for fetching plugins --- src/plugins/auth/plugin.xml | 1 + .../auth/src/android/Authenticator.java | 19 +- .../auth/src/android/PluginRetriever.java | 172 ++++++++++++++++++ 3 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/plugins/auth/src/android/PluginRetriever.java diff --git a/src/plugins/auth/plugin.xml b/src/plugins/auth/plugin.xml index e6e3e448c..284e43d89 100644 --- a/src/plugins/auth/plugin.xml +++ b/src/plugins/auth/plugin.xml @@ -13,6 +13,7 @@ + diff --git a/src/plugins/auth/src/android/Authenticator.java b/src/plugins/auth/src/android/Authenticator.java index 48bf3cc08..d67642f77 100644 --- a/src/plugins/auth/src/android/Authenticator.java +++ b/src/plugins/auth/src/android/Authenticator.java @@ -10,7 +10,6 @@ import java.util.Scanner; public class Authenticator extends CordovaPlugin { - // Standard practice: use a TAG for easy filtering in Logcat private static final String TAG = "AcodeAuth"; private static final String PREFS_FILENAME = "acode_auth_secure"; private static final String KEY_TOKEN = "auth_token"; @@ -42,6 +41,22 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo prefManager.setString(KEY_TOKEN, token); callbackContext.success(); return true; + case "retrieveFilteredPlugins": + try { + JSONObject filterState = args.length() > 0 ? args.getJSONObject(0) : null; + final JSONObject finalFilterState = filterState; + cordova.getThreadPool().execute(() -> { + try { + PluginRetriever.retrieveFilteredPlugins(prefManager.getString(KEY_TOKEN, null), finalFilterState, callbackContext); + } catch (Exception e) { + Log.e(TAG, "retrieveFilteredPlugins error: " + e.getMessage(), e); + callbackContext.error("Error: " + e.getMessage()); + } + }); + } catch (JSONException e) { + callbackContext.error("Invalid filter JSON: " + e.getMessage()); + } + return true; default: Log.w(TAG, "Attempted to call unknown action: " + action); return false; @@ -114,7 +129,7 @@ private String validateToken(String token) { HttpURLConnection conn = null; try { Log.d(TAG, "Network Request: Connecting to https://acode.app/api/login"); - URL url = new URL("https://acode.app/api/login"); // Changed from /api to /api/login + URL url = new URL("https://acode.app/api/login"); conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty("x-auth-token", token); conn.setRequestMethod("GET"); diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java new file mode 100644 index 000000000..54fdfdd4c --- /dev/null +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -0,0 +1,172 @@ +package com.foxdebug.acode.rk.auth; + +import android.util.Log; +import org.apache.cordova.CallbackContext; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Scanner; + +public class PluginRetriever { + private static final String TAG = "AcodePluginRetriever"; + private static final int LIMIT = 20; + private static final String API_BASE = "https://acode.app/api"; + + public static void retrieveFilteredPlugins(String token,JSONObject filterState, CallbackContext callbackContext) throws Exception { + JSONObject result = new JSONObject(); + + if (filterState == null) { + result.put("items", new JSONArray()); + result.put("hasMore", false); + callbackContext.success(result); + return; + } + + String type = filterState.optString("type", ""); + + if ("orderBy".equals(type)) { + int page = filterState.optInt("nextPage", 1); + String value = filterState.optString("value", ""); + String url; + + if ("top_rated".equals(value)) { + url = API_BASE + "/plugins?explore=random&page=" + page + "&limit=" + LIMIT; + } else { + url = API_BASE + "/plugin?orderBy=" + value + "&page=" + page + "&limit=" + LIMIT; + } + + JSONArray items = fetchJsonArray(token,url); + if (items == null) items = new JSONArray(); + + filterState.put("nextPage", page + 1); + result.put("items", items); + result.put("hasMore", items.length() == LIMIT); + callbackContext.success(result); + return; + } + + JSONArray buffer = filterState.optJSONArray("buffer"); + if (buffer == null) { + buffer = new JSONArray(); + filterState.put("buffer", buffer); + } + + boolean hasMoreSource = !filterState.has("hasMoreSource") || filterState.getBoolean("hasMoreSource"); + int nextPage = filterState.optInt("nextPage", 1); + if (!filterState.has("nextPage")) { + filterState.put("nextPage", 1); + } + + JSONArray items = new JSONArray(); + + while (items.length() < LIMIT) { + if (buffer.length() > 0) { + items.put(buffer.get(0)); + JSONArray newBuffer = new JSONArray(); + for (int i = 1; i < buffer.length(); i++) newBuffer.put(buffer.get(i)); + buffer = newBuffer; + filterState.put("buffer", buffer); + continue; + } + + if (!hasMoreSource) break; + + String url = API_BASE + "/plugins?page=" + nextPage + "&limit=" + LIMIT; + JSONArray data = fetchJsonArray(token,url); + nextPage++; + filterState.put("nextPage", nextPage); + + if (data == null || data.length() == 0) { + hasMoreSource = false; + filterState.put("hasMoreSource", false); + break; + } + + if (data.length() < LIMIT) { + hasMoreSource = false; + filterState.put("hasMoreSource", false); + } + + for (int i = 0; i < data.length(); i++) { + JSONObject plugin = data.getJSONObject(i); + if (matchesFilter(plugin, filterState)) { + buffer.put(plugin); + } + } + filterState.put("buffer", buffer); + } + + while (items.length() < LIMIT && buffer.length() > 0) { + items.put(buffer.get(0)); + JSONArray newBuffer = new JSONArray(); + for (int i = 1; i < buffer.length(); i++) newBuffer.put(buffer.get(i)); + buffer = newBuffer; + filterState.put("buffer", buffer); + } + + boolean hasMore = (hasMoreSource && filterState.has("nextPage")) || buffer.length() > 0; + + result.put("items", items); + result.put("hasMore", hasMore); + callbackContext.success(result); + } + + // Change fetchJsonArray signature to accept token + private static JSONArray fetchJsonArray(String urlString, String token) { + HttpURLConnection conn = null; + try { + Log.d(TAG, "Fetching: " + urlString); + URL url = new URL(urlString); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + // Add auth header if token is present + if (token != null && !token.isEmpty()) { + conn.setRequestProperty("x-auth-token", token); + } + + int code = conn.getResponseCode(); + if (code != 200) { + Log.w(TAG, "Non-200 response (" + code + ") for: " + urlString); + return null; + } + + Scanner s = new Scanner(conn.getInputStream(), "UTF-8").useDelimiter("\\A"); + String body = s.hasNext() ? s.next() : "[]"; + return new JSONArray(body); + } catch (Exception e) { + Log.e(TAG, "fetchJsonArray error: " + e.getMessage(), e); + return null; + } finally { + if (conn != null) conn.disconnect(); + } + } + + private static boolean matchesFilter(JSONObject plugin, JSONObject filterState) { + try { + String type = filterState.optString("type", ""); + String value = filterState.optString("value", "").toLowerCase(); + + if (value.isEmpty()) return true; + + switch (type) { + case "search": + String name = plugin.optString("name", "").toLowerCase(); + String desc = plugin.optString("description", "").toLowerCase(); + return name.contains(value) || desc.contains(value); + case "author": + String author = plugin.optString("author", "").toLowerCase(); + return author.contains(value); + default: + return true; + } + } catch (Exception e) { + Log.e(TAG, "matchesFilter error: " + e.getMessage(), e); + return false; + } + } +} \ No newline at end of file From 87fa1ddf823fbd7ad02598e60e4c93f218670239 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 16:10:57 +0530 Subject: [PATCH 02/16] feat: fix api call --- src/plugins/auth/src/android/Authenticator.java | 3 ++- src/plugins/auth/src/android/PluginRetriever.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/auth/src/android/Authenticator.java b/src/plugins/auth/src/android/Authenticator.java index d67642f77..406bd1487 100644 --- a/src/plugins/auth/src/android/Authenticator.java +++ b/src/plugins/auth/src/android/Authenticator.java @@ -8,6 +8,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.Scanner; +import org.json.JSONObject; public class Authenticator extends CordovaPlugin { private static final String TAG = "AcodeAuth"; @@ -134,7 +135,7 @@ private String validateToken(String token) { conn.setRequestProperty("x-auth-token", token); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); // Add read timeout too + conn.setReadTimeout(5000); int code = conn.getResponseCode(); Log.d(TAG, "Server responded with status code: " + code); diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 54fdfdd4c..972d260b7 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -37,7 +37,7 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, url = API_BASE + "/plugin?orderBy=" + value + "&page=" + page + "&limit=" + LIMIT; } - JSONArray items = fetchJsonArray(token,url); + JSONArray items = fetchJsonArray(url,token); if (items == null) items = new JSONArray(); filterState.put("nextPage", page + 1); @@ -74,7 +74,7 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, if (!hasMoreSource) break; String url = API_BASE + "/plugins?page=" + nextPage + "&limit=" + LIMIT; - JSONArray data = fetchJsonArray(token,url); + JSONArray data = fetchJsonArray(url,token); nextPage++; filterState.put("nextPage", nextPage); From b945ca5695fb3eba5208aabcd483c2ff90dd7db8 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 16:15:03 +0530 Subject: [PATCH 03/16] use standard auth header --- src/plugins/auth/src/android/PluginRetriever.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 972d260b7..c38321d52 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -126,7 +126,7 @@ private static JSONArray fetchJsonArray(String urlString, String token) { // Add auth header if token is present if (token != null && !token.isEmpty()) { - conn.setRequestProperty("x-auth-token", token); + conn.setRequestProperty("Authorization", "Bearer " + token); } int code = conn.getResponseCode(); From 30e2717385dd537af7735d139f7ddd34bb5f0676 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 16:22:11 +0530 Subject: [PATCH 04/16] Revert "use standard auth header" This reverts commit b945ca5695fb3eba5208aabcd483c2ff90dd7db8. --- src/plugins/auth/src/android/PluginRetriever.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index c38321d52..972d260b7 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -126,7 +126,7 @@ private static JSONArray fetchJsonArray(String urlString, String token) { // Add auth header if token is present if (token != null && !token.isEmpty()) { - conn.setRequestProperty("Authorization", "Bearer " + token); + conn.setRequestProperty("x-auth-token", token); } int code = conn.getResponseCode(); From c00048760b89b28cc72735d231f2249195d96da6 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 16:50:28 +0530 Subject: [PATCH 05/16] feat: add support for getAllPlugins --- .../auth/src/android/Authenticator.java | 10 ++ .../auth/src/android/PluginRetriever.java | 153 +++++++++++++++--- 2 files changed, 140 insertions(+), 23 deletions(-) diff --git a/src/plugins/auth/src/android/Authenticator.java b/src/plugins/auth/src/android/Authenticator.java index 406bd1487..39d0ac75e 100644 --- a/src/plugins/auth/src/android/Authenticator.java +++ b/src/plugins/auth/src/android/Authenticator.java @@ -42,6 +42,16 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo prefManager.setString(KEY_TOKEN, token); callbackContext.success(); return true; + case "getAllPlugins": + cordova.getThreadPool().execute(() -> { + try { + PluginRetriever.getAllPlugins(prefManager.getString(KEY_TOKEN, null), args.getInt(0), callbackContext); + } catch (Exception e) { + Log.e(TAG, "getAllPlugins error: " + e.getMessage(), e); + callbackContext.error("Error: " + e.getMessage()); + } + }); + return true; case "retrieveFilteredPlugins": try { JSONObject filterState = args.length() > 0 ? args.getJSONObject(0) : null; diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 972d260b7..a5141b218 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -11,10 +11,36 @@ public class PluginRetriever { private static final String TAG = "AcodePluginRetriever"; - private static final int LIMIT = 20; + private static final int LIMIT = 50; private static final String API_BASE = "https://acode.app/api"; + private static final String SUPPORTED_EDITOR = "cm"; - public static void retrieveFilteredPlugins(String token,JSONObject filterState, CallbackContext callbackContext) throws Exception { + // Appends supported_editor param — mirrors JS withSupportedEditor() + private static String withSupportedEditor(String url) { + String separator = url.contains("?") ? "&" : "?"; + return url + separator + "supported_editor=" + SUPPORTED_EDITOR; + } + + + public static void getAllPlugins(String token, int page, CallbackContext callbackContext) { + String url = withSupportedEditor(API_BASE + "/plugins?page=" + page + "&limit=" + LIMIT); + JSONArray plugins = fetchJsonArray(url, token); + + try { + JSONObject result = new JSONObject(); + if (plugins == null) plugins = new JSONArray(); + result.put("items", plugins); + result.put("hasMore", plugins.length() == LIMIT); + callbackContext.success(result); + } catch (JSONException e) { + Log.e(TAG, "getAllPlugins error: " + e.getMessage(), e); + callbackContext.error("Failed to build result: " + e.getMessage()); + } + } + + + + public static void retrieveFilteredPlugins(String token, JSONObject filterState, CallbackContext callbackContext) throws Exception { JSONObject result = new JSONObject(); if (filterState == null) { @@ -32,12 +58,12 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, String url; if ("top_rated".equals(value)) { - url = API_BASE + "/plugins?explore=random&page=" + page + "&limit=" + LIMIT; + url = withSupportedEditor(API_BASE + "/plugins?explore=random&page=" + page + "&limit=" + LIMIT); } else { - url = API_BASE + "/plugin?orderBy=" + value + "&page=" + page + "&limit=" + LIMIT; + url = withSupportedEditor(API_BASE + "/plugin?orderBy=" + value + "&page=" + page + "&limit=" + LIMIT); } - JSONArray items = fetchJsonArray(url,token); + JSONArray items = fetchJsonArray(url, token); if (items == null) items = new JSONArray(); filterState.put("nextPage", page + 1); @@ -47,6 +73,8 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, return; } + // --- Buffered filter path (verified, author, keywords) --- + JSONArray buffer = filterState.optJSONArray("buffer"); if (buffer == null) { buffer = new JSONArray(); @@ -64,17 +92,15 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, while (items.length() < LIMIT) { if (buffer.length() > 0) { items.put(buffer.get(0)); - JSONArray newBuffer = new JSONArray(); - for (int i = 1; i < buffer.length(); i++) newBuffer.put(buffer.get(i)); - buffer = newBuffer; + buffer = shiftBuffer(buffer); filterState.put("buffer", buffer); continue; } if (!hasMoreSource) break; - String url = API_BASE + "/plugins?page=" + nextPage + "&limit=" + LIMIT; - JSONArray data = fetchJsonArray(url,token); + String url = withSupportedEditor(API_BASE + "/plugins?page=" + nextPage + "&limit=" + LIMIT); + JSONArray data = fetchJsonArray(url, token); nextPage++; filterState.put("nextPage", nextPage); @@ -100,9 +126,7 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, while (items.length() < LIMIT && buffer.length() > 0) { items.put(buffer.get(0)); - JSONArray newBuffer = new JSONArray(); - for (int i = 1; i < buffer.length(); i++) newBuffer.put(buffer.get(i)); - buffer = newBuffer; + buffer = shiftBuffer(buffer); filterState.put("buffer", buffer); } @@ -113,7 +137,15 @@ public static void retrieveFilteredPlugins(String token,JSONObject filterState, callbackContext.success(result); } - // Change fetchJsonArray signature to accept token + // Removes first element from JSONArray (mirrors JS Array.shift()) + private static JSONArray shiftBuffer(JSONArray buffer) throws JSONException { + JSONArray newBuffer = new JSONArray(); + for (int i = 1; i < buffer.length(); i++) { + newBuffer.put(buffer.get(i)); + } + return newBuffer; + } + private static JSONArray fetchJsonArray(String urlString, String token) { HttpURLConnection conn = null; try { @@ -123,8 +155,7 @@ private static JSONArray fetchJsonArray(String urlString, String token) { conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); - - // Add auth header if token is present + if (token != null && !token.isEmpty()) { conn.setRequestProperty("x-auth-token", token); } @@ -149,18 +180,47 @@ private static JSONArray fetchJsonArray(String urlString, String token) { private static boolean matchesFilter(JSONObject plugin, JSONObject filterState) { try { String type = filterState.optString("type", ""); - String value = filterState.optString("value", "").toLowerCase(); - - if (value.isEmpty()) return true; switch (type) { - case "search": + case "verified": + // Mirrors JS: Boolean(plugin.author_verified) + return plugin.optBoolean("author_verified", false); + + case "author": { + String filterValue = filterState.optString("value", "").toLowerCase(); + if (filterValue.isEmpty()) return true; + String authorName = normalizePluginAuthor(plugin).toLowerCase(); + return authorName.contains(filterValue); + } + + case "keywords": { + // value is a JSONArray of lowercase keyword strings + JSONArray filterKeywords = filterState.optJSONArray("value"); + if (filterKeywords == null || filterKeywords.length() == 0) return true; + + JSONArray pluginKeywords = normalizePluginKeywords(plugin); + if (pluginKeywords.length() == 0) return false; + + // JS: filterState.value.some(keyword => + // pluginKeywords.some(pk => pk.includes(keyword))) + for (int fi = 0; fi < filterKeywords.length(); fi++) { + String fk = filterKeywords.getString(fi).toLowerCase(); + for (int pi = 0; pi < pluginKeywords.length(); pi++) { + String pk = pluginKeywords.getString(pi).toLowerCase(); + if (pk.contains(fk)) return true; + } + } + return false; + } + + case "search": { + String value = filterState.optString("value", "").toLowerCase(); + if (value.isEmpty()) return true; String name = plugin.optString("name", "").toLowerCase(); String desc = plugin.optString("description", "").toLowerCase(); return name.contains(value) || desc.contains(value); - case "author": - String author = plugin.optString("author", "").toLowerCase(); - return author.contains(value); + } + default: return true; } @@ -169,4 +229,51 @@ private static boolean matchesFilter(JSONObject plugin, JSONObject filterState) return false; } } + + // Mirrors JS normalizePluginAuthor() — author can be string or object + private static String normalizePluginAuthor(JSONObject plugin) { + try { + Object author = plugin.opt("author"); + if (author == null) return ""; + if (author instanceof String) return (String) author; + if (author instanceof JSONObject) { + JSONObject authorObj = (JSONObject) author; + String name = authorObj.optString("name", ""); + if (!name.isEmpty()) return name; + String username = authorObj.optString("username", ""); + if (!username.isEmpty()) return username; + return authorObj.optString("github", ""); + } + } catch (Exception e) { + Log.e(TAG, "normalizePluginAuthor error: " + e.getMessage(), e); + } + return ""; + } + + // Mirrors JS normalizePluginKeywords() — keywords can be array or comma string + private static JSONArray normalizePluginKeywords(JSONObject plugin) { + try { + Object keywords = plugin.opt("keywords"); + if (keywords == null) return new JSONArray(); + if (keywords instanceof JSONArray) return (JSONArray) keywords; + if (keywords instanceof String) { + String kStr = (String) keywords; + // Try parsing as JSON array first + try { + return new JSONArray(kStr); + } catch (JSONException e) { + // Fall back to comma-split + JSONArray result = new JSONArray(); + for (String part : kStr.split(",")) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) result.put(trimmed); + } + return result; + } + } + } catch (Exception e) { + Log.e(TAG, "normalizePluginKeywords error: " + e.getMessage(), e); + } + return new JSONArray(); + } } \ No newline at end of file From 8b7390976d8133fcbca36a04e4247767987f5a5f Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 16:52:16 +0530 Subject: [PATCH 06/16] feat: add support for getAllPlugins --- src/plugins/auth/src/android/PluginRetriever.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index a5141b218..5e393f51a 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -168,6 +168,7 @@ private static JSONArray fetchJsonArray(String urlString, String token) { Scanner s = new Scanner(conn.getInputStream(), "UTF-8").useDelimiter("\\A"); String body = s.hasNext() ? s.next() : "[]"; + s.close(); return new JSONArray(body); } catch (Exception e) { Log.e(TAG, "fetchJsonArray error: " + e.getMessage(), e); From 6b5547ccad826c7c1e41e9d57479745b216dd10e Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Thu, 16 Apr 2026 17:02:50 +0530 Subject: [PATCH 07/16] feat: add filterstate --- src/plugins/auth/src/android/PluginRetriever.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 5e393f51a..08b4aefac 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -69,6 +69,7 @@ public static void retrieveFilteredPlugins(String token, JSONObject filterState, filterState.put("nextPage", page + 1); result.put("items", items); result.put("hasMore", items.length() == LIMIT); + result.put("filterState", filterState); callbackContext.success(result); return; } From 232b9da8ee419592075f3431c800215ae3dc52c9 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 08:25:44 +0530 Subject: [PATCH 08/16] feat: add owned filter --- .../auth/src/android/Authenticator.java | 2 +- .../auth/src/android/PluginRetriever.java | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/plugins/auth/src/android/Authenticator.java b/src/plugins/auth/src/android/Authenticator.java index 39d0ac75e..a3fe4f721 100644 --- a/src/plugins/auth/src/android/Authenticator.java +++ b/src/plugins/auth/src/android/Authenticator.java @@ -45,7 +45,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo case "getAllPlugins": cordova.getThreadPool().execute(() -> { try { - PluginRetriever.getAllPlugins(prefManager.getString(KEY_TOKEN, null), args.getInt(0), callbackContext); + PluginRetriever.getAllPlugins(args.getInt(0), callbackContext); } catch (Exception e) { Log.e(TAG, "getAllPlugins error: " + e.getMessage(), e); callbackContext.error("Error: " + e.getMessage()); diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 08b4aefac..a453d9da2 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -15,16 +15,16 @@ public class PluginRetriever { private static final String API_BASE = "https://acode.app/api"; private static final String SUPPORTED_EDITOR = "cm"; - // Appends supported_editor param — mirrors JS withSupportedEditor() + private static String withSupportedEditor(String url) { String separator = url.contains("?") ? "&" : "?"; return url + separator + "supported_editor=" + SUPPORTED_EDITOR; } - public static void getAllPlugins(String token, int page, CallbackContext callbackContext) { + public static void getAllPlugins(int page, CallbackContext callbackContext) { String url = withSupportedEditor(API_BASE + "/plugins?page=" + page + "&limit=" + LIMIT); - JSONArray plugins = fetchJsonArray(url, token); + JSONArray plugins = fetchJsonArray(url, null); try { JSONObject result = new JSONObject(); @@ -74,6 +74,21 @@ public static void retrieveFilteredPlugins(String token, JSONObject filterState, return; } + if ("owned".equals(type)) { + int page = filterState.optInt("nextPage", 1); + String url = withSupportedEditor(API_BASE + "/plugins?owned=true&page=" + page + "&limit=" + LIMIT); + + JSONArray items = fetchJsonArray(url, token); + if (items == null) items = new JSONArray(); + + filterState.put("nextPage", page + 1); + result.put("items", items); + result.put("hasMore", items.length() == LIMIT); + result.put("filterState", filterState); + callbackContext.success(result); + return; + } + // --- Buffered filter path (verified, author, keywords) --- JSONArray buffer = filterState.optJSONArray("buffer"); From 33ac7df39f359ea17f984c95daeb0b8086e4ec38 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 09:42:39 +0530 Subject: [PATCH 09/16] feat: implement getOwned plugins --- src/lib/installPlugin.js | 30 +- src/pages/plugins/plugins.js | 204 ++++++++------ .../auth/src/android/Authenticator.java | 47 ++-- .../auth/src/android/PluginRetriever.java | 259 +++--------------- 4 files changed, 196 insertions(+), 344 deletions(-) diff --git a/src/lib/installPlugin.js b/src/lib/installPlugin.js index 5488d9131..7695c226c 100644 --- a/src/lib/installPlugin.js +++ b/src/lib/installPlugin.js @@ -82,23 +82,23 @@ export default async function installPlugin( }, ); } else { - // cordova http plugin for others - plugin = await new Promise((resolve, reject) => { - cordova.plugin.http.sendRequest( + const tempPath = cordova.file.cacheDirectory + "plugin_download.zip"; + + await new Promise((resolve, reject) => { + cordova.exec(resolve, reject, "Authenticator", "downloadPlugin", [ pluginUrl, - { - method: "GET", - responseType: "arraybuffer", - }, - (response) => { - resolve(response.data); - loaderDialog.setMessage(`${strings.loading} 100%`); - }, - (error) => { - reject(error); - }, - ); + tempPath, + ]); }); + + plugin = await fsOperation(tempPath).readFile( + undefined, + (loaded, total) => { + loaderDialog.setMessage( + `${strings.loading} ${((loaded / total) * 100).toFixed(2)}%`, + ); + }, + ); } if (plugin) { diff --git a/src/pages/plugins/plugins.js b/src/pages/plugins/plugins.js index d16aa2dcc..e270ebab9 100644 --- a/src/pages/plugins/plugins.js +++ b/src/pages/plugins/plugins.js @@ -416,95 +416,82 @@ export default function PluginsInclude(updates) { } } - async function retrieveFilteredPlugins(filterState) { + //fetch plugins with the auth token + function fetchPlugins(url) { + return new Promise((resolve, reject) => { + cordova.exec( + (items) => resolve(items), + (err) => reject(new Error(err)), + "Authenticator", + "fetchPlugins", + [url] + ); + }); +} + +async function retrieveFilteredPlugins(filterState) { if (!filterState) return { items: [], hasMore: false }; if (filterState.type === "orderBy") { - const page = filterState.nextPage || 1; - try { - let response; - if (filterState.value === "top_rated") { - response = await fetch( - withSupportedEditor( - `${constants.API_BASE}/plugins?explore=random&page=${page}&limit=${LIMIT}`, - ), - ); - } else { - response = await fetch( - withSupportedEditor( - `${constants.API_BASE}/plugin?orderBy=${filterState.value}&page=${page}&limit=${LIMIT}`, - ), - ); - } - const items = await response.json(); - if (!Array.isArray(items)) { - return { items: [], hasMore: false }; + const page = filterState.nextPage || 1; + try { + let url; + if (filterState.value === "top_rated") { + url = withSupportedEditor(`${constants.API_BASE}/plugins?explore=random&page=${page}&limit=${LIMIT}`); + } else { + url = withSupportedEditor(`${constants.API_BASE}/plugin?orderBy=${filterState.value}&page=${page}&limit=${LIMIT}`); + } + + const items = await fetchPlugins(url); + filterState.nextPage = page + 1; + return { items, hasMore: items.length === LIMIT }; + } catch (error) { + console.error("Failed to fetch ordered plugins:", error); + return { items: [], hasMore: false }; } - filterState.nextPage = page + 1; - const hasMoreResults = items.length === LIMIT; - return { items, hasMore: hasMoreResults }; - } catch (error) { - console.error("Failed to fetch ordered plugins:", error); - return { items: [], hasMore: false }; - } } - if (!Array.isArray(filterState.buffer)) { - filterState.buffer = []; - } - if (filterState.hasMoreSource === undefined) { - filterState.hasMoreSource = true; - } - if (!filterState.nextPage) { - filterState.nextPage = 1; - } + if (!Array.isArray(filterState.buffer)) filterState.buffer = []; + if (filterState.hasMoreSource === undefined) filterState.hasMoreSource = true; + if (!filterState.nextPage) filterState.nextPage = 1; const items = []; while (items.length < LIMIT) { - if (filterState.buffer.length) { - items.push(filterState.buffer.shift()); - continue; - } + if (filterState.buffer.length) { + items.push(filterState.buffer.shift()); + continue; + } - if (filterState.hasMoreSource === false) break; + if (filterState.hasMoreSource === false) break; - try { - const page = filterState.nextPage; - const response = await fetch( - withSupportedEditor(`${constants.API_BASE}/plugins?page=${page}&limit=${LIMIT}`), - ); - const data = await response.json(); - filterState.nextPage = page + 1; + try { + const url = withSupportedEditor(`${constants.API_BASE}/plugins?page=${filterState.nextPage}&limit=${LIMIT}`); + const data = await fetchPlugins(url); // <-- java call + filterState.nextPage++; - if (!Array.isArray(data) || !data.length) { - filterState.hasMoreSource = false; - break; - } + if (!Array.isArray(data) || !data.length) { + filterState.hasMoreSource = false; + break; + } - if (data.length < LIMIT) { - filterState.hasMoreSource = false; - } + if (data.length < LIMIT) filterState.hasMoreSource = false; - const matched = data.filter((plugin) => matchesFilter(plugin, filterState)); - filterState.buffer.push(...matched); - } catch (error) { - console.error("Failed to fetch filtered plugins:", error); - filterState.hasMoreSource = false; - break; - } + filterState.buffer.push(...data.filter(plugin => matchesFilter(plugin, filterState))); + } catch (error) { + console.error("Failed to fetch filtered plugins:", error); + filterState.hasMoreSource = false; + break; + } } while (items.length < LIMIT && filterState.buffer.length) { - items.push(filterState.buffer.shift()); + items.push(filterState.buffer.shift()); } - const hasMoreResults = - (filterState.hasMoreSource !== false && filterState.nextPage) || - filterState.buffer.length > 0; - + const hasMoreResults = (filterState.hasMoreSource !== false && filterState.nextPage) || filterState.buffer.length > 0; return { items, hasMore: Boolean(hasMoreResults) }; - } +} function matchesFilter(plugin, filterState) { if (!plugin) return false; @@ -559,6 +546,7 @@ export default function PluginsInclude(updates) { return []; } + //auth token is not needed here as this endpoint only returns public plugins and the server handles the filtering based on supported editor async function getAllPlugins() { if (currentFilter) return; if (isLoading || !hasMore) return; @@ -632,26 +620,76 @@ export default function PluginsInclude(updates) { $list.installed.setAttribute("empty-msg", strings["no plugins found"]); } - async function getOwned() { - $list.owned.setAttribute("empty-msg", strings["loading..."]); - const purchases = await helpers.promisify(iap.getPurchases); - const disabledMap = settings.value.pluginsDisabled || {}; +async function getOwned() { + $list.owned.setAttribute("empty-msg", strings["loading..."]); + + const purchases = await helpers.promisify(iap.getPurchases); + const disabledMap = settings.value.pluginsDisabled || {}; + + // Prevent duplicates + const addedIds = new Set(); - purchases.forEach(async ({ productIds }) => { + // Play Store / IAP purchases + await Promise.all( + purchases.map(async ({ productIds }) => { const [sku] = productIds; - const url = Url.join(constants.API_BASE, "plugin/owned", sku); - const plugin = await fsOperation(url).readFile("json"); - const isInstalled = plugins.installed.find(({ id }) => id === plugin.id); - plugin.installed = !!isInstalled; - if (plugin.installed) { - plugin.enabled = disabledMap[plugin.id] !== true; - plugin.onToggleEnabled = onToggleEnabled; + try { + const url = Url.join(constants.API_BASE, "plugin/owned", sku); + const plugin = await fsOperation(url).readFile("json"); + + if (!plugin || addedIds.has(plugin.id)) return; + + const isInstalled = plugins.installed.find( + ({ id }) => id === plugin.id + ); + + plugin.installed = !!isInstalled; + + if (plugin.installed) { + plugin.enabled = disabledMap[plugin.id] !== true; + plugin.onToggleEnabled = onToggleEnabled; + } + + addedIds.add(plugin.id); + plugins.owned.push(plugin); + $list.owned.append(); + } catch (err) { + console.error("Failed to load owned IAP plugin:", err); } + }) + ); + + // Razorpay / external purchases + try { + const url = withSupportedEditor( + `${constants.API_BASE}/plugins?owned=true` + ); + + const ownedPlugins = await fetchPlugins(url); + + ownedPlugins.forEach((plugin) => { + if (!plugin || addedIds.has(plugin.id)) return; + + const isInstalled = plugins.installed.find( + ({ id }) => id === plugin.id + ); + + plugin.installed = !!isInstalled; + + if (plugin.installed) { + plugin.enabled = disabledMap[plugin.id] !== true; + plugin.onToggleEnabled = onToggleEnabled; + } + + addedIds.add(plugin.id); + plugins.owned.push(plugin); + $list.owned.append(); + }); + } catch (err) { + console.error("Failed to fetch owned plugins:", err); + } - plugins.owned.push(plugin); - $list.owned.append(); - }); $list.owned.setAttribute("empty-msg", strings["no plugins found"]); } diff --git a/src/plugins/auth/src/android/Authenticator.java b/src/plugins/auth/src/android/Authenticator.java index a3fe4f721..51028f18e 100644 --- a/src/plugins/auth/src/android/Authenticator.java +++ b/src/plugins/auth/src/android/Authenticator.java @@ -42,31 +42,32 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo prefManager.setString(KEY_TOKEN, token); callbackContext.success(); return true; - case "getAllPlugins": + case "downloadPlugin": cordova.getThreadPool().execute(() -> { - try { - PluginRetriever.getAllPlugins(args.getInt(0), callbackContext); - } catch (Exception e) { - Log.e(TAG, "getAllPlugins error: " + e.getMessage(), e); - callbackContext.error("Error: " + e.getMessage()); - } - }); + try { + PluginRetriever.downloadPlugin( + prefManager.getString(KEY_TOKEN, null), + args.getString(0), + args.getString(1), + callbackContext + ); + } catch (Exception e) { + Log.e(TAG, "downloadPlugin error: " + e.getMessage(), e); + callbackContext.error("Error: " + e.getMessage()); + } + }); return true; - case "retrieveFilteredPlugins": - try { - JSONObject filterState = args.length() > 0 ? args.getJSONObject(0) : null; - final JSONObject finalFilterState = filterState; - cordova.getThreadPool().execute(() -> { - try { - PluginRetriever.retrieveFilteredPlugins(prefManager.getString(KEY_TOKEN, null), finalFilterState, callbackContext); - } catch (Exception e) { - Log.e(TAG, "retrieveFilteredPlugins error: " + e.getMessage(), e); - callbackContext.error("Error: " + e.getMessage()); - } - }); - } catch (JSONException e) { - callbackContext.error("Invalid filter JSON: " + e.getMessage()); - } + case "fetchPlugins": + cordova.getThreadPool().execute(() -> { + try { + String url = args.getString(0); + JSONArray items = PluginRetriever.fetchJsonArray(url, prefManager.getString(KEY_TOKEN, null)); + callbackContext.success(items != null ? items : new JSONArray()); + } catch (Exception e) { + Log.e(TAG, "fetchPlugins error: " + e.getMessage(), e); + callbackContext.error("Error: " + e.getMessage()); + } + }); return true; default: Log.w(TAG, "Attempted to call unknown action: " + action); diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index a453d9da2..2249e7b3b 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -8,6 +8,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.Scanner; +import android.content.Context; public class PluginRetriever { private static final String TAG = "AcodePluginRetriever"; @@ -22,146 +23,51 @@ private static String withSupportedEditor(String url) { } - public static void getAllPlugins(int page, CallbackContext callbackContext) { - String url = withSupportedEditor(API_BASE + "/plugins?page=" + page + "&limit=" + LIMIT); - JSONArray plugins = fetchJsonArray(url, null); + public static void downloadPlugin(String token, String pluginUrl, String destFile, CallbackContext callbackContext) { try { - JSONObject result = new JSONObject(); - if (plugins == null) plugins = new JSONArray(); - result.put("items", plugins); - result.put("hasMore", plugins.length() == LIMIT); - callbackContext.success(result); - } catch (JSONException e) { - Log.e(TAG, "getAllPlugins error: " + e.getMessage(), e); - callbackContext.error("Failed to build result: " + e.getMessage()); - } - } - - - - public static void retrieveFilteredPlugins(String token, JSONObject filterState, CallbackContext callbackContext) throws Exception { - JSONObject result = new JSONObject(); - - if (filterState == null) { - result.put("items", new JSONArray()); - result.put("hasMore", false); - callbackContext.success(result); - return; - } - - String type = filterState.optString("type", ""); - - if ("orderBy".equals(type)) { - int page = filterState.optInt("nextPage", 1); - String value = filterState.optString("value", ""); - String url; - - if ("top_rated".equals(value)) { - url = withSupportedEditor(API_BASE + "/plugins?explore=random&page=" + page + "&limit=" + LIMIT); - } else { - url = withSupportedEditor(API_BASE + "/plugin?orderBy=" + value + "&page=" + page + "&limit=" + LIMIT); + // Strip file:// prefix if present + if (destFile.startsWith("file://")) { + destFile = destFile.substring(7); } - JSONArray items = fetchJsonArray(url, token); - if (items == null) items = new JSONArray(); - - filterState.put("nextPage", page + 1); - result.put("items", items); - result.put("hasMore", items.length() == LIMIT); - result.put("filterState", filterState); - callbackContext.success(result); - return; - } - - if ("owned".equals(type)) { - int page = filterState.optInt("nextPage", 1); - String url = withSupportedEditor(API_BASE + "/plugins?owned=true&page=" + page + "&limit=" + LIMIT); - - JSONArray items = fetchJsonArray(url, token); - if (items == null) items = new JSONArray(); - - filterState.put("nextPage", page + 1); - result.put("items", items); - result.put("hasMore", items.length() == LIMIT); - result.put("filterState", filterState); - callbackContext.success(result); - return; - } - - // --- Buffered filter path (verified, author, keywords) --- - - JSONArray buffer = filterState.optJSONArray("buffer"); - if (buffer == null) { - buffer = new JSONArray(); - filterState.put("buffer", buffer); - } - - boolean hasMoreSource = !filterState.has("hasMoreSource") || filterState.getBoolean("hasMoreSource"); - int nextPage = filterState.optInt("nextPage", 1); - if (!filterState.has("nextPage")) { - filterState.put("nextPage", 1); - } - - JSONArray items = new JSONArray(); + URL url = new URL(pluginUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); - while (items.length() < LIMIT) { - if (buffer.length() > 0) { - items.put(buffer.get(0)); - buffer = shiftBuffer(buffer); - filterState.put("buffer", buffer); - continue; + if (token != null && !token.isEmpty()) { + if (url.getHost().endsWith("acode.app")) { + connection.setRequestProperty("x-auth-token", token); + } else { + Log.w(TAG, "Not adding auth token for untrusted URL: " + pluginUrl); + } } - if (!hasMoreSource) break; + connection.connect(); - String url = withSupportedEditor(API_BASE + "/plugins?page=" + nextPage + "&limit=" + LIMIT); - JSONArray data = fetchJsonArray(url, token); - nextPage++; - filterState.put("nextPage", nextPage); - - if (data == null || data.length() == 0) { - hasMoreSource = false; - filterState.put("hasMoreSource", false); - break; - } - - if (data.length() < LIMIT) { - hasMoreSource = false; - filterState.put("hasMoreSource", false); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + callbackContext.error("HTTP error: " + connection.getResponseCode()); + return; } - for (int i = 0; i < data.length(); i++) { - JSONObject plugin = data.getJSONObject(i); - if (matchesFilter(plugin, filterState)) { - buffer.put(plugin); + File tempFile = new File(destFile); + try (InputStream inputStream = connection.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); } } - filterState.put("buffer", buffer); - } - while (items.length() < LIMIT && buffer.length() > 0) { - items.put(buffer.get(0)); - buffer = shiftBuffer(buffer); - filterState.put("buffer", buffer); - } - - boolean hasMore = (hasMoreSource && filterState.has("nextPage")) || buffer.length() > 0; - - result.put("items", items); - result.put("hasMore", hasMore); - callbackContext.success(result); - } + callbackContext.success(); - // Removes first element from JSONArray (mirrors JS Array.shift()) - private static JSONArray shiftBuffer(JSONArray buffer) throws JSONException { - JSONArray newBuffer = new JSONArray(); - for (int i = 1; i < buffer.length(); i++) { - newBuffer.put(buffer.get(i)); + } catch (Exception e) { + callbackContext.error("Download failed: " + e.getMessage()); } - return newBuffer; } + private static JSONArray fetchJsonArray(String urlString, String token) { HttpURLConnection conn = null; try { @@ -173,7 +79,11 @@ private static JSONArray fetchJsonArray(String urlString, String token) { conn.setReadTimeout(5000); if (token != null && !token.isEmpty()) { - conn.setRequestProperty("x-auth-token", token); + if (url.getHost().endsWith("acode.app")) { + connection.setRequestProperty("x-auth-token", token); + } else { + Log.w(TAG, "Not adding auth token for untrusted URL: " + url); + } } int code = conn.getResponseCode(); @@ -194,103 +104,6 @@ private static JSONArray fetchJsonArray(String urlString, String token) { } } - private static boolean matchesFilter(JSONObject plugin, JSONObject filterState) { - try { - String type = filterState.optString("type", ""); - - switch (type) { - case "verified": - // Mirrors JS: Boolean(plugin.author_verified) - return plugin.optBoolean("author_verified", false); - - case "author": { - String filterValue = filterState.optString("value", "").toLowerCase(); - if (filterValue.isEmpty()) return true; - String authorName = normalizePluginAuthor(plugin).toLowerCase(); - return authorName.contains(filterValue); - } - - case "keywords": { - // value is a JSONArray of lowercase keyword strings - JSONArray filterKeywords = filterState.optJSONArray("value"); - if (filterKeywords == null || filterKeywords.length() == 0) return true; - - JSONArray pluginKeywords = normalizePluginKeywords(plugin); - if (pluginKeywords.length() == 0) return false; - - // JS: filterState.value.some(keyword => - // pluginKeywords.some(pk => pk.includes(keyword))) - for (int fi = 0; fi < filterKeywords.length(); fi++) { - String fk = filterKeywords.getString(fi).toLowerCase(); - for (int pi = 0; pi < pluginKeywords.length(); pi++) { - String pk = pluginKeywords.getString(pi).toLowerCase(); - if (pk.contains(fk)) return true; - } - } - return false; - } - - case "search": { - String value = filterState.optString("value", "").toLowerCase(); - if (value.isEmpty()) return true; - String name = plugin.optString("name", "").toLowerCase(); - String desc = plugin.optString("description", "").toLowerCase(); - return name.contains(value) || desc.contains(value); - } - - default: - return true; - } - } catch (Exception e) { - Log.e(TAG, "matchesFilter error: " + e.getMessage(), e); - return false; - } - } - - // Mirrors JS normalizePluginAuthor() — author can be string or object - private static String normalizePluginAuthor(JSONObject plugin) { - try { - Object author = plugin.opt("author"); - if (author == null) return ""; - if (author instanceof String) return (String) author; - if (author instanceof JSONObject) { - JSONObject authorObj = (JSONObject) author; - String name = authorObj.optString("name", ""); - if (!name.isEmpty()) return name; - String username = authorObj.optString("username", ""); - if (!username.isEmpty()) return username; - return authorObj.optString("github", ""); - } - } catch (Exception e) { - Log.e(TAG, "normalizePluginAuthor error: " + e.getMessage(), e); - } - return ""; - } - - // Mirrors JS normalizePluginKeywords() — keywords can be array or comma string - private static JSONArray normalizePluginKeywords(JSONObject plugin) { - try { - Object keywords = plugin.opt("keywords"); - if (keywords == null) return new JSONArray(); - if (keywords instanceof JSONArray) return (JSONArray) keywords; - if (keywords instanceof String) { - String kStr = (String) keywords; - // Try parsing as JSON array first - try { - return new JSONArray(kStr); - } catch (JSONException e) { - // Fall back to comma-split - JSONArray result = new JSONArray(); - for (String part : kStr.split(",")) { - String trimmed = part.trim(); - if (!trimmed.isEmpty()) result.put(trimmed); - } - return result; - } - } - } catch (Exception e) { - Log.e(TAG, "normalizePluginKeywords error: " + e.getMessage(), e); - } - return new JSONArray(); - } + + } \ No newline at end of file From 84b32195f3bc20b890b53d6984e129a6dc308ba4 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 09:48:40 +0530 Subject: [PATCH 10/16] fix: build --- src/plugins/auth/src/android/PluginRetriever.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 2249e7b3b..886d71f4b 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -9,6 +9,9 @@ import java.net.URL; import java.util.Scanner; import android.content.Context; +import java.io.File; +import java.io.InputStream; +import java.io.FileOutputStream; public class PluginRetriever { private static final String TAG = "AcodePluginRetriever"; @@ -68,7 +71,7 @@ public static void downloadPlugin(String token, String pluginUrl, String destFil } - private static JSONArray fetchJsonArray(String urlString, String token) { + public static JSONArray fetchJsonArray(String urlString, String token) { HttpURLConnection conn = null; try { Log.d(TAG, "Fetching: " + urlString); @@ -80,7 +83,7 @@ private static JSONArray fetchJsonArray(String urlString, String token) { if (token != null && !token.isEmpty()) { if (url.getHost().endsWith("acode.app")) { - connection.setRequestProperty("x-auth-token", token); + conn.setRequestProperty("x-auth-token", token); } else { Log.w(TAG, "Not adding auth token for untrusted URL: " + url); } From 4cac5b3e3e16e17bf2969821704654b88135b9fd Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 12:09:54 +0530 Subject: [PATCH 11/16] fix: added owned check for plugins purchased externally --- src/pages/plugin/plugin.js | 2 +- src/plugins/auth/src/android/PluginRetriever.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/plugin/plugin.js b/src/pages/plugin/plugin.js index 56f7f7f86..75c601851 100644 --- a/src/pages/plugin/plugin.js +++ b/src/pages/plugin/plugin.js @@ -168,7 +168,7 @@ export default async function PluginInclude( ]); if (product) { const purchase = await getPurchase(product.productId); - purchased = !!purchase; + purchased = !!purchase || remotePlugin.owned; price = product.price; purchaseToken = purchase?.purchaseToken; } diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 886d71f4b..5953f5d2e 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -15,8 +15,6 @@ public class PluginRetriever { private static final String TAG = "AcodePluginRetriever"; - private static final int LIMIT = 50; - private static final String API_BASE = "https://acode.app/api"; private static final String SUPPORTED_EDITOR = "cm"; From 56302196232c64011ec27eb4516a54926bbee646 Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 12:21:14 +0530 Subject: [PATCH 12/16] fix: resource leak --- .../auth/src/android/PluginRetriever.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index 5953f5d2e..f57b48c41 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -26,6 +26,8 @@ private static String withSupportedEditor(String url) { public static void downloadPlugin(String token, String pluginUrl, String destFile, CallbackContext callbackContext) { + HttpURLConnection connection = null; + try { // Strip file:// prefix if present if (destFile.startsWith("file://")) { @@ -33,8 +35,10 @@ public static void downloadPlugin(String token, String pluginUrl, String destFil } URL url = new URL(pluginUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); + connection.setConnectTimeout(15000); + connection.setReadTimeout(30000); if (token != null && !token.isEmpty()) { if (url.getHost().endsWith("acode.app")) { @@ -46,16 +50,21 @@ public static void downloadPlugin(String token, String pluginUrl, String destFil connection.connect(); - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - callbackContext.error("HTTP error: " + connection.getResponseCode()); + int responseCode = connection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + callbackContext.error("HTTP error: " + responseCode); return; } File tempFile = new File(destFile); - try (InputStream inputStream = connection.getInputStream(); - FileOutputStream outputStream = new FileOutputStream(tempFile)) { + + try ( + InputStream inputStream = connection.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(tempFile) + ) { byte[] buffer = new byte[4096]; int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } @@ -65,6 +74,11 @@ public static void downloadPlugin(String token, String pluginUrl, String destFil } catch (Exception e) { callbackContext.error("Download failed: " + e.getMessage()); + + } finally { + if (connection != null) { + connection.disconnect(); + } } } From 32a101279302ced5d706f3f5cb47edef0f481beb Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 12:28:03 +0530 Subject: [PATCH 13/16] fix: bug --- src/pages/plugin/plugin.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/plugin/plugin.js b/src/pages/plugin/plugin.js index 75c601851..4465e4214 100644 --- a/src/pages/plugin/plugin.js +++ b/src/pages/plugin/plugin.js @@ -171,6 +171,8 @@ export default async function PluginInclude( purchased = !!purchase || remotePlugin.owned; price = product.price; purchaseToken = purchase?.purchaseToken; + } else if (remotePlugin.owned) { + purchased = true; } } catch (error) { helpers.error(error); From d0cb25b4f88ee9ab60ddb1184c60528bb1d9982c Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Fri, 17 Apr 2026 12:38:04 +0530 Subject: [PATCH 14/16] fix: bug --- src/pages/plugins/plugins.js | 109 ++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/src/pages/plugins/plugins.js b/src/pages/plugins/plugins.js index e270ebab9..fa017b219 100644 --- a/src/pages/plugins/plugins.js +++ b/src/pages/plugins/plugins.js @@ -619,80 +619,85 @@ async function retrieveFilteredPlugins(filterState) { ); $list.installed.setAttribute("empty-msg", strings["no plugins found"]); } - async function getOwned() { $list.owned.setAttribute("empty-msg", strings["loading..."]); - const purchases = await helpers.promisify(iap.getPurchases); const disabledMap = settings.value.pluginsDisabled || {}; - - // Prevent duplicates const addedIds = new Set(); - // Play Store / IAP purchases - await Promise.all( - purchases.map(async ({ productIds }) => { - const [sku] = productIds; + // ------------------- + // Google Play / IAP + // ------------------- + try { + const purchases = await helpers.promisify(iap.getPurchases); - try { - const url = Url.join(constants.API_BASE, "plugin/owned", sku); - const plugin = await fsOperation(url).readFile("json"); + await Promise.all( + purchases.map(async ({ productIds }) => { + const [sku] = productIds; - if (!plugin || addedIds.has(plugin.id)) return; + try { + const url = Url.join(constants.API_BASE, "plugin/owned", sku); + const plugin = await fsOperation(url).readFile("json"); - const isInstalled = plugins.installed.find( - ({ id }) => id === plugin.id - ); + if (!plugin || addedIds.has(plugin.id)) return; - plugin.installed = !!isInstalled; + const isInstalled = plugins.installed.find( + ({ id }) => id === plugin.id + ); - if (plugin.installed) { - plugin.enabled = disabledMap[plugin.id] !== true; - plugin.onToggleEnabled = onToggleEnabled; - } + plugin.installed = !!isInstalled; - addedIds.add(plugin.id); - plugins.owned.push(plugin); - $list.owned.append(); - } catch (err) { - console.error("Failed to load owned IAP plugin:", err); - } - }) - ); + if (plugin.installed) { + plugin.enabled = disabledMap[plugin.id] !== true; + plugin.onToggleEnabled = onToggleEnabled; + } - // Razorpay / external purchases - try { - const url = withSupportedEditor( - `${constants.API_BASE}/plugins?owned=true` - ); + addedIds.add(plugin.id); + plugins.owned.push(plugin); + $list.owned.append(); + } catch (err) { + console.error("Failed to load owned IAP plugin:", err); + } + }) + ); + } catch (err) { + console.warn("IAP unavailable, continuing with Razorpay:", err); + } - const ownedPlugins = await fetchPlugins(url); + // ------------------- + // Razorpay purchases + // ------------------- + try { + const url = withSupportedEditor( + `${constants.API_BASE}/plugins?owned=true` + ); - ownedPlugins.forEach((plugin) => { - if (!plugin || addedIds.has(plugin.id)) return; + const ownedPlugins = await fetchPlugins(url); - const isInstalled = plugins.installed.find( - ({ id }) => id === plugin.id - ); + ownedPlugins.forEach((plugin) => { + if (!plugin || addedIds.has(plugin.id)) return; - plugin.installed = !!isInstalled; + const isInstalled = plugins.installed.find( + ({ id }) => id === plugin.id + ); - if (plugin.installed) { - plugin.enabled = disabledMap[plugin.id] !== true; - plugin.onToggleEnabled = onToggleEnabled; - } + plugin.installed = !!isInstalled; - addedIds.add(plugin.id); - plugins.owned.push(plugin); - $list.owned.append(); - }); - } catch (err) { - console.error("Failed to fetch owned plugins:", err); - } + if (plugin.installed) { + plugin.enabled = disabledMap[plugin.id] !== true; + plugin.onToggleEnabled = onToggleEnabled; + } - $list.owned.setAttribute("empty-msg", strings["no plugins found"]); + addedIds.add(plugin.id); + plugins.owned.push(plugin); + $list.owned.append(); + }); + } catch (err) { + console.error("Failed to fetch owned plugins:", err); } + $list.owned.setAttribute("empty-msg", strings["no plugins found"]); +} function onInstall(plugin) { if (updates) return; From dc802971e96a6667b96428220c9983196e42560a Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Sat, 18 Apr 2026 12:48:45 +0530 Subject: [PATCH 15/16] fix: remove razorpay mention --- src/pages/plugins/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/plugins/plugins.js b/src/pages/plugins/plugins.js index fa017b219..9e6154a51 100644 --- a/src/pages/plugins/plugins.js +++ b/src/pages/plugins/plugins.js @@ -661,7 +661,7 @@ async function getOwned() { }) ); } catch (err) { - console.warn("IAP unavailable, continuing with Razorpay:", err); + console.warn("IAP unavailable", err); } // ------------------- From e5c9527d92f8fbf1fdd786f7e3aeb7a27ea9c0ac Mon Sep 17 00:00:00 2001 From: Rohit Kushwaha Date: Sun, 19 Apr 2026 09:41:58 +0530 Subject: [PATCH 16/16] fix: host check --- .../auth/src/android/PluginRetriever.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/auth/src/android/PluginRetriever.java b/src/plugins/auth/src/android/PluginRetriever.java index f57b48c41..6d4d2f929 100644 --- a/src/plugins/auth/src/android/PluginRetriever.java +++ b/src/plugins/auth/src/android/PluginRetriever.java @@ -41,10 +41,13 @@ public static void downloadPlugin(String token, String pluginUrl, String destFil connection.setReadTimeout(30000); if (token != null && !token.isEmpty()) { - if (url.getHost().endsWith("acode.app")) { + String host = url.getHost(); + + if (host != null && + (host.equals("acode.app") || host.endsWith(".acode.app"))) { connection.setRequestProperty("x-auth-token", token); - } else { - Log.w(TAG, "Not adding auth token for untrusted URL: " + pluginUrl); + }else { + Log.w(TAG, "Not adding auth token for untrusted URL: " + url); } } @@ -94,9 +97,12 @@ public static JSONArray fetchJsonArray(String urlString, String token) { conn.setReadTimeout(5000); if (token != null && !token.isEmpty()) { - if (url.getHost().endsWith("acode.app")) { + String host = url.getHost(); + + if (host != null && + (host.equals("acode.app") || host.endsWith(".acode.app"))) { conn.setRequestProperty("x-auth-token", token); - } else { + }else { Log.w(TAG, "Not adding auth token for untrusted URL: " + url); } }