2024-01-30 17:19:34 +01:00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
2023-11-24 22:21:57 +01:00
/*!
Chorus combines all the required functionalities of a user - centric Spacebar library into one package .
The library handles various aspects on your behalf , such as rate limiting , authentication and maintaining
a WebSocket connection to the Gateway . This means that you can focus on building your application ,
instead of worrying about the underlying implementation details .
### Establishing a Connection
To connect to a Spacebar compatible server , you need to create an [ ` Instance ` ] ( https ://docs.rs/chorus/latest/chorus/instance/struct.Instance.html) like this:
2023-12-02 17:44:24 +01:00
` ` ` rs
2023-11-24 22:21:57 +01:00
use chorus ::instance ::Instance ;
use chorus ::UrlBundle ;
#[ tokio::main ]
async fn main ( ) {
let bundle = UrlBundle ::new (
" https://example.com/api " . to_string ( ) ,
" wss://example.com/ " . to_string ( ) ,
" https://example.com/cdn " . to_string ( ) ,
) ;
2023-12-10 18:40:03 +01:00
let instance = Instance ::new ( bundle )
2023-11-24 22:21:57 +01:00
. await
. expect ( " Failed to connect to the Spacebar server " ) ;
// You can create as many instances of `Instance` as you want, but each `Instance` should likely be unique.
dbg! ( instance . instance_info ) ;
dbg! ( instance . limits_information ) ;
}
` ` `
This Instance can now be used to log in , register and from there on , interact with the server in all sorts of ways .
### Logging In
Logging in correctly provides you with an instance of [ ` ChorusUser ` ] ( https ://docs.rs/chorus/latest/chorus/instance/struct.ChorusUser.html), with which you can interact with the server and
manipulate the account . Assuming you already have an account on the server , you can log in like this :
2023-12-02 17:44:24 +01:00
` ` ` rs
2023-11-24 22:21:57 +01:00
use chorus ::types ::LoginSchema ;
// Assume, you already have an account created on this instance. Registering an account works
// the same way, but you'd use the Register-specific Structs and methods instead.
let login_schema = LoginSchema {
login : " user@example.com " . to_string ( ) ,
password : " Correct-Horse-Battery-Staple " . to_string ( ) ,
.. Default ::default ( )
} ;
2024-01-31 22:27:53 +01:00
// Each user connects to the Gateway. The Gateway connection lives on a separate thread. Depending on
2023-11-24 22:21:57 +01:00
// the runtime feature you choose, this can potentially take advantage of all of your computers' threads.
let user = instance
. login_account ( login_schema )
. await
. expect ( " An error occurred during the login process " ) ;
dbg! ( user . belongs_to ) ;
dbg! ( & user . object . read ( ) . unwrap ( ) . username ) ;
` ` `
## Supported Platforms
All major desktop operating systems ( Windows , macOS ( aarch64 / x86_64 ) , Linux ( aarch64 / x86_64 ) ) are supported .
` wasm32 - unknown - unknown ` is a supported compilation target on versions ` 0.1 2.0 ` and up . This allows you to use
Chorus in your browser , or in any other environment that supports WebAssembly .
We recommend checking out the examples directory , as well as the documentation for more information .
## MSRV ( Minimum Supported Rust Version )
Rust * * 1.6 7.1 * * . This number might change at any point while Chorus is not yet at version 1. 0. 0.
## Development Setup
Make sure that you have at least Rust 1.6 7.1 installed . You can check your Rust version by running ` cargo - - version `
in your terminal . To compile for ` wasm32 - unknown - unknown ` , you need to install the ` wasm32 - unknown - unknown ` target .
You can do this by running ` rustup target add wasm32 - unknown - unknown ` .
### Testing
In general , the tests will require you to run a local instance of the Spacebar server . You can find instructions on how
to do that [ here ] ( https ://docs.spacebar.chat/setup/server/). You can find a pre-configured version of the server
[ here ] ( https ://github.com/bitfl0wer/server). It is recommended to use the pre-configured version, as certain things
like " proxy connection checking " are already disabled on this version , which otherwise might break tests .
### wasm
To test for wasm , you will need to ` cargo install wasm - pack ` . You can then run
` wasm - pack test - - < chrome / firefox / safari > - - headless - - - - target wasm32 - unknown - unknown - - features = " rt, client " - - no - default - features `
to run the tests for wasm .
## Versioning
This crate uses Semantic Versioning 2. 0.0 as its versioning scheme . You can read the specification [ here ] ( https ://semver.org/spec/v2.0.0.html).
! * /
2023-08-28 12:27:38 +02:00
#![ doc(
html_logo_url = " https://raw.githubusercontent.com/polyphony-chat/design/main/branding/polyphony-chorus-round-8bit.png "
) ]
2023-06-19 10:27:32 +02:00
#![ allow(clippy::module_inception) ]
2023-08-28 12:27:38 +02:00
#![ deny(
missing_debug_implementations ,
clippy ::extra_unused_lifetimes ,
clippy ::from_over_into ,
clippy ::needless_borrow ,
clippy ::new_without_default ,
clippy ::useless_conversion
) ]
2023-12-03 13:28:50 +01:00
#![ warn(
clippy ::todo ,
clippy ::unimplemented ,
clippy ::dbg_macro ,
clippy ::print_stdout ,
clippy ::print_stderr
) ]
2023-11-13 15:26:46 +01:00
#[ cfg(all(feature = " rt " , feature = " rt_multi_thread " )) ]
compile_error! ( " feature \" rt \" and feature \" rt_multi_thread \" cannot be enabled at the same time " ) ;
2023-06-19 10:27:32 +02:00
2023-12-03 12:49:22 +01:00
use errors ::ChorusResult ;
2023-12-02 17:36:36 +01:00
use serde ::{ Deserialize , Serialize } ;
2023-12-03 13:04:17 +01:00
use types ::types ::domains_configuration ::WellKnownResponse ;
2023-06-11 13:52:31 +02:00
use url ::{ ParseError , Url } ;
2023-12-03 12:49:22 +01:00
use crate ::errors ::ChorusError ;
2023-05-26 16:16:08 +02:00
#[ cfg(feature = " client " ) ]
2023-05-09 14:04:46 +02:00
pub mod api ;
pub mod errors ;
2023-05-26 16:16:08 +02:00
#[ cfg(feature = " client " ) ]
2023-05-09 14:04:46 +02:00
pub mod gateway ;
2023-05-26 16:16:08 +02:00
#[ cfg(feature = " client " ) ]
2023-05-09 14:04:46 +02:00
pub mod instance ;
2023-05-26 16:16:08 +02:00
#[ cfg(feature = " client " ) ]
2023-07-09 18:38:02 +02:00
pub mod ratelimiter ;
2023-05-25 21:11:08 +02:00
pub mod types ;
Primitive voice implementation (feature/voice) (#457)
* Add Webrtc Identify & Ready
* Add more webrtc typings
* Attempt an untested voice gateway implementation
* fmt
* Merge with main
* Same allow as for voice as normal gateway
* Test error observer
* Minor updates
* More derives
* Even more derives
* Small types update
* e
* Minor doc fixes
* Modernise voice gateway
* Add default impl for voicegatewayerror
* Make voice event fields pub
* Event updates via the scientific method
* ??
* Fix bad request in voice gateway init
* Voice gateway updates
* Fix error failing to 'deserialize' properly
* Update voice identify
* Clarify FIXME related to #430
* Update to v7
* Create seperate voice_gateway.rs and voice_udp.rs
* Restructure voice to new module
* fix: deserialization error in speaking bitflags
* feat: kinda janky ip discovery impl
* feat: return ip discovery data + minor update
* feat: packet parsing!
* fix: voice works again
* feat: add voice_media_sink_wants
(comitting uncommited changes to merge)
* chore: rename events/webrtc to events/voice_gateway
* Add UdpHandle
* chore: clippy + other misc updates
* fix: attempt to fix failing wasm build
* chore: yes clippy, that is indeed an unneeded return statement
* feat: add VoiceData struct
* feat: add VoiceData reference to UdpHandler
* feat: decryption?
* chore: formatting
* feat: add ssrc definition (op 12)
* feat: add untested sending & asbtract nonce generation
* feat: Public api! (sorta)
* small updates
* feat: add sequence number
* chore: yes
* feat: merge VoiceHandler into official development
* chore: yes clippy, you are special
* fix: duplicated gateway events
* feat: first try at vgw wasm compat
* fix: blunder
* fix: gateway connect using wrong url
* fix: properly using encrypted data, bad practice for buffer creation
* chore: split voice udp
* feat: udp error handling, create udp/backends
* fix: its the same
* chore: clarify UDP on WASM
* api: split voice gateway and udp features, test for voice gateway in WASM
* feat: new encryption modes, minor code quality
* docs: document voice encryption modes
* chore: unused imports
* chore: update getrandom version to match wasm version
* chore: update on packet size FIXME
* drop buf asap
* Okay can't do that actually
* tests: add nonce test
* normal tests work?
* docs: fix doc warning, fix incorrect refrences to 'webrtc'
* chore: json isn't a doc test
* tests: better gateway auth test
* testing tests
* update voice heartbeat, fix the new test issue
* committed too much
* fix: unused import
* fix: use ip discovery address as string, not as Vec<u8>
* chore: less obnoxious logging
* chore: better unimplemented voice modes handling
* chore: remove unused variable
* chore: use matches macro
* add voice examples, make gateway ones clearer
* rename voice example
* chore: remove unused VoiceHandler
* fix: implement gateway Reconnect and InvalidSession
* Typo
Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com>
* Fix a bunch of typos
Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com>
* fix: error handling while loading native certs
* fix: guh
* use be for nonce bytes
* fix: refactor gw and vgw closures
* remove outdated docs
---------
Co-authored-by: Flori <39242991+bitfl0wer@users.noreply.github.com>
2024-04-16 17:18:21 +02:00
#[ cfg(all(
feature = " client " ,
any ( feature = " voice_udp " , feature = " voice_gateway " )
) ) ]
2023-05-09 14:04:46 +02:00
pub mod voice ;
2023-04-05 21:54:27 +02:00
2023-12-02 17:35:47 +01:00
#[ derive(Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize) ]
2023-07-29 10:23:04 +02:00
/// A URLBundle bundles together the API-, Gateway- and CDN-URLs of a Spacebar instance.
///
2023-07-10 17:22:31 +02:00
/// # Notes
/// All the urls can be found on the /api/policies/instance/domains endpoint of a spacebar server
2023-06-20 02:59:18 +02:00
pub struct UrlBundle {
2023-12-15 00:10:33 +01:00
/// The root url of an Instance. Usually, this would be the url where `.well-known/spacebar` can
/// be located under. If the instance you are connecting to for some reason does not have a
/// `.well-known` set up (for example, if it is a local/testing instance), you can use the api
/// url as a substitute.
/// Ex: `https://spacebar.chat`
pub root : String ,
2023-07-10 17:22:31 +02:00
/// The api's url.
/// Ex: `https://old.server.spacebar.chat/api`
2023-04-05 21:54:27 +02:00
pub api : String ,
2023-07-10 17:22:31 +02:00
/// The gateway websocket url.
2023-07-29 10:23:04 +02:00
/// Note that because this is a websocket url, it will always start with `wss://` or `ws://`
2023-07-10 17:22:31 +02:00
/// Ex: `wss://gateway.old.server.spacebar.chat`
2023-04-05 21:54:27 +02:00
pub wss : String ,
2023-07-10 17:22:31 +02:00
/// The CDN's url.
/// Ex: `https://cdn.old.server.spacebar.chat`
2023-04-05 21:54:27 +02:00
pub cdn : String ,
}
2023-06-20 02:59:18 +02:00
impl UrlBundle {
2023-07-29 11:26:00 +02:00
/// Creates a new UrlBundle from the relevant urls.
2023-12-15 00:10:33 +01:00
pub fn new ( root : String , api : String , wss : String , cdn : String ) -> Self {
2023-04-05 21:54:27 +02:00
Self {
2023-12-15 00:10:33 +01:00
root : UrlBundle ::parse_url ( root ) ,
2023-06-20 02:59:18 +02:00
api : UrlBundle ::parse_url ( api ) ,
wss : UrlBundle ::parse_url ( wss ) ,
cdn : UrlBundle ::parse_url ( cdn ) ,
2023-04-05 21:54:27 +02:00
}
}
2023-07-29 10:23:04 +02:00
/// Parses a URL using the Url library and formats it in a standardized way.
/// If no protocol is given, HTTP (not HTTPS) is assumed.
///
/// # Examples:
2023-04-05 22:19:15 +02:00
/// ```rs
2023-04-05 21:54:27 +02:00
/// let url = parse_url("localhost:3000");
/// ```
/// `-> Outputs "http://localhost:3000".`
pub fn parse_url ( url : String ) -> String {
let url = match Url ::parse ( & url ) {
Ok ( url ) = > {
if url . scheme ( ) = = " localhost " {
2023-06-20 02:59:18 +02:00
return UrlBundle ::parse_url ( format! ( " http:// {} " , url ) ) ;
2023-04-05 21:54:27 +02:00
}
url
}
Err ( ParseError ::RelativeUrlWithoutBase ) = > {
2023-04-15 13:27:34 +02:00
let url_fmt = format! ( " http:// {} " , url ) ;
2023-06-20 02:59:18 +02:00
return UrlBundle ::parse_url ( url_fmt ) ;
2023-04-05 21:54:27 +02:00
}
2023-12-03 12:49:22 +01:00
Err ( _ ) = > panic! ( " Invalid URL " ) , // TODO: should not panic here
2023-04-05 21:54:27 +02:00
} ;
2023-04-15 13:03:51 +02:00
// if the last character of the string is a slash, remove it.
let mut url_string = url . to_string ( ) ;
2023-04-25 17:41:14 +02:00
if url_string . ends_with ( '/' ) {
2023-04-15 13:03:51 +02:00
url_string . pop ( ) ;
}
2023-04-25 17:41:14 +02:00
url_string
2023-04-05 21:54:27 +02:00
}
2023-12-03 12:49:22 +01:00
2023-12-03 13:39:23 +01:00
/// Performs a few HTTP requests to try and retrieve a `UrlBundle` from an instances' root url.
2023-12-03 12:49:22 +01:00
/// The method tries to retrieve the `UrlBundle` via these three strategies, in order:
/// - GET: `$url/.well-known/spacebar` -> Retrieve UrlBundle via `$wellknownurl/api/policies/instance/domains`
/// - GET: `$url/api/policies/instance/domains`
/// - GET: `$url/policies/instance/domains`
///
/// The URL stored at `.well-known/spacebar` is the instances' API endpoint. The API
/// stores the CDN and WSS URLs under the `$api/policies/instance/domains` endpoint. If all three
/// of the above approaches fail, it is very likely that the instance is misconfigured, unreachable, or that
/// a wrong URL was provided.
2023-12-03 13:39:23 +01:00
pub async fn from_root_url ( url : & str ) -> ChorusResult < UrlBundle > {
2023-12-03 12:49:22 +01:00
let parsed = UrlBundle ::parse_url ( url . to_string ( ) ) ;
let client = reqwest ::Client ::new ( ) ;
let request_wellknown = client
. get ( format! ( " {} /.well-known/spacebar " , & parsed ) )
. header ( http ::header ::ACCEPT , " application/json " )
. build ( ) ? ;
let response_wellknown = client . execute ( request_wellknown ) . await ? ;
if response_wellknown . status ( ) . is_success ( ) {
let body = response_wellknown . json ::< WellKnownResponse > ( ) . await ? . api ;
UrlBundle ::from_api_url ( & body ) . await
} else {
if let Ok ( response_slash_api ) =
2023-12-03 13:28:50 +01:00
UrlBundle ::from_api_url ( & format! ( " {} /api/policies/instance/domains " , parsed ) ) . await
2023-12-03 12:49:22 +01:00
{
return Ok ( response_slash_api ) ;
}
if let Ok ( response_api ) =
2023-12-03 13:28:50 +01:00
UrlBundle ::from_api_url ( & format! ( " {} /policies/instance/domains " , parsed ) ) . await
2023-12-03 12:49:22 +01:00
{
Ok ( response_api )
} else {
2023-12-03 13:28:50 +01:00
Err ( ChorusError ::RequestFailed { url : parsed . to_string ( ) , error : " Could not retrieve UrlBundle from url after trying 3 different approaches. Check the provided Url and make sure the instance is reachable. " . to_string ( ) } )
2023-12-03 12:49:22 +01:00
}
}
}
async fn from_api_url ( url : & str ) -> ChorusResult < UrlBundle > {
2023-12-03 13:16:34 +01:00
let client = reqwest ::Client ::new ( ) ;
let request = client
. get ( url )
. header ( http ::header ::ACCEPT , " application/json " )
. build ( ) ? ;
let response = client . execute ( request ) . await ? ;
if let Ok ( body ) = response
. json ::< types ::types ::domains_configuration ::Domains > ( )
. await
{
2023-12-15 00:10:33 +01:00
Ok ( UrlBundle ::new (
url . to_string ( ) ,
body . api_endpoint ,
body . gateway ,
body . cdn ,
) )
2023-12-03 13:16:34 +01:00
} else {
Err ( ChorusError ::RequestFailed {
url : url . to_string ( ) ,
error : " Could not retrieve a UrlBundle from the given url. Check the provided url and make sure the instance is reachable. " . to_string ( ) ,
} )
}
2023-12-03 12:49:22 +01:00
}
}
2023-04-04 17:37:11 +02:00
#[ cfg(test) ]
2023-04-15 13:27:34 +02:00
mod lib {
2023-04-04 17:37:11 +02:00
use super ::* ;
#[ test ]
2023-04-05 21:54:27 +02:00
fn test_parse_url ( ) {
2023-06-20 02:59:18 +02:00
let mut result = UrlBundle ::parse_url ( String ::from ( " localhost:3000/ " ) ) ;
2023-04-05 21:54:27 +02:00
assert_eq! ( result , String ::from ( " http://localhost:3000 " ) ) ;
2023-06-20 02:59:18 +02:00
result = UrlBundle ::parse_url ( String ::from ( " https://some.url.com/ " ) ) ;
2023-04-15 13:27:34 +02:00
assert_eq! ( result , String ::from ( " https://some.url.com " ) ) ;
2023-06-20 02:59:18 +02:00
result = UrlBundle ::parse_url ( String ::from ( " https://some.url.com/ " ) ) ;
2023-04-15 13:27:34 +02:00
assert_eq! ( result , String ::from ( " https://some.url.com " ) ) ;
2023-06-20 02:59:18 +02:00
result = UrlBundle ::parse_url ( String ::from ( " https://some.url.com " ) ) ;
2023-04-15 13:27:34 +02:00
assert_eq! ( result , String ::from ( " https://some.url.com " ) ) ;
2023-04-04 17:37:11 +02:00
}
}