Skip to content

Commit

Permalink
Feat/coinlib split
Browse files Browse the repository at this point in the history
  • Loading branch information
jsamol authored and godenzim committed Oct 26, 2022
1 parent b4b53e2 commit 3811f52
Show file tree
Hide file tree
Showing 54 changed files with 4,150 additions and 2,695 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ $RECYCLE.BIN/
Thumbs.db
UserInterfaceState.xcuserstate

src/assets/libs/airgap-coin-lib.browserify.js
src/assets/libs/*.browserify.js
1 change: 1 addition & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.exifinterface:exifinterface:$androidxExifinterfaceVersion"
implementation project(':capacitor-android')
implementation 'androidx.webkit:webkit:1.4.0'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:core:$androidxTestCoreVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
Expand Down
5 changes: 2 additions & 3 deletions android/app/src/main/java/it/airgap/vault/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import android.os.Bundle;

import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;

import java.util.ArrayList;

import it.airgap.vault.plugin.appinfo.AppInfo;
import it.airgap.vault.plugin.camerapreview.CameraPreview;
import it.airgap.vault.plugin.isolatedmodules.IsolatedProtocol;
import it.airgap.vault.plugin.saplingnative.SaplingNative;
import it.airgap.vault.plugin.securityutils.SecurityUtils;

Expand All @@ -19,6 +17,7 @@ public void onCreate(Bundle savedInstanceState) {
registerPlugin(AppInfo.class);
registerPlugin(SecurityUtils.class);
registerPlugin(SaplingNative.class);
registerPlugin(IsolatedProtocol.class);

super.onCreate(savedInstanceState);
}
Expand Down
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"
}
}
29 changes: 29 additions & 0 deletions android/app/src/main/java/it/airgap/vault/util/JS.kt
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)
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)
}
28 changes: 28 additions & 0 deletions ios/App/App.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
88F9F15423D06A00008351D0 /* CameraPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F9F15323D06A00008351D0 /* CameraPreview.swift */; };
88F9F15623D06A76008351D0 /* CameraPreview.m in Sources */ = {isa = PBXBuildFile; fileRef = 88F9F15523D06A76008351D0 /* CameraPreview.m */; };
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
A33BA9E528D9C4EF00BD8015 /* JS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33BA9E428D9C4EF00BD8015 /* JS.swift */; };
A33BA9E728D9C52D00BD8015 /* JSValue+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33BA9E628D9C52D00BD8015 /* JSValue+Additions.swift */; };
A33BA9E928D9E23E00BD8015 /* WK+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33BA9E828D9E23E00BD8015 /* WK+Additions.swift */; };
A3CF6E4D28CA2FA100A9A883 /* IsolatedProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3CF6E4C28CA2FA100A9A883 /* IsolatedProtocol.swift */; };
A3CF6E4F28CA2FD900A9A883 /* IsolatedProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A3CF6E4E28CA2FD900A9A883 /* IsolatedProtocol.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -62,6 +67,11 @@
88F9F15123D069E9008351D0 /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; };
88F9F15323D06A00008351D0 /* CameraPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPreview.swift; sourceTree = "<group>"; };
88F9F15523D06A76008351D0 /* CameraPreview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreview.m; sourceTree = "<group>"; };
A33BA9E428D9C4EF00BD8015 /* JS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JS.swift; sourceTree = "<group>"; };
A33BA9E628D9C52D00BD8015 /* JSValue+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSValue+Additions.swift"; sourceTree = "<group>"; };
A33BA9E828D9E23E00BD8015 /* WK+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WK+Additions.swift"; sourceTree = "<group>"; };
A3CF6E4C28CA2FA100A9A883 /* IsolatedProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IsolatedProtocol.swift; sourceTree = "<group>"; };
A3CF6E4E28CA2FD900A9A883 /* IsolatedProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IsolatedProtocol.m; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -110,6 +120,7 @@
504EC3061FED79650016851F /* App */ = {
isa = PBXGroup;
children = (
A36CA3BE28CA2AAE00635173 /* IsolatedModules */,
8885568F2721A9EC009A831D /* public */,
885F55E525EE220A00D85A88 /* SaplingNative */,
88AE2EEA23D5CB1300428560 /* Helpers */,
Expand Down Expand Up @@ -165,6 +176,7 @@
isa = PBXGroup;
children = (
88AE2EEB23D5CB1C00428560 /* Extensions */,
A33BA9E428D9C4EF00BD8015 /* JS.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand All @@ -173,6 +185,8 @@
isa = PBXGroup;
children = (
88AE2EEC23D5CB3200428560 /* PluginCall+Additions.swift */,
A33BA9E628D9C52D00BD8015 /* JSValue+Additions.swift */,
A33BA9E828D9E23E00BD8015 /* WK+Additions.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand All @@ -196,6 +210,15 @@
path = Camera;
sourceTree = "<group>";
};
A36CA3BE28CA2AAE00635173 /* IsolatedModules */ = {
isa = PBXGroup;
children = (
A3CF6E4C28CA2FA100A9A883 /* IsolatedProtocol.swift */,
A3CF6E4E28CA2FD900A9A883 /* IsolatedProtocol.m */,
);
path = IsolatedModules;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -317,11 +340,16 @@
buildActionMask = 2147483647;
files = (
88AE2EF323D5DC3900428560 /* Keychain.swift in Sources */,
A33BA9E928D9E23E00BD8015 /* WK+Additions.swift in Sources */,
88F9F14723D059AE008351D0 /* AppInfo.m in Sources */,
A3CF6E4F28CA2FD900A9A883 /* IsolatedProtocol.m in Sources */,
A33BA9E728D9C52D00BD8015 /* JSValue+Additions.swift in Sources */,
A3CF6E4D28CA2FA100A9A883 /* IsolatedProtocol.swift in Sources */,
885F55E825EE224200D85A88 /* SaplingNative.m in Sources */,
49690A7E25C2BF80004A3586 /* VaultError.swift in Sources */,
88AE2EF923D5DC8300428560 /* SecureStorage.swift in Sources */,
88F9F15623D06A76008351D0 /* CameraPreview.m in Sources */,
A33BA9E528D9C4EF00BD8015 /* JS.swift in Sources */,
88AE2EF523D5DC5100428560 /* LocalAuthentication.swift in Sources */,
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
88AE2EEF23D5D7B300428560 /* SecurityUtils.m in Sources */,
Expand Down
43 changes: 43 additions & 0 deletions ios/App/App/Helpers/Extensions/JSValue+Additions.swift
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: ","))]"
}
}
16 changes: 16 additions & 0 deletions ios/App/App/Helpers/Extensions/WK+Additions.swift
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)
}
}
Loading

0 comments on commit 3811f52

Please sign in to comment.