Remove support for handling SPA calls within the app. (#5515)

* Remove support for handling SPA call links.

They were a stop-gap solution whilst we were building support for embedded room calling.

* Simplify ElementCallConfiguration now that there is only 1 type of call to handle.

* Remove the unused overlayModule from NavigationRoomCoordinator.
This commit is contained in:
Doug
2026-04-29 19:32:38 +01:00
committed by GitHub
parent 5bd0800fe5
commit df1a407142
19 changed files with 65 additions and 410 deletions

View File

@@ -578,7 +578,6 @@
62C5876C4254C58C2086F0DE /* HomeScreenContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B4B58B79A6FA250B24A1EC /* HomeScreenContent.swift */; };
633400018E07D2DC7175B16E /* LiveLocationShareProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA12A7F5EF5C6D0B992869ED /* LiveLocationShareProxy.swift */; };
633501761094E09DFBEBFFAD /* CopyTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B682FE2C44C5E163E7023B05 /* CopyTextButton.swift */; };
63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */; };
6386EA3C898AD1A4BC1DC8A5 /* TimelineMediaPreviewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD40B92FCF20165658296AD /* TimelineMediaPreviewModifier.swift */; };
639A0A27383EC655B0E81E95 /* SpaceScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459A3921046977CBF4F3C359 /* SpaceScreenViewModelProtocol.swift */; };
63CDC201A5980F304F6D0A1C /* WaveformInteractionModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEE91FB8ABB5F5884B6D940 /* WaveformInteractionModifier.swift */; };
@@ -1859,7 +1858,6 @@
284FEEB0789B8894E52A7F34 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
287FC98AF2664EAD79C0D902 /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = "<group>"; };
28C19F54A0C4FC9AB7ABD583 /* TextRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItemContent.swift; sourceTree = "<group>"; };
28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericCallLinkWidgetDriver.swift; sourceTree = "<group>"; };
28EA8BE9EEDBD17555141C7E /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
2910422CB628D3B2BBE47449 /* SeparatorRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineView.swift; sourceTree = "<group>"; };
292EEE1F71DCC205C45728F7 /* ReportRoomScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportRoomScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -5531,7 +5529,6 @@
6FC8B21E86B137BE4E91F82A /* ElementCallServiceProtocol.swift */,
309AD8BAE6437C31BA7157BF /* ElementCallWidgetDriver.swift */,
A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */,
28C202C1C7E330F124981A31 /* GenericCallLinkWidgetDriver.swift */,
);
path = ElementCall;
sourceTree = "<group>";
@@ -8352,7 +8349,6 @@
5705511EBE083295EF98F998 /* FrequentlyUsedEmoji.swift in Sources */,
46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */,
F18CA61A58C77C84F551B8E7 /* GeneratedMocks.swift in Sources */,
63780F9DA06573E38A471ECA /* GenericCallLinkWidgetDriver.swift in Sources */,
4295E5F850897710A51AE114 /* GeoURI.swift in Sources */,
F0DACC95F24128A54CD537E4 /* GlobalSearchScreen.swift in Sources */,
9F11E743EA01482E78A438B0 /* GlobalSearchScreenCell.swift in Sources */,

View File

@@ -274,12 +274,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
case .accountProvisioningLink:
handleAppRoute(route,
windowType: windowType)
case .genericCallLink(let url):
if let userSessionFlowCoordinator {
userSessionFlowCoordinator.handleAppRoute(route, animated: true)
} else {
presentCallScreen(genericCallLink: url)
}
case .userProfile(let userID):
if isExternalURL {
handleAppRoute(route,
@@ -878,35 +872,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
elementCallService.setClientProxy(userSession.clientProxy)
}
private func presentCallScreen(genericCallLink url: URL) {
let configuration = ElementCallConfiguration(genericCallLink: url)
let callScreenCoordinator = CallScreenCoordinator(parameters: .init(elementCallService: elementCallService,
configuration: configuration,
allowPictureInPicture: false,
appSettings: appSettings,
appHooks: appHooks,
analytics: ServiceLocator.shared.analytics))
callScreenCoordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .pictureInPictureIsAvailable:
break
case .pictureInPictureStarted, .pictureInPictureStopped:
// Don't allow PiP when signed out - the user could login at which point we'd
// need to hand over the call from here to the user session flow coordinator.
MXLog.error("Picture in Picture not supported before login.")
case .dismiss:
navigationRootCoordinator.setOverlayCoordinator(nil)
}
}
.store(in: &cancellables)
navigationRootCoordinator.setOverlayCoordinator(callScreenCoordinator, animated: false)
}
private func configureNotificationManager() {
notificationManager.setUserSession(userSession)

View File

@@ -41,8 +41,6 @@ enum AppRoute: Hashable {
case userProfile(userID: String)
/// An Element Call running in a particular room
case call(roomID: String, isVoiceCall: Bool)
/// An Element Call link generated outside of a chat room.
case genericCallLink(url: URL)
/// The settings screen.
case settings
/// The setting screen for key backup.
@@ -84,8 +82,7 @@ struct AppRouteURLParser {
AppGroupURLParser(),
MatrixPermalinkParser(),
ElementWebURLParser(domains: appSettings.elementWebHosts),
AccountProvisioningURLParser(domain: appSettings.accountProvisioningHost),
ElementCallURLParser()
AccountProvisioningURLParser(domain: appSettings.accountProvisioningHost)
]
}
@@ -135,45 +132,6 @@ private struct AppGroupURLParser: URLParser {
}
}
/// The parser for Element Call links. This always returns a `.genericCallLink`.
private struct ElementCallURLParser: URLParser {
private let knownHosts = ["call.element.io"]
private let customSchemeURLQueryParameterName = "url"
func route(from url: URL) -> AppRoute? {
// Element Call not supported, WebRTC not available
// https://github.com/element-hq/element-x-ios/issues/1794
if ProcessInfo.processInfo.isiOSAppOnMac {
return nil
}
// First try processing URLs with custom schemes
if let scheme = url.scheme,
scheme == InfoPlistReader.app.elementCallScheme {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
guard let encodedURLString = components.queryItems?.first(where: { $0.name == customSchemeURLQueryParameterName })?.value,
let callURL = URL(string: encodedURLString),
callURL.scheme == "https" // Don't allow URLs from potentially unsafe domains
else {
MXLog.error("Invalid custom scheme call parameters: \(url)")
return nil
}
return .genericCallLink(url: callURL)
}
// Otherwise try to interpret it as an universal link
guard let host = url.host, knownHosts.contains(host) else {
return nil
}
return .genericCallLink(url: url)
}
}
private struct MatrixPermalinkParser: URLParser {
func route(from url: URL) -> AppRoute? {
guard let entity = parseMatrixEntityFrom(uri: url.absoluteString) else { return nil }

View File

@@ -48,25 +48,6 @@ import SwiftUI
sheetModule?.coordinator
}
fileprivate var overlayModule: NavigationModule? {
didSet {
if let oldValue {
logPresentationChange("Remove overlay", oldValue)
oldValue.tearDown()
}
if let overlayModule {
logPresentationChange("Set overlay", overlayModule)
overlayModule.coordinator?.start()
}
}
}
/// The currently displayed overlay coordinator
var overlayCoordinator: (any CoordinatorProtocol)? {
overlayModule?.coordinator
}
/// The lowest-level `AlertInfo`, directly available to the root of the app.
var alertInfo: AlertInfo<UUID>?
@@ -104,31 +85,6 @@ import SwiftUI
sheetModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}
}
/// Present an overlay on top of the split view
/// - Parameters:
/// - coordinator: the coordinator to display
/// - animated: whether the transition should be animated
/// - dismissalCallback: called when the overlay has been dismissed, programatically or otherwise
func setOverlayCoordinator(_ coordinator: (any CoordinatorProtocol)?,
animated: Bool = true,
dismissalCallback: (() -> Void)? = nil) {
guard let coordinator else {
overlayModule = nil
return
}
if overlayModule?.coordinator === coordinator {
fatalError("Cannot use the same coordinator more than once")
}
var transaction = Transaction()
transaction.disablesAnimations = !animated
withTransaction(transaction) {
overlayModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
}
}
// MARK: - CoordinatorProtocol
@@ -167,15 +123,5 @@ private struct NavigationRootCoordinatorView: View {
.sheet(item: $rootCoordinator.sheetModule) { module in
module.coordinator?.toPresentable()
}
.accessibilityHidden(rootCoordinator.overlayModule?.coordinator != nil)
.overlay {
Group {
if let coordinator = rootCoordinator.overlayModule?.coordinator {
coordinator.toPresentable()
.transition(.opacity)
}
}
.animation(.elementDefault, value: rootCoordinator.overlayModule)
}
}
}

View File

@@ -172,7 +172,7 @@ class ChatsTabFlowCoordinator: FlowCoordinatorProtocol {
}
case .globalSearch:
presentGlobalSearch()
case .accountProvisioningLink, .settings, .chatBackupSettings, .call, .genericCallLink:
case .accountProvisioningLink, .settings, .chatBackupSettings, .call:
break // These routes cannot be handled.
}
}

View File

@@ -83,7 +83,7 @@ class EncryptionSettingsFlowCoordinator: FlowCoordinatorProtocol {
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
.roomDetails, .roomMemberDetails, .userProfile, .thread,
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
.call, .genericCallLink, .settings, .share, .transferOwnership,
.call, .settings, .share, .transferOwnership,
.globalSearch:
// These routes aren't in this flow so clear the entire stack.
clearRoute(animated: animated)

View File

@@ -199,7 +199,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
}
case .roomAlias, .childRoomAlias, .eventOnRoomAlias, .childEventOnRoomAlias:
break // These are converted to a room ID route one level above.
case .accountProvisioningLink, .roomList, .userProfile, .call, .genericCallLink, .settings, .chatBackupSettings, .globalSearch:
case .accountProvisioningLink, .roomList, .userProfile, .call, .settings, .chatBackupSettings, .globalSearch:
break // These routes can't be handled.
case .transferOwnership(let roomID):
guard self.roomID == roomID else { fatalError("Navigation route doesn't belong to this room flow.") }

View File

@@ -119,7 +119,7 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
case .roomAlias, .childRoomAlias, .eventOnRoomAlias, .childEventOnRoomAlias:
break // These are converted to a room ID route one level above.
case .accountProvisioningLink, .roomList, .room, .roomDetails, .event,
.userProfile, .call, .genericCallLink, .settings, .chatBackupSettings,
.userProfile, .call, .settings, .chatBackupSettings,
.share, .transferOwnership, .thread, .globalSearch:
break
}

View File

@@ -132,8 +132,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
case .call(let roomID, let isVoiceCall):
Task { await presentCallScreen(roomID: roomID, isVoiceCall: isVoiceCall) }
case .genericCallLink(let url):
presentCallScreen(genericCallLink: url)
case .roomList, .room, .roomAlias, .childRoom, .childRoomAlias,
.roomDetails, .roomMemberDetails, .userProfile,
.event, .eventOnRoomAlias, .childEvent, .childEventOnRoomAlias,
@@ -401,10 +399,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - Calls
private func presentCallScreen(genericCallLink url: URL) {
presentCallScreen(configuration: .init(genericCallLink: url))
}
private func presentCallScreen(roomID: String, isVoiceCall: Bool) async {
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
return

View File

@@ -19,7 +19,6 @@ enum CallScreenViewModelAction {
struct CallScreenViewState: BindableState {
let script: String?
var url: URL?
let isGenericCallLink: Bool
let certificateValidator: CertificateValidatorHookProtocol

View File

@@ -48,18 +48,10 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
self.analyticsService = analyticsService
isPictureInPictureAllowed = allowPictureInPicture
var isGenericCallLink = false
switch configuration.kind {
case .genericCallLink(let url):
widgetDriver = GenericCallLinkWidgetDriver(url: url)
isGenericCallLink = true
case .roomCall(let roomProxy, let clientProxy, _, _, _, _, _):
guard let deviceID = clientProxy.deviceID else { fatalError("Missing device ID for the call.") }
widgetDriver = roomProxy.elementCallWidgetDriver(deviceID: deviceID)
}
guard let deviceID = configuration.clientProxy.deviceID else { fatalError("Missing device ID for the call.") }
widgetDriver = configuration.roomProxy.elementCallWidgetDriver(deviceID: deviceID)
super.init(initialViewState: CallScreenViewState(script: CallScreenJavaScriptMessageName.allCasesInjectionScript,
isGenericCallLink: isGenericCallLink,
certificateValidator: appHooks.certificateValidatorHook))
elementCallService.actions
@@ -162,67 +154,60 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
}
private func setupCall() {
switch configuration.kind {
case .genericCallLink(let url):
state.url = url
// We need widget messaging to work before enabling CallKit, otherwise mute, hangup etc do nothing.
Task { [weak self] in
guard let self else { return }
case .roomCall(let roomProxy, _, let clientID, let voiceOnly, let elementCallBaseURL, let elementCallBaseURLOverride, let colorScheme):
Task { [weak self] in
guard let self else { return }
let baseURL = if let elementCallBaseURLOverride {
elementCallBaseURLOverride
} else {
elementCallBaseURL
}
// We only set the analytics configuration if analytics are enabled
let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled {
.init(posthogAPIHost: appSettings.elementCallPosthogAPIHost,
posthogAPIKey: appSettings.elementCallPosthogAPIKey,
sentryDSN: appSettings.elementCallPosthogSentryDSN)
} else {
nil
}
let rageshakeURL: String? = if case let .url(baseURL) = appSettings.bugReportRageshakeURL.publisher.value {
baseURL.absoluteString
} else {
nil
}
switch await widgetDriver.start(baseURL: baseURL,
clientID: clientID,
colorScheme: colorScheme,
voiceOnly: voiceOnly,
rageshakeURL: rageshakeURL,
analyticsConfiguration: analyticsConfiguration) {
case .success(let url):
state.url = url
case .failure(let error):
MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)")
state.bindings.alertInfo = .init(id: UUID(),
title: L10n.errorUnknown,
primaryButton: .init(title: L10n.actionOk) {
self.actionsSubject.send(.dismiss)
})
return
}
await elementCallService.setupCallSession(roomID: roomProxy.id,
roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id)
let baseURL = if let baseURLOverride = configuration.elementCallBaseURLOverride {
baseURLOverride
} else {
configuration.elementCallBaseURL
}
timeoutTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(10))
guard !Task.isCancelled, let self else { return }
MXLog.error("Failed to join Element Call: Timeout")
// We only set the analytics configuration if analytics are enabled
let analyticsConfiguration: ElementCallAnalyticsConfiguration? = if analyticsService.isEnabled {
.init(posthogAPIHost: appSettings.elementCallPosthogAPIHost,
posthogAPIKey: appSettings.elementCallPosthogAPIKey,
sentryDSN: appSettings.elementCallPosthogSentryDSN)
} else {
nil
}
let rageshakeURL: String? = if case let .url(baseURL) = appSettings.bugReportRageshakeURL.publisher.value {
baseURL.absoluteString
} else {
nil
}
switch await widgetDriver.start(baseURL: baseURL,
clientID: configuration.clientID,
colorScheme: configuration.colorScheme,
voiceOnly: configuration.voiceOnly,
rageshakeURL: rageshakeURL,
analyticsConfiguration: analyticsConfiguration) {
case .success(let url):
state.url = url
case .failure(let error):
MXLog.error("Failed starting ElementCall Widget Driver with error: \(error)")
state.bindings.alertInfo = .init(id: UUID(),
title: L10n.commonError,
message: L10n.errorUnknown,
primaryButton: .init(title: L10n.actionDismiss) { [weak self] in self?.actionsSubject.send(.dismiss) })
timeoutTask = nil
title: L10n.errorUnknown,
primaryButton: .init(title: L10n.actionOk) {
self.actionsSubject.send(.dismiss)
})
return
}
await elementCallService.setupCallSession(roomID: configuration.roomProxy.id,
roomDisplayName: configuration.roomProxy.infoPublisher.value.displayName ?? configuration.roomProxy.id)
}
timeoutTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(10))
guard !Task.isCancelled, let self else { return }
MXLog.error("Failed to join Element Call: Timeout")
state.bindings.alertInfo = .init(id: UUID(),
title: L10n.commonError,
message: L10n.errorUnknown,
primaryButton: .init(title: L10n.actionDismiss) { [weak self] in self?.actionsSubject.send(.dismiss) })
timeoutTask = nil
}
}

View File

@@ -22,11 +22,10 @@ struct CallScreen: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.navigationBarTitleDisplayMode(.inline)
.toolbar(context.viewState.isGenericCallLink ? .visible : .hidden, for: .navigationBar)
.toolbar(.hidden, for: .navigationBar)
.toolbar { toolbar }
}
.alert(item: $context.alertInfo)
.preferredColorScheme(context.viewState.isGenericCallLink ? .dark : nil)
}
@ViewBuilder

View File

@@ -8,76 +8,18 @@
import SwiftUI
private enum GenericCallLinkQueryParameters {
static let appPrompt = "appPrompt"
static let confineToRoom = "confineToRoom"
}
/// Information about how a call should be configured.
struct ElementCallConfiguration {
enum Kind {
case genericCallLink(URL)
case roomCall(roomProxy: JoinedRoomProxyProtocol,
clientProxy: ClientProxyProtocol,
clientID: String,
voiceOnly: Bool,
elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?,
colorScheme: ColorScheme)
}
/// The type of call being configured i.e. whether it's an external URL or an internal room call.
let kind: Kind
/// Creates a configuration for an external call URL.
init(genericCallLink url: URL) {
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) {
var fragmentQueryItems = urlComponents.fragmentQueryItems ?? []
fragmentQueryItems.removeAll { $0.name == GenericCallLinkQueryParameters.appPrompt }
fragmentQueryItems.removeAll { $0.name == GenericCallLinkQueryParameters.confineToRoom }
fragmentQueryItems.append(.init(name: GenericCallLinkQueryParameters.appPrompt, value: "false"))
fragmentQueryItems.append(.init(name: GenericCallLinkQueryParameters.confineToRoom, value: "true"))
urlComponents.fragmentQueryItems = fragmentQueryItems
if let adjustedURL = urlComponents.url {
kind = .genericCallLink(adjustedURL)
} else {
MXLog.error("Failed adjusting URL with components: \(urlComponents)")
kind = .genericCallLink(url)
}
} else {
MXLog.error("Failed constructing URL components for url: \(url)")
kind = .genericCallLink(url)
}
}
/// Creates a configuration for an internal room call.
init(roomProxy: JoinedRoomProxyProtocol,
clientProxy: ClientProxyProtocol,
clientID: String,
elementCallBaseURL: URL,
elementCallBaseURLOverride: URL?,
voiceOnly: Bool,
colorScheme: ColorScheme) {
kind = .roomCall(roomProxy: roomProxy,
clientProxy: clientProxy,
clientID: clientID,
voiceOnly: voiceOnly,
elementCallBaseURL: elementCallBaseURL,
elementCallBaseURLOverride: elementCallBaseURLOverride,
colorScheme: colorScheme)
}
let roomProxy: JoinedRoomProxyProtocol
let clientProxy: ClientProxyProtocol
let clientID: String
let elementCallBaseURL: URL
let elementCallBaseURLOverride: URL?
let voiceOnly: Bool
let colorScheme: ColorScheme
/// A string representing the call being configured.
var callRoomID: String {
switch kind {
case .genericCallLink(let url):
url.absoluteString
case .roomCall(let roomProxy, _, _, _, _, _, _):
roomProxy.id
}
roomProxy.id
}
}

View File

@@ -1,41 +0,0 @@
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2024-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Combine
import SwiftUI
class GenericCallLinkWidgetDriver: ElementCallWidgetDriverProtocol {
private let url: URL
let widgetID = UUID().uuidString
let messagePublisher = PassthroughSubject<String, Never>()
private let actionsSubject: PassthroughSubject<ElementCallWidgetDriverAction, Never> = .init()
var actions: AnyPublisher<ElementCallWidgetDriverAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(url: URL) {
self.url = url
}
func start(baseURL: URL,
clientID: String,
colorScheme: ColorScheme,
voiceOnly: Bool,
rageshakeURL: String?,
analyticsConfiguration: ElementCallAnalyticsConfiguration?) async -> Result<URL, ElementCallWidgetDriverError> {
MXLog.error("Nothing to start, use the configuration's URL directly instead.")
return .success(url)
}
func handleMessage(_ message: String) async -> Result<Bool, ElementCallWidgetDriverError> {
// The web view doesn't send us messages through the Widget API, so nothing to implement (yet?).
.failure(.driverNotSetup)
}
}

View File

@@ -11,8 +11,6 @@
<string>applinks:staging.element.io</string>
<string>applinks:develop.element.io</string>
<string>applinks:mobile.element.io</string>
<string>applinks:call.element.io</string>
<string>applinks:call.element.dev</string>
<string>applinks:matrix.to</string>
<string>webcredentials:*.element.io</string>
</array>

View File

@@ -37,16 +37,6 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>Element Call</string>
<key>CFBundleURLSchemes</key>
<array>
<string>io.element.call</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>

View File

@@ -51,13 +51,6 @@ targets:
CFBundleShortVersionString: $(MARKETING_VERSION)
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
CFBundleURLTypes: [
{
CFBundleTypeRole: Editor,
CFBundleURLName: "Element Call",
CFBundleURLSchemes: [
io.element.call
]
},
{
CFBundleTypeRole: Editor,
CFBundleURLName: "Application",
@@ -126,8 +119,6 @@ targets:
- applinks:staging.element.io
- applinks:develop.element.io
- applinks:mobile.element.io
- applinks:call.element.io
- applinks:call.element.dev
- applinks:matrix.to
# - applinks:localhost?mode=developer # developer mode only works with https (but self-signed is fine).
- webcredentials:*.element.io

View File

@@ -20,43 +20,6 @@ struct AppRouteURLParserTests {
appRouteURLParser = AppRouteURLParser(appSettings: appSettings)
}
@Test
func elementCallRoutes() throws {
let url = try #require(URL(string: "https://call.element.io/test"))
#expect(appRouteURLParser.route(from: url) == AppRoute.genericCallLink(url: url))
let customSchemeURL = try #require(URL(string: "io.element.call:/?url=https%3A%2F%2Fcall.element.io%2Ftest"))
#expect(appRouteURLParser.route(from: customSchemeURL) == AppRoute.genericCallLink(url: url))
}
@Test
func customDomainUniversalLinkCallRoutes() throws {
let url = try #require(URL(string: "https://somecustomdomain.element.io/test"))
#expect(appRouteURLParser.route(from: url) == nil)
}
@Test
func customSchemeLinkCallRoutes() throws {
let urlString = "https://somecustomdomain.element.io/test?param=123"
let url = try #require(URL(string: urlString))
let encodedURLString = try #require(urlString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed))
let customSchemeURL = try #require(URL(string: "io.element.call:/?url=\(encodedURLString)"))
#expect(appRouteURLParser.route(from: customSchemeURL) == AppRoute.genericCallLink(url: url))
}
@Test
func httpCustomSchemeLinkCallRoutes() throws {
let customSchemeURL = try #require(URL(string: "io.element.call:/?url=http%3A%2F%2Fcall.element.io%2Ftest"))
#expect(appRouteURLParser.route(from: customSchemeURL) == nil)
}
@Test
func matrixUserURL() throws {
let userID = "@test:matrix.org"

View File

@@ -33,23 +33,6 @@ struct NavigationRootCoordinatorTests {
assertCoordinatorsEqual(secondRootCoordinator, navigationRootCoordinator.rootCoordinator)
}
@Test
func overlay() {
let rootCoordinator = SomeTestCoordinator()
navigationRootCoordinator.setRootCoordinator(rootCoordinator)
let overlayCoordinator = SomeTestCoordinator()
navigationRootCoordinator.setOverlayCoordinator(overlayCoordinator)
assertCoordinatorsEqual(rootCoordinator, navigationRootCoordinator.rootCoordinator)
assertCoordinatorsEqual(overlayCoordinator, navigationRootCoordinator.overlayCoordinator)
navigationRootCoordinator.setOverlayCoordinator(nil)
assertCoordinatorsEqual(rootCoordinator, navigationRootCoordinator.rootCoordinator)
#expect(navigationRootCoordinator.overlayCoordinator == nil)
}
// MARK: - Dismissal Callbacks
@Test
@@ -67,19 +50,6 @@ struct NavigationRootCoordinatorTests {
}
}
@Test
func overlayDismissalCallback() async {
let overlayCoordinator = SomeTestCoordinator()
await confirmation("Wait for callback") { confirm in
navigationRootCoordinator.setOverlayCoordinator(overlayCoordinator) {
confirm()
}
navigationRootCoordinator.setOverlayCoordinator(nil)
}
}
// MARK: - Private
private func assertCoordinatorsEqual(_ lhs: CoordinatorProtocol?, _ rhs: CoordinatorProtocol?) {