Skip to content

Commit

Permalink
[Deeplink]Support path callId (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
ipavlidakis authored Dec 22, 2023
1 parent 1eeba21 commit dd0fd92
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 45 deletions.
22 changes: 19 additions & 3 deletions DemoApp/Sources/Components/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@ extension AppEnvironment {

enum BaseURL: String, Debuggable, CaseIterable {
case pronto = "https://pronto.getstream.io"
case staging = "https://staging.getstream.io"
case demo = "https://getstream.io"
case legacy = "https://stream-calls-dogfood.vercel.app"

var url: URL { URL(string: rawValue)! }
var title: String {
switch self {
case .pronto:
return "Pronto"
case .staging:
return "Staging"
case .legacy:
return "Legacy"
case .demo:
return "Demo"
}
Expand Down Expand Up @@ -185,35 +191,45 @@ extension AppEnvironment {

extension AppEnvironment {

enum SupportedDeeplink: Debuggable {
enum SupportedDeeplink: Debuggable, CaseIterable {
case pronto
case staging
case demo
case legacy

var deeplinkURL: URL {
switch self {
case .pronto:
return BaseURL.pronto.url
case .staging:
return BaseURL.staging.url
case .demo:
return BaseURL.demo.url
case .legacy:
return BaseURL.legacy.url
}
}

var title: String {
switch self {
case .pronto:
return "Pronto"
case .staging:
return "Staging"
case .demo:
return "Demo"
case .legacy:
return "Legacy"
}
}
}

static var supportedDeeplinks: [SupportedDeeplink] = {
switch configuration {
case .debug:
return [.pronto, .demo]
return [.pronto, .demo, .staging, .legacy]
case .test:
return [.pronto, .demo]
return [.pronto, .demo, .staging, .legacy]
case .release:
return [.demo]
}
Expand Down
26 changes: 14 additions & 12 deletions DemoApp/Sources/Components/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,21 @@ final class AppState: ObservableObject {
}
}

func logout() {
Task {
if let voipPushToken = unsecureRepository.currentVoIPPushToken() {
_ = try? await streamVideo?.deleteDevice(id: voipPushToken)
}
if let pushToken = unsecureRepository.currentPushToken() {
_ = try? await streamVideo?.deleteDevice(id: pushToken)
}
await streamVideo?.disconnect()
unsecureRepository.removeCurrentUser()
streamVideo = nil
userState = .notLoggedIn
func logout() async {
if let voipPushToken = unsecureRepository.currentVoIPPushToken() {
_ = try? await streamVideo?.deleteDevice(id: voipPushToken)
}
if let pushToken = unsecureRepository.currentPushToken() {
_ = try? await streamVideo?.deleteDevice(id: pushToken)
}
await streamVideo?.disconnect()
unsecureRepository.removeCurrentUser()
streamVideo = nil
userState = .notLoggedIn
}

func dispatchLogout() {
Task { await logout() }
}

// MARK: - Private API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ enum AuthenticationProvider {
} else {
let environment = {
switch AppEnvironment.baseURL {
case .staging:
return "pronto"
case .pronto:
return "pronto"
case .legacy:
return "pronto"
case .demo:
return "demo"
}
Expand Down
53 changes: 49 additions & 4 deletions DemoApp/Sources/Components/Deeplinks/DeeplinkAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ import Foundation
import StreamVideo

struct DeeplinkInfo: Equatable {
var url: URL?
var callId: String
var callType: String
var baseURL: AppEnvironment.BaseURL

static let empty = DeeplinkInfo(callId: "", callType: "")
static let empty = DeeplinkInfo(
url: nil,
callId: "",
callType: "",
baseURL: AppEnvironment.baseURL
)
}

struct DeeplinkAdapter {
Expand All @@ -18,25 +25,63 @@ struct DeeplinkAdapter {
return true
}

return AppEnvironment
let result = AppEnvironment
.supportedDeeplinks
.compactMap(\.deeplinkURL.host)
.first { url.host == $0 } != nil

return result
}

func handle(url: URL) -> (deeplinkInfo: DeeplinkInfo, user: User?) {
guard canHandle(url: url) else {
return (.empty, nil)
}

let pathComponentsCount = url.pathComponents.endIndex

// Fetch the callId from the path components
// e.g https://getstream.io/join/path-call-id
let callPathId: String? = {
guard
pathComponentsCount >= 2,
url.pathComponents[pathComponentsCount - 2] == "join",
let callId = url.pathComponents.last
else {
return nil
}
return callId
}()

guard let callId = url.queryParameters["id"] else {
// Fetch the callId from the query parameters
// e.g https://getstream.io/video/demos?id=parameter-call-id
let callParameterId = url.queryParameters["id"]

guard
// Use the the callPathId with higher priority if it's available.
let callId = callPathId ?? callParameterId
else {
log.warning("Unable to handle deeplink because id was missing.")
return (.empty, nil)
}

let callType = url.queryParameters["type"] ?? "default"

log.debug("Deeplink handled was: \(url)")
return (DeeplinkInfo(callId: callId, callType: callType), nil)
let host = url.host
let baseURL: AppEnvironment.BaseURL = AppEnvironment
.BaseURL
.allCases
.first { $0.url.host == host } ?? AppEnvironment.baseURL

return (
DeeplinkInfo(
url: url,
callId: callId,
callType: callType,
baseURL: baseURL
),
nil
)
}
}
21 changes: 16 additions & 5 deletions DemoApp/Sources/Components/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,23 @@ final class Router: ObservableObject {
return
}

log.debug("Request to handle deeplink \(url) accepted ✅")
if streamVideoUI != nil {
appState.deeplinkInfo = deeplinkInfo
} else {
if
deeplinkInfo.baseURL != AppEnvironment.baseURL,
let currentUser = appState.currentUser
{
Task {
try await handleGuestUser(deeplinkInfo: deeplinkInfo)
await appState.logout()
AppEnvironment.baseURL = deeplinkInfo.baseURL
try await handleLoggedInUserCredentials(.init(userInfo: currentUser, token: .empty), deeplinkInfo: deeplinkInfo)
}
} else {
log.debug("Request to handle deeplink \(url) accepted ✅")
if streamVideoUI != nil {
appState.deeplinkInfo = deeplinkInfo
} else {
Task {
try await handleGuestUser(deeplinkInfo: deeplinkInfo)
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions DemoApp/Sources/DemoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct DemoApp: App {
}
}
}
.preferredColorScheme(.dark)
.onOpenURL { router.handle(url: $0) }
.onContinueUserActivity(
NSUserActivityTypeBrowsingWeb
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ struct DetailedCallingView: View {
message: Text("Are you sure you want to sign out?"),
primaryButton: .destructive(Text("Sign out")) {
withAnimation {
AppState.shared.logout()
AppState.shared.dispatchLogout()
}
},
secondaryButton: .cancel()
Expand All @@ -127,7 +127,7 @@ struct DetailedCallingView: View {
.onChange(of: viewModel.call?.callId, perform: { [callId = viewModel.call?.callId] newValue in
if newValue == nil, callId != nil, !appState.activeAnonymousCallId.isEmpty {
appState.activeAnonymousCallId = ""
appState.logout()
appState.dispatchLogout()
}
})
.onReceive(appState.$activeCall) { call in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ struct SimpleCallingView: View {
@Injected(\.appearance) var appearance

@State var text = ""
@State private var changeEnvironmentPromptForURL: URL?
@State private var showChangeEnvironmentPrompt: Bool = false

private var callId: String
@ObservedObject var appState = AppState.shared
Expand Down Expand Up @@ -57,11 +59,15 @@ struct SimpleCallingView: View {
.foregroundColor(appearance.colors.text)
.padding(.all, 12)

DemoQRCodeScannerButton(viewModel: viewModel) { self.text = $0 ?? self.text }
DemoQRCodeScannerButton(viewModel: viewModel) { handleDeeplink($0) }
}
.background(Color(appearance.colors.background))
.clipShape(RoundedRectangle(cornerRadius: 8))
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color(appearance.colors.textLowEmphasis), lineWidth: 1))
.changeEnvironmentIfRequired(
showPrompt: $showChangeEnvironmentPrompt,
environmentURL: $changeEnvironmentPromptForURL
)

Button {
resignFirstResponder()
Expand All @@ -73,6 +79,7 @@ struct SimpleCallingView: View {
.disabled(appState.loading || text.isEmpty)
}
.disabled(appState.loading || text.isEmpty)

}

HStack {
Expand Down Expand Up @@ -132,4 +139,28 @@ struct SimpleCallingView: View {
}
}
}

private func handleDeeplink(_ deeplinkInfo: DeeplinkInfo?) {
guard let deeplinkInfo else {
self.text = ""
return
}

if deeplinkInfo.baseURL == AppEnvironment.baseURL {
self.text = deeplinkInfo.callId
} else if let url = deeplinkInfo.url {
self.changeEnvironmentPromptForURL = url
DispatchQueue
.main
.asyncAfter(deadline: .now() + 0.1) {
self.showChangeEnvironmentPrompt = true
}
}
}
}

extension URL: Identifiable {
public var id: ObjectIdentifier {
.init(self.absoluteString as NSString)
}
}
2 changes: 1 addition & 1 deletion DemoApp/Sources/Views/CallView/DemoCallingTopView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ struct DemoCallingTopView: View {
message: Text("Are you sure you want to sign out?"),
primaryButton: .destructive(Text("Sign out")) {
withAnimation {
AppState.shared.logout()
AppState.shared.dispatchLogout()
}
},
secondaryButton: .cancel()
Expand Down
14 changes: 9 additions & 5 deletions DemoApp/Sources/Views/CallView/DemoQRCodeScannerButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ struct DemoQRCodeScannerButton: View {

@State private var isQRScannerPresented = false
@ObservedObject var viewModel: CallViewModel
private let completion: (String?) -> Void
private let completion: (DeeplinkInfo?) -> Void
private let deeplinkAdapter: DeeplinkAdapter

init(
viewModel: CallViewModel,
completion: @escaping (String?) -> Void
completion: @escaping (DeeplinkInfo?) -> Void
) {
self.viewModel = viewModel
self.completion = completion
Expand All @@ -40,14 +40,18 @@ struct DemoQRCodeScannerButton: View {
case .success(let scanResult):
if let url = URL(string: scanResult.string), url.isWeb {
if deeplinkAdapter.canHandle(url: url) {
let callId = deeplinkAdapter.handle(url: url).deeplinkInfo.callId
completion(callId)
let deeplinkInfo = deeplinkAdapter.handle(url: url).deeplinkInfo
completion(deeplinkInfo)
} else {
viewModel.toast = Toast(style: .error, message: "The recognised URL from the QR code isn't supported.")
completion(nil)
}
} else {
completion(scanResult.string)
completion(.init(
callId: scanResult.string,
callType: .default,
baseURL: AppEnvironment.baseURL
))
}
case .failure(let error):
log.error(error)
Expand Down
Loading

0 comments on commit dd0fd92

Please sign in to comment.