diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/package-lock.json b/package-lock.json index a1f07947e..97f3075b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "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": "^14.0.1", "cordova-clipboard": "^1.3.0", "cordova-plugin-advanced-http": "^3.3.1", @@ -4010,6 +4011,10 @@ "resolved": "src/plugins/terminal", "link": true }, + "node_modules/com.foxdebug.acode.rk.plugin.plugincontext": { + "resolved": "src/plugins/pluginContext", + "link": true + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -8795,6 +8800,12 @@ "dev": true, "license": "ISC" }, + "src/plugins/pluginContext": { + "name": "com.foxdebug.acode.rk.plugin.plugincontext", + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, "src/plugins/proot": { "name": "com.foxdebug.acode.rk.exec.proot", "version": "1.0.0", diff --git a/package.json b/package.json index 1475f227d..850aed7af 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "com.foxdebug.acode.rk.exec.proot": {}, "com.foxdebug.acode.rk.exec.terminal": {}, "com.foxdebug.acode.rk.customtabs": {}, + "com.foxdebug.acode.rk.plugin.plugincontext": {}, "com.foxdebug.acode.rk.auth": {} }, "platforms": [ @@ -69,6 +70,7 @@ "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": "^14.0.1", "cordova-clipboard": "^1.3.0", "cordova-plugin-advanced-http": "^3.3.1", diff --git a/src/lib/loadPlugin.js b/src/lib/loadPlugin.js index 4078f29c9..bad8e81eb 100644 --- a/src/lib/loadPlugin.js +++ b/src/lib/loadPlugin.js @@ -56,6 +56,10 @@ export default async function loadPlugin(pluginId, justInstalled = false) { cacheFileUrl: await helpers.toInternalUri(cacheFile), cacheFile: fsOperation(cacheFile), firstInit: justInstalled, + ctx: await PluginContext.generate( + pluginId, + JSON.stringify(pluginJson), + ), }); resolve(); diff --git a/src/plugins/pluginContext/package.json b/src/plugins/pluginContext/package.json new file mode 100644 index 000000000..301d27b99 --- /dev/null +++ b/src/plugins/pluginContext/package.json @@ -0,0 +1,17 @@ +{ + "name": "com.foxdebug.acode.rk.plugin.plugincontext", + "version": "1.0.0", + "description": "PluginContext", + "cordova": { + "id": "com.foxdebug.acode.rk.plugin.plugincontext", + "platforms": [ + "android" + ] + }, + "keywords": [ + "ecosystem:cordova", + "cordova-android" + ], + "author": "@RohitKushvaha01", + "license": "MIT" +} diff --git a/src/plugins/pluginContext/plugin.xml b/src/plugins/pluginContext/plugin.xml new file mode 100644 index 000000000..2e7dd6ed5 --- /dev/null +++ b/src/plugins/pluginContext/plugin.xml @@ -0,0 +1,22 @@ + + + PluginContext + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/pluginContext/src/android/Tee.java b/src/plugins/pluginContext/src/android/Tee.java new file mode 100644 index 000000000..be6b75f4b --- /dev/null +++ b/src/plugins/pluginContext/src/android/Tee.java @@ -0,0 +1,138 @@ +package com.foxdebug.acode.rk.plugin; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.UUID; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class Tee extends CordovaPlugin { + + // pluginId : token + private /*static*/ final Map tokenStore = new ConcurrentHashMap<>(); + + //assigned tokens + private /*static*/ final Set disclosed = ConcurrentHashMap.newKeySet(); + + // token : list of permissions + private /*static*/ final Map> permissionStore = new ConcurrentHashMap<>(); + + @Override + public boolean execute(String action, JSONArray args, CallbackContext callback) + throws JSONException { + + if ("requestToken".equals(action)) { + String pluginId = args.getString(0); + String pluginJson = args.getString(1); + handleTokenRequest(pluginId, pluginJson, callback); + return true; + } + + if ("grantedPermission".equals(action)) { + String token = args.getString(0); + String permission = args.getString(1); + + if (!permissionStore.containsKey(token)) { + callback.error("INVALID_TOKEN"); + return true; + } + + boolean granted = grantedPermission(token, permission); + callback.success(granted ? 1 : 0); + return true; + } + + if ("listAllPermissions".equals(action)) { + String token = args.getString(0); + + if (!permissionStore.containsKey(token)) { + callback.error("INVALID_TOKEN"); + return true; + } + + List permissions = listAllPermissions(token); + JSONArray result = new JSONArray(permissions); + + callback.success(result); + return true; + } + + return false; + } + + //============================================================ + //do not change function signatures + public boolean isTokenValid(String token, String pluginId) { + String storedToken = tokenStore.get(pluginId); + return storedToken != null && token.equals(storedToken); + } + + + public boolean grantedPermission(String token, String permission) { + List permissions = permissionStore.get(token); + return permissions != null && permissions.contains(permission); + } + + public List listAllPermissions(String token) { + List permissions = permissionStore.get(token); + + if (permissions == null) { + return new ArrayList<>(); + } + + return new ArrayList<>(permissions); // return copy (safe) + } + //============================================================ + + + private synchronized void handleTokenRequest( + String pluginId, + String pluginJson, + CallbackContext callback + ) { + + if (disclosed.contains(pluginId)) { + callback.error("TOKEN_ALREADY_ISSUED"); + return; + } + + String token = tokenStore.get(pluginId); + + if (token == null) { + token = UUID.randomUUID().toString(); + tokenStore.put(pluginId, token); + } + + try { + JSONObject json = new JSONObject(pluginJson); + JSONArray permissions = json.optJSONArray("permissions"); + + List permissionList = new ArrayList<>(); + + if (permissions != null) { + for (int i = 0; i < permissions.length(); i++) { + permissionList.add(permissions.getString(i)); + } + } + + // Bind permissions to token + permissionStore.put(token, permissionList); + + } catch (JSONException e) { + callback.error("INVALID_PLUGIN_JSON"); + return; + } + + disclosed.add(pluginId); + callback.success(token); + } +} diff --git a/src/plugins/pluginContext/www/PluginContext.js b/src/plugins/pluginContext/www/PluginContext.js new file mode 100644 index 000000000..380dfbb9b --- /dev/null +++ b/src/plugins/pluginContext/www/PluginContext.js @@ -0,0 +1,65 @@ +var exec = require("cordova/exec"); + +const PluginContext = (function () { + //============================= + class _PluginContext { + constructor(uuid) { + this.created_at = Date.now(); + this.uuid = uuid; + Object.freeze(this); + } + + toString() { + return this.uuid; + } + + [Symbol.toPrimitive](hint) { + if (hint === "number") { + return NaN; // prevent numeric coercion + } + return this.uuid; + } + + grantedPermission(permission) { + return new Promise((resolve, reject) => { + exec(resolve, reject, "Tee", "grantedPermission", [ + this.uuid, + permission, + ]); + }); + } + + listAllPermissions() { + return new Promise((resolve, reject) => { + exec(resolve, reject, "Tee", "listAllPermissions", [this.uuid]); + }); + } + } + + //Object.freeze(this); + + //=============================== + + return { + generate: async function (pluginId, pluginJson) { + try { + function requestToken(pluginId) { + return new Promise((resolve, reject) => { + exec(resolve, reject, "Tee", "requestToken", [ + pluginId, + pluginJson, + ]); + }); + } + + const uuid = await requestToken(pluginId); + return new _PluginContext(uuid); + } catch (err) { + console.warn(`PluginContext creation failed for pluginId ${pluginId}:`, err); + return null; + } + }, + }; +})(); + +module.exports = PluginContext;