mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
a651bb589d
commit
a8b02015ce
@ -14096,3 +14096,13 @@ Sorry for the inconvenience.";
|
||||
"Gift.Unpin.Unpin" = "Unpin";
|
||||
|
||||
"ChatList.Search.Ad" = "Ad";
|
||||
|
||||
"Login.Fee.Title" = "SMS Fee";
|
||||
"Login.Fee.SmsCost.Title" = "High SMS Costs";
|
||||
"Login.Fee.SmsCost.Text" = "Telecom providers in your country (%@) charge Telegram very high prices for SMS.";
|
||||
"Login.Fee.Verification.Title" = "Verification Required";
|
||||
"Login.Fee.Verification.Text" = "Telegram needs to send you an SMS with a verification code to confirm your phone number.";
|
||||
"Login.Fee.Support.Title" = "Support via [Telegram Premium >]()";
|
||||
"Login.Fee.Support.Text" = "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.";
|
||||
"Login.Fee.SignUp" = "Sign Up for %@";
|
||||
"Login.Fee.GetPremiumForAWeek" = "Get Telegram Premium for 1 week";
|
||||
|
@ -848,7 +848,13 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
let pasteSize = self.pasteButton.measure(layout.size)
|
||||
let pasteButtonSize = CGSize(width: pasteSize.width + 16.0, height: 24.0)
|
||||
transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: layout.size.width - 40.0 - pasteButtonSize.width, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize))
|
||||
let pasteOriginX: CGFloat
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
pasteOriginX = layout.size.width - 40.0 - pasteButtonSize.width
|
||||
} else {
|
||||
pasteOriginX = self.textField.frame.maxX + 32.0
|
||||
}
|
||||
transition.updateFrame(node: self.pasteButton, frame: CGRect(origin: CGPoint(x: pasteOriginX, y: self.textField.frame.midY - pasteButtonSize.height / 2.0), size: pasteButtonSize))
|
||||
self.hintArrowNode.isHidden = true
|
||||
} else if case .word = codeType {
|
||||
self.hintButtonNode.alpha = 0.0
|
||||
|
@ -198,7 +198,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "SMS Fee", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Login_Fee_Title, font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)))
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
@ -218,9 +218,9 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
AnyComponentWithIdentity(
|
||||
id: "cost",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "High SMS Costs",
|
||||
title: environment.strings.Login_Fee_SmsCost_Title,
|
||||
titleColor: textColor,
|
||||
text: "Telecom providers in your country (\(countryName)) charge Telegram very high prices for SMS.",
|
||||
text: environment.strings.Login_Fee_SmsCost_Text(countryName).string,
|
||||
textColor: secondaryTextColor,
|
||||
iconName: "Premium/Authorization/Cost",
|
||||
iconColor: linkColor
|
||||
@ -231,9 +231,9 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
AnyComponentWithIdentity(
|
||||
id: "verification",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "Verification Required",
|
||||
title: environment.strings.Login_Fee_Verification_Title,
|
||||
titleColor: textColor,
|
||||
text: "Telegram needs to send you an SMS with a verification code to confirm your phone number.",
|
||||
text: environment.strings.Login_Fee_Verification_Text,
|
||||
textColor: secondaryTextColor,
|
||||
iconName: "Premium/Authorization/Verification",
|
||||
iconColor: linkColor
|
||||
@ -242,11 +242,11 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
)
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: "withdrawal",
|
||||
id: "support",
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: "Support via [Telegram Premium >]()",
|
||||
title: environment.strings.Login_Fee_Support_Title,
|
||||
titleColor: textColor,
|
||||
text: "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.",
|
||||
text: environment.strings.Login_Fee_Support_Text,
|
||||
textColor: secondaryTextColor,
|
||||
iconName: "Premium/Authorization/Support",
|
||||
iconColor: linkColor,
|
||||
@ -315,7 +315,8 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
} else {
|
||||
priceString = "–"
|
||||
}
|
||||
let buttonString = "Sign up for \(priceString)"
|
||||
|
||||
let buttonString = environment.strings.Login_Fee_SignUp(priceString).string
|
||||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
let buttonSize = self.button.update(
|
||||
transition: transition,
|
||||
@ -331,7 +332,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
||||
component: AnyComponent(
|
||||
VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Login_Fee_GetPremiumForAWeek, font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
|
||||
], spacing: 1.0)
|
||||
)
|
||||
),
|
||||
|
@ -300,6 +300,8 @@ private final class ScrollContent: CombinedComponent {
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: linkColor.withAlphaComponent(0.1),
|
||||
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
@ -598,6 +600,7 @@ private final class ParagraphComponent: CombinedComponent {
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: accentColor.withAlphaComponent(0.1),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
@ -949,6 +952,7 @@ public class AdsInfoScreen: ViewController {
|
||||
context: controller.context,
|
||||
theme: self.presentationData.theme,
|
||||
title: self.presentationData.strings.AdsInfo_Understood,
|
||||
showBackground: controller.mode != .search,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
@ -992,7 +996,7 @@ public class AdsInfoScreen: ViewController {
|
||||
}
|
||||
|
||||
private var defaultTopInset: CGFloat {
|
||||
guard let layout = self.currentLayout else {
|
||||
guard let layout = self.currentLayout, let controller = self.controller else {
|
||||
return 210.0
|
||||
}
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
@ -1006,9 +1010,13 @@ public class AdsInfoScreen: ViewController {
|
||||
let contentHeight = self.containerExternalState.contentHeight
|
||||
let footerHeight = self.footerHeight
|
||||
if contentHeight > 0.0 {
|
||||
let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0
|
||||
if delta > 0.0 {
|
||||
defaultTopInset += delta
|
||||
if case .search = controller.mode {
|
||||
return (layout.size.height - containerTopInset) - contentHeight
|
||||
} else {
|
||||
let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0
|
||||
if delta > 0.0 {
|
||||
defaultTopInset += delta
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultTopInset
|
||||
@ -1029,7 +1037,7 @@ public class AdsInfoScreen: ViewController {
|
||||
}
|
||||
|
||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
guard let layout = self.currentLayout else {
|
||||
guard let layout = self.currentLayout, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -1064,6 +1072,9 @@ public class AdsInfoScreen: ViewController {
|
||||
let contentOffset = scrollView?.contentOffset.y ?? 0.0
|
||||
|
||||
var translation = recognizer.translation(in: self.view).y
|
||||
if case .search = controller.mode {
|
||||
translation = max(0.0, translation)
|
||||
}
|
||||
|
||||
var currentOffset = topInset + translation
|
||||
|
||||
@ -1111,9 +1122,13 @@ public class AdsInfoScreen: ViewController {
|
||||
|
||||
let contentOffset = scrollView?.contentOffset.y ?? 0.0
|
||||
|
||||
let translation = recognizer.translation(in: self.view).y
|
||||
var translation = recognizer.translation(in: self.view).y
|
||||
var velocity = recognizer.velocity(in: self.view)
|
||||
|
||||
if case .search = controller.mode {
|
||||
translation = max(0.0, translation)
|
||||
velocity.y = max(0.0, velocity.y)
|
||||
}
|
||||
|
||||
if self.isExpanded {
|
||||
if contentOffset > 0.1 {
|
||||
velocity = CGPoint()
|
||||
@ -1435,12 +1450,14 @@ private final class FooterComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let showBackground: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, title: String, action: @escaping () -> Void) {
|
||||
init(context: AccountContext, theme: PresentationTheme, title: String, showBackground: Bool, action: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.showBackground = showBackground
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@ -1454,6 +1471,9 @@ private final class FooterComponent: Component {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.showBackground != rhs.showBackground {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1494,6 +1514,9 @@ private final class FooterComponent: Component {
|
||||
self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor
|
||||
transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
self.backgroundView.isHidden = !component.showBackground
|
||||
self.separator.isHidden = !component.showBackground
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
|
@ -1868,15 +1868,15 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
||||
guard let self, let navigationController = self.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.dismissAnimated()
|
||||
|
||||
let _ = (context.engine.privacy.requestAccountPrivacySettings()
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak navigationController] privacySettings in
|
||||
|> deliverOnMainQueue).start(next: { [weak navigationController] privacySettings in
|
||||
let controller = context.sharedContext.makeIncomingMessagePrivacyScreen(context: context, value: privacySettings.globalSettings.nonContactChatsPrivacy, exceptions: privacySettings.noPaidMessages, update: { settingValue in
|
||||
let _ = context.engine.privacy.updateNonContactChatsPrivacy(value: settingValue).start()
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
|
||||
Queue.mainQueue().after(1.0) {
|
||||
self?.dismissAnimated()
|
||||
Queue.mainQueue().after(0.4) {
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1558,25 +1558,46 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
controller.parentController()?.lockOrientation = lock
|
||||
}
|
||||
case "web_app_device_storage_save_key":
|
||||
if let json, let requestId = json["req_id"] as? String, let key = json["key"] as? String, let value = json["value"] {
|
||||
var effectiveValue: String?
|
||||
if let stringValue = value as? String {
|
||||
effectiveValue = stringValue
|
||||
if let json, let requestId = json["req_id"] as? String {
|
||||
if let key = json["key"] as? String {
|
||||
let value = json["value"]
|
||||
|
||||
var effectiveValue: String?
|
||||
if let stringValue = value as? String {
|
||||
effectiveValue = stringValue
|
||||
} else if value is NSNull {
|
||||
effectiveValue = nil
|
||||
} else {
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": "VALUE_INVALID"
|
||||
]
|
||||
self.webView?.sendEvent(name: "device_storage_failed", data: data.string)
|
||||
return
|
||||
}
|
||||
let _ = self.context.engine.peers.setBotStorageValue(peerId: controller.botId, key: key, value: effectiveValue).start(error: { [weak self] error in
|
||||
var errorValue = "UNKNOWN_ERROR"
|
||||
if case .quotaExceeded = error {
|
||||
errorValue = "QUOTA_EXCEEDED"
|
||||
}
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": errorValue
|
||||
]
|
||||
self?.webView?.sendEvent(name: "device_storage_failed", data: data.string)
|
||||
}, completed: { [weak self] in
|
||||
let data: JSON = [
|
||||
"req_id": requestId
|
||||
]
|
||||
self?.webView?.sendEvent(name: "device_storage_key_saved", data: data.string)
|
||||
})
|
||||
} else {
|
||||
effectiveValue = nil
|
||||
}
|
||||
let _ = self.context.engine.peers.setBotStorageValue(peerId: controller.botId, key: key, value: effectiveValue).start(error: { [weak self] _ in
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": "UNKNOWN_ERROR"
|
||||
"error": "KEY_INVALID"
|
||||
]
|
||||
self?.webView?.sendEvent(name: "device_storage_failed", data: data.string)
|
||||
}, completed: { [weak self] in
|
||||
let data: JSON = [
|
||||
"req_id": requestId
|
||||
]
|
||||
self?.webView?.sendEvent(name: "device_storage_key_saved", data: data.string)
|
||||
})
|
||||
self.webView?.sendEvent(name: "device_storage_failed", data: data.string)
|
||||
}
|
||||
}
|
||||
case "web_app_device_storage_get_key":
|
||||
if let json, let requestId = json["req_id"] as? String {
|
||||
@ -1607,6 +1628,78 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self?.webView?.sendEvent(name: "device_storage_cleared", data: data.string)
|
||||
})
|
||||
}
|
||||
case "web_app_secure_storage_save_key":
|
||||
if let json, let requestId = json["req_id"] as? String {
|
||||
if let key = json["key"] as? String {
|
||||
let value = json["value"]
|
||||
|
||||
var effectiveValue: String?
|
||||
if let stringValue = value as? String {
|
||||
effectiveValue = stringValue
|
||||
} else if value is NSNull {
|
||||
effectiveValue = nil
|
||||
} else {
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": "VALUE_INVALID"
|
||||
]
|
||||
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
|
||||
return
|
||||
}
|
||||
let _ = (WebAppSecureStorage.setValue(userId: self.context.account.peerId, botId: controller.botId, key: key, value: effectiveValue)
|
||||
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||
var errorValue = "UNKNOWN_ERROR"
|
||||
if case .quotaExceeded = error {
|
||||
errorValue = "QUOTA_EXCEEDED"
|
||||
}
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": errorValue
|
||||
]
|
||||
self?.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
|
||||
}, completed: { [weak self] in
|
||||
let data: JSON = [
|
||||
"req_id": requestId
|
||||
]
|
||||
self?.webView?.sendEvent(name: "secure_storage_key_saved", data: data.string)
|
||||
})
|
||||
} else {
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": "KEY_INVALID"
|
||||
]
|
||||
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
|
||||
}
|
||||
}
|
||||
case "web_app_secure_storage_get_key":
|
||||
if let json, let requestId = json["req_id"] as? String {
|
||||
if let key = json["key"] as? String {
|
||||
let _ = (WebAppSecureStorage.getValue(userId: self.context.account.peerId, botId: controller.botId, key: key)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"value": value ?? NSNull()
|
||||
]
|
||||
self?.webView?.sendEvent(name: "secure_storage_key_received", data: data.string)
|
||||
})
|
||||
} else {
|
||||
let data: JSON = [
|
||||
"req_id": requestId,
|
||||
"error": "KEY_INVALID"
|
||||
]
|
||||
self.webView?.sendEvent(name: "secure_storage_failed", data: data.string)
|
||||
}
|
||||
}
|
||||
case "web_app_secure_storage_clear":
|
||||
if let json, let requestId = json["req_id"] as? String {
|
||||
let _ = (WebAppSecureStorage.clearStorage(userId: self.context.account.peerId, botId: controller.botId)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
let data: JSON = [
|
||||
"req_id": requestId
|
||||
]
|
||||
self?.webView?.sendEvent(name: "secure_storage_cleared", data: data.string)
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
147
submodules/WebUI/Sources/WebAppSecureStorage.swift
Normal file
147
submodules/WebUI/Sources/WebAppSecureStorage.swift
Normal file
@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
import Security
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
final class WebAppSecureStorage {
|
||||
enum Error {
|
||||
case quotaExceeded
|
||||
case unknown
|
||||
}
|
||||
|
||||
static private let maxKeyCount = 10
|
||||
|
||||
private init() {
|
||||
}
|
||||
|
||||
static private func keyPrefix(userId: EnginePeer.Id, botId: EnginePeer.Id) -> String {
|
||||
return "A\(UInt64(bitPattern: userId.toInt64()))WebBot\(UInt64(bitPattern: botId.toInt64()))Key_"
|
||||
}
|
||||
|
||||
static private func makeQuery(userId: EnginePeer.Id, botId: EnginePeer.Id, key: String) -> [String: Any] {
|
||||
let identifier = self.keyPrefix(userId: userId, botId: botId) + key
|
||||
return [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: identifier,
|
||||
kSecAttrService as String: "TMASecureStorage"
|
||||
]
|
||||
}
|
||||
|
||||
static private func countKeys(userId: EnginePeer.Id, botId: EnginePeer.Id) -> Int {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: "TMASecureStorage",
|
||||
kSecMatchLimit as String: kSecMatchLimitAll,
|
||||
kSecReturnAttributes as String: true
|
||||
]
|
||||
|
||||
var result: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
|
||||
if status == errSecSuccess, let items = result as? [[String: Any]] {
|
||||
let relevantPrefix = self.keyPrefix(userId: userId, botId: botId)
|
||||
let count = items.filter {
|
||||
if let account = $0[kSecAttrAccount as String] as? String {
|
||||
return account.hasPrefix(relevantPrefix)
|
||||
}
|
||||
return false
|
||||
}.count
|
||||
return count
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
static func setValue(userId: EnginePeer.Id, botId: EnginePeer.Id, key: String, value: String?) -> Signal<Never, WebAppSecureStorage.Error> {
|
||||
var query = makeQuery(userId: userId, botId: botId, key: key)
|
||||
if value == nil {
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
if status == errSecSuccess || status == errSecItemNotFound {
|
||||
return .complete()
|
||||
} else {
|
||||
return .fail(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
guard let valueData = value?.data(using: .utf8) else {
|
||||
return .fail(.unknown)
|
||||
}
|
||||
|
||||
query[kSecAttrAccessible as String] = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
|
||||
|
||||
let status = SecItemCopyMatching(query as CFDictionary, nil)
|
||||
if status == errSecSuccess {
|
||||
let updateQuery: [String: Any] = [
|
||||
kSecValueData as String: valueData
|
||||
]
|
||||
let updateStatus = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
|
||||
if updateStatus == errSecSuccess {
|
||||
return .complete()
|
||||
} else {
|
||||
return .fail(.unknown)
|
||||
}
|
||||
} else if status == errSecItemNotFound {
|
||||
let currentCount = countKeys(userId: userId, botId: botId)
|
||||
if currentCount >= maxKeyCount {
|
||||
return .fail(.quotaExceeded)
|
||||
}
|
||||
|
||||
query[kSecValueData as String] = valueData
|
||||
|
||||
let createStatus = SecItemAdd(query as CFDictionary, nil)
|
||||
if createStatus == errSecSuccess {
|
||||
return .complete()
|
||||
} else {
|
||||
return .fail(.unknown)
|
||||
}
|
||||
} else {
|
||||
return .fail(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
static func getValue(userId: EnginePeer.Id, botId: EnginePeer.Id, key: String) -> Signal<String?, WebAppSecureStorage.Error> {
|
||||
var query = makeQuery(userId: userId, botId: botId, key: key)
|
||||
query[kSecReturnData as String] = true
|
||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
|
||||
var result: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
|
||||
if status == errSecSuccess, let data = result as? Data, let value = String(data: data, encoding: .utf8) {
|
||||
return .single(value)
|
||||
} else if status == errSecItemNotFound {
|
||||
return .single(nil)
|
||||
} else {
|
||||
return .fail(.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
static func clearStorage(userId: EnginePeer.Id, botId: EnginePeer.Id) -> Signal<Never, WebAppSecureStorage.Error> {
|
||||
let serviceQuery: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: "TMASecureStorage",
|
||||
kSecMatchLimit as String: kSecMatchLimitAll,
|
||||
kSecReturnAttributes as String: true
|
||||
]
|
||||
|
||||
var result: CFTypeRef?
|
||||
let status = SecItemCopyMatching(serviceQuery as CFDictionary, &result)
|
||||
|
||||
if status == errSecSuccess, let items = result as? [[String: Any]] {
|
||||
let relevantPrefix = self.keyPrefix(userId: userId, botId: botId)
|
||||
|
||||
for item in items {
|
||||
if let account = item[kSecAttrAccount as String] as? String, account.hasPrefix(relevantPrefix) {
|
||||
let deleteQuery: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: account,
|
||||
kSecAttrService as String: "TMASecureStorage"
|
||||
]
|
||||
|
||||
SecItemDelete(deleteQuery as CFDictionary)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user