mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Web auth improvements
This commit is contained in:
parent
f852dca2c8
commit
bae4591799
6 changed files with 156 additions and 16 deletions
|
|
@ -58,6 +58,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
|
||||
"//submodules/TelegramUI/Components/GlassControls",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/TelegramUI/Components/AuthConfirmationScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import LegacyMediaPickerUI
|
|||
import PassKit
|
||||
import AlertComponent
|
||||
import UIKitRuntimeUtils
|
||||
import AuthConfirmationScreen
|
||||
|
||||
private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
|
||||
private final class PendingTask {
|
||||
|
|
@ -157,6 +158,18 @@ final class WebView: WKWebView {
|
|||
self.evaluateJavaScript(script, completionHandler: { _, _ in
|
||||
})
|
||||
}
|
||||
|
||||
var origin: String? {
|
||||
guard let url = self.url, let scheme = url.scheme, let host = url.host else {
|
||||
return nil
|
||||
}
|
||||
let port = url.port
|
||||
var origin = "\(scheme)://\(host)"
|
||||
if let port {
|
||||
origin += ":\(port)"
|
||||
}
|
||||
return origin
|
||||
}
|
||||
}
|
||||
|
||||
private class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
|
|
@ -402,11 +415,104 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
|||
guard let body = message.body as? [String: Any], let eventName = body["eventName"] as? String else {
|
||||
return
|
||||
}
|
||||
let eventData = (body["eventData"] as? String)?.data(using: .utf8)
|
||||
let json = try? JSONSerialization.jsonObject(with: eventData ?? Data(), options: []) as? [String: Any]
|
||||
switch eventName {
|
||||
case "cancellingTouch":
|
||||
self.cancelInteractiveTransitionGestures()
|
||||
case "oauth_request":
|
||||
self.webView.sendEvent(name: "oauth_supported", data: nil)
|
||||
let url = json?["url"] as? String
|
||||
if let url {
|
||||
let securityOrigin = message.frameInfo.securityOrigin
|
||||
var origin = ""
|
||||
origin.append(securityOrigin.protocol)
|
||||
origin.append("://")
|
||||
origin.append(securityOrigin.host)
|
||||
if securityOrigin.port != 0 {
|
||||
origin.append(":")
|
||||
origin.append("\(securityOrigin.port)")
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let subject: MessageActionUrlSubject = .url(url: url, inAppOrigin: origin)
|
||||
let _ = (self.context.engine.messages.requestMessageActionUrlAuth(subject: subject)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self, case .request = result else {
|
||||
return
|
||||
}
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = AuthConfirmationScreen(context: self.context, subject: result, completion: { [weak self] accountContext, accountPeer, authResult in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch authResult {
|
||||
case let .accept(allowWriteAccess, sharePhoneNumber):
|
||||
let signal: Signal<MessageActionUrlAuthResult, MessageActionUrlAuthError>
|
||||
if accountContext === context {
|
||||
signal = accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
} else {
|
||||
accountContext.account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||
signal = accountContext.engine.messages.requestMessageActionUrlAuth(subject: subject)
|
||||
|> castError(MessageActionUrlAuthError.self)
|
||||
|> mapToSignal { result in
|
||||
return accountContext.engine.messages.acceptMessageActionUrlAuth(subject: subject, allowWriteAccess: allowWriteAccess, sharePhoneNumber: sharePhoneNumber)
|
||||
} |> afterDisposed {
|
||||
accountContext.account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { authResult in
|
||||
dismissImpl?()
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
let text: String
|
||||
if case let .request(domain, _, _, flags, _, _) = result {
|
||||
if flags.contains(.requestPhoneNumber) && !sharePhoneNumber {
|
||||
text = presentationData.strings.AuthConfirmation_LoginSuccess_TextNoNumber(domain).string
|
||||
} else {
|
||||
text = presentationData.strings.AuthConfirmation_LoginSuccess_Text(domain).string
|
||||
}
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginSuccess_Title, text: text, cancel: nil, destructive: false), action: { _ in return true })
|
||||
if let navigationController = self.getNavigationController() {
|
||||
(navigationController.topViewController as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
if case let .accepted(url) = authResult, let url, let currentOrigin = self.webView.origin, currentOrigin == origin {
|
||||
let data: JSON = ["result_url": url]
|
||||
self.webView.sendEvent(name: "oauth_result_confirmed", data: data.string)
|
||||
} else {
|
||||
self.webView.sendEvent(name: "oauth_result_failed", data: nil)
|
||||
}
|
||||
}
|
||||
}, error: { _ in
|
||||
guard case let .request(domain, _, _, _, _, _) = result else {
|
||||
return
|
||||
}
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, cancel: nil, destructive: false), action: { _ in return true })
|
||||
if let navigationController = self.getNavigationController() {
|
||||
(navigationController.topViewController as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.declineUrlAuth(url: url).start()
|
||||
self.webView.sendEvent(name: "oauth_result_failed", data: nil)
|
||||
})
|
||||
case .decline:
|
||||
let _ = self.context.engine.messages.declineUrlAuth(url: url).start()
|
||||
self.webView.sendEvent(name: "oauth_result_failed", data: nil)
|
||||
}
|
||||
})
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
if let navigationController = self.getNavigationController() {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.webView.sendEvent(name: "oauth_supported", data: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
|
|||
|
||||
if !skipSafari {
|
||||
options.append(OpenInOption(identifier: "safari", application: .safari, action: {
|
||||
var url = url
|
||||
if url.hasPrefix("http://") || url.hasPrefix("https://") {
|
||||
url = url.replacingOccurrences(of: "https://", with: "x-safari-https")
|
||||
}
|
||||
return .openUrl(url: url)
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2345,10 +2345,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||
} else {
|
||||
|
||||
}
|
||||
var attributes: [MessageAttribute] = []
|
||||
if !entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil)
|
||||
|
||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: nil, defaultReaction: nil, areStarReactionsEnabled: false, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import ContactListUI
|
|||
import DeviceAccess
|
||||
import ProxyServerPreviewScreen
|
||||
import AuthConfirmationScreen
|
||||
import OpenInExternalAppUI
|
||||
|
||||
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
||||
if case .default = navigation {
|
||||
|
|
@ -1839,7 +1840,7 @@ func openResolvedUrlImpl(
|
|||
}
|
||||
|
||||
let _ = (signal
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
|> deliverOnMainQueue).start(next: { acceptResult in
|
||||
dismissImpl?()
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
|
|
@ -1854,6 +1855,40 @@ func openResolvedUrlImpl(
|
|||
(navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
if case let .accepted(url) = acceptResult, let url {
|
||||
var browserIdentifier = "safari"
|
||||
if case let .request(_, _, clientData, _, _, _) = result, let browser = clientData?.browser {
|
||||
if browser.hasPrefix("Safari") {
|
||||
browserIdentifier = "safari"
|
||||
} else if browser.hasPrefix("Opera") {
|
||||
browserIdentifier = "operaTouch"
|
||||
} else if browser.hasPrefix("Microsoft Edge") {
|
||||
browserIdentifier = "edge"
|
||||
} else if browser.hasPrefix("Chrome") {
|
||||
browserIdentifier = "chrome"
|
||||
} else if browser.hasPrefix("Firefox") {
|
||||
browserIdentifier = "firefox"
|
||||
} else if browser.hasPrefix("Yandex") {
|
||||
browserIdentifier = "yandex"
|
||||
} else if browser.hasPrefix("UC Browser") {
|
||||
browserIdentifier = "ucbrowser"
|
||||
} else if browser.hasPrefix("Firefox Focus") {
|
||||
browserIdentifier = "firefoxFocus"
|
||||
} else if browser.hasPrefix("DuckDuckGo") {
|
||||
browserIdentifier = "duckDuckGo"
|
||||
} else if browser.hasPrefix("Alook") {
|
||||
browserIdentifier = "alook"
|
||||
}
|
||||
}
|
||||
|
||||
let openInOptions = availableOpenInOptions(context: context, item: .url(url: url))
|
||||
if let match = openInOptions.first(where: { $0.identifier == browserIdentifier }), case let .openUrl(openUrl) = match.action() {
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .external, url: openUrl, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
} else {
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .external, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
}
|
||||
}, error: { _ in
|
||||
if case let .request(domain, _, _, _, _, _) = result {
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: presentationData.strings.AuthConfirmation_LoginFail_Title, text: presentationData.strings.AuthConfirmation_LoginFail_Text(domain).string, cancel: nil, destructive: false), action: { _ in return true })
|
||||
|
|
@ -1865,8 +1900,8 @@ func openResolvedUrlImpl(
|
|||
}
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
dismissImpl = {
|
||||
controller.dismissAnimated()
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -417,7 +417,6 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess
|
|||
}
|
||||
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
|
|
@ -464,10 +463,6 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess
|
|||
resultsContext.state
|
||||
)
|
||||
|> map { presentationData, state, resultsState -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
var isEmpty = false
|
||||
for (_, optionState) in resultsState.options {
|
||||
if !optionState.hasLoadedOnce {
|
||||
|
|
@ -504,7 +499,7 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess
|
|||
}
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .textWithSubtitle(presentationData.strings.PollResults_Title, presentationData.strings.MessagePoll_VotedCount(totalVoters)), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .textWithSubtitle(presentationData.strings.PollResults_Title, presentationData.strings.MessagePoll_VotedCount(totalVoters)), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: nil, emptyStateItem: nil, initialScrollToItem: initialScrollToItem, crossfadeState: previousWasEmptyValue != nil && previousWasEmptyValue == true && isEmpty == false, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
|
|
@ -518,9 +513,6 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess
|
|||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
controller.acceptsFocusWhenInOverlay = true
|
||||
|
||||
return controller
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue