跳轉到

Presence

Presence tells you whether a friend has the App open. Each device connects to an MQTT broker and reports a status; the broker passes it on to the user's friends. PresenceManager handles it, through Revue.presence on Revue.shared, and your RevueDelegate receives the presence events.

Call every PresenceManager method on the main thread.

To start presence, switch accounts or stop it, see Start presence after sign-in on the Initialization page.

已知問題

呼叫 stopPresence() 後,只要雙方的好友連線還在,好友可能看到這位使用者先變 .offline,又變回 .online 並一直維持。修好前,如果一定要讓好友看到離線,請呼叫 destroy()。

What presence is, and what it is not

Presence is one of three separate things:

Answers Read with
Presence Does the broker see the friend with the App open? PresenceManager.getFriendStatus(friendId:)
Friend connection Is there a path between your device and the friend's? FriendManager.getFriendPaths(friendId:) (see Connection state)
Discoverable Can other users find you on the network? Revue.connectionState (see Connection state)

A friend the broker sees as online is not necessarily reachable: a call needs a friend connection. To decide whether a call can go through, look at the friend connection, not at presence.

Once presence is connected, the SDK also exchanges connection details with friends automatically, which helps friend connections form faster.

Your own status

Set the status your friends see with PresenceManager.setStatus(_:). It accepts four values: PresenceStatus.online, PresenceStatus.away, PresenceStatus.busy and PresenceStatus.offline. Do not pass PresenceStatus.unknown; it describes a friend whose status cannot be read, not a status you can set.

Invisible. PresenceManager.setStatus(_:) with PresenceStatus.offline makes friends see you as offline. Friend connections and calls are not affected: friends can still reach you and call you.

import RevueSDK

/// What the user picks in the App's status menu.
enum StatusChoice {
    case available, away, busy, invisible
}

/// Set your own presence status. Invisible is `.offline`: friends see you offline, but friend
/// connections and calls keep working.
@MainActor
func setOwnPresence(_ choice: StatusChoice) -> Bool {
    let status: PresenceStatus
    switch choice {
    case .available: status = .online
    case .away: status = .away
    case .busy: status = .busy
    case .invisible: status = .offline
    }
    // Works before the broker is connected too: the SDK sends it once connected.
    return Revue.shared.presence.setStatus(status)
}

/// Read back what you set. Before the first `setStatus(_:)`, this is `.online`.
@MainActor
func currentOwnPresence() -> PresenceStatus {
    Revue.shared.presence.getStatus()
}

Presence status or user status

The SDK has a second status, UserStatus, set with Revue.setSelfStatus(_:) (see Friends). They travel differently:

Sent through A friend receives it
PresenceStatus The broker Without a friend connection between you
UserStatus The friend connection Only once the friend connection is up

For away and busy in a friend list, use PresenceStatus: friends see it even before a friend connection forms.

Friends' status

Two methods read a friend's presence status, and they return the same value: PresenceManager.getFriendStatus(friendId:) and FriendManager.getFriendPresenceStatus(friendId:). They differ only before the SDK is initialized: the first returns PresenceStatus.unknown, the second PresenceStatus.offline.

When a friend's status changes, your RevueDelegate receives didUpdatePresenceStatus with the friend ID and the new PresenceStatus.

import RevueSDK

/// What a friend list row shows for a friend's presence status.
enum PresenceLabel {
    case available, away, busy, offline, unknown
}

func presenceLabel(_ status: PresenceStatus) -> PresenceLabel {
    switch status {
    case .online: return .available
    case .away: return .away
    case .busy: return .busy
    case .offline: return .offline
    case .unknown: return .unknown
    }
}

/// Read a friend's presence status. Only trust it while the broker connection is `.connected`.
@MainActor
func loadFriendPresence(friendId: String) -> PresenceLabel {
    let presence = Revue.shared.presence
    guard presence.getConnectionState() == .connected else { return .unknown }
    return presenceLabel(presence.getFriendStatus(friendId: friendId))
}

/// Keep the friend list current: friends' status changes, and your own broker connection.
final class FriendPresenceObserver: RevueDelegate {
    var onFriendPresenceChanged: ((String, PresenceLabel) -> Void)?
    var onBrokerConnectionChanged: ((Bool) -> Void)?

    func revue(_ revue: Revue, friend friendId: String, didUpdatePresenceStatus status: PresenceStatus) {
        onFriendPresenceChanged?(friendId, presenceLabel(status))
    }

    func revue(_ revue: Revue, didUpdatePresenceConnectionState state: PresenceConnectionState) {
        // Anything other than .connected: every friend's status reads as .unknown until it comes back.
        onBrokerConnectionChanged?(state == .connected)
    }
}

Connection to the broker

PresenceManager.getConnectionState() returns your own connection to the broker as a PresenceConnectionState, and your RevueDelegate receives didUpdatePresenceConnectionState whenever it changes:

State Meaning
PresenceConnectionState.notInitialized Presence is not started.
PresenceConnectionState.connecting Connecting for the first time.
PresenceConnectionState.connected Connected. Friends' statuses are reliable only in this state.
PresenceConnectionState.reconnecting The connection was up and dropped; the SDK is reconnecting.
PresenceConnectionState.disconnected Disconnected.

Detecting a lost network

  • Your network drops. The SDK closes the broker connection right away. You receive didUpdatePresenceConnectionState with a state other than PresenceConnectionState.connected, and every friend's status reads as PresenceStatus.unknown. When the network comes back, the SDK reconnects on its own.
  • A friend's network drops. The friend cannot tell the broker. The broker notices when it stops hearing from the friend's device and then announces the friend as offline for them, so you receive didUpdatePresenceStatus with PresenceStatus.offline. This takes some time.
  • To know whether a call to a friend can go through, look at the friend connection (paths), not at presence.