diff --git a/DemoApp/Sources/AppDelegate.swift b/DemoApp/Sources/AppDelegate.swift index 7b44db297..85703654f 100644 --- a/DemoApp/Sources/AppDelegate.swift +++ b/DemoApp/Sources/AppDelegate.swift @@ -26,31 +26,6 @@ class AppDelegate: NSObject, UIApplicationDelegate, @MainActor UNUserNotificatio return true } - /// Method used to handle custom URL schemes - func application( - _ app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - Router.shared.handle(url: url) - return true - } - - /// Method used to handle universal deeplinks - func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: @escaping ( - [UIUserActivityRestoring]? - ) -> Void - ) -> Bool { - guard let url = userActivity.webpageURL else { - return false - } - Router.shared.handle(url: url) - return true - } - func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data diff --git a/DemoApp/Sources/Components/Router.swift b/DemoApp/Sources/Components/Router.swift index b4a90d173..8533535d4 100644 --- a/DemoApp/Sources/Components/Router.swift +++ b/DemoApp/Sources/Components/Router.swift @@ -77,6 +77,20 @@ final class Router: ObservableObject { return } + let deeplinkCallCid = callCid( + from: deeplinkInfo.callId, + callType: deeplinkInfo.callType + ) + + if + deeplinkInfo.baseURL == AppEnvironment.baseURL, + appState.activeCall?.cId == deeplinkCallCid { + log.debug( + "Request to handle deeplink \(url) ignored because the call is already active." + ) + return + } + if deeplinkInfo.baseURL != AppEnvironment.baseURL, let currentUser = appState.currentUser { diff --git a/DemoApp/Sources/Extensions/DemoApp+Sentry.swift b/DemoApp/Sources/Extensions/DemoApp+Sentry.swift index a2f4d748c..74ebff6dc 100644 --- a/DemoApp/Sources/Extensions/DemoApp+Sentry.swift +++ b/DemoApp/Sources/Extensions/DemoApp+Sentry.swift @@ -6,7 +6,14 @@ import Foundation import Sentry import StreamVideo +private let terminalLogsEnvironmentKey = "STREAM_VIDEO_TERMINAL_LOGS" + func configureSentry() { + let terminalDestinationTypes: [LogDestination.Type] = + ProcessInfo.processInfo.environment[terminalLogsEnvironmentKey] == "1" + ? [ConsoleLogDestination.self] + : [] + if AppEnvironment.configuration.isRelease { // We're tracking Crash Reports / Issues from the Demo App to keep improving the SDK SentrySDK.start { options in @@ -20,24 +27,18 @@ func configureSentry() { ] } - LogConfig.destinationTypes = resolving( - baseTypes: [ - SentryLogDestination.self, - MemoryLogDestination.self, - OSLogDestination.self - ], - environment: ProcessInfo.processInfo.environment - ) + LogConfig.destinationTypes = [ + SentryLogDestination.self, + MemoryLogDestination.self, + OSLogDestination.self + ] + terminalDestinationTypes } else { LogConfig.level = .debug LogConfig.webRTCLogsEnabled = true - LogConfig.destinationTypes = resolving( - baseTypes: [ - MemoryLogDestination.self, - OSLogDestination.self - ], - environment: ProcessInfo.processInfo.environment - ) + LogConfig.destinationTypes = [ + MemoryLogDestination.self, + OSLogDestination.self + ] + terminalDestinationTypes } } diff --git a/DemoApp/Sources/Views/CallView/CallingView/DemoCallingViewModifier.swift b/DemoApp/Sources/Views/CallView/CallingView/DemoCallingViewModifier.swift index 22160f191..5a2859967 100644 --- a/DemoApp/Sources/Views/CallView/CallingView/DemoCallingViewModifier.swift +++ b/DemoApp/Sources/Views/CallView/CallingView/DemoCallingViewModifier.swift @@ -39,22 +39,7 @@ struct DemoCallingViewModifier: ViewModifier { .alignedToReadableContentGuide() .background(appearance.colors.lobbyBackground.edgesIgnoringSafeArea(.all)) .onReceive(appState.$deeplinkInfo) { deeplinkInfo in - guard - !isAnonymous, - deeplinkInfo.callId != self.text.wrappedValue - else { return } - - // We may get in this situation when launching the app from a - // deeplink. - if deeplinkInfo.callId.isEmpty { - joinCallIfNeeded(with: self.text.wrappedValue, callType: callType) - } else { - self.text.wrappedValue = deeplinkInfo.callId - joinCallIfNeeded( - with: self.text.wrappedValue, - callType: callType - ) - } + autoJoinIfNeeded(from: deeplinkInfo) } .onChange(of: viewModel.callingState) { callingState in switch callingState { @@ -76,7 +61,7 @@ struct DemoCallingViewModifier: ViewModifier { callKitAdapter.registerForIncomingCalls() callKitAdapter.iconTemplateImageData = UIImage(named: "logo")?.pngData() configureVideoRenderingOptions() - joinCallIfNeeded(with: text.wrappedValue, callType: callType) + autoJoinIfNeeded(from: appState.deeplinkInfo) } .onReceive(appState.$activeCall) { call in viewModel.setActiveCall(call) @@ -113,10 +98,24 @@ struct DemoCallingViewModifier: ViewModifier { } catch { log.error(error) } - AppState.shared.deeplinkInfo = .empty } } + private func autoJoinIfNeeded(from deeplinkInfo: DeeplinkInfo) { + guard !isAnonymous, !deeplinkInfo.callId.isEmpty else { + return + } + + let resolvedCallType = deeplinkInfo.callType.isEmpty + ? callType + : deeplinkInfo.callType + + callType = resolvedCallType + text.wrappedValue = deeplinkInfo.callId + appState.deeplinkInfo = .empty + joinCallIfNeeded(with: deeplinkInfo.callId, callType: resolvedCallType) + } + private func configureVideoRenderingOptions() { InjectedValues[\.videoRenderingOptions] = .init( backend: AppEnvironment.videoRenderingBackend.rawBackend, diff --git a/SwiftUIDemoAppUITests/Tests/DeeplinkTests.swift b/SwiftUIDemoAppUITests/Tests/DeeplinkTests.swift index de015c7b8..d42cdd4e0 100644 --- a/SwiftUIDemoAppUITests/Tests/DeeplinkTests.swift +++ b/SwiftUIDemoAppUITests/Tests/DeeplinkTests.swift @@ -7,24 +7,14 @@ import XCTest // Requires running a standalone Sinatra server final class DeeplinkTests: StreamTestCase { - override static func setUp() { - super.setUp() - - // We are launching and terminating the app to ensure the executable - // has been installed. - app.launch() - app.terminate() - } - - override func setUpWithError() throws { - launchApp = false - try super.setUpWithError() - } - private enum MockDeeplink { static let deeplinkUrlWithCallIdInPath: URL = .init(string: "https://getstream.io/video/demos/join/test-call")! static let customScheme: URL = .init(string: "streamvideo://video/demos?id=test-call")! static let customSchemeWithCallIdInPath: URL = .init(string: "streamvideo://video/demos/join/test-call")! + + static func customScheme(callId: String) -> URL { + .init(string: "streamvideo://video/demos?id=\(callId)")! + } } func test_associationFile_validationWasSuccessful() throws { @@ -96,4 +86,36 @@ final class DeeplinkTests: StreamTestCase { .assertParticipantsAreVisible(count: 1) } } + + func test_customSchemeURL_forActiveCall_doesNotRejoinAfterLeaving() throws { + GIVEN("user starts the call referenced by the deeplink") { + userRobot + .waitForAutoLogin() + .startCall(callId) + } + WHEN("user opens a deeplink for the active call") { + openURL(MockDeeplink.customScheme(callId: callId)) + } + AND("user leaves the call") { + userRobot.endCall() + } + THEN("user stays out of the call") { + userRobot.assertThereAreNoCallControls() + XCTAssertTrue( + CallDetailsPage.callIdInputField.wait().exists, + "callIdInputField should appear" + ) + + let rejoinExpectation = XCTNSPredicateExpectation( + predicate: NSPredicate(format: "exists == true"), + object: CallPage.hangUpButton + ) + rejoinExpectation.isInverted = true + + XCTAssertEqual( + XCTWaiter.wait(for: [rejoinExpectation], timeout: 3), + .completed + ) + } + } }