react-native-security-suite 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/android/build.gradle +45 -27
- package/android/src/main/AndroidManifest.xml +47 -0
- package/android/src/main/{AndroidManifestDeprecated.xml → AndroidManifestNew.xml} +1 -2
- package/android/src/main/java/com/securitysuite/NetworkLogger.java +25 -0
- package/android/src/main/java/com/securitysuite/Sslpinning.java +38 -54
- package/android/src/main/java/com/securitysuite/api/BodyDecoder.kt +35 -0
- package/android/src/main/java/com/securitysuite/api/Chucker.kt +108 -0
- package/android/src/main/java/com/securitysuite/api/ChuckerCollector.kt +129 -0
- package/android/src/main/java/com/securitysuite/api/ChuckerInterceptor.kt +280 -0
- package/android/src/main/java/com/securitysuite/api/ExportFormat.kt +12 -0
- package/android/src/main/java/com/securitysuite/api/RetentionManager.kt +109 -0
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpHeader.kt +8 -0
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransaction.kt +344 -0
- package/android/src/main/java/com/securitysuite/internal/data/entity/HttpTransactionTuple.kt +62 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/Har.kt +13 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/Log.kt +24 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Browser.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Creator.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Entry.kt +49 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/Page.kt +14 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Cache.kt +12 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Header.kt +17 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Request.kt +35 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Response.kt +33 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/Timings.kt +25 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/cache/SecondaryRequest.kt +13 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/PostData.kt +20 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/QueryString.kt +23 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/request/postdata/Params.kt +13 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/entry/response/Content.kt +30 -0
- package/android/src/main/java/com/securitysuite/internal/data/har/log/page/PageTimings.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/data/model/DialogData.kt +8 -0
- package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionDatabaseRepository.kt +60 -0
- package/android/src/main/java/com/securitysuite/internal/data/repository/HttpTransactionRepository.kt +33 -0
- package/android/src/main/java/com/securitysuite/internal/data/repository/RepositoryProvider.kt +38 -0
- package/android/src/main/java/com/securitysuite/internal/data/room/ChuckerDatabase.kt +22 -0
- package/android/src/main/java/com/securitysuite/internal/data/room/HttpTransactionDao.kt +53 -0
- package/android/src/main/java/com/securitysuite/internal/support/BitmapUtils.kt +45 -0
- package/android/src/main/java/com/securitysuite/internal/support/CacheDirectoryProvider.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/support/ChessboardDrawable.kt +76 -0
- package/android/src/main/java/com/securitysuite/internal/support/ChuckerFileProvider.kt +9 -0
- package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseJobIntentServiceReceiver.kt +14 -0
- package/android/src/main/java/com/securitysuite/internal/support/ClearDatabaseService.kt +32 -0
- package/android/src/main/java/com/securitysuite/internal/support/ContextExt.kt +22 -0
- package/android/src/main/java/com/securitysuite/internal/support/DepletingSource.kt +37 -0
- package/android/src/main/java/com/securitysuite/internal/support/FileFactory.kt +30 -0
- package/android/src/main/java/com/securitysuite/internal/support/FileSaver.kt +41 -0
- package/android/src/main/java/com/securitysuite/internal/support/FormatUtils.kt +117 -0
- package/android/src/main/java/com/securitysuite/internal/support/FormattedUrl.kt +76 -0
- package/android/src/main/java/com/securitysuite/internal/support/HarUtils.kt +28 -0
- package/android/src/main/java/com/securitysuite/internal/support/HttpUrlUtils.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/support/JsonConverter.kt +19 -0
- package/android/src/main/java/com/securitysuite/internal/support/LimitingSource.kt +22 -0
- package/android/src/main/java/com/securitysuite/internal/support/LiveDataUtils.kt +68 -0
- package/android/src/main/java/com/securitysuite/internal/support/Logger.kt +43 -0
- package/android/src/main/java/com/securitysuite/internal/support/NotificationHelper.kt +149 -0
- package/android/src/main/java/com/securitysuite/internal/support/OkHttpUtils.kt +86 -0
- package/android/src/main/java/com/securitysuite/internal/support/OkioUtils.kt +34 -0
- package/android/src/main/java/com/securitysuite/internal/support/PlainTextDecoder.kt +30 -0
- package/android/src/main/java/com/securitysuite/internal/support/ReportingSink.kt +114 -0
- package/android/src/main/java/com/securitysuite/internal/support/RequestProcessor.kt +102 -0
- package/android/src/main/java/com/securitysuite/internal/support/ResponseProcessor.kt +170 -0
- package/android/src/main/java/com/securitysuite/internal/support/SearchHighlightUtil.kt +80 -0
- package/android/src/main/java/com/securitysuite/internal/support/Sharable.kt +86 -0
- package/android/src/main/java/com/securitysuite/internal/support/SpanTextUtil.kt +202 -0
- package/android/src/main/java/com/securitysuite/internal/support/TeeSource.kt +68 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionCurlCommandSharable.kt +46 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsHarSharable.kt +11 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDetailsSharable.kt +73 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionDiffCallback.kt +26 -0
- package/android/src/main/java/com/securitysuite/internal/support/TransactionListDetailsSharable.kt +23 -0
- package/android/src/main/java/com/securitysuite/internal/ui/BaseChuckerActivity.kt +35 -0
- package/android/src/main/java/com/securitysuite/internal/ui/MainActivity.kt +375 -0
- package/android/src/main/java/com/securitysuite/internal/ui/MainViewModel.kt +47 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/PayloadType.kt +6 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/ProtocolResources.kt +14 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionActivity.kt +186 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionAdapter.kt +139 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionOverviewFragment.kt +100 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPagerAdapter.kt +29 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadAdapter.kt +269 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionPayloadFragment.kt +529 -0
- package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionViewModel.kt +69 -0
- package/android/src/main/res/A.java +12 -0
- package/android/src/main/res/color/chucker_fab_background_colour.xml +5 -0
- package/android/src/main/res/drawable/chucker_empty_payload.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_arrow_down.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_copy.xml +12 -0
- package/android/src/main/res/drawable/chucker_ic_decoded_url_white.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_delete_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_encoded_url_white.xml +11 -0
- package/android/src/main/res/drawable/chucker_ic_graphql.xml +27 -0
- package/android/src/main/res/drawable/chucker_ic_http.xml +10 -0
- package/android/src/main/res/drawable/chucker_ic_https.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_launcher_foreground.xml +14 -0
- package/android/src/main/res/drawable/chucker_ic_save_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_search_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_share_white.xml +9 -0
- package/android/src/main/res/drawable/chucker_ic_transaction_notification.xml +9 -0
- package/android/src/main/res/layout/activity_main.xml +83 -0
- package/android/src/main/res/layout/activity_transaction.xml +48 -0
- package/android/src/main/res/layout/fragment_transaction_overview.xml +365 -0
- package/android/src/main/res/layout/fragment_transaction_payload.xml +132 -0
- package/android/src/main/res/layout/list_item_transaction.xml +122 -0
- package/android/src/main/res/layout/transaction_item_body_line.xml +13 -0
- package/android/src/main/res/layout/transaction_item_copy.xml +19 -0
- package/android/src/main/res/layout/transaction_item_headers.xml +12 -0
- package/android/src/main/res/layout/transaction_item_image.xml +16 -0
- package/android/src/main/res/menu/chucker_transaction.xml +46 -0
- package/android/src/main/res/menu/chucker_transactions_list.xml +41 -0
- package/android/src/main/res/mipmap-anydpi-v26/chucker_ic_launcher.xml +5 -0
- package/android/src/main/res/mipmap-hdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xhdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxhdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxxhdpi/chucker_ic_launcher.png +0 -0
- package/android/src/main/res/values/chucker_ic_launcher_background.xml +4 -0
- package/android/src/main/res/values/colors.xml +38 -0
- package/android/src/main/res/values/dimens.xml +10 -0
- package/android/src/main/res/values/public.xml +5 -0
- package/android/src/main/res/values/strings.xml +77 -0
- package/android/src/main/res/values/styles.xml +44 -0
- package/android/src/main/res/values-es/strings.xml +75 -0
- package/android/src/main/res/values-night/colors.xml +32 -0
- package/android/src/main/res/xml/chucker_provider_paths.xml +4 -0
- package/ios/SecuritySuite.swift +0 -2
- package/ios/SslPinning.swift +0 -26
- package/lib/commonjs/helpers.js +1 -1
- package/lib/commonjs/helpers.js.map +1 -1
- package/lib/commonjs/index.js +23 -40
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/helpers.js +3 -1
- package/lib/module/helpers.js.map +1 -1
- package/lib/module/index.js +21 -33
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -0
- package/lib/typescript/{index.d.ts → commonjs/src/index.d.ts} +1 -6
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/helpers.d.ts +3 -0
- package/lib/typescript/module/src/helpers.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +72 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/package.json +70 -43
- package/react-native-security-suite.podspec +23 -15
- package/src/helpers.ts +1 -1
- package/src/index.tsx +5 -18
- package/android/src/main/java/com/securitysuite/AndroidLogger.kt +0 -19
- package/android/src/main/java/com/securitysuite/modifier/Base64Decoder.kt +0 -11
- package/android/src/main/java/com/securitysuite/modifier/BasicAuthorizationHeaderModifier.kt +0 -16
- package/ios/SecuritySuite.xcodeproj/project.pbxproj +0 -293
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/SecuritySuite.xcodeproj/project.xcworkspace/xcuserdata/m.navabifar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/SecuritySuite.xcodeproj/xcuserdata/m.navabifar.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/lib/typescript/helpers.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- /package/lib/typescript/{helpers.d.ts → commonjs/src/helpers.d.ts} +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
package com.securitysuite.internal.ui
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.Bundle
|
|
9
|
+
import android.provider.Settings
|
|
10
|
+
import android.text.method.LinkMovementMethod
|
|
11
|
+
import android.view.Menu
|
|
12
|
+
import android.view.MenuItem
|
|
13
|
+
import android.widget.Toast
|
|
14
|
+
import androidx.activity.result.contract.ActivityResultContracts
|
|
15
|
+
import androidx.activity.viewModels
|
|
16
|
+
import androidx.annotation.RequiresApi
|
|
17
|
+
import androidx.annotation.StringRes
|
|
18
|
+
import androidx.appcompat.widget.SearchView
|
|
19
|
+
import androidx.core.content.ContextCompat
|
|
20
|
+
import androidx.core.view.MenuCompat
|
|
21
|
+
import androidx.core.view.isVisible
|
|
22
|
+
import androidx.lifecycle.lifecycleScope
|
|
23
|
+
import androidx.recyclerview.widget.DividerItemDecoration
|
|
24
|
+
import com.securitysuite.R
|
|
25
|
+
import com.securitysuite.api.Chucker
|
|
26
|
+
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
27
|
+
import com.securitysuite.internal.data.model.DialogData
|
|
28
|
+
import com.securitysuite.internal.support.FileSaver
|
|
29
|
+
import com.securitysuite.internal.support.HarUtils
|
|
30
|
+
import com.securitysuite.internal.support.Logger
|
|
31
|
+
import com.securitysuite.internal.support.Sharable
|
|
32
|
+
import com.securitysuite.internal.support.TransactionDetailsHarSharable
|
|
33
|
+
import com.securitysuite.internal.support.TransactionListDetailsSharable
|
|
34
|
+
import com.securitysuite.internal.support.shareAsFile
|
|
35
|
+
import com.securitysuite.internal.support.showDialog
|
|
36
|
+
import com.securitysuite.internal.ui.MainActivity.ExportType.HAR
|
|
37
|
+
import com.securitysuite.internal.ui.MainActivity.ExportType.TEXT
|
|
38
|
+
import com.securitysuite.internal.ui.transaction.TransactionActivity
|
|
39
|
+
import com.securitysuite.internal.ui.transaction.TransactionAdapter
|
|
40
|
+
import com.google.android.material.snackbar.Snackbar
|
|
41
|
+
import com.securitysuite.databinding.ActivityMainBinding
|
|
42
|
+
import kotlinx.coroutines.Dispatchers
|
|
43
|
+
import kotlinx.coroutines.launch
|
|
44
|
+
import kotlinx.coroutines.withContext
|
|
45
|
+
import okio.Source
|
|
46
|
+
import okio.buffer
|
|
47
|
+
import okio.source
|
|
48
|
+
|
|
49
|
+
internal class MainActivity :
|
|
50
|
+
BaseChuckerActivity(),
|
|
51
|
+
SearchView.OnQueryTextListener {
|
|
52
|
+
private val viewModel: MainViewModel by viewModels()
|
|
53
|
+
|
|
54
|
+
private lateinit var mainBinding: ActivityMainBinding
|
|
55
|
+
private lateinit var transactionsAdapter: TransactionAdapter
|
|
56
|
+
|
|
57
|
+
private val applicationName: CharSequence
|
|
58
|
+
get() = applicationInfo.loadLabel(packageManager)
|
|
59
|
+
|
|
60
|
+
private val permissionRequest =
|
|
61
|
+
registerForActivityResult(
|
|
62
|
+
ActivityResultContracts.RequestPermission(),
|
|
63
|
+
) { isPermissionGranted: Boolean ->
|
|
64
|
+
if (!isPermissionGranted) {
|
|
65
|
+
showToast(
|
|
66
|
+
applicationContext.getString(R.string.chucker_notifications_permission_not_granted),
|
|
67
|
+
Toast.LENGTH_LONG,
|
|
68
|
+
)
|
|
69
|
+
Logger.error("Notification permission denied. Can't show transactions info")
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private val saveTextToFile =
|
|
74
|
+
registerForActivityResult(ActivityResultContracts.CreateDocument(TEXT.mimeType)) { uri ->
|
|
75
|
+
onSaveToFileActivityResult(uri, TEXT)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private val saveHarToFile =
|
|
79
|
+
registerForActivityResult(ActivityResultContracts.CreateDocument(HAR.mimeType)) { uri ->
|
|
80
|
+
onSaveToFileActivityResult(uri, HAR)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
84
|
+
super.onCreate(savedInstanceState)
|
|
85
|
+
|
|
86
|
+
mainBinding = ActivityMainBinding.inflate(layoutInflater)
|
|
87
|
+
transactionsAdapter =
|
|
88
|
+
TransactionAdapter(this) { transactionId ->
|
|
89
|
+
TransactionActivity.start(this, transactionId)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
with(mainBinding) {
|
|
93
|
+
setContentView(root)
|
|
94
|
+
setSupportActionBar(toolbar)
|
|
95
|
+
toolbar.subtitle = applicationName
|
|
96
|
+
|
|
97
|
+
tutorialLink.movementMethod = LinkMovementMethod.getInstance()
|
|
98
|
+
transactionsRecyclerView.apply {
|
|
99
|
+
setHasFixedSize(true)
|
|
100
|
+
addItemDecoration(
|
|
101
|
+
DividerItemDecoration(
|
|
102
|
+
this@MainActivity,
|
|
103
|
+
DividerItemDecoration.VERTICAL,
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
adapter = transactionsAdapter
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
viewModel.transactions.observe(
|
|
111
|
+
this,
|
|
112
|
+
) { transactionTuples ->
|
|
113
|
+
transactionsAdapter.submitList(transactionTuples)
|
|
114
|
+
mainBinding.tutorialGroup.isVisible = transactionTuples.isEmpty()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (Chucker.showNotifications && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
118
|
+
handleNotificationsPermission()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
|
123
|
+
private fun handleNotificationsPermission() {
|
|
124
|
+
when {
|
|
125
|
+
ContextCompat.checkSelfPermission(
|
|
126
|
+
this,
|
|
127
|
+
Manifest.permission.POST_NOTIFICATIONS,
|
|
128
|
+
) == PackageManager.PERMISSION_GRANTED -> {
|
|
129
|
+
// We have permission, all good
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> {
|
|
133
|
+
Snackbar.make(
|
|
134
|
+
mainBinding.root,
|
|
135
|
+
applicationContext.getString(R.string.chucker_notifications_permission_not_granted),
|
|
136
|
+
Snackbar.LENGTH_LONG,
|
|
137
|
+
).setAction(applicationContext.getString(R.string.chucker_change)) {
|
|
138
|
+
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
139
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
140
|
+
data = Uri.fromParts("package", packageName, null)
|
|
141
|
+
}.also { intent ->
|
|
142
|
+
startActivity(intent)
|
|
143
|
+
}
|
|
144
|
+
}.show()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
else -> {
|
|
148
|
+
permissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
154
|
+
menuInflater.inflate(R.menu.chucker_transactions_list, menu)
|
|
155
|
+
MenuCompat.setGroupDividerEnabled(menu, true)
|
|
156
|
+
setUpSearch(menu)
|
|
157
|
+
return super.onCreateOptionsMenu(menu)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun setUpSearch(menu: Menu) {
|
|
161
|
+
val searchMenuItem = menu.findItem(R.id.search)
|
|
162
|
+
val searchView = searchMenuItem.actionView as SearchView
|
|
163
|
+
searchView.setOnQueryTextListener(this)
|
|
164
|
+
searchView.setIconifiedByDefault(true)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
168
|
+
return when (item.itemId) {
|
|
169
|
+
R.id.clear -> {
|
|
170
|
+
showDialog(
|
|
171
|
+
getClearDialogData(),
|
|
172
|
+
onPositiveClick = {
|
|
173
|
+
viewModel.clearTransactions()
|
|
174
|
+
},
|
|
175
|
+
onNegativeClick = null,
|
|
176
|
+
)
|
|
177
|
+
true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
R.id.share_text -> {
|
|
181
|
+
showDialog(
|
|
182
|
+
getExportDialogData(R.string.chucker_export_text_http_confirmation),
|
|
183
|
+
onPositiveClick = {
|
|
184
|
+
exportTransactions(EXPORT_TXT_FILE_NAME) { transactions ->
|
|
185
|
+
TransactionListDetailsSharable(transactions, encodeUrls = false)
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
onNegativeClick = null,
|
|
189
|
+
)
|
|
190
|
+
true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
R.id.share_har -> {
|
|
194
|
+
showDialog(
|
|
195
|
+
getExportDialogData(R.string.chucker_export_har_http_confirmation),
|
|
196
|
+
onPositiveClick = {
|
|
197
|
+
exportTransactions(EXPORT_HAR_FILE_NAME) { transactions ->
|
|
198
|
+
TransactionDetailsHarSharable(
|
|
199
|
+
HarUtils.harStringFromTransactions(
|
|
200
|
+
transactions,
|
|
201
|
+
getString(R.string.chucker_name),
|
|
202
|
+
getString(R.string.chucker_version),
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
onNegativeClick = null,
|
|
208
|
+
)
|
|
209
|
+
true
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
R.id.save_text -> {
|
|
213
|
+
showSaveDialog(TEXT)
|
|
214
|
+
true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
R.id.save_har -> {
|
|
218
|
+
showSaveDialog(HAR)
|
|
219
|
+
true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
else -> {
|
|
223
|
+
super.onOptionsItemSelected(item)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
override fun onQueryTextSubmit(query: String): Boolean = true
|
|
229
|
+
|
|
230
|
+
override fun onQueryTextChange(newText: String): Boolean {
|
|
231
|
+
viewModel.updateItemsFilter(newText)
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fun exportTransactions(
|
|
236
|
+
fileName: String,
|
|
237
|
+
block: suspend (List<HttpTransaction>) -> Sharable,
|
|
238
|
+
) {
|
|
239
|
+
val applicationContext = this.applicationContext
|
|
240
|
+
lifecycleScope.launch {
|
|
241
|
+
val transactions = viewModel.getAllTransactions()
|
|
242
|
+
if (transactions.isEmpty()) {
|
|
243
|
+
showToast(applicationContext.getString(R.string.chucker_export_empty_text))
|
|
244
|
+
return@launch
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
val sharableTransactions = block(transactions)
|
|
248
|
+
val shareIntent =
|
|
249
|
+
withContext(Dispatchers.IO) {
|
|
250
|
+
sharableTransactions.shareAsFile(
|
|
251
|
+
activity = this@MainActivity,
|
|
252
|
+
fileName = fileName,
|
|
253
|
+
intentTitle = getString(R.string.chucker_share_all_transactions_title),
|
|
254
|
+
intentSubject = getString(R.string.chucker_share_all_transactions_subject),
|
|
255
|
+
clipDataLabel = "transactions",
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
if (shareIntent != null) {
|
|
259
|
+
startActivity(shareIntent)
|
|
260
|
+
} else {
|
|
261
|
+
showToast(applicationContext.getString(R.string.chucker_export_no_file))
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private fun getClearDialogData(): DialogData =
|
|
267
|
+
DialogData(
|
|
268
|
+
title = getString(R.string.chucker_clear),
|
|
269
|
+
message = getString(R.string.chucker_clear_http_confirmation),
|
|
270
|
+
positiveButtonText = getString(R.string.chucker_clear),
|
|
271
|
+
negativeButtonText = getString(R.string.chucker_cancel),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
private fun getExportDialogData(
|
|
275
|
+
@StringRes dialogMessage: Int,
|
|
276
|
+
): DialogData =
|
|
277
|
+
DialogData(
|
|
278
|
+
title = getString(R.string.chucker_export),
|
|
279
|
+
message = getString(dialogMessage),
|
|
280
|
+
positiveButtonText = getString(R.string.chucker_export),
|
|
281
|
+
negativeButtonText = getString(R.string.chucker_cancel),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
private fun getSaveDialogData(
|
|
285
|
+
@StringRes dialogMessage: Int,
|
|
286
|
+
): DialogData =
|
|
287
|
+
DialogData(
|
|
288
|
+
title = getString(R.string.chucker_save),
|
|
289
|
+
message = getString(dialogMessage),
|
|
290
|
+
positiveButtonText = getString(R.string.chucker_save),
|
|
291
|
+
negativeButtonText = getString(R.string.chucker_cancel),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
private fun showSaveDialog(exportType: ExportType) {
|
|
295
|
+
showDialog(
|
|
296
|
+
getSaveDialogData(
|
|
297
|
+
when (exportType) {
|
|
298
|
+
TEXT -> R.string.chucker_save_text_http_confirmation
|
|
299
|
+
HAR -> R.string.chucker_save_har_http_confirmation
|
|
300
|
+
},
|
|
301
|
+
),
|
|
302
|
+
onPositiveClick = {
|
|
303
|
+
when (exportType) {
|
|
304
|
+
TEXT -> saveTextToFile.launch(EXPORT_TXT_FILE_NAME)
|
|
305
|
+
HAR -> saveHarToFile.launch(EXPORT_HAR_FILE_NAME)
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
onNegativeClick = null,
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private fun onSaveToFileActivityResult(
|
|
313
|
+
uri: Uri?,
|
|
314
|
+
exportType: ExportType,
|
|
315
|
+
) {
|
|
316
|
+
if (uri == null) {
|
|
317
|
+
Toast.makeText(
|
|
318
|
+
applicationContext,
|
|
319
|
+
R.string.chucker_save_failed_to_open_document,
|
|
320
|
+
Toast.LENGTH_SHORT,
|
|
321
|
+
).show()
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
lifecycleScope.launch {
|
|
325
|
+
val source =
|
|
326
|
+
runCatching {
|
|
327
|
+
prepareDataToSave(exportType)
|
|
328
|
+
}.getOrNull() ?: return@launch
|
|
329
|
+
val result = FileSaver.saveFile(source, uri, contentResolver)
|
|
330
|
+
val toastMessageId =
|
|
331
|
+
if (result) {
|
|
332
|
+
R.string.chucker_file_saved
|
|
333
|
+
} else {
|
|
334
|
+
R.string.chucker_file_not_saved
|
|
335
|
+
}
|
|
336
|
+
Toast.makeText(applicationContext, toastMessageId, Toast.LENGTH_SHORT).show()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private suspend fun prepareDataToSave(exportType: ExportType): Source? {
|
|
341
|
+
val transactions = viewModel.getAllTransactions()
|
|
342
|
+
if (transactions.isEmpty()) {
|
|
343
|
+
showToast(applicationContext.getString(R.string.chucker_save_empty_text))
|
|
344
|
+
return null
|
|
345
|
+
}
|
|
346
|
+
return withContext(Dispatchers.IO) {
|
|
347
|
+
when (exportType) {
|
|
348
|
+
TEXT -> {
|
|
349
|
+
TransactionListDetailsSharable(
|
|
350
|
+
transactions,
|
|
351
|
+
encodeUrls = false,
|
|
352
|
+
).toSharableContent(this@MainActivity)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
HAR -> {
|
|
356
|
+
HarUtils.harStringFromTransactions(
|
|
357
|
+
transactions,
|
|
358
|
+
getString(R.string.chucker_name),
|
|
359
|
+
getString(R.string.chucker_version),
|
|
360
|
+
).byteInputStream().source().buffer()
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private enum class ExportType(val mimeType: String) {
|
|
367
|
+
TEXT("text/plain"),
|
|
368
|
+
HAR("application/har+json"),
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
companion object {
|
|
372
|
+
private const val EXPORT_TXT_FILE_NAME = "transactions.txt"
|
|
373
|
+
private const val EXPORT_HAR_FILE_NAME = "transactions.har"
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package com.securitysuite.internal.ui
|
|
2
|
+
|
|
3
|
+
import android.text.TextUtils
|
|
4
|
+
import androidx.lifecycle.LiveData
|
|
5
|
+
import androidx.lifecycle.MutableLiveData
|
|
6
|
+
import androidx.lifecycle.ViewModel
|
|
7
|
+
import androidx.lifecycle.switchMap
|
|
8
|
+
import androidx.lifecycle.viewModelScope
|
|
9
|
+
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
10
|
+
import com.securitysuite.internal.data.entity.HttpTransactionTuple
|
|
11
|
+
import com.securitysuite.internal.data.repository.RepositoryProvider
|
|
12
|
+
import com.securitysuite.internal.support.NotificationHelper
|
|
13
|
+
import kotlinx.coroutines.launch
|
|
14
|
+
|
|
15
|
+
internal class MainViewModel : ViewModel() {
|
|
16
|
+
private val currentFilter = MutableLiveData("")
|
|
17
|
+
|
|
18
|
+
val transactions: LiveData<List<HttpTransactionTuple>> =
|
|
19
|
+
currentFilter.switchMap { searchQuery ->
|
|
20
|
+
with(RepositoryProvider.transaction()) {
|
|
21
|
+
when {
|
|
22
|
+
searchQuery.isNullOrBlank() -> {
|
|
23
|
+
getSortedTransactionTuples()
|
|
24
|
+
}
|
|
25
|
+
TextUtils.isDigitsOnly(searchQuery) -> {
|
|
26
|
+
getFilteredTransactionTuples(searchQuery, "")
|
|
27
|
+
}
|
|
28
|
+
else -> {
|
|
29
|
+
getFilteredTransactionTuples("", searchQuery)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
suspend fun getAllTransactions(): List<HttpTransaction> = RepositoryProvider.transaction().getAllTransactions()
|
|
36
|
+
|
|
37
|
+
fun updateItemsFilter(searchQuery: String) {
|
|
38
|
+
currentFilter.value = searchQuery
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fun clearTransactions() {
|
|
42
|
+
viewModelScope.launch {
|
|
43
|
+
RepositoryProvider.transaction().deleteAllTransactions()
|
|
44
|
+
}
|
|
45
|
+
NotificationHelper.clearBuffer()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.securitysuite.internal.ui.transaction
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.ColorRes
|
|
4
|
+
import androidx.annotation.DrawableRes
|
|
5
|
+
import com.securitysuite.R
|
|
6
|
+
|
|
7
|
+
internal sealed class ProtocolResources(
|
|
8
|
+
@DrawableRes val icon: Int,
|
|
9
|
+
@ColorRes val color: Int,
|
|
10
|
+
) {
|
|
11
|
+
class Http : ProtocolResources(R.drawable.chucker_ic_http, R.color.chucker_color_error)
|
|
12
|
+
|
|
13
|
+
class Https : ProtocolResources(R.drawable.chucker_ic_https, R.color.chucker_color_primary)
|
|
14
|
+
}
|
package/android/src/main/java/com/securitysuite/internal/ui/transaction/TransactionActivity.kt
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package com.securitysuite.internal.ui.transaction
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.view.Menu
|
|
7
|
+
import android.view.MenuItem
|
|
8
|
+
import android.widget.Toast
|
|
9
|
+
import androidx.activity.viewModels
|
|
10
|
+
import androidx.lifecycle.Observer
|
|
11
|
+
import androidx.lifecycle.lifecycleScope
|
|
12
|
+
import androidx.viewpager.widget.ViewPager
|
|
13
|
+
import com.securitysuite.R
|
|
14
|
+
import com.securitysuite.databinding.ActivityTransactionBinding
|
|
15
|
+
import com.securitysuite.internal.data.entity.HttpTransaction
|
|
16
|
+
import com.securitysuite.internal.support.HarUtils
|
|
17
|
+
import com.securitysuite.internal.support.Sharable
|
|
18
|
+
import com.securitysuite.internal.support.TransactionCurlCommandSharable
|
|
19
|
+
import com.securitysuite.internal.support.TransactionDetailsHarSharable
|
|
20
|
+
import com.securitysuite.internal.support.TransactionDetailsSharable
|
|
21
|
+
import com.securitysuite.internal.support.shareAsFile
|
|
22
|
+
import com.securitysuite.internal.support.shareAsUtf8Text
|
|
23
|
+
import com.securitysuite.internal.ui.BaseChuckerActivity
|
|
24
|
+
import kotlinx.coroutines.Dispatchers
|
|
25
|
+
import kotlinx.coroutines.launch
|
|
26
|
+
import kotlinx.coroutines.withContext
|
|
27
|
+
|
|
28
|
+
internal class TransactionActivity : BaseChuckerActivity() {
|
|
29
|
+
private val viewModel: TransactionViewModel by viewModels {
|
|
30
|
+
TransactionViewModelFactory(intent.getLongExtra(EXTRA_TRANSACTION_ID, 0))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private lateinit var transactionBinding: ActivityTransactionBinding
|
|
34
|
+
|
|
35
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
36
|
+
super.onCreate(savedInstanceState)
|
|
37
|
+
transactionBinding = ActivityTransactionBinding.inflate(layoutInflater)
|
|
38
|
+
|
|
39
|
+
with(transactionBinding) {
|
|
40
|
+
setContentView(root)
|
|
41
|
+
setSupportActionBar(toolbar)
|
|
42
|
+
setupViewPager(viewPager)
|
|
43
|
+
tabLayout.setupWithViewPager(viewPager)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
|
47
|
+
|
|
48
|
+
viewModel.transactionTitle.observe(
|
|
49
|
+
this,
|
|
50
|
+
Observer { transactionBinding.toolbarTitle.text = it },
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
55
|
+
menuInflater.inflate(R.menu.chucker_transaction, menu)
|
|
56
|
+
setUpUrlEncoding(menu)
|
|
57
|
+
return super.onCreateOptionsMenu(menu)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun setUpUrlEncoding(menu: Menu) {
|
|
61
|
+
val encodeUrlMenuItem = menu.findItem(R.id.encode_url)
|
|
62
|
+
encodeUrlMenuItem.setOnMenuItemClickListener {
|
|
63
|
+
viewModel.switchUrlEncoding()
|
|
64
|
+
return@setOnMenuItemClickListener true
|
|
65
|
+
}
|
|
66
|
+
viewModel.encodeUrl.observe(
|
|
67
|
+
this,
|
|
68
|
+
Observer { encode ->
|
|
69
|
+
val icon =
|
|
70
|
+
if (encode) {
|
|
71
|
+
R.drawable.chucker_ic_encoded_url_white
|
|
72
|
+
} else {
|
|
73
|
+
R.drawable.chucker_ic_decoded_url_white
|
|
74
|
+
}
|
|
75
|
+
encodeUrlMenuItem.setIcon(icon)
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override fun onOptionsItemSelected(item: MenuItem) =
|
|
81
|
+
when (item.itemId) {
|
|
82
|
+
R.id.share_text ->
|
|
83
|
+
shareTransactionAsText { transaction ->
|
|
84
|
+
val encodeUrls = viewModel.encodeUrl.value!!
|
|
85
|
+
TransactionDetailsSharable(transaction, encodeUrls)
|
|
86
|
+
}
|
|
87
|
+
R.id.share_curl ->
|
|
88
|
+
shareTransactionAsText { transaction ->
|
|
89
|
+
TransactionCurlCommandSharable(transaction)
|
|
90
|
+
}
|
|
91
|
+
R.id.share_file ->
|
|
92
|
+
shareTransactionAsFile(EXPORT_TXT_FILE_NAME) { transaction ->
|
|
93
|
+
val encodeUrls = viewModel.encodeUrl.value!!
|
|
94
|
+
TransactionDetailsSharable(transaction, encodeUrls)
|
|
95
|
+
}
|
|
96
|
+
R.id.share_har ->
|
|
97
|
+
shareTransactionAsFile(EXPORT_HAR_FILE_NAME) { transaction ->
|
|
98
|
+
TransactionDetailsHarSharable(
|
|
99
|
+
HarUtils.harStringFromTransactions(
|
|
100
|
+
listOf(transaction),
|
|
101
|
+
getString(R.string.chucker_name),
|
|
102
|
+
getString(R.string.chucker_version),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
else -> super.onOptionsItemSelected(item)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private fun shareTransactionAsText(block: (HttpTransaction) -> Sharable): Boolean {
|
|
110
|
+
val transaction = viewModel.transaction.value
|
|
111
|
+
if (transaction == null) {
|
|
112
|
+
showToast(getString(R.string.chucker_request_not_ready))
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
val sharable = block(transaction)
|
|
116
|
+
lifecycleScope.launch {
|
|
117
|
+
val shareIntent =
|
|
118
|
+
sharable.shareAsUtf8Text(
|
|
119
|
+
activity = this@TransactionActivity,
|
|
120
|
+
intentTitle = getString(R.string.chucker_share_transaction_title),
|
|
121
|
+
intentSubject = getString(R.string.chucker_share_transaction_subject),
|
|
122
|
+
)
|
|
123
|
+
startActivity(shareIntent)
|
|
124
|
+
}
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun shareTransactionAsFile(
|
|
129
|
+
fileName: String,
|
|
130
|
+
block: suspend (HttpTransaction) -> Sharable,
|
|
131
|
+
): Boolean {
|
|
132
|
+
lifecycleScope.launch {
|
|
133
|
+
val transaction = viewModel.transaction.value
|
|
134
|
+
if (transaction == null) {
|
|
135
|
+
showToast(getString(R.string.chucker_request_not_ready))
|
|
136
|
+
return@launch
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
val sharable = block(transaction)
|
|
140
|
+
val shareIntent =
|
|
141
|
+
withContext(Dispatchers.IO) {
|
|
142
|
+
sharable.shareAsFile(
|
|
143
|
+
activity = this@TransactionActivity,
|
|
144
|
+
fileName = fileName,
|
|
145
|
+
intentTitle = getString(R.string.chucker_share_transaction_title),
|
|
146
|
+
intentSubject = getString(R.string.chucker_share_transaction_subject),
|
|
147
|
+
clipDataLabel = "transaction",
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
if (shareIntent != null) {
|
|
151
|
+
startActivity(shareIntent)
|
|
152
|
+
} else {
|
|
153
|
+
Toast.makeText(applicationContext, R.string.chucker_export_no_file, Toast.LENGTH_SHORT).show()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private fun setupViewPager(viewPager: ViewPager) {
|
|
160
|
+
viewPager.adapter = TransactionPagerAdapter(this, supportFragmentManager)
|
|
161
|
+
viewPager.addOnPageChangeListener(
|
|
162
|
+
object : ViewPager.SimpleOnPageChangeListener() {
|
|
163
|
+
override fun onPageSelected(position: Int) {
|
|
164
|
+
selectedTabPosition = position
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
viewPager.currentItem = selectedTabPosition
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
companion object {
|
|
172
|
+
private const val EXPORT_TXT_FILE_NAME = "transaction.txt"
|
|
173
|
+
private const val EXPORT_HAR_FILE_NAME = "transaction.har"
|
|
174
|
+
private const val EXTRA_TRANSACTION_ID = "transaction_id"
|
|
175
|
+
private var selectedTabPosition = 0
|
|
176
|
+
|
|
177
|
+
fun start(
|
|
178
|
+
context: Context,
|
|
179
|
+
transactionId: Long,
|
|
180
|
+
) {
|
|
181
|
+
val intent = Intent(context, TransactionActivity::class.java)
|
|
182
|
+
intent.putExtra(EXTRA_TRANSACTION_ID, transactionId)
|
|
183
|
+
context.startActivity(intent)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|