iOS/Sources/App/Utilities/OpenInChromeController.swift
Bruno Pantaleão Gonçalves 8c46161ff0
Replace usage of UIApplication.shared by URLOpener class (#3961)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-12 15:41:49 +01:00

111 lines
5.0 KiB
Swift

// swiftformat:disable fileHeader
// Copyright (c) 2015 Ce Zheng
//
// Copyright 2012, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import Shared
import UIKit
private let googleChromeHTTPScheme: String = "googlechrome:"
private let googleChromeHTTPSScheme: String = "googlechromes:"
private let googleChromeCallbackScheme: String = "googlechrome-x-callback:"
private let characterSet: CharacterSet = .urlQueryAllowed
private func encodeByAddingPercentEscapes(_ input: String?) -> String {
if let input {
if let percentEncoded = input.addingPercentEncoding(withAllowedCharacters: characterSet) {
return percentEncoded
}
}
return ""
}
open class OpenInChromeController {
public static let sharedInstance = OpenInChromeController()
open func isChromeInstalled() -> Bool {
if let simpleURL = URL(string: googleChromeHTTPScheme),
let callbackURL = URL(string: googleChromeCallbackScheme) {
return URLOpener.shared.canOpenURL(simpleURL) || URLOpener.shared.canOpenURL(callbackURL)
}
return false
}
// swiftlint:disable:next cyclomatic_complexity
open func openInChrome(_ url: URL, callbackURL: URL?, createNewTab: Bool = false) {
if let chromeSimpleURL = URL(string: googleChromeHTTPScheme),
let chromeCallbackURL = URL(string: googleChromeCallbackScheme) {
if URLOpener.shared.canOpenURL(chromeCallbackURL) {
var appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String
// CFBundleDisplayName is an optional key, so we will use CFBundleName if it does not exist
if appName == nil {
appName = Bundle.main.infoDictionary?["CFBundleName"] as? String
}
let scheme = url.scheme?.lowercased()
if scheme == "http" || scheme == "https" {
var chromeURLString = String(
format: "%@//x-callback-url/open/?x-source=%@&url=%@",
googleChromeCallbackScheme,
encodeByAddingPercentEscapes(appName),
encodeByAddingPercentEscapes(url.absoluteString)
)
if callbackURL != nil {
chromeURLString += String(
format: "&x-success=%@",
encodeByAddingPercentEscapes(callbackURL!.absoluteString)
)
}
if createNewTab {
chromeURLString += "&create-new-tab"
}
if let chromeURL = URL(string: chromeURLString) {
URLOpener.shared.open(chromeURL, options: [:], completionHandler: nil)
}
}
} else if URLOpener.shared.canOpenURL(chromeSimpleURL) {
let scheme = url.scheme?.lowercased()
var chromeScheme: String?
if scheme == "http" {
chromeScheme = googleChromeHTTPScheme
} else if scheme == "https" {
chromeScheme = googleChromeHTTPSScheme
}
if let chromeScheme {
let absoluteURLString = url.absoluteString
let lowerBound = absoluteURLString.range(of: ":")!.lowerBound
let chromeURLString = chromeScheme + absoluteURLString.suffix(from: lowerBound)
URLOpener.shared.open(URL(string: chromeURLString)!, options: [:], completionHandler: nil)
}
}
}
}
}