diff --git a/assets/json/junit_report_example.xml b/assets/json/junit_report_example.xml new file mode 100644 index 0000000..c6415f7 --- /dev/null +++ b/assets/json/junit_report_example.xml @@ -0,0 +1,14 @@ + + + + + + WARNING: Use a program name that matches the source file name + Category: COBOL Code Review – Naming Conventions + File: /project/PROGRAM.cbl + Line: 2 + + + + \ No newline at end of file diff --git a/assets/json/test_json_3.json b/assets/json/test_json_3.json new file mode 100644 index 0000000..3f67c8d --- /dev/null +++ b/assets/json/test_json_3.json @@ -0,0 +1,237 @@ +{ + "setup": { + "name": "Experiment", + "description": "It shows what we can do with a card", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": false, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "TANGEM", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1626326543961 + }, + "steps": [ + { + "name": "0_scan", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Empty" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Empty" + ] + }, + { + "type": "EQUALS", + "fields": [ + "{#setup.personalizationConfig.manufacturer.name}", + "{#parent.actualResult.manufacturerName}" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "1_createWallet", + "method": "CREATE_WALLET_TASK", + "parameters": { + "config": { + "isReusable": true, + "prohibitPurgeWallet": null, + "curveId": "Secp256k1", + "signingMethods": "SignHash" + } + }, + "expectedResult": { + "cardId": "BB03000000000004", + "status": "Loaded", + "walletPublicKey": "049AD86A7F7F7696369A39D53CFA619FD1D917608AEF6CAF9F85BC9E7577D28B1C76EFF56384DDB867EFD1A38DE0479240258649A03CB76DF7EB8F8CAE9A8775C0" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Loaded" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "2_sign", + "method": "SIGN_COMMAND", + "parameters": { + "hashes": [ + "44617461207573656420666f722068617368696e67" + ], + "walletPublicKey": "{#1_createWallet.actualResult.walletPublicKey}" + }, + "expectedResult": { + "signedHashes": [ + "2EF25F7E70D4332E4C915A50BA56C3143950DD51130B4723F998AC05A2F5679F03462CCA69E2A3B37372A108CD2BB411FBD00575C9FC30DF4EFB82BB4BF93276" + ] + }, + "asserts": [ + { + "type": "IS_NOT_EMPTY", + "fields": [ + "{#parent.actualResult.signatures}" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + }, + { + "name": "scan_after_sign", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Empty", + "wallets": [ + { + "index": 0, + "status": "Empty", + "curve": "Secp256k1", + "signedHashes": 1 + } + ] + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.wallets.0.signedHashes}", + 0 + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "4_purgeWallet", + "method": "PURGE_WALLET_COMMAND", + "parameters": { + "walletIndex": 0 + }, + "expectedResult": { + "cardId": "BB03000000000004", + "status": "Empty" + }, + "asserts": [ + { + "type": "SUCCESS" + }, + { + "type": "EQUALS", + "fields": [ + "{#parent.expectedResult.status}", + "Empty" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + } + ] +} \ No newline at end of file diff --git a/assets/json/test_json_fail_3.json b/assets/json/test_json_fail_3.json new file mode 100644 index 0000000..acba8a8 --- /dev/null +++ b/assets/json/test_json_fail_3.json @@ -0,0 +1,183 @@ +{ + "setup": { + "name": "Experiment with fails", + "description": "It shows what we can do with a card", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": false, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "TANGEM", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 1, + "creationDateMs": 1626326543961 + }, + "steps": [ + { + "name": "0_scan", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Empty" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Empty" + ] + }, + { + "type": "EQUALS", + "fields": [ + "{#setup.personalizationConfig.manufacturer.name}", + "{#parent.actualResult.manufacturerName}" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "1_createWallet", + "method": "CREATE_WALLET_TASK", + "parameters": { + "config": { + "isReusable": true, + "prohibitPurgeWallet": null, + "curveId": "Secp256k1", + "signingMethods": "SignHash" + } + }, + "expectedResult": { + "cardId": "BB03000000000004", + "status": "Loaded", + "walletPublicKey": "049AD86A7F7F7696369A39D53CFA619FD1D917608AEF6CAF9F85BC9E7577D28B1C76EFF56384DDB867EFD1A38DE0479240258649A03CB76DF7EB8F8CAE9A8775C0" + }, + "asserts": [ + { + "type": "EQUALS", + "fields": [ + "{#parent.actualResult.status}", + "Loaded" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 1 + }, + { + "name": "2_sign", + "method": "SIGN_COMMAND", + "parameters": { + "hashes": [ + "44617461207573656420666f722068617368696e67" + ], + "walletPublicKey": "{#1_createWallet.actualResult.walletPublicKey}" + }, + "expectedResult": { + "signedHashes": [ + "2EF25F7E70D4332E4C915A50BA56C3143950DD51130B4723F998AC05A2F5679F03462CCA69E2A3B37372A108CD2BB411FBD00575C9FC30DF4EFB82BB4BF93276" + ] + }, + "asserts": [ + { + "type": "IS_NOT_EMPTY", + "fields": [ + "{#parent.actualResult.walletPublicKey}" + ] + } + ], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore index e96ef60..151026b 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -18,6 +18,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id deleted file mode 100644 index acbe8b3..0000000 --- a/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -28141c607f8a167c562e88ee9f9fc15d \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index be08867..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 29cbe91..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile index 57274ff..4cc8643 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,94 +1,43 @@ # Uncomment this line to define a global platform for your project -platform :ios, '13.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' -project 'Tangem DevKit', { +project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } -def parse_KV_file(file, separator='=') - file_abs_path = File.expand_path(file) - if !File.exists? file_abs_path - return []; +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end - generated_key_values = {} - skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) do |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - generated_key_values[podname] = podpath - else - puts "Invalid plugin specification: #{line}" - end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches end - generated_key_values + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end -target 'Tangem DevKit' do - use_frameworks! - use_modular_headers! - - # Flutter Pod - - copied_flutter_dir = File.join(__dir__, 'Flutter') - copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') - copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') - unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) - # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. - # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. - # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. - - generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') - unless File.exist?(generated_xcode_build_settings_path) - raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) - cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - unless File.exist?(copied_framework_path) - FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) - end - unless File.exist?(copied_podspec_path) - FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) - end - end - - # Keep pod path relative so it can be checked into Podfile.lock. - pod 'Flutter', :path => 'Flutter' +flutter_ios_podfile_setup - # Plugin Pods +target 'Runner' do + use_frameworks! + use_modular_headers! - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') - plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.each do |name, path| - symlink = File.join('.symlinks', 'plugins', name) - File.symlink(path, symlink) - pod name, :path => File.join(symlink, 'ios') - end + pod 'TangemSdk', :path => '../../tangem-sdk-ios' - pod 'TangemSdk' - #pod 'TangemSdk', :path => '../../card-sdk-swift' + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end -# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. -install! 'cocoapods', :disable_input_output_paths => true - post_install do |installer| installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' - end + flutter_additional_ios_build_settings(target) end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 65d466e..0f2122d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -8,6 +8,7 @@ PODS: - SDWebImageWebPCoder - fluttertoast (0.0.2): - Flutter + - Toast - libwebp (1.2.0): - libwebp/demux (= 1.2.0) - libwebp/mux (= 1.2.0) @@ -20,64 +21,32 @@ PODS: - Mantle (2.1.6): - Mantle/extobjc (= 2.1.6) - Mantle/extobjc (2.1.6) - - native_device_orientation (0.0.1): - - Flutter - path_provider (0.0.1): - Flutter - - path_provider_linux (0.0.1): - - Flutter - - path_provider_macos (0.0.1): - - Flutter - - path_provider_windows (0.0.1): - - Flutter - - SDWebImage/Core (5.10.4) - - SDWebImageWebPCoder (0.8.3): + - SDWebImage/Core (5.11.1) + - SDWebImageWebPCoder (0.8.4): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - share (0.0.1): - Flutter - shared_preferences (0.0.1): - Flutter - - shared_preferences_linux (0.0.1): - - Flutter - - shared_preferences_macos (0.0.1): - - Flutter - - shared_preferences_web (0.0.1): - - Flutter - - shared_preferences_windows (0.0.1): - - Flutter - tangem_sdk (0.0.3): - Flutter - - TangemSdk (~> 2.4.2) - - TangemSdk (2.4.2) - - video_player (0.0.1): - - Flutter - - video_player_web (0.0.1): - - Flutter - - wakelock (0.0.1): - - Flutter + - TangemSdk (~> 3.0.2) + - TangemSdk (3.0.2) + - Toast (4.0.0) DEPENDENCIES: - camera (from `.symlinks/plugins/camera/ios`) - Flutter (from `Flutter`) - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) - - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) - - path_provider_windows (from `.symlinks/plugins/path_provider_windows/ios`) - share (from `.symlinks/plugins/share/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - - shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`) - - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) - - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) - - shared_preferences_windows (from `.symlinks/plugins/shared_preferences_windows/ios`) - tangem_sdk (from `.symlinks/plugins/tangem_sdk/ios`) - - TangemSdk - - video_player (from `.symlinks/plugins/video_player/ios`) - - video_player_web (from `.symlinks/plugins/video_player_web/ios`) - - wakelock (from `.symlinks/plugins/wakelock/ios`) + - TangemSdk (from `../../tangem-sdk-ios`) SPEC REPOS: trunk: @@ -85,7 +54,7 @@ SPEC REPOS: - Mantle - SDWebImage - SDWebImageWebPCoder - - TangemSdk + - Toast EXTERNAL SOURCES: camera: @@ -96,63 +65,33 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_image_compress/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - native_device_orientation: - :path: ".symlinks/plugins/native_device_orientation/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" - path_provider_linux: - :path: ".symlinks/plugins/path_provider_linux/ios" - path_provider_macos: - :path: ".symlinks/plugins/path_provider_macos/ios" - path_provider_windows: - :path: ".symlinks/plugins/path_provider_windows/ios" share: :path: ".symlinks/plugins/share/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" - shared_preferences_linux: - :path: ".symlinks/plugins/shared_preferences_linux/ios" - shared_preferences_macos: - :path: ".symlinks/plugins/shared_preferences_macos/ios" - shared_preferences_web: - :path: ".symlinks/plugins/shared_preferences_web/ios" - shared_preferences_windows: - :path: ".symlinks/plugins/shared_preferences_windows/ios" tangem_sdk: :path: ".symlinks/plugins/tangem_sdk/ios" - video_player: - :path: ".symlinks/plugins/video_player/ios" - video_player_web: - :path: ".symlinks/plugins/video_player_web/ios" - wakelock: - :path: ".symlinks/plugins/wakelock/ios" + TangemSdk: + :path: "../../tangem-sdk-ios" SPEC CHECKSUMS: - camera: a0ca5080336f7af47b88436e5e26da3dee5568f0 - Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + camera: 3164201dc344383e62282964016528c4f5a9ad50 + Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c flutter_image_compress: 082f8daaf6c1b0c9fe798251c750ef0ecd98d7ae - fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b + fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58 libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0 Mantle: 4c0ed6ce47c96eccc4dc3bb071deb3def0e2c3be - native_device_orientation: e24d00be281de72996640885d80e706142707660 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c - path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 - path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 - path_provider_windows: a2b81600c677ac1959367280991971cb9a1edb3b - SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 - SDWebImageWebPCoder: bbf46e29fb8d1980a78ad3d5e9b4123c77f10ebc + SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d + SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815 share: 0b2c3e82132f5888bccca3351c504d0003b3b410 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78 - shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 - shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 - shared_preferences_windows: 36b76d6f54e76ead957e60b49e2f124b4cd3e6ae - tangem_sdk: e7063c3a57970265a09d435fc3f533286efddb6d - TangemSdk: 468a619db3a2105b2f93524893bc7aa5de0647c3 - video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e - video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 - wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 + tangem_sdk: 37d2c964859b56040342eb01cb3d2c35e4e8f2fb + TangemSdk: 26b8d99c941d307ce44c2eb0bb73d5337ced5bed + Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 -PODFILE CHECKSUM: c158dd0976c7bc7fa3d9cfd2d0fb213ba376cb3e +PODFILE CHECKSUM: b3ccbb53d4ae4e225daec72aebe6f42015aaa139 COCOAPODS: 1.10.1 diff --git a/ios/Tangem DevKit.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj similarity index 79% rename from ios/Tangem DevKit.xcodeproj/project.pbxproj rename to ios/Runner.xcodeproj/project.pbxproj index 41d5657..63b255b 100644 --- a/ios/Tangem DevKit.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,17 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5D3F3CFC25CAA8BC00BA3BC8 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D3F3CFB25CAA8BC00BA3BC8 /* App.framework */; }; + 4E7BA296778EAA717D7B3614 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C61C09B78255B9DC5704B4C5 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - FE8E94330493F527E1283A8C /* Pods_Tangem_DevKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A54215D1256FD8320E9A7CE2 /* Pods_Tangem_DevKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -33,26 +32,22 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 211F0E5BAD43410CA55ECEE2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 274A521632525F3F9DB03A17 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 5D3F3CFB25CAA8BC00BA3BC8 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 5DE841A3246D272300AE6931 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 66D8E5CB1DF2E9A73602813A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 3FA1D002D52E3F5FDF1BC105 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 5DE3952D268B3373007B0BCE /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Tangem DevKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Tangem DevKit.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A54215D1256FD8320E9A7CE2 /* Pods_Tangem_DevKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tangem_DevKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BD993B71A6F4C4E0CD17F7BD /* Pods-Tangem DevKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tangem DevKit.debug.xcconfig"; path = "Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.debug.xcconfig"; sourceTree = ""; }; - C9A49CB04432C2F706E9F7EB /* Pods-Tangem DevKit.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tangem DevKit.profile.xcconfig"; path = "Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.profile.xcconfig"; sourceTree = ""; }; - CF7C2B205B5D73E75615718A /* Pods-Tangem DevKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tangem DevKit.release.xcconfig"; path = "Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit.release.xcconfig"; sourceTree = ""; }; - DDFD9136A4E1F70D8F2BB736 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C61C09B78255B9DC5704B4C5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DD859713615359523B05CDDE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,14 +55,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5D3F3CFC25CAA8BC00BA3BC8 /* App.framework in Frameworks */, - FE8E94330493F527E1283A8C /* Pods_Tangem_DevKit.framework in Frameworks */, + 4E7BA296778EAA717D7B3614 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 81FDCC4795E568773F142C14 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C61C09B78255B9DC5704B4C5 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -85,15 +87,15 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - BC08B59DEF1C545A67596105 /* Pods */, - F75C03F14861BE7370BEB05F /* Frameworks */, + F7D6F278003A8834356819F3 /* Pods */, + 81FDCC4795E568773F142C14 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( - 97C146EE1CF9000F007C117D /* Tangem DevKit.app */, + 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; @@ -101,12 +103,11 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 5DE841A3246D272300AE6931 /* Runner.entitlements */, + 5DE3952D268B3373007B0BCE /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -115,58 +116,40 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { + F7D6F278003A8834356819F3 /* Pods */ = { isa = PBXGroup; children = ( + DD859713615359523B05CDDE /* Pods-Runner.debug.xcconfig */, + 274A521632525F3F9DB03A17 /* Pods-Runner.release.xcconfig */, + 3FA1D002D52E3F5FDF1BC105 /* Pods-Runner.profile.xcconfig */, ); - name = "Supporting Files"; - sourceTree = ""; - }; - BC08B59DEF1C545A67596105 /* Pods */ = { - isa = PBXGroup; - children = ( - 211F0E5BAD43410CA55ECEE2 /* Pods-Runner.debug.xcconfig */, - DDFD9136A4E1F70D8F2BB736 /* Pods-Runner.release.xcconfig */, - 66D8E5CB1DF2E9A73602813A /* Pods-Runner.profile.xcconfig */, - BD993B71A6F4C4E0CD17F7BD /* Pods-Tangem DevKit.debug.xcconfig */, - CF7C2B205B5D73E75615718A /* Pods-Tangem DevKit.release.xcconfig */, - C9A49CB04432C2F706E9F7EB /* Pods-Tangem DevKit.profile.xcconfig */, - ); + name = Pods; path = Pods; sourceTree = ""; }; - F75C03F14861BE7370BEB05F /* Frameworks */ = { - isa = PBXGroup; - children = ( - 5D3F3CFB25CAA8BC00BA3BC8 /* App.framework */, - A54215D1256FD8320E9A7CE2 /* Pods_Tangem_DevKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Tangem DevKit */ = { + 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Tangem DevKit" */; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - C63BF11100DAB782052ACD7B /* [CP] Check Pods Manifest.lock */, + 1C8047A9A95B48009C92A486 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 21A9DB3CE5A2D07C4AA6AE68 /* [CP] Embed Pods Frameworks */, + 0301E404DD96C2B851A9E10E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); - name = "Tangem DevKit"; + name = Runner; productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Tangem DevKit.app */; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -176,17 +159,16 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 4897UJ6D8C; LastSwiftMigration = 1100; }; }; }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Tangem DevKit" */; - compatibilityVersion = "Xcode 3.2"; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -198,7 +180,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 97C146ED1CF9000F007C117D /* Tangem DevKit */, + 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ @@ -218,70 +200,72 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 21A9DB3CE5A2D07C4AA6AE68 /* [CP] Embed Pods Frameworks */ = { + 0301E404DD96C2B851A9E10E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Tangem DevKit/Pods-Tangem DevKit-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 1C8047A9A95B48009C92A486 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Thin Binary"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Run Script"; + name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - C63BF11100DAB782052ACD7B /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( ); + name = "Run Script"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Tangem DevKit-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ @@ -374,21 +358,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4897UJ6D8C; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tangem.devkit; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -498,7 +476,8 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -511,21 +490,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4897UJ6D8C; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tangem.devkit; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -543,21 +516,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4897UJ6D8C; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tangem.devkit; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -570,7 +537,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Tangem DevKit" */ = { + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, @@ -580,7 +547,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Tangem DevKit" */ = { + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, diff --git a/ios/Tangem DevKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 71% rename from ios/Tangem DevKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a1..919434a 100644 --- a/ios/Tangem DevKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/ios/Tangem DevKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from ios/Tangem DevKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/ios/Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 78% rename from ios/Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 18d9810..f9b0d7c 100644 --- a/ios/Tangem DevKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,7 +2,7 @@ - IDEDidComputeMac32BitWarning - + PreviewsEnabled + diff --git a/ios/Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 78% rename from ios/Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme rename to ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae6a7b7..a28140c 100644 --- a/ios/Tangem DevKit.xcodeproj/xcshareddata/xcschemes/Tangem DevKit.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> @@ -27,17 +27,19 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> - - + + + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + + + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 9bb7689..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:Runner.xcodeproj"> diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index 7335fdf..308a2a5 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata b/ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index b83c8b5..0000000 --- a/ios/Tangem DevKit.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/lib/app/domain/actions_bloc/abstracts.dart b/lib/app/domain/actions_bloc/abstracts.dart index f6b3a05..9041b0e 100644 --- a/lib/app/domain/actions_bloc/abstracts.dart +++ b/lib/app/domain/actions_bloc/abstracts.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:devkit/app/domain/model/command_data_models.dart'; import 'package:devkit/commons/common_abstracts.dart'; +import 'package:devkit/main.dart'; import 'package:rxdart/rxdart.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; @@ -13,7 +14,7 @@ abstract class ActionBloc extends BaseBloc { final PublishSubject _commandDataIsReady = PublishSubject(); final PublishSubject _successResponse = PublishSubject(); - final PublishSubject _errorResponse = PublishSubject(); + final PublishSubject _errorResponse = PublishSubject(); ActionBloc() { addSubscription(bsCid.stream.listen((event) => _cid = event)); @@ -23,7 +24,7 @@ abstract class ActionBloc extends BaseBloc { Stream get successResponseStream => _successResponse.stream; - Stream get errorResponseStream => _errorResponse.stream; + Stream get errorResponseStream => _errorResponse.stream; Callback get callback => Callback((success) => sendSuccess(success), (error) => sendError(error)); @@ -31,7 +32,7 @@ abstract class ActionBloc extends BaseBloc { _successResponse.add(success); } - sendError(TangemSdkBaseError? error) { + sendError(TangemSdkPluginError? error) { _errorResponse.add(error); } @@ -47,7 +48,20 @@ abstract class ActionBloc extends BaseBloc { } invokeAction() async { - createCommandData((commandData) => _prepareCommandAndRun(commandData, callback), sendSnackbarMessage); + createCommandData((commandData) { + if (commandData.type == TangemSdk.cCreateWallet) { + _prepareCommandAndRun( + commandData, + Callback((result) { + if (result is CreateWalletResponse) { + gWalletPublicKey = result.walletPublicKey; + } + callback.onSuccess(result); + }, callback.onError)); + } else { + _prepareCommandAndRun(commandData, callback); + } + }, sendSnackbarMessage); } _prepareCommandAndRun(CommandDataModel commandData, Callback callback) async { diff --git a/lib/app/domain/actions_bloc/actions_blocs.dart b/lib/app/domain/actions_bloc/actions_blocs.dart index 1d1fe97..530ba5a 100644 --- a/lib/app/domain/actions_bloc/actions_blocs.dart +++ b/lib/app/domain/actions_bloc/actions_blocs.dart @@ -5,6 +5,7 @@ import 'package:devkit/app/domain/actions_bloc/personalize/personalization_value import 'package:devkit/app/domain/model/command_data_models.dart'; import 'package:devkit/app/domain/model/personalization/utils.dart'; import 'package:devkit/commons/common_abstracts.dart'; +import 'package:devkit/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; @@ -368,8 +369,7 @@ class SignBloc extends ActionBloc { addSubscription(bsWalletPublicKey.stream.listen((event) => _walletPublicKey = event)); bsDataForHashing.add("Data used for hashing"); //TODO: before trying to sign, must read the card and fetch walletPubKey (v.<4) - by 0 index, (v.>=4) - by using slider - bsWalletPublicKey.add( - "04B2A74E1E502A3E5C4B03B53412A5891F270752543D77B5FE685F3125196610E43C880E29ADA29B2D9641FEAB37A699355863F920DE98937B426B1F303A4752C5"); + bsWalletPublicKey.add(gWalletPublicKey); } @override diff --git a/lib/app/domain/actions_bloc/test_bloc.dart b/lib/app/domain/actions_bloc/test_bloc.dart index a96dff5..685a36b 100644 --- a/lib/app/domain/actions_bloc/test_bloc.dart +++ b/lib/app/domain/actions_bloc/test_bloc.dart @@ -22,7 +22,7 @@ class TestBlock extends ActionBloc { invokeAction() async { _clearFields(); if (_inputedCommand.isNullOrEmpty()) { - sendError(TangemSdkError("Input the command json first")); + sendError(PluginFlutterError("Input the command json first")); return; } @@ -31,11 +31,11 @@ class TestBlock extends ActionBloc { _commandType = _command![TangemSdk.commandType]; if (_commandType == null) { - sendError(TangemSdkError("Missing the commandType attribute")); + sendError(PluginFlutterError("Missing the commandType attribute")); return; } } catch (e) { - sendError(TangemSdkError("Json conversion error: $e")); + sendError(PluginFlutterError("Json conversion error: $e")); return; } @@ -43,10 +43,10 @@ class TestBlock extends ActionBloc { createCommandData((commandData) { TangemSdk.runCommand(callback, commandData); }, (errorMessage) { - sendError(TangemSdkError("Command data signature not created. Cause: $errorMessage")); + sendError(PluginFlutterError("Command data signature not created. Cause: $errorMessage")); }); } catch (e) { - sendError(TangemSdkError("Can't create the command data: $e")); + sendError(PluginFlutterError("Can't create the command data: $e")); } } @@ -116,8 +116,8 @@ class TestBlock extends ActionBloc { } @override - sendError(TangemSdkBaseError? error) { - if (error is UserCancelledError) return; + sendError(TangemSdkPluginError? error) { + if (error?.isUserCancelledError() == true) return; super.sendError(error); } diff --git a/lib/app/domain/model/command_data_models.dart b/lib/app/domain/model/command_data_models.dart index 29abf42..4323fcb 100644 --- a/lib/app/domain/model/command_data_models.dart +++ b/lib/app/domain/model/command_data_models.dart @@ -36,13 +36,22 @@ class SignModel extends CommandDataModel { class PersonalizationModel extends CommandDataModel { final PersonalizationConfig config; final Issuer issuer; + final Manufacturer manufacturer; + final Acquirer? acquirer; - PersonalizationModel(this.config, this.issuer) : super(TangemSdk.cPersonalize); + PersonalizationModel( + this.config, + this.issuer, + this.manufacturer, [ + this.acquirer, + ]) : super(TangemSdk.cPersonalize); factory PersonalizationModel.fromJson(Map json) { final model = PersonalizationModel( PersonalizationConfig.fromJson(json["config"]), Issuer.fromJson(json["issuer"]), + Manufacturer.fromJson(json["manufacturer"]), + Acquirer.fromJson(json["acquirer"]), ); return CommandDataModel.attachBaseData(model, json); } @@ -110,7 +119,7 @@ class WriteIssuerDataModel extends CommandDataModel { factory WriteIssuerDataModel.fromJson(Map json) { final model = WriteIssuerDataModel( - json[TangemSdk.cid], + json[TangemSdk.cardId], json[TangemSdk.issuerData], json[TangemSdk.privateKey], json[TangemSdk.issuerDataCounter], @@ -175,7 +184,7 @@ class WriteIssuerExDataModel extends CommandDataModel { factory WriteIssuerExDataModel.fromJson(Map json) { final model = WriteIssuerExDataModel( - json[TangemSdk.cid], + json[TangemSdk.cardId], json[TangemSdk.issuerData], json[TangemSdk.privateKey], json[TangemSdk.issuerDataCounter], diff --git a/lib/app/resources/keys.dart b/lib/app/resources/keys.dart index b3f0063..3725278 100644 --- a/lib/app/resources/keys.dart +++ b/lib/app/resources/keys.dart @@ -112,6 +112,7 @@ class ItemName { //hidden static final navigateToTestScreen = "navigateToTestScreen"; static final navigateToJsonTestAssembler = "navigateToJsonTestAssembler"; + static final navigateToJsonTestLauncher = "navigateToJsonTestLauncher"; static final commandJson = "commandJson"; static final responseSuccessJson = "responseSuccessJson"; static final responseErrorJson = "responseErrorJson"; diff --git a/lib/app/ui/screen/card_action/test_screen.dart b/lib/app/ui/screen/card_action/test_screen.dart index 7871171..5deee6a 100644 --- a/lib/app/ui/screen/card_action/test_screen.dart +++ b/lib/app/ui/screen/card_action/test_screen.dart @@ -113,7 +113,7 @@ class _TestBodyState extends State { child: TextWidget.center("Error Response"), ), HorizontalDelimiter(), - StreamBuilder( + StreamBuilder( stream: _bloc.errorResponseStream, initialData: null, builder: (context, snapshot) { @@ -121,7 +121,7 @@ class _TestBodyState extends State { if (snapshot.data == null) return stub; final data = snapshot.data!; - if (data is TangemSdkError) { + if (data is TangemSdkPluginError) { Fluttertoast.showToast(msg: data.message, toastLength: Toast.LENGTH_LONG); return stub; } else { diff --git a/lib/app/ui/widgets/menu/menu.dart b/lib/app/ui/widgets/menu/menu.dart index 9dba103..5542b3e 100644 --- a/lib/app/ui/widgets/menu/menu.dart +++ b/lib/app/ui/widgets/menu/menu.dart @@ -15,6 +15,7 @@ enum MenuItem { personalizationExport, navigateToTestScreen, navigateToJsonTestAssembler, + navigateToJsonTestLauncher, } class Menu { @@ -31,7 +32,10 @@ class Menu { Navigator.of(temporaryContext).pushNamed(Routes.TEST); break; case MenuItem.navigateToJsonTestAssembler: - Navigator.of(temporaryContext).pushNamed(Routes.JSON_TEST_LIST); + Navigator.of(temporaryContext).pushNamed(Routes.JSON_TEST_ASSEMBLER); + break; + case MenuItem.navigateToJsonTestLauncher: + Navigator.of(temporaryContext).pushNamed(Routes.JSON_TEST_LAUNCHER); break; } }, @@ -52,7 +56,13 @@ class Menu { value: MenuItem.navigateToJsonTestAssembler, child: TextWidget("Json tests assembler"), ); + final cardTesterScreenItem = PopupMenuItem( + key: ItemId.btnFrom(ItemName.navigateToJsonTestLauncher), + value: MenuItem.navigateToJsonTestLauncher, + child: TextWidget("Json tests launcher"), + ); menuItemList.add(jsonTestAssemblerScreenItem); + menuItemList.add(cardTesterScreenItem); return menuItemList; }, ); diff --git a/lib/app_test_assembler/domain/bloc/json_test_list_bloc.dart b/lib/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart similarity index 96% rename from lib/app_test_assembler/domain/bloc/json_test_list_bloc.dart rename to lib/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart index 7a839c5..26d9d3e 100644 --- a/lib/app_test_assembler/domain/bloc/json_test_list_bloc.dart +++ b/lib/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart @@ -8,14 +8,14 @@ import 'package:devkit/commons/common_abstracts.dart'; import 'package:rxdart/rxdart.dart'; import 'package:share/share.dart'; -class JsonTestListBloc extends BaseBloc { +class JsonTestAssemblerBloc extends BaseBloc { final bsRecords = BehaviorSubject>(); final StorageRepository _storageRepo; late final JsonTestsStorage _jsonTestsStorage; final _storedJsonTests = []; - JsonTestListBloc(this._storageRepo) { + JsonTestAssemblerBloc(this._storageRepo) { this._jsonTestsStorage = _storageRepo.testsStorage; addSubject(bsRecords); addSubscription(_jsonTestsStorage.isReadyToUseStream.listen(_listenStorageReady)); diff --git a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart index b46bbc7..80cd209 100644 --- a/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart +++ b/lib/app_test_assembler/domain/bloc/test_recorder_bloc.dart @@ -3,7 +3,6 @@ import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_assembler/domain/test_storages.dart'; import 'package:devkit/commons/common_abstracts.dart'; import 'package:tangem_sdk/model/command_data.dart'; -import 'package:tangem_sdk/sdk_plugin.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; class TestRecorderBlock extends BaseBloc { @@ -65,7 +64,7 @@ class TestRecorderBlock extends BaseBloc { _currentRecord = null; } - handleCommandError(TangemSdkBaseError? error) { + handleCommandError(TangemSdkPluginError? error) { _currentRecord = null; } } @@ -104,7 +103,7 @@ class TestAssembler { } } - TestStep? _createStep(StepRecord record, int index, TestStep stepConfig) { + StepModel? _createStep(StepRecord record, int index, StepModel stepConfig) { final errorMessage = "Can't create a test step for the command: ${record.commandData.type}"; if (!record.commandData.isPrepared()) { onErrorListener?.call("$errorMessage. Command isn't prepared."); @@ -125,10 +124,11 @@ class TestAssembler { final jsonRpc = JSONRPCRequest.fromCommandDataJson(jsonData); final expectedResult = (record.response as TangemSdkResponse).toJson(); - return TestStep( - "$index.${stepConfig.name}.${jsonRpc.method}", + final modelName = ["$index", jsonRpc.method].join("_"); + return StepModel( + modelName, jsonRpc.method, - jsonRpc.parameters, + jsonRpc.params, expectedResult, stepConfig.asserts, stepConfig.actionType, diff --git a/lib/app_test_assembler/domain/model/json_test_model.dart b/lib/app_test_assembler/domain/model/json_test_model.dart index 83561f2..e5381b2 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.dart @@ -8,7 +8,7 @@ part 'json_test_model.g.dart'; @JsonSerializable() class JsonTest { final TestSetup setup; - final List steps; + final List steps; JsonTest(this.setup, this.steps); @@ -16,10 +16,10 @@ class JsonTest { Map toJson() => _$JsonTestToJson(this); - JsonTest copyWith({TestSetup? setup, List? steps}) => JsonTest( - setup ?? this.setup, - steps ?? this.steps, - ); + JsonTest copyWith({TestSetup? setup, List? steps}) => JsonTest( + setup ?? this.setup, + steps ?? this.steps, + ); } @JsonSerializable() @@ -27,7 +27,7 @@ class TestSetup { final String name; final String description; final Map personalizationConfig; - final ConfigSdk? sdkConfig; + final Map sdkConfig; final FirmwareVersion? minimalFirmware; final String? platform; final int? iterations; @@ -36,8 +36,8 @@ class TestSetup { TestSetup( this.name, this.description, - this.personalizationConfig, [ - this.sdkConfig, + this.personalizationConfig, + this.sdkConfig, [ this.minimalFirmware, this.platform, this.iterations, @@ -51,7 +51,7 @@ class TestSetup { "Simple test", "It shows what we can do with a card", persCommandConfig, - null, + {}, null, null, 1, @@ -66,7 +66,7 @@ class TestSetup { String? name, String? description, Map? personalizationConfig, - ConfigSdk? sdkConfig, + Map? sdkConfig, FirmwareVersion? minimalFirmware, String? platform, int? iterations, @@ -83,55 +83,70 @@ class TestSetup { } @JsonSerializable() -class ConfigSdk { - ConfigSdk(); - - factory ConfigSdk.fromJson(Map json) => ConfigSdk(); - - Map toJson() => {}; -} - -@JsonSerializable() -class TestStep { +class StepModel { final String name; final String method; - final Map parameters; + final Map params = {}; final Map expectedResult; - final List asserts; + final List asserts; final String actionType; final int? iterations; - TestStep( + Map _rawParams = {}; + + Map get rawParams => {}..addAll(_rawParams); + + StepModel( this.name, this.method, - this.parameters, + Map params, this.expectedResult, this.asserts, this.actionType, this.iterations, - ); + ) : this._rawParams = params; - factory TestStep.getDefault() { - return TestStep("stepName", "methodName", {}, {}, [], "NFC_SESSION_RUNNABLE", 1); + factory StepModel.getDefault() { + return StepModel("stepName", "methodName", {}, {}, [], "NFC_SESSION_RUNNABLE", 1); } - factory TestStep.empty(String name, String method) { - return TestStep(name, method, {}, {}, [], "NFC_SESSION_RUNNABLE", 1); + factory StepModel.empty(String name, String method) { + return StepModel(name, method, {}, {}, [], "NFC_SESSION_RUNNABLE", 1); } - factory TestStep.fromJson(Map json) => _$TestStepFromJson(json); + factory StepModel.fromJson(Map json) => _$TestStepFromJson(json); Map toJson() => _$TestStepToJson(this); + + StepModel copyWith({ + String? name, + String? method, + Map? params, + Map? expectedResult, + List? asserts, + String? actionType, + int? iterations, + }) { + return StepModel( + name ?? this.name, + method ?? this.method, + params ?? this._rawParams, + expectedResult ?? this.expectedResult, + asserts ?? this.asserts, + actionType ?? this.actionType, + iterations ?? this.iterations, + ); + } } @JsonSerializable() -class TestAssert { +class AssertModel { final String type; - final List fields; + final List? fields; - TestAssert(this.type, this.fields); + AssertModel(this.type, this.fields); - factory TestAssert.fromJson(Map json) => _$TestAssertFromJson(json); + factory AssertModel.fromJson(Map json) => _$TestAssertFromJson(json); Map toJson() => _$TestAssertToJson(this); } diff --git a/lib/app_test_assembler/domain/model/json_test_model.g.dart b/lib/app_test_assembler/domain/model/json_test_model.g.dart index 34ddab9..7d1e08b 100644 --- a/lib/app_test_assembler/domain/model/json_test_model.g.dart +++ b/lib/app_test_assembler/domain/model/json_test_model.g.dart @@ -9,7 +9,7 @@ part of 'json_test_model.dart'; JsonTest _$JsonTestFromJson(Map json) { return JsonTest( TestSetup.fromJson(json['setup'] as Map), - (json['steps'] as List).map((e) => TestStep.fromJson(e as Map)).toList(), + (json['steps'] as List).map((e) => StepModel.fromJson(e as Map)).toList(), ); } @@ -23,11 +23,11 @@ TestSetup _$TestSetupFromJson(Map json) { json['name'] as String, json['description'] as String, json['personalizationConfig'] as Map, - json['sdkConfig'] == null ? null : ConfigSdk.fromJson(json['sdkConfig'] as Map), - json['minimalFirmware'] == null ? null : FirmwareVersion.fromJson(json['minimalFirmware']), + json['sdkConfig'] as Map, + json['minimalFirmware'] == null ? null : FirmwareVersion.fromJson(json['minimalFirmware'] as String), json['platform'] as String?, json['iterations'] as int?, - json['creationDateMs'] as int, + json['creationDateMs'] as int?, ); } @@ -42,42 +42,36 @@ Map _$TestSetupToJson(TestSetup instance) => { 'creationDateMs': instance.creationDateMs, }; -ConfigSdk _$ConfigSdkFromJson(Map json) { - return ConfigSdk(); -} - -Map _$ConfigSdkToJson(ConfigSdk instance) => {}; - -TestStep _$TestStepFromJson(Map json) { - return TestStep( +StepModel _$TestStepFromJson(Map json) { + return StepModel( json['name'] as String, json['method'] as String, json['parameters'] as Map, json['expectedResult'] as Map, - (json['asserts'] as List).map((e) => TestAssert.fromJson(e as Map)).toList(), + (json['asserts'] as List).map((e) => AssertModel.fromJson(e as Map)).toList(), json['actionType'] as String, json['iterations'] as int?, ); } -Map _$TestStepToJson(TestStep instance) => { +Map _$TestStepToJson(StepModel instance) => { 'name': instance.name, 'method': instance.method, - 'parameters': instance.parameters, + 'parameters': instance.params, 'expectedResult': instance.expectedResult, 'asserts': instance.asserts, 'actionType': instance.actionType, 'iterations': instance.iterations, }; -TestAssert _$TestAssertFromJson(Map json) { - return TestAssert( +AssertModel _$TestAssertFromJson(Map json) { + return AssertModel( json['type'] as String, - (json['fields'] as List).map((e) => e as String).toList(), + (json['fields'] == null ? null : (json['fields'] as List).map((e) => e).toList()), ); } -Map _$TestAssertToJson(TestAssert instance) => { +Map _$TestAssertToJson(AssertModel instance) => { 'type': instance.type, 'fields': instance.fields, }; diff --git a/lib/app_test_assembler/domain/test_storages.dart b/lib/app_test_assembler/domain/test_storages.dart index 07be9b5..4deb0b8 100644 --- a/lib/app_test_assembler/domain/test_storages.dart +++ b/lib/app_test_assembler/domain/test_storages.dart @@ -16,12 +16,12 @@ class TestSetupConfigStorage extends ConfigSharedPrefsStorage { TestSetup convertFrom(Map json) => TestSetup.fromJson(json); } -class TestStepConfigStorage extends ConfigSharedPrefsStorage { +class TestStepConfigStorage extends ConfigSharedPrefsStorage { TestStepConfigStorage() : super("testStepConfigStorage"); @override - TestStep getDefaultValue() => TestStep.getDefault(); + StepModel getDefaultValue() => StepModel.getDefault(); @override - TestStep convertFrom(Map json) => TestStep.fromJson(json); + StepModel convertFrom(Map json) => StepModel.fromJson(json); } diff --git a/lib/app_test_assembler/ui/screen/json_test_list_screen.dart b/lib/app_test_assembler/ui/screen/json_test_assembler_screen.dart similarity index 71% rename from lib/app_test_assembler/ui/screen/json_test_list_screen.dart rename to lib/app_test_assembler/ui/screen/json_test_assembler_screen.dart index eed4f33..79bd334 100644 --- a/lib/app_test_assembler/ui/screen/json_test_list_screen.dart +++ b/lib/app_test_assembler/ui/screen/json_test_assembler_screen.dart @@ -1,6 +1,6 @@ import 'package:devkit/app/ui/screen/card_action/helpers.dart'; import 'package:devkit/app/ui/widgets/app_widgets.dart'; -import 'package:devkit/app_test_assembler/domain/bloc/json_test_list_bloc.dart'; +import 'package:devkit/app_test_assembler/domain/bloc/json_test_assembler_bloc.dart'; import 'package:devkit/app_test_assembler/domain/bloc/test_recorder_bloc.dart'; import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; import 'package:devkit/app_test_assembler/ui/screen/json_test_detail_screen.dart'; @@ -11,22 +11,24 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tangem_sdk/extensions/exp_extensions.dart'; -class JsonTestListScreen extends StatefulWidget { - const JsonTestListScreen({Key? key}) : super(key: key); +class JsonTestAssemblerScreen extends StatefulWidget { + const JsonTestAssemblerScreen({Key? key}) : super(key: key); @override - _JsonTestListScreenState createState() => _JsonTestListScreenState(); + _JsonTestAssemblerScreenState createState() => _JsonTestAssemblerScreenState(); } -class _JsonTestListScreenState extends State { - late JsonTestListBloc _bloc; +class _JsonTestAssemblerScreenState extends State { + late JsonTestAssemblerBloc _bloc; @override Widget build(BuildContext context) { final storageRepo = context.read().storageRepo; return MultiRepositoryProvider( - providers: [RepositoryProvider(create: (context) => JsonTestListBloc(storageRepo).apply((it) => _bloc = it))], - child: JsonTestListFrame(), + providers: [ + RepositoryProvider(create: (context) => JsonTestAssemblerBloc(storageRepo).apply((it) => _bloc = it)) + ], + child: JsonTestAssemblerFrame(), ); } @@ -37,7 +39,7 @@ class _JsonTestListScreenState extends State { } } -class JsonTestListFrame extends StatelessWidget { +class JsonTestAssemblerFrame extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( @@ -45,25 +47,25 @@ class JsonTestListFrame extends StatelessWidget { title: Text("Test assembler"), actions: [StartStopRecordWidget()], ), - body: JsonTestListBody(), + body: JsonTestAssemblerBody(), ); } } -class JsonTestListBody extends StatefulWidget { +class JsonTestAssemblerBody extends StatefulWidget { @override - _JsonTestListBodyState createState() => _JsonTestListBodyState(); + _JsonTestAssemblerBodyState createState() => _JsonTestAssemblerBodyState(); } -class _JsonTestListBodyState extends State { - late JsonTestListBloc _jsonTestListBloc; +class _JsonTestAssemblerBodyState extends State { + late JsonTestAssemblerBloc _jsonTestAssemblerBloc; late TestRecorderBlock _testRecorderBlock; @override void initState() { super.initState(); - _jsonTestListBloc = context.read(); + _jsonTestAssemblerBloc = context.read(); _testRecorderBlock = context.read().testRecorderBloc; } @@ -71,9 +73,9 @@ class _JsonTestListBodyState extends State { Widget build(BuildContext context) { return Stack( children: [ - HiddenSnackBarHandlerWidget([_jsonTestListBloc, _testRecorderBlock]), + HiddenSnackBarHandlerWidget([_jsonTestAssemblerBloc, _testRecorderBlock]), StreamBuilder>( - stream: _jsonTestListBloc.bsRecords.stream, + stream: _jsonTestAssemblerBloc.bsRecords.stream, builder: (context, snapshot) { if (snapshot.data == null) return CenterLoadingText(); if (snapshot.data!.isEmpty) return CenterText("No tests have been created yet"); @@ -91,9 +93,10 @@ class _JsonTestListBodyState extends State { children: [ IconButton( icon: Icon(Icons.delete_outline), - onPressed: _testRecorderBlock.recordIsActive() ? null : () => _jsonTestListBloc.delete(index), + onPressed: + _testRecorderBlock.recordIsActive() ? null : () => _jsonTestAssemblerBloc.delete(index), ), - IconButton(icon: Icon(Icons.ios_share), onPressed: () => _jsonTestListBloc.share(index)), + IconButton(icon: Icon(Icons.ios_share), onPressed: () => _jsonTestAssemblerBloc.share(index)), ], ), ); diff --git a/lib/app_test_launcher/domain/common/test_result.dart b/lib/app_test_launcher/domain/common/test_result.dart new file mode 100644 index 0000000..0f39090 --- /dev/null +++ b/lib/app_test_launcher/domain/common/test_result.dart @@ -0,0 +1,40 @@ +import 'package:devkit/app_test_launcher/domain/error/error.dart'; + +class TestResult {} + +class Success implements TestResult { + final String? name; + + Success([this.name]); +} + +class StepSuccess extends Success { + StepSuccess(String name) : super(name); +} + +class AssertSuccess extends Success { + AssertSuccess(String name) : super(name); +} + +class Failure implements TestResult { + final TestFrameworkError error; + + Failure(this.error); +} + +class StepFailure extends Failure { + final String stepName; + final String? assertName; + + StepFailure(this.stepName, TestFrameworkError error, [this.assertName]) : super(error); + + factory StepFailure.fromAssert(String stepName, AssertFailure failure) { + return StepFailure(stepName, failure.error, failure.assertName); + } +} + +class AssertFailure extends Failure { + final String assertName; + + AssertFailure(this.assertName, TestFrameworkError error) : super(error); +} diff --git a/lib/app_test_launcher/domain/common/typedefs.dart b/lib/app_test_launcher/domain/common/typedefs.dart new file mode 100644 index 0000000..93d9d2d --- /dev/null +++ b/lib/app_test_launcher/domain/common/typedefs.dart @@ -0,0 +1,7 @@ +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; + +typedef SourceMap = Map; +typedef OnComplete = void Function(TestResult result); +typedef OnTestSequenceComplete = void Function(TestFrameworkError? error); +typedef OnStepSequenceComplete = void Function(TestResult result); diff --git a/lib/app_test_launcher/domain/error/error.dart b/lib/app_test_launcher/domain/error/error.dart new file mode 100644 index 0000000..db67248 --- /dev/null +++ b/lib/app_test_launcher/domain/error/error.dart @@ -0,0 +1,31 @@ +import 'package:tangem_sdk/model/json_rpc.dart'; +import 'package:tangem_sdk/plugin_error.dart'; + +abstract class TestFrameworkError { + String get errorMessage; +} + +class TangemSdkPluginWrappedError extends TestFrameworkError { + final TangemSdkPluginError error; + + TangemSdkPluginWrappedError(this.error); + + @override + String get errorMessage => "TangemSdkPluginError: ${error.toString()}"; +} + +extension OnJSONRPCError on JSONRPCError { + bool isInterruptTest() { + switch (code) { + case -32000: + return data != null && data!.contains("code: 50002"); + case 1000: + return true; + case 50002: + return true; + case 50003: + return true; + } + return false; + } +} diff --git a/lib/app_test_launcher/domain/error/test_assert_error.dart b/lib/app_test_launcher/domain/error/test_assert_error.dart new file mode 100644 index 0000000..5e467db --- /dev/null +++ b/lib/app_test_launcher/domain/error/test_assert_error.dart @@ -0,0 +1,30 @@ +import 'error.dart'; + +abstract class TestAssertError extends TestFrameworkError {} + +class ExpectedAndActualResultError extends TestAssertError { + final String? message; + + ExpectedAndActualResultError([this.message]); + + @override + String get errorMessage => "Expected and actual results doesn't match. $message"; +} + +class EqualsError extends TestAssertError { + final dynamic firstValue; + final dynamic secondValue; + + EqualsError(this.firstValue, this.secondValue); + + @override + String get errorMessage => "Fields doesn't match. f1: $firstValue, f2: $secondValue"; +} + +class IsNotEmptyError extends TestAssertError { + final String fieldName; + + IsNotEmptyError(this.fieldName); + @override + String get errorMessage => "Field $fieldName is empty"; +} diff --git a/lib/app_test_launcher/domain/error/test_step_error.dart b/lib/app_test_launcher/domain/error/test_step_error.dart new file mode 100644 index 0000000..6b0568f --- /dev/null +++ b/lib/app_test_launcher/domain/error/test_step_error.dart @@ -0,0 +1,12 @@ +import 'error.dart'; + +abstract class TestStepError extends TestFrameworkError {} + +class AssertNotRegisteredError extends TestStepError { + final String name; + + AssertNotRegisteredError(this.name); + + @override + String get errorMessage => "Executable is not found for the name: $name"; +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert.dart b/lib/app_test_launcher/domain/executable/assert/assert.dart new file mode 100644 index 0000000..758522b --- /dev/null +++ b/lib/app_test_launcher/domain/executable/assert/assert.dart @@ -0,0 +1,83 @@ +import 'dart:core'; + +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_assert_error.dart'; +import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; + +class AssertsFactory { + Map _assertsBuilders = {}; + + registerAssert(String type, TestAssert Function() assertBuilder) { + _assertsBuilders[type] = assertBuilder; + } + + TestAssert? getAssert(String type) { + return _assertsBuilders[type]?.call(); + } +} + +abstract class TestAssert implements Executable { + final String _type; + + late String parentName; + late List fields; + + TestAssert(this._type); + + void init(String parentName, List? fields) { + this.parentName = parentName; + this.fields = fields == null ? [] : fields; + } + + @override + void run(OnComplete callback); + + dynamic _getFieldValue(dynamic pointer) { + return pointer is String ? VariableService.getValue(parentName, pointer) : pointer; + } + + static String equals = "EQUALS"; + static String isNotEmpty = "IS_NOT_EMPTY"; + static String success = "SUCCESS"; +} + +class EqualsAssert extends TestAssert { + EqualsAssert() : super(TestAssert.equals); + + @override + run(OnComplete callback) { + final firstValue = _getFieldValue(fields[0]); + final secondValue = _getFieldValue(fields[1]); + if (firstValue == secondValue) { + callback(AssertSuccess(_type)); + } else { + callback(AssertFailure(_type, EqualsError(firstValue, secondValue))); + } + } +} + +class IsNotEmptyAssert extends TestAssert { + IsNotEmptyAssert() : super(TestAssert.isNotEmpty); + + @override + void run(OnComplete callback) { + final value = _getFieldValue(fields[0]); + if (value != null) { + callback(AssertSuccess(_type)); + } else { + _getFieldValue(fields[0]); + callback(AssertFailure(_type, IsNotEmptyError("${fields[0]}"))); + } + } +} + +class SuccessAssert extends TestAssert { + SuccessAssert() : super(TestAssert.success); + + @override + void run(OnComplete callback) { + callback(AssertSuccess(_type)); + } +} diff --git a/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart b/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart new file mode 100644 index 0000000..af077af --- /dev/null +++ b/lib/app_test_launcher/domain/executable/assert/assert_launcher.dart @@ -0,0 +1,33 @@ +import 'dart:collection'; + +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +import 'assert.dart'; + +class AssertsLauncher implements Executable { + final Queue _assertsQueue; + + AssertsLauncher(this._assertsQueue); + + @override + void run(OnComplete callback) { + _executeAssert(_assertsQueue.poll(), callback); + } + + void _executeAssert(TestAssert? testAssert, OnComplete callback) { + if (testAssert == null) { + callback(Success()); + } else { + testAssert.run((result) { + if (result is AssertSuccess) { + _executeAssert(_assertsQueue.poll(), callback); + } else { + callback(result); + } + }); + } + } +} diff --git a/lib/app_test_launcher/domain/executable/executable.dart b/lib/app_test_launcher/domain/executable/executable.dart new file mode 100644 index 0000000..1e03f7b --- /dev/null +++ b/lib/app_test_launcher/domain/executable/executable.dart @@ -0,0 +1,5 @@ +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; + +abstract class Executable { + void run(OnComplete callback); +} diff --git a/lib/app_test_launcher/domain/executable/step/step_launcher.dart b/lib/app_test_launcher/domain/executable/step/step_launcher.dart new file mode 100644 index 0000000..0b45b30 --- /dev/null +++ b/lib/app_test_launcher/domain/executable/step/step_launcher.dart @@ -0,0 +1,109 @@ +import 'dart:collection'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_assert_error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_step_error.dart'; +import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; +import 'package:devkit/app_test_launcher/domain/executable/assert/assert_launcher.dart'; +import 'package:devkit/app_test_launcher/domain/executable/executable.dart'; +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; +import 'package:tangem_sdk/tangem_sdk.dart'; + +class StepLauncher implements Executable { + final StepModel _model; + final AssertsFactory _assertsFactory; + + StepLauncher(this._model, this._assertsFactory) { + VariableService.registerStep(_model.name, _model.toJson()); + } + + @override + void run(OnComplete callback) { + _fetchVariables(); + + final jsonRpcRequestCallback = Callback((result) { + final jsonRpcResponse = result as JSONRPCResponse; + if (jsonRpcResponse.result == null && jsonRpcResponse.error != null) { + if (jsonRpcResponse.error?.isInterruptTest() == true) { + // TODO: приудмать что отправить + // callback(Failure()); + return; + } else { + VariableService.registerError(_model.name, jsonRpcResponse.error); + } + } else { + final error = _checkExpectedWithActualResult(_model.expectedResult, jsonRpcResponse); + if (error != null) { + callback(StepFailure(_model.name, error)); + return; + } + VariableService.registerActualResult(_model.name, jsonRpcResponse.result); + } + _executeAsserts(callback); + }, (error) { + callback(StepFailure(_model.name, TangemSdkPluginWrappedError(error))); + }); + + TangemSdk.runJSONRPCRequest(jsonRpcRequestCallback, JSONRPCRequest(_model.method, _model.params)); + } + + void _fetchVariables() { + _model.params.clear(); + _model.rawParams.forEach((key, value) { + //TODO: добавить раскрытие переменных во вложенных структурах + final extractedValue = VariableService.getValue(_model.name, value); + _model.params[key] = extractedValue; + }); + } + + void _executeAsserts(OnComplete callback) { + if (_model.asserts.isEmpty) { + callback(StepSuccess(_model.name)); + return; + } + + final assertsQueue = Queue(); + for (final element in _model.asserts){ + final testAssert = _assertsFactory.getAssert(element.type); + if (testAssert == null) { + callback(StepFailure(_model.name, AssertNotRegisteredError(element.type))); + return; + } + + testAssert.init(_model.name, element.fields); + assertsQueue.add(testAssert); + } + + AssertsLauncher(assertsQueue).run((result) { + if (result is Success) { + callback(StepSuccess(_model.name)); + } else { + callback(StepFailure.fromAssert(_model.name, result as AssertFailure)); + } + }); + } + + TestAssertError? _checkExpectedWithActualResult(Map expResult, JSONRPCResponse jsonRpcResponse) { + if (jsonRpcResponse.result is! Map) + return ExpectedAndActualResultError("JsonRpc result is not a map"); + + return null; + + final actualResult = jsonRpcResponse.result as Map; + if (expResult.length != actualResult.length) return ExpectedAndActualResultError("Results length doesn't match"); + + final missedKeys = expResult.keys.toList()..removeWhere((element) => actualResult.containsKey(element)); + final unexpectedKeys = actualResult.keys.toList()..removeWhere((element) => expResult.containsKey(element)); + + if (missedKeys.isNotEmpty || unexpectedKeys.isNotEmpty) { + final missed = "Missed keys in the actual result: [${missedKeys.join(", ")}]"; + final unexpected = "Unexpected keys in the actual result: [${unexpectedKeys.join(", ")}]"; + return ExpectedAndActualResultError("$missed. $unexpected"); + } + + return null; + } +} diff --git a/lib/app_test_launcher/domain/json_value_finder.dart b/lib/app_test_launcher/domain/json_value_finder.dart new file mode 100644 index 0000000..7b61c27 --- /dev/null +++ b/lib/app_test_launcher/domain/json_value_finder.dart @@ -0,0 +1,83 @@ +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +class JsonValueFinder { + final _variablePattern = RegExp("\\{[^\\{\\}]*\\}"); + final _bracketLeft = "{"; + final _bracketRight = "}"; + + final _variables = >{}; + + final String pathDelimiter; + + JsonValueFinder([this.pathDelimiter = "."]); + + void reset() { + _variables.clear(); + } + + void setValue(String name, dynamic value) { + _variables[name] = value; + } + + dynamic removeValue(String name) { + return _variables.remove(name); + } + + dynamic getValue(dynamic pointer) { + return canBeInterpret(pointer) ? getValueFrom(pointer, _variables) : pointer; + } + + dynamic getValueFrom(String pointer, dynamic from) { + if (from == null) return null; + + return _getValueByPattern(removeBrackets(pointer).split(pathDelimiter), 0, from); + } + + dynamic _getValueByPattern(List? pathList, int position, dynamic result) { + if (result == null) return null; + if (pathList == null || position >= pathList.length) return result; + + final key = pathList[position]; + if (result is Map) { + return _getValueByPattern(pathList, ++position, result[key]); + } + + if (result is List) { + if (!key.isNumber()) return null; + + final index = int.parse(key.toString()); + if (index >= result.length) { + return null; + } else { + return _getValueByPattern(pathList, ++position, result[index]); + } + } else { + return result; + } + } + + bool canBeInterpret(dynamic pointer) { + if (pointer == null) { + return false; + } else if (pointer is! String) { + return false; + } else if (!containsVariable(pointer)) { + return false; + } else + return true; + } + + bool containsVariable(String? pointer) { + if (pointer == null || !pointer.contains(_bracketLeft)) return false; + + return _variablePattern.hasMatch(pointer); + } + + String removeBrackets(String text) { + if (text.startsWith(_bracketLeft) && text.endsWith(_bracketRight)) { + return text.substring(1, text.length - 1); + } else { + return text; + } + } +} diff --git a/lib/app_test_launcher/domain/repository/repositories.dart b/lib/app_test_launcher/domain/repository/repositories.dart new file mode 100644 index 0000000..c5397a9 --- /dev/null +++ b/lib/app_test_launcher/domain/repository/repositories.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_assembler/domain/test_storages.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +abstract class JsonTestsRepository { + Future init(); + + List getList(); + + JsonTest? get(String name); +} + +class JsonTestStorageRepository implements JsonTestsRepository { + final JsonTestsStorage storage = JsonTestsStorage(); + + @override + Future init() { + final completer = Completer(); + storage.isReadyToUseStream.listen((event) { + if (event) completer.complete(); + }); + return completer.future; + } + + @override + JsonTest? get(String name) { + return storage.get(name); + } + + @override + List getList() { + return storage.names().map((e) => storage.get(e)).toList().toNullSafe(); + } +} + +class JsonTestAssetsRepository implements JsonTestsRepository { + List _jsonTests = []; + + @override + Future init() async { + final completer = Completer(); + + final manifestContent = await rootBundle.loadString('AssetManifest.json'); + + final Map manifestMap = json.decode(manifestContent); + // >> To get paths you need these 2 lines + + final testPaths = manifestMap.keys + .where((String key) => key.contains('json/')) + .where((String key) => key.contains('.json')) + .toList(); + + int inconvertibleCount = 0; + testPaths.forEach((element) { + rootBundle.loadString(element).then((value) { + try { + final map = jsonDecode(value); + _jsonTests.add(JsonTest.fromJson(map)); + } catch (ex) { + inconvertibleCount++; + } + if (testPaths.length - inconvertibleCount == _jsonTests.length) completer.complete(); + }); + }); + + return completer.future; + } + + @override + JsonTest? get(String name) { + return _jsonTests.firstWhereOrNull((e) => e.setup.name == name); + } + + @override + List getList() { + return _jsonTests; + } +} diff --git a/lib/app_test_launcher/domain/test_launcher.dart b/lib/app_test_launcher/domain/test_launcher.dart new file mode 100644 index 0000000..026113f --- /dev/null +++ b/lib/app_test_launcher/domain/test_launcher.dart @@ -0,0 +1,202 @@ +import 'dart:collection'; +import 'dart:convert'; + +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_launcher/domain/error/error.dart'; +import 'package:devkit/app_test_launcher/domain/error/test_step_error.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; +import 'package:tangem_sdk/tangem_sdk.dart'; + +import 'common/test_result.dart'; +import 'common/typedefs.dart'; +import 'error/test_assert_error.dart'; +import 'executable/assert/assert.dart'; +import 'executable/step/step_launcher.dart'; +import 'variable_service.dart'; + +class TestLauncher { + static final int defaultIterationsCount = 1; + + final JsonTest _jsonTest; + final AssertsFactory _assertsFactory; + OnComplete? onTestComplete; + + Queue _stepQueue = Queue(); + + late JsonTest _testTemplate; + late StepModel _stepTemplate; + + int _testIterationsLeft = defaultIterationsCount; + int _stepIterationsLeft = defaultIterationsCount; + + List _successSteps = []; + StepFailure? _stepFailure; + + TestLauncher(this._jsonTest, this._assertsFactory); + + void launch() async { + if (_jsonTest.steps.isEmpty) { + onTestComplete?.call(Success(_jsonTest.setup.name)); + return; + } + + _testTemplate = _jsonTest.copyWith(); + _testIterationsLeft = _jsonTest.setup.iterations ?? defaultIterationsCount; + _startNewTest(_jsonTest); + } + + void _startNewTest(JsonTest test) async { + await Future.delayed(Duration(milliseconds: 1000)); + if (_testIterationsLeft == 0) { + onTestComplete?.call(Success(_jsonTest.setup.name)); + return; + } + + VariableService.reset(); + VariableService.registerSetup(test.setup.toJson()); + + print(""); + print("Test: ${test.setup.name}: Start"); + _prepare(() async { + print("Test: Prepare: Complete"); + await Future.delayed(Duration(milliseconds: 1000)); + _startSession(() { + _runTest(test); + }); + }); + } + + void _runTest(JsonTest test) { + print("Test: ${test.setup.name}: Run: iterations left: $_testIterationsLeft"); + _testIterationsLeft--; + _stepQueue = Queue.from(test.steps); + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(Success(test.setup.name)); + } else { + _runStep(nextStep); + } + } + + void _runStep(StepModel step, [bool isNewIteration = true]) async { + await Future.delayed(Duration(milliseconds: 500)); + if (isNewIteration) { + _stepTemplate = step.copyWith(); + _stepIterationsLeft = step.iterations ?? defaultIterationsCount; + } + print("Test: ${_testTemplate.setup.name}: Step: ${step.name}: Run: iterations left: $_stepIterationsLeft"); + _stepIterationsLeft--; + StepLauncher(step, _assertsFactory).run(_onStepComplete); + } + + void _onStepComplete(TestResult result) { + if (result is Success) { + final stepName = result.name ?? "Undefined"; + _successSteps.add(stepName); + print("Test: ${_testTemplate.setup.name}: Step: $stepName: Complete"); + if (_stepIterationsLeft == 0) { + final nextStep = _stepQueue.poll(); + if (nextStep == null) { + _onStepSequenceComplete(result); + } else { + _runStep(nextStep); + } + } else { + _runStep(_stepTemplate, false); + } + } else { + _onStepSequenceComplete(result); + } + } + + void _onStepSequenceComplete(TestResult result) { + print("Test: ${_testTemplate.setup.name}: Complete"); + if (result is Success) { + _stopSession(() { + _startNewTest(_testTemplate); + }); + } else { + final failure = result as StepFailure; + _stopSession(() { + _onStepError(failure); + }, _extractErrorMessage(failure)); + } + } + + void _prepare(Function onSuccess) { + print("Test: Prepare"); + _stepFailure = null; + _rePersonalize(onSuccess); + } + + void _rePersonalize(Function onSuccess) { + final config = _jsonTest.setup.personalizationConfig; + final personalizationRequest = JSONRPCRequest(TangemSdk.getJsonRpcMethod(TangemSdk.cPersonalize)!, config); + final onPersonalizeCallback = Callback((result) { + print("Test: Prepare: PERSONALIZE complete"); + _stopSession(onSuccess); + }, _onSdkError); + + final depersonalizeRequest = JSONRPCRequest(TangemSdk.getJsonRpcMethod(TangemSdk.cDepersonalize)!, {}); + final onDepersonalizeCallback = Callback((result) { + print("Test: Prepare: DEPERSONALIZE complete"); + TangemSdk.runJSONRPCRequest(onPersonalizeCallback, personalizationRequest); + }, _onSdkError); + + _startSession(() { + print("Test: Prepare: Run: DE/PERSONALIZE"); + TangemSdk.runJSONRPCRequest(onDepersonalizeCallback, depersonalizeRequest); + }); + } + + // все ошибки TangemSdkPluginError должны прерывать выполнение теста, т.к. они формируется + // не в результате работы jsonRPC + + _onSdkError(dynamic error) { + print("Error type: ${error.runtimeType.toString()}"); + print(jsonEncode(error)); + } + + _onStepError(StepFailure failure) { + _stepFailure = failure; + final message = _extractErrorMessage(failure); + print(message); + print(jsonEncode(failure.error)); + } + + String _extractErrorMessage(StepFailure failure) { + if (failure.error is TangemSdkPluginWrappedError) { + return "TangemSdkPluginWrappedError: ${failure.error.errorMessage}"; + } else if (failure.error is TestAssertError) { + return "${failure.error.runtimeType.toString()}. ${failure.error.errorMessage}"; + } else if (failure.error is TestStepError) { + return "Step failure: ${failure.error.runtimeType.toString()}"; + } else { + return "Some undefined failure: ${failure.error.runtimeType.toString()}"; + } + } + + void _startSession(Function onSuccess) { + // print("Starting session..."); + TangemSdk.startSession( + Callback((success) { + print("Session started +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + onSuccess(); + }, _onSdkError), + { + TangemSdk.initialMessage: _jsonTest.setup.sdkConfig[TangemSdk.initialMessage], + TangemSdk.cardId: _jsonTest.setup.sdkConfig[TangemSdk.cardId], + }); + } + + //TODO: останавливать сессию только при ошибке ассертов + void _stopSession([Function? onSuccess, String? error]) { + // print("Stopping session..."); + TangemSdk.stopSession( + Callback((success) { + print("Session stopped ---------------------------------------------------------------------"); + onSuccess?.call(); + }, _onSdkError), + error != null ? {"error": error} : {}); + } +} diff --git a/lib/app_test_launcher/domain/variable_service.dart b/lib/app_test_launcher/domain/variable_service.dart new file mode 100644 index 0000000..9bda294 --- /dev/null +++ b/lib/app_test_launcher/domain/variable_service.dart @@ -0,0 +1,74 @@ +import 'package:devkit/app_test_launcher/domain/common/typedefs.dart'; + +import 'json_value_finder.dart'; + +class VariableService { + static final _valueFinder = JsonValueFinder(); + + static final _targetKey = "#"; + static final _parentTargetKey = "#parent"; + + static final _actualResult = "actualResult"; + static final _error = "error"; + + static void registerSetup(SourceMap source) { + _valueFinder.setValue("setup", source); + } + + static void registerStep(String name, SourceMap source) { + _valueFinder.setValue(name, source); + } + + static void registerActualResult(String name, dynamic result) { + final stepMap = _valueFinder.getValue("{$name}"); + if (stepMap == null) { + // Step is not registered + return; + } + + stepMap[_actualResult] = result; + } + + static void registerError(String name, dynamic result) { + final stepMap = _valueFinder.getValue("{$name}"); + if (stepMap == null) { + // Step is not registered + return; + } + + stepMap[_error] = result; + } + + static dynamic getValue(String name, dynamic pointer) { + if (!_valueFinder.canBeInterpret(pointer)) return pointer; + + if (_containsTarget(pointer)) { + final targetPointer = _extractPointer(pointer); + if (targetPointer == null) return null; + + final targetName = targetPointer == _parentTargetKey ? name : _extractTargetName(targetPointer); + final valuePointer = pointer.replaceAll("$targetPointer.", ""); + final target = _valueFinder.getValue("{$targetName}"); + return target == null ? null : _valueFinder.getValueFrom(valuePointer, target); + } else { + return _valueFinder.getValueFrom(pointer, _valueFinder.getValue("{$name}")); + } + } + + static String _extractTargetName(String stepPointer) => stepPointer.replaceAll(_targetKey, ""); + + static String? _extractPointer(String pointer) => _getPrefix(_valueFinder.removeBrackets(pointer)); + + static String? _getPrefix(String value) { + final suffixIdx = value.indexOf("."); + return suffixIdx < 0 ? null : value.substring(0, suffixIdx); + } + + static bool _containsTarget(String? pointer) { + return pointer == null ? false : pointer.indexOf(_targetKey) == 1; + } + + static void reset() { + _valueFinder.reset(); + } +} diff --git a/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart new file mode 100644 index 0000000..8ea34a1 --- /dev/null +++ b/lib/app_test_launcher/ui/screen/json_test_launcher_screen.dart @@ -0,0 +1,137 @@ +import 'package:devkit/app/ui/screen/card_action/helpers.dart'; +import 'package:devkit/app/ui/widgets/app_widgets.dart'; +import 'package:devkit/app_test_assembler/domain/model/json_test_model.dart'; +import 'package:devkit/app_test_launcher/domain/common/test_result.dart'; +import 'package:devkit/app_test_launcher/domain/executable/assert/assert.dart'; +import 'package:devkit/app_test_launcher/domain/repository/repositories.dart'; +import 'package:devkit/app_test_launcher/domain/test_launcher.dart'; +import 'package:devkit/commons/common_abstracts.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:tangem_sdk/extensions/exp_extensions.dart'; + +class JsonTestLauncherScreen extends StatefulWidget { + const JsonTestLauncherScreen({Key? key}) : super(key: key); + + @override + _JsonTestLauncherScreenState createState() => _JsonTestLauncherScreenState(); +} + +class _JsonTestLauncherScreenState extends State { + late JsonTestLauncherBloc _bloc; + + @override + Widget build(BuildContext context) { + return MultiRepositoryProvider( + providers: [ + RepositoryProvider( + create: (context) => JsonTestLauncherBloc(JsonTestAssetsRepository()).apply((it) => _bloc = it)), + ], + child: JsonTestLauncherFrame(), + ); + } + + @override + void dispose() { + _bloc.dispose(); + super.dispose(); + } +} + +class JsonTestLauncherFrame extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("Tests")), + body: JsonTestLauncherBody(), + ); + } +} + +class JsonTestLauncherBody extends StatefulWidget { + final bool attachSnackBarHandler; + + const JsonTestLauncherBody({Key? key, this.attachSnackBarHandler = true}) : super(key: key); + + @override + _JsonTestLauncherBodyState createState() => _JsonTestLauncherBodyState(); +} + +class _JsonTestLauncherBodyState extends State { + late JsonTestLauncherBloc _bloc; + + @override + void initState() { + super.initState(); + _bloc = context.read(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + HiddenSnackBarHandlerWidget([_bloc]), + StubStreamBuilder>( + _bloc.bsJsonTests.stream, + (context, data) { + return ListView.separated( + itemCount: _bloc.repo.getList().length, + separatorBuilder: (context, index) => HorizontalDelimiter(), + itemBuilder: (context, index) { + final jsonTest = data[index]; + return ListTile( + title: Text(jsonTest.setup.name), + subtitle: Text(jsonTest.setup.description), + trailing: IconButton( + icon: Icon(Icons.play_arrow), + onPressed: () { + _bloc.launch(jsonTest); + }, + ), + ); + }, + ); + }, + ), + ], + ); + } +} + +class JsonTestLauncherBloc extends BaseBloc { + final JsonTestsRepository repo; + final bsJsonTests = BehaviorSubject>(); + + JsonTestLauncherBloc(this.repo) { + addSubject(bsJsonTests); + initRepository(); + } + + void initRepository() async { + await repo.init(); + bsJsonTests.add(repo.getList()); + } + + void launch(JsonTest jsonTest) { + final assertsFactory = AssertsFactory().apply((it) { + it.registerAssert(TestAssert.equals, () => EqualsAssert()); + it.registerAssert(TestAssert.isNotEmpty, () => IsNotEmptyAssert()); + it.registerAssert(TestAssert.success, () => SuccessAssert()); + }); + final launcher = TestLauncher(jsonTest, assertsFactory); + launcher.onTestComplete = (result) { + if (result is Success) { + final message = result.name ?? "Unknown test"; + sendSnackbarMessage("$message is completed"); + } else { + sendSnackbarMessage(result); + } + }; + launcher.launch(); + } + + void _handleError(dynamic error) { + sendSnackbarMessage(error); + } +} diff --git a/lib/commons/common_abstracts.dart b/lib/commons/common_abstracts.dart index c1e733d..85424ef 100644 --- a/lib/commons/common_abstracts.dart +++ b/lib/commons/common_abstracts.dart @@ -59,9 +59,9 @@ abstract class Disposable { dispose(); } -abstract class DisposableBloc extends Disposable {} +abstract class DisposableBloc implements Disposable {} -abstract class BaseBloc extends DisposableBloc with SnackBarStreamHolder { +abstract class AsyncBloc implements DisposableBloc { final _subscriptions = []; final _subjects = []; @@ -78,8 +78,8 @@ abstract class BaseBloc extends DisposableBloc with SnackBarStreamHolder { _subscriptions.forEach((element) => element.cancel()); _subjects.forEach((element) => element.close()); } - } +abstract class BaseBloc with AsyncBloc, SnackBarStreamHolder {} abstract class SnackBarStreamHolder { final PublishSubject _snackbarMessage = PublishSubject(); diff --git a/lib/main.dart b/lib/main.dart index e71ad2f..b5564d8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,3 +8,5 @@ void main() async { final launcher = App.isDebug ? DebugLauncher() : ReleaseLauncher(); await launcher.launch(); } + +String gWalletPublicKey = ""; \ No newline at end of file diff --git a/lib/navigation/routes.dart b/lib/navigation/routes.dart index 86d747e..88e5988 100644 --- a/lib/navigation/routes.dart +++ b/lib/navigation/routes.dart @@ -19,10 +19,11 @@ import 'package:devkit/app/ui/screen/card_action/wallet_create_screen.dart'; import 'package:devkit/app/ui/screen/card_action/wallet_purge_screen.dart'; import 'package:devkit/app/ui/screen/main_screen.dart'; import 'package:devkit/app/ui/screen/response/response_screen.dart'; +import 'package:devkit/app_test_assembler/ui/screen/json_test_assembler_screen.dart'; import 'package:devkit/app_test_assembler/ui/screen/json_test_detail_screen.dart'; -import 'package:devkit/app_test_assembler/ui/screen/json_test_list_screen.dart'; import 'package:devkit/app_test_assembler/ui/screen/test_setup_detail_screen.dart'; import 'package:devkit/app_test_assembler/ui/screen/test_step_detail_screen.dart'; +import 'package:devkit/app_test_launcher/ui/screen/json_test_launcher_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:tangem_sdk/model/sdk.dart'; @@ -30,10 +31,11 @@ import 'package:tangem_sdk/model/sdk.dart'; class Routes { static const MAIN = "/"; static const TEST = "/test"; - static const JSON_TEST_LIST = "/json_test_list"; + static const JSON_TEST_ASSEMBLER = "/json_test_assembler"; static const JSON_TEST_DETAIL = "/json_test_detail"; static const TEST_SETUP_DETAIL = "/test_setup_detail"; static const TEST_STEP_DETAIL = "/test_step_detail"; + static const JSON_TEST_LAUNCHER = "/json_test_launcher"; static const SCAN = "/scan"; static const SIGN = "/sign"; static const PERSONALIZE = "/personalize"; @@ -101,8 +103,8 @@ class Routes { return PageRouteBuilder(pageBuilder: (_, __, ___) => ResponseScreen(arguments: settings.arguments)); case TEST: return MaterialPageRoute(builder: (_) => TestScreen()); - case JSON_TEST_LIST: - return MaterialPageRoute(builder: (_) => JsonTestListScreen()); + case JSON_TEST_ASSEMBLER: + return MaterialPageRoute(builder: (_) => JsonTestAssemblerScreen()); case JSON_TEST_DETAIL: return CupertinoPageRoute( builder: (_) => JsonTestDetailScreen(), @@ -118,6 +120,8 @@ class Routes { builder: (_) => TestStepDetailScreen(), settings: settings, ); + case JSON_TEST_LAUNCHER: + return MaterialPageRoute(builder: (_) => JsonTestLauncherScreen()); default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/plugin/tangem-sdk-flutter/android/build.gradle b/plugin/tangem-sdk-flutter/android/build.gradle index c93048f..2c003c7 100644 --- a/plugin/tangem-sdk-flutter/android/build.gradle +++ b/plugin/tangem-sdk-flutter/android/build.gradle @@ -18,6 +18,7 @@ rootProject.allprojects { repositories { google() jcenter() + mavenLocal() maven {url "https://nexus.tangem-tech.com/repository/maven-releases/"} maven {url 'https://jitpack.io'} } @@ -52,8 +53,12 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.tangem:core:develop-37' - implementation 'com.tangem:sdk:develop-37' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-46' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-46' + + // local dependencies +// implementation 'com.tangem.tangem-sdk-kotlin:core:0.0.1' +// implementation 'com.tangem.tangem-sdk-kotlin:android:0.0.1' implementation "com.squareup.sqldelight:android-driver:1.4.0" diff --git a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt index 1db19e2..fef8d5f 100644 --- a/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt +++ b/plugin/tangem-sdk-flutter/android/src/main/kotlin/com/tangem/flutter/plugin/tangem_sdk/TangemSdkPlugin.kt @@ -10,7 +10,7 @@ import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.* import com.tangem.commands.common.card.FirmwareType import com.tangem.commands.common.jsonConverter.MoshiJsonConverter -import com.tangem.commands.file.FileData +import com.tangem.commands.file.DataToWrite import com.tangem.commands.file.FileSettingsChange import com.tangem.common.CardValuesDbStorage import com.tangem.common.CardValuesStorage @@ -39,6 +39,8 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var wActivity: WeakReference private lateinit var sdk: TangemSdk + private var cardSession: CardSession? = null + private var replyAlreadySubmit = false override fun onAttachedToActivity(pluginBinding: ActivityPluginBinding) { @@ -82,8 +84,9 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - val channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, "tangemSdk") - channel.setMethodCallHandler(this) + val messenger = flutterPluginBinding.binaryMessenger + MethodChannel(messenger, "tangemSdk").setMethodCallHandler(this) + MethodChannel(messenger, "tangemSdk_JSONRPC").setMethodCallHandler(this) } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { @@ -92,6 +95,9 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { replyAlreadySubmit = false when (call.method) { + "startSession" -> startSession(call, result) + "stopSession" -> stopSession(call, result) + "runJSONRPCRequest" -> runJSONRPCRequest(call, result) "allowsOnlyDebugCards" -> allowsOnlyDebugCards(call, result) "scanCard" -> scanCard(call, result) "sign" -> sign(call, result) @@ -118,6 +124,103 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } } + private fun startSession(call: MethodCall, result: Result) { + try { + if (cardSession != null && cardSession !!.state == CardSessionState.Active) + throw PluginException("The CardSession has already started") + + sdk.startSession( + call.extractOptional("cardId"), + call.extractOptional("initialMessage"), + ) { session, error -> + if (error == null) { + cardSession = session + handleResult(result, CompletionResult.Success(true)) + } else { + cardSession = null + handleResult(result, CompletionResult.Failure(error)) + } + } + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun stopSession(call: MethodCall, result: Result) { + try { + val session = cardSession ?: throw PluginException("Session not started") + session.stop() + cardSession = null + handleResult(result, CompletionResult.Success(true)) + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun runJSONRPCRequest(call: MethodCall, result: Result) { + try { + replyAlreadySubmit = false + val stringOfJSONRPCRequest = call.extract("JSONRPCRequest") + + val callback = callbackWithResult@{ response: String -> + if (! replyAlreadySubmit) { + replyAlreadySubmit = true + handler.post { result.success(response) } + } + } + + if (cardSession == null) { + sdk.startSessionWithJsonRequest( + stringOfJSONRPCRequest, + call.extractOptional("cardId"), + call.extractOptional("initialMessage"), + callback + ) + } else { + cardSession !!.run(stringOfJSONRPCRequest, callback) + } + } catch (ex: Exception) { + handleException(result, ex) + } + } + + private fun handleResult(result: Result, completionResult: CompletionResult<*>) { + if (replyAlreadySubmit) return + replyAlreadySubmit = true + + when (completionResult) { + is CompletionResult.Success -> { + handler.post { result.success(converter.toJson(completionResult.data)) } + } + is CompletionResult.Failure -> { + val error = completionResult.error + val errorMessage = if (error is TangemSdkError) { + val activity = wActivity.get() + if (activity == null) error.customMessage else error.localizedDescription(activity) + } else { + error.customMessage + } + val pluginError = PluginError(error.code, errorMessage) + handler.post { + result.error("${error.code}", errorMessage, converter.toJson(pluginError)) + } + } + } + } + + private fun handleException(result: Result, ex: Exception) { + if (replyAlreadySubmit) return + replyAlreadySubmit = true + + val exception = ex as? PluginException ?: TangemSdkException(ex) + handler.post { + val code = 1000 + val localizedDescription: String = exception.toString() + result.error("$code", localizedDescription, + converter.toJson(PluginError(code, localizedDescription))) + } + } + private fun scanCard(call: MethodCall, result: Result) { try { sdk.scanCard( @@ -144,10 +247,10 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { private fun personalize(call: MethodCall, result: Result) { try { sdk.personalize( - call.extract("cardConfig"), + call.extract("config"), call.extract("issuer"), call.extract("manufacturer"), - call.extract("acquirer"), + call.extractOptional("acquirer"), call.extractOptional("initialMessage") ) { handleResult(result, it) } } catch (ex: Exception) { @@ -384,45 +487,13 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { } } - private fun handleResult(result: Result, completionResult: CompletionResult<*>) { - if (replyAlreadySubmit) return - replyAlreadySubmit = true - - when (completionResult) { - is CompletionResult.Success -> { - handler.post { result.success(converter.toJson(completionResult.data)) } - } - is CompletionResult.Failure -> { - val error = completionResult.error - val errorMessage = if (error is TangemSdkError) { - val activity = wActivity.get() - if (activity == null) error.customMessage else error.localizedDescription(activity) - } else { - error.customMessage - } - val pluginError = PluginError(error.code, errorMessage) - handler.post { - result.error("${error.code}", errorMessage, converter.toJson(pluginError)) - } - } - } - } - - private fun handleException(result: Result, ex: Exception) { - if (replyAlreadySubmit) return - replyAlreadySubmit = true - - handler.post { - val code = 9999 - val localizedDescription: String = ex.toString() - result.error("$code".capitalize(), localizedDescription, - converter.toJson(PluginError(code, localizedDescription))) - } - } - - @Throws(Exception::class) + @Throws(PluginException::class) inline fun MethodCall.extract(name: String): T { - return this.extractOptional(name) ?: throw NoSuchFieldException(name) + return try { + this.extractOptional(name) ?: throw PluginException("MethodCall.extract: no such field: $name, or field is NULL") + } catch (ex: Exception) { + throw ex as? PluginException ?: PluginException("MethodCall.extractOptional", ex) + } } inline fun MethodCall.extractOptional(name: String): T? { @@ -457,7 +528,7 @@ class TangemSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { sealed class FileCommand { data class Read(val readPrivateFiles: Boolean = false, val indices: List? = null) - data class Write(val files: List) + data class Write(val files: List) data class Delete(val indices: List?) data class ChangeSettings(val changes: List) } @@ -467,15 +538,15 @@ class MoshiAdapters { class DataToWriteAdapter { @ToJson - fun toJson(src: FileData): String { + fun toJson(src: DataToWrite): String { return when (src) { - is FileData.DataProtectedBySignature -> DataProtectedBySignatureAdapter().toJson(src) - is FileData.DataProtectedByPasscode -> DataProtectedByPasscodeAdapter().toJson(src) + is DataToWrite.DataProtectedBySignature -> DataProtectedBySignatureAdapter().toJson(src) + is DataToWrite.DataProtectedByPasscode -> DataProtectedByPasscodeAdapter().toJson(src) } } @FromJson - fun fromJson(map: MutableMap): FileData { + fun fromJson(map: MutableMap): DataToWrite { return if (map.containsKey("signature")) { DataProtectedBySignatureAdapter().fromJson(map) } else { @@ -486,10 +557,10 @@ class MoshiAdapters { class DataProtectedBySignatureAdapter { @ToJson - fun toJson(src: FileData.DataProtectedBySignature): String = MoshiJsonConverter.default().toJson(src) + fun toJson(src: DataToWrite.DataProtectedBySignature): String = MoshiJsonConverter.default().toJson(src) @FromJson - fun fromJson(map: MutableMap): FileData.DataProtectedBySignature { + fun fromJson(map: MutableMap): DataToWrite.DataProtectedBySignature { val converter = MoshiJsonConverter.default() return converter.fromJson(converter.toJson(map)) !! } @@ -497,14 +568,25 @@ class MoshiAdapters { class DataProtectedByPasscodeAdapter { @ToJson - fun toJson(src: FileData.DataProtectedByPasscode): String = MoshiJsonConverter.default().toJson(src) + fun toJson(src: DataToWrite.DataProtectedByPasscode): String = MoshiJsonConverter.default().toJson(src) @FromJson - fun fromJson(map: MutableMap): FileData.DataProtectedByPasscode { + fun fromJson(map: MutableMap): DataToWrite.DataProtectedByPasscode { val converter = MoshiJsonConverter.default() return converter.fromJson(converter.toJson(map)) !! } } } -data class PluginError(val code: Int, val localizedDescription: String) \ No newline at end of file +data class PluginError( + // code = 1000 - it's the plugin or it's the tangemSdk internal exception + // any other value in code greater than 10000 - it's the tangemSdk internal error + val code: Int, + val localizedDescription: String +) + +class PluginException( + message: String, cause: Throwable? = null +): Exception("TangemSdkPlugin exception. Message: $message", cause) + +class TangemSdkException(cause: Throwable): Exception("TangemSdk internal exception", cause) diff --git a/plugin/tangem-sdk-flutter/example/lib/main.dart b/plugin/tangem-sdk-flutter/example/lib/main.dart index f35faae..3081c36 100644 --- a/plugin/tangem-sdk-flutter/example/lib/main.dart +++ b/plugin/tangem-sdk-flutter/example/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:tangem_sdk/plugin_error.dart'; import 'package:tangem_sdk/tangem_sdk.dart'; import 'app_widgets.dart'; @@ -46,7 +47,7 @@ class _CommandListWidgetState extends State { final prettyJson = _jsonEncoder.convert(success.toJson()); prettyJson.split("\n").forEach((element) => print(element)); }, (error) { - if (error is SdkPluginError) { + if (error is TangemSdkPluginError) { print(error.message); } else { print(error); @@ -121,11 +122,11 @@ class _CommandListWidgetState extends State { final listOfData = List.generate(_utils.randomInt(1, 10), (index) => _utils.randomString(20)); final hashes = listOfData.map((e) => e.toHexString()).toList(); - TangemSdk.sign(_callback, hashes, {TangemSdk.cid: _cardId}); + TangemSdk.sign(_callback, hashes, {TangemSdk.cardId: _cardId}); } handleReadIssuerData() { - TangemSdk.readIssuerData(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.readIssuerData(_callback, {TangemSdk.cardId: _cardId}); } handleWriteIssuerData() { @@ -139,13 +140,13 @@ class _CommandListWidgetState extends State { final issuerDataCounter = 1; TangemSdk.writeIssuerData(_callback, issuerData.toHexString(), issuerDataSignature.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.issuerDataCounter: issuerDataCounter, }); } handleReadIssuerExtraData() { - TangemSdk.readIssuerExtraData(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.readIssuerExtraData(_callback, {TangemSdk.cardId: _cardId}); } handleWriteIssuerExtraData() { @@ -162,13 +163,13 @@ class _CommandListWidgetState extends State { TangemSdk.writeIssuerExtraData( _callback, issuerData.toHexString(), startingSignature.toHexString(), finalizingSignature.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.issuerDataCounter: counter, }); } handleReadUserData() { - TangemSdk.readUserData(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.readUserData(_callback, {TangemSdk.cardId: _cardId}); } handleWriteUserData() { @@ -176,7 +177,7 @@ class _CommandListWidgetState extends State { final userCounter = 1; TangemSdk.writeUserData(_callback, userData.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.userCounter: userCounter, }); } @@ -186,25 +187,25 @@ class _CommandListWidgetState extends State { final protectedCounter = 1; TangemSdk.writeUserProtectedData(_callback, userProtectedData.toHexString(), { - TangemSdk.cid: _cardId, + TangemSdk.cardId: _cardId, TangemSdk.userProtectedCounter: protectedCounter, }); } handleCreateWallet() { - TangemSdk.createWallet(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.createWallet(_callback, {TangemSdk.cardId: _cardId}); } handlePurgeWallet() { - TangemSdk.purgeWallet(_callback, {TangemSdk.cid: _cardId}); + TangemSdk.purgeWallet(_callback, {TangemSdk.cardId: _cardId}); } handleSetPin1() { - TangemSdk.setPinCode(_callback, PinType.PIN1, {TangemSdk.cid: _cardId}); + TangemSdk.setPinCode(_callback, PinType.PIN1, {TangemSdk.cardId: _cardId}); } handleSetPin2() { - TangemSdk.setPinCode(_callback, PinType.PIN2, {TangemSdk.cid: _cardId}); + TangemSdk.setPinCode(_callback, PinType.PIN2, {TangemSdk.cardId: _cardId}); } _showToast(String message) { diff --git a/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift b/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift index b5868f5..2e8650d 100644 --- a/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift +++ b/plugin/tangem-sdk-flutter/ios/Classes/SwiftTangemSdkPlugin.swift @@ -12,53 +12,61 @@ public class SwiftTangemSdkPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "tangemSdk", binaryMessenger: registrar.messenger()) + let channelJS = FlutterMethodChannel(name: "tangemSdk_JSONRPC", binaryMessenger: registrar.messenger()) let instance = SwiftTangemSdkPlugin() registrar.addMethodCallDelegate(instance, channel: channel) + registrar.addMethodCallDelegate(instance, channel: channelJS) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { do { switch call.method { - case "scanCard": - try scanCard(call.arguments, result) - case "sign": - try sign(call.arguments, result) - case "personalize": - try personalize(call.arguments, result) - case "depersonalize": - try depersonalize(call.arguments, result) - case "createWallet": - try createWallet(call.arguments, result) - case "purgeWallet": - try purgeWallet(call.arguments, result) - case "readIssuerData": - try readIssuerData(call.arguments, result) - case "writeIssuerData": - try writeIssuerData(call.arguments, result) - case "readIssuerExData": - try readIssuerExData(call.arguments, result) - case "writeIssuerExData": - try writeIssuerExData(call.arguments, result) - case "readUserData": - try readUserData(call.arguments, result) - case "writeUserData": - try writeUserData(call.arguments, result) - case "writeUserProtectedData": - try writeUserProtectedData(call.arguments, result) - case "setPin1": - try setPin1(call.arguments, result) - case "setPin2": - try setPin2(call.arguments, result) - case "allowsOnlyDebugCards": - allowsOnlyDebugCards(call.arguments, result) - case "prepareHashes": - try prepareHashes(call.arguments, result) + case "runJSONRPCRequest": + try runJSONRPCRequest(call.arguments, result) + case "startSession": + result("null") + case "stopSession": + result("null") +// case "scanCard": +// try scanCard(call.arguments, result) +// case "sign": +// try sign(call.arguments, result) +// case "personalize": +// try personalize(call.arguments, result) +// case "depersonalize": +// try depersonalize(call.arguments, result) +// case "createWallet": +// try createWallet(call.arguments, result) +// case "purgeWallet": +// try purgeWallet(call.arguments, result) +// case "readIssuerData": +// try readIssuerData(call.arguments, result) +// case "writeIssuerData": +// try writeIssuerData(call.arguments, result) +// case "readIssuerExData": +// try readIssuerExData(call.arguments, result) +// case "writeIssuerExData": +// try writeIssuerExData(call.arguments, result) +// case "readUserData": +// try readUserData(call.arguments, result) +// case "writeUserData": +// try writeUserData(call.arguments, result) +// case "writeUserProtectedData": +// try writeUserProtectedData(call.arguments, result) +// case "setPin1": +// try setPin1(call.arguments, result) +// case "setPin2": +// try setPin2(call.arguments, result) +// case "allowsOnlyDebugCards": +// allowsOnlyDebugCards(call.arguments, result) +// case "prepareHashes": +// try prepareHashes(call.arguments, result) // case "writeFiles": // try writeFiles(call.arguments, result) - case "readFiles": - try readFiles(call.arguments, result) - case "deleteFiles": - try deleteFiles(call.arguments, result) +// case "readFiles": +// try readFiles(call.arguments, result) +// case "deleteFiles": +// try deleteFiles(call.arguments, result) // case "changeFilesSettings": // try changeFilesSettings(call.arguments, result) default: @@ -67,180 +75,196 @@ public class SwiftTangemSdkPlugin: NSObject, FlutterPlugin { } catch { print(error) print(error.localizedDescription) - handleError(error, result) + let flutterError = error as? FlutterError ?? .underlyingError(error) + result(flutterError) } } - private func scanCard(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.scanCard(onlineVerification: false, - walletIndex: nil, - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) + private func runJSONRPCRequest(_ args: Any?, _ completion: @escaping FlutterResult) throws { + guard let request: String = getArg(for: .request, from: args) else { + throw FlutterError.missingRequest } - } - - private func sign(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let hexHashes: [String] = try getArg(.hashes, from: args) - let hashes = hexHashes.compactMap({Data(hexString: $0)}) - sdk.sign(hashes: hashes, - cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args) - ) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func personalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.personalize(config: try getArg(.cardConfig, from: args), - issuer: try getArg(.issuer, from: args), - manufacturer: try getArg(.manufacturer, from: args), - acquirer: try getArgOptional(.acquirer, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func depersonalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.depersonalize(initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func createWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.createWallet(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func purgeWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.purgeWallet(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func readIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.readIssuerData(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeIssuerData(cardId: try getArg(.cid, from: args), - issuerData: try getArg(.issuerData, from: args), - issuerDataSignature: try getArg(.issuerDataSignature, from: args), - issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func readIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.readIssuerExtraData(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeIssuerExtraData(cardId: try getArg(.cid, from: args), - issuerData: try getArg(.issuerData, from: args), - startingSignature: try getArg(.startingSignature, from: args), - finalizingSignature: try getArg(.finalizingSignature, from: args), - issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func readUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.readUserData(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeUserData(cardId: try getArgOptional(.cid, from: args), - userData: try getArg(.userData, from: args), - userCounter: try getArgOptional(.userCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func writeUserProtectedData(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.writeUserProtectedData(cardId: try getArgOptional(.cid, from: args), - userProtectedData: try getArg(.userProtectedData, from: args), - userProtectedCounter: try getArgOptional(.userProtectedCounter, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func setPin1(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let pin: String? = try getArgOptional(.pinCode, from: args) + let cardId: String? = getArg(for: .cardId, from: args) + let initialMessage: String? = getArg(for: .initialMessage, from: args) - sdk.changePin1(cardId: try getArgOptional(.cid, from: args), - pin: pin?.sha256(), - initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) + sdk.startSession(with: request, + cardId: cardId, + initialMessage: initialMessage) { result in + completion(result) } } - private func setPin2(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let pin: String? = try getArgOptional(.pinCode, from: args) - - sdk.changePin2(cardId: try getArgOptional(.cid, from: args), - pin: pin?.sha256(), - initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } - - private func allowsOnlyDebugCards(_ args: Any?, _ completion: @escaping FlutterResult) { - if let allowedOnlyDebug = (args as? [String: Any])?["isAllowedOnlyDebugCards"] as? Bool { - sdk.config.allowedCardTypes = allowedOnlyDebug ? [.sdk] : FirmwareType.allCases - } - } - - private func prepareHashes(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let hashes = sdk.prepareHashes(cardId: try getArg(.cid, from: args), - fileData: try getArg(.fileData, from: args), - fileCounter: try getArg(.fileCounter, from: args), - privateKey: try getArgOptional(.privateKey, from: args)) - completion(hashes.description) - } - - private func readFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { - let readPrivateFiles: Bool = try getArgOptional(.readPrivateFiles, from: args) ?? false - //let indices: [Int] = try getArg(.indices, from: args) - - sdk.readFiles(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args), - readSettings: ReadFilesTaskSettings(readPrivateFiles: readPrivateFiles)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } +// private func scanCard(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.scanCard(onlineVerification: false, +// walletIndex: nil, +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func sign(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let hexHashes: [String] = try getArg(.hashes, from: args) +// let hashes = hexHashes.compactMap({Data(hexString: $0)}) +// +// sdk.sign(hashes: hashes, +// cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args) +// ) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func personalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.personalize(config: try getArg(.cardConfig, from: args), +// issuer: try getArg(.issuer, from: args), +// manufacturer: try getArg(.manufacturer, from: args), +// acquirer: try getArgOptional(.acquirer, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func depersonalize(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.depersonalize(initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func createWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.createWallet(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func purgeWallet(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.purgeWallet(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func readIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.readIssuerData(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeIssuerData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeIssuerData(cardId: try getArg(.cid, from: args), +// issuerData: try getArg(.issuerData, from: args), +// issuerDataSignature: try getArg(.issuerDataSignature, from: args), +// issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func readIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.readIssuerExtraData(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeIssuerExData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeIssuerExtraData(cardId: try getArg(.cid, from: args), +// issuerData: try getArg(.issuerData, from: args), +// startingSignature: try getArg(.startingSignature, from: args), +// finalizingSignature: try getArg(.finalizingSignature, from: args), +// issuerDataCounter: try getArgOptional(.issuerDataCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func readUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.readUserData(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeUserData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeUserData(cardId: try getArgOptional(.cid, from: args), +// userData: try getArg(.userData, from: args), +// userCounter: try getArgOptional(.userCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)){ [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func writeUserProtectedData(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.writeUserProtectedData(cardId: try getArgOptional(.cid, from: args), +// userProtectedData: try getArg(.userProtectedData, from: args), +// userProtectedCounter: try getArgOptional(.userProtectedCounter, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func setPin1(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let pin: String? = try getArgOptional(.pinCode, from: args) +// +// sdk.changePin1(cardId: try getArgOptional(.cid, from: args), +// pin: pin?.sha256(), +// initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func setPin2(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let pin: String? = try getArgOptional(.pinCode, from: args) +// +// sdk.changePin2(cardId: try getArgOptional(.cid, from: args), +// pin: pin?.sha256(), +// initialMessage: try getArgOptional(.initialMessage, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } +// +// private func allowsOnlyDebugCards(_ args: Any?, _ completion: @escaping FlutterResult) { +// if let allowedOnlyDebug = (args as? [String: Any])?["isAllowedOnlyDebugCards"] as? Bool { +// sdk.config.allowedCardTypes = allowedOnlyDebug ? [.sdk] : FirmwareType.allCases +// } +// } +//// +// private func prepareHashes(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let hashes = sdk.prepareHashes(cardId: try getArg(.cid, from: args), +// fileData: try getArg(.fileData, from: args), +// fileCounter: try getArg(.fileCounter, from: args), +// privateKey: try getArgOptional(.privateKey, from: args)) +// completion(hashes.description) +// } +// +// private func readFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// let readPrivateFiles: Bool = try getArgOptional(.readPrivateFiles, from: args) ?? false +// //let indices: [Int] = try getArg(.indices, from: args) +// +// sdk.readFiles(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args), +// readSettings: ReadFilesTaskSettings(readPrivateFiles: readPrivateFiles)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } // private func writeFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { // sdk.writeFiles(cardId: try getArgOptional(.cid, from: args), @@ -263,150 +287,149 @@ public class SwiftTangemSdkPlugin: NSObject, FlutterPlugin { // } // } - private func deleteFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { - sdk.deleteFiles(cardId: try getArgOptional(.cid, from: args), - initialMessage: try getArgOptional(.initialMessage, from: args), - pin1: try getArgOptional(.pin1, from: args), - pin2: try getArgOptional(.pin2, from: args), - indicesToDelete: try getArgOptional(.indices, from: args)) { [weak self] result in - self?.handleCompletion(result, completion) - } - } +// private func deleteFiles(_ args: Any?, _ completion: @escaping FlutterResult) throws { +// sdk.deleteFiles(cardId: try getArgOptional(.cid, from: args), +// initialMessage: try getArgOptional(.initialMessage, from: args), +// pin1: try getArgOptional(.pin1, from: args), +// pin2: try getArgOptional(.pin2, from: args), +// indicesToDelete: try getArgOptional(.indices, from: args)) { [weak self] result in +// self?.handleCompletion(result, completion) +// } +// } - private func handleCompletion(_ sdkResult: Result, _ completion: @escaping FlutterResult) { - switch sdkResult { - case .success(let response): - completion(response.description) - case .failure(let error): - handleError(error, completion) - } - } +// private func handleCompletion(_ sdkResult: Result, _ completion: @escaping FlutterResult) { +// switch sdkResult { +// case .success(let response): +// completion(response.description) +// case .failure(let error): +// handleError(error, completion) +// } +// } +// +// private func handleError(_ error: Error, _ completion: @escaping FlutterResult) { +// var pluginError: PluginError +// +// if let sdkError = error as? TangemSdkError { +// pluginError = sdkError.toPluginError() +// } else if let pluginInternalError = error as? PluginInternalError { +// pluginError = pluginInternalError.toPluginError() +// } else { +// pluginError = PluginError(code: 9998, localizedDescription: "\(error)") +// } +// +// completion(pluginError.flutterError) +// } - private func handleError(_ error: Error, _ completion: @escaping FlutterResult) { - var pluginError: PluginError - - if let sdkError = error as? TangemSdkError { - pluginError = sdkError.toPluginError() - } else if let pluginInternalError = error as? PluginInternalError { - pluginError = pluginInternalError.toPluginError() - } else { - pluginError = PluginError(code: 9998, localizedDescription: "\(error)") +// private func getArgOptional(_ key: ArgKey, from arguments: Any?) throws -> T? { +// do { +// return try getArg(key, from: arguments) +// } catch PluginInternalError.missingArgument { +// return nil +// } +// } +// + private func getArg(for key: ArgKey, from arguments: Any?) -> T? { + if let value = (arguments as? NSDictionary)?[key.rawValue] { + return value as? T } - completion(pluginError.flutterError) - } - - private func getArgOptional(_ key: ArgKey, from arguments: Any?) throws -> T? { - do { - return try getArg(key, from: arguments) - } catch PluginInternalError.missingArgument { - return nil - } - } - - private func getArg(_ key: ArgKey, from arguments: Any?) throws -> T { - if let value = (arguments as? [String: Any])?[key.rawValue] { - if String(describing: value) == "" { - throw PluginInternalError.missingArgument(key) - } - - if T.self == Data.self || T.self == Data?.self { - if let hex = value as? String { - return (Data(hexString: hex) as! T) - } else { - throw PluginInternalError.failedToParseDataFromHex(key) - } - } else { - do { - return try decodeObject(value) - } catch { - if let converted = value as? T { - return converted - } else { - throw error - } - } - } - } else { - throw PluginInternalError.missingArgument(key) - } - } - - private func decodeObject(_ value: Any) throws -> T { - if let json = value as? String, let jsonData = json.data(using: .utf8) { - return try JSONDecoder.tangemSdkDecoder.decode(T.self, from: jsonData) - } else { - throw PluginInternalError.failedToParseJson - } + return nil } +// +// private func decodeObject(_ value: Any) throws -> T { +// if let json = value as? String, let jsonData = json.data(using: .utf8) { +// return try JSONDecoder.tangemSdkDecoder.decode(T.self, from: jsonData) +// } else { +// throw PluginInternalError.failedToParseJson +// } +// } } -fileprivate struct PluginError: Encodable { - let code: Int - let localizedDescription: String - - var jsonDescription: String { - let encoder = JSONEncoder() - encoder.outputFormatting = [.sortedKeys, .prettyPrinted] - let data = (try? encoder.encode(self)) ?? Data() - return String(data: data, encoding: .utf8)! - } - - var flutterError: FlutterError { - FlutterError(code: "\(code)", message: localizedDescription, details: jsonDescription) - } -} +//fileprivate struct PluginError: Encodable { +// let code: Int +// let localizedDescription: String +// +// var jsonDescription: String { +// let encoder = JSONEncoder() +// encoder.outputFormatting = [.sortedKeys, .prettyPrinted] +// let data = (try? encoder.encode(self)) ?? Data() +// return String(data: data, encoding: .utf8)! +// } +// +// var flutterError: FlutterError { +// FlutterError(code: "\(code)", message: localizedDescription, details: jsonDescription) +// } +//} +// +//fileprivate extension TangemSdkError { +// func toPluginError() -> PluginError { +// return PluginError(code: code, localizedDescription: localizedDescription) +// } +//} -fileprivate extension TangemSdkError { - func toPluginError() -> PluginError { - return PluginError(code: code, localizedDescription: localizedDescription) - } +//fileprivate enum PluginInternalError: Error, LocalizedError { +// case failedToParseJson +// case missingArgument(_ value: ArgKey) +// case failedToParseDataFromHex(_ value: ArgKey) +// +// var errorDescription: String? { +// switch self { +// case .failedToParseJson: +// return "Failed to parse JSON object" +// case .missingArgument(let value): +// return "Missing required argument: \(value.rawValue)" +// case .failedToParseDataFromHex(let value): +// return "Failed to parse Data from HEX for: \(value.rawValue)" +// } +// } +// +// func toPluginError() -> PluginError { +// return PluginError(code: 9999 , localizedDescription: localizedDescription) +// } +//} + +fileprivate enum ArgKey: String { +// case pin1 +// case pin2 + case cardId +// case hashes +// case userCounter +// case userProtectedCounter +// case userData +// case issuerDataCounter +// case userProtectedData +// case issuerData +// case issuerDataSignature +// case startingSignature +// case finalizingSignature +// case issuer +// case manufacturer +// case acquirer +// case cardConfig +// case pinCode + case initialMessage +// case fileData +// case fileCounter +// case privateKey +// case readPrivateFiles +// case indices + case request = "JSONRPCRequest" } -fileprivate enum PluginInternalError: Error, LocalizedError { - case failedToParseJson - case missingArgument(_ value: ArgKey) - case failedToParseDataFromHex(_ value: ArgKey) +extension FlutterError: Error {} + +fileprivate extension FlutterError { + static let genericCode = "9999" - var errorDescription: String? { - switch self { - case .failedToParseJson: - return "Failed to parse JSON object" - case .missingArgument(let value): - return "Missing required argument: \(value.rawValue)" - case .failedToParseDataFromHex(let value): - return "Failed to parse Data from HEX for: \(value.rawValue)" - } + static var parseArgumentsError: FlutterError { + FlutterError(code: genericCode, message: "Faield to parse arguments", details: nil) } - func toPluginError() -> PluginError { - return PluginError(code: 9999 , localizedDescription: localizedDescription) + static var missingRequest: FlutterError { + FlutterError(code: genericCode, message: "Missing JSON RPC request", details: nil) + } + + static func underlyingError(_ error: Error) -> FlutterError { + FlutterError(code: genericCode, message: "Some error occured", details: error) } -} - -fileprivate enum ArgKey: String { - case pin1 - case pin2 - case cid - case hashes - case userCounter - case userProtectedCounter - case userData - case issuerDataCounter - case userProtectedData - case issuerData - case issuerDataSignature - case startingSignature - case finalizingSignature - case issuer - case manufacturer - case acquirer - case cardConfig - case pinCode - case initialMessage - case fileData - case fileCounter - case privateKey - case readPrivateFiles - case indices } diff --git a/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec b/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec index d55c88f..1350091 100644 --- a/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec +++ b/plugin/tangem-sdk-flutter/ios/tangem_sdk.podspec @@ -18,7 +18,7 @@ TangemSdk plugin for integration into flutter projects s.ios.deployment_target = '13.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' - s.dependency 'TangemSdk', "~> 2.4.2" + s.dependency 'TangemSdk', "~> 3.0.2" end diff --git a/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart b/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart index ea23ef4..e09401d 100644 --- a/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart +++ b/plugin/tangem-sdk-flutter/lib/card_responses/card_response.g.dart @@ -16,7 +16,9 @@ CardResponse _$CardResponseFromJson(Map json) { json['defaultCurve'] as String?, (json['settingsMask'] as List?)?.map((e) => e as String).toList(), json['issuerPublicKey'] as String?, - (json['signingMethods'] as List?)?.map((e) => e as String).toList(), + (json['signingMethods'] as List?) + ?.map((e) => e as String) + .toList(), json['pauseBeforePin2'] as int?, json['walletsCount'] as int?, json['walletIndex'] as int?, @@ -27,14 +29,19 @@ CardResponse _$CardResponseFromJson(Map json) { json['userCounter'] as int?, json['userProtectedCounter'] as int?, json['terminalIsLinked'] as bool, - json['cardData'] == null ? null : CardData.fromJson(json['cardData'] as Map), + json['cardData'] == null + ? null + : CardData.fromJson(json['cardData'] as Map), json['isPin1Default'] as bool?, json['isPin2Default'] as bool?, - (json['wallets'] as List?)?.map((e) => CardWallet.fromJson(e as Map)).toList(), + (json['wallets'] as List?) + ?.map((e) => CardWallet.fromJson(e as Map)) + .toList(), ); } -Map _$CardResponseToJson(CardResponse instance) => { +Map _$CardResponseToJson(CardResponse instance) => + { 'cardId': instance.cardId, 'manufacturerName': instance.manufacturerName, 'status': instance.status, @@ -66,17 +73,21 @@ SignResponse _$SignResponseFromJson(Map json) { ); } -Map _$SignResponseToJson(SignResponse instance) => { +Map _$SignResponseToJson(SignResponse instance) => + { 'signedHashes': instance.signedHashes, }; -DepersonalizeResponse _$DepersonalizeResponseFromJson(Map json) { +DepersonalizeResponse _$DepersonalizeResponseFromJson( + Map json) { return DepersonalizeResponse( json['success'] as bool, ); } -Map _$DepersonalizeResponseToJson(DepersonalizeResponse instance) => { +Map _$DepersonalizeResponseToJson( + DepersonalizeResponse instance) => + { 'success': instance.success, }; @@ -88,7 +99,9 @@ CreateWalletResponse _$CreateWalletResponseFromJson(Map json) { ); } -Map _$CreateWalletResponseToJson(CreateWalletResponse instance) => { +Map _$CreateWalletResponseToJson( + CreateWalletResponse instance) => + { 'cardId': instance.cardId, 'status': instance.status, 'walletPublicKey': instance.walletPublicKey, @@ -101,12 +114,15 @@ PurgeWalletResponse _$PurgeWalletResponseFromJson(Map json) { ); } -Map _$PurgeWalletResponseToJson(PurgeWalletResponse instance) => { +Map _$PurgeWalletResponseToJson( + PurgeWalletResponse instance) => + { 'cardId': instance.cardId, 'status': instance.status, }; -ReadIssuerDataResponse _$ReadIssuerDataResponseFromJson(Map json) { +ReadIssuerDataResponse _$ReadIssuerDataResponseFromJson( + Map json) { return ReadIssuerDataResponse( json['cardId'] as String, json['issuerData'] as String, @@ -115,24 +131,30 @@ ReadIssuerDataResponse _$ReadIssuerDataResponseFromJson(Map jso ); } -Map _$ReadIssuerDataResponseToJson(ReadIssuerDataResponse instance) => { +Map _$ReadIssuerDataResponseToJson( + ReadIssuerDataResponse instance) => + { 'cardId': instance.cardId, 'issuerData': instance.issuerData, 'issuerDataSignature': instance.issuerDataSignature, 'issuerDataCounter': instance.issuerDataCounter, }; -WriteIssuerDataResponse _$WriteIssuerDataResponseFromJson(Map json) { +WriteIssuerDataResponse _$WriteIssuerDataResponseFromJson( + Map json) { return WriteIssuerDataResponse( json['cardId'] as String, ); } -Map _$WriteIssuerDataResponseToJson(WriteIssuerDataResponse instance) => { +Map _$WriteIssuerDataResponseToJson( + WriteIssuerDataResponse instance) => + { 'cardId': instance.cardId, }; -ReadIssuerExDataResponse _$ReadIssuerExDataResponseFromJson(Map json) { +ReadIssuerExDataResponse _$ReadIssuerExDataResponseFromJson( + Map json) { return ReadIssuerExDataResponse( json['cardId'] as String, json['size'] as int, @@ -142,7 +164,9 @@ ReadIssuerExDataResponse _$ReadIssuerExDataResponseFromJson(Map ); } -Map _$ReadIssuerExDataResponseToJson(ReadIssuerExDataResponse instance) => { +Map _$ReadIssuerExDataResponseToJson( + ReadIssuerExDataResponse instance) => + { 'cardId': instance.cardId, 'size': instance.size, 'issuerData': instance.issuerData, @@ -150,13 +174,16 @@ Map _$ReadIssuerExDataResponseToJson(ReadIssuerExDataResponse i 'issuerDataCounter': instance.issuerDataCounter, }; -WriteIssuerExDataResponse _$WriteIssuerExDataResponseFromJson(Map json) { +WriteIssuerExDataResponse _$WriteIssuerExDataResponseFromJson( + Map json) { return WriteIssuerExDataResponse( json['cardId'] as String, ); } -Map _$WriteIssuerExDataResponseToJson(WriteIssuerExDataResponse instance) => { +Map _$WriteIssuerExDataResponseToJson( + WriteIssuerExDataResponse instance) => + { 'cardId': instance.cardId, }; @@ -170,7 +197,9 @@ ReadUserDataResponse _$ReadUserDataResponseFromJson(Map json) { ); } -Map _$ReadUserDataResponseToJson(ReadUserDataResponse instance) => { +Map _$ReadUserDataResponseToJson( + ReadUserDataResponse instance) => + { 'cardId': instance.cardId, 'userData': instance.userData, 'userCounter': instance.userCounter, @@ -178,13 +207,16 @@ Map _$ReadUserDataResponseToJson(ReadUserDataResponse instance) 'userProtectedCounter': instance.userProtectedCounter, }; -WriteUserDataResponse _$WriteUserDataResponseFromJson(Map json) { +WriteUserDataResponse _$WriteUserDataResponseFromJson( + Map json) { return WriteUserDataResponse( json['cardId'] as String, ); } -Map _$WriteUserDataResponseToJson(WriteUserDataResponse instance) => { +Map _$WriteUserDataResponseToJson( + WriteUserDataResponse instance) => + { 'cardId': instance.cardId, }; @@ -195,7 +227,8 @@ SetPinResponse _$SetPinResponseFromJson(Map json) { ); } -Map _$SetPinResponseToJson(SetPinResponse instance) => { +Map _$SetPinResponseToJson(SetPinResponse instance) => + { 'cardId': instance.cardId, 'status': instance.status, }; @@ -207,18 +240,22 @@ WriteFilesResponse _$WriteFilesResponseFromJson(Map json) { ); } -Map _$WriteFilesResponseToJson(WriteFilesResponse instance) => { +Map _$WriteFilesResponseToJson(WriteFilesResponse instance) => + { 'cardId': instance.cardId, 'fileIndices': instance.fileIndices, }; ReadFilesResponse _$ReadFilesResponseFromJson(Map json) { return ReadFilesResponse( - (json['files'] as List).map((e) => FileHex.fromJson(e as Map)).toList(), + (json['files'] as List) + .map((e) => FileHex.fromJson(e as Map)) + .toList(), ); } -Map _$ReadFilesResponseToJson(ReadFilesResponse instance) => { +Map _$ReadFilesResponseToJson(ReadFilesResponse instance) => + { 'files': instance.files, }; @@ -228,16 +265,21 @@ DeleteFilesResponse _$DeleteFilesResponseFromJson(Map json) { ); } -Map _$DeleteFilesResponseToJson(DeleteFilesResponse instance) => { +Map _$DeleteFilesResponseToJson( + DeleteFilesResponse instance) => + { 'cardId': instance.cardId, }; -ChangeFilesSettingsResponse _$ChangeFilesSettingsResponseFromJson(Map json) { +ChangeFilesSettingsResponse _$ChangeFilesSettingsResponseFromJson( + Map json) { return ChangeFilesSettingsResponse( json['cardId'] as String, ); } -Map _$ChangeFilesSettingsResponseToJson(ChangeFilesSettingsResponse instance) => { +Map _$ChangeFilesSettingsResponseToJson( + ChangeFilesSettingsResponse instance) => + { 'cardId': instance.cardId, }; diff --git a/plugin/tangem-sdk-flutter/lib/extensions/any.dart b/plugin/tangem-sdk-flutter/lib/extensions/any.dart index 70f820b..15c4862 100644 --- a/plugin/tangem-sdk-flutter/lib/extensions/any.dart +++ b/plugin/tangem-sdk-flutter/lib/extensions/any.dart @@ -15,4 +15,6 @@ extension OnObject on T { final split = this.toString().split('.'); return split.length > 1 && split[0] == this.runtimeType.toString(); } -} \ No newline at end of file + + bool isNumber() => int.tryParse(this.toString()) != null; +} diff --git a/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart b/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart index 4a60e7f..546e889 100644 --- a/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart +++ b/plugin/tangem-sdk-flutter/lib/extensions/exp_extensions.dart @@ -1,4 +1,5 @@ export 'package:tangem_sdk/extensions/any.dart'; +export 'package:tangem_sdk/extensions/int.dart'; export 'package:tangem_sdk/extensions/iterable.dart'; export 'package:tangem_sdk/extensions/list.dart'; export 'package:tangem_sdk/extensions/map.dart'; diff --git a/plugin/tangem-sdk-flutter/lib/extensions/int.dart b/plugin/tangem-sdk-flutter/lib/extensions/int.dart new file mode 100644 index 0000000..5a31eca --- /dev/null +++ b/plugin/tangem-sdk-flutter/lib/extensions/int.dart @@ -0,0 +1,6 @@ +extension OnInt on int { + void foreach(void Function(int e) block) { + if (this <= 0) return; + for (var i = 0; i < this; i++) block(i); + } +} diff --git a/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart b/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart index f295a74..cf6481f 100644 --- a/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart +++ b/plugin/tangem-sdk-flutter/lib/extensions/iterable.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + extension OnIterableNullSafe on Iterable { E? firstWhereOrNull(bool test(E e)) { for (E element in this) { @@ -19,3 +21,9 @@ extension OnIterable on Iterable? { return this!.isEmpty; } } + +extension OnQueue on Queue { + E? poll() { + return isEmpty ? null : this.removeFirst(); + } +} diff --git a/plugin/tangem-sdk-flutter/lib/model/command_data.dart b/plugin/tangem-sdk-flutter/lib/model/command_data.dart index 1841f93..b30846c 100644 --- a/plugin/tangem-sdk-flutter/lib/model/command_data.dart +++ b/plugin/tangem-sdk-flutter/lib/model/command_data.dart @@ -25,13 +25,13 @@ abstract class CommandDataModel { Map getBaseData() { final map = {TangemSdk.commandType: type}; - if (cardId != null) map[TangemSdk.cid] = cardId; + if (cardId != null) map[TangemSdk.cardId] = cardId; if (initialMessage != null) map[TangemSdk.initialMessage] = initialMessage!.toJson(); return map; } static T attachBaseData(T taskData, Map json) { - taskData.cardId = json[TangemSdk.cid]; + taskData.cardId = json[TangemSdk.cardId]; if (json[TangemSdk.initialMessage] != null) taskData.initialMessage = Message.fromJson(json[TangemSdk.initialMessage]); return taskData; diff --git a/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart b/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart index bb99d0c..7e89ad7 100644 --- a/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart +++ b/plugin/tangem-sdk-flutter/lib/model/json_rpc.dart @@ -10,115 +10,42 @@ abstract class JSONRPC { final String jsonrpc; JSONRPC(this.id, this.jsonrpc); - - static String? getJsonRpcMethod(String commandType) { - switch (commandType) { - case TangemSdk.cScanCard: - { - return TangemSdk.cScanCard; - } - case TangemSdk.cSign: - { - return TangemSdk.cSign; - } - case TangemSdk.cPersonalize: - { - return TangemSdk.cPersonalize; - } - case TangemSdk.cDepersonalize: - { - return TangemSdk.cDepersonalize; - } - case TangemSdk.cCreateWallet: - { - return TangemSdk.cCreateWallet; - } - case TangemSdk.cPurgeWallet: - { - return TangemSdk.cPurgeWallet; - } - case TangemSdk.cReadIssuerData: - { - return TangemSdk.cReadIssuerData; - } - case TangemSdk.cWriteIssuerData: - { - return TangemSdk.cWriteIssuerData; - } - case TangemSdk.cReadIssuerExData: - { - return TangemSdk.cReadIssuerExData; - } - case TangemSdk.cWriteIssuerExData: - { - return TangemSdk.cWriteIssuerExData; - } - case TangemSdk.cReadUserData: - { - return TangemSdk.cReadUserData; - } - case TangemSdk.cWriteUserData: - { - return TangemSdk.cWriteUserData; - } - case TangemSdk.cWriteUserProtectedData: - { - return TangemSdk.cWriteUserProtectedData; - } - case TangemSdk.cSetPin1: - { - return TangemSdk.cSetPin1; - } - case TangemSdk.cSetPin2: - { - return TangemSdk.cSetPin2; - } - case TangemSdk.cWriteFiles: - { - return TangemSdk.cWriteFiles; - } - case TangemSdk.cReadFiles: - { - return TangemSdk.cReadFiles; - } - case TangemSdk.cDeleteFiles: - { - return TangemSdk.cDeleteFiles; - } - case TangemSdk.cChangeFilesSettings: - { - return TangemSdk.cChangeFilesSettings; - } - case TangemSdk.cPrepareHashes: - { - return TangemSdk.cPrepareHashes; - } - } - return null; - } } @JsonSerializable() class JSONRPCRequest extends JSONRPC { final String method; - final Map parameters; + final Map params; - JSONRPCRequest(this.method, this.parameters, [dynamic id, String jsonrpc = "2.0"]) : super(id, jsonrpc); + JSONRPCRequest(this.method, this.params, [dynamic id, String jsonrpc = "2.0"]) : super(id, jsonrpc); Map toJson() => _$JSONRPCRequestToJson(this); factory JSONRPCRequest.fromJson(Map json) => _$JSONRPCRequestFromJson(json); factory JSONRPCRequest.fromCommandDataJson(Map commandDataJson) { - final method = JSONRPC.getJsonRpcMethod(commandDataJson.remove(TangemSdk.commandType)) ?? ""; + final method = TangemSdk.getJsonRpcMethod(commandDataJson.remove(TangemSdk.commandType)) ?? ""; return JSONRPCRequest(method, commandDataJson); } } +@JsonSerializable() +class JSONRPCError { + final int code; + final String message; + final String? data; + + JSONRPCError(this.code, this.message, this.data); + + Map toJson() => _$JSONRPCErrorToJson(this); + + factory JSONRPCError.fromJson(Map json) => _$JSONRPCErrorFromJson(json); +} + @JsonSerializable() class JSONRPCResponse extends JSONRPC { final dynamic result; - final dynamic error; + final JSONRPCError? error; JSONRPCResponse(this.result, this.error, [dynamic id, String jsonrpc = "2.0"]) : super(id, jsonrpc); diff --git a/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart b/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart index 72bb2d0..a5a5fe1 100644 --- a/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart +++ b/plugin/tangem-sdk-flutter/lib/model/json_rpc.g.dart @@ -9,7 +9,7 @@ part of 'json_rpc.dart'; JSONRPCRequest _$JSONRPCRequestFromJson(Map json) { return JSONRPCRequest( json['method'] as String, - json['parameters'] as Map, + json['params'] as Map, json['id'], json['jsonrpc'] as String, ); @@ -19,13 +19,13 @@ Map _$JSONRPCRequestToJson(JSONRPCRequest instance) => json) { return JSONRPCResponse( json['result'], - json['error'], + json['error'] == null ? null : JSONRPCError.fromJson(json['error']), json['id'], json['jsonrpc'] as String, ); @@ -35,5 +35,19 @@ Map _$JSONRPCResponseToJson(JSONRPCResponse instance) => json) { + return JSONRPCError( + json['code'] as int, + json['message'] as String, + json['data'] as String?, + ); +} + +Map _$JSONRPCErrorToJson(JSONRPCError instance) => { + 'code': instance.code, + 'message': instance.message, + 'data': instance.data, }; diff --git a/plugin/tangem-sdk-flutter/lib/plugin_error.dart b/plugin/tangem-sdk-flutter/lib/plugin_error.dart new file mode 100644 index 0000000..f2f1e5f --- /dev/null +++ b/plugin/tangem-sdk-flutter/lib/plugin_error.dart @@ -0,0 +1,65 @@ +import 'package:flutter/services.dart'; + +abstract class TangemSdkPluginError implements Exception { + final int code; + final String message; + + TangemSdkPluginError(this.code, this.message); + + String toString() => "${this.runtimeType}. Code: $code, message: $message"; + + String toJson() => toString(); + + static const int unknownCode = -1; + static const int pluginFlutter = 100; + static const int pluginKotlin = 1000; + + static TangemSdkPluginError createError(dynamic error) { + if (error is TangemSdkPluginError) return error; + + if (error is PlatformException) { + final code = int.tryParse(error.code) ?? unknownCode; + final message = error.message ?? error.details ?? ""; + + switch (code) { + case unknownCode: + return PluginUnknownError(message, error.details?.toString() ?? ""); + case pluginKotlin: + return PluginKotlinError(message); + default: + return PluginTangemSdkError(code, "$message. Detail: ${error.details}"); + } + } + return PluginUnknownError(error.toString(), error.toString()); + } +} + +class PluginFlutterError extends TangemSdkPluginError { + PluginFlutterError(String message) : super(TangemSdkPluginError.pluginFlutter, message); +} + +class PluginKotlinError extends TangemSdkPluginError { + PluginKotlinError(String message) : super(TangemSdkPluginError.pluginKotlin, message); +} + +class PluginTangemSdkError extends TangemSdkPluginError { + PluginTangemSdkError(int code, String message) : super(code, message); +} + +class PluginUnknownError extends TangemSdkPluginError { + final String details; + + PluginUnknownError(String message, this.details) : super(TangemSdkPluginError.unknownCode, message); +} + +extension OnTangemSdkPluginError on TangemSdkPluginError { + bool isUnknownError() => code == TangemSdkPluginError.unknownCode; + + bool isPluginFlutterError() => code == TangemSdkPluginError.pluginFlutter; + + bool isPluginKotlinError() => code == TangemSdkPluginError.pluginKotlin; + + bool isTangemSdkError() => code >= 10000 && code <= 100000; + + bool isUserCancelledError() => code == 50002; +} diff --git a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart index 2a81d8e..3fa76af 100644 --- a/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart +++ b/plugin/tangem-sdk-flutter/lib/sdk_plugin.dart @@ -5,7 +5,9 @@ import 'package:flutter/services.dart'; import 'package:tangem_sdk/card_responses/card_response.dart'; import 'package:tangem_sdk/model/command_data.dart'; import 'package:tangem_sdk/model/json_rpc.dart'; +import 'package:tangem_sdk/plugin_error.dart'; +import 'extensions/exp_extensions.dart'; import 'model/sdk.dart'; /// Flutter TangemSdk is an interface which provides access to platform specific TangemSdk library. @@ -14,7 +16,8 @@ import 'model/sdk.dart'; class TangemSdk { static const commandType = "commandType"; - static const cStartSessionWithJsonRunnable = 'startSessionWithJsonRunnable'; + static const cStartSession = 'startSession'; + static const cStopSession = 'stopSession'; static const cScanCard = 'scanCard'; static const cSign = 'sign'; static const cPersonalize = 'personalize'; @@ -35,19 +38,21 @@ class TangemSdk { static const cDeleteFiles = 'deleteFiles'; static const cChangeFilesSettings = 'changeFilesSettings'; static const cPrepareHashes = "prepareHashes"; + static const cJsonRpcRequest = 'runJSONRPCRequest'; static const isAllowedOnlyDebugCards = "isAllowedOnlyDebugCards"; - static const cid = "cardId"; + static const cardId = "cardId"; static const initialMessage = "initialMessage"; static const initialMessageHeader = "header"; static const initialMessageBody = "body"; static const hashes = "hashes"; static const walletPublicKey = "walletPublicKey"; + //TODO: replace by walletPublicKey @Deprecated("replace by walletPublicKey") static const walletIndex = "walletIndex"; static const walletConfig = "config"; - static const cardConfig = "cardConfig"; + static const cardConfig = "config"; static const issuer = "issuer"; static const manufacturer = "manufacturer"; static const acquirer = "acquirer"; @@ -69,12 +74,50 @@ class TangemSdk { static const counter = "counter"; static const changes = "changes"; static const privateKey = "privateKey"; + static const jsonRpcRequest = 'JSONRPCRequest'; + static const jsonRpcResponse = 'JSONRPCResponse'; + + static const _jsonRpcCommands = { + cScanCard: 'SCAN_TASK', + cSign: 'SIGN_COMMAND', + cPersonalize: 'PERSONALIZE_COMMAND', + cDepersonalize: 'DEPERSONALIZE_COMMAND', + cCreateWallet: 'createWallet', + cPurgeWallet: 'purgeWallet', + cReadIssuerData: 'readIssuerData', + cWriteIssuerData: 'writeIssuerData', + cReadIssuerExData: 'readIssuerExData', + cWriteIssuerExData: 'writeIssuerExData', + cReadUserData: 'readUserData', + cWriteUserData: 'writeUserData', + cWriteUserProtectedData: 'writeUserProtectedData', + cSetPin1: 'setPin1', + cSetPin2: 'setPin2', + cWriteFiles: 'writeFiles', + cReadFiles: 'readFiles', + cDeleteFiles: 'deleteFiles', + cChangeFilesSettings: 'changeFilesSettings', + }; + + static String? getJsonRpcMethod(String commandType) { + return _jsonRpcCommands[commandType]; + } static const MethodChannel _channel = const MethodChannel('tangemSdk'); + static const MethodChannel _channelJSONRPC = const MethodChannel('tangemSdk_JSONRPC'); - static Future get platformVersion async { - final String version = await _channel.invokeMethod('getPlatformVersion'); - return version; + static Future startSession(Callback callback, [Map valuesToExport = const {}]) async { + _channel + .invokeMethod(cStartSession, valuesToExport) + .then((result) => callback.onSuccess(_createResponse(cStartSession, result))) + .catchError((error) => _sendBackError(callback, error)); + } + + static Future stopSession(Callback callback, [Map valuesToExport = const {}]) async { + _channel + .invokeMethod(cStopSession, valuesToExport) + .then((result) => callback.onSuccess(_createResponse(cStopSession, result))) + .catchError((error) => _sendBackError(callback, error)); } static Future allowsOnlyDebugCards(bool isAllowed) { @@ -96,7 +139,7 @@ class TangemSdk { final type = commandJsonMap[commandType]; final jsonRpc = JSONRPCRequest.fromCommandDataJson(commandJsonMap); _channel - .invokeMethod(cStartSessionWithJsonRunnable, jsonRpc.toJson()) + .invokeMethod(cJsonRpcRequest, jsonRpc.toJson()) .then((result) => callback.onSuccess(_createResponse(type, result))) .catchError((error) => _sendBackError(callback, error)); }); @@ -116,13 +159,13 @@ class TangemSdk { jsonMap = map; } } catch (exception) { - _sendBackError(callback, TangemSdkError("Can't get command json data. Error: ${exception.toString()}")); + _sendBackError(callback, PluginFlutterError("Can't get command json data. Error: ${exception.toString()}")); return; } final type = jsonMap[commandType]; if (type == null) { - _sendBackError(callback, TangemSdkError("Can't execute the task. Missing the '$commandType' field")); + _sendBackError(callback, PluginFlutterError("Can't execute the task. Missing the '$commandType' field")); return; } onPrepareComplete(jsonMap); @@ -273,10 +316,10 @@ class TangemSdk { .catchError((error) => _sendBackError(callback, error)); } - static Future prepareHashes(Callback callback, String cardId, String fileDataHex, int counter, + static Future prepareHashes(Callback callback, String cid, String fileDataHex, int counter, [String? privateKeyHex]) async { final valuesToExport = { - cid: cardId, + cid: cid, fileData: fileDataHex, fileCounter: counter, privateKey: privateKeyHex, @@ -336,42 +379,30 @@ class TangemSdk { } static _sendBackError(Callback callback, dynamic error) { - if (error is TangemSdkBaseError) { - callback.onError(error); - } else if (error is PlatformException) { - final jsonString = error.details; - final map = json.decode(jsonString); - if (map["code"] == 50002) { - callback.onError(UserCancelledError(map['localizedDescription'])); - } else { - callback.onError(SdkPluginError(map['localizedDescription'])); - } - } else if (error is Exception) { - callback.onError(SdkPluginError(error.toString())); - } else { - callback.onError(SdkPluginError("Unknown plugin error: ${error.toString()}")); - } + callback.onError(TangemSdkPluginError.createError(error)); } -} - -abstract class TangemSdkBaseError implements Exception { - final String message; - - TangemSdkBaseError(this.message); - String toString() => "${this.runtimeType}: $message"; -} - -class SdkPluginError extends TangemSdkBaseError { - SdkPluginError(String message) : super(message); -} - -class TangemSdkError extends TangemSdkBaseError { - TangemSdkError(String message) : super(message); -} + static Future runJSONRPCRequest( + Callback callback, + JSONRPCRequest request, [ + String? cardId, + Message? initialMessage, + ]) async { + final valuesToExport = {TangemSdk.jsonRpcRequest: jsonEncode(request.toJson())}; + cardId?.let((it) => valuesToExport[TangemSdk.cardId] = it); + initialMessage?.let((it) => valuesToExport[TangemSdk.initialMessage] = it.toJson()); + + _channelJSONRPC + .invokeMethod(cJsonRpcRequest, valuesToExport) + .then((result) => callback.onSuccess(_createJSONRPCResponse(result))) + .catchError((error) => _sendBackError(callback, error)); + } + // все ошибки перехваченные этим методом должны прерывать выполнение теста -class UserCancelledError extends SdkPluginError { - UserCancelledError(String message) : super(message); + static dynamic _createJSONRPCResponse(dynamic response) { + final jsonResponse = jsonDecode(response); + return JSONRPCResponse.fromJson(jsonResponse); + } } class Callback { @@ -380,10 +411,3 @@ class Callback { Callback(this.onSuccess, this.onError); } - -class TangemSdkJson { - static const keyMethod = "method"; - static const keyParams = "parameters"; - - static const methodScan = "SCAN_TASK"; -} diff --git a/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart b/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart index f091826..39d9bed 100644 --- a/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart +++ b/plugin/tangem-sdk-flutter/lib/tangem_sdk.dart @@ -7,4 +7,5 @@ export 'model/masks/settings_mask.dart'; export 'model/masks/signing_method_mask.dart'; export 'model/masks/wallet_settings_mask.dart'; export 'model/sdk.dart'; +export 'plugin_error.dart'; export 'sdk_plugin.dart'; diff --git a/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart b/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart index 6759b91..0d86cc2 100644 --- a/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart +++ b/plugin/tangem-sdk-flutter/test/tangem_sdk_test.dart @@ -1,6 +1,5 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tangem_sdk/tangem_sdk.dart'; void main() { const MethodChannel channel = MethodChannel('tangem_sdk'); @@ -16,8 +15,4 @@ void main() { tearDown(() { channel.setMockMethodCallHandler(null); }); - - test('getPlatformVersion', () async { - expect(await TangemSdk.platformVersion, '42'); - }); } diff --git a/pubspec.yaml b/pubspec.yaml index 01faf1e..8b984ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,7 @@ flutter: assets: - assets/ - assets/icons/ + - assets/json/ # To add custom fonts to your application, add a fonts section here, diff --git a/test/assets.dart b/test/assets.dart new file mode 100644 index 0000000..d037b95 --- /dev/null +++ b/test/assets.dart @@ -0,0 +1,272 @@ +final json = ''' +{ + "setup": { + "name": "Twins", + "description": "Two test iterations with two steps iterations", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": true, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "AAR", + "value": "com.tangem.wallet" + }, + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "Tangem", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1624407062610 + }, + "steps": [ + { + "name": "Scan card 1", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + }, + { + "name": "Scan card 2", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000005", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} +'''; \ No newline at end of file diff --git a/test/json_value_finder_test.dart b/test/json_value_finder_test.dart new file mode 100644 index 0000000..6c60240 --- /dev/null +++ b/test/json_value_finder_test.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +import 'package:devkit/app_test_launcher/domain/json_value_finder.dart'; +import 'package:test/test.dart'; + +import 'assets.dart'; + +void main() { + group("JsonValueFinder", () { + test("Find a variable by a simple variable path", findBySimplePath); + test("Find a variable by by an array element and into the element", findByArrayElement); + }); +} + +void findBySimplePath() { + final map = jsonDecode(json); + final finder = JsonValueFinder(); + finder.setValue("test", map); + dynamic resultValue = finder.getValue("{test.setup.name}"); + expect(resultValue, "Twins"); + resultValue = finder.getValue("{test.setup.iterations}"); + expect(resultValue, 2); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.pin}"); + expect(resultValue, "000000"); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.p}"); + expect(resultValue, null); +} + +void findByArrayElement() { + final map = jsonDecode(json); + final finder = JsonValueFinder(); + finder.setValue("test", map); + dynamic resultValue = finder.getValue("{test.setup.personalizationConfig.config.signingMethods.0}"); + expect(resultValue, "SignHash"); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.ndefRecords.0.type}"); + expect(resultValue, "AAR"); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.signingMethods.1}"); + expect(resultValue, null); + resultValue = finder.getValue("{test.setup.personalizationConfig.config.signingMethods.qwe}"); + expect(resultValue, null); +} diff --git a/test/variable_service_test.dart b/test/variable_service_test.dart new file mode 100644 index 0000000..d3941c5 --- /dev/null +++ b/test/variable_service_test.dart @@ -0,0 +1,321 @@ +import 'dart:convert'; + +import 'package:devkit/app_test_launcher/domain/variable_service.dart'; +import 'package:test/test.dart'; + +final _stepName1 = "Scan card 1"; +final _stepName2 = "Scan card 2"; + +void main() { + group("VariableService", () { + prepareTestForVariableService(); + test("Find the variable into the step", findInStep); + test("Find the variable from another step", findFromAnotherStep); + test("Find the variable by parent", findByParent); + }); +} + +void prepareTestForVariableService() { + final map = jsonDecode(json); + VariableService.registerStep(_stepName1, (map["steps"] as List)[0]); + VariableService.registerStep(_stepName2, (map["steps"] as List)[1]); +} + +void findInStep() { + dynamic resultValue = VariableService.getValue(_stepName1, "{expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getValue(_stepName1, "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getValue(_stepName2, "{#$_stepName2.expectedResult.cardId}"); + expect(resultValue, "BB03000000000005"); +} + +void findFromAnotherStep() { + dynamic resultValue = VariableService.getValue("", "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getValue("any", "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getValue(_stepName2, "{#$_stepName1.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); +} + +void findByParent() { + // no access to the #parent key from the empty step + dynamic resultValue = VariableService.getValue(_stepName1, "{#parent.expectedResult.cardId}"); + expect(resultValue, "BB03000000000004"); + resultValue = VariableService.getValue("", "{#parent.expectedResult.cardId}"); + expect(resultValue, null); +} + +final json = ''' +{ + "setup": { + "name": "Twins", + "description": "Two test iterations with two steps iterations", + "personalizationConfig": { + "config": { + "issuerName": "TANGEM SDK", + "acquirerName": "Smart Cash", + "series": "BB", + "startNumber": 300000000000, + "count": 0, + "pin": "000000", + "pin2": "000", + "pin3": "", + "hexCrExKey": "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF", + "cvc": "000", + "pauseBeforePin2": 5000, + "smartSecurityDelay": true, + "curveID": "Secp256k1", + "signingMethods": [ + "SignHash" + ], + "maxSignatures": 999999, + "isReusable": true, + "allowSetPIN1": true, + "allowSetPIN2": true, + "useActivation": false, + "useCvc": false, + "useNDEF": true, + "useDynamicNDEF": true, + "useOneCommandAtTime": false, + "useBlock": false, + "allowSelectBlockchain": true, + "prohibitPurgeWallet": false, + "allowUnencrypted": true, + "allowFastEncryption": true, + "protectIssuerDataAgainstReplay": false, + "prohibitDefaultPIN1": false, + "disablePrecomputedNDEF": false, + "skipSecurityDelayIfValidatedByIssuer": true, + "skipCheckPIN2CVCIfValidatedByIssuer": true, + "skipSecurityDelayIfValidatedByLinkedTerminal": true, + "restrictOverwriteIssuerExtraData": false, + "requireTerminalTxSignature": false, + "requireTerminalCertSignature": false, + "checkPIN3OnCard": false, + "createWallet": true, + "walletsCount": 1, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": null, + "manufactureDateTime": "2021-06-22", + "productMask": [ + "Note" + ] + }, + "ndefRecords": [ + { + "type": "AAR", + "value": "com.tangem.wallet" + }, + { + "type": "URI", + "value": "https://tangem.com" + } + ] + }, + "issuer": { + "name": "TANGEM SDK", + "id": "TANGEM SDK", + "dataKeyPair": { + "publicKey": "045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + }, + "transactionKeyPair": { + "publicKey": "0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8", + "privateKey": "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312" + } + }, + "acquirer": { + "name": "Smart Cash", + "id": "Smart Cash", + "keyPair": { + "publicKey": "0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a986f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf", + "privateKey": "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92" + } + }, + "manufacturer": { + "name": "Tangem", + "keyPair": { + "publicKey": "04bab86d56298c996f564a84fc88e28aed38184b12f07e519113bef48c76f3df3adc303599b08ac05b55ec3df98d9338573a6242f76f5d28f4f0f364e87e8fca2f", + "privateKey": "1b48cfd24bbb5b394771ed81f2bacf57479e4735eb1405083927372d40da9e92" + } + } + }, + "sdkConfig": {}, + "minimalFirmware": null, + "platform": null, + "iterations": 2, + "creationDateMs": 1624407062610 + }, + "steps": [ + { + "name": "Scan card 1", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000004", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + }, + { + "name": "Scan card 2", + "method": "SCAN_TASK", + "parameters": {}, + "expectedResult": { + "cardId": "BB03000000000005", + "manufacturerName": "TANGEM", + "status": "Loaded", + "firmwareVersion": { + "version": "3.37d SDK" + }, + "cardPublicKey": "0497C0424AF7BF8CE9920CB90EDAC2010FDED904937BC6D1D6E82E9254E0EFE4D4FDF355BB16BD0E9550D8BE5AB741B897FBC70360E99A86B97BA7DCC2FBF77D7E", + "defaultCurve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "issuerPublicKey": "045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26", + "signingMethods": [ + "SignHash" + ], + "pauseBeforePin2": 500, + "walletsCount": null, + "walletIndex": null, + "health": 0, + "isActivated": false, + "activationSeed": null, + "paymentFlowVersion": null, + "userCounter": null, + "userProtectedCounter": null, + "terminalIsLinked": false, + "cardData": { + "batchId": "FFFF", + "blockchainName": "ETH", + "issuerName": "TANGEM SDK", + "manufacturerSignature": "7CBEABA11F9D564A244260AC15CE0A26AB885811322C38B9FCA9E55EC7654C2027219ABB1BEAD190C2C370F48635129F76E8423FA8003DB6A08D2F64B1574004", + "manufactureDateTime": "2021-06-23", + "productMask": [ + "Note" + ] + }, + "isPin1Default": true, + "isPin2Default": true, + "wallets": [ + { + "index": 0, + "status": "Loaded", + "curve": "Secp256k1", + "settingsMask": [ + "IsReusable", + "AllowSetPIN1", + "AllowSetPIN2", + "UseNDEF", + "UseDynamicNDEF", + "SmartSecurityDelay", + "AllowUnencrypted", + "AllowFastEncryption", + "AllowSelectBlockchain", + "SkipSecurityDelayIfValidatedByLinkedTerminal", + "SkipCheckPIN2CVCIfValidatedByIssuer", + "SkipSecurityDelayIfValidatedByIssuer" + ], + "publicKey": "0408C8EF8DEC3B9C910B44D8A5C72138A333365ED2DC34E91FACA8B9A1B37EB5010CB0080E8144807998F50F6BE4B1CA4BF140ADFCEF34124D1417DD116535FB2D", + "signedHashes": 0, + "remainingSignatures": 999999 + } + ] + }, + "asserts": [], + "actionType": "NFC_SESSION_RUNNABLE", + "iterations": 2 + } + ] +} +''';