@npaw/forge-react-native 10.0.2 → 10.1.0

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.
@@ -28,7 +28,7 @@ def safeExtGet(prop, fallback) {
28
28
  // FORGE_CORE_VERSION (see scripts/react-native/hydrate-natives.sh).
29
29
  def forgeCoreVersion = project.hasProperty('NpawForge_forgeCoreVersion')
30
30
  ? project.property('NpawForge_forgeCoreVersion')
31
- : (System.getenv('FORGE_CORE_VERSION') ?: '10.0.2')
31
+ : (System.getenv('FORGE_CORE_VERSION') ?: '10.1.0')
32
32
 
33
33
  android {
34
34
  namespace 'com.npaw.forge.reactnative'
@@ -28,15 +28,18 @@
28
28
  <key>HeadersPath</key>
29
29
  <string>Headers</string>
30
30
  <key>LibraryIdentifier</key>
31
- <string>tvos-arm64</string>
31
+ <string>ios-arm64_x86_64-simulator</string>
32
32
  <key>LibraryPath</key>
33
33
  <string>libForgeKernelFFI.a</string>
34
34
  <key>SupportedArchitectures</key>
35
35
  <array>
36
36
  <string>arm64</string>
37
+ <string>x86_64</string>
37
38
  </array>
38
39
  <key>SupportedPlatform</key>
39
- <string>tvos</string>
40
+ <string>ios</string>
41
+ <key>SupportedPlatformVariant</key>
42
+ <string>simulator</string>
40
43
  </dict>
41
44
  <dict>
42
45
  <key>BinaryPath</key>
@@ -44,18 +47,15 @@
44
47
  <key>HeadersPath</key>
45
48
  <string>Headers</string>
46
49
  <key>LibraryIdentifier</key>
47
- <string>ios-arm64_x86_64-simulator</string>
50
+ <string>tvos-arm64</string>
48
51
  <key>LibraryPath</key>
49
52
  <string>libForgeKernelFFI.a</string>
50
53
  <key>SupportedArchitectures</key>
51
54
  <array>
52
55
  <string>arm64</string>
53
- <string>x86_64</string>
54
56
  </array>
55
57
  <key>SupportedPlatform</key>
56
- <string>ios</string>
57
- <key>SupportedPlatformVariant</key>
58
- <string>simulator</string>
58
+ <string>tvos</string>
59
59
  </dict>
60
60
  <dict>
61
61
  <key>BinaryPath</key>
@@ -481,6 +481,22 @@ fileprivate struct FfiConverterUInt32: FfiConverterPrimitive {
481
481
  }
482
482
  }
483
483
 
484
+ #if swift(>=5.8)
485
+ @_documentation(visibility: private)
486
+ #endif
487
+ fileprivate struct FfiConverterInt32: FfiConverterPrimitive {
488
+ typealias FfiType = Int32
489
+ typealias SwiftType = Int32
490
+
491
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Int32 {
492
+ return try lift(readInt(&buf))
493
+ }
494
+
495
+ public static func write(_ value: Int32, into buf: inout [UInt8]) {
496
+ writeInt(&buf, lower(value))
497
+ }
498
+ }
499
+
484
500
  #if swift(>=5.8)
485
501
  @_documentation(visibility: private)
486
502
  #endif
@@ -1596,10 +1612,14 @@ public struct AppleDeviceSample: Equatable, Hashable {
1596
1612
  public var batteryCharging: Bool?
1597
1613
  public var thermal: AppleThermalState?
1598
1614
  public var diskFreeBytes: UInt64?
1615
+ public var netSignalDbm: Int32?
1616
+ public var netLinkMbps: UInt32?
1617
+ public var netDownlinkKbps: UInt32?
1618
+ public var netFreqMhz: UInt32?
1599
1619
 
1600
1620
  // Default memberwise initializers are never public by default, so we
1601
1621
  // declare one manually.
1602
- public init(sampledAtMs: UInt64, cpuTimeMs: UInt64?, cpuCores: UInt32?, memUsedBytes: UInt64?, memAvailBytes: UInt64?, batteryLevelPct: UInt8?, batteryCharging: Bool?, thermal: AppleThermalState?, diskFreeBytes: UInt64?) {
1622
+ public init(sampledAtMs: UInt64, cpuTimeMs: UInt64?, cpuCores: UInt32?, memUsedBytes: UInt64?, memAvailBytes: UInt64?, batteryLevelPct: UInt8?, batteryCharging: Bool?, thermal: AppleThermalState?, diskFreeBytes: UInt64?, netSignalDbm: Int32?, netLinkMbps: UInt32?, netDownlinkKbps: UInt32?, netFreqMhz: UInt32?) {
1603
1623
  self.sampledAtMs = sampledAtMs
1604
1624
  self.cpuTimeMs = cpuTimeMs
1605
1625
  self.cpuCores = cpuCores
@@ -1609,6 +1629,10 @@ public struct AppleDeviceSample: Equatable, Hashable {
1609
1629
  self.batteryCharging = batteryCharging
1610
1630
  self.thermal = thermal
1611
1631
  self.diskFreeBytes = diskFreeBytes
1632
+ self.netSignalDbm = netSignalDbm
1633
+ self.netLinkMbps = netLinkMbps
1634
+ self.netDownlinkKbps = netDownlinkKbps
1635
+ self.netFreqMhz = netFreqMhz
1612
1636
  }
1613
1637
 
1614
1638
 
@@ -1635,7 +1659,11 @@ public struct FfiConverterTypeAppleDeviceSample: FfiConverterRustBuffer {
1635
1659
  batteryLevelPct: FfiConverterOptionUInt8.read(from: &buf),
1636
1660
  batteryCharging: FfiConverterOptionBool.read(from: &buf),
1637
1661
  thermal: FfiConverterOptionTypeAppleThermalState.read(from: &buf),
1638
- diskFreeBytes: FfiConverterOptionUInt64.read(from: &buf)
1662
+ diskFreeBytes: FfiConverterOptionUInt64.read(from: &buf),
1663
+ netSignalDbm: FfiConverterOptionInt32.read(from: &buf),
1664
+ netLinkMbps: FfiConverterOptionUInt32.read(from: &buf),
1665
+ netDownlinkKbps: FfiConverterOptionUInt32.read(from: &buf),
1666
+ netFreqMhz: FfiConverterOptionUInt32.read(from: &buf)
1639
1667
  )
1640
1668
  }
1641
1669
 
@@ -1649,6 +1677,10 @@ public struct FfiConverterTypeAppleDeviceSample: FfiConverterRustBuffer {
1649
1677
  FfiConverterOptionBool.write(value.batteryCharging, into: &buf)
1650
1678
  FfiConverterOptionTypeAppleThermalState.write(value.thermal, into: &buf)
1651
1679
  FfiConverterOptionUInt64.write(value.diskFreeBytes, into: &buf)
1680
+ FfiConverterOptionInt32.write(value.netSignalDbm, into: &buf)
1681
+ FfiConverterOptionUInt32.write(value.netLinkMbps, into: &buf)
1682
+ FfiConverterOptionUInt32.write(value.netDownlinkKbps, into: &buf)
1683
+ FfiConverterOptionUInt32.write(value.netFreqMhz, into: &buf)
1652
1684
  }
1653
1685
  }
1654
1686
 
@@ -4099,6 +4131,30 @@ fileprivate struct FfiConverterOptionUInt32: FfiConverterRustBuffer {
4099
4131
  }
4100
4132
  }
4101
4133
 
4134
+ #if swift(>=5.8)
4135
+ @_documentation(visibility: private)
4136
+ #endif
4137
+ fileprivate struct FfiConverterOptionInt32: FfiConverterRustBuffer {
4138
+ typealias SwiftType = Int32?
4139
+
4140
+ public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
4141
+ guard let value = value else {
4142
+ writeInt(&buf, Int8(0))
4143
+ return
4144
+ }
4145
+ writeInt(&buf, Int8(1))
4146
+ FfiConverterInt32.write(value, into: &buf)
4147
+ }
4148
+
4149
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
4150
+ switch try readInt(&buf) as Int8 {
4151
+ case 0: return nil
4152
+ case 1: return try FfiConverterInt32.read(from: &buf)
4153
+ default: throw UniffiInternalError.unexpectedOptionalTag
4154
+ }
4155
+ }
4156
+ }
4157
+
4102
4158
  #if swift(>=5.8)
4103
4159
  @_documentation(visibility: private)
4104
4160
  #endif
@@ -17,10 +17,10 @@ import Foundation
17
17
  import UIKit
18
18
  #endif
19
19
 
20
- /// Thermal pressure on the kernel's normalised ordinal scale (`dev_thermal`).
20
+ /// Thermal pressure on the kernel's normalised ordinal scale (the `thermal` wire ordinal).
21
21
  ///
22
22
  /// The raw values are the wire ordinals shared with Android, so a Suite
23
- /// filter on `dev_thermal=2` means "serious" on every platform.
23
+ /// filter on `thermal=2` means "serious" on every platform.
24
24
  public enum ForgeThermalState: Int, Equatable, CaseIterable {
25
25
  case nominal = 0
26
26
  case fair = 1
@@ -52,14 +52,14 @@ public enum ForgeThermalState: Int, Equatable, CaseIterable {
52
52
  /// cannot read is left `nil`, and the kernel then omits the metric instead of
53
53
  /// inventing `0`. The kernel validates field-wise (a battery level above 100,
54
54
  /// zero cores or a CPU counter that went backwards is dropped, the rest kept),
55
- /// derives `dev_cpu` between two consecutive samples, and attaches the set to
56
- /// the next session beat and video ping.
55
+ /// derives the CPU rate between two consecutive samples, and emits each
56
+ /// accepted sample as a `device_status` session event.
57
57
  public struct ForgeDeviceSample: Equatable {
58
58
  /// Wall clock at read time, ms since the Unix epoch. `0` lets the bridge
59
59
  /// stamp the call with its own clock.
60
60
  public var sampledAtMs: Int64
61
61
  /// Cumulative process CPU time, user + system, ms, monotonic over the
62
- /// process lifetime. Must travel together with ``cpuCores`` for `dev_cpu`.
62
+ /// process lifetime. Must travel together with ``cpuCores`` for the derived CPU rate.
63
63
  public var cpuTimeMs: UInt64?
64
64
  /// Logical cores available to the process, `>= 1`.
65
65
  public var cpuCores: UInt32?
@@ -79,6 +79,17 @@ public struct ForgeDeviceSample: Equatable {
79
79
  /// declare the reason can supply the value itself through
80
80
  /// `NPAWForge.recordDeviceSample(_:)`.
81
81
  public var diskFreeBytes: UInt64?
82
+ /// WiFi signal strength, dBm (negative). **Never filled by Forge on
83
+ /// Apple**: no public RSSI API exists without special entitlements
84
+ /// (`NEHotspotNetwork` needs the hotspot entitlement plus Location;
85
+ /// `CoreWLAN` is macOS-only). Hosts with those grants can supply it.
86
+ public var netSignalDbm: Int32?
87
+ /// Negotiated WiFi PHY link rate, Mbps. Never filled by Forge on Apple.
88
+ public var netLinkMbps: UInt32?
89
+ /// Downstream-bandwidth estimate, kbps. Never filled by Forge on Apple.
90
+ public var netDownlinkKbps: UInt32?
91
+ /// WiFi channel frequency, MHz. Never filled by Forge on Apple.
92
+ public var netFreqMhz: UInt32?
82
93
 
83
94
  public init(
84
95
  sampledAtMs: Int64 = 0,
@@ -89,7 +100,11 @@ public struct ForgeDeviceSample: Equatable {
89
100
  batteryLevelPct: UInt8? = nil,
90
101
  batteryCharging: Bool? = nil,
91
102
  thermal: ForgeThermalState? = nil,
92
- diskFreeBytes: UInt64? = nil
103
+ diskFreeBytes: UInt64? = nil,
104
+ netSignalDbm: Int32? = nil,
105
+ netLinkMbps: UInt32? = nil,
106
+ netDownlinkKbps: UInt32? = nil,
107
+ netFreqMhz: UInt32? = nil
93
108
  ) {
94
109
  self.sampledAtMs = sampledAtMs
95
110
  self.cpuTimeMs = cpuTimeMs
@@ -100,6 +115,10 @@ public struct ForgeDeviceSample: Equatable {
100
115
  self.batteryCharging = batteryCharging
101
116
  self.thermal = thermal
102
117
  self.diskFreeBytes = diskFreeBytes
118
+ self.netSignalDbm = netSignalDbm
119
+ self.netLinkMbps = netLinkMbps
120
+ self.netDownlinkKbps = netDownlinkKbps
121
+ self.netFreqMhz = netFreqMhz
103
122
  }
104
123
 
105
124
  /// `true` when no metric field is present (only the timestamp).
@@ -108,6 +127,8 @@ public struct ForgeDeviceSample: Equatable {
108
127
  && memUsedBytes == nil && memAvailBytes == nil
109
128
  && batteryLevelPct == nil && batteryCharging == nil
110
129
  && thermal == nil && diskFreeBytes == nil
130
+ && netSignalDbm == nil && netLinkMbps == nil
131
+ && netDownlinkKbps == nil && netFreqMhz == nil
111
132
  }
112
133
  }
113
134
 
@@ -119,6 +140,7 @@ public enum ForgeDeviceMetricFamily: String, CaseIterable, Equatable {
119
140
  case bat
120
141
  case thm
121
142
  case disk
143
+ case net
122
144
  }
123
145
 
124
146
  /// A battery reading exactly as the platform reports it, before shaping.
@@ -277,7 +299,7 @@ final class ForgeDeviceInsightsCollector {
277
299
  var sample = ForgeDeviceSample(sampledAtMs: readers.nowMs())
278
300
  if requested.contains(.cpu) {
279
301
  // The two travel together or not at all: the kernel needs both to
280
- // derive `dev_cpu`, and half a pair is a value nobody can use.
302
+ // derive the CPU rate, and half a pair is a value nobody can use.
281
303
  if let cpuTimeMs = readers.cpuTimeMs(), let cpuCores = readers.cpuCores() {
282
304
  sample.cpuTimeMs = cpuTimeMs
283
305
  sample.cpuCores = cpuCores
@@ -296,6 +318,10 @@ final class ForgeDeviceInsightsCollector {
296
318
  sample.batteryCharging = fields.charging
297
319
  }
298
320
  // `disk` is deliberately never read: see ForgeDeviceSample.diskFreeBytes.
321
+ // `net` is deliberately never read either: no public RSSI/link/
322
+ // bandwidth API exists on Apple without entitlements — see
323
+ // ForgeDeviceSample.netSignalDbm. Host-supplied samples pass the
324
+ // fields through.
299
325
 
300
326
  lock.lock()
301
327
  let isActive = active
@@ -78,8 +78,8 @@ public protocol ForgeRuntime: AnyObject {
78
78
  /// so the three platforms cannot drift into different definitions of moving.
79
79
  func recordLocationFix(_ fix: ForgeLocationFix) -> ForgeOutcome
80
80
  /// Forwards a device runtime health sample. Every field is optional; the
81
- /// kernel validates field-wise, derives `dev_cpu` between samples and
82
- /// attaches the set to the next session beat and video ping. Refused
81
+ /// kernel validates field-wise, derives the CPU rate between samples and
82
+ /// emits the sample as a `device_status` session event. Refused
83
83
  /// (`ignoredInvalidState`) while device insights are off remotely.
84
84
  func recordDeviceSample(_ sample: ForgeDeviceSample) -> ForgeOutcome
85
85
  /// Registers (or clears with `nil`) the receiver of the kernel's device
@@ -40,7 +40,7 @@ public final class NPAWForge: NSObject {
40
40
  /// `scripts/apple/publish-local-shipped.sh` rewrites this literal to the
41
41
  /// release version when staging the shipped package, and fails loudly if
42
42
  /// it cannot find it. Keep it a plain stored literal for that reason.
43
- public static let defaultPluginVersion = "10.0.2"
43
+ public static let defaultPluginVersion = "10.1.0"
44
44
  public static let defaultAppName = "apple"
45
45
  public static let defaultBuildVariant = "release"
46
46
  public static let defaultLmaEndpoint = "https://lma.npaw.com"
@@ -606,7 +606,11 @@ private func appleDeviceSample(_ sample: ForgeDeviceSample) -> AppleDeviceSample
606
606
  batteryLevelPct: sample.batteryLevelPct,
607
607
  batteryCharging: sample.batteryCharging,
608
608
  thermal: sample.thermal.map(appleThermalState),
609
- diskFreeBytes: sample.diskFreeBytes
609
+ diskFreeBytes: sample.diskFreeBytes,
610
+ netSignalDbm: sample.netSignalDbm,
611
+ netLinkMbps: sample.netLinkMbps,
612
+ netDownlinkKbps: sample.netDownlinkKbps,
613
+ netFreqMhz: sample.netFreqMhz
610
614
  )
611
615
  }
612
616
 
@@ -96,7 +96,15 @@ public struct NetworkRequestRecord: Equatable {
96
96
  copy.targetUrl = configuration.sanitizeUrl(targetUrl)
97
97
  copy.requestQuery = configuration.filterMap(requestQuery, allowlist: configuration.queryAllowlist)
98
98
  copy.requestHeaders = configuration.filterMap(requestHeaders, allowlist: configuration.requestHeaderAllowlist)
99
- copy.responseHeaders = configuration.filterMap(responseHeaders, allowlist: configuration.responseHeaderAllowlist)
99
+ // The cache-stat headers ride beside the host allowlist so the
100
+ // kernel's network_stats cache counters can classify; their values
101
+ // reach the raw-event wire only when the remote `nc.sh` allowlist
102
+ // names them.
103
+ copy.responseHeaders = configuration.filterMap(
104
+ responseHeaders,
105
+ allowlist: configuration.responseHeaderAllowlist
106
+ .union(NetworkPrivacyConfiguration.cacheStatsResponseHeaders)
107
+ )
100
108
  copy.errorMessage = errorMessage.map(configuration.sanitizeText)
101
109
  return copy
102
110
  }
@@ -123,6 +131,19 @@ public struct NetworkPrivacyConfiguration: Equatable {
123
131
  self.maxStringLength = max(1, maxStringLength)
124
132
  }
125
133
 
134
+ /// Response headers the kernel's cache hit/miss statistics classify
135
+ /// (`network_stats` cacheHits/cacheMisses per tier). Always kept beside
136
+ /// `responseHeaderAllowlist`; values carry CDN node hostnames, never
137
+ /// user data, and hit the raw-event wire only when the remote `nc.sh`
138
+ /// allowlist names them.
139
+ public static let cacheStatsResponseHeaders: Set<String> = [
140
+ "cache-status",
141
+ "cf-cache-status",
142
+ "x-cache-status",
143
+ "x-cache",
144
+ "x-cache-remote",
145
+ ]
146
+
126
147
  public static let defaultRedactionKeys: Set<String> = [
127
148
  "authorization",
128
149
  "proxy-authorization",
@@ -125,6 +125,23 @@ static void NPAWEmit(NSURLRequest *request, NSURLResponse *response, NSData *dat
125
125
  if (http.MIMEType.length > 0) {
126
126
  payload[@"responseHeader.Content-Type"] = http.MIMEType;
127
127
  }
128
+ // Cache-stat headers for the kernel's network_stats hit/miss counters.
129
+ // -valueForHTTPHeaderField: is case-insensitive and comma-joins repeated
130
+ // lines, so multi-hop X-Cache chains arrive whole. The values only reach
131
+ // the raw-event wire when the remote allowlist names them.
132
+ NSArray<NSString *> *cacheHeaderNames = @[
133
+ @"Cache-Status",
134
+ @"CF-Cache-Status",
135
+ @"X-Cache-Status",
136
+ @"X-Cache",
137
+ @"X-Cache-Remote",
138
+ ];
139
+ for (NSString *headerName in cacheHeaderNames) {
140
+ NSString *headerValue = [http valueForHTTPHeaderField:headerName];
141
+ if (headerValue.length > 0) {
142
+ payload[[@"responseHeader." stringByAppendingString:headerName]] = headerValue;
143
+ }
144
+ }
128
145
  callback(payload);
129
146
  }
130
147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npaw/forge-react-native",
3
- "version": "10.0.2",
3
+ "version": "10.1.0",
4
4
  "description": "NPAW Forge analytics for React Native: New Architecture TurboModule facade over the native Forge Android/Apple SDKs.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Victor Soto <victor.soto@nicepeopleatwork.com>",