Skip to main content

anytype_rpc/
client.rs

1use std::time::Duration;
2use tonic::transport::{Channel, Endpoint};
3
4use crate::anytype::ClientCommandsClient;
5use crate::auth::{create_session_token_from_account_key, create_session_token_from_app_key};
6use crate::error::AnytypeGrpcError;
7
8// optional environment variable containing grpc endpoint
9const ANYTYPE_GRPC_ENDPOINT_ENV: &str = "ANYTYPE_GRPC_ENDPOINT";
10const ANYTYPE_GRPC_ENDPOINT: &str = "http://127.0.0.1:31010"; // headless server
11
12/// checks environment variable "ANYTYPE_GRPC_ENDPOINT", then falls back to headless cli endpoint
13pub fn default_grpc_endpoint() -> String {
14    std::env::var(ANYTYPE_GRPC_ENDPOINT_ENV).unwrap_or_else(|_| ANYTYPE_GRPC_ENDPOINT.to_string())
15}
16
17/// Configuration for connecting to Anytype gRPC.
18#[derive(Debug, Clone)]
19pub struct AnytypeGrpcConfig {
20    endpoint: String,
21}
22
23impl Default for AnytypeGrpcConfig {
24    fn default() -> Self {
25        Self {
26            endpoint: default_grpc_endpoint(),
27        }
28    }
29}
30
31impl AnytypeGrpcConfig {
32    pub fn new(endpoint: impl Into<String>) -> Self {
33        Self {
34            endpoint: endpoint.into(),
35        }
36    }
37
38    pub fn endpoint(&self) -> &str {
39        &self.endpoint
40    }
41}
42
43/// gRPC client wrapper holding the connection and session token.
44#[derive(Debug, Clone)]
45pub struct AnytypeGrpcClient {
46    channel: Channel,
47    token: String,
48    endpoint: String,
49}
50
51impl AnytypeGrpcClient {
52    /// returns the endpoint
53    pub fn get_endpoint(&self) -> &str {
54        &self.endpoint
55    }
56
57    pub async fn connect_channel(config: &AnytypeGrpcConfig) -> Result<Channel, AnytypeGrpcError> {
58        let endpoint = Endpoint::from_shared(config.endpoint.clone())?
59            .connect_timeout(Duration::from_secs(30))
60            .tcp_keepalive(Some(Duration::from_secs(60)))
61            .http2_keep_alive_interval(Duration::from_secs(30))
62            .keep_alive_timeout(Duration::from_secs(10))
63            .keep_alive_while_idle(true);
64        Ok(endpoint.connect().await?)
65    }
66
67    /// if you're using the headless client, you can generate a session token
68    /// from the account key in ~/.anytype/config.json
69    pub async fn from_account_key(
70        config: &AnytypeGrpcConfig,
71        account_key: impl AsRef<str>,
72    ) -> Result<Self, AnytypeGrpcError> {
73        let channel = Self::connect_channel(config).await?;
74        let token = create_session_token_from_account_key(channel.clone(), account_key).await?;
75        Ok(Self {
76            channel,
77            token,
78            endpoint: config.endpoint.clone(),
79        })
80    }
81
82    // this may not work: the api may not have sufficient scope to create a grpc token
83    pub async fn from_app_key(
84        config: &AnytypeGrpcConfig,
85        app_key: impl AsRef<str>,
86    ) -> Result<Self, AnytypeGrpcError> {
87        let channel = Self::connect_channel(config).await?;
88        let token = create_session_token_from_app_key(channel.clone(), app_key).await?;
89        Ok(Self {
90            channel,
91            token,
92            endpoint: config.endpoint.clone(),
93        })
94    }
95
96    pub async fn from_token(
97        config: &AnytypeGrpcConfig,
98        token: impl Into<String>,
99    ) -> Result<Self, AnytypeGrpcError> {
100        let channel = Self::connect_channel(config).await?;
101        Ok(Self {
102            channel,
103            token: token.into(),
104            endpoint: config.endpoint.clone(),
105        })
106    }
107
108    pub fn client_commands(&self) -> ClientCommandsClient<Channel> {
109        ClientCommandsClient::new(self.channel.clone())
110    }
111
112    pub fn token(&self) -> &str {
113        &self.token
114    }
115
116    pub fn channel(&self) -> Channel {
117        self.channel.clone()
118    }
119}