@onekeyfe/react-native-native-list 3.0.136 → 3.0.137

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.
@@ -2,6 +2,9 @@ package com.margelo.nitro.nativelist
2
2
 
3
3
  import org.json.JSONArray
4
4
  import org.json.JSONObject
5
+ import java.nio.ByteBuffer
6
+ import java.security.MessageDigest
7
+ import java.util.Locale
5
8
 
6
9
  internal fun isNativeListRowPressEnabled(
7
10
  type: String,
@@ -22,6 +25,28 @@ internal fun isNativeListWholeRowInteractive(
22
25
  pressDisabled = pressDisabled,
23
26
  )
24
27
 
28
+ // The image fallback-state cache is process-wide, so it is keyed by a digest of the request
29
+ // identity instead of the raw headers, which can carry credentials such as Authorization.
30
+ internal fun nativeListSourceFallbackStateKey(uri: String, headers: Map<String, String>): String? {
31
+ val trimmedUri = uri.trim().takeIf(String::isNotEmpty) ?: return null
32
+ val digest = MessageDigest.getInstance("SHA-256")
33
+ updateLengthPrefixed(digest, trimmedUri)
34
+ headers.entries
35
+ .map { it.key.lowercase(Locale.ROOT) to it.value }
36
+ .sortedWith(compareBy<Pair<String, String>>({ it.first }, { it.second }))
37
+ .forEach { (name, value) ->
38
+ updateLengthPrefixed(digest, name)
39
+ updateLengthPrefixed(digest, value)
40
+ }
41
+ return digest.digest().joinToString("") { "%02x".format(Locale.ROOT, it.toInt() and 0xff) }
42
+ }
43
+
44
+ private fun updateLengthPrefixed(digest: MessageDigest, value: String) {
45
+ val bytes = value.toByteArray(Charsets.UTF_8)
46
+ digest.update(ByteBuffer.allocate(Int.SIZE_BYTES).putInt(bytes.size).array())
47
+ digest.update(bytes)
48
+ }
49
+
25
50
  internal data class NativeListItem(
26
51
  val key: String,
27
52
  val type: String,
@@ -3538,9 +3538,12 @@ internal class NativeListRowView(
3538
3538
  }
3539
3539
 
3540
3540
  private fun sourceFallbackStateKey(source: JSONObject): String? =
3541
- source.optString("uri").trim().takeIf(String::isNotEmpty)?.let { uri ->
3542
- "$uri\u0000${source.optJSONObject("headers")?.toString().orEmpty()}"
3543
- }
3541
+ nativeListSourceFallbackStateKey(
3542
+ uri = source.optString("uri"),
3543
+ headers = source.optJSONObject("headers")?.let { headers ->
3544
+ headers.keys().asSequence().associateWith { headers.optString(it) }
3545
+ }.orEmpty(),
3546
+ )
3544
3547
 
3545
3548
  private fun leadingImageLayout(
3546
3549
  index: Int,
@@ -0,0 +1,43 @@
1
+ package com.margelo.nitro.nativelist
2
+
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertFalse
5
+ import org.junit.Assert.assertNull
6
+ import org.junit.Assert.assertTrue
7
+ import org.junit.Test
8
+
9
+ class NativeListSourceFallbackStateKeyTest {
10
+ private val uri = "https://example.com/token.png"
11
+
12
+ @Test
13
+ fun keyIsADigestThatDoesNotRetainHeaderValues() {
14
+ val key = nativeListSourceFallbackStateKey(uri, mapOf("Authorization" to "Bearer secret-token"))
15
+
16
+ assertTrue(key.orEmpty().matches(Regex("[0-9a-f]{64}")))
17
+ assertFalse(key.orEmpty().contains("secret-token"))
18
+ }
19
+
20
+ @Test
21
+ fun keyIgnoresHeaderOrderAndNameCase() {
22
+ assertEquals(
23
+ nativeListSourceFallbackStateKey(uri, linkedMapOf("Authorization" to "Bearer a", "X-Trace" to "1")),
24
+ nativeListSourceFallbackStateKey(uri, linkedMapOf("x-trace" to "1", "authorization" to "Bearer a")),
25
+ )
26
+ }
27
+
28
+ @Test
29
+ fun keyDistinguishesSourcesThatDifferOnlyByHeaders() {
30
+ val keys = listOf(
31
+ nativeListSourceFallbackStateKey(uri, emptyMap()),
32
+ nativeListSourceFallbackStateKey(uri, mapOf("Authorization" to "Bearer a")),
33
+ nativeListSourceFallbackStateKey(uri, mapOf("Authorization" to "Bearer b")),
34
+ )
35
+
36
+ assertEquals(keys.size, keys.toSet().size)
37
+ }
38
+
39
+ @Test
40
+ fun blankUriHasNoKey() {
41
+ assertNull(nativeListSourceFallbackStateKey(" ", mapOf("Authorization" to "Bearer a")))
42
+ }
43
+ }
@@ -1,6 +1,7 @@
1
1
  import Foundation
2
2
  // OneKey patch: preserve native font faces while enabling tabular number features.
3
3
  import CoreText
4
+ import CryptoKit
4
5
  import OneKeyImage
5
6
  import UIKit
6
7
 
@@ -3694,15 +3695,21 @@ final class NativeListCell: UICollectionViewCell {
3694
3695
  private func sourceFallbackStateKey(_ source: [String: Any]) -> String? {
3695
3696
  let uri = source.string("uri").trimmingCharacters(in: .whitespacesAndNewlines)
3696
3697
  guard !uri.isEmpty else { return nil }
3697
- let headersJson: String
3698
- if let headers = source.dictionary("headers"),
3699
- JSONSerialization.isValidJSONObject(headers),
3700
- let data = try? JSONSerialization.data(withJSONObject: headers, options: [.sortedKeys]) {
3701
- headersJson = String(data: data, encoding: .utf8) ?? ""
3702
- } else {
3703
- headersJson = ""
3698
+ // The fallback-state cache is process-wide, so key it by a digest of the request identity
3699
+ // instead of retaining raw header values, which can carry credentials such as Authorization.
3700
+ let headers = source.dictionary("headers") ?? [:]
3701
+ let fields: [(name: String, value: String)] = headers.map { key, value in
3702
+ (name: key.lowercased(), value: String(describing: value))
3703
+ }
3704
+ let sortedFields = fields.sorted { lhs, rhs in
3705
+ lhs.name == rhs.name ? lhs.value < rhs.value : lhs.name < rhs.name
3706
+ }
3707
+ var canonicalValue = "\(uri.utf8.count):\(uri)"
3708
+ for field in sortedFields {
3709
+ canonicalValue += "\(field.name.utf8.count):\(field.name)\(field.value.utf8.count):\(field.value)"
3704
3710
  }
3705
- return "\(uri)\u{0}\(headersJson)"
3711
+ let digest = SHA256.hash(data: Data(canonicalValue.utf8))
3712
+ return digest.map { String(format: "%02x", $0) }.joined()
3706
3713
  }
3707
3714
 
3708
3715
  private func leadingConstraints(
@@ -4147,8 +4154,10 @@ final class NativeListCell: UICollectionViewCell {
4147
4154
  },
4148
4155
  onError: retryLimit == 0 ? handleError : { [weak self, weak imageView] in
4149
4156
  guard let self, self.bindingEpoch == expectedEpoch, let imageView else { return }
4150
- guard retryAttempt < retryLimit else { handleError?(); return }
4151
- guard self.selectorImageRetries[imageID] == nil else { return }
4157
+ // Report every failed attempt: a rebind during the retry delay cancels the pending retry,
4158
+ // so deferring to the last attempt would never record the source fallback state.
4159
+ handleError?()
4160
+ guard retryAttempt < retryLimit, self.selectorImageRetries[imageID] == nil else { return }
4152
4161
  let retry = DispatchWorkItem { [weak self, weak imageView] in
4153
4162
  guard let self, self.bindingEpoch == expectedEpoch, let imageView else { return }
4154
4163
  self.selectorImageRetries.removeValue(forKey: imageID)
@@ -537,8 +537,10 @@ export const WEB_LIST_CSS = `
537
537
  .ok-native-list-account-row[data-native-list-selector="accountSelector"]>.ok-native-list-accessories[data-native-list-account-control="createAddress"]{position:absolute;top:18px;right:12px}
538
538
  .ok-native-list-account-row[data-native-list-selector="accountSelector"] .ok-native-list-accessories>[data-native-list-account-control="createAddress"]{flex-basis:36px;width:36px;height:36px;padding:6px;border-radius:8px}
539
539
  .ok-native-list-account-action-row{padding-left:12px;padding-right:12px}.ok-native-list-account-action-row .ok-native-list-action-title{font-size:16px;line-height:24px;font-weight:400}.ok-native-list-account-action-row .ok-native-list-action-title[data-tone="primary"]{color:var(--nl-primary)}
540
- /* OneKey patch: account selector wallets and accounts keep the default cursor instead of pointer or grab affordances. */
541
- .ok-native-list-root .ok-native-list-item>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-wallet-member>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-action-row,.ok-native-list-wallet-member,.ok-native-list-wallet-row *,.ok-native-list-account-row *,.ok-native-list-account-action-row *{cursor:default}
540
+ /* OneKey patch: account selector wallets and accounts keep the default cursor instead of pointer or grab affordances.
541
+ Only the row surface is matched: cursor inherits, so plain children follow the row, while icon buttons,
542
+ checkboxes, and action buttons keep advertising themselves with their own pointer cursor. */
543
+ .ok-native-list-root .ok-native-list-item>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-wallet-member>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-action-row,.ok-native-list-wallet-member{cursor:default}
542
544
  /* OneKey patch: Add account hover and pressed backgrounds keep the account row corner radius. */
543
545
  .ok-native-list-account-action-row{border-radius:12px}
544
546
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-native-list",
3
- "version": "3.0.136",
3
+ "version": "3.0.137",
4
4
  "description": "Template-driven native RecyclerView and UICollectionView for React Native",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",
@@ -83,8 +83,8 @@
83
83
  "typescript": "^5.9.2"
84
84
  },
85
85
  "peerDependencies": {
86
- "@onekeyfe/react-native-image": "3.0.136",
87
- "@onekeyfe/react-native-native-logger": "3.0.136",
86
+ "@onekeyfe/react-native-image": "3.0.137",
87
+ "@onekeyfe/react-native-native-logger": "3.0.137",
88
88
  "react": "*",
89
89
  "react-native": "*",
90
90
  "react-native-nitro-modules": "0.37.0"
@@ -965,8 +965,10 @@ export const WEB_LIST_CSS = `
965
965
  .ok-native-list-account-row[data-native-list-selector="accountSelector"]>.ok-native-list-accessories[data-native-list-account-control="createAddress"]{position:absolute;top:18px;right:12px}
966
966
  .ok-native-list-account-row[data-native-list-selector="accountSelector"] .ok-native-list-accessories>[data-native-list-account-control="createAddress"]{flex-basis:36px;width:36px;height:36px;padding:6px;border-radius:8px}
967
967
  .ok-native-list-account-action-row{padding-left:12px;padding-right:12px}.ok-native-list-account-action-row .ok-native-list-action-title{font-size:16px;line-height:24px;font-weight:400}.ok-native-list-account-action-row .ok-native-list-action-title[data-tone="primary"]{color:var(--nl-primary)}
968
- /* OneKey patch: account selector wallets and accounts keep the default cursor instead of pointer or grab affordances. */
969
- .ok-native-list-root .ok-native-list-item>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-wallet-member>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-action-row,.ok-native-list-wallet-member,.ok-native-list-wallet-row *,.ok-native-list-account-row *,.ok-native-list-account-action-row *{cursor:default}
968
+ /* OneKey patch: account selector wallets and accounts keep the default cursor instead of pointer or grab affordances.
969
+ Only the row surface is matched: cursor inherits, so plain children follow the row, while icon buttons,
970
+ checkboxes, and action buttons keep advertising themselves with their own pointer cursor. */
971
+ .ok-native-list-root .ok-native-list-item>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-wallet-member>.ok-native-list-wallet-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-row,.ok-native-list-root .ok-native-list-item>.ok-native-list-account-action-row,.ok-native-list-wallet-member{cursor:default}
970
972
  /* OneKey patch: Add account hover and pressed backgrounds keep the account row corner radius. */
971
973
  .ok-native-list-account-action-row{border-radius:12px}
972
974
  `;