Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions Sources/BandwidthRTC/BandwidthRTC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public final class BandwidthRTCClient: @unchecked Sendable {
/// Called when the BRTC platform signals readiness.
public var onReady: (@Sendable (ReadyMetadata) -> Void)?

/// Called when the gateway reports a connect status update (terminal outcome of a `<Connect>` verb).
public var onConnectStatus: (@Sendable (ReadyMetadata) -> Void)?

/// Called when the remote side disconnects (subscribe ICE disconnected/failed).
public var onRemoteDisconnected: (@Sendable () -> Void)?

Expand Down Expand Up @@ -325,7 +328,9 @@ public final class BandwidthRTCClient: @unchecked Sendable {
}
}

// Handle ready event (may arrive after connect, e.g. for reconnection)
// Handle ready event. When connectStatus is present the gateway is reporting
// the outcome of a <Connect> verb; route to onConnectStatus. Otherwise this
// is the normal session-ready signal; route to onReady.
await signaling.onEvent("ready") { [weak self] data in
guard let self else { return }

Expand All @@ -336,13 +341,13 @@ public final class BandwidthRTCClient: @unchecked Sendable {
metadata = (try? JSONDecoder().decode(ReadyMetadata.self, from: data)) ?? ReadyMetadata()
}

Logger.shared.debug("Ready event: endpoint=\(metadata.endpointId ?? "nil")")
self.onReady?(metadata)
}

// Handle established event
await signaling.onEvent("established") { _ in
Logger.shared.debug("Connection established")
if metadata.connectStatus != nil {
Logger.shared.debug("Connect status via ready: status=\(String(describing: metadata.connectStatus!))")
self.onConnectStatus?(metadata)
} else {
Logger.shared.debug("Ready event: endpoint=\(metadata.endpointId ?? "nil")")
self.onReady?(metadata)
}
}

// Handle disconnect
Expand Down
11 changes: 11 additions & 0 deletions Sources/BandwidthRTC/Types/ConnectStatus.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

/// The terminal outcome of a `<Connect>` verb execution.
public enum ConnectStatus: String, Decodable, Sendable {
case initiated = "INITIATED"
case completed = "COMPLETED"
case timedOut = "TIMED_OUT"
case denied = "DENIED"
case canceled = "CANCELED"
case failed = "FAILED"
}
34 changes: 33 additions & 1 deletion Sources/BandwidthRTC/Types/ReadyMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,43 @@ public struct ReadyMetadata: Decodable, Sendable {
public let deviceId: String?
public let territory: String?
public let region: String?
public let connectStatus: ConnectStatus?
public let accountId: String?
public let sessionId: String?
public let from: String?
public let fromType: String?
public let fromTags: String?
public let to: String?
public let toType: String?
public let toTags: String?

public init(endpointId: String? = nil, deviceId: String? = nil, territory: String? = nil, region: String? = nil) {
public init(
endpointId: String? = nil,
deviceId: String? = nil,
territory: String? = nil,
region: String? = nil,
connectStatus: ConnectStatus? = nil,
accountId: String? = nil,
sessionId: String? = nil,
from: String? = nil,
fromType: String? = nil,
fromTags: String? = nil,
to: String? = nil,
toType: String? = nil,
toTags: String? = nil
) {
self.endpointId = endpointId
self.deviceId = deviceId
self.territory = territory
self.region = region
self.connectStatus = connectStatus
self.accountId = accountId
self.sessionId = sessionId
self.from = from
self.fromType = fromType
self.fromTags = fromTags
self.to = to
self.toType = toType
self.toTags = toTags
}
}
82 changes: 82 additions & 0 deletions Tests/BandwidthRTCTests/BandwidthRTCTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,88 @@ final class BandwidthRTCTests: XCTestCase {
XCTAssertTrue(streamCallbackFired)
}

// MARK: - Connect Status

func testReadyEventWithConnectStatusFiresOnConnectStatusCallback() async throws {
let sig = MockSignalingClient()
let sut = makeSUT(signaling: sig)
try await sut.connect(authParams: validAuthParams)

var receivedMetadata: ReadyMetadata?
sut.onConnectStatus = { metadata in
receivedMetadata = metadata
}

let readyJson = """
{"endpointId":"ep-1","deviceId":"dev-1","connectStatus":"COMPLETED","accountId":"9900000","sessionId":"session-1","from":"ep-1","fromType":"ENDPOINT","fromTags":"tag1","to":"ep-2","toType":"ENDPOINT","toTags":"tag2"}
""".data(using: .utf8)!
sig.triggerEvent("ready", data: readyJson)
try await Task.sleep(for: .milliseconds(50))

XCTAssertEqual(receivedMetadata?.connectStatus, .completed)
XCTAssertEqual(receivedMetadata?.accountId, "9900000")
XCTAssertEqual(receivedMetadata?.sessionId, "session-1")
XCTAssertEqual(receivedMetadata?.from, "ep-1")
XCTAssertEqual(receivedMetadata?.fromType, "ENDPOINT")
XCTAssertEqual(receivedMetadata?.fromTags, "tag1")
XCTAssertEqual(receivedMetadata?.to, "ep-2")
XCTAssertEqual(receivedMetadata?.toType, "ENDPOINT")
XCTAssertEqual(receivedMetadata?.toTags, "tag2")
}

func testReadyEventWithConnectStatusDoesNotFireOnReady() async throws {
let sig = MockSignalingClient()
let sut = makeSUT(signaling: sig)
try await sut.connect(authParams: validAuthParams)

var onReadyFired = false
sut.onReady = { _ in onReadyFired = true }

let readyJson = """
{"endpointId":"ep-1","connectStatus":"COMPLETED","accountId":"1234"}
""".data(using: .utf8)!
sig.triggerEvent("ready", data: readyJson)
try await Task.sleep(for: .milliseconds(50))

XCTAssertFalse(onReadyFired)
}

func testReadyEventWithoutConnectStatusFiresOnReady() async throws {
let sig = MockSignalingClient()
let sut = makeSUT(signaling: sig)

var receivedMetadata: ReadyMetadata?
sut.onReady = { metadata in
receivedMetadata = metadata
}
try await sut.connect(authParams: validAuthParams)

let readyJson = """
{"endpointId":"test-ep","deviceId":"dev-1","territory":"US","region":"us-east-1"}
""".data(using: .utf8)!
sig.triggerEvent("ready", data: readyJson)
try await Task.sleep(for: .milliseconds(50))

XCTAssertEqual(receivedMetadata?.endpointId, "test-ep")
XCTAssertNil(receivedMetadata?.connectStatus)
}

func testConnectStatusEventNoLongerRegistered() async throws {
let sig = MockSignalingClient()
let sut = makeSUT(signaling: sig)
try await sut.connect(authParams: validAuthParams)

XCTAssertFalse(sig.hasEventHandler(for: "connectStatus"))
}

func testEstablishedEventNoLongerRegistered() async throws {
let sig = MockSignalingClient()
let sut = makeSUT(signaling: sig)
try await sut.connect(authParams: validAuthParams)

XCTAssertFalse(sig.hasEventHandler(for: "established"))
}

func testCloseEventSetsNotConnected() async throws {
let sig = MockSignalingClient()
let sut = makeSUT(signaling: sig)
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.2
1.1.0
Loading