Skip to content

Commit 3a9b188

Browse files
committed
feat: support RN app migration
1 parent 4a27a88 commit 3a9b188

File tree

8 files changed

+1224
-75
lines changed

8 files changed

+1224
-75
lines changed

Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/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.

Bitkit/AppScene.swift

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct AppScene: View {
2323
@StateObject private var tagManager = TagManager()
2424
@StateObject private var transferTracking: TransferTrackingManager
2525
@StateObject private var channelDetails = ChannelDetailsViewModel.shared
26+
@StateObject private var migrations = MigrationsService.shared
2627

2728
@State private var hideSplash = false
2829
@State private var removeSplash = false
@@ -72,6 +73,9 @@ struct AppScene: View {
7273
.onChange(of: wallet.walletExists, perform: handleWalletExistsChange)
7374
.onChange(of: wallet.nodeLifecycleState, perform: handleNodeLifecycleChange)
7475
.onChange(of: scenePhase, perform: handleScenePhaseChange)
76+
.onChange(of: migrations.isShowingMigrationLoading) { isLoading in
77+
if !isLoading { widgets.loadSavedWidgets() }
78+
}
7579
.environmentObject(app)
7680
.environmentObject(navigation)
7781
.environmentObject(network)
@@ -111,7 +115,9 @@ struct AppScene: View {
111115
@ViewBuilder
112116
private var mainContent: some View {
113117
ZStack {
114-
if showRecoveryScreen {
118+
if migrations.isShowingMigrationLoading {
119+
migrationLoadingContent
120+
} else if showRecoveryScreen {
115121
RecoveryRouter()
116122
.accentColor(.white)
117123
} else if hasCriticalUpdate {
@@ -127,6 +133,32 @@ struct AppScene: View {
127133
}
128134
}
129135

136+
@ViewBuilder
137+
private var migrationLoadingContent: some View {
138+
VStack(spacing: 24) {
139+
Spacer()
140+
141+
ProgressView()
142+
.scaleEffect(1.5)
143+
.tint(.white)
144+
145+
VStack(spacing: 8) {
146+
Text("Updating Wallet")
147+
.font(.system(size: 24, weight: .semibold))
148+
.foregroundColor(.white)
149+
150+
Text("Please wait while we update the app...")
151+
.font(.system(size: 16))
152+
.foregroundColor(.white.opacity(0.7))
153+
.multilineTextAlignment(.center)
154+
}
155+
156+
Spacer()
157+
}
158+
.frame(maxWidth: .infinity, maxHeight: .infinity)
159+
.background(Color.black)
160+
}
161+
130162
@ViewBuilder
131163
private var walletContent: some View {
132164
if wallet.walletExists == true {
@@ -247,6 +279,7 @@ struct AppScene: View {
247279
@Sendable
248280
private func setupTask() async {
249281
do {
282+
await checkAndPerformRNMigration()
250283
try wallet.setWalletExistsState()
251284

252285
// Setup TimedSheetManager with all timed sheets
@@ -262,6 +295,43 @@ struct AppScene: View {
262295
}
263296
}
264297

298+
private func checkAndPerformRNMigration() async {
299+
let migrations = MigrationsService.shared
300+
301+
guard !migrations.isMigrationChecked else {
302+
Logger.debug("RN migration already checked, skipping", context: "AppScene")
303+
return
304+
}
305+
306+
guard !migrations.hasNativeWalletData() else {
307+
Logger.info("Native wallet data exists, skipping RN migration", context: "AppScene")
308+
migrations.markMigrationChecked()
309+
return
310+
}
311+
312+
guard migrations.hasRNWalletData() else {
313+
Logger.info("No RN wallet data found, skipping migration", context: "AppScene")
314+
migrations.markMigrationChecked()
315+
return
316+
}
317+
318+
await MainActor.run { migrations.isShowingMigrationLoading = true }
319+
Logger.info("RN wallet data found, starting migration...", context: "AppScene")
320+
321+
do {
322+
try await migrations.migrateFromReactNative()
323+
} catch {
324+
Logger.error("RN migration failed: \(error)", context: "AppScene")
325+
migrations.markMigrationChecked()
326+
await MainActor.run { migrations.isShowingMigrationLoading = false }
327+
app.toast(
328+
type: .error,
329+
title: "Migration Failed",
330+
description: "Please restore your wallet manually using your recovery phrase"
331+
)
332+
}
333+
}
334+
265335
private func handleNodeLifecycleChange(_ state: NodeLifecycleState) {
266336
if state == .initializing {
267337
walletIsInitializing = true

Bitkit/Services/CoreService.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,42 @@ class ActivityService {
154154
}
155155
}
156156

157+
func markAllUnseenActivitiesAsSeen() async {
158+
let timestamp = UInt64(Date().timeIntervalSince1970)
159+
160+
do {
161+
let activities = try await get()
162+
var didMarkAny = false
163+
164+
for activity in activities {
165+
let id: String
166+
let isSeen: Bool
167+
168+
switch activity {
169+
case let .onchain(onchain):
170+
id = onchain.id
171+
isSeen = onchain.seenAt != nil
172+
case let .lightning(lightning):
173+
id = lightning.id
174+
isSeen = lightning.seenAt != nil
175+
}
176+
177+
if !isSeen {
178+
try await ServiceQueue.background(.core) {
179+
try BitkitCore.markActivityAsSeen(activityId: id, seenAt: timestamp)
180+
}
181+
didMarkAny = true
182+
}
183+
}
184+
185+
if didMarkAny {
186+
activitiesChangedSubject.send()
187+
}
188+
} catch {
189+
Logger.error("Failed to mark all activities as seen: \(error)", context: "ActivityService")
190+
}
191+
}
192+
157193
// MARK: - Transaction Status Checks
158194

159195
func wasTransactionReplaced(txid: String) async -> Bool {

Bitkit/Services/LightningService.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ class LightningService {
2222

2323
private init() {}
2424

25-
func setup(walletIndex: Int, electrumServerUrl: String? = nil, rgsServerUrl: String? = nil) async throws {
25+
func setup(
26+
walletIndex: Int,
27+
electrumServerUrl: String? = nil,
28+
rgsServerUrl: String? = nil,
29+
channelMigration: ChannelDataMigration? = nil
30+
) async throws {
2631
Logger.debug("Checking lightning process lock...")
2732
try StateLocker.lock(.lightning, wait: 30) // Wait 30 seconds to lock because maybe extension is still running
2833

@@ -80,7 +85,11 @@ class LightningService {
8085
Logger.debug("Building ldk-node with vssUrl: '\(vssUrl)'")
8186
Logger.debug("Building ldk-node with lnurlAuthServerUrl: '\(lnurlAuthServerUrl)'")
8287

83-
// Set entropy from mnemonic on builder
88+
if let channelMigration {
89+
builder.setChannelDataMigration(migration: channelMigration)
90+
Logger.info("Applied channel migration: \(channelMigration.channelMonitors.count) monitors", context: "Migration")
91+
}
92+
8493
builder.setEntropyBip39Mnemonic(mnemonic: mnemonic, passphrase: passphrase)
8594

8695
try await ServiceQueue.background(.ldk) {

0 commit comments

Comments
 (0)