feat(configureNativeFetcher): optional JWT pool for background native fetch
Add jwtTokens / jwtTokenPoolJson to the TypeScript API, parse and validate (max 128) on Android and iOS, persist jwtTokenPool with native_fetcher_config when persistToken is true (Android), and extend NativeNotificationContentFetcher with a four-argument configure overload delegating to the existing three-arg default. iOS stores the pool in UserDefaults JSON and uses primary jwt or first pool entry in the plugin background fetch path. Bump version to 2.2.0. Update TestNativeFetcher to exercise the new configure overload.
This commit is contained in:
@@ -248,17 +248,46 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
call.reject("jwtToken or jwtSecret is required")
|
||||
return
|
||||
}
|
||||
|
||||
let jwtTokenPoolMax = 128
|
||||
var jwtTokenPool: [String]? = nil
|
||||
if let rawTokens = options["jwtTokens"] as? [Any] {
|
||||
let parsed = rawTokens.compactMap { $0 as? String }.filter { !$0.isEmpty }
|
||||
if parsed.count > jwtTokenPoolMax {
|
||||
call.reject("jwtTokens must have at most \(jwtTokenPoolMax) entries")
|
||||
return
|
||||
}
|
||||
jwtTokenPool = parsed.isEmpty ? nil : parsed
|
||||
} else if let jsonStr = options["jwtTokenPoolJson"] as? String, !jsonStr.isEmpty {
|
||||
guard let data = jsonStr.data(using: .utf8),
|
||||
let parsed = try? JSONSerialization.jsonObject(with: data) as? [String] else {
|
||||
call.reject("jwtTokenPoolJson must be a JSON array of strings")
|
||||
return
|
||||
}
|
||||
let filtered = parsed.filter { !$0.isEmpty }
|
||||
if filtered.count > jwtTokenPoolMax {
|
||||
call.reject("jwtTokens must have at most \(jwtTokenPoolMax) entries")
|
||||
return
|
||||
}
|
||||
jwtTokenPool = filtered.isEmpty ? nil : filtered
|
||||
}
|
||||
|
||||
print("DNP-PLUGIN: Configuring native fetcher: apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...")
|
||||
if let pool = jwtTokenPool {
|
||||
print("DNP-PLUGIN: jwtTokenPool size=\(pool.count)")
|
||||
}
|
||||
|
||||
// Store configuration in database for persistence across app restarts
|
||||
// Note: iOS native fetcher interface not yet implemented, but we store config for future use
|
||||
let configId = "native_fetcher_config"
|
||||
let configValue: [String: Any] = [
|
||||
var configValue: [String: Any] = [
|
||||
"apiBaseUrl": apiBaseUrl,
|
||||
"activeDid": activeDid,
|
||||
"jwtToken": jwtToken
|
||||
]
|
||||
if let pool = jwtTokenPool {
|
||||
configValue["jwtTokenPool"] = pool
|
||||
}
|
||||
|
||||
// Convert to JSON string for storage
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: configValue, options: []),
|
||||
@@ -594,32 +623,49 @@ public class DailyNotificationPlugin: CAPPlugin {
|
||||
let configData = configJson.data(using: .utf8),
|
||||
let config = try? JSONSerialization.jsonObject(with: configData) as? [String: Any],
|
||||
let apiBaseUrl = config["apiBaseUrl"] as? String,
|
||||
let activeDid = config["activeDid"] as? String,
|
||||
let jwtToken = config["jwtToken"] as? String {
|
||||
// Phase 3: JWT-signed fetcher is configured - attempt HTTP fetch
|
||||
print("DNP-FETCH: Using JWT-signed fetcher (apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...)")
|
||||
|
||||
// Attempt to fetch content from TimeSafari API
|
||||
// Note: This is a minimal implementation - can be extended with full API client
|
||||
do {
|
||||
let fetchedContent = try await fetchContentFromAPI(
|
||||
apiBaseUrl: apiBaseUrl,
|
||||
activeDid: activeDid,
|
||||
jwtToken: jwtToken
|
||||
)
|
||||
content = fetchedContent
|
||||
print("DNP-FETCH: Successfully fetched content from API")
|
||||
} catch {
|
||||
// Fallback to dummy content on fetch failure
|
||||
print("DNP-FETCH: API fetch failed (\(error.localizedDescription)), using fallback content")
|
||||
let activeDid = config["activeDid"] as? String {
|
||||
let jwtFromPrimary = (config["jwtToken"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
||||
let jwtFromPool = (config["jwtTokenPool"] as? [String])?.first { !$0.isEmpty }
|
||||
let bearerToken = jwtFromPrimary ?? jwtFromPool
|
||||
if let jwtToken = bearerToken {
|
||||
// Phase 3: JWT-signed fetcher is configured - attempt HTTP fetch
|
||||
print("DNP-FETCH: Using JWT-signed fetcher (apiBaseUrl=\(apiBaseUrl), activeDid=\(activeDid.prefix(30))...)")
|
||||
|
||||
// Attempt to fetch content from TimeSafari API
|
||||
// Note: This is a minimal implementation - can be extended with full API client
|
||||
do {
|
||||
let fetchedContent = try await fetchContentFromAPI(
|
||||
apiBaseUrl: apiBaseUrl,
|
||||
activeDid: activeDid,
|
||||
jwtToken: jwtToken
|
||||
)
|
||||
content = fetchedContent
|
||||
print("DNP-FETCH: Successfully fetched content from API")
|
||||
} catch {
|
||||
// Fallback to dummy content on fetch failure
|
||||
print("DNP-FETCH: API fetch failed (\(error.localizedDescription)), using fallback content")
|
||||
content = NotificationContent(
|
||||
id: "fallback_\(Date().timeIntervalSince1970)",
|
||||
title: "Daily Update",
|
||||
body: "Your daily notification is ready",
|
||||
scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000),
|
||||
fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
|
||||
url: nil,
|
||||
payload: ["fetchError": error.localizedDescription],
|
||||
etag: nil
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Config present but no bearer (empty jwtToken and pool)
|
||||
print("DNP-FETCH: Using dummy content (no bearer token)")
|
||||
content = NotificationContent(
|
||||
id: "fallback_\(Date().timeIntervalSince1970)",
|
||||
id: "dummy_\(Date().timeIntervalSince1970)",
|
||||
title: "Daily Update",
|
||||
body: "Your daily notification is ready",
|
||||
scheduledTime: Int64(Date().timeIntervalSince1970 * 1000) + (5 * 60 * 1000),
|
||||
fetchedAt: Int64(Date().timeIntervalSince1970 * 1000),
|
||||
url: nil,
|
||||
payload: ["fetchError": error.localizedDescription],
|
||||
payload: nil,
|
||||
etag: nil
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user