Files
claude-code-usage-bubble/src/usage/refresh.rs
T
tiennm99 aa6217d2cf feat: clean-room rewrite — replace ported modules with original implementations
Every Rust module under src/ that previously contained upstream-derivative
code has been replaced by a from-scratch implementation:

  diag/    log + simplelog file appender (was: diagnose.rs)
  os/      color, dpi, registry, string, theme (was: theme.rs, native_interop.rs)
  net/     WinHTTP-based HTTP client (was: ureq + native-tls)
  i18n/    TOML-embedded locale tables (was: localization/*.rs)
  usage/   trait UsageProvider + ClaudeProvider + ChatGptProvider + refresh
           orchestrator + registry (was: poller.rs, models.rs)
  creds/   trait CredentialSource + local/WSL/Codex impls (was: poller.rs)
  tray/    stateless tray manager + tiny-skia anti-aliased badge renderer
           (was: tray_icon.rs)
  update/  release fetch + inline cmd /c handoff installer
           (was: updater.rs's helper-exe pattern)

Application files (app.rs, bubble.rs, panel.rs, settings.rs) migrated to
the new modules. main.rs declares only the new modules.

NOTICE deleted; LICENSE is plain Apache-2.0; README updated to credit
inspiration rather than claim derivation. Cargo.toml drops ureq + native-tls
+ winres in favour of log + simplelog + thiserror + toml + tiny-skia +
embed-resource. Build script swapped to embed-resource via res/icon.rc.

External contracts preserved unchanged: Anthropic + ChatGPT endpoints and
headers, ~/.claude/.credentials.json + Codex auth.json paths, WSL bridging
via wsl.exe, CLI-driven token refresh, GitHub Releases JSON shape, Windows
registry path for startup, single-instance mutex name.

Phase docs: plans/260516-0707-cleanroom-rewrite/.
2026-05-16 10:09:43 +07:00

124 lines
3.6 KiB
Rust

// Token refresh orchestrator.
//
// Each provider's credential source advertises a `RefreshHint` describing
// which CLI to spawn. We invoke that CLI and watch the credential file's
// signature; if it changes within the timeout we declare success.
use std::os::windows::process::CommandExt;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use crate::creds::{CredentialSource, RefreshHint};
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Outcome {
Refreshed,
StillExpired,
CliMissing,
Timeout,
}
pub struct Orchestrator {
timeout: Duration,
poll_interval: Duration,
}
impl Orchestrator {
pub fn new(timeout: Duration) -> Self {
Self {
timeout,
poll_interval: Duration::from_millis(500),
}
}
pub fn refresh(&self, source: &dyn CredentialSource) -> Outcome {
let initial_sig = source.signature();
let hint = source.refresh_hint();
if !spawn_cli(&hint) {
return Outcome::CliMissing;
}
let start = Instant::now();
while start.elapsed() < self.timeout {
std::thread::sleep(self.poll_interval);
if source.signature() != initial_sig {
return Outcome::Refreshed;
}
}
if source.signature() != initial_sig {
Outcome::Refreshed
} else {
Outcome::Timeout
}
}
}
fn spawn_cli(hint: &RefreshHint) -> bool {
match hint {
RefreshHint::LocalClaudeCli => spawn_local(&["claude.cmd", "claude.exe", "claude"], &["-p", "."]),
RefreshHint::WslClaudeCli { distro } => spawn_wsl(distro),
RefreshHint::LocalCodexCli => {
spawn_local(&["codex.cmd", "codex.ps1", "codex.exe", "codex"], &["exec", "."])
}
}
}
fn spawn_local(candidates: &[&str], args: &[&str]) -> bool {
for name in candidates {
let lower = name.to_ascii_lowercase();
let mut cmd = if lower.ends_with(".ps1") {
let mut c = Command::new("powershell.exe");
c.arg("-NoProfile").arg("-ExecutionPolicy").arg("Bypass").arg("-File").arg(name);
for a in args {
c.arg(a);
}
c
} else if lower.ends_with(".cmd") || lower.ends_with(".bat") {
let mut c = Command::new("cmd.exe");
c.arg("/c").arg(name);
for a in args {
c.arg(a);
}
c
} else {
let mut c = Command::new(name);
for a in args {
c.arg(a);
}
c
};
cmd.env_remove("CLAUDECODE")
.env_remove("CLAUDE_CODE_ENTRYPOINT")
.creation_flags(CREATE_NO_WINDOW)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
if cmd.spawn().is_ok() {
return true;
}
}
false
}
fn spawn_wsl(distro: &str) -> bool {
let script = "if command -v claude >/dev/null 2>&1; then claude -p .; \
elif [ -x \"$HOME/.local/bin/claude\" ]; then \"$HOME/.local/bin/claude\" -p .; \
else exit 127; fi";
Command::new("wsl.exe")
.arg("-d")
.arg(distro)
.arg("--")
.arg("bash")
.arg("-lic")
.arg(script)
.env_remove("CLAUDECODE")
.env_remove("CLAUDE_CODE_ENTRYPOINT")
.creation_flags(CREATE_NO_WINDOW)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.is_ok()
}