diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50d8e32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +/.vs +.DS_Store +.Thumbs.db +*.sublime* +.idea/ +debug.log +package-lock.json +.vscode/settings.json +yarn.lock + +/.tauri +/target +Cargo.lock +node_modules/ + +dist-js +dist diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6f02d8a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tauri-plugin-bluclas" +version = "0.1.0" +authors = [ "You" ] +description = "" +edition = "2021" +rust-version = "1.77.2" +exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"] +links = "tauri-plugin-bluclas" + +[dependencies] +tauri = { version = "2.10.3" } +serde = "1.0" +thiserror = "2" + +[build-dependencies] +tauri-plugin = { version = "2.5.4", features = ["build"] } diff --git a/README.md b/README.md index 0d4955f..14dbf06 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# bluclas - +# Tauri Plugin bluclas diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e0ff2cc --- /dev/null +++ b/build.rs @@ -0,0 +1,8 @@ +const COMMANDS: &[&str] = &["ping"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .android_path("android") + .ios_path("ios") + .build(); +} diff --git a/examples/tauri-app/.gitignore b/examples/tauri-app/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/tauri-app/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/tauri-app/.vscode/extensions.json b/examples/tauri-app/.vscode/extensions.json new file mode 100644 index 0000000..61343e9 --- /dev/null +++ b/examples/tauri-app/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "svelte.svelte-vscode", + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer" + ] +} diff --git a/examples/tauri-app/README.md b/examples/tauri-app/README.md new file mode 100644 index 0000000..72726a1 --- /dev/null +++ b/examples/tauri-app/README.md @@ -0,0 +1,8 @@ +# Svelte + Vite + +This template should help get you started developing with Tauri and Svelte in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). + diff --git a/examples/tauri-app/index.html b/examples/tauri-app/index.html new file mode 100644 index 0000000..fad1c5d --- /dev/null +++ b/examples/tauri-app/index.html @@ -0,0 +1,14 @@ + + + + + + + Tauri + Svelte + + + +
+ + + diff --git a/examples/tauri-app/jsconfig.json b/examples/tauri-app/jsconfig.json new file mode 100644 index 0000000..0882056 --- /dev/null +++ b/examples/tauri-app/jsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/examples/tauri-app/package.json b/examples/tauri-app/package.json new file mode 100644 index 0000000..7c1a5b4 --- /dev/null +++ b/examples/tauri-app/package.json @@ -0,0 +1,22 @@ +{ + "name": "tauri-app", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "tauri-plugin-bluclas-api": "file:../../" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "svelte": "^5.0.0", + "vite": "^7.0.0", + "@tauri-apps/cli": "^2.0.0" + } +} diff --git a/examples/tauri-app/public/svelte.svg b/examples/tauri-app/public/svelte.svg new file mode 100644 index 0000000..c5e0848 --- /dev/null +++ b/examples/tauri-app/public/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/tauri-app/public/tauri.svg b/examples/tauri-app/public/tauri.svg new file mode 100644 index 0000000..31b62c9 --- /dev/null +++ b/examples/tauri-app/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/tauri-app/public/vite.svg b/examples/tauri-app/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/examples/tauri-app/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/tauri-app/src-tauri/.gitignore b/examples/tauri-app/src-tauri/.gitignore new file mode 100644 index 0000000..f4dfb82 --- /dev/null +++ b/examples/tauri-app/src-tauri/.gitignore @@ -0,0 +1,4 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + diff --git a/examples/tauri-app/src-tauri/Cargo.toml b/examples/tauri-app/src-tauri/Cargo.toml new file mode 100644 index 0000000..f358d25 --- /dev/null +++ b/examples/tauri-app/src-tauri/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tauri-app" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.77.2" + +[lib] +name = "tauri_app_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "2.5.6", default-features = false } + +[dependencies] +tauri = { version = "2.10.3" } +tauri-plugin-bluclas = { path = "../../../" } + diff --git a/examples/tauri-app/src-tauri/build.rs b/examples/tauri-app/src-tauri/build.rs new file mode 100644 index 0000000..795b9b7 --- /dev/null +++ b/examples/tauri-app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/examples/tauri-app/src-tauri/capabilities/default.json b/examples/tauri-app/src-tauri/capabilities/default.json new file mode 100644 index 0000000..85caf85 --- /dev/null +++ b/examples/tauri-app/src-tauri/capabilities/default.json @@ -0,0 +1,12 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "enables the default permissions", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "bluclas:default" + ] +} diff --git a/examples/tauri-app/src-tauri/icons/128x128.png b/examples/tauri-app/src-tauri/icons/128x128.png new file mode 100644 index 0000000..77e7d23 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/128x128.png differ diff --git a/examples/tauri-app/src-tauri/icons/128x128@2x.png b/examples/tauri-app/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..0f7976f Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/128x128@2x.png differ diff --git a/examples/tauri-app/src-tauri/icons/32x32.png b/examples/tauri-app/src-tauri/icons/32x32.png new file mode 100644 index 0000000..98fda06 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/32x32.png differ diff --git a/examples/tauri-app/src-tauri/icons/icon.icns b/examples/tauri-app/src-tauri/icons/icon.icns new file mode 100644 index 0000000..29d6685 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/icon.icns differ diff --git a/examples/tauri-app/src-tauri/icons/icon.ico b/examples/tauri-app/src-tauri/icons/icon.ico new file mode 100644 index 0000000..06c23c8 Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/icon.ico differ diff --git a/examples/tauri-app/src-tauri/icons/icon.png b/examples/tauri-app/src-tauri/icons/icon.png new file mode 100644 index 0000000..d1756ce Binary files /dev/null and b/examples/tauri-app/src-tauri/icons/icon.png differ diff --git a/examples/tauri-app/src-tauri/src/lib.rs b/examples/tauri-app/src-tauri/src/lib.rs new file mode 100644 index 0000000..405d260 --- /dev/null +++ b/examples/tauri-app/src-tauri/src/lib.rs @@ -0,0 +1,14 @@ +// Learn more about Tauri commands at https://v2.tauri.app/develop/calling-rust/#commands +#[tauri::command] +fn greet(name: &str) -> String { + format!("Hello, {}! You've been greeted from Rust!", name) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .invoke_handler(tauri::generate_handler![greet]) + .plugin(tauri_plugin_bluclas::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/examples/tauri-app/src-tauri/src/main.rs b/examples/tauri-app/src-tauri/src/main.rs new file mode 100644 index 0000000..455963e --- /dev/null +++ b/examples/tauri-app/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + tauri_app_lib::run(); +} diff --git a/examples/tauri-app/src-tauri/tauri.conf.json b/examples/tauri-app/src-tauri/tauri.conf.json new file mode 100644 index 0000000..72ebf40 --- /dev/null +++ b/examples/tauri-app/src-tauri/tauri.conf.json @@ -0,0 +1,37 @@ +{ + "productName": "tauri-app", + "version": "0.1.0", + "identifier": "com.tauri.dev", + "build": { + "beforeDevCommand": "pnpm dev", + "beforeBuildCommand": "pnpm build", + "devUrl": "http://localhost:1420", + "frontendDist": "../dist" + }, + "app": { + "withGlobalTauri": false, + "security": { + "csp": null + }, + "windows": [ + { + "fullscreen": false, + "height": 600, + "resizable": true, + "title": "tauri-app", + "width": 800 + } + ] + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/examples/tauri-app/src/App.svelte b/examples/tauri-app/src/App.svelte new file mode 100644 index 0000000..6e2a1d2 --- /dev/null +++ b/examples/tauri-app/src/App.svelte @@ -0,0 +1,54 @@ + + +
+

Welcome to Tauri!

+ +
+ + + + + + + + + +
+ +

+ Click on the Tauri, Vite, and Svelte logos to learn more. +

+ +
+ +
+ +
+ +
{@html response}
+
+ +
+ + diff --git a/examples/tauri-app/src/lib/Greet.svelte b/examples/tauri-app/src/lib/Greet.svelte new file mode 100644 index 0000000..1da5e30 --- /dev/null +++ b/examples/tauri-app/src/lib/Greet.svelte @@ -0,0 +1,22 @@ + + +
+
+ + +
+

{greetMsg}

+
+ diff --git a/examples/tauri-app/src/main.js b/examples/tauri-app/src/main.js new file mode 100644 index 0000000..d3398c8 --- /dev/null +++ b/examples/tauri-app/src/main.js @@ -0,0 +1,9 @@ +import "./style.css"; +import App from "./App.svelte"; +import { mount } from 'svelte'; + +const app = mount(App, { + target: document.getElementById("app"), +}); + +export default app; diff --git a/examples/tauri-app/src/style.css b/examples/tauri-app/src/style.css new file mode 100644 index 0000000..c0f9e3b --- /dev/null +++ b/examples/tauri-app/src/style.css @@ -0,0 +1,102 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #0f0f0f; + background-color: #f6f6f6; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +.container { + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: 0.75s; +} + +.logo.tauri:hover { + filter: drop-shadow(0 0 2em #24c8db); +} + +.row { + display: flex; + justify-content: center; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +h1 { + text-align: center; +} + +input, +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #0f0f0f; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); +} + +button { + cursor: pointer; +} + +button:hover { + border-color: #396cd8; +} + +input, +button { + outline: none; +} + +#greet-input { + margin-right: 5px; +} + +@media (prefers-color-scheme: dark) { + :root { + color: #f6f6f6; + background-color: #2f2f2f; + } + + a:hover { + color: #24c8db; + } + + input, + button { + color: #ffffff; + background-color: #0f0f0f98; + } +} diff --git a/examples/tauri-app/src/vite-env.d.ts b/examples/tauri-app/src/vite-env.d.ts new file mode 100644 index 0000000..4078e74 --- /dev/null +++ b/examples/tauri-app/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/tauri-app/vite.config.js b/examples/tauri-app/vite.config.js new file mode 100644 index 0000000..82e1f23 --- /dev/null +++ b/examples/tauri-app/vite.config.js @@ -0,0 +1,24 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; + +const host = process.env.TAURI_DEV_HOST; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], + + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // prevent Vite from obscuring rust errors + clearScreen: false, + // tauri expects a fixed port, fail if that port is not available + server: { + host: host || false, + port: 1420, + strictPort: true, + hmr: host ? { + protocol: 'ws', + host, + port: 1421 + } : undefined, + }, +}) diff --git a/guest-js/index.ts b/guest-js/index.ts new file mode 100644 index 0000000..fca440d --- /dev/null +++ b/guest-js/index.ts @@ -0,0 +1,9 @@ +import { invoke } from '@tauri-apps/api/core' + +export async function ping(value: string): Promise { + return await invoke<{value?: string}>('plugin:bluclas|ping', { + payload: { + value, + }, + }).then((r) => (r.value ? r.value : null)); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fc79e8d --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "tauri-plugin-bluclas-api", + "version": "0.1.0", + "author": "You", + "description": "", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "files": [ + "dist-js", + "README.md" + ], + "scripts": { + "build": "rollup -c", + "prepublishOnly": "pnpm build", + "pretest": "pnpm build" + }, + "dependencies": { + "@tauri-apps/api": "^2.0.0" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^12.0.0", + "rollup": "^4.9.6", + "typescript": "^5.3.3", + "tslib": "^2.6.2" + } +} diff --git a/permissions/default.toml b/permissions/default.toml new file mode 100644 index 0000000..cc5a76f --- /dev/null +++ b/permissions/default.toml @@ -0,0 +1,3 @@ +[default] +description = "Default permissions for the plugin" +permissions = ["allow-ping"] diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..8b4768f --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,31 @@ +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { cwd } from 'node:process' +import typescript from '@rollup/plugin-typescript' + +const pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8')) + +export default { + input: 'guest-js/index.ts', + output: [ + { + file: pkg.exports.import, + format: 'esm' + }, + { + file: pkg.exports.require, + format: 'cjs' + } + ], + plugins: [ + typescript({ + declaration: true, + declarationDir: dirname(pkg.exports.import) + }) + ], + external: [ + /^@tauri-apps\/api/, + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ] +} diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..a0bee4a --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,13 @@ +use tauri::{AppHandle, command, Runtime}; + +use crate::models::*; +use crate::Result; +use crate::BluclasExt; + +#[command] +pub(crate) async fn ping( + app: AppHandle, + payload: PingRequest, +) -> Result { + app.bluclas().ping(payload) +} diff --git a/src/desktop.rs b/src/desktop.rs new file mode 100644 index 0000000..2416875 --- /dev/null +++ b/src/desktop.rs @@ -0,0 +1,22 @@ +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::models::*; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Bluclas(app.clone())) +} + +/// Access to the bluclas APIs. +pub struct Bluclas(AppHandle); + +impl Bluclas { + pub fn ping(&self, payload: PingRequest) -> crate::Result { + Ok(PingResponse { + value: payload.value, + }) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..895220d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,21 @@ +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fc82184 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,48 @@ +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +use desktop::Bluclas; +#[cfg(mobile)] +use mobile::Bluclas; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the bluclas APIs. +pub trait BluclasExt { + fn bluclas(&self) -> &Bluclas; +} + +impl> crate::BluclasExt for T { + fn bluclas(&self) -> &Bluclas { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("bluclas") + .invoke_handler(tauri::generate_handler![commands::ping]) + .setup(|app, api| { + #[cfg(mobile)] + let bluclas = mobile::init(app, api)?; + #[cfg(desktop)] + let bluclas = desktop::init(app, api)?; + app.manage(bluclas); + Ok(()) + }) + .build() +} diff --git a/src/mobile.rs b/src/mobile.rs new file mode 100644 index 0000000..a37ab08 --- /dev/null +++ b/src/mobile.rs @@ -0,0 +1,34 @@ +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_bluclas); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin("", "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_bluclas)?; + Ok(Bluclas(handle)) +} + +/// Access to the bluclas APIs. +pub struct Bluclas(PluginHandle); + +impl Bluclas { + pub fn ping(&self, payload: PingRequest) -> crate::Result { + self + .0 + .run_mobile_plugin("ping", payload) + .map_err(Into::into) + } +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..1b53e9e --- /dev/null +++ b/src/models.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PingRequest { + pub value: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PingResponse { + pub value: Option, +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0591122 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "esnext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "noEmit": true + }, + "include": ["guest-js/*.ts"], + "exclude": ["dist-js", "node_modules"] +}