expo-observe 0.1.3 → 0.1.5

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.
@@ -46,6 +46,8 @@ android {
46
46
  defaultConfig {
47
47
  versionCode 1
48
48
  versionName packageJson.version
49
+
50
+ buildConfigField "String", "EXPO_OBSERVE_VERSION", "\"${packageJson.version}\""
49
51
  }
50
52
  }
51
53
 
@@ -54,6 +54,7 @@ data class EASMetric(
54
54
  val name: String,
55
55
  val value: Double,
56
56
  val routeName: String? = null,
57
+ val updateId: String? = null,
57
58
  val customParams: JsonObject? = null
58
59
  ) {
59
60
  companion object {
@@ -65,6 +66,7 @@ data class EASMetric(
65
66
  name = metric.name,
66
67
  value = metric.value,
67
68
  routeName = metric.routeName,
69
+ updateId = metric.updateId,
68
70
  // TODO(@lukmccall): Consider using `org.json.JSONObject` instead of kotlinx.serialization. Also, we're not handling exceptions that might be thrown here.
69
71
  customParams = metric.params?.let { Json.decodeFromString<JsonObject>(it) }
70
72
  )
@@ -1,7 +1,6 @@
1
1
  package expo.modules.observe
2
2
 
3
3
  import expo.modules.appmetrics.utils.TimeUtils.timestampToDateNS
4
- import java.util.UUID
5
4
  import kotlinx.serialization.Serializable
6
5
  import kotlinx.serialization.json.Json
7
6
 
@@ -93,6 +92,9 @@ private val metricNameMap = mapOf(
93
92
  // Legacy metrics - will be removed in a future release
94
93
  "loadTime" to "expo.app_startup.load_time",
95
94
  "launchTime" to "expo.app_startup.launch_time",
95
+
96
+ // Updates
97
+ "updateDownloadTime" to "expo.updates.download_time",
96
98
  )
97
99
 
98
100
  fun EASMetric.toOTMetric(): OTMetric {
@@ -102,6 +104,9 @@ fun EASMetric.toOTMetric(): OTMetric {
102
104
  routeName?.let {
103
105
  attributes.add(OTAttribute.of(key = "expo.route_name", rawValue = it))
104
106
  }
107
+ updateId?.let {
108
+ attributes.add(OTAttribute.of(key = "expo.update_id", rawValue = it))
109
+ }
105
110
  customParams?.let {
106
111
  attributes.add(OTAttribute.of(key = "expo.custom_params", rawValue = it.toString()))
107
112
  }
@@ -122,29 +127,56 @@ fun EASMetric.toOTMetric(): OTMetric {
122
127
  }
123
128
 
124
129
  fun Event.toOTMetadata(easClientId: String): OTMetadata {
125
- return OTMetadata(
126
- attributes = listOf(
127
- OTAttribute.of("service.name", metadata.appIdentifier),
128
- OTAttribute.of("service.version", metadata.appVersion ?: ""),
129
- OTAttribute.of("os.type", "linux"),
130
- OTAttribute.of("os.name", metadata.deviceOs ?: ""),
131
- OTAttribute.of("os.version", metadata.deviceOsVersion ?: ""),
132
- OTAttribute.of("device.model.name", metadata.deviceName ?: ""),
133
- OTAttribute.of("device.model.identifier", metadata.deviceModel ?: ""),
134
- OTAttribute.of("browser.language", metadata.languageTag ?: ""),
135
- OTAttribute.of("telemetry.sdk.name", "expo-observe"),
136
- OTAttribute.of("telemetry.sdk.version", metadata.clientVersion ?: ""),
137
- OTAttribute.of("telemetry.sdk.language", "kotlin"),
138
- OTAttribute.of("expo.app.name", metadata.appName ?: ""),
139
- OTAttribute.of("expo.app.build_number", metadata.appBuildNumber ?: ""),
140
- OTAttribute.of("expo.app.update_id", metadata.appUpdateId ?: ""),
141
- OTAttribute.of("expo.sdk.version", metadata.expoSdkVersion),
142
- OTAttribute.of("expo.environment", metadata.environment ?: ""),
143
- OTAttribute.of("expo.react_native.version", metadata.reactNativeVersion),
144
- OTAttribute.of("expo.eas_client.id", easClientId),
145
- OTAttribute.of("expo.eas_build.id", metadata.appEasBuildId ?: "")
146
- )
130
+ val attributes = mutableListOf(
131
+ OTAttribute.of("service.name", metadata.appIdentifier),
132
+ OTAttribute.of("os.type", "linux"),
133
+ OTAttribute.of("telemetry.sdk.name", "expo-observe"),
134
+ OTAttribute.of("telemetry.sdk.language", "kotlin"),
135
+ OTAttribute.of("expo.sdk.version", metadata.expoSdkVersion),
136
+ OTAttribute.of("expo.react_native.version", metadata.reactNativeVersion),
137
+ OTAttribute.of("expo.eas_client.id", easClientId),
147
138
  )
139
+
140
+ // Send optional attributes only if they are set.
141
+ // Their defaults should be defined by the backend.
142
+ metadata.appVersion?.let {
143
+ attributes.add(OTAttribute.of("service.version", it))
144
+ }
145
+ metadata.deviceOs?.let {
146
+ attributes.add(OTAttribute.of("os.name", it))
147
+ }
148
+ metadata.deviceOsVersion?.let {
149
+ attributes.add(OTAttribute.of("os.version", it))
150
+ }
151
+ metadata.deviceName?.let {
152
+ attributes.add(OTAttribute.of("device.model.name", it))
153
+ }
154
+ metadata.deviceModel?.let {
155
+ attributes.add(OTAttribute.of("device.model.identifier", it))
156
+ }
157
+ metadata.languageTag?.let {
158
+ attributes.add(OTAttribute.of("browser.language", it))
159
+ }
160
+ metadata.clientVersion?.let {
161
+ attributes.add(OTAttribute.of("telemetry.sdk.version", it))
162
+ }
163
+ metadata.appName?.let {
164
+ attributes.add(OTAttribute.of("expo.app.name", it))
165
+ }
166
+ metadata.appBuildNumber?.let {
167
+ attributes.add(OTAttribute.of("expo.app.build_number", it))
168
+ }
169
+ metadata.appUpdateId?.let {
170
+ attributes.add(OTAttribute.of("expo.app.update_id", it))
171
+ }
172
+ metadata.environment?.let {
173
+ attributes.add(OTAttribute.of("expo.environment", it))
174
+ }
175
+ metadata.appEasBuildId?.let {
176
+ attributes.add(OTAttribute.of("expo.eas_build.id", it))
177
+ }
178
+
179
+ return OTMetadata(attributes = attributes)
148
180
  }
149
181
 
150
182
  fun Event.toOTEvent(easClientId: String): OTEvent {
@@ -152,7 +184,7 @@ fun Event.toOTEvent(easClientId: String): OTEvent {
152
184
  resource = toOTMetadata(easClientId),
153
185
  scopeMetrics = listOf(
154
186
  OTScopeMetrics(
155
- scope = OTScope(name = "expo-observe", version = metadata.clientVersion ?: ""),
187
+ scope = OTScope(name = "expo-observe", version = BuildConfig.EXPO_OBSERVE_VERSION),
156
188
  metrics = metrics.map { it.toOTMetric() }
157
189
  )
158
190
  )
package/ios/Event.swift CHANGED
@@ -46,6 +46,7 @@ struct Event: Codable, Sendable {
46
46
 
47
47
  // Metadata
48
48
  let routeName: String?
49
+ let updateId: String?
49
50
  let customParams: AnyCodable?
50
51
  }
51
52
 
@@ -60,15 +61,15 @@ struct Event: Codable, Sendable {
60
61
  appVersion: app.appVersion,
61
62
  appBuildNumber: app.buildNumber,
62
63
  appUpdateId: app.updateId,
63
- appEasBuildId: app.easBuildId == "" ? nil : app.easBuildId,
64
+ appEasBuildId: app.easBuildId,
64
65
 
65
66
  deviceName: device.modelName,
66
67
  deviceModel: device.modelIdentifier,
67
68
  deviceOs: device.systemName,
68
69
  deviceOsVersion: device.systemVersion,
69
70
 
70
- reactNativeVersion: ObserveVersions.reactNativeVersion,
71
- expoSdkVersion: ObserveVersions.expoSdkVersion,
71
+ reactNativeVersion: app.reactNativeVersion,
72
+ expoSdkVersion: app.expoSdkVersion,
72
73
  clientVersion: ObserveVersions.clientVersion,
73
74
 
74
75
  languageTag: Locale.preferredLanguages.first ?? "en-US",
@@ -84,6 +85,7 @@ struct Event: Codable, Sendable {
84
85
  sessionId: session.id,
85
86
  parentSessionId: nil,
86
87
  routeName: metric.routeName,
88
+ updateId: metric.updateId,
87
89
  customParams: metric.params
88
90
  )
89
91
  }
@@ -2,10 +2,6 @@ require 'json'
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
4
 
5
- reactNativeVersion = begin `node --print "require('react-native/package.json').version"`.strip rescue '0.0.0' end
6
- expoSdkVersion = begin `node --print "require('expo/package.json').version"`.strip rescue '0.0.0' end
7
- easBuildId = ENV.has_key?('EAS_BUILD_ID') ? ENV['EAS_BUILD_ID'] : nil
8
-
9
5
  Pod::Spec.new do |s|
10
6
  s.name = 'ExpoObserve'
11
7
  s.version = package['version']
@@ -32,10 +28,7 @@ Pod::Spec.new do |s|
32
28
  'DEFINES_MODULE' => 'YES',
33
29
  'SWIFT_COMPILATION_MODE' => 'wholemodule',
34
30
  'GCC_PREPROCESSOR_DEFINITIONS' => [
35
- "REACT_NATIVE_VERSION=\"#{reactNativeVersion}\"",
36
- "EXPO_SDK_VERSION=\"#{expoSdkVersion}\"",
37
- "EXPO_OBSERVE_VERSION=\"#{package['version']}\"",
38
- easBuildId ? "EXPO_OBSERVE_BUILD_ID=\"#{easBuildId}\"" : nil
31
+ "EXPO_OBSERVE_VERSION=\"#{package['version']}\""
39
32
  ].compact
40
33
  }
41
34
 
@@ -4,9 +4,6 @@
4
4
 
5
5
  @interface ObserveVersions : NSObject
6
6
 
7
- @property (class, readonly, nonnull) NSString *reactNativeVersion;
8
- @property (class, readonly, nonnull) NSString *expoSdkVersion;
9
7
  @property (class, readonly, nonnull) NSString *clientVersion;
10
- @property (class, readonly, nullable) NSString *easBuildId;
11
8
 
12
9
  @end
@@ -5,35 +5,13 @@
5
5
  #define STRINGIZE(x) #x
6
6
  #define STRINGIZE2(x) STRINGIZE(x)
7
7
 
8
- #define REACT_NATIVE_VERSION_STRING STRINGIZE2(REACT_NATIVE_VERSION)
9
- #define EXPO_SDK_VERSION_STRING STRINGIZE2(EXPO_SDK_VERSION)
10
8
  #define EXPO_OBSERVE_VERSION_STRING STRINGIZE2(EXPO_OBSERVE_VERSION)
11
- #define EXPO_OBSERVE_BUILD_ID_STRING STRINGIZE2(EXPO_OBSERVE_BUILD_ID)
12
9
 
13
10
  @implementation ObserveVersions
14
11
 
15
- + (NSString *)reactNativeVersion
16
- {
17
- return @REACT_NATIVE_VERSION_STRING;
18
- }
19
-
20
- + (NSString *)expoSdkVersion
21
- {
22
- return @EXPO_SDK_VERSION_STRING;
23
- }
24
-
25
12
  + (NSString *)clientVersion
26
13
  {
27
14
  return @EXPO_OBSERVE_VERSION_STRING;
28
15
  }
29
16
 
30
- + (NSString *)easBuildId
31
- {
32
- #ifdef EXPO_OBSERVE_BUILD_ID
33
- return @EXPO_OBSERVE_BUILD_ID_STRING;
34
- #else
35
- return nil;
36
- #endif
37
- }
38
-
39
17
  @end
@@ -1,4 +1,5 @@
1
1
  import Foundation
2
+ import ExpoAppMetrics
2
3
 
3
4
  // MARK: -- Open Telemetry data classes
4
5
 
@@ -65,6 +66,9 @@ let metricNameMap = [
65
66
  // Legacy metrics - will be removed in a future release
66
67
  "loadTime": "expo.app_startup.load_time",
67
68
  "launchTime": "expo.app_startup.launch_time",
69
+
70
+ // Updates
71
+ "updateDownloadTime": "expo.updates.download_time"
68
72
  ]
69
73
 
70
74
  nonisolated(unsafe) let formatter = ISO8601DateFormatter()
@@ -85,6 +89,9 @@ extension Event.Metric {
85
89
  if let routeName {
86
90
  attributes.append(OTAttribute(key: "expo.route_name", rawValue: routeName))
87
91
  }
92
+ if let updateId {
93
+ attributes.append(OTAttribute(key: "expo.update_id", rawValue: updateId))
94
+ }
88
95
  if let customParamsString = try? customParams?.toJSONString() {
89
96
  attributes.append(OTAttribute(key: "expo.custom_params", rawValue: customParamsString))
90
97
  }
@@ -105,9 +112,7 @@ extension Event.Metric {
105
112
 
106
113
  extension Event {
107
114
  func toOTMetadata(_ easClientId: String) -> OTMetadata {
108
- OTMetadata(attributes: [
109
- OTAttribute(key: "service.name", rawValue: Bundle.main.bundleIdentifier ?? ""),
110
- OTAttribute(key: "service.version", rawValue: metadata.appVersion ?? ""),
115
+ var attributes: [OTAttribute] = [
111
116
  OTAttribute(key: "os.type", rawValue: "darwin"),
112
117
  OTAttribute(key: "os.name", rawValue: metadata.deviceOs),
113
118
  OTAttribute(key: "os.version", rawValue: metadata.deviceOsVersion),
@@ -117,15 +122,35 @@ extension Event {
117
122
  OTAttribute(key: "telemetry.sdk.name", rawValue: "expo-observe"),
118
123
  OTAttribute(key: "telemetry.sdk.version", rawValue: ObserveVersions.clientVersion),
119
124
  OTAttribute(key: "telemetry.sdk.language", rawValue: "swift"),
120
- OTAttribute(key: "expo.app.name", rawValue: metadata.appName ?? ""),
121
- OTAttribute(key: "expo.app.build_number", rawValue: metadata.appBuildNumber ?? ""),
122
- OTAttribute(key: "expo.app.update_id", rawValue: metadata.appUpdateId ?? ""),
123
125
  OTAttribute(key: "expo.sdk.version", rawValue: metadata.expoSdkVersion),
124
- OTAttribute(key: "expo.environment", rawValue: metadata.environment ?? ""),
125
126
  OTAttribute(key: "expo.react_native.version", rawValue: metadata.reactNativeVersion),
126
127
  OTAttribute(key: "expo.eas_client.id", rawValue: easClientId),
127
- OTAttribute(key: "expo.eas_build.id", rawValue: metadata.appEasBuildId ?? ""),
128
- ])
128
+ ]
129
+
130
+ // Send optional attributes only if they are set.
131
+ // Their defaults should be defined by the backend.
132
+ if let appIdentifier = metadata.appIdentifier {
133
+ attributes.append(OTAttribute(key: "service.name", rawValue: appIdentifier))
134
+ }
135
+ if let appVersion = metadata.appVersion {
136
+ attributes.append(OTAttribute(key: "service.version", rawValue: appVersion))
137
+ }
138
+ if let appName = metadata.appName {
139
+ attributes.append(OTAttribute(key: "expo.app.name", rawValue: appName))
140
+ }
141
+ if let appBuildNumber = metadata.appBuildNumber {
142
+ attributes.append(OTAttribute(key: "expo.app.build_number", rawValue: appBuildNumber))
143
+ }
144
+ if let appUpdateId = metadata.appUpdateId {
145
+ attributes.append(OTAttribute(key: "expo.app.update_id", rawValue: appUpdateId))
146
+ }
147
+ if let environment = metadata.environment {
148
+ attributes.append(OTAttribute(key: "expo.environment", rawValue: environment))
149
+ }
150
+ if let appEasBuildId = metadata.appEasBuildId {
151
+ attributes.append(OTAttribute(key: "expo.eas_build.id", rawValue: appEasBuildId))
152
+ }
153
+ return OTMetadata(attributes: attributes)
129
154
  }
130
155
 
131
156
  func toOTEvent(_ easClientId: String) -> OTEvent {
@@ -36,6 +36,7 @@ struct OpenTelemetryTests {
36
36
  sessionId: testSessionId,
37
37
  parentSessionId: nil,
38
38
  routeName: nil,
39
+ updateId: nil,
39
40
  customParams: nil
40
41
  )
41
42
  }
@@ -136,7 +137,7 @@ struct OpenTelemetryTests {
136
137
  #expect(attrs["expo.sdk.version"] == "55.0.0")
137
138
  #expect(attrs["expo.react_native.version"] == "0.83.1")
138
139
  #expect(attrs["expo.eas_client.id"] == testEasClientId)
139
- #expect(attrs["expo.eas_build.id"] == "")
140
+ #expect(attrs["expo.eas_build.id"] == nil)
140
141
  }
141
142
 
142
143
  // MARK: - Full OTEvent
@@ -235,6 +236,7 @@ struct OpenTelemetryTests {
235
236
  sessionId: testSessionId,
236
237
  parentSessionId: nil,
237
238
  routeName: "/home",
239
+ updateId: nil,
238
240
  customParams: nil
239
241
  )
240
242
  let otMetric = metric.toOTMetric()
@@ -243,6 +245,36 @@ struct OpenTelemetryTests {
243
245
  #expect(attrs["expo.route_name"] == "/home")
244
246
  }
245
247
 
248
+ @Test
249
+ func `toOTMetric includes update id attribute when present`() {
250
+ let metric = Event.Metric(
251
+ category: "updates",
252
+ name: "updateDownloadTime",
253
+ value: 2.5,
254
+ timestamp: "2026-01-01T00:00:00Z",
255
+ sessionId: testSessionId,
256
+ parentSessionId: nil,
257
+ routeName: nil,
258
+ updateId: "abc123-def456",
259
+ customParams: nil
260
+ )
261
+ let otMetric = metric.toOTMetric()
262
+ let attrs = Dictionary(uniqueKeysWithValues: otMetric.gauge.dataPoints[0].attributes.map { ($0.key, $0.value.stringValue) })
263
+
264
+ #expect(otMetric.name == "expo.updates.download_time")
265
+ #expect(attrs["expo.update_id"] == "abc123-def456")
266
+ #expect(attrs["session.id"] == testSessionId)
267
+ }
268
+
269
+ @Test
270
+ func `toOTMetric excludes update id attribute when nil`() {
271
+ let metric = makeMetric(name: "bundleLoadTime", value: 1.0, timestamp: "2026-01-01T00:00:00Z")
272
+ let otMetric = metric.toOTMetric()
273
+ let keys = otMetric.gauge.dataPoints[0].attributes.map { $0.key }
274
+
275
+ #expect(keys.contains("expo.update_id") == false)
276
+ }
277
+
246
278
  @Test
247
279
  func `toOTMetric excludes route name attribute when nil`() {
248
280
  let metric = makeMetric(name: "bundleLoadTime", value: 1.0, timestamp: "2026-01-01T00:00:00Z")
@@ -262,6 +294,7 @@ struct OpenTelemetryTests {
262
294
  sessionId: testSessionId,
263
295
  parentSessionId: nil,
264
296
  routeName: nil,
297
+ updateId: nil,
265
298
  customParams: AnyCodable(["screen": "dashboard", "variant": "A"] as [String: Any])
266
299
  )
267
300
  let otMetric = metric.toOTMetric()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-observe",
3
3
  "title": "Expo Observe",
4
- "version": "0.1.3",
4
+ "version": "0.1.5",
5
5
  "description": "Expo module that dispatches collected app metrics to EAS Observe",
6
6
  "main": "src/index.ts",
7
7
  "types": "build/index.d.ts",
@@ -33,7 +33,7 @@
33
33
  "author": "650 Industries, Inc.",
34
34
  "license": "MIT",
35
35
  "dependencies": {
36
- "expo-app-metrics": "~0.1.3",
36
+ "expo-app-metrics": "~0.1.5",
37
37
  "expo-eas-client": "~55.0.3"
38
38
  },
39
39
  "devDependencies": {