Changes for commercial use (#138)

This commit is contained in:
Gregory Schier
2024-12-03 09:28:27 -08:00
committed by GitHub
parent 2b076c90e4
commit 88bcfb9e66
49 changed files with 1072 additions and 96 deletions

44
src-tauri/Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -964,16 +964,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "command-group"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409"
dependencies = [
"nix",
"winapi",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@@ -6197,9 +6187,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.0.1"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e6660a409963e4d57b9bfab4addd141eeff41bd3a7fb14e13004a832cf7ef6"
checksum = "e753f2a30933a9bbf0a202fa47d7cc4a3401f06e8d6dcc53b79aa62954828c79"
dependencies = [
"anyhow",
"glob",
@@ -6373,6 +6363,22 @@ dependencies = [
"thiserror 1.0.63",
]
[[package]]
name = "tauri-plugin-yaak-license"
version = "0.1.0"
dependencies = [
"chrono",
"log",
"reqwest",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"thiserror 2.0.3",
"ts-rs",
"yaak_models",
]
[[package]]
name = "tauri-runtime"
version = "2.2.0"
@@ -6785,9 +6791,9 @@ dependencies = [
[[package]]
name = "tonic"
version = "0.12.3"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
checksum = "38659f4a91aba8598d27821589f5db7dddd94601e7a01b1e485a50e5484c7401"
dependencies = [
"async-stream",
"async-trait",
@@ -7991,6 +7997,7 @@ dependencies = [
"tauri-plugin-shell",
"tauri-plugin-updater",
"tauri-plugin-window-state",
"tauri-plugin-yaak-license",
"tokio",
"tokio-stream",
"urlencoding",
@@ -8051,22 +8058,19 @@ dependencies = [
name = "yaak_plugin_runtime"
version = "0.1.0"
dependencies = [
"anyhow",
"command-group",
"dunce",
"log",
"path-slash",
"prost 0.13.3",
"rand 0.8.5",
"regex",
"reqwest",
"serde",
"serde_json",
"tauri",
"tauri-plugin-shell",
"thiserror 1.0.63",
"tokio",
"tonic 0.12.3",
"tonic 0.12.1",
"tonic-build",
"ts-rs",
"yaak_models",
@@ -8077,7 +8081,6 @@ name = "yaak_sse"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"ts-rs",
]
@@ -8087,7 +8090,6 @@ version = "0.1.0"
dependencies = [
"log",
"serde",
"serde_json",
"tokio",
"ts-rs",
]

View File

@@ -1,11 +1,19 @@
[workspace]
members = ["yaak_grpc", "yaak_templates", "yaak_plugin_runtime", "yaak_models", "yaak_sse"]
members = [
"yaak_grpc",
"yaak_license",
"yaak_models",
"yaak_plugin_runtime",
"yaak_sse",
"yaak_templates",
]
[package]
name = "yaak-app"
version = "0.0.0"
edition = "2021"
authors = ["Gregory Schier"]
publish = false
# Produce a library for mobile support
[lib]
@@ -27,10 +35,11 @@ openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installa
[dependencies]
yaak_grpc = { path = "yaak_grpc" }
yaak_templates = { path = "yaak_templates" }
tauri-plugin-yaak-license = { path = "yaak_license" }
yaak_models = { workspace = true }
yaak_plugin_runtime = { workspace = true }
yaak_sse = { workspace = true }
yaak_models = { workspace = true }
yaak_templates = { path = "yaak_templates" }
base64 = "0.22.0"
chrono = { version = "0.4.31", features = ["serde"] }
datetime = "0.5.2"
@@ -39,7 +48,7 @@ http = "1"
log = "0.4.21"
rand = "0.8.5"
regex = "1.10.2"
reqwest = { version = "0.12.4", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
reqwest = { workspace = true, features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
reqwest_cookie_store = "0.8.0"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
@@ -66,6 +75,7 @@ yaak_plugin_runtime = { path = "yaak_plugin_runtime" }
serde = "1.0.215"
serde_json = "1.0.132"
tauri-plugin-shell = "2.0.2"
tauri = { version = "2.1.1", features = ["devtools", "protocol-asset"] }
tauri = "2.1.1"
thiserror = "2.0.3"
ts-rs = "10.0.0"
reqwest = "0.12.4"

View File

@@ -7,6 +7,7 @@
"*"
],
"permissions": [
"yaak-license:default",
"core:event:allow-emit",
"core:event:allow-listen",
"core:event:allow-unlisten",

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["yaak-license:default","core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}

View File

@@ -5141,6 +5141,31 @@
"description": "Denies the save_window_state command without any pre-configured scope.",
"type": "string",
"const": "window-state:deny-save-window-state"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "yaak-license:default"
},
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-activate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-check"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-activate"
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-check"
}
]
},

View File

@@ -5141,6 +5141,31 @@
"description": "Denies the save_window_state command without any pre-configured scope.",
"type": "string",
"const": "window-state:deny-save-window-state"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "yaak-license:default"
},
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-activate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:allow-check"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-activate"
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "yaak-license:deny-check"
}
]
},

View File

@@ -1687,6 +1687,7 @@ pub fn run() {
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init())
.plugin(yaak_models::plugin::Builder::default().build())
.plugin(tauri_plugin_yaak_license::init())
.plugin(yaak_plugin_runtime::plugin::init());
#[cfg(target_os = "macos")]

View File

@@ -2,6 +2,7 @@
name = "yaak_grpc"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
tonic = "0.10.2"
@@ -22,4 +23,4 @@ tauri = { workspace = true }
tauri-plugin-shell = { workspace = true }
md5 = "0.7.0"
dunce = "1.0.4"
async-recursion = "1.1.1"
async-recursion = "1.1.1"

View File

@@ -0,0 +1,20 @@
[package]
name = "tauri-plugin-yaak-license"
links = "tauri-plugin-yaak-license"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
reqwest = { workspace = true, features = ["json"] }
serde = { version = "1.0.208", features = ["derive"] }
ts-rs = { workspace = true }
thiserror = { workspace = true }
tauri = { workspace = true }
yaak_models = { workspace = true }
chrono = "0.4.38"
log = "0.4.22"
serde_json = "1.0.132"
[build-dependencies]
tauri-plugin = { version = "2.0.3", features = ["build"] }

View File

@@ -0,0 +1,11 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type APIErrorResponsePayload = { error: string, message: string, };
export type ActivateLicenseRequestPayload = { licenseKey: string, appVersion: string, appPlatform: string, };
export type ActivateLicenseResponsePayload = { activationId: string, };
export type CheckActivationResponsePayload = { active: boolean, };
export type LicenseCheckStatus = { "type": "personal_use" } | { "type": "commercial_use" } | { "type": "trialing", end: string, } | { "type": "trial_ended", end: string, };

View File

@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CheckActivationRequestPayload = { activationId: string, };

View File

@@ -0,0 +1,5 @@
const COMMANDS: &[&str] = &["activate", "check"];
fn main() {
tauri_plugin::Builder::new(COMMANDS).build();
}

View File

@@ -0,0 +1,33 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { useListenToTauriEvent } from '@yaakapp/app/hooks/useListenToTauriEvent';
import { LicenseCheckStatus } from './bindings/license';
export * from './bindings/license';
export function useLicense() {
const queryClient = useQueryClient();
const activate = useMutation<void, string, { licenseKey: string }>({
mutationKey: ['license.activate'],
mutationFn: (payload) => invoke('plugin:yaak-license|activate', payload),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY });
},
});
// Check the license again after a license is activated
useListenToTauriEvent('license-activated', async () => {
await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY });
});
const CHECK_QUERY_KEY = ['license.check'];
const check = useQuery<void, string, LicenseCheckStatus>({
queryKey: CHECK_QUERY_KEY,
queryFn: () => invoke('plugin:yaak-license|check'),
});
return {
activate,
check,
} as const;
}

View File

@@ -1,5 +1,5 @@
{
"name": "@yaakapp-internal/template",
"name": "@yaakapp-internal/license",
"private": true,
"version": "1.0.0",
"main": "index.ts"

View File

@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-activate"
description = "Enables the activate command without any pre-configured scope."
commands.allow = ["activate"]
[[permission]]
identifier = "deny-activate"
description = "Denies the activate command without any pre-configured scope."
commands.deny = ["activate"]

View File

@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-check"
description = "Enables the check command without any pre-configured scope."
commands.allow = ["check"]
[[permission]]
identifier = "deny-check"
description = "Denies the check command without any pre-configured scope."
commands.deny = ["check"]

View File

@@ -0,0 +1,68 @@
## Default Permission
Default permissions for the plugin
- `allow-check`
- `allow-activate`
## Permission Table
<table>
<tr>
<th>Identifier</th>
<th>Description</th>
</tr>
<tr>
<td>
`yaak-license:allow-activate`
</td>
<td>
Enables the activate command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-license:deny-activate`
</td>
<td>
Denies the activate command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-license:allow-check`
</td>
<td>
Enables the check command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`yaak-license:deny-check`
</td>
<td>
Denies the check command without any pre-configured scope.
</td>
</tr>
</table>

View File

@@ -0,0 +1,3 @@
[default]
description = "Default permissions for the plugin"
permissions = ["allow-check", "allow-activate"]

View File

@@ -0,0 +1,325 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PermissionFile",
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
"type": "object",
"properties": {
"default": {
"description": "The default permission set for the plugin",
"anyOf": [
{
"$ref": "#/definitions/DefaultPermission"
},
{
"type": "null"
}
]
},
"set": {
"description": "A list of permissions sets defined",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionSet"
}
},
"permission": {
"description": "A list of inlined permissions",
"default": [],
"type": "array",
"items": {
"$ref": "#/definitions/Permission"
}
}
},
"definitions": {
"DefaultPermission": {
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
"type": "object",
"required": [
"permissions"
],
"properties": {
"version": {
"description": "The version of the permission.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 1.0
},
"description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
"type": [
"string",
"null"
]
},
"permissions": {
"description": "All permissions this set contains.",
"type": "array",
"items": {
"type": "string"
}
}
}
},
"PermissionSet": {
"description": "A set of direct permissions grouped together under a new name.",
"type": "object",
"required": [
"description",
"identifier",
"permissions"
],
"properties": {
"identifier": {
"description": "A unique identifier for the permission.",
"type": "string"
},
"description": {
"description": "Human-readable description of what the permission does.",
"type": "string"
},
"permissions": {
"description": "All permissions this set contains.",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionKind"
}
}
}
},
"Permission": {
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"version": {
"description": "The version of the permission.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 1.0
},
"identifier": {
"description": "A unique identifier for the permission.",
"type": "string"
},
"description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
"type": [
"string",
"null"
]
},
"commands": {
"description": "Allowed or denied commands when using this permission.",
"default": {
"allow": [],
"deny": []
},
"allOf": [
{
"$ref": "#/definitions/Commands"
}
]
},
"scope": {
"description": "Allowed or denied scoped when using this permission.",
"allOf": [
{
"$ref": "#/definitions/Scopes"
}
]
},
"platforms": {
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Target"
}
}
}
},
"Commands": {
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
"type": "object",
"properties": {
"allow": {
"description": "Allowed command.",
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"deny": {
"description": "Denied command, which takes priority.",
"default": [],
"type": "array",
"items": {
"type": "string"
}
}
}
},
"Scopes": {
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
"type": "object",
"properties": {
"allow": {
"description": "Data that defines what is allowed by the scope.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Value"
}
},
"deny": {
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/Value"
}
}
}
},
"Value": {
"description": "All supported ACL values.",
"anyOf": [
{
"description": "Represents a null JSON value.",
"type": "null"
},
{
"description": "Represents a [`bool`].",
"type": "boolean"
},
{
"description": "Represents a valid ACL [`Number`].",
"allOf": [
{
"$ref": "#/definitions/Number"
}
]
},
{
"description": "Represents a [`String`].",
"type": "string"
},
{
"description": "Represents a list of other [`Value`]s.",
"type": "array",
"items": {
"$ref": "#/definitions/Value"
}
},
{
"description": "Represents a map of [`String`] keys to [`Value`]s.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Value"
}
}
]
},
"Number": {
"description": "A valid ACL number.",
"anyOf": [
{
"description": "Represents an [`i64`].",
"type": "integer",
"format": "int64"
},
{
"description": "Represents a [`f64`].",
"type": "number",
"format": "double"
}
]
},
"Target": {
"description": "Platform target.",
"oneOf": [
{
"description": "MacOS.",
"type": "string",
"enum": [
"macOS"
]
},
{
"description": "Windows.",
"type": "string",
"enum": [
"windows"
]
},
{
"description": "Linux.",
"type": "string",
"enum": [
"linux"
]
},
{
"description": "Android.",
"type": "string",
"enum": [
"android"
]
},
{
"description": "iOS.",
"type": "string",
"enum": [
"iOS"
]
}
]
},
"PermissionKind": {
"type": "string",
"oneOf": [
{
"description": "Enables the activate command without any pre-configured scope.",
"type": "string",
"const": "allow-activate"
},
{
"description": "Denies the activate command without any pre-configured scope.",
"type": "string",
"const": "deny-activate"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "allow-check"
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "deny-check"
},
{
"description": "Default permissions for the plugin",
"type": "string",
"const": "default"
}
]
}
}
}

View File

@@ -0,0 +1,37 @@
use crate::errors::Result;
use crate::{activate_license, check_license, ActivateLicenseRequestPayload, LicenseCheckStatus};
use log::{debug, info};
use std::string::ToString;
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
}
#[command]
pub async fn activate<R: Runtime>(license_key: &str, window: WebviewWindow<R>) -> Result<()> {
info!("Activating license {}", license_key);
activate_license(
&window,
ActivateLicenseRequestPayload {
license_key: license_key.to_string(),
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"
} else if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "linux") {
"linux"
} else {
"unknown"
}
}

View File

@@ -0,0 +1,28 @@
use serde::{Serialize, Serializer};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Reqwest error: {0}")]
APIError(#[from] reqwest::Error),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("{message}")]
ClientError { message: String, error: String },
#[error("Internal server error")]
ServerError,
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -0,0 +1,16 @@
use tauri::{
generate_handler,
plugin::{Builder, TauriPlugin},
Runtime,
};
mod commands;
mod errors;
mod license;
use crate::commands::{activate, check};
pub use license::*;
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("yaak-license").invoke_handler(generate_handler![check, activate]).build()
}

View File

@@ -0,0 +1,155 @@
use crate::errors::Error::{ClientError, ServerError};
use crate::errors::Result;
use chrono::{NaiveDateTime, Utc};
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 ts_rs::TS;
const KV_NAMESPACE: &str = "license";
const KV_ACTIVATION_ID_KEY: &str = "activation_id";
const TRIAL_SECONDS: u64 = 3600 * 24 * 14;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "models.ts")]
pub struct CheckActivationRequestPayload {
pub activation_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "license.ts")]
pub struct CheckActivationResponsePayload {
pub active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "license.ts")]
pub struct ActivateLicenseRequestPayload {
pub license_key: String,
pub app_version: String,
pub app_platform: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "license.ts")]
pub struct ActivateLicenseResponsePayload {
pub activation_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "license.ts")]
pub struct APIErrorResponsePayload {
pub error: String,
pub message: String,
}
pub async fn activate_license<R: Runtime>(
window: &WebviewWindow<R>,
p: ActivateLicenseRequestPayload,
) -> Result<()> {
let client = reqwest::Client::new();
let response = client.post(build_url("/activate")).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);
}
let body: ActivateLicenseResponsePayload = response.json().await?;
yaak_models::queries::set_key_value_string(
window,
KV_ACTIVATION_ID_KEY,
KV_NAMESPACE,
body.activation_id.as_str(),
)
.await;
if let Err(e) = window.emit("license-activated", true) {
warn!("Failed to emit check-license event: {}", e);
}
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case", tag = "type")]
#[ts(export, export_to = "license.ts")]
pub enum LicenseCheckStatus {
PersonalUse,
CommercialUse,
InvalidLicense,
Trialing { end: NaiveDateTime },
TrialEnded { 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;
let settings = yaak_models::queries::get_or_create_settings(app_handle).await;
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
debug!("Trial ending at {trial_end:?}");
let has_activation_id = !activation_id.is_empty();
let trial_period_active = Utc::now().naive_utc() < trial_end;
match (has_activation_id, trial_period_active) {
(false, true) => Ok(LicenseCheckStatus::Trialing { end: trial_end }),
(false, false) => Ok(LicenseCheckStatus::TrialEnded { end: trial_end }),
(true, _) => {
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?;
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);
}
let body: CheckActivationResponsePayload = response.json().await?;
if !body.active {
return Ok(LicenseCheckStatus::InvalidLicense);
}
Ok(LicenseCheckStatus::CommercialUse)
}
}
}
fn build_url(path: &str) -> String {
if is_dev() {
format!("http://localhost:9444/licenses{path}")
} else {
format!("https://license.yaak.app/licenses{path}")
}
}

View File

@@ -2,6 +2,7 @@
name = "yaak_models"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
chrono = { version = "0.4.38", features = ["serde"] }
@@ -11,7 +12,7 @@ sea-query-rusqlite = { version = "0.6.0", features = ["with-chrono"] }
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.122"
thiserror = "1.0.63"
ts-rs = { version = "10.0.0", features = ["chrono-impl", "serde-json-impl"] }
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
tauri = { workspace = true }
sqlx = { version = "0.8.0", features = ["sqlite", "runtime-tokio-rustls"] }
log = "0.4.22"

View File

@@ -2,15 +2,13 @@
name = "yaak_plugin_runtime"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
anyhow = "1.0.86"
command-group = "5.0.1"
dunce = "1.0.4"
log = "0.4.21"
prost = "0.13.1"
rand = "0.8.5"
reqwest = { version = "0.12.5", features = ["stream"] }
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.113"
tauri = { workspace = true }

View File

@@ -57,9 +57,7 @@ impl PluginRuntime for PluginRuntimeServerImpl {
let plugin_to_app_events_tx = self.plugin_to_app_events_tx.clone();
let client_disconnect_tx = self.client_disconnect_tx.clone();
self.client_connect_tx
.send(true)
.expect("Failed to send client ready event");
self.client_connect_tx.send(true).expect("Failed to send client ready event");
tokio::spawn(async move {
while let Some(result) = in_stream.next().await {
@@ -96,8 +94,6 @@ impl PluginRuntime for PluginRuntimeServerImpl {
// Write the same data that was received
let out_stream = ReceiverStream::new(to_plugin_rx);
Ok(Response::new(
Box::pin(out_stream) as Self::EventStreamStream
))
Ok(Response::new(Box::pin(out_stream) as Self::EventStreamStream))
}
}

View File

@@ -2,8 +2,8 @@
name = "yaak_sse"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.122"
ts-rs = { version = "10.0.0", features = ["serde-json-impl"] }

View File

@@ -2,10 +2,10 @@
name = "yaak_templates"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
log = "0.4.22"
serde = { version = "1.0.208", features = ["derive"] }
serde_json = "1.0.125"
ts-rs = { version = "10.0.0" }
tokio = { version = "1.39.3", features = ["macros", "rt"] }

View File

@@ -1,9 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type FnArg = { name: string, value: Val, };
export type Token = { "type": "raw", text: string, } | { "type": "tag", val: Val, } | { "type": "eof" };
export type Tokens = { tokens: Array<Token>, };
export type Val = { "type": "str", text: string, } | { "type": "var", name: string, } | { "type": "bool", value: boolean, } | { "type": "fn", name: string, args: Array<FnArg>, } | { "type": "null" };

View File

@@ -1,4 +0,0 @@
export * from './bindings/parser';
export type COOL = {
bar: string;
};