-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
54 changed files
with
4,150 additions
and
2,695 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
android/app/src/main/java/it/airgap/vault/plugin/isolatedmodules/IsolatedProtocol.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package it.airgap.vault.plugin.isolatedmodules | ||
|
||
import android.webkit.WebView | ||
import androidx.lifecycle.lifecycleScope | ||
import com.getcapacitor.* | ||
import com.getcapacitor.annotation.CapacitorPlugin | ||
import it.airgap.vault.util.JSCompletableDeferred | ||
import it.airgap.vault.util.JSUndefined | ||
import it.airgap.vault.util.addJavascriptInterface | ||
import it.airgap.vault.util.assertReceived | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withContext | ||
|
||
@CapacitorPlugin | ||
class IsolatedProtocol : Plugin() { | ||
|
||
@PluginMethod | ||
fun getField(call: PluginCall) { | ||
with(call) { | ||
assertReceived(Param.IDENTIFIER, Param.KEY) | ||
|
||
activity.lifecycleScope.launch(Dispatchers.Default) { | ||
call.resolve(getField(identifier, options, key)) | ||
} | ||
} | ||
} | ||
|
||
@PluginMethod | ||
fun callMethod(call: PluginCall) { | ||
with(call) { | ||
assertReceived(Param.IDENTIFIER, Param.KEY) | ||
|
||
activity.lifecycleScope.launch(Dispatchers.Default) { | ||
call.resolve(callMethod(identifier, options, key, args?.replaceNullWithUndefined())) | ||
} | ||
} | ||
} | ||
|
||
private suspend fun getField(identifier: String, options: JSObject?, key: String): Result<JSObject> = | ||
spawnCoinlibWebView(identifier, options, JSProtocolAction.GetField, """ | ||
var __platform__field = '$key' | ||
""".trimIndent()) | ||
|
||
private suspend fun callMethod(identifier: String, options: JSObject?, key: String, args: JSArray?): Result<JSObject> { | ||
val args = args?.toString() ?: "[]" | ||
|
||
return spawnCoinlibWebView(identifier, options, JSProtocolAction.CallMethod, """ | ||
var __platform__method = '$key' | ||
var __platform__args = $args | ||
""".trimIndent()) | ||
} | ||
|
||
private suspend fun spawnCoinlibWebView( | ||
identifier: String, | ||
options: JSObject?, | ||
action: JSProtocolAction, | ||
script: String? = null, | ||
): Result<JSObject> { | ||
val webViewContext = Dispatchers.Main | ||
|
||
val resultDeferred = JSCompletableDeferred("resultDeferred") | ||
val webView = withContext(webViewContext) { | ||
WebView(context).apply { | ||
settings.javaScriptEnabled = true | ||
addJavascriptInterface(resultDeferred) | ||
} | ||
} | ||
|
||
val html = """ | ||
<script type="text/javascript"> | ||
var __platform__identifier = '$identifier' | ||
var __platform__options = ${options.orUndefined()} | ||
var __platform__action = '${action.value}' | ||
${script ?: ""} | ||
function __platform__handleError(error) { | ||
${resultDeferred.name}.throwFromJS(error) | ||
} | ||
function __platform__handleResult(result) { | ||
${resultDeferred.name}.completeFromJS(JSON.stringify({ result })) | ||
} | ||
</script> | ||
<script src="$ASSETS_PATH/$COINLIB_SOURCE" type="text/javascript"></script> | ||
<script src="$ASSETS_PATH/$COMMON_SOURCE" type="text/javascript"></script> | ||
""".trimIndent() | ||
|
||
withContext(webViewContext) { | ||
webView.loadDataWithBaseURL( | ||
ASSETS_PATH, | ||
html, | ||
"text/html", | ||
"utf-8", | ||
null, | ||
) | ||
} | ||
|
||
return resultDeferred.await() | ||
} | ||
|
||
private fun JSObject?.orUndefined(): Any = this ?: JSUndefined | ||
|
||
private fun JSArray.replaceNullWithUndefined(): JSArray = | ||
JSArray(toList<Any>().map { if (it == JSObject.NULL) JSUndefined else it }) | ||
|
||
private fun PluginCall.resolve(result: Result<JSObject>) { | ||
result.onSuccess { resolve(it) } | ||
result.onFailure { errorCallback(it.message) } | ||
} | ||
|
||
private val PluginCall.identifier: String | ||
get() = getString(Param.IDENTIFIER)!! | ||
|
||
private val PluginCall.options: JSObject? | ||
get() = getObject(Param.OPTIONS, null) | ||
|
||
private val PluginCall.key: String | ||
get() = getString(Param.KEY)!! | ||
|
||
private val PluginCall.args: JSArray? | ||
get() = getArray(Param.ARGS, null) | ||
|
||
private enum class JSProtocolAction(val value: String) { | ||
GetField("getField"), | ||
CallMethod("callMethod") | ||
} | ||
|
||
private object Param { | ||
const val IDENTIFIER = "identifier" | ||
const val OPTIONS = "options" | ||
const val KEY = "key" | ||
const val ARGS = "args" | ||
} | ||
|
||
companion object { | ||
private const val ASSETS_PATH: String = "file:///android_asset" | ||
|
||
private const val COINLIB_SOURCE: String = "public/assets/libs/airgap-coin-lib.browserify.js" | ||
private const val COMMON_SOURCE: String = "public/assets/native/isolated_modules/protocol_common.js" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package it.airgap.vault.util | ||
|
||
import android.webkit.JavascriptInterface | ||
import com.getcapacitor.JSObject | ||
import kotlinx.coroutines.CompletableDeferred | ||
import java.util.* | ||
|
||
@Suppress("PrivatePropertyName") | ||
val JSUndefined: Any by lazy { | ||
object : Any() { | ||
override fun equals(other: Any?): Boolean = other == this || other?.equals(null) ?: true | ||
override fun hashCode(): Int = Objects.hashCode(null) | ||
override fun toString(): String = "undefined" | ||
} | ||
} | ||
|
||
class JSCompletableDeferred(val name: String) : CompletableDeferred<Result<JSObject>> by CompletableDeferred() { | ||
@JavascriptInterface | ||
fun completeFromJS(value: String) { | ||
complete(Result.success(JSObject(value))) | ||
} | ||
|
||
@JavascriptInterface | ||
fun throwFromJS(error: String) { | ||
complete(Result.failure(JSException(error))) | ||
} | ||
} | ||
|
||
class JSException(message: String) : Exception(message) |
8 changes: 8 additions & 0 deletions
8
android/app/src/main/java/it/airgap/vault/util/WebViewExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package it.airgap.vault.util | ||
|
||
import android.webkit.WebView | ||
|
||
|
||
fun WebView.addJavascriptInterface(jsCompletableDeferred: JSCompletableDeferred) { | ||
addJavascriptInterface(jsCompletableDeferred, jsCompletableDeferred.name) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// JSValue+Additions.swift | ||
// App | ||
// | ||
// Created by Julia Samol on 20.09.22. | ||
// | ||
|
||
import Foundation | ||
import Capacitor | ||
|
||
extension JSObject: JSONConvertible { | ||
|
||
func toJSONString() throws -> String { | ||
guard JSONSerialization.isValidJSONObject(self) else { | ||
throw JSError.invalidJSON | ||
} | ||
|
||
let data = try JSONSerialization.data(withJSONObject: self, options: []) | ||
return String(data: data, encoding: .utf8)! | ||
} | ||
} | ||
|
||
extension JSArray: JSONConvertible { | ||
|
||
func toJSONString() throws -> String { | ||
let jsonEncoder = JSONEncoder() | ||
let elements = try map { | ||
if JSONSerialization.isValidJSONObject($0) { | ||
let data = try JSONSerialization.data(withJSONObject: $0, options: []) | ||
return String(data: data, encoding: .utf8)! | ||
} else if let encodable = $0 as? Encodable { | ||
let data = try jsonEncoder.encode(encodable) | ||
return String(data: data, encoding: .utf8)! | ||
} else if let jsonConvertible = $0 as? JSONConvertible { | ||
return try jsonConvertible.toJSONString() | ||
} else { | ||
throw JSError.invalidJSON | ||
} | ||
} | ||
|
||
return "[\(elements.joined(separator: ","))]" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// WK+Additions.swift | ||
// App | ||
// | ||
// Created by Julia Samol on 20.09.22. | ||
// | ||
|
||
import Foundation | ||
import WebKit | ||
|
||
extension WKUserContentController { | ||
|
||
func add(_ jsCallbackHandler: JSCallbackHandler) { | ||
add(jsCallbackHandler, name: jsCallbackHandler.name) | ||
} | ||
} |
Oops, something went wrong.