mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:13:51 +01:00
Add ability to deactivate license
This commit is contained in:
2
src-tauri/gen/schemas/acl-manifests.json
generated
2
src-tauri/gen/schemas/acl-manifests.json
generated
File diff suppressed because one or more lines are too long
10
src-tauri/gen/schemas/desktop-schema.json
generated
10
src-tauri/gen/schemas/desktop-schema.json
generated
@@ -5572,6 +5572,11 @@
|
||||
"type": "string",
|
||||
"const": "yaak-license:allow-check"
|
||||
},
|
||||
{
|
||||
"description": "Enables the deactivate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-license:allow-deactivate"
|
||||
},
|
||||
{
|
||||
"description": "Denies the activate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5582,6 +5587,11 @@
|
||||
"type": "string",
|
||||
"const": "yaak-license:deny-check"
|
||||
},
|
||||
{
|
||||
"description": "Denies the deactivate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-license:deny-deactivate"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
10
src-tauri/gen/schemas/macOS-schema.json
generated
10
src-tauri/gen/schemas/macOS-schema.json
generated
@@ -5572,6 +5572,11 @@
|
||||
"type": "string",
|
||||
"const": "yaak-license:allow-check"
|
||||
},
|
||||
{
|
||||
"description": "Enables the deactivate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-license:allow-deactivate"
|
||||
},
|
||||
{
|
||||
"description": "Denies the activate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5582,6 +5587,11 @@
|
||||
"type": "string",
|
||||
"const": "yaak-license:deny-check"
|
||||
},
|
||||
{
|
||||
"description": "Denies the deactivate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-license:deny-deactivate"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
@@ -8,4 +8,6 @@ export type ActivateLicenseResponsePayload = { activationId: string, };
|
||||
|
||||
export type CheckActivationResponsePayload = { active: boolean, };
|
||||
|
||||
export type DeactivateLicenseRequestPayload = { licenseKey: string, appVersion: string, appPlatform: string, };
|
||||
|
||||
export type LicenseCheckStatus = { "type": "personal_use", trial_ended: string, } | { "type": "commercial_use" } | { "type": "invalid_license" } | { "type": "trialing", end: string, };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const COMMANDS: &[&str] = &["activate", "check"];
|
||||
const COMMANDS: &[&str] = &["activate", "deactivate", "check"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
|
||||
@@ -14,6 +14,12 @@ export function useLicense() {
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }),
|
||||
});
|
||||
|
||||
const deactivate = useMutation<void, string, void>({
|
||||
mutationKey: ['license.deactivate'],
|
||||
mutationFn: () => invoke('plugin:yaak-license|deactivate'),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }),
|
||||
});
|
||||
|
||||
// Check the license again after a license is activated
|
||||
useEffect(() => {
|
||||
const unlisten = listen('license-activated', async () => {
|
||||
@@ -27,12 +33,14 @@ export function useLicense() {
|
||||
const CHECK_QUERY_KEY = ['license.check'];
|
||||
const check = useQuery<void, string, LicenseCheckStatus>({
|
||||
refetchInterval: 1000 * 60 * 60 * 12, // Refetch every 12 hours
|
||||
refetchOnWindowFocus: false,
|
||||
queryKey: CHECK_QUERY_KEY,
|
||||
queryFn: () => invoke('plugin:yaak-license|check'),
|
||||
});
|
||||
|
||||
return {
|
||||
activate,
|
||||
deactivate,
|
||||
check,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-deactivate"
|
||||
description = "Enables the deactivate command without any pre-configured scope."
|
||||
commands.allow = ["deactivate"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-deactivate"
|
||||
description = "Denies the deactivate command without any pre-configured scope."
|
||||
commands.deny = ["deactivate"]
|
||||
@@ -4,6 +4,7 @@ Default permissions for the plugin
|
||||
|
||||
- `allow-check`
|
||||
- `allow-activate`
|
||||
- `allow-deactivate`
|
||||
|
||||
## Permission Table
|
||||
|
||||
@@ -63,6 +64,32 @@ Enables the check command without any pre-configured scope.
|
||||
|
||||
Denies the check command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-license:allow-deactivate`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the deactivate command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-license:deny-deactivate`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the deactivate command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = ["allow-check", "allow-activate"]
|
||||
permissions = ["allow-check", "allow-activate", "allow-deactivate"]
|
||||
|
||||
@@ -314,6 +314,16 @@
|
||||
"type": "string",
|
||||
"const": "deny-check"
|
||||
},
|
||||
{
|
||||
"description": "Enables the deactivate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-deactivate"
|
||||
},
|
||||
{
|
||||
"description": "Denies the deactivate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-deactivate"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::errors::Result;
|
||||
use crate::{activate_license, check_license, ActivateLicenseRequestPayload, LicenseCheckStatus};
|
||||
use crate::{activate_license, check_license, deactivate_license, get_activation_id, ActivateLicenseRequestPayload, CheckActivationRequestPayload, DeactivateLicenseRequestPayload, LicenseCheckStatus};
|
||||
use log::{debug, info};
|
||||
use std::string::ToString;
|
||||
use tauri::{command, AppHandle, Manager, Runtime, WebviewWindow};
|
||||
@@ -7,7 +7,10 @@ use tauri::{command, AppHandle, Manager, Runtime, WebviewWindow};
|
||||
#[command]
|
||||
pub async fn check<R: Runtime>(app_handle: AppHandle<R>) -> Result<LicenseCheckStatus> {
|
||||
debug!("Checking license");
|
||||
check_license(&app_handle).await
|
||||
check_license(&app_handle, CheckActivationRequestPayload{
|
||||
app_platform: get_os().to_string(),
|
||||
app_version: app_handle.package_info().version.to_string(),
|
||||
}).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
@@ -24,6 +27,19 @@ pub async fn activate<R: Runtime>(license_key: &str, window: WebviewWindow<R>) -
|
||||
.await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn deactivate<R: Runtime>(window: WebviewWindow<R>) -> Result<()> {
|
||||
info!("Deactivating activation");
|
||||
deactivate_license(
|
||||
&window,
|
||||
DeactivateLicenseRequestPayload {
|
||||
app_platform: get_os().to_string(),
|
||||
app_version: window.app_handle().package_info().version.to_string(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn get_os() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
|
||||
@@ -8,9 +8,11 @@ mod commands;
|
||||
mod errors;
|
||||
mod license;
|
||||
|
||||
use crate::commands::{activate, check};
|
||||
use crate::commands::{activate, check, deactivate};
|
||||
pub use license::*;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("yaak-license").invoke_handler(generate_handler![check, activate]).build()
|
||||
Builder::new("yaak-license")
|
||||
.invoke_handler(generate_handler![check, activate, deactivate])
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use log::{debug, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Add;
|
||||
use std::time::Duration;
|
||||
use tauri::{is_dev, AppHandle, Emitter, Runtime, WebviewWindow};
|
||||
use tauri::{is_dev, AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||
use ts_rs::TS;
|
||||
use yaak_models::queries::UpdateSource;
|
||||
|
||||
@@ -17,7 +17,8 @@ const TRIAL_SECONDS: u64 = 3600 * 24 * 30;
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct CheckActivationRequestPayload {
|
||||
pub activation_id: String,
|
||||
pub app_version: String,
|
||||
pub app_platform: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
@@ -36,6 +37,14 @@ pub struct ActivateLicenseRequestPayload {
|
||||
pub app_platform: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
pub struct DeactivateLicenseRequestPayload {
|
||||
pub app_version: String,
|
||||
pub app_platform: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
@@ -56,7 +65,7 @@ pub async fn activate_license<R: Runtime>(
|
||||
p: ActivateLicenseRequestPayload,
|
||||
) -> Result<()> {
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.post(build_url("/activate")).json(&p).send().await?;
|
||||
let response = client.post(build_url("/licenses/activate")).json(&p).send().await?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let body: APIErrorResponsePayload = response.json().await?;
|
||||
@@ -86,6 +95,44 @@ pub async fn activate_license<R: Runtime>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn deactivate_license<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
p: DeactivateLicenseRequestPayload,
|
||||
) -> Result<()> {
|
||||
let activation_id = get_activation_id(window).await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let path = format!("/licenses/activations/{}/deactivate", activation_id);
|
||||
let response = client.post(build_url(&path)).json(&p).send().await?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let body: APIErrorResponsePayload = response.json().await?;
|
||||
return Err(ClientError {
|
||||
message: body.message,
|
||||
error: body.error,
|
||||
});
|
||||
}
|
||||
|
||||
if response.status().is_server_error() {
|
||||
return Err(ServerError);
|
||||
}
|
||||
|
||||
yaak_models::queries::delete_key_value(
|
||||
window,
|
||||
KV_ACTIVATION_ID_KEY,
|
||||
KV_NAMESPACE,
|
||||
&UpdateSource::Window,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(e) = window.emit("license-deactivated", true) {
|
||||
warn!("Failed to emit deactivate-license event: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
@@ -96,15 +143,8 @@ pub enum LicenseCheckStatus {
|
||||
Trialing { end: NaiveDateTime },
|
||||
}
|
||||
|
||||
pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>) -> Result<LicenseCheckStatus> {
|
||||
let activation_id = yaak_models::queries::get_key_value_string(
|
||||
app_handle,
|
||||
KV_ACTIVATION_ID_KEY,
|
||||
KV_NAMESPACE,
|
||||
"",
|
||||
)
|
||||
.await;
|
||||
|
||||
pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>, payload: CheckActivationRequestPayload) -> Result<LicenseCheckStatus> {
|
||||
let activation_id = get_activation_id(app_handle).await;
|
||||
let settings = yaak_models::queries::get_or_create_settings(app_handle).await;
|
||||
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
||||
|
||||
@@ -122,10 +162,8 @@ pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Lice
|
||||
info!("Checking license activation");
|
||||
// A license has been activated, so let's check the license server
|
||||
let client = reqwest::Client::new();
|
||||
let payload = CheckActivationRequestPayload {
|
||||
activation_id: activation_id.clone(),
|
||||
};
|
||||
let response = client.post(build_url("/check")).json(&payload).send().await?;
|
||||
let path = format!("/licenses/activations/{activation_id}/check");
|
||||
let response = client.post(build_url(&path)).json(&payload).send().await?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
let body: APIErrorResponsePayload = response.json().await?;
|
||||
@@ -151,8 +189,13 @@ pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Lice
|
||||
|
||||
fn build_url(path: &str) -> String {
|
||||
if is_dev() {
|
||||
format!("http://localhost:9444/licenses{path}")
|
||||
format!("http://localhost:9444{path}")
|
||||
} else {
|
||||
format!("https://license.yaak.app/licenses{path}")
|
||||
format!("https://license.yaak.app{path}")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_activation_id<R: Runtime>(mgr: &impl Manager<R>) -> String {
|
||||
yaak_models::queries::get_key_value_string(mgr, KV_ACTIVATION_ID_KEY, KV_NAMESPACE, "")
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -134,6 +134,31 @@ pub async fn set_key_value_raw<R: Runtime>(
|
||||
(m, existing.is_none())
|
||||
}
|
||||
|
||||
pub async fn delete_key_value<R: Runtime>(
|
||||
w: &WebviewWindow<R>,
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
update_source: &UpdateSource,
|
||||
) {
|
||||
let kv = match get_key_value_raw(w, namespace, key).await {
|
||||
None => return,
|
||||
Some(m) => m,
|
||||
};
|
||||
|
||||
let dbm = &*w.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
let (sql, params) = Query::delete()
|
||||
.from_table(KeyValueIden::Table)
|
||||
.cond_where(
|
||||
Cond::all()
|
||||
.add(Expr::col(KeyValueIden::Namespace).eq(namespace))
|
||||
.add(Expr::col(KeyValueIden::Key).eq(key)),
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
db.execute(sql.as_str(), &*params.as_params()).expect("Failed to delete PluginKeyValue");
|
||||
emit_deleted_model(w, &AnyModel::KeyValue(kv.to_owned()), update_source);
|
||||
}
|
||||
|
||||
pub async fn list_key_values_raw<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<KeyValue>> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user