diff --git a/firka/ios/FirkaWatch Watch App/FirkaWatch Watch App.entitlements b/firka/ios/FirkaWatch Watch App/FirkaWatch Watch App.entitlements
index af013c5..a96138b 100644
--- a/firka/ios/FirkaWatch Watch App/FirkaWatch Watch App.entitlements
+++ b/firka/ios/FirkaWatch Watch App/FirkaWatch Watch App.entitlements
@@ -7,6 +7,6 @@
group.app.firka.firkaa
com.apple.developer.ubiquity-kvstore-identifier
- $(TeamIdentifierPrefix)$(CFBundleIdentifier)
+ $(TeamIdentifierPrefix)app.firka.firkaa
diff --git a/firka/ios/FirkaWatch Watch App/Services/DataStore.swift b/firka/ios/FirkaWatch Watch App/Services/DataStore.swift
index bf65526..6508221 100644
--- a/firka/ios/FirkaWatch Watch App/Services/DataStore.swift
+++ b/firka/ios/FirkaWatch Watch App/Services/DataStore.swift
@@ -174,13 +174,27 @@ class DataStore {
}
print("[Watch] Recovery Step 2: Attempting API token refresh...")
+ var isNetworkError = false
+ var isTokenPermanentlyInvalid = false
+
do {
_ = try await TokenManager.shared.refreshToken()
print("[Watch] Recovery: Token refresh succeeded!")
checkTokenState()
return true
+ } catch let tokenError as TokenError {
+ print("[Watch] Recovery: API token refresh failed: \(tokenError)")
+ switch tokenError {
+ case .networkError:
+ isNetworkError = true
+ case .refreshExpired, .invalidGrant:
+ isTokenPermanentlyInvalid = true
+ default:
+ break
+ }
} catch {
- print("[Watch] Recovery: API token refresh failed: \(error)")
+ print("[Watch] Recovery: API token refresh failed with unknown error: \(error)")
+ isNetworkError = true // Assume network issue for unknown errors
}
print("[Watch] Recovery Step 3: Checking if iPhone is reachable...")
@@ -190,9 +204,19 @@ class DataStore {
return true
}
- print("[Watch] Recovery: All attempts failed, will show reauth screen")
- recoveryAttempted = true
- self.error = "token_expired"
+ if isTokenPermanentlyInvalid {
+ print("[Watch] Recovery: Token permanently invalid, showing reauth screen")
+ recoveryAttempted = true
+ self.error = "token_expired"
+ } else if isNetworkError {
+ print("[Watch] Recovery: Network error - not showing reauth, user can retry")
+ self.error = "network"
+ } else {
+ print("[Watch] Recovery: Unknown failure, showing reauth screen")
+ recoveryAttempted = true
+ self.error = "token_expired"
+ }
+
return false
}
diff --git a/firka/ios/FirkaWatchComplicationsExtension.entitlements b/firka/ios/FirkaWatchComplicationsExtension.entitlements
index af013c5..a96138b 100644
--- a/firka/ios/FirkaWatchComplicationsExtension.entitlements
+++ b/firka/ios/FirkaWatchComplicationsExtension.entitlements
@@ -7,6 +7,6 @@
group.app.firka.firkaa
com.apple.developer.ubiquity-kvstore-identifier
- $(TeamIdentifierPrefix)$(CFBundleIdentifier)
+ $(TeamIdentifierPrefix)app.firka.firkaa
diff --git a/firka/ios/Runner.xcodeproj/project.pbxproj b/firka/ios/Runner.xcodeproj/project.pbxproj
index d97857b..d04c664 100644
--- a/firka/ios/Runner.xcodeproj/project.pbxproj
+++ b/firka/ios/Runner.xcodeproj/project.pbxproj
@@ -1076,7 +1076,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -1108,7 +1108,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = app.firka.firka.RunnerTests;
@@ -1127,7 +1127,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = app.firka.firka.RunnerTests;
@@ -1144,7 +1144,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = app.firka.firka.RunnerTests;
@@ -1171,7 +1171,7 @@
CODE_SIGN_ENTITLEMENTS = LiveActivityWidget.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1222,7 +1222,7 @@
CODE_SIGN_ENTITLEMENTS = LiveActivityWidget.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1270,7 +1270,7 @@
CODE_SIGN_ENTITLEMENTS = LiveActivityWidget.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1317,7 +1317,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FirkaWatchComplicationsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1370,7 +1370,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FirkaWatchComplicationsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1420,7 +1420,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FirkaWatchComplicationsExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1471,7 +1471,7 @@
CODE_SIGN_ENTITLEMENTS = HomeWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1522,7 +1522,7 @@
CODE_SIGN_ENTITLEMENTS = HomeWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1569,7 +1569,7 @@
CODE_SIGN_ENTITLEMENTS = HomeWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -1874,7 +1874,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -1910,7 +1910,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1062;
+ CURRENT_PROJECT_VERSION = 1068;
DEVELOPMENT_TEAM = UT7MSP4GWZ;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/firka/ios/Runner/Info.plist b/firka/ios/Runner/Info.plist
index 069b100..338316a 100644
--- a/firka/ios/Runner/Info.plist
+++ b/firka/ios/Runner/Info.plist
@@ -46,7 +46,7 @@
CFBundleVersion
- 1062
+ 1068
LSRequiresIPhoneOS
NSAppTransportSecurity
diff --git a/firka/ios/Runner/Runner.entitlements b/firka/ios/Runner/Runner.entitlements
index 17ab594..48392c5 100644
--- a/firka/ios/Runner/Runner.entitlements
+++ b/firka/ios/Runner/Runner.entitlements
@@ -11,6 +11,6 @@
group.app.firka.firkaa
com.apple.developer.ubiquity-kvstore-identifier
- $(TeamIdentifierPrefix)$(CFBundleIdentifier)
+ $(TeamIdentifierPrefix)app.firka.firkaa
diff --git a/firka/ios/Runner/WatchSessionManager.swift b/firka/ios/Runner/WatchSessionManager.swift
index a2b1771..540d928 100644
--- a/firka/ios/Runner/WatchSessionManager.swift
+++ b/firka/ios/Runner/WatchSessionManager.swift
@@ -29,6 +29,10 @@ class WatchSessionManager: NSObject, WCSessionDelegate {
self?.handleNotifyReauthRequired(result: result)
case "requestTokenFromWatch":
self?.handleRequestTokenFromWatch(result: result)
+ case "checkiCloudToken":
+ self?.handleCheckiCloudToken(result: result)
+ case "saveTokeToniCloud":
+ self?.handleSaveTokenToiCloud(arguments: call.arguments, result: result)
default:
result(FlutterMethodNotImplemented)
}
@@ -151,6 +155,70 @@ class WatchSessionManager: NSObject, WCSessionDelegate {
)
}
+ private func handleCheckiCloudToken(result: @escaping FlutterResult) {
+ print("[WatchSessionManager] Checking iCloud for token...")
+
+ guard let token = iCloudTokenManager.shared.loadToken() else {
+ print("[WatchSessionManager] No token in iCloud")
+ result(["error": "no_token"])
+ return
+ }
+
+ let formatter = DateFormatter()
+ formatter.dateFormat = "HH:mm:ss"
+ print("[WatchSessionManager] Found iCloud token, expiry: \(formatter.string(from: token.expiryDate))")
+
+ let tokenData: [String: Any] = [
+ "studentId": token.studentId,
+ "studentIdNorm": token.studentIdNorm,
+ "iss": token.iss,
+ "idToken": token.idToken,
+ "accessToken": token.accessToken,
+ "refreshToken": token.refreshToken,
+ "expiryDate": Int64(token.expiryDate.timeIntervalSince1970 * 1000)
+ ]
+
+ result(tokenData)
+ }
+
+ private func handleSaveTokenToiCloud(arguments: Any?, result: @escaping FlutterResult) {
+ guard let tokenData = arguments as? [String: Any] else {
+ result(FlutterError(code: "INVALID_ARGS", message: "Arguments must be a dictionary", details: nil))
+ return
+ }
+
+ guard let accessToken = tokenData["accessToken"] as? String,
+ let refreshToken = tokenData["refreshToken"] as? String,
+ let idToken = tokenData["idToken"] as? String,
+ let iss = tokenData["iss"] as? String,
+ let studentId = tokenData["studentId"] as? String,
+ let expiryMs = tokenData["expiryDate"] as? Int64 else {
+ result(FlutterError(code: "INVALID_ARGS", message: "Missing required token fields", details: nil))
+ return
+ }
+
+ let studentIdNorm = tokenData["studentIdNorm"] as? Int64 ?? 0
+ let expiryDate = Date(timeIntervalSince1970: Double(expiryMs) / 1000.0)
+
+ let token = WatchToken(
+ accessToken: accessToken,
+ refreshToken: refreshToken,
+ idToken: idToken,
+ iss: iss,
+ studentId: studentId,
+ studentIdNorm: studentIdNorm,
+ expiryDate: expiryDate
+ )
+
+ iCloudTokenManager.shared.saveToken(token, deviceName: "iPhone")
+
+ let formatter = DateFormatter()
+ formatter.dateFormat = "HH:mm:ss"
+ print("[WatchSessionManager] Token saved to iCloud, expiry: \(formatter.string(from: expiryDate))")
+
+ result(nil)
+ }
+
func session(
_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
diff --git a/firka/lib/helpers/api/client/kreta_client.dart b/firka/lib/helpers/api/client/kreta_client.dart
index 1e8fa32..8d98e10 100644
--- a/firka/lib/helpers/api/client/kreta_client.dart
+++ b/firka/lib/helpers/api/client/kreta_client.dart
@@ -94,6 +94,16 @@ class KretaClient {
if (!Platform.isIOS || !initDone) return false;
try {
+ final recoveredFromiCloud = await WatchSyncHelper.checkAndRecoverFromiCloud(
+ isar: initData.isar,
+ tokens: initData.tokens,
+ client: initData.client,
+ );
+ if (recoveredFromiCloud) {
+ debugPrint('[KretaClient] Recovered fresh token from iCloud');
+ return true;
+ }
+
await WatchSyncHelper.syncTokenFromWatch(
isar: initData.isar,
tokens: initData.tokens,
@@ -113,7 +123,7 @@ class KretaClient {
return false;
} catch (e) {
- debugPrint('[KretaClient] Failed to recover from Watch: $e');
+ debugPrint('[KretaClient] Failed to recover from Watch/iCloud: $e');
return false;
}
}
@@ -126,7 +136,25 @@ class KretaClient {
final fiveMinutesFromNow = now.add(const Duration(minutes: 5));
if (model.expiryDate == null || model.expiryDate!.isBefore(fiveMinutesFromNow)) {
- logger.info("[Proactive] Token expired or expiring soon, refreshing proactively...");
+ logger.info("[Proactive] Token expired or expiring soon...");
+
+ if (Platform.isIOS && initDone) {
+ final recoveredFromiCloud = await WatchSyncHelper.checkAndRecoverFromiCloud(
+ isar: isar,
+ tokens: initData.tokens,
+ client: this,
+ );
+ if (recoveredFromiCloud) {
+ logger.info("[Proactive] Found fresh token in iCloud, no refresh needed");
+ initData.tokens = await isar.tokenModels.where().findAll();
+ if (initData.tokens.isNotEmpty) {
+ model = initData.tokens.first;
+ }
+ return true;
+ }
+ }
+
+ logger.info("[Proactive] No fresh token in iCloud, refreshing...");
try {
var extended = await extendToken(model);
@@ -140,6 +168,12 @@ class KretaClient {
model = tokenModel;
if (Platform.isIOS) {
+ try {
+ await WatchSyncHelper.saveTokenToiCloud(tokenModel);
+ } catch (e) {
+ debugPrint('[KretaClient] iCloud token sync skipped: $e');
+ }
+
try {
await _watchChannel.invokeMethod('sendTokenToWatch', {
'studentId': model.studentId,
@@ -207,6 +241,12 @@ class KretaClient {
model = tokenModel;
if (Platform.isIOS) {
+ try {
+ await WatchSyncHelper.saveTokenToiCloud(tokenModel);
+ } catch (e) {
+ debugPrint('[KretaClient] iCloud token sync skipped: $e');
+ }
+
try {
await _watchChannel.invokeMethod('sendTokenToWatch', {
'studentId': model.studentId,
diff --git a/firka/lib/helpers/watch_sync_helper.dart b/firka/lib/helpers/watch_sync_helper.dart
index 5c6a415..34fd072 100644
--- a/firka/lib/helpers/watch_sync_helper.dart
+++ b/firka/lib/helpers/watch_sync_helper.dart
@@ -194,6 +194,126 @@ class WatchSyncHelper {
}
}
+ /// Check iCloud for a fresher token and update local storage if found.
+ /// This should be called on app startup BEFORE any API calls.
+ /// Returns true if a fresher token was found and applied.
+ static Future checkAndRecoverFromiCloud({
+ Isar? isar,
+ List? tokens,
+ KretaClient? client,
+ }) async {
+ if (!Platform.isIOS) return false;
+
+ final effectiveIsar = isar ?? (initDone ? initData.isar : null);
+ final effectiveTokens = tokens ?? (initDone ? initData.tokens : null);
+ final effectiveClient = client ?? (initDone ? initData.client : null);
+
+ if (effectiveIsar == null) {
+ debugPrint('[WatchSync] Cannot check iCloud: no isar available');
+ return false;
+ }
+
+ try {
+ debugPrint('[WatchSync] Checking iCloud for fresher token...');
+ final result = await _watchChannel.invokeMethod('checkiCloudToken');
+
+ if (result == null) {
+ debugPrint('[WatchSync] No response from native');
+ return false;
+ }
+
+ final tokenData = result as Map;
+ if (tokenData.containsKey('error')) {
+ debugPrint('[WatchSync] iCloud check returned: ${tokenData['error']}');
+ return false;
+ }
+
+ final iCloudExpiry = tokenData['expiryDate'] as int?;
+ if (iCloudExpiry == null) {
+ debugPrint('[WatchSync] iCloud token has no expiry');
+ return false;
+ }
+
+ final iCloudExpiryDate = DateTime.fromMillisecondsSinceEpoch(iCloudExpiry);
+
+ if (iCloudExpiryDate.isBefore(DateTime.now())) {
+ debugPrint('[WatchSync] iCloud token is expired');
+ return false;
+ }
+
+ final currentToken = effectiveTokens?.isNotEmpty == true ? effectiveTokens!.first : null;
+ final localExpiry = currentToken?.expiryDate;
+
+ if (localExpiry == null || iCloudExpiryDate.isAfter(localExpiry)) {
+ debugPrint('[WatchSync] iCloud has fresher token! iCloud: $iCloudExpiryDate, Local: $localExpiry');
+
+ final newToken = TokenModel.fromValues(
+ (tokenData['studentIdNorm'] as int?) ?? 0,
+ tokenData['studentId'] as String,
+ tokenData['iss'] as String,
+ tokenData['idToken'] as String,
+ tokenData['accessToken'] as String,
+ tokenData['refreshToken'] as String,
+ iCloudExpiry,
+ );
+
+ await effectiveIsar.writeTxn(() async {
+ await effectiveIsar.tokenModels.put(newToken);
+ });
+
+ final updatedTokens = await effectiveIsar.tokenModels.where().findAll();
+
+ if (initDone) {
+ initData.tokens = updatedTokens;
+ }
+
+ if (effectiveClient != null) {
+ effectiveClient.model = newToken;
+ }
+
+ KretaClient.clearReauthFlag();
+
+ debugPrint('[WatchSync] Token recovered from iCloud! New expiry: $iCloudExpiryDate');
+ return true;
+ } else {
+ debugPrint('[WatchSync] Local token is same or fresher. Local: $localExpiry, iCloud: $iCloudExpiryDate');
+ return false;
+ }
+ } catch (e) {
+ debugPrint('[WatchSync] Failed to check iCloud: $e');
+ return false;
+ }
+ }
+
+ /// Save token to iCloud. Call this after refreshing token on iPhone.
+ static Future saveTokenToiCloud(TokenModel token) async {
+ if (!Platform.isIOS) return;
+
+ if (token.accessToken == null ||
+ token.refreshToken == null ||
+ token.expiryDate == null) {
+ debugPrint('[WatchSync] Token incomplete, not saving to iCloud');
+ return;
+ }
+
+ final tokenData = {
+ 'studentId': token.studentId,
+ 'studentIdNorm': token.studentIdNorm,
+ 'iss': token.iss,
+ 'idToken': token.idToken,
+ 'accessToken': token.accessToken,
+ 'refreshToken': token.refreshToken,
+ 'expiryDate': token.expiryDate!.millisecondsSinceEpoch,
+ };
+
+ try {
+ await _watchChannel.invokeMethod('saveTokeToniCloud', tokenData);
+ debugPrint('[WatchSync] Token saved to iCloud');
+ } catch (e) {
+ debugPrint('[WatchSync] Failed to save token to iCloud: $e');
+ }
+ }
+
static Future syncTokenFromWatch({
Isar? isar,
List? tokens,
diff --git a/firka/lib/main.dart b/firka/lib/main.dart
index edff368..9d70e27 100644
--- a/firka/lib/main.dart
+++ b/firka/lib/main.dart
@@ -215,10 +215,21 @@ Future _initData(AppInitialization init) async {
logger.fine("Initializing kréta client as: ${token.studentId}");
init.client = KretaClient(token, init.isar);
- // Sync token from Watch first (Watch might have fresher token)
if (Platform.isIOS) {
- await Future.delayed(const Duration(milliseconds: 300));
+ final recoveredFromiCloud = await WatchSyncHelper.checkAndRecoverFromiCloud(
+ isar: init.isar,
+ tokens: init.tokens,
+ client: init.client,
+ );
+ if (recoveredFromiCloud) {
+ init.tokens = await init.isar.tokenModels.where().findAll();
+ if (init.tokens.isNotEmpty) {
+ init.client.model = init.tokens.first;
+ }
+ logger.info('[Init] Recovered fresher token from iCloud');
+ }
+ await Future.delayed(const Duration(milliseconds: 300));
await WatchSyncHelper.syncTokenFromWatch(
isar: init.isar,
tokens: init.tokens,