mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(voice): add opt-in peer-to-peer calls and country rollouts (#3367)
This commit is contained in:
1 parent
e5a568acca
commit
bef5e33eec
215 files changed
+15840
-2693
No files matched your search
@@ -5,11 +5,12 @@ use crate::{
|
||||
AccountIdentityConfigResponse, AccountIdentityMode, AppPublicConfigResponse,
|
||||
CAPTCHA_COST_RANGE, CAPTCHA_MAX_COUNTER_RANGE, CaptchaConfigResponse,
|
||||
DOMAIN_MIGRATION_DEFAULT_SALT, DomainMigrationConfigResponse,
|
||||
EXPERIMENT_MAX_TARGETED_USERS, ExperimentDeliveryConfigResponse,
|
||||
GatewayRolloutConfigResponse, InstanceConfigResponse, InstanceIntegrationsResponse,
|
||||
InstanceMediaResponse, InstancePolicyResponse, InstanceRegistrationResponse,
|
||||
LimitConfigResponse, PendingRegistrationResponse, PushRelayConfigResponse,
|
||||
RegistrationUrlResponse, SsoConfigResponse, TagStyle,
|
||||
EXPERIMENT_MAX_ROLLOUT_COUNTRY_CODES, EXPERIMENT_MAX_TARGETED_USERS,
|
||||
ExperimentDeliveryConfigResponse, GatewayRolloutConfigResponse, InstanceConfigResponse,
|
||||
InstanceIntegrationsResponse, InstanceMediaResponse, InstancePolicyResponse,
|
||||
InstanceRegistrationResponse, LimitConfigResponse, PendingRegistrationResponse,
|
||||
PushRelayConfigResponse, RegistrationUrlResponse, SsoConfigResponse, TagStyle,
|
||||
VOICE_P2P_DEFAULT_SALT, VOICE_P2P_MAX_PARTICIPANTS_RANGE, VoiceP2pConfigResponse,
|
||||
},
|
||||
config::AdminConfig,
|
||||
middleware::auth::AuthContext,
|
||||
@@ -58,6 +59,22 @@ fn entry_count_hint(count: usize, cap: usize) -> Markup {
|
||||
}
|
||||
}
|
||||
|
||||
fn experiment_rollout_country_codes_field(name: &str, country_codes: &[String]) -> Markup {
|
||||
html! {
|
||||
div class="flex flex-col gap-2" {
|
||||
(text_input(name, "Rollout Countries", &country_codes.join(", "), "SE, NO, FI"))
|
||||
(entry_count_hint(country_codes.len(), EXPERIMENT_MAX_ROLLOUT_COUNTRY_CODES))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Two-letter ISO 3166-1 country codes, separated by commas or spaces. When set, \
|
||||
the user rollout percentage only applies to requests from these countries, and \
|
||||
a request with an unknown country is left out. Leave empty to apply the \
|
||||
percentage everywhere. The always-on lists, the premium switch and the \
|
||||
never-on list ignore the country. Invalid entries prevent the save."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn number_field(
|
||||
name: &str,
|
||||
label: &str,
|
||||
@@ -193,6 +210,7 @@ pub fn instance_config_page(
|
||||
@if !instance_config.self_hosted {
|
||||
(domain_migration_section(base, csrf_token, &instance_config.domain_migration))
|
||||
}
|
||||
(voice_p2p_section(base, csrf_token, &instance_config.voice_p2p))
|
||||
(experiment_delivery_section(base, csrf_token, &instance_config.experiment_delivery))
|
||||
@if let Some(limit_config) = limit_config {
|
||||
(limit_config_section(base, limit_config))
|
||||
@@ -1193,6 +1211,10 @@ fn domain_migration_section(
|
||||
Some(0), Some(10000), "1",
|
||||
Some("Share of logged-out devices sent to the new origin, in basis points. Each device is bucketed on its own random ID."),
|
||||
))
|
||||
(experiment_rollout_country_codes_field(
|
||||
"domain_migration_rollout_country_codes",
|
||||
&domain_migration.rollout_country_codes,
|
||||
))
|
||||
div class="flex flex-col gap-2" {
|
||||
(text_input(
|
||||
"domain_migration_rollout_salt",
|
||||
@@ -1287,6 +1309,162 @@ fn domain_migration_section(
|
||||
)
|
||||
}
|
||||
|
||||
fn voice_p2p_section(base: &str, csrf_token: &str, voice_p2p: &VoiceP2pConfigResponse) -> Markup {
|
||||
let status = if voice_p2p.enabled {
|
||||
("Live", BadgeVariant::Success)
|
||||
} else {
|
||||
("Inert", BadgeVariant::Default)
|
||||
};
|
||||
let included_user_ids = voice_p2p.included_user_ids.join("\n");
|
||||
let excluded_user_ids = voice_p2p.excluded_user_ids.join("\n");
|
||||
section_card_with_description(
|
||||
"Peer-to-peer voice",
|
||||
"Lets selected users start small voice calls that connect the participants directly \
|
||||
instead of through the voice servers. A peer-to-peer call only moves to the voice \
|
||||
servers when a participant switches it or its region changes.",
|
||||
html! {
|
||||
form method="post" action={(base) "/instance-config?action=update_voice_p2p"} {
|
||||
(csrf_input(csrf_token))
|
||||
div class="space-y-6" {
|
||||
div class="flex flex-wrap items-center gap-2" {
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Master switch" }
|
||||
(badge(status.0, status.1))
|
||||
span class="text-xs text-neutral-500" {
|
||||
"Config version " (voice_p2p.config_version)
|
||||
}
|
||||
}
|
||||
(checkbox(
|
||||
"voice_p2p_enabled",
|
||||
"true",
|
||||
"Let the selected users start peer-to-peer calls",
|
||||
voice_p2p.enabled,
|
||||
true,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Off is the safe state and the kill switch. With this unchecked every new \
|
||||
call uses the voice servers and a running peer-to-peer call accepts no \
|
||||
new joins, so the rollout and targeting fields below have no effect at \
|
||||
all. While it is on, the fields below select who can start a \
|
||||
peer-to-peer call, and anyone who agrees to a direct connection can \
|
||||
join one."
|
||||
}
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Call size" }
|
||||
(number_field(
|
||||
"voice_p2p_max_participants",
|
||||
"Maximum participants",
|
||||
&voice_p2p.max_participants.to_string(),
|
||||
Some(*VOICE_P2P_MAX_PARTICIPANTS_RANGE.start()),
|
||||
Some(*VOICE_P2P_MAX_PARTICIPANTS_RANGE.end()),
|
||||
"1",
|
||||
Some("Most participants a peer-to-peer call can hold, 2 through 4. Clients only offer a peer-to-peer call in a direct message or group with at most this many members, and a join past it is refused as full. Lowering it never removes anyone from a running call."),
|
||||
))
|
||||
|
||||
h3 class="text-sm font-semibold text-neutral-900" { "Rollout" }
|
||||
(number_field(
|
||||
"voice_p2p_rollout_basis_points",
|
||||
"Rollout (basis points)",
|
||||
&voice_p2p.rollout_basis_points.to_string(),
|
||||
Some(0), Some(10000), "1",
|
||||
Some("Share of users who can start peer-to-peer calls, in basis points: 0 is nobody, 100 is 1%, 10000 is everybody."),
|
||||
))
|
||||
(experiment_rollout_country_codes_field(
|
||||
"voice_p2p_rollout_country_codes",
|
||||
&voice_p2p.rollout_country_codes,
|
||||
))
|
||||
div class="flex flex-col gap-2" {
|
||||
(text_input(
|
||||
"voice_p2p_rollout_salt",
|
||||
"Rollout Salt",
|
||||
&voice_p2p.rollout_salt,
|
||||
VOICE_P2P_DEFAULT_SALT,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Seeds the bucketing hash. Changing it reshuffles which users fall \
|
||||
inside the percentage above. Leave it alone to keep the current \
|
||||
cohort stable."
|
||||
}
|
||||
}
|
||||
div class="flex flex-col gap-2" {
|
||||
(textarea_input(
|
||||
"voice_p2p_included_user_ids",
|
||||
"Always-on User IDs",
|
||||
"1500000000000000001\n1500000000000000002",
|
||||
&included_user_ids,
|
||||
4,
|
||||
false,
|
||||
))
|
||||
(entry_count_hint(
|
||||
voice_p2p.included_user_ids.len(),
|
||||
EXPERIMENT_MAX_TARGETED_USERS,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"One snowflake per line, or comma separated. These users are targeted \
|
||||
regardless of the percentage above. IDs must contain 1 to 20 decimal \
|
||||
digits. Invalid entries prevent the save. Blank entries and duplicate \
|
||||
IDs are ignored."
|
||||
}
|
||||
}
|
||||
div class="flex flex-col gap-2" {
|
||||
(checkbox(
|
||||
"voice_p2p_include_premium_users",
|
||||
"true",
|
||||
"Include premium users",
|
||||
voice_p2p.include_premium_users,
|
||||
true,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Includes every account with active premium perks, regardless of the \
|
||||
percentage above. The never-on list still wins."
|
||||
}
|
||||
}
|
||||
div class="flex flex-col gap-2" {
|
||||
(textarea_input(
|
||||
"voice_p2p_included_guild_ids",
|
||||
"Always-on Guild IDs",
|
||||
"1500000000000000005\n1500000000000000006",
|
||||
&voice_p2p.included_guild_ids.join("\n"),
|
||||
4,
|
||||
false,
|
||||
))
|
||||
(entry_count_hint(
|
||||
voice_p2p.included_guild_ids.len(),
|
||||
EXPERIMENT_MAX_TARGETED_USERS,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Same format, with guild IDs. Every member of a listed guild is \
|
||||
included regardless of the percentage above, unless the user is \
|
||||
in the never-on list."
|
||||
}
|
||||
}
|
||||
div class="flex flex-col gap-2" {
|
||||
(textarea_input(
|
||||
"voice_p2p_excluded_user_ids",
|
||||
"Never-on User IDs",
|
||||
"1500000000000000003\n1500000000000000004",
|
||||
&excluded_user_ids,
|
||||
4,
|
||||
false,
|
||||
))
|
||||
(entry_count_hint(
|
||||
voice_p2p.excluded_user_ids.len(),
|
||||
EXPERIMENT_MAX_TARGETED_USERS,
|
||||
))
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Same format. Exclusion wins over both the always-on list and the \
|
||||
percentage."
|
||||
}
|
||||
}
|
||||
|
||||
(form_actions(html! {
|
||||
(submit_button("Save Peer-to-Peer Voice Configuration"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn estimate_low_end_solve_seconds(cost: u32, max_counter: u32) -> f64 {
|
||||
0.75 * f64::from(cost) * f64::from(max_counter) / 1_050_000.0
|
||||
}
|
||||
@@ -2094,11 +2272,49 @@ mod tests {
|
||||
assert!(markup.contains("name=\"domain_migration_anonymous_rollout_basis_points\""));
|
||||
assert!(markup.contains("value=\"250\""));
|
||||
assert!(markup.contains("name=\"domain_migration_standalone_forwarding\""));
|
||||
assert!(markup.contains("name=\"domain_migration_rollout_country_codes\""));
|
||||
assert!(markup.contains("1 of 1000 stored"));
|
||||
assert!(markup.contains("2 of 1000 stored"));
|
||||
assert!(!markup.contains("at the cap"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_p2p_section_shows_the_rollout_and_list_counts() {
|
||||
let voice_p2p = VoiceP2pConfigResponse {
|
||||
enabled: true,
|
||||
config_version: 3,
|
||||
rollout_basis_points: 250,
|
||||
rollout_country_codes: vec!["SE".to_owned(), "NO".to_owned()],
|
||||
max_participants: 3,
|
||||
included_user_ids: vec!["1500000000000000001".to_owned()],
|
||||
excluded_user_ids: vec![
|
||||
"1500000000000000002".to_owned(),
|
||||
"1500000000000000003".to_owned(),
|
||||
],
|
||||
..VoiceP2pConfigResponse::default()
|
||||
};
|
||||
let markup = voice_p2p_section("/admin", "csrf", &voice_p2p).into_string();
|
||||
assert!(markup.contains("Peer-to-peer voice"));
|
||||
assert!(markup.contains("action=update_voice_p2p"));
|
||||
assert!(markup.contains("name=\"voice_p2p_enabled\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_rollout_basis_points\""));
|
||||
assert!(markup.contains("value=\"250\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_max_participants\""));
|
||||
assert!(markup.contains("value=\"3\""));
|
||||
assert!(markup.contains("min=\"2\""));
|
||||
assert!(markup.contains("max=\"4\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_include_premium_users\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_included_guild_ids\""));
|
||||
assert!(markup.contains("name=\"voice_p2p_rollout_country_codes\""));
|
||||
assert!(markup.contains("value=\"SE, NO\""));
|
||||
assert!(markup.contains("2 of 250 stored"));
|
||||
assert!(markup.contains("Config version 3"));
|
||||
assert!(markup.contains("1 of 1000 stored"));
|
||||
assert!(markup.contains("2 of 1000 stored"));
|
||||
assert!(!markup.contains("anonymous_rollout_basis_points"));
|
||||
assert!(!markup.contains("standalone_forwarding"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_relay_section_shows_the_consent_toggle() {
|
||||
let accepted = PushRelayConfigResponse {
|
||||
|
||||
Reference in new issue
Block a user