pub struct CortexClient { /* private fields */ }Expand description
AI Cortex SDK 客户端。
鉴权方式:用户 PAT(Authorization: Bearer actx_pat_...),由配置直接提供。
构造后自动启动心跳(除非 heartbeat_interval == 0),首次心跳即向服务端绑定当前设备。
Implementations§
Source§impl CortexClient
impl CortexClient
Sourcepub fn new(config: CortexConfig) -> Self
pub fn new(config: CortexConfig) -> Self
Examples found in repository?
examples/auth_check.rs (line 17)
8async fn main() {
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id 占位:心跳会按此软件的许可证校验设备上限。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new("http://localhost:40404", pat, software_id);
17 let client = CortexClient::new(config);
18
19 match client.list_software().await {
20 Ok(items) => {
21 println!("PAT valid. Software store returned {} item(s).", items.len());
22 }
23 Err(e) => {
24 eprintln!("PAT check failed: {e}");
25 std::process::exit(1);
26 }
27 }
28
29 println!("\n--- My Devices ---");
30 match client.list_devices().await {
31 Ok(devices) => {
32 println!("{} bound device(s).", devices.len());
33 for d in devices {
34 println!(
35 "- fingerprint={}, last_active={}",
36 d.fingerprint, d.last_active_time
37 );
38 }
39 }
40 Err(e) => eprintln!("List devices failed: {e}"),
41 }
42}More examples
examples/offline_license.rs (line 36)
15async fn main() {
16 let server_url =
17 std::env::var("CORTEX_SERVER_URL").unwrap_or_else(|_| "http://localhost:40404".into());
18 let pat = std::env::args()
19 .nth(1)
20 .or_else(|| std::env::var("CORTEX_PAT").ok())
21 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
22 let software_id = std::env::args()
23 .nth(2)
24 .or_else(|| std::env::var("CORTEX_SOFTWARE_ID").ok())
25 .unwrap_or_else(|| "00000000-0000-0000-0000-000000000000".to_string());
26 let pinned_pk = std::env::args().nth(3);
27
28 // 关闭心跳,避免示例对服务端产生副作用
29 let config = CortexConfig::new(&server_url, &pat, &software_id)
30 .without_heartbeat();
31 let config = if let Some(pk) = &pinned_pk {
32 config.with_software_public_key(pk)
33 } else {
34 config
35 };
36 let client = CortexClient::new(config);
37
38 // 采集设备指纹
39 let dev = ai_cortex_sdk::device::collect();
40 let fingerprint = dev.fingerprint.as_str();
41 println!("Device fingerprint: {fingerprint}");
42
43 // 1. 拉取离线许可证
44 println!("\n=== Fetch offline license ===");
45 let file: OfflineLicenseFile = match client.fetch_offline_license(fingerprint).await {
46 Ok(f) => f,
47 Err(e) => {
48 eprintln!("fetch failed: {e}");
49 return;
50 }
51 };
52 println!("public_key: {}", file.public_key);
53 println!("signature : {}", file.signature);
54 println!("payload : {}", file.payload);
55
56 // 2. 本地验签(钉扎公钥来自参数)
57 println!("\n=== Verify offline license ===");
58 match verify_offline_license(
59 &file,
60 &software_id,
61 fingerprint,
62 pinned_pk.as_deref(),
63 ) {
64 Ok(payload) => {
65 println!("VALID. payload parsed:");
66 println!(" license_id : {}", payload.license_id);
67 println!(" user_id : {}", payload.user_id);
68 println!(" software_id : {}", payload.software_id);
69 println!(" fingerprint : {}", payload.fingerprint);
70 println!(" max_devices : {}", payload.max_devices);
71 println!(" issued_at : {}", payload.issued_at);
72 println!(" expire_time : {}", payload.expire_time);
73 }
74 Err(e) => {
75 eprintln!("INVALID license: {e}");
76 }
77 }
78}examples/basic.rs (line 17)
7async fn main() {
8 let server_url = "http://localhost:40404";
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id:SDK 以哪个软件身份心跳/下载,需与用户持有的有效 auth_record.software_id 一致。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new(server_url, pat, software_id).with_timeout(30);
17 let client = CortexClient::new(config);
18
19 // --- List Software ---
20 println!("=== Software Store ===");
21 match client.list_software().await {
22 Ok(items) => {
23 if items.is_empty() {
24 println!("No software available.");
25 }
26 for (i, item) in items.iter().enumerate() {
27 let version_str = item
28 .latest_version
29 .as_ref()
30 .map(|v| format!("v{} ({} bytes)", v.version, v.file_size))
31 .unwrap_or_else(|| "no version".to_string());
32 println!(
33 "{}. {} — {} [{}]",
34 i + 1,
35 item.software.name,
36 item.software.description,
37 version_str
38 );
39 }
40
41 // --- Download first software's latest version ---
42 if let Some(item) = items.first() {
43 if let Some(version) = &item.latest_version {
44 println!("\n=== Download ===");
45 match client.download(&version.id).await {
46 Ok(info) => {
47 println!(
48 "Download: v{}, size={} bytes\n URL: {}",
49 info.version, info.file_size, info.download_url
50 );
51 }
52 Err(e) => eprintln!("Download failed: {e}"),
53 }
54 }
55 }
56 }
57 Err(e) => eprintln!("List software failed: {e}"),
58 }
59
60 // --- List Devices (PAT 所属用户的设备;心跳在后台自动绑定) ---
61 println!("\n=== My Devices ===");
62 match client.list_devices().await {
63 Ok(devices) => {
64 if devices.is_empty() {
65 println!("No bound devices yet (heartbeat may not have fired yet).");
66 }
67 for d in devices {
68 println!(
69 "- id={}, fingerprint={}, last_active={}",
70 d.id, d.fingerprint, d.last_active_time
71 );
72 }
73 }
74 Err(e) => eprintln!("List devices failed: {e}"),
75 }
76}examples/auto_update.rs (line 29)
13async fn main() {
14 let server_url =
15 std::env::var("CORTEX_SERVER_URL").unwrap_or_else(|_| "http://localhost:40404".into());
16 let pat = std::env::args()
17 .nth(1)
18 .or_else(|| std::env::var("CORTEX_PAT").ok())
19 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
20 let software_id = std::env::args()
21 .nth(2)
22 .or_else(|| std::env::var("CORTEX_SOFTWARE_ID").ok())
23 .unwrap_or_else(|| "00000000-0000-0000-0000-000000000000".to_string());
24 // 第 3 个位置参数(任意值)= 演示 SSE 流
25 let demo_stream = std::env::args().nth(3).is_some();
26
27 // 关闭心跳,避免示例对服务端产生副作用
28 let config = CortexConfig::new(&server_url, &pat, &software_id).without_heartbeat();
29 let client = CortexClient::new(config);
30
31 // --- 1. Pull:检查更新 ---
32 println!("=== Check Update (Pull) ===");
33 let platform = std::env::consts::OS; // linux / macos / windows
34 let current_version = "1.0.0";
35 match client
36 .check_update(platform, current_version, None)
37 .await
38 {
39 Ok(info) => {
40 println!("current_version : {}", info.current_version);
41 println!("has_update : {}", info.has_update);
42 println!("force_update : {}", info.force_update);
43 if let Some(t) = &info.target_version {
44 println!("target_version : {} (channel={}, size={} bytes)", t.version, t.channel, t.file_size);
45 println!("release_notes : {}", t.release_notes);
46 println!("min_compatible : {}", t.min_compatible);
47 }
48 // force_update 语义:客户端应阻止启动直到用户升级
49 if info.has_update && info.force_update {
50 println!("\n>>> 强制更新:当前版本被阻塞,必须升级后才能继续。");
51 }
52 }
53 Err(e) => eprintln!("check_update failed: {e}"),
54 }
55
56 // --- 2. Push:订阅 SSE 更新事件流(可选) ---
57 if demo_stream {
58 println!("\n=== Update Events (Push / SSE) ===");
59 match client.open_update_events(Some(&software_id), None).await {
60 Ok(mut stream) => {
61 println!("SSE connected. Waiting for events (will print 3 then exit)...");
62 let mut count = 0usize;
63 while let Some(ev) = stream.next().await {
64 match ev {
65 Ok(e) => {
66 println!(
67 "event #{}: software={} version={} platform={} channel={} force={}",
68 e.id, e.software_id, e.version, e.platform, e.channel, e.force_update
69 );
70 count += 1;
71 if count >= 3 {
72 println!("Received 3 events, exiting demo.");
73 break;
74 }
75 }
76 Err(e) => {
77 eprintln!("stream error: {e}");
78 break;
79 }
80 }
81 }
82 }
83 Err(e) => eprintln!("open_update_events failed: {e}"),
84 }
85 }
86}Sourcepub async fn list_software(&self) -> SdkResult<Vec<SoftwareStoreItem>>
pub async fn list_software(&self) -> SdkResult<Vec<SoftwareStoreItem>>
Examples found in repository?
examples/auth_check.rs (line 19)
8async fn main() {
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id 占位:心跳会按此软件的许可证校验设备上限。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new("http://localhost:40404", pat, software_id);
17 let client = CortexClient::new(config);
18
19 match client.list_software().await {
20 Ok(items) => {
21 println!("PAT valid. Software store returned {} item(s).", items.len());
22 }
23 Err(e) => {
24 eprintln!("PAT check failed: {e}");
25 std::process::exit(1);
26 }
27 }
28
29 println!("\n--- My Devices ---");
30 match client.list_devices().await {
31 Ok(devices) => {
32 println!("{} bound device(s).", devices.len());
33 for d in devices {
34 println!(
35 "- fingerprint={}, last_active={}",
36 d.fingerprint, d.last_active_time
37 );
38 }
39 }
40 Err(e) => eprintln!("List devices failed: {e}"),
41 }
42}More examples
examples/basic.rs (line 21)
7async fn main() {
8 let server_url = "http://localhost:40404";
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id:SDK 以哪个软件身份心跳/下载,需与用户持有的有效 auth_record.software_id 一致。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new(server_url, pat, software_id).with_timeout(30);
17 let client = CortexClient::new(config);
18
19 // --- List Software ---
20 println!("=== Software Store ===");
21 match client.list_software().await {
22 Ok(items) => {
23 if items.is_empty() {
24 println!("No software available.");
25 }
26 for (i, item) in items.iter().enumerate() {
27 let version_str = item
28 .latest_version
29 .as_ref()
30 .map(|v| format!("v{} ({} bytes)", v.version, v.file_size))
31 .unwrap_or_else(|| "no version".to_string());
32 println!(
33 "{}. {} — {} [{}]",
34 i + 1,
35 item.software.name,
36 item.software.description,
37 version_str
38 );
39 }
40
41 // --- Download first software's latest version ---
42 if let Some(item) = items.first() {
43 if let Some(version) = &item.latest_version {
44 println!("\n=== Download ===");
45 match client.download(&version.id).await {
46 Ok(info) => {
47 println!(
48 "Download: v{}, size={} bytes\n URL: {}",
49 info.version, info.file_size, info.download_url
50 );
51 }
52 Err(e) => eprintln!("Download failed: {e}"),
53 }
54 }
55 }
56 }
57 Err(e) => eprintln!("List software failed: {e}"),
58 }
59
60 // --- List Devices (PAT 所属用户的设备;心跳在后台自动绑定) ---
61 println!("\n=== My Devices ===");
62 match client.list_devices().await {
63 Ok(devices) => {
64 if devices.is_empty() {
65 println!("No bound devices yet (heartbeat may not have fired yet).");
66 }
67 for d in devices {
68 println!(
69 "- id={}, fingerprint={}, last_active={}",
70 d.id, d.fingerprint, d.last_active_time
71 );
72 }
73 }
74 Err(e) => eprintln!("List devices failed: {e}"),
75 }
76}pub async fn get_latest_version( &self, software_id: &str, ) -> SdkResult<SoftwareVersion>
Sourcepub async fn download(&self, version_id: &str) -> SdkResult<DownloadInfo>
pub async fn download(&self, version_id: &str) -> SdkResult<DownloadInfo>
Examples found in repository?
examples/basic.rs (line 45)
7async fn main() {
8 let server_url = "http://localhost:40404";
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id:SDK 以哪个软件身份心跳/下载,需与用户持有的有效 auth_record.software_id 一致。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new(server_url, pat, software_id).with_timeout(30);
17 let client = CortexClient::new(config);
18
19 // --- List Software ---
20 println!("=== Software Store ===");
21 match client.list_software().await {
22 Ok(items) => {
23 if items.is_empty() {
24 println!("No software available.");
25 }
26 for (i, item) in items.iter().enumerate() {
27 let version_str = item
28 .latest_version
29 .as_ref()
30 .map(|v| format!("v{} ({} bytes)", v.version, v.file_size))
31 .unwrap_or_else(|| "no version".to_string());
32 println!(
33 "{}. {} — {} [{}]",
34 i + 1,
35 item.software.name,
36 item.software.description,
37 version_str
38 );
39 }
40
41 // --- Download first software's latest version ---
42 if let Some(item) = items.first() {
43 if let Some(version) = &item.latest_version {
44 println!("\n=== Download ===");
45 match client.download(&version.id).await {
46 Ok(info) => {
47 println!(
48 "Download: v{}, size={} bytes\n URL: {}",
49 info.version, info.file_size, info.download_url
50 );
51 }
52 Err(e) => eprintln!("Download failed: {e}"),
53 }
54 }
55 }
56 }
57 Err(e) => eprintln!("List software failed: {e}"),
58 }
59
60 // --- List Devices (PAT 所属用户的设备;心跳在后台自动绑定) ---
61 println!("\n=== My Devices ===");
62 match client.list_devices().await {
63 Ok(devices) => {
64 if devices.is_empty() {
65 println!("No bound devices yet (heartbeat may not have fired yet).");
66 }
67 for d in devices {
68 println!(
69 "- id={}, fingerprint={}, last_active={}",
70 d.id, d.fingerprint, d.last_active_time
71 );
72 }
73 }
74 Err(e) => eprintln!("List devices failed: {e}"),
75 }
76}Sourcepub async fn check_update(
&self,
platform: &str,
current_version: &str,
channel: Option<&str>,
) -> SdkResult<UpdateInfo>
pub async fn check_update( &self, platform: &str, current_version: &str, channel: Option<&str>, ) -> SdkResult<UpdateInfo>
检查更新(Pull)。按配置的 software_id + 指定 platform/channel,与 current_version 比较。
channel 传 None 默认 “stable”。需对该 software 持有有效许可证,否则返回 ServerError。
Examples found in repository?
examples/auto_update.rs (line 36)
13async fn main() {
14 let server_url =
15 std::env::var("CORTEX_SERVER_URL").unwrap_or_else(|_| "http://localhost:40404".into());
16 let pat = std::env::args()
17 .nth(1)
18 .or_else(|| std::env::var("CORTEX_PAT").ok())
19 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
20 let software_id = std::env::args()
21 .nth(2)
22 .or_else(|| std::env::var("CORTEX_SOFTWARE_ID").ok())
23 .unwrap_or_else(|| "00000000-0000-0000-0000-000000000000".to_string());
24 // 第 3 个位置参数(任意值)= 演示 SSE 流
25 let demo_stream = std::env::args().nth(3).is_some();
26
27 // 关闭心跳,避免示例对服务端产生副作用
28 let config = CortexConfig::new(&server_url, &pat, &software_id).without_heartbeat();
29 let client = CortexClient::new(config);
30
31 // --- 1. Pull:检查更新 ---
32 println!("=== Check Update (Pull) ===");
33 let platform = std::env::consts::OS; // linux / macos / windows
34 let current_version = "1.0.0";
35 match client
36 .check_update(platform, current_version, None)
37 .await
38 {
39 Ok(info) => {
40 println!("current_version : {}", info.current_version);
41 println!("has_update : {}", info.has_update);
42 println!("force_update : {}", info.force_update);
43 if let Some(t) = &info.target_version {
44 println!("target_version : {} (channel={}, size={} bytes)", t.version, t.channel, t.file_size);
45 println!("release_notes : {}", t.release_notes);
46 println!("min_compatible : {}", t.min_compatible);
47 }
48 // force_update 语义:客户端应阻止启动直到用户升级
49 if info.has_update && info.force_update {
50 println!("\n>>> 强制更新:当前版本被阻塞,必须升级后才能继续。");
51 }
52 }
53 Err(e) => eprintln!("check_update failed: {e}"),
54 }
55
56 // --- 2. Push:订阅 SSE 更新事件流(可选) ---
57 if demo_stream {
58 println!("\n=== Update Events (Push / SSE) ===");
59 match client.open_update_events(Some(&software_id), None).await {
60 Ok(mut stream) => {
61 println!("SSE connected. Waiting for events (will print 3 then exit)...");
62 let mut count = 0usize;
63 while let Some(ev) = stream.next().await {
64 match ev {
65 Ok(e) => {
66 println!(
67 "event #{}: software={} version={} platform={} channel={} force={}",
68 e.id, e.software_id, e.version, e.platform, e.channel, e.force_update
69 );
70 count += 1;
71 if count >= 3 {
72 println!("Received 3 events, exiting demo.");
73 break;
74 }
75 }
76 Err(e) => {
77 eprintln!("stream error: {e}");
78 break;
79 }
80 }
81 }
82 }
83 Err(e) => eprintln!("open_update_events failed: {e}"),
84 }
85 }
86}Sourcepub async fn open_update_events(
&self,
software_id: Option<&str>,
last_event_id: Option<u64>,
) -> SdkResult<UpdateEventStream>
pub async fn open_update_events( &self, software_id: Option<&str>, last_event_id: Option<u64>, ) -> SdkResult<UpdateEventStream>
打开更新事件 SSE 流(Push)。software_id 传 None 订阅全部;last_event_id 用于断线重连补播。
返回的流需配合 futures_util::StreamExt 使用:while let Some(ev) = stream.next().await { ... }。
注意:SSE 是长连接,内部使用无读超时的 HTTP 客户端;靠服务端 15s keepalive 保活。
Examples found in repository?
examples/auto_update.rs (line 59)
13async fn main() {
14 let server_url =
15 std::env::var("CORTEX_SERVER_URL").unwrap_or_else(|_| "http://localhost:40404".into());
16 let pat = std::env::args()
17 .nth(1)
18 .or_else(|| std::env::var("CORTEX_PAT").ok())
19 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
20 let software_id = std::env::args()
21 .nth(2)
22 .or_else(|| std::env::var("CORTEX_SOFTWARE_ID").ok())
23 .unwrap_or_else(|| "00000000-0000-0000-0000-000000000000".to_string());
24 // 第 3 个位置参数(任意值)= 演示 SSE 流
25 let demo_stream = std::env::args().nth(3).is_some();
26
27 // 关闭心跳,避免示例对服务端产生副作用
28 let config = CortexConfig::new(&server_url, &pat, &software_id).without_heartbeat();
29 let client = CortexClient::new(config);
30
31 // --- 1. Pull:检查更新 ---
32 println!("=== Check Update (Pull) ===");
33 let platform = std::env::consts::OS; // linux / macos / windows
34 let current_version = "1.0.0";
35 match client
36 .check_update(platform, current_version, None)
37 .await
38 {
39 Ok(info) => {
40 println!("current_version : {}", info.current_version);
41 println!("has_update : {}", info.has_update);
42 println!("force_update : {}", info.force_update);
43 if let Some(t) = &info.target_version {
44 println!("target_version : {} (channel={}, size={} bytes)", t.version, t.channel, t.file_size);
45 println!("release_notes : {}", t.release_notes);
46 println!("min_compatible : {}", t.min_compatible);
47 }
48 // force_update 语义:客户端应阻止启动直到用户升级
49 if info.has_update && info.force_update {
50 println!("\n>>> 强制更新:当前版本被阻塞,必须升级后才能继续。");
51 }
52 }
53 Err(e) => eprintln!("check_update failed: {e}"),
54 }
55
56 // --- 2. Push:订阅 SSE 更新事件流(可选) ---
57 if demo_stream {
58 println!("\n=== Update Events (Push / SSE) ===");
59 match client.open_update_events(Some(&software_id), None).await {
60 Ok(mut stream) => {
61 println!("SSE connected. Waiting for events (will print 3 then exit)...");
62 let mut count = 0usize;
63 while let Some(ev) = stream.next().await {
64 match ev {
65 Ok(e) => {
66 println!(
67 "event #{}: software={} version={} platform={} channel={} force={}",
68 e.id, e.software_id, e.version, e.platform, e.channel, e.force_update
69 );
70 count += 1;
71 if count >= 3 {
72 println!("Received 3 events, exiting demo.");
73 break;
74 }
75 }
76 Err(e) => {
77 eprintln!("stream error: {e}");
78 break;
79 }
80 }
81 }
82 }
83 Err(e) => eprintln!("open_update_events failed: {e}"),
84 }
85 }
86}Sourcepub async fn list_devices(&self) -> SdkResult<Vec<DeviceRecord>>
pub async fn list_devices(&self) -> SdkResult<Vec<DeviceRecord>>
列出当前用户名下绑定的有效设备。
Examples found in repository?
examples/auth_check.rs (line 30)
8async fn main() {
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id 占位:心跳会按此软件的许可证校验设备上限。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new("http://localhost:40404", pat, software_id);
17 let client = CortexClient::new(config);
18
19 match client.list_software().await {
20 Ok(items) => {
21 println!("PAT valid. Software store returned {} item(s).", items.len());
22 }
23 Err(e) => {
24 eprintln!("PAT check failed: {e}");
25 std::process::exit(1);
26 }
27 }
28
29 println!("\n--- My Devices ---");
30 match client.list_devices().await {
31 Ok(devices) => {
32 println!("{} bound device(s).", devices.len());
33 for d in devices {
34 println!(
35 "- fingerprint={}, last_active={}",
36 d.fingerprint, d.last_active_time
37 );
38 }
39 }
40 Err(e) => eprintln!("List devices failed: {e}"),
41 }
42}More examples
examples/basic.rs (line 62)
7async fn main() {
8 let server_url = "http://localhost:40404";
9 let pat = std::env::args()
10 .nth(1)
11 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
12
13 // software_id:SDK 以哪个软件身份心跳/下载,需与用户持有的有效 auth_record.software_id 一致。
14 let software_id = "00000000-0000-0000-0000-000000000000".to_string();
15
16 let config = CortexConfig::new(server_url, pat, software_id).with_timeout(30);
17 let client = CortexClient::new(config);
18
19 // --- List Software ---
20 println!("=== Software Store ===");
21 match client.list_software().await {
22 Ok(items) => {
23 if items.is_empty() {
24 println!("No software available.");
25 }
26 for (i, item) in items.iter().enumerate() {
27 let version_str = item
28 .latest_version
29 .as_ref()
30 .map(|v| format!("v{} ({} bytes)", v.version, v.file_size))
31 .unwrap_or_else(|| "no version".to_string());
32 println!(
33 "{}. {} — {} [{}]",
34 i + 1,
35 item.software.name,
36 item.software.description,
37 version_str
38 );
39 }
40
41 // --- Download first software's latest version ---
42 if let Some(item) = items.first() {
43 if let Some(version) = &item.latest_version {
44 println!("\n=== Download ===");
45 match client.download(&version.id).await {
46 Ok(info) => {
47 println!(
48 "Download: v{}, size={} bytes\n URL: {}",
49 info.version, info.file_size, info.download_url
50 );
51 }
52 Err(e) => eprintln!("Download failed: {e}"),
53 }
54 }
55 }
56 }
57 Err(e) => eprintln!("List software failed: {e}"),
58 }
59
60 // --- List Devices (PAT 所属用户的设备;心跳在后台自动绑定) ---
61 println!("\n=== My Devices ===");
62 match client.list_devices().await {
63 Ok(devices) => {
64 if devices.is_empty() {
65 println!("No bound devices yet (heartbeat may not have fired yet).");
66 }
67 for d in devices {
68 println!(
69 "- id={}, fingerprint={}, last_active={}",
70 d.id, d.fingerprint, d.last_active_time
71 );
72 }
73 }
74 Err(e) => eprintln!("List devices failed: {e}"),
75 }
76}Sourcepub async fn unbind_device(&self, device_id: &str) -> SdkResult<()>
pub async fn unbind_device(&self, device_id: &str) -> SdkResult<()>
解绑指定设备(将其 status 置 0,释放设备名额)。
Sourcepub async fn fetch_offline_license(
&self,
fingerprint: &str,
) -> SdkResult<OfflineLicenseFile>
pub async fn fetch_offline_license( &self, fingerprint: &str, ) -> SdkResult<OfflineLicenseFile>
向服务端拉取离线许可证(需有效 PAT 与有效软件授权)。
fingerprint 为当前设备指纹。返回未验签的许可证文件,需配合
verify_offline_license 进行本地验签后才可信任。
Examples found in repository?
examples/offline_license.rs (line 45)
15async fn main() {
16 let server_url =
17 std::env::var("CORTEX_SERVER_URL").unwrap_or_else(|_| "http://localhost:40404".into());
18 let pat = std::env::args()
19 .nth(1)
20 .or_else(|| std::env::var("CORTEX_PAT").ok())
21 .unwrap_or_else(|| "REPLACE_WITH_YOUR_PAT".to_string());
22 let software_id = std::env::args()
23 .nth(2)
24 .or_else(|| std::env::var("CORTEX_SOFTWARE_ID").ok())
25 .unwrap_or_else(|| "00000000-0000-0000-0000-000000000000".to_string());
26 let pinned_pk = std::env::args().nth(3);
27
28 // 关闭心跳,避免示例对服务端产生副作用
29 let config = CortexConfig::new(&server_url, &pat, &software_id)
30 .without_heartbeat();
31 let config = if let Some(pk) = &pinned_pk {
32 config.with_software_public_key(pk)
33 } else {
34 config
35 };
36 let client = CortexClient::new(config);
37
38 // 采集设备指纹
39 let dev = ai_cortex_sdk::device::collect();
40 let fingerprint = dev.fingerprint.as_str();
41 println!("Device fingerprint: {fingerprint}");
42
43 // 1. 拉取离线许可证
44 println!("\n=== Fetch offline license ===");
45 let file: OfflineLicenseFile = match client.fetch_offline_license(fingerprint).await {
46 Ok(f) => f,
47 Err(e) => {
48 eprintln!("fetch failed: {e}");
49 return;
50 }
51 };
52 println!("public_key: {}", file.public_key);
53 println!("signature : {}", file.signature);
54 println!("payload : {}", file.payload);
55
56 // 2. 本地验签(钉扎公钥来自参数)
57 println!("\n=== Verify offline license ===");
58 match verify_offline_license(
59 &file,
60 &software_id,
61 fingerprint,
62 pinned_pk.as_deref(),
63 ) {
64 Ok(payload) => {
65 println!("VALID. payload parsed:");
66 println!(" license_id : {}", payload.license_id);
67 println!(" user_id : {}", payload.user_id);
68 println!(" software_id : {}", payload.software_id);
69 println!(" fingerprint : {}", payload.fingerprint);
70 println!(" max_devices : {}", payload.max_devices);
71 println!(" issued_at : {}", payload.issued_at);
72 println!(" expire_time : {}", payload.expire_time);
73 }
74 Err(e) => {
75 eprintln!("INVALID license: {e}");
76 }
77 }
78}Sourcepub fn verify_offline_license(
&self,
file: &OfflineLicenseFile,
expected_fingerprint: &str,
) -> SdkResult<OfflineLicensePayload>
pub fn verify_offline_license( &self, file: &OfflineLicenseFile, expected_fingerprint: &str, ) -> SdkResult<OfflineLicensePayload>
使用配置中的 software_public_key(若设置)验签当前许可证并解析 payload。
若未钉扎公钥,则信任服务端下发公钥(依赖 TLS)。
Trait Implementations§
Source§impl Drop for CortexClient
impl Drop for CortexClient
Auto Trait Implementations§
impl !RefUnwindSafe for CortexClient
impl !UnwindSafe for CortexClient
impl Freeze for CortexClient
impl Send for CortexClient
impl Sync for CortexClient
impl Unpin for CortexClient
impl UnsafeUnpin for CortexClient
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more