pub mod browser;
pub mod callback;
mod flow;
pub mod pkce;
pub use flow::{fresh, login};
use serde_json::{Value, json};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Service {
Claude,
ChatGpt,
}
impl Service {
pub fn provider_id(self) -> &'static str {
match self {
Service::Claude => "claude-subscription",
Service::ChatGpt => "chatgpt-subscription",
}
}
pub fn from_provider(provider: &str) -> Option<Self> {
[Service::Claude, Service::ChatGpt].into_iter().find(|service| service.provider_id() == provider)
}
pub fn account(self) -> &'static str {
match self {
Service::Claude => "Claude",
Service::ChatGpt => "ChatGPT",
}
}
fn slot(self) -> String {
format!("oauth-{}", self.provider_id())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Credential {
pub access: String,
pub refresh: String,
pub expires_ms: u64,
pub account_id: Option<String>,
}
impl Credential {
fn to_json(&self) -> String {
json!({ "access": self.access, "refresh": self.refresh, "expires_ms": self.expires_ms, "account_id": self.account_id })
.to_string()
}
fn from_json(text: &str) -> Option<Self> {
let value: Value = serde_json::from_str(text).ok()?;
Some(Self {
access: value["access"].as_str()?.to_owned(),
refresh: value["refresh"].as_str()?.to_owned(),
expires_ms: value["expires_ms"].as_u64()?,
account_id: value["account_id"].as_str().map(str::to_owned),
})
}
}
pub fn stored(service: Service) -> Option<Credential> {
crate::ai::secrets::get_exact(&service.slot()).and_then(|text| Credential::from_json(&text))
}
fn save(service: Service, credential: &Credential) -> Result<(), String> {
crate::ai::secrets::set(&service.slot(), &credential.to_json())
}
pub fn logout(service: Service) -> Result<(), String> {
crate::ai::secrets::remove(&service.slot())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn las_credenciales_ida_y_vuelta() {
let credential =
Credential { access: "a".into(), refresh: "r".into(), expires_ms: 42, account_id: Some("acc".into()) };
assert_eq!(Credential::from_json(&credential.to_json()), Some(credential));
assert!(Credential::from_json("{}").is_none());
}
#[test]
fn los_proveedores_se_reconocen_por_su_id() {
assert_eq!(Service::from_provider("claude-subscription"), Some(Service::Claude));
assert_eq!(Service::from_provider("chatgpt-subscription"), Some(Service::ChatGpt));
assert_eq!(Service::from_provider("anthropic"), None);
}
}