core: account-derived rooms — same account → same room everywhere
Add room_for_account(account) = sha256(account)[..4] hex, exported via UniFFI so every client derives the IDENTICAL room from a shared identity. Clients now default their room to the account-derived id (falling back to a device id only when signed out). With the prefilled account, all our devices land in one room across LAN, the relay, and RTC — so "just us" works cross-network, not only on the same Wi-Fi. The single canonical Rust impl guarantees the ids match across Swift/Kotlin/agent (a hand-rolled per-platform hash would risk drift). Verified: agent and the live Mac app both derive room 360309c8 from "pecord@gmail.com"; the Mac app publishes there on the public relay. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -51,14 +51,20 @@ fn arg(flag: &str, default: &str) -> String {
|
||||
|
||||
fn main() {
|
||||
let server = arg("--server", "https://tether.pecord.io");
|
||||
let room = arg("--room", "default");
|
||||
let source = arg("--source", "agent");
|
||||
let from = arg("--from", &format!("agent-{source}"));
|
||||
// Friendly name for other devices' nearby lists.
|
||||
let host = std::env::var("HOSTNAME").unwrap_or_else(|_| "tether-agent".into());
|
||||
let name = arg("--name", &host);
|
||||
// Shared identity: same-account devices pair on the LAN regardless of room.
|
||||
// Shared identity: same-account devices pair on the LAN regardless of room,
|
||||
// and share an account-derived room everywhere (so cross-network works too).
|
||||
let account = arg("--account", "pecord@gmail.com");
|
||||
let default_room = if account.is_empty() {
|
||||
"default".to_string()
|
||||
} else {
|
||||
tethercore::room_for_account(account.clone())
|
||||
};
|
||||
let room = arg("--room", &default_room);
|
||||
|
||||
eprintln!("tether-agent → {server} room={room} source={source}");
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@ data class FeedItem(val text: String, val source: String, val ts: Long, val id:
|
||||
class TetherStore(private val context: Context) {
|
||||
val serverURL = mutableStateOf(prefs().getString("serverURL", "https://tether.pecord.io")!!)
|
||||
val account = mutableStateOf(prefs().getString("account", "pecord@gmail.com")!!)
|
||||
val room = mutableStateOf(RoomIdentity.derive(context))
|
||||
// Room follows the account → all our devices share one room everywhere.
|
||||
val room = mutableStateOf(
|
||||
if (account.value.isEmpty()) RoomIdentity.derive(context)
|
||||
else uniffi.tethercore.roomForAccount(account.value)
|
||||
)
|
||||
val feed = mutableStateListOf<FeedItem>()
|
||||
val connected = mutableStateOf(false)
|
||||
|
||||
|
||||
115
core/Cargo.lock
generated
115
core/Cargo.lock
generated
@@ -8,7 +8,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
@@ -20,7 +20,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -271,6 +271,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
@@ -404,7 +413,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -426,7 +435,7 @@ version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -483,6 +492,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -492,6 +507,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.4.0"
|
||||
@@ -530,6 +554,15 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
@@ -546,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"curve25519-dalek-derive",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
@@ -577,7 +610,7 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"const-oid 0.9.6",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -608,12 +641,23 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"block-buffer 0.10.4",
|
||||
"const-oid 0.9.6",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.1",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.6"
|
||||
@@ -653,7 +697,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"sec1",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"webrtc-util",
|
||||
@@ -668,7 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
@@ -683,7 +727,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
@@ -977,7 +1021,7 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1019,6 +1063,15 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.10.1"
|
||||
@@ -1307,7 +1360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1487,7 +1540,7 @@ dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1499,7 +1552,7 @@ dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1590,7 +1643,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
@@ -1602,7 +1655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"cpufeatures 0.2.17",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
@@ -2175,8 +2228,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2186,8 +2239,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2212,7 +2276,7 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -2397,6 +2461,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"uniffi",
|
||||
"webrtc",
|
||||
@@ -2799,7 +2864,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"crypto-common 0.1.7",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
@@ -3004,7 +3069,7 @@ dependencies = [
|
||||
"sdp",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.9",
|
||||
"smol_str",
|
||||
"stun",
|
||||
"thiserror 1.0.69",
|
||||
|
||||
@@ -24,3 +24,4 @@ tokio = { version = "1", features = ["rt-multi-thread", "time", "sync", "macros"
|
||||
futures-util = "0.3"
|
||||
webrtc = "0.17.1"
|
||||
mdns-sd = "0.20.0"
|
||||
sha2 = "0.11.0"
|
||||
|
||||
@@ -74,6 +74,20 @@ pub struct Peer {
|
||||
/// engine's `nearby()` (reader). Instant is last-seen for staleness pruning.
|
||||
type Discovered = Arc<Mutex<HashMap<String, (Peer, Instant)>>>;
|
||||
|
||||
/// Canonical room id for a shared identity: sha256(account)[..4] as 8 hex chars.
|
||||
/// Defined once here and exported so every client (Swift/Kotlin/agent) derives
|
||||
/// the SAME room — that's what puts all of one account's devices together across
|
||||
/// LAN, relay, and RTC. Empty account → empty (caller falls back to a device id).
|
||||
#[uniffi::export]
|
||||
pub fn room_for_account(account: String) -> String {
|
||||
use sha2::{Digest, Sha256};
|
||||
if account.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
let digest = Sha256::digest(account.as_bytes());
|
||||
digest[..4].iter().map(|b| format!("{b:02x}")).collect()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Config {
|
||||
server: String,
|
||||
|
||||
@@ -19,7 +19,11 @@ final class MacTetherStore {
|
||||
var serverURL: String = UserDefaults.standard.string(forKey: "serverURL") ?? "https://tether.pecord.io"
|
||||
// Prefilled shared identity — for now it's just us, so all devices pair.
|
||||
var account: String = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com"
|
||||
var room: String = RoomIdentity.derive()
|
||||
// Room follows the account → all our devices share one room everywhere.
|
||||
var room: String = {
|
||||
let a = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com"
|
||||
return a.isEmpty ? RoomIdentity.derive() : roomForAccount(account: a)
|
||||
}()
|
||||
var feed: [FeedItem] = []
|
||||
var nearby: [Peer] = [] // tether devices on the LAN, for the picker
|
||||
var connected = false
|
||||
|
||||
@@ -23,7 +23,12 @@ final class TetherStore {
|
||||
// Prefilled shared identity — for now it's just us, so all devices pair.
|
||||
// Becomes the Sign-in-with-Apple `sub` once the dev program clears.
|
||||
var account: String = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com"
|
||||
var room: String = RoomIdentity.derive()
|
||||
// Room follows the account, so all our devices share one room everywhere
|
||||
// (LAN, relay, RTC). Falls back to a device id only when signed out.
|
||||
var room: String = {
|
||||
let a = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com"
|
||||
return a.isEmpty ? RoomIdentity.derive() : roomForAccount(account: a)
|
||||
}()
|
||||
var feed: [FeedItem] = []
|
||||
var connected = false
|
||||
var lastError: String?
|
||||
|
||||
Reference in New Issue
Block a user