pub const REASON_DISCONNECTED_BY_USER: &str = "disconnected by user";
pub const REASON_MANUAL_DISCONNECT: &str = "manual disconnect";
pub const REASON_SESSION_TOKEN_REVOKED: &str = "session-token-revoked";
pub const REASON_CLOSED: &str = "closed";
pub const REASON_DUPLICATE_KEPT_EXISTING: &str = "duplicate-kept-existing";
pub const REASON_DUPLICATE_ACCEPT_SUPERSEDED: &str = "duplicate-accept-superseded";
pub const REASON_DUPLICATE_DIAL_SUPERSEDED: &str = "duplicate-dial-superseded";
pub const REASON_REPLACED_BY_NEW_INBOUND: &str = "replaced-by-new-inbound";
pub const REASON_REPLACED_BY_NEW_OUTBOUND: &str = "replaced-by-new-outbound";
pub const REASON_REPLACED_BY_FRESH_INCOMING: &str = "replaced-by-fresh-incoming";
pub const REASON_ZOMBIE_REPLACED_BY_FRESH_DIAL: &str = "zombie-replaced-by-fresh-dial";
pub const REASON_ZOMBIE_REPLACED_BY_FRESH_ACCEPT: &str = "zombie-replaced-by-fresh-accept";
pub const REASON_STALE_SETTLED_PEER_SNAPSHOT: &str = "stale-settled-peer-snapshot";
pub const REASON_SESSION_ADMISSION_REJECTED: &str = "session-admission-rejected";
pub const REASON_REPLACEMENT_PEER_ADMITTED: &str = "replacement-peer-admitted";
pub const REASON_INCOMING_TRANSPORT_CLOSED_WITHOUT_LIVE_REPLACEMENT: &str =
"incoming-transport-closed-without-live-replacement";
pub const REASON_REPLACEMENT_IN_PROGRESS: &str = "replacement-in-progress";
pub const REASON_APP_BACKGROUNDED: &str = "app-backgrounded";
pub const REASON_STALE_ACTIVE_CONNECTION_RECONNECT: &str = "stale-active-connection-reconnect";
pub const REASON_CONNECTION_FAILED_STABILITY: &str = "connection-failed-stability-verification";
pub const REASON_NETWORK_CHANGE_RECONNECT: &str = "network-change-forced-reconnect";
pub const REASON_AUTO_CONNECT_FAILURE: &str = "auto-connect-failure";
pub const REASON_RETIRED_CONFLICTING_RECORD: &str = "retired-conflicting-record";
pub const REASON_ENDPOINT_HARD_RESET_REDIAL: &str = "endpoint-hard-reset-redial";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LifecycleReasonCode {
ManualDisconnect,
Closed,
DuplicateKeptExisting,
DuplicateAcceptSuperseded,
DuplicateDialSuperseded,
ReplacedByNewInbound,
ReplacedByNewOutbound,
ReplacedByFreshIncoming,
ZombieReplacedByFreshDial,
ZombieReplacedByFreshAccept,
StaleSettledPeerSnapshot,
SessionTokenRevoked,
SessionAdmissionRejected,
ReplacementPeerAdmitted,
IncomingTransportClosedWithoutLiveReplacement,
ReplacementInProgress,
AppBackgrounded,
StaleActiveConnectionReconnect,
ConnectionFailedStability,
NetworkChangeReconnect,
AutoConnectFailure,
RetiredConflictingRecord,
EndpointHardResetRedial,
Unknown,
}
impl LifecycleReasonCode {
pub fn from_text(value: Option<&str>) -> Option<Self> {
let value = value.map(str::trim).filter(|value| !value.is_empty())?;
if value.contains(REASON_DISCONNECTED_BY_USER) || value.contains(REASON_MANUAL_DISCONNECT) {
return Some(Self::ManualDisconnect);
}
if value.contains(REASON_DUPLICATE_KEPT_EXISTING) {
return Some(Self::DuplicateKeptExisting);
}
if value.contains(REASON_DUPLICATE_ACCEPT_SUPERSEDED) {
return Some(Self::DuplicateAcceptSuperseded);
}
if value.contains(REASON_DUPLICATE_DIAL_SUPERSEDED) {
return Some(Self::DuplicateDialSuperseded);
}
if value.contains(REASON_REPLACED_BY_NEW_INBOUND) {
return Some(Self::ReplacedByNewInbound);
}
if value.contains(REASON_REPLACED_BY_NEW_OUTBOUND) {
return Some(Self::ReplacedByNewOutbound);
}
if value.contains(REASON_REPLACED_BY_FRESH_INCOMING) {
return Some(Self::ReplacedByFreshIncoming);
}
if value.contains(REASON_ZOMBIE_REPLACED_BY_FRESH_DIAL) {
return Some(Self::ZombieReplacedByFreshDial);
}
if value.contains(REASON_ZOMBIE_REPLACED_BY_FRESH_ACCEPT) {
return Some(Self::ZombieReplacedByFreshAccept);
}
if value.contains(REASON_STALE_SETTLED_PEER_SNAPSHOT) {
return Some(Self::StaleSettledPeerSnapshot);
}
if value.contains(REASON_SESSION_TOKEN_REVOKED) {
return Some(Self::SessionTokenRevoked);
}
if value.contains(REASON_SESSION_ADMISSION_REJECTED) {
return Some(Self::SessionAdmissionRejected);
}
if value.contains(REASON_REPLACEMENT_PEER_ADMITTED) {
return Some(Self::ReplacementPeerAdmitted);
}
if value.contains(REASON_INCOMING_TRANSPORT_CLOSED_WITHOUT_LIVE_REPLACEMENT) {
return Some(Self::IncomingTransportClosedWithoutLiveReplacement);
}
if value.contains(REASON_REPLACEMENT_IN_PROGRESS) {
return Some(Self::ReplacementInProgress);
}
if value.contains(REASON_APP_BACKGROUNDED) {
return Some(Self::AppBackgrounded);
}
if value.contains(REASON_STALE_ACTIVE_CONNECTION_RECONNECT) {
return Some(Self::StaleActiveConnectionReconnect);
}
if value.contains(REASON_CONNECTION_FAILED_STABILITY) {
return Some(Self::ConnectionFailedStability);
}
if value.contains(REASON_NETWORK_CHANGE_RECONNECT) {
return Some(Self::NetworkChangeReconnect);
}
if value.contains(REASON_AUTO_CONNECT_FAILURE) {
return Some(Self::AutoConnectFailure);
}
if value.contains(REASON_RETIRED_CONFLICTING_RECORD) {
return Some(Self::RetiredConflictingRecord);
}
if value.contains(REASON_ENDPOINT_HARD_RESET_REDIAL) {
return Some(Self::EndpointHardResetRedial);
}
if value.contains("ApplicationClosed") && value.contains("b\"closed\"") {
return Some(Self::Closed);
}
if value == REASON_CLOSED {
return Some(Self::Closed);
}
Some(Self::Unknown)
}
pub fn as_str(self) -> &'static str {
match self {
Self::ManualDisconnect => REASON_MANUAL_DISCONNECT,
Self::Closed => REASON_CLOSED,
Self::DuplicateKeptExisting => REASON_DUPLICATE_KEPT_EXISTING,
Self::DuplicateAcceptSuperseded => REASON_DUPLICATE_ACCEPT_SUPERSEDED,
Self::DuplicateDialSuperseded => REASON_DUPLICATE_DIAL_SUPERSEDED,
Self::ReplacedByNewInbound => REASON_REPLACED_BY_NEW_INBOUND,
Self::ReplacedByNewOutbound => REASON_REPLACED_BY_NEW_OUTBOUND,
Self::ReplacedByFreshIncoming => REASON_REPLACED_BY_FRESH_INCOMING,
Self::ZombieReplacedByFreshDial => REASON_ZOMBIE_REPLACED_BY_FRESH_DIAL,
Self::ZombieReplacedByFreshAccept => REASON_ZOMBIE_REPLACED_BY_FRESH_ACCEPT,
Self::StaleSettledPeerSnapshot => REASON_STALE_SETTLED_PEER_SNAPSHOT,
Self::SessionTokenRevoked => REASON_SESSION_TOKEN_REVOKED,
Self::SessionAdmissionRejected => REASON_SESSION_ADMISSION_REJECTED,
Self::ReplacementPeerAdmitted => REASON_REPLACEMENT_PEER_ADMITTED,
Self::IncomingTransportClosedWithoutLiveReplacement => {
REASON_INCOMING_TRANSPORT_CLOSED_WITHOUT_LIVE_REPLACEMENT
}
Self::ReplacementInProgress => REASON_REPLACEMENT_IN_PROGRESS,
Self::AppBackgrounded => REASON_APP_BACKGROUNDED,
Self::StaleActiveConnectionReconnect => REASON_STALE_ACTIVE_CONNECTION_RECONNECT,
Self::ConnectionFailedStability => REASON_CONNECTION_FAILED_STABILITY,
Self::NetworkChangeReconnect => REASON_NETWORK_CHANGE_RECONNECT,
Self::AutoConnectFailure => REASON_AUTO_CONNECT_FAILURE,
Self::RetiredConflictingRecord => REASON_RETIRED_CONFLICTING_RECORD,
Self::EndpointHardResetRedial => REASON_ENDPOINT_HARD_RESET_REDIAL,
Self::Unknown => "unknown",
}
}
pub fn is_transient_reconnect(self) -> bool {
matches!(
self,
Self::StaleActiveConnectionReconnect
| Self::NetworkChangeReconnect
| Self::EndpointHardResetRedial
| Self::RetiredConflictingRecord
)
}
pub fn is_manual_disconnect(self) -> bool {
matches!(self, Self::ManualDisconnect)
}
pub fn is_graceful_disconnect(self) -> bool {
matches!(self, Self::ManualDisconnect | Self::Closed)
}
pub fn is_replacement_churn(self) -> bool {
matches!(
self,
Self::DuplicateKeptExisting
| Self::DuplicateAcceptSuperseded
| Self::DuplicateDialSuperseded
| Self::ReplacedByNewInbound
| Self::ReplacedByNewOutbound
| Self::ReplacedByFreshIncoming
| Self::ZombieReplacedByFreshDial
| Self::ZombieReplacedByFreshAccept
)
}
pub fn should_immediately_retire_active_webrtc(self) -> bool {
matches!(
self,
Self::ManualDisconnect
| Self::StaleSettledPeerSnapshot
| Self::SessionAdmissionRejected
| Self::ReplacementPeerAdmitted
| Self::IncomingTransportClosedWithoutLiveReplacement
| Self::AppBackgrounded
)
}
}
pub fn reason_is_manual_disconnect(value: Option<&str>) -> bool {
LifecycleReasonCode::from_text(value)
.map(LifecycleReasonCode::is_manual_disconnect)
.unwrap_or(false)
}
pub fn reason_is_graceful_disconnect(value: Option<&str>) -> bool {
LifecycleReasonCode::from_text(value)
.map(LifecycleReasonCode::is_graceful_disconnect)
.unwrap_or(false)
}
pub fn reason_is_replacement_churn(value: Option<&str>) -> bool {
LifecycleReasonCode::from_text(value)
.map(LifecycleReasonCode::is_replacement_churn)
.unwrap_or(false)
}