Skip to content

Commit 1d47831

Browse files
feat: appMetadata (#104)
1 parent 0de4477 commit 1d47831

5 files changed

Lines changed: 89 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## 1.9.0 (unreleased)
4+
5+
* Add `appMetadata` parameter to `PowerSyncDatabase.connect()` (via `ConnectOptions`) to include application metadata in sync requests. This metadata is merged into sync requests and displayed in PowerSync service logs.
6+
7+
Note: This requires a PowerSync service version `>=1.17.0` in order for logs to display metadata.
8+
9+
```swift
10+
try await database.connect(
11+
connector: connector,
12+
options: ConnectOptions(
13+
appMetadata: ["appVersion": "1.0.0", "deviceId": "device456"]
14+
)
15+
)
16+
```
17+
318
## 1.8.2
419

520
* Fix `PowerSyncKotlin` frameworks not containing a `CFBundleVersion`.

Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import CSQLite
12
import Foundation
23
import PowerSyncKotlin
3-
import CSQLite
44

55
final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
66
// `PowerSyncKotlin.PowerSyncDatabase` cannot be marked as Sendable
@@ -17,11 +17,11 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
1717
dbFilename: String,
1818
logger: DatabaseLogger
1919
) {
20-
let rc = sqlite3_initialize();
21-
if (rc != 0) {
20+
let rc = sqlite3_initialize()
21+
if rc != 0 {
2222
fatalError("Call to sqlite3_initialize() failed with \(rc)")
2323
}
24-
24+
2525
let factory = sqlite3DatabaseFactory(initialStatements: [])
2626
kotlinDatabase = PowerSyncDatabase(
2727
factory: factory,
@@ -71,7 +71,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
7171
userAgent: "PowerSync Swift SDK",
7272
loggingConfig: resolvedOptions.clientConfiguration?.requestLogger?.toKotlinConfig()
7373
),
74-
appMetadata: [:]
74+
appMetadata: resolvedOptions.appMetadata
7575
)
7676
}
7777

Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ public struct ConnectOptions: Sendable {
5555
/// ```
5656
public var params: JsonParam
5757

58+
/// Application metadata that will be displayed in PowerSync service logs.
59+
///
60+
/// Provide small, non-sensitive key/value pairs (for example: `appName`, `version`, `environment`) to
61+
/// help identify the client in logs and diagnostics. Do not include secrets or tokens.
62+
///
63+
/// Example:
64+
/// ```swift
65+
/// ["appName": "MyApp", "version": "1.2.3"]
66+
/// ```
67+
public var appMetadata: [String: String]
68+
5869
/// Uses a new sync client implemented in Rust instead of the one implemented in Kotlin.
5970
///
6071
/// The new client is more efficient and will become the default in the future, but is still marked as experimental for now.
@@ -85,13 +96,15 @@ public struct ConnectOptions: Sendable {
8596
crudThrottle: TimeInterval = 1,
8697
retryDelay: TimeInterval = 5,
8798
params: JsonParam = [:],
88-
clientConfiguration: SyncClientConfiguration? = nil
99+
clientConfiguration: SyncClientConfiguration? = nil,
100+
appMetadata: [String: String] = [:]
89101
) {
90102
self.crudThrottle = crudThrottle
91103
self.retryDelay = retryDelay
92104
self.params = params
93105
newClientImplementation = true
94106
self.clientConfiguration = clientConfiguration
107+
self.appMetadata = appMetadata
95108
}
96109

97110
/// Initializes a ``ConnectOptions`` instance with optional values, including experimental options.
@@ -105,13 +118,15 @@ public struct ConnectOptions: Sendable {
105118
retryDelay: TimeInterval = 5,
106119
params: JsonParam = [:],
107120
newClientImplementation: Bool = true,
108-
clientConfiguration: SyncClientConfiguration? = nil
121+
clientConfiguration: SyncClientConfiguration? = nil,
122+
appMetadata: [String: String] = [:]
109123
) {
110124
self.crudThrottle = crudThrottle
111125
self.retryDelay = retryDelay
112126
self.params = params
113127
self.newClientImplementation = newClientImplementation
114128
self.clientConfiguration = clientConfiguration
129+
self.appMetadata = appMetadata
115130
}
116131
}
117132

Tests/PowerSyncTests/ConnectTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,57 @@ final class ConnectTests: XCTestCase {
139139
try await database.disconnectAndClear()
140140
}
141141

142+
func testAppMetadata() async throws {
143+
let expectation = XCTestExpectation(
144+
description: "Should log a request to the PowerSync endpoint with metadata"
145+
)
146+
147+
let fakeUrl = "https://fakepowersyncinstance.fakepowersync.local"
148+
let testAppName = "testAppName"
149+
final class TestConnector: PowerSyncBackendConnectorProtocol {
150+
let url: String
151+
152+
init(url: String) {
153+
self.url = url
154+
}
155+
156+
func fetchCredentials() async throws -> PowerSyncCredentials? {
157+
PowerSyncCredentials(
158+
endpoint: url,
159+
token: "123"
160+
)
161+
}
162+
163+
func uploadData(database _: PowerSyncDatabaseProtocol) async throws {}
164+
}
165+
166+
try await database.connect(
167+
connector: TestConnector(url: fakeUrl),
168+
options: ConnectOptions(
169+
/// Note that currently, HTTP logs are only supported with the old client implementation
170+
/// which uses HTTP streams.
171+
/// The new client implementation uses a WebSocket connection instead.
172+
/// Which we don't get logs for currently.
173+
newClientImplementation: false,
174+
clientConfiguration: SyncClientConfiguration(
175+
requestLogger: SyncRequestLoggerConfiguration(
176+
requestLevel: .all
177+
) { message in
178+
// We want to see a request to the specified instance with the app_metadata present
179+
if message.contains("\"app_metadata\":{\"appName\":\"\(testAppName)\"}") {
180+
expectation.fulfill()
181+
}
182+
}
183+
),
184+
appMetadata: ["appName": testAppName]
185+
)
186+
)
187+
188+
await fulfillment(of: [expectation], timeout: 5)
189+
190+
try await database.disconnectAndClear()
191+
}
192+
142193
func testSendableConnect() async throws {
143194
/// This is just a basic sanity check to confirm that these protocols are
144195
/// correctly defined as Sendable.

0 commit comments

Comments
 (0)