Moved shared account info to app code

This commit is contained in:
Peter 2019-02-18 00:17:08 +03:00
parent 0ab79889fb
commit c245c4bbb6
12 changed files with 190 additions and 104 deletions

View File

@ -1,78 +1,6 @@
import Foundation
import CommonCrypto
struct MasterNotificationKey: Codable {
let id: Data
let data: Data
}
struct AccountDatacenterKey: Codable {
let id: Int64
let data: Data
}
struct AccountDatacenterAddress: Codable {
let host: String
let port: Int32
let isMedia: Bool
let secret: Data?
}
struct AccountDatacenterInfo: Codable {
let masterKey: AccountDatacenterKey
let addressList: [AccountDatacenterAddress]
}
struct AccountProxyConnection: Codable {
let host: String
let port: Int32
let username: String?
let password: String?
let secret: Data?
}
struct StoredAccountInfo: Codable {
let primaryId: Int32
let isTestingEnvironment: Bool
let peerName: String
let datacenters: [Int32: AccountDatacenterInfo]
let proxy: AccountProxyConnection?
}
struct AccountData {
let id: Int64
let isTestingEnvironment: Bool
let basePath: String
let datacenterId: Int32
let datacenters: [Int32: AccountDatacenterInfo]
let notificationKey: MasterNotificationKey?
let peerName: String
let proxy: AccountProxyConnection?
}
func loadAccountsData(rootPath: String) -> [Int64: AccountData] {
var result: [Int64: AccountData] = [:]
if let contents = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: rootPath), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants]) {
for url in contents {
let directoryName = url.lastPathComponent
if directoryName.hasPrefix("account-"), let id = UInt64(directoryName[directoryName.index(directoryName.startIndex, offsetBy: "account-".count)...]) {
var notificationKey: MasterNotificationKey?
if let data = try? Data(contentsOf: URL(fileURLWithPath: url.path + "/notificationsKey")), let value = try? JSONDecoder().decode(MasterNotificationKey.self, from: data) {
notificationKey = value
}
var storedInfo: StoredAccountInfo?
if let data = try? Data(contentsOf: URL(fileURLWithPath: url.path + "/storedInfo")), let value = try? JSONDecoder().decode(StoredAccountInfo.self, from: data) {
storedInfo = value
}
if let storedInfo = storedInfo {
result[Int64(bitPattern: id)] = AccountData(id: Int64(bitPattern: id), isTestingEnvironment: storedInfo.isTestingEnvironment, basePath: url.path, datacenterId: storedInfo.primaryId, datacenters: storedInfo.datacenters, notificationKey: notificationKey, peerName: storedInfo.peerName, proxy: storedInfo.proxy)
}
}
}
}
return result
}
private func sha256Digest(_ data: Data) -> Data {
let length = data.count
return data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
@ -84,15 +12,14 @@ private func sha256Digest(_ data: Data) -> Data {
}
}
func decryptedNotificationPayload(accounts: [Int64: AccountData], data: Data) -> (AccountData, [AnyHashable: Any])? {
func decryptedNotificationPayload(accounts: [StoredAccountInfo], data: Data) -> (StoredAccountInfo, [AnyHashable: Any])? {
if data.count < 8 + 16 {
return nil
}
for (_, account) in accounts {
guard let notificationKey = account.notificationKey else {
continue
}
for account in accounts {
let notificationKey = account.notificationKey
if data.subdata(in: 0 ..< 8) != notificationKey.id {
continue
}

View File

@ -304,7 +304,7 @@ private final class ParsedFile: NSObject {
}
}
func fetchImageWithAccount(account: AccountData, resource: ImageResource, completion: @escaping (Data?) -> Void) -> () -> Void {
func fetchImageWithAccount(proxyConnection: AccountProxyConnection?, account: StoredAccountInfo, resource: ImageResource, completion: @escaping (Data?) -> Void) -> () -> Void {
MTLogSetEnabled(true)
MTLogSetLoggingFunction({ str, args in
//let string = NSString(format: str! as NSString, args!)
@ -321,7 +321,7 @@ func fetchImageWithAccount(account: AccountData, resource: ImageResource, comple
apiEnvironment.disableUpdates = true
apiEnvironment = apiEnvironment.withUpdatedLangPackCode("en")
if let proxy = account.proxy {
if let proxy = proxyConnection {
apiEnvironment = apiEnvironment.withUpdatedSocksProxySettings(MTSocksProxySettings(ip: proxy.host, port: UInt16(clamping: proxy.port), username: proxy.username, password: proxy.password, secret: proxy.secret))
}

View File

@ -115,9 +115,13 @@ class NotificationService: UNNotificationServiceExtension {
}
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
let accountsData = self.rootPath.flatMap({ rootPath in
guard let rootPath = rootPath else {
contentHandler(request.content)
return
}
let accountInfos = self.rootPath.flatMap({ rootPath in
loadAccountsData(rootPath: rootPath)
}) ?? [:]
}) ?? StoredAccountInfos(proxy: nil, accounts: [])
self.contentHandler = contentHandler
self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
@ -132,7 +136,7 @@ class NotificationService: UNNotificationServiceExtension {
encryptedData = Data(base64Encoded: encryptedPayload)
}
if let (account, dict) = encryptedData.flatMap({ decryptedNotificationPayload(accounts: accountsData, data: $0) }) {
if let (account, dict) = encryptedData.flatMap({ decryptedNotificationPayload(accounts: accountInfos.accounts, data: $0) }) {
var userInfo = self.bestAttemptContent?.userInfo ?? [:]
userInfo["accountId"] = account.id
@ -172,7 +176,9 @@ class NotificationService: UNNotificationServiceExtension {
let imagesPath = NSTemporaryDirectory() + "aps-data"
let _ = try? FileManager.default.createDirectory(atPath: imagesPath, withIntermediateDirectories: true, attributes: nil)
let mediaBoxPath = account.basePath + "/postbox/media"
let accountBasePath = rootPath + "account-\(UInt64(bitPattern: account.id))"
let mediaBoxPath = accountBasePath + "/postbox/media"
let tempImagePath = thumbnailImage.flatMap({ imagesPath + "/\($0.resourceId).jpg" })
let mediaBoxThumbnailImagePath = thumbnailImage.flatMap({ mediaBoxPath + "/\($0.resourceId)" })
@ -188,8 +194,8 @@ class NotificationService: UNNotificationServiceExtension {
self.bestAttemptContent?.body = alert["body"] as? String ?? ""
}
if accountsData.count > 1 {
if let title = self.bestAttemptContent?.title, !account.peerName.isEmpty {
if accountInfos.accounts.count > 1 {
if let title = self.bestAttemptContent?.title, !title.isEmpty, !account.peerName.isEmpty {
self.bestAttemptContent?.title = "\(title) [\(account.peerName)]"
}
}
@ -241,7 +247,7 @@ class NotificationService: UNNotificationServiceExtension {
self.cancelFetch?()
if let mediaBoxThumbnailImagePath = mediaBoxThumbnailImagePath, let tempImagePath = tempImagePath, let thumbnailImage = thumbnailImage {
self.cancelFetch = fetchImageWithAccount(account: account, resource: thumbnailImage, completion: { [weak self] data in
self.cancelFetch = fetchImageWithAccount(proxyConnection: accountInfos.proxy, account: account, resource: thumbnailImage, completion: { [weak self] data in
DispatchQueue.main.async {
guard let strongSelf = self else {
return

View File

@ -347,6 +347,9 @@
D096C2C21CC3C104006D814E /* Postbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D096C2C01CC3C104006D814E /* Postbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D096C2C51CC3C11A006D814E /* SwiftSignalKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D096C2C31CC3C11A006D814E /* SwiftSignalKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D09A59601B5858DB00FC3724 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D09A595F1B5858DB00FC3724 /* SystemConfiguration.framework */; };
D09B79C52219C784003B1F9D /* SharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09B79C42219C784003B1F9D /* SharedAccountInfo.swift */; };
D09B79C62219C784003B1F9D /* SharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09B79C42219C784003B1F9D /* SharedAccountInfo.swift */; };
D09B79C82219C7AE003B1F9D /* ManageSharedAccountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09B79C72219C7AE003B1F9D /* ManageSharedAccountInfo.swift */; };
D09DCBB71D0C856B00F51FFE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D09DCBB51D0C856B00F51FFE /* Localizable.strings */; };
D0A18D631E149043004C6734 /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0A18D621E149043004C6734 /* PushKit.framework */; };
D0A18D651E15C020004C6734 /* WakeupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A18D641E15C020004C6734 /* WakeupManager.swift */; };
@ -1071,6 +1074,8 @@
D096C2C31CC3C11A006D814E /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D09A595F1B5858DB00FC3724 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
D09A59B71B5876B600FC3724 /* Telegram-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Telegram-Bridging-Header.h"; sourceTree = "<group>"; };
D09B79C42219C784003B1F9D /* SharedAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedAccountInfo.swift; sourceTree = "<group>"; };
D09B79C72219C7AE003B1F9D /* ManageSharedAccountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageSharedAccountInfo.swift; sourceTree = "<group>"; };
D09DCBB61D0C856B00F51FFE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
D0A18D621E149043004C6734 /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; };
D0A18D641E15C020004C6734 /* WakeupManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WakeupManager.swift; sourceTree = "<group>"; };
@ -1982,6 +1987,8 @@
D008599F1B28189D00EAF753 /* Supporting Files */,
D0B21B0C2203A9A1003F741D /* SharedWakeupManager.swift */,
D0B21B0E220438E9003F741D /* SharedNotificationManager.swift */,
D09B79C42219C784003B1F9D /* SharedAccountInfo.swift */,
D09B79C72219C7AE003B1F9D /* ManageSharedAccountInfo.swift */,
);
path = "Telegram-iOS";
sourceTree = "<group>";
@ -3190,6 +3197,7 @@
files = (
D073E52222003E1E00742DDD /* Data.swift in Sources */,
D0ED633B21FF3EFD001D4648 /* BuildConfig.m in Sources */,
D09B79C62219C784003B1F9D /* SharedAccountInfo.swift in Sources */,
D0ED633D21FF4580001D4648 /* NotificationService.swift in Sources */,
D0ED633A21FF3EDF001D4648 /* AccountData.swift in Sources */,
D0ED634121FF4786001D4648 /* Serialization.swift in Sources */,
@ -3224,6 +3232,7 @@
09D304242174340E00C00567 /* TGBridgeMessage.m in Sources */,
09D304362174344900C00567 /* TGBridgeUnsupportedMediaAttachment.m in Sources */,
D0ADF95A212B5AC600310BBC /* LegacyResourceImport.swift in Sources */,
D09B79C52219C784003B1F9D /* SharedAccountInfo.swift in Sources */,
D06E4C2F21347D9200088087 /* UIImage+ImageEffects.m in Sources */,
D0B3B53B21666C0000FC60A0 /* LegacyFileImport.swift in Sources */,
D0ADF95E212C818F00310BBC /* LegacyPreferencesImport.swift in Sources */,
@ -3246,6 +3255,7 @@
09D304332174344900C00567 /* TGBridgeMessageEntitiesAttachment.m in Sources */,
09D304342174344900C00567 /* TGBridgeReplyMarkupMediaAttachment.m in Sources */,
D0A18D651E15C020004C6734 /* WakeupManager.swift in Sources */,
D09B79C82219C7AE003B1F9D /* ManageSharedAccountInfo.swift in Sources */,
D051DB5D21602D6E00F30F92 /* LegacyDataImportSplash.swift in Sources */,
09D304382174344900C00567 /* TGBridgeWebPageMediaAttachment.m in Sources */,
09D3042F2174344900C00567 /* TGBridgeDocumentMediaAttachment.m in Sources */,

View File

@ -596,6 +596,16 @@ private final class SharedApplicationContext {
}, navigateToChat: { accountId, peerId, messageId in
self.openChatWhenReady(accountId: accountId, peerId: peerId, messageId: messageId)
})
let rawAccounts = sharedContext.activeAccounts
|> map { _, accounts, _ -> [Account] in
return accounts.map({ $0.1 })
}
let _ = (sharedAccountInfos(accountManager: sharedContext.accountManager, accounts: rawAccounts)
|> deliverOn(Queue())).start(next: { infos in
storeAccountsData(rootPath: rootPath, accounts: infos)
})
sharedContext.presentGlobalController = { [weak self] c, a in
guard let strongSelf = self else {
return
@ -793,41 +803,41 @@ private final class SharedApplicationContext {
}
return true
})
|> mapToSignal { authAndAccounts -> Signal<(UnauthorizedAccount, ((String, AccountRecordId)?, [(String, AccountRecordId)]))?, NoError> in
|> mapToSignal { authAndAccounts -> Signal<(UnauthorizedAccount, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))?, NoError> in
if let (primary, auth, accounts) = authAndAccounts {
let phoneNumbers = combineLatest(accounts.map { account -> Signal<(AccountRecordId, String)?, NoError> in
return account.postbox.transaction { transaction -> (AccountRecordId, String)? in
let phoneNumbers = combineLatest(accounts.map { account -> Signal<(AccountRecordId, String, Bool)?, NoError> in
return account.postbox.transaction { transaction -> (AccountRecordId, String, Bool)? in
if let phone = (transaction.getPeer(account.peerId) as? TelegramUser)?.phone {
return (account.id, phone)
return (account.id, phone, account.testingEnvironment)
} else {
return nil
}
}
})
return phoneNumbers
|> map { phoneNumbers -> (UnauthorizedAccount, ((String, AccountRecordId)?, [(String, AccountRecordId)]))? in
var primaryNumber: (String, AccountRecordId)?
|> map { phoneNumbers -> (UnauthorizedAccount, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))? in
var primaryNumber: (String, AccountRecordId, Bool)?
if let primary = primary {
for idAndNumber in phoneNumbers {
if let (id, number) = idAndNumber, id == primary.id {
primaryNumber = (number, id)
if let (id, number, testingEnvironment) = idAndNumber, id == primary.id {
primaryNumber = (number, id, testingEnvironment)
break
}
}
}
return (auth, (primaryNumber, phoneNumbers.compactMap({ $0.flatMap({ ($0.1, $0.0) }) })))
return (auth, (primaryNumber, phoneNumbers.compactMap({ $0.flatMap({ ($0.1, $0.0, $0.2) }) })))
}
} else {
return .single(nil)
}
}
|> mapToSignal { accountAndOtherAccountPhoneNumbers -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, ((String, AccountRecordId)?, [(String, AccountRecordId)]))?, NoError> in
|> mapToSignal { accountAndOtherAccountPhoneNumbers -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))?, NoError> in
return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> CallListSettings in
return transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings) as? CallListSettings ?? CallListSettings.defaultSettings
}
|> mapToSignal { callListSettings -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, ((String, AccountRecordId)?, [(String, AccountRecordId)]))?, NoError> in
|> mapToSignal { callListSettings -> Signal<(UnauthorizedAccount, LimitsConfiguration, CallListSettings, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))?, NoError> in
if let (account, otherAccountPhoneNumbers) = accountAndOtherAccountPhoneNumbers {
return account.postbox.transaction { transaction -> (UnauthorizedAccount, LimitsConfiguration, CallListSettings, ((String, AccountRecordId)?, [(String, AccountRecordId)]))? in
return account.postbox.transaction { transaction -> (UnauthorizedAccount, LimitsConfiguration, CallListSettings, ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]))? in
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
return (account, limitsConfiguration, callListSettings, otherAccountPhoneNumbers)
}
@ -858,6 +868,7 @@ private final class SharedApplicationContext {
let firstTime = self.contextValue == nil
if let contextValue = self.contextValue {
contextValue.context.isCurrent = false
contextValue.passcodeController?.dismiss()
contextValue.context.account.shouldExplicitelyKeepWorkerConnections.set(.single(false))
contextValue.context.account.shouldKeepBackgroundDownloadConnections.set(.single(false))
}

View File

@ -21,7 +21,7 @@ final class UnauthorizedApplicationContext {
let rootController: AuthorizationSequenceController
init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId)?, [(String, AccountRecordId)])) {
init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])) {
self.sharedContext = sharedContext
self.account = account
let presentationData = sharedContext.currentPresentationData.with { $0 }
@ -70,7 +70,7 @@ final class AuthorizedApplicationContext {
private var inAppNotificationSettings: InAppNotificationSettings?
private var isLocked: Bool = true
private var passcodeController: ViewController?
var passcodeController: ViewController?
private var currentTermsOfServiceUpdate: TermsOfServiceUpdate?
private var currentPermissionsController: PermissionController?

View File

@ -0,0 +1,77 @@
import Foundation
import SwiftSignalKit
import TelegramCore
import Postbox
private func accountInfo(account: Account) -> Signal<StoredAccountInfo, NoError> {
let peerName = account.postbox.transaction { transaction -> String in
guard let peer = transaction.getPeer(account.peerId) else {
return ""
}
if let addressName = peer.addressName {
return "\(addressName)"
}
return peer.displayTitle
}
let primaryDatacenterId = Int32(account.network.datacenterId)
let context = account.network.context
var datacenters: [Int32: AccountDatacenterInfo] = [:]
for nId in context.knownDatacenterIds() {
if let id = nId as? Int {
if let authInfo = context.authInfoForDatacenter(withId: id), let authKey = authInfo.authKey {
let transportScheme = context.chooseTransportSchemeForConnection(toDatacenterId: id, schemes: context.transportSchemesForDatacenter(withId: id, media: true, enforceMedia: false, isProxy: false))
var addressList: [AccountDatacenterAddress] = []
if let transportScheme = transportScheme, let address = transportScheme.address, let host = address.host {
let secret: Data? = address.secret
addressList.append(AccountDatacenterAddress(host: host, port: Int32(address.port), isMedia: address.preferForMedia, secret: secret))
}
datacenters[Int32(id)] = AccountDatacenterInfo(masterKey: AccountDatacenterKey(id: authInfo.authKeyId, data: authKey), addressList: addressList)
}
}
}
let notificationKey = masterNotificationsKey(account: account, ignoreDisabled: false)
return combineLatest(peerName, notificationKey)
|> map { peerName, notificationKey -> StoredAccountInfo in
return StoredAccountInfo(id: account.id.int64, primaryId: primaryDatacenterId, isTestingEnvironment: account.testingEnvironment, peerName: peerName, datacenters: datacenters, notificationKey: AccountNotificationKey(id: notificationKey.id, data: notificationKey.data))
}
}
func sharedAccountInfos(accountManager: AccountManager, accounts: Signal<[Account], NoError>) -> Signal<StoredAccountInfos, NoError> {
return combineLatest(accountManager.sharedData(keys: [SharedDataKeys.proxySettings]), accounts)
|> mapToSignal { sharedData, accounts -> Signal<StoredAccountInfos, NoError> in
let proxySettings = sharedData.entries[SharedDataKeys.proxySettings] as? ProxySettings
let proxy = proxySettings?.effectiveActiveServer.flatMap { proxyServer -> AccountProxyConnection? in
var username: String?
var password: String?
var secret: Data?
switch proxyServer.connection {
case let .socks5(usernameValue, passwordValue):
username = usernameValue
password = passwordValue
case let .mtp(secretValue):
secret = secretValue
}
return AccountProxyConnection(host: proxyServer.host, port: proxyServer.port, username: username, password: password, secret: secret)
}
return combineLatest(accounts.map(accountInfo))
|> map { infos -> StoredAccountInfos in
return StoredAccountInfos(proxy: proxy, accounts: infos)
}
}
}
func storeAccountsData(rootPath: String, accounts: StoredAccountInfos) {
guard let data = try? JSONEncoder().encode(accounts) else {
Logger.shared.log("storeAccountsData", "Error encoding data")
return
}
guard let _ = try? data.write(to: URL(fileURLWithPath: rootPath + "/accounts-shared-data")) else {
Logger.shared.log("storeAccountsData", "Error saving data")
return
}
}

View File

@ -0,0 +1,55 @@
import Foundation
struct AccountNotificationKey: Codable {
let id: Data
let data: Data
}
struct AccountDatacenterKey: Codable {
let id: Int64
let data: Data
}
struct AccountDatacenterAddress: Codable {
let host: String
let port: Int32
let isMedia: Bool
let secret: Data?
}
struct AccountDatacenterInfo: Codable {
let masterKey: AccountDatacenterKey
let addressList: [AccountDatacenterAddress]
}
struct AccountProxyConnection: Codable {
let host: String
let port: Int32
let username: String?
let password: String?
let secret: Data?
}
struct StoredAccountInfo: Codable {
let id: Int64
let primaryId: Int32
let isTestingEnvironment: Bool
let peerName: String
let datacenters: [Int32: AccountDatacenterInfo]
let notificationKey: AccountNotificationKey
}
struct StoredAccountInfos: Codable {
let proxy: AccountProxyConnection?
let accounts: [StoredAccountInfo]
}
func loadAccountsData(rootPath: String) -> StoredAccountInfos {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: rootPath + "/accounts-shared-data")) else {
return StoredAccountInfos(proxy: nil, accounts: [])
}
guard let value = try? JSONDecoder().decode(StoredAccountInfos.self, from: data) else {
return StoredAccountInfos(proxy: nil, accounts: [])
}
return value
}

@ -1 +1 @@
Subproject commit dd02b83037bc296d3fc0c7d1962a58c828788537
Subproject commit b7ade4bccd8b88a9938eb96d590403c5d42d1cdf

@ -1 +1 @@
Subproject commit 90fd9374a3e2a154aca6ce3339289b3b4282e87c
Subproject commit 09f1b2631594c56a886368290f9a77d73a017cf5

@ -1 +1 @@
Subproject commit b04d42e547aa5abcb71b2a43e79cab7769538d88
Subproject commit 343bdc3aaf45933f5918e8f955db52f57c93348a

@ -1 +1 @@
Subproject commit 98fa310db582ed0a4b821e98dd374834d955131e
Subproject commit 3a254d05a3c419af1ac7f3c1e0d0f2b9920fe44c