diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 46adaad..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[env] -MTP_TYPE_MAPS = { value = "mtp-type-maps/type-maps.yaml", relative = true } diff --git a/.fallowrc.json b/.fallowrc.json index ba50fb5..022e0cc 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -3,7 +3,6 @@ "entry": [ "src/index.{ts,tsx,js,jsx}", "src/main.{ts,tsx,js,jsx}", - "apps/pwa/src/serviceWorker.ts", "apps/tauri/render-version.ts", "packages/**/*.test.ts" ], diff --git a/.forgejo/workflows/dependency-builds.yml b/.forgejo/workflows/dependency-builds.yml deleted file mode 100644 index 2ae48fe..0000000 --- a/.forgejo/workflows/dependency-builds.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Dependency builds - -on: - pull_request: - -env: - FORGEJO_TOKEN: "" - GITHUB_TOKEN: "" - -jobs: - web: - if: ${{ github.actor == 'rasensprenger' }} - name: Build web - runs-on: nixos - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#electron --command pnpm install --frozen-lockfile - - run: nix develop .#electron --command pnpm run build:packages - - run: nix develop .#electron --command pnpm run build:web - - desktop: - if: ${{ github.actor == 'rasensprenger' }} - name: Build desktop - runs-on: nixos - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#electron --command pnpm install --frozen-lockfile - - run: nix develop .#electron --command pnpm run build:packages - - run: nix develop .#electron --command pnpm run build:desktop - - native-mtp: - if: ${{ github.actor == 'rasensprenger' }} - name: Test native MTP - runs-on: nixos - steps: - - run: nix profile add nixpkgs#nodejs_24 - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#electron --command bash -lc 'cd apps/tauri/src-tauri && cargo test' - - mobile: - if: ${{ github.actor == 'rasensprenger' }} - name: Build mobile - runs-on: nixos - steps: - - uses: https://data.forgejo.org/actions/checkout@v4 - with: - persist-credentials: false - - run: git submodule update --init --recursive - - run: nix develop .#tauri --command pnpm install --frozen-lockfile - - run: nix develop .#tauri --command pnpm run build:packages - - run: nix develop .#tauri --command pnpm --dir apps/tauri run build:mobile:ci diff --git a/.forgejo/workflows/deploy-dev.yml b/.forgejo/workflows/deploy-dev.yml index 7f59ec1..0d5be50 100644 --- a/.forgejo/workflows/deploy-dev.yml +++ b/.forgejo/workflows/deploy-dev.yml @@ -1,15 +1,20 @@ on: - workflow_dispatch: push: branches: - dev paths-ignore: - flake.nix +env: + NIX_CONFIG: experimental-features = nix-command flakes + jobs: build-web: runs-on: nixos steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -34,6 +39,9 @@ jobs: build-mobile: runs-on: nixos steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -54,6 +62,8 @@ jobs: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} run: | + nix profile add nixpkgs#gnused + set -euo pipefail if [ -z "$KEYSTORE_BASE64" ]; then @@ -118,6 +128,9 @@ jobs: matrix: target: [linux] steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -168,6 +181,9 @@ jobs: runs-on: nixos needs: [build-web, build-mobile, build-desktop] steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 with: diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml index 621a0cd..d85ce27 100644 --- a/.forgejo/workflows/deploy-prod.yml +++ b/.forgejo/workflows/deploy-prod.yml @@ -6,10 +6,16 @@ on: paths-ignore: - flake.nix +env: + NIX_CONFIG: experimental-features = nix-command flakes + jobs: build-web: runs-on: nixos steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -34,6 +40,9 @@ jobs: build-mobile: runs-on: nixos steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -54,6 +63,8 @@ jobs: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} run: | + nix profile add nixpkgs#gnused + set -euo pipefail if [ -z "$KEYSTORE_BASE64" ]; then @@ -118,6 +129,9 @@ jobs: matrix: target: [linux] steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -166,6 +180,9 @@ jobs: runs-on: nixos needs: [build-web, build-mobile, build-desktop] steps: + - name: Install node + run: nix profile add nixpkgs#nodejs_24 + - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 @@ -311,3 +328,58 @@ jobs: "$API/repos/$REPO/releases/$release_id" done < "$DELETE_RELEASES" EOF + + - name: Update root flake release hash + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + nix develop .#electron --command bash <<'EOF' + set -eu + + DEB="$(find releases -maxdepth 1 -type f -name 'Tensamin-*-linux-amd64.deb' -print -quit)" + test -n "$DEB" + + HASH="$(node -e 'const fs = require("fs"); const crypto = require("crypto"); const file = process.argv[1]; console.log("sha256-" + crypto.createHash("sha256").update(fs.readFileSync(file)).digest("base64"));' "$DEB")" + export HASH + + node -e ' + const fs = require("fs"); + const version = process.env.TAG; + const hash = process.env.HASH; + let content = fs.readFileSync("flake.nix", "utf8"); + content = content.replace(/version = "[^"]+";/, `version = "${version}";`); + content = content.replace(/x86_64DebHash = "sha256-[^"]+";/, `x86_64DebHash = "${hash}";`); + fs.writeFileSync("flake.nix", content); + ' + + if git diff --quiet -- flake.nix; then + echo "flake.nix already has the current release hash on main." + else + git add flake.nix + git -c user.name="forgejo-actions" -c user.email="forgejo-actions@localhost" commit -m "(qol): update release flake hash" + git push + fi + + git fetch origin dev + git worktree add ../dev-flake-update origin/dev + cd ../dev-flake-update + + node -e ' + const fs = require("fs"); + const version = process.env.TAG; + const hash = process.env.HASH; + let content = fs.readFileSync("flake.nix", "utf8"); + content = content.replace(/version = "[^"]+";/, `version = "${version}";`); + content = content.replace(/x86_64DebHash = "sha256-[^"]+";/, `x86_64DebHash = "${hash}";`); + fs.writeFileSync("flake.nix", content); + ' + + if git diff --quiet -- flake.nix; then + echo "flake.nix already has the current release hash on dev." + exit 0 + fi + + git add flake.nix + git -c user.name="forgejo-actions" -c user.email="forgejo-actions@localhost" commit -m "(qol): update release flake hash" + git push origin HEAD:dev + EOF diff --git a/.gitignore b/.gitignore index 261f60b..c0e6a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,3 @@ node_modules releases .fallow .direnv -keystore.jks -keystore.properties diff --git a/TODO b/TODO index e4b9435..3ec0948 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ - Add a bunch of tests +- Add packages/hotkeys/ - Full accessability - Add settings saving & update onboarding to use it - Add onboarding page to load one profile during onboarding diff --git a/apps/electron/build/icons/128x128.png b/apps/electron/build/icons/128x128.png new file mode 100644 index 0000000..357e1c2 Binary files /dev/null and b/apps/electron/build/icons/128x128.png differ diff --git a/apps/electron/build/icons/128x128@2x.png b/apps/electron/build/icons/128x128@2x.png new file mode 100644 index 0000000..be55e24 Binary files /dev/null and b/apps/electron/build/icons/128x128@2x.png differ diff --git a/apps/electron/build/icons/32x32.png b/apps/electron/build/icons/32x32.png new file mode 100644 index 0000000..bc4fba4 Binary files /dev/null and b/apps/electron/build/icons/32x32.png differ diff --git a/apps/electron/build/icons/64x64.png b/apps/electron/build/icons/64x64.png new file mode 100644 index 0000000..567f4ad Binary files /dev/null and b/apps/electron/build/icons/64x64.png differ diff --git a/apps/electron/build/icons/icon.icns b/apps/electron/build/icons/icon.icns new file mode 100644 index 0000000..7f3b591 Binary files /dev/null and b/apps/electron/build/icons/icon.icns differ diff --git a/apps/electron/build/icons/icon.ico b/apps/electron/build/icons/icon.ico new file mode 100644 index 0000000..c5c68f8 Binary files /dev/null and b/apps/electron/build/icons/icon.ico differ diff --git a/apps/electron/build/icons/icon.png b/apps/electron/build/icons/icon.png new file mode 100644 index 0000000..dad3edb Binary files /dev/null and b/apps/electron/build/icons/icon.png differ diff --git a/apps/electron/package.json b/apps/electron/package.json index 560ed34..f410033 100644 --- a/apps/electron/package.json +++ b/apps/electron/package.json @@ -28,11 +28,12 @@ "validate:raw": "pnpm run build && pnpm run package:linux:raw", "validate": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#electron --command pnpm run validate:raw; else pnpm run validate:raw; fi" }, + "dependencies": {}, "devDependencies": { - "@types/node": "^26.1.2", - "electron": "^43.3.0", - "electron-builder": "^26.15.3", - "esbuild": "^0.28.1", + "@types/node": "^25.9.1", + "electron": "^39.2.7", + "electron-builder": "^26.0.12", + "esbuild": "^0.25.11", "typescript": "~6.0.3" }, "build": { @@ -94,11 +95,7 @@ "target": [ "dmg" ], - "icon": "build/icons/icon.icns", - "extendInfo": { - "NSCameraUsageDescription": "Tensamin uses your camera when you choose to share it in a call.", - "NSMicrophoneUsageDescription": "Tensamin uses your microphone for calls." - } + "icon": "build/icons/icon.icns" }, "publish": null } diff --git a/apps/electron/src/main/main.ts b/apps/electron/src/main/main.ts index 083d4d4..a5843e0 100644 --- a/apps/electron/src/main/main.ts +++ b/apps/electron/src/main/main.ts @@ -5,7 +5,6 @@ import { app, BrowserWindow, desktopCapturer, - globalShortcut, ipcMain, session, shell, @@ -14,7 +13,6 @@ import { checkForUpdates } from "./updates.js"; import { ipcChannels, type DesktopCallStatus, - type DesktopGlobalHotkeyBinding, type DesktopScreenShareAudioOutput, type DesktopScreenShareCapabilities, } from "../shared/ipc.js"; @@ -31,8 +29,6 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const verbose = process.argv.includes("--verbose"); let mainWindow: BrowserWindow | null = null; let selectedScreenShareSourceId: string | null = null; -let globalHotkeyBindings: DesktopGlobalHotkeyBinding[] = []; -let globalHotkeysSuspended = false; app.setName("tensamin"); app.setPath("userData", join(app.getPath("appData"), "tensamin", "electron")); @@ -50,13 +46,6 @@ if ( app.commandLine.appendSwitch("password-store", "gnome-libsecret"); } -if ( - process.platform === "linux" && - process.env.XDG_SESSION_TYPE === "wayland" -) { - app.commandLine.appendSwitch("enable-features", "GlobalShortcutsPortal"); -} - if ( process.platform === "linux" && process.env.XDG_SESSION_TYPE === "wayland" && @@ -191,29 +180,6 @@ function registerDisplayMediaHandler() { ); } -function registerMediaPermissionHandler() { - const isTrustedRenderer = (url: string) => { - try { - const parsed = new URL(url); - return parsed.protocol === "file:"; - } catch { - return false; - } - }; - - session.defaultSession.setPermissionCheckHandler( - (_webContents, permission, requestingOrigin) => - permission === "media" && isTrustedRenderer(requestingOrigin), - ); - session.defaultSession.setPermissionRequestHandler( - (_webContents, permission, callback, details) => { - callback( - permission === "media" && isTrustedRenderer(details.requestingUrl), - ); - }, - ); -} - function registerIpc() { verboseLog("registering ipc handlers"); @@ -253,24 +219,6 @@ function registerIpc() { deleteSecureStorage(key), ); ipcMain.handle(ipcChannels.clearSecureStorage, clearSecureStorage); - ipcMain.handle( - ipcChannels.setGlobalHotkeyBindings, - (event, bindings: unknown) => { - assertTrustedRenderer(event); - return setGlobalHotkeyBindings(bindings); - }, - ); - ipcMain.handle( - ipcChannels.setGlobalHotkeysSuspended, - (event, suspended: unknown) => { - assertTrustedRenderer(event); - if (typeof suspended !== "boolean") { - throw new Error("Invalid hotkey suspension state."); - } - globalHotkeysSuspended = suspended; - return applyGlobalHotkeyBindings(); - }, - ); ipcMain.handle(ipcChannels.setCallStatus, (_event, status: unknown) => { if ( typeof status !== "object" || @@ -311,90 +259,6 @@ function registerIpc() { }); } -function assertTrustedRenderer(event: Electron.IpcMainInvokeEvent) { - const target = mainWindow; - if ( - !target || - target.isDestroyed() || - event.sender !== target.webContents || - event.senderFrame !== target.webContents.mainFrame - ) { - throw new Error("Untrusted hotkey IPC sender."); - } - - try { - if (fileURLToPath(event.senderFrame.url) === getRendererIndex()) return; - } catch { - // Fall through to the rejection below. - } - throw new Error("Untrusted hotkey IPC sender."); -} - -function validGlobalHotkeyBindings( - value: unknown, -): value is DesktopGlobalHotkeyBinding[] { - return ( - Array.isArray(value) && - value.length <= 64 && - value.every( - (binding) => - binding && - typeof binding === "object" && - typeof (binding as DesktopGlobalHotkeyBinding).id === "string" && - /^[a-z0-9.-]+$/i.test((binding as DesktopGlobalHotkeyBinding).id) && - (binding as DesktopGlobalHotkeyBinding).id.length > 0 && - (binding as DesktopGlobalHotkeyBinding).id.length <= 128 && - typeof (binding as DesktopGlobalHotkeyBinding).accelerator === - "string" && - (binding as DesktopGlobalHotkeyBinding).accelerator.length > 0 && - (binding as DesktopGlobalHotkeyBinding).accelerator.length <= 128, - ) - ); -} - -function applyGlobalHotkeyBindings() { - globalShortcut.unregisterAll(); - const statuses = Object.fromEntries( - globalHotkeyBindings.map(({ id }) => [id, false]), - ); - if (globalHotkeysSuspended) return statuses; - - const grouped = new Map(); - for (const { id, accelerator } of globalHotkeyBindings) { - const ids = grouped.get(accelerator) ?? []; - ids.push(id); - grouped.set(accelerator, ids); - } - - for (const [accelerator, ids] of grouped) { - let registered = false; - try { - registered = globalShortcut.register(accelerator, () => { - const target = mainWindow; - if (!target || target.isDestroyed()) return; - ids.forEach((id) => - target.webContents.send(ipcChannels.globalHotkeyTriggered, id), - ); - }); - } catch (error) { - console.error("Failed to register global hotkey", accelerator, error); - } - ids.forEach((id) => { - statuses[id] = registered; - }); - } - - return statuses; -} - -function setGlobalHotkeyBindings(bindings: unknown) { - if (!validGlobalHotkeyBindings(bindings)) { - throw new Error("Invalid global hotkey bindings."); - } - globalHotkeyBindings = bindings; - return applyGlobalHotkeyBindings(); -} - async function createWindow() { const rendererIndex = getRendererIndex(); verboseLog("creating main window", { @@ -480,10 +344,6 @@ app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) void createWindow(); }); -app.on("will-quit", () => { - globalShortcut.unregisterAll(); -}); - if (verbose) { process.on("uncaughtException", (error) => { console.error("[tensamin:electron] uncaught exception", error); @@ -500,7 +360,6 @@ async function start() { verboseLog("app ready"); registerIpc(); registerDisplayMediaHandler(); - registerMediaPermissionHandler(); initTray(() => mainWindow); await createWindow(); } diff --git a/apps/electron/src/preload/preload.ts b/apps/electron/src/preload/preload.ts index 9f1c932..c6b61e4 100644 --- a/apps/electron/src/preload/preload.ts +++ b/apps/electron/src/preload/preload.ts @@ -2,7 +2,6 @@ import { contextBridge, ipcRenderer } from "electron"; import { ipcChannels, type DesktopCallStatus, - type DesktopGlobalHotkeyBinding, type DesktopScreenShareSource, secureStorageLimits, } from "../shared/ipc.js"; @@ -56,23 +55,6 @@ const desktopApi = { return ipcRenderer.invoke(ipcChannels.setCallStatus, status); }, }, - hotkeys: { - setBindings: (bindings: DesktopGlobalHotkeyBinding[]) => - ipcRenderer.invoke(ipcChannels.setGlobalHotkeyBindings, bindings), - setSuspended: (suspended: boolean) => - typeof suspended === "boolean" - ? ipcRenderer.invoke(ipcChannels.setGlobalHotkeysSuspended, suspended) - : Promise.reject(new Error("Invalid hotkey suspension state.")), - onTriggered: (callback: (id: string) => void) => { - const listener = (_event: Electron.IpcRendererEvent, id: unknown) => { - if (typeof id === "string") callback(id); - }; - ipcRenderer.on(ipcChannels.globalHotkeyTriggered, listener); - return () => { - ipcRenderer.removeListener(ipcChannels.globalHotkeyTriggered, listener); - }; - }, - }, secureStorage: { getStatus: () => ipcRenderer.invoke(ipcChannels.getSecureStorageStatus), load: (key: string) => diff --git a/apps/electron/src/shared/ipc.ts b/apps/electron/src/shared/ipc.ts index c424265..0679349 100644 --- a/apps/electron/src/shared/ipc.ts +++ b/apps/electron/src/shared/ipc.ts @@ -31,11 +31,6 @@ export type DesktopSecureStorageStatus = { backend: string | null; }; -export type DesktopGlobalHotkeyBinding = { - id: string; - accelerator: string; -}; - export const secureStorageLimits = { maxKeyBytes: 256, maxValueBytes: 1024 * 1024, @@ -82,7 +77,4 @@ export const ipcChannels = { saveSecureStorage: "secureStorage:save", deleteSecureStorage: "secureStorage:delete", clearSecureStorage: "secureStorage:clear", - setGlobalHotkeyBindings: "hotkeys:setBindings", - setGlobalHotkeysSuspended: "hotkeys:setSuspended", - globalHotkeyTriggered: "hotkeys:triggered", } as const; diff --git a/apps/pwa/package.json b/apps/pwa/package.json deleted file mode 100644 index 39c4ec5..0000000 --- a/apps/pwa/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@tensamin/pwa", - "private": true, - "version": "0.0.0", - "type": "module", - "exports": { - "./vite": "./src/vite.ts", - "./runtime": "./src/runtime.tsx" - }, - "scripts": { - "format": "pnpm exec prettier --write .", - "lint": "eslint src", - "build": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.worker.json --noEmit" - }, - "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tensamin/crypto": "workspace:*", - "@tensamin/shared": "workspace:*", - "@tensamin/storage": "workspace:*", - "mtp": "*", - "react": "^19.2.8", - "sonner": "^2.0.7", - "vite-plugin-pwa": "^1.1.0", - "workbox-core": "^7.3.0", - "workbox-precaching": "^7.3.0", - "workbox-routing": "^7.3.0", - "workbox-strategies": "^7.3.0" - }, - "devDependencies": { - "@types/node": "^26.1.2", - "@types/react": "^19.2.18", - "typescript": "~6.0.3", - "vite": "^8.2.1" - } -} diff --git a/apps/pwa/src/runtime.tsx b/apps/pwa/src/runtime.tsx deleted file mode 100644 index a1947eb..0000000 --- a/apps/pwa/src/runtime.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { useEffect } from "react"; -import { toast } from "sonner"; - -import { setDatabaseEntry } from "@tensamin/shared/indexedDb"; -import { isTauri } from "@tauri-apps/api/core"; - -import "./style.css"; - -const launchedFiles: File[] = []; -const fileListeners = new Set<(file: File) => void>(); - -function emitLaunchedFile(file: File) { - if (fileListeners.size === 0) launchedFiles.push(file); - else for (const listener of fileListeners) listener(file); -} - -export function subscribeTuFileLaunch(listener: (file: File) => void) { - fileListeners.add(listener); - for (const file of launchedFiles.splice(0)) listener(file); - return () => { - fileListeners.delete(listener); - }; -} - -function isInstalledPwa() { - return ( - window.matchMedia("(display-mode: standalone)").matches || - window.matchMedia("(display-mode: window-controls-overlay)").matches || - (navigator as Navigator & { standalone?: boolean }).standalone === true - ); -} - -function applicationServerKey(value: string) { - const normalized = value.replace(/-/g, "+").replace(/_/g, "/"); - const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "="); - return Uint8Array.from(atob(padded), (character) => character.charCodeAt(0)); -} - -async function enablePush() { - if (!("Notification" in window)) - throw new Error("Notifications are not supported by this browser."); - const permission = await Notification.requestPermission(); - if (permission !== "granted") - throw new Error("Notification permission was not granted."); - - const publicKey = import.meta.env.VITE_WEB_PUSH_PUBLIC_KEY; - if ( - !publicKey || - !("serviceWorker" in navigator) || - !("PushManager" in window) - ) { - return; - } - const registration = await navigator.serviceWorker.ready; - const subscription = - (await registration.pushManager.getSubscription()) ?? - (await registration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: applicationServerKey(publicKey), - })); - await setDatabaseEntry("keys", "push-subscription", subscription.toJSON()); -} - -function InstalledPwaRuntime() { - useEffect(() => { - if ( - !("serviceWorker" in navigator) || - !["http:", "https:"].includes(window.location.protocol) - ) { - return; - } - let reloading = false; - const handleControllerChange = () => { - if (reloading) return; - reloading = true; - // A newly activated service worker must reload the document it controls. - // eslint-disable-next-line tensamin/no-window-location-reload - window.location.reload(); - }; - navigator.serviceWorker.addEventListener( - "controllerchange", - handleControllerChange, - ); - void navigator.serviceWorker - .register( - import.meta.env.DEV ? "/dev-sw.js?dev-sw" : "/serviceWorker.js", - { - type: "module", - }, - ) - .then((registration) => { - const watchWorker = (worker: ServiceWorker) => { - worker.addEventListener("statechange", () => { - if (worker.state !== "installed") return; - if (!navigator.serviceWorker.controller) { - toast.success("Tensamin is ready for offline startup"); - return; - } - toast("A Tensamin update is ready", { - duration: Infinity, - action: { - label: "Update", - onClick: () => worker.postMessage({ type: "SKIP_WAITING" }), - }, - }); - }); - }; - if (registration.installing) watchWorker(registration.installing); - registration.addEventListener("updatefound", () => { - if (registration.installing) watchWorker(registration.installing); - }); - }) - .catch((error: unknown) => { - console.error("Failed to register the Tensamin service worker", error); - }); - return () => { - navigator.serviceWorker.removeEventListener( - "controllerchange", - handleControllerChange, - ); - }; - }, []); - - useEffect(() => { - const launchQueue = ( - window as Window & { - launchQueue?: { - setConsumer: ( - consumer: (params: { - files?: Array<{ getFile: () => Promise }>; - }) => void, - ) => void; - }; - } - ).launchQueue; - launchQueue?.setConsumer((params) => { - for (const handle of params.files ?? []) { - void handle.getFile().then(emitLaunchedFile); - } - }); - }, []); - - useEffect(() => { - if ( - !("Notification" in window) || - Notification.permission !== "default" || - localStorage.getItem("pwa-push-hint") - ) { - return; - } - localStorage.setItem("pwa-push-hint", "shown"); - toast("Enable message notifications", { - duration: Infinity, - action: { - label: "Enable", - onClick: () => { - void enablePush() - .then(() => toast.success("Notifications enabled")) - .catch((error: unknown) => - toast.error( - error instanceof Error - ? error.message - : "Could not enable notifications", - ), - ); - }, - }, - }); - }, []); - - useEffect(() => { - if ( - "Notification" in window && - Notification.permission === "granted" && - import.meta.env.VITE_WEB_PUSH_PUBLIC_KEY - ) { - void enablePush().catch((error: unknown) => { - console.error("Failed to refresh the Web Push subscription", error); - }); - } - }, []); - - return null; -} - -export default function PwaRuntime() { - if (isTauri() || !isInstalledPwa()) return null; - return ; -} diff --git a/apps/pwa/src/serviceWorker.ts b/apps/pwa/src/serviceWorker.ts deleted file mode 100644 index 554ceeb..0000000 --- a/apps/pwa/src/serviceWorker.ts +++ /dev/null @@ -1,161 +0,0 @@ -/// - -import { base64ToBytes } from "mtp"; -import { clientsClaim } from "workbox-core"; -import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching"; -import { NavigationRoute, registerRoute } from "workbox-routing"; -import { createHandlerBoundToURL } from "workbox-precaching"; -import { CacheFirst } from "workbox-strategies"; - -import { decryptChatText, unwrapChatSecret } from "@tensamin/crypto/chatSecret"; -import { loadSecureBrowserValue } from "@tensamin/storage/browserSecure"; - -declare let self: ServiceWorkerGlobalScope; - -type PushPayload = { - version: 1; - senderId: number; - sender: string; - avatar?: string; - message: { content: string }; - secret: { - chatId: string; - secretId: string; - version: number; - encryptedSecret: string; - kemCiphertext: string; - wrappingScheme: string; - }; -}; - -function isPushPayload(value: unknown): value is PushPayload { - if (!value || typeof value !== "object") return false; - const payload = value as Partial; - const message = payload.message as - Partial | undefined; - const secret = payload.secret as Partial | undefined; - const stringValues = [ - payload.sender, - message?.content, - secret?.chatId, - secret?.secretId, - secret?.encryptedSecret, - secret?.kemCiphertext, - secret?.wrappingScheme, - ]; - return ( - payload.version === 1 && - typeof payload.senderId === "number" && - Number.isSafeInteger(payload.senderId) && - payload.senderId > 0 && - typeof secret?.version === "number" && - stringValues.every((item) => typeof item === "string") - ); -} - -async function decryptPush(payload: PushPayload) { - const keyring = await loadSecureBrowserValue("mtp_keyring"); - if (!keyring) throw new Error("MTP credentials are unavailable."); - const chatSecret = await unwrapChatSecret({ - encryptedSecret: base64ToBytes(payload.secret.encryptedSecret), - kemCiphertext: base64ToBytes(payload.secret.kemCiphertext), - keyring, - chatId: payload.secret.chatId, - secretId: payload.secret.secretId, - version: payload.secret.version, - wrappingScheme: payload.secret.wrappingScheme, - }); - try { - return await decryptChatText(chatSecret, payload.message.content); - } finally { - chatSecret.fill(0); - } -} - -clientsClaim(); -cleanupOutdatedCaches(); -const precacheManifest = self.__WB_MANIFEST; -precacheAndRoute(precacheManifest); - -if ( - precacheManifest.some((entry) => - (typeof entry === "string" ? entry : entry.url).endsWith("index.html"), - ) -) { - registerRoute( - new NavigationRoute(createHandlerBoundToURL("index.html"), { - denylist: [/^\/api\//], - }), - ); -} -registerRoute( - ({ request, url }) => - url.origin === self.location.origin && - ["font", "image", "style"].includes(request.destination), - new CacheFirst({ cacheName: "tensamin-static-v1" }), -); - -self.addEventListener("push", (event) => { - event.waitUntil( - (async () => { - let payload: PushPayload | undefined; - try { - const value = event.data?.json() as unknown; - if (isPushPayload(value)) payload = value; - } catch { - // The generic notification below is safe for malformed payloads. - } - - let body = "Open Tensamin to view the encrypted message."; - if (payload) { - try { - body = await decryptPush(payload); - } catch { - // Do not leak credential or decryption failures in the notification. - } - } - - await self.registration.showNotification(payload?.sender ?? "Tensamin", { - body, - icon: payload?.avatar || "./icons/icon-192.png", - badge: "./icons/notification-badge.png", - tag: payload ? `message-${payload.senderId}` : "message", - data: { url: payload ? `/chat?id=${payload.senderId}` : "/" }, - }); - - const navigatorWithBadge = self.navigator as WorkerNavigator & { - setAppBadge?: (contents?: number) => Promise; - }; - await navigatorWithBadge.setAppBadge?.().catch(() => undefined); - })(), - ); -}); - -self.addEventListener("notificationclick", (event) => { - event.notification.close(); - event.waitUntil( - (async () => { - const target = new URL( - String( - (event.notification.data as { url?: string } | undefined)?.url ?? "/", - ), - self.location.origin, - ); - const windows = await self.clients.matchAll({ - type: "window", - includeUncontrolled: true, - }); - for (const client of windows) { - if ("navigate" in client) await client.navigate(target.href); - return client.focus(); - } - return self.clients.openWindow(target.href); - })(), - ); -}); - -self.addEventListener("message", (event) => { - if ((event.data as { type?: string } | undefined)?.type === "SKIP_WAITING") { - void self.skipWaiting(); - } -}); diff --git a/apps/pwa/src/style.css b/apps/pwa/src/style.css deleted file mode 100644 index 213d773..0000000 --- a/apps/pwa/src/style.css +++ /dev/null @@ -1,18 +0,0 @@ -@media (display-mode: standalone), (display-mode: fullscreen) { - [data-pwa-root] { - padding-top: env(safe-area-inset-top, 0px); - padding-right: env(safe-area-inset-right, 0px); - padding-left: env(safe-area-inset-left, 0px); - } -} - -@media (display-mode: window-controls-overlay) and (min-width: 768px) { - [data-pwa-navbar] { - min-height: env(titlebar-area-height, 3.375rem); - padding-left: max(1px, env(titlebar-area-x, 0px)); - padding-right: max( - 0px, - calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw)) - ); - } -} diff --git a/apps/pwa/src/vite.ts b/apps/pwa/src/vite.ts deleted file mode 100644 index 962dce1..0000000 --- a/apps/pwa/src/vite.ts +++ /dev/null @@ -1,218 +0,0 @@ -import { readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { Plugin } from "vite"; -import { VitePWA } from "vite-plugin-pwa"; - -const pwaDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const tauriIcons = resolve(pwaDirectory, "../tauri/src-tauri/icons"); -const androidResources = resolve( - pwaDirectory, - "../tauri/src-tauri/gen/android/app/src/main/res", -); - -function emitIcons(): Plugin { - const icons = [ - { - fileName: "icons/icon-180.png", - source: resolve(tauriIcons, "ios/AppIcon-60x60@3x.png"), - }, - { - fileName: "icons/icon-192.png", - source: resolve(androidResources, "mipmap-xxxhdpi/ic_launcher.png"), - }, - { - fileName: "icons/icon-96.png", - source: resolve(androidResources, "mipmap-xhdpi/ic_launcher.png"), - }, - { - fileName: "icons/icon-512.png", - source: resolve(tauriIcons, "icon.png"), - }, - { - fileName: "icons/icon-maskable-512.png", - source: resolve(tauriIcons, "icon.png"), - }, - { - fileName: "icons/icon-monochrome-432.png", - source: resolve( - androidResources, - "mipmap-xxxhdpi/ic_launcher_monochrome.png", - ), - }, - { - fileName: "icons/notification-badge.png", - source: resolve(androidResources, "drawable/ic_notification_small.png"), - }, - ]; - - return { - name: "tensamin-pwa-icons", - configureServer(server) { - server.middlewares.use((request, response, next) => { - const pathname = request.url - ? new URL(request.url, "http://localhost").pathname.slice(1) - : ""; - const icon = icons.find(({ fileName }) => fileName === pathname); - if (!icon) { - next(); - return; - } - response.statusCode = 200; - response.setHeader("Content-Type", "image/png"); - response.setHeader("Cache-Control", "no-cache"); - response.end(readFileSync(icon.source)); - }); - }, - generateBundle() { - for (const icon of icons) { - this.emitFile({ - type: "asset", - fileName: icon.fileName, - source: readFileSync(icon.source), - }); - } - }, - transformIndexHtml: { - order: "post", - handler() { - return [ - { - tag: "link", - attrs: { - rel: "apple-touch-icon", - sizes: "180x180", - href: "./icons/icon-180.png", - }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { name: "apple-mobile-web-app-capable", content: "yes" }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { - name: "apple-mobile-web-app-status-bar-style", - content: "black-translucent", - }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { - name: "apple-mobile-web-app-title", - content: "Tensamin", - }, - injectTo: "head", - }, - { - tag: "meta", - attrs: { name: "theme-color", content: "#006a67" }, - injectTo: "head", - }, - ]; - }, - }, - }; -} - -export function tensaminPwa(): Plugin[] { - return [ - emitIcons(), - ...VitePWA({ - strategies: "injectManifest", - srcDir: resolve(pwaDirectory, "src"), - filename: "serviceWorker.ts", - injectRegister: null, - registerType: "prompt", - buildBase: "/", - manifestFilename: "manifest.json", - includeAssets: ["favicon.ico", "icons/*.png"], - manifest: { - id: "/", - name: "Tensamin", - short_name: "Tensamin", - description: "Private messaging and calls with Tensamin.", - start_url: "/", - scope: "/", - display: "standalone", - display_override: ["window-controls-overlay", "standalone"], - background_color: "#001f1e", - theme_color: "#006a67", - categories: ["social", "communication"], - orientation: "any", - launch_handler: { client_mode: "focus-existing" }, - icons: [ - { - src: "icons/icon-192.png", - sizes: "192x192", - type: "image/png", - purpose: "any", - }, - { - src: "icons/icon-512.png", - sizes: "512x512", - type: "image/png", - purpose: "any", - }, - { - src: "icons/icon-maskable-512.png", - sizes: "512x512", - type: "image/png", - purpose: "maskable", - }, - { - src: "icons/icon-monochrome-432.png", - sizes: "432x432", - type: "image/png", - purpose: "monochrome", - }, - ], - shortcuts: [ - { - name: "Chats", - short_name: "Chats", - url: "/", - icons: [ - { - src: "icons/icon-96.png", - sizes: "96x96", - type: "image/png", - }, - ], - }, - { - name: "Settings", - short_name: "Settings", - url: "/settings", - icons: [ - { - src: "icons/icon-96.png", - sizes: "96x96", - type: "image/png", - }, - ], - }, - ], - file_handlers: [ - { - action: "/login", - accept: { "application/x-tensamin-user": [".tu"] }, - }, - ], - }, - injectManifest: { - globPatterns: ["**/*.{js,css,html,ico,png,svg,woff2,wasm,mp3,wav}"], - globIgnores: ["assets/v2/**"], - maximumFileSizeToCacheInBytes: 15 * 1024 * 1024, - }, - devOptions: { - enabled: true, - type: "module", - }, - }), - ]; -} diff --git a/apps/pwa/todo.md b/apps/pwa/todo.md deleted file mode 100644 index b5b2055..0000000 --- a/apps/pwa/todo.md +++ /dev/null @@ -1,33 +0,0 @@ -# Web Push Backend TODO - -The client can subscribe and decrypt version 1 push payloads, but reliable delivery requires backend support. - -- Generate and securely store a VAPID key pair. Expose only the public key to the web build as `VITE_WEB_PUSH_PUBLIC_KEY`. -- Add authenticated MTP requests for registering, replacing, and deleting a browser `PushSubscription` per user and installation. -- Persist the endpoint, `p256dh`, `auth`, expiration time, stable installation ID, and last-seen time. -- Remove subscriptions when a push service returns HTTP 404 or 410 and rate-limit registrations per user. -- Send pushes when an encrypted live message cannot be delivered to an active browser client. Define duplicate suppression for clients that receive both MTP and Web Push. -- Keep the JSON payload within push-provider limits and use this version 1 shape: - -```json -{ - "version": 1, - "senderId": 123, - "sender": "Display name", - "avatar": "https://optional.example/avatar", - "message": { "content": "base64 encrypted message content" }, - "secret": { - "chatId": "123:456", - "secretId": "chat:123:456:main", - "version": 1, - "encryptedSecret": "base64 wrapped chat secret", - "kemCiphertext": "base64 KEM ciphertext", - "wrappingScheme": "mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1" - } -} -``` - -- Ensure the wrapped secret is intended for the receiving user's MTP keyring. The server must never receive plaintext message content or plaintext chat secrets. -- Decide how edits, deletions, reactions, calls, read states, and per-chat notification cancellation map to push events. -- Add subscription rotation handling and unregister subscriptions when a user logs out or clears application data. -- Configure production HTTPS, SPA route fallback, `application/manifest+json` for `manifest.json`, and `Cache-Control: no-cache` for the service worker. diff --git a/apps/pwa/tsconfig.json b/apps/pwa/tsconfig.json deleted file mode 100644 index 1e7cfde..0000000 --- a/apps/pwa/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["vite/client", "vite-plugin-pwa/client", "node"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src/runtime.tsx", "src/vite.ts"] -} diff --git a/apps/pwa/tsconfig.worker.json b/apps/pwa/tsconfig.worker.json deleted file mode 100644 index a478894..0000000 --- a/apps/pwa/tsconfig.worker.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "lib": ["ES2022", "WebWorker"], - "types": ["vite-plugin-pwa/client"], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src/serviceWorker.ts"] -} diff --git a/apps/tauri/.gitignore b/apps/tauri/.gitignore index 8952e07..dc67136 100644 --- a/apps/tauri/.gitignore +++ b/apps/tauri/.gitignore @@ -24,3 +24,6 @@ dist-ssr *.sw? .android + +/src-tauri/gen/android/keystore.properties +/src-tauri/gen/android/keystore.jks diff --git a/apps/tauri/monochrome_cropped.png b/apps/tauri/monochrome_cropped.png deleted file mode 100644 index 424ebd1..0000000 Binary files a/apps/tauri/monochrome_cropped.png and /dev/null differ diff --git a/apps/tauri/package.json b/apps/tauri/package.json index 36937be..27be94c 100644 --- a/apps/tauri/package.json +++ b/apps/tauri/package.json @@ -7,13 +7,16 @@ "./deeplinkHandler": { "types": "./src/deeplinkHandler.tsx", "default": "./src/deeplinkHandler.tsx" + }, + "./qrCodeScanner": { + "types": "./src/qrCodeScanner.tsx", + "default": "./src/qrCodeScanner.tsx" } }, "scripts": { - "dev:mobile:raw": "adb reverse tcp:3000 tcp:3000 && tauri android dev --host ${TAURI_DEV_HOST:-127.0.0.1}", + "dev:mobile:raw": "tauri android dev --host ${TAURI_DEV_HOST:-127.0.0.1}", "start-adb:mobile:raw": "adb devices", "build:mobile:raw": "tauri android build", - "build:mobile:ci": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && tauri android build --debug", "dev:mobile": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run dev:mobile:raw; else pnpm run dev:mobile:raw; fi", "start-adb:mobile": "if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run start-adb:mobile:raw; else pnpm run start-adb:mobile:raw; fi", "build:mobile": "node render-version.ts && trap 'node render-version.ts --unrender' EXIT && if command -v nix >/dev/null 2>&1 && [ -z \"$IN_NIX_SHELL\" ]; then nix develop ../..#tauri --command pnpm run build:mobile:raw; else pnpm run build:mobile:raw; fi", @@ -22,15 +25,18 @@ "lint": "eslint src" }, "dependencies": { - "@methanium/ui": "*", - "@tauri-apps/api": "^2.11.1", - "@tauri-apps/plugin-deep-link": "~2.4.9", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-barcode-scanner": "~2", + "@tauri-apps/plugin-deep-link": "~2", + "@tauri-apps/plugin-log": "~2", + "@tauri-apps/plugin-notification": "~2", "@tensamin/shared": "workspace:*", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "@methanium/ui": "*", + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { - "@tauri-apps/cli": "^2.11.4", - "@types/node": "^26.1.2" + "@tauri-apps/cli": "^2", + "@types/node": "^25.9.1" } } diff --git a/apps/tauri/scripts/delete-mobile.ts b/apps/tauri/scripts/delete-mobile.ts deleted file mode 100644 index e41fa50..0000000 --- a/apps/tauri/scripts/delete-mobile.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { createInterface } from "node:readline/promises"; - -const devicesResult = spawnSync("adb", ["devices", "-l"], { - encoding: "utf8", -}); - -if (devicesResult.status !== 0) { - process.stderr.write(devicesResult.stderr); - process.exit(devicesResult.status ?? 1); -} - -const devices = devicesResult.stdout - .split("\n") - .slice(1) - .map((line) => line.trim()) - .filter((line) => /\sdevice(?:\s|$)/.test(line)); - -if (devices.length === 0) { - console.error("No connected ADB devices found."); - process.exit(1); -} - -let selectedDevice = devices[0]; - -if (devices.length > 1) { - console.log("Select a device:"); - devices.forEach((device, index) => console.log(`${index + 1}) ${device}`)); - - const readline = createInterface({ - input: process.stdin, - output: process.stdout, - }); - const answer = await readline.question("Device: "); - readline.close(); - - const selectedIndex = Number(answer) - 1; - if (!Number.isInteger(selectedIndex) || !devices[selectedIndex]) { - console.error("Invalid device selection."); - process.exit(1); - } - - selectedDevice = devices[selectedIndex]; -} - -const serial = selectedDevice.split(/\s+/, 1)[0]; -const uninstallResult = spawnSync( - "adb", - ["-s", serial, "uninstall", "net.tensamin.client.dev"], - { stdio: "inherit" }, -); - -process.exit(uninstallResult.status ?? 1); diff --git a/apps/tauri/src-tauri/Cargo.lock b/apps/tauri/src-tauri/Cargo.lock index d404d61..c872475 100644 --- a/apps/tauri/src-tauri/Cargo.lock +++ b/apps/tauri/src-tauri/Cargo.lock @@ -9,20 +9,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] -name = "aead" -version = "0.5.2" +name = "ahash" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "crypto-common 0.1.7", - "generic-array", + "getrandom 0.2.17", + "once_cell", + "version_check", ] [[package]] name = "aho-corasick" -version = "1.1.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -61,57 +62,24 @@ dependencies = [ [[package]] name = "android_system_properties" -version = "0.1.6" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" -version = "1.0.104" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] -name = "asn1-rs" -version = "0.7.2" +name = "arrayvec" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.19", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "async-broadcast" @@ -206,7 +174,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -235,13 +203,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] @@ -279,30 +247,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "aws-lc-rs" -version = "1.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", - "pkg-config", -] - [[package]] name = "base64" version = "0.21.7" @@ -315,25 +259,13 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] [[package]] @@ -342,15 +274,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -359,13 +282,25 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -375,15 +310,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - [[package]] name = "block2" version = "0.6.2" @@ -406,6 +332,30 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3f6da4992df95bbcd9af42a6c7dcb994498fc9048230405f3b36ff7cd3f145" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8fb4fb5740e4b2c4884ff95f5f32f5e8479db1e8fd8eb49ddbe09eb09bb7c" +dependencies = [ + "once_cell", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "brotli" version = "8.0.4" @@ -443,10 +393,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] -name = "bytemuck" -version = "1.25.2" +name = "byte-unit" +version = "5.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +checksum = "4a813de7f2bbedb7dce265b64f1cf5908ebe4d56281ece8d847e98113788b9b0" +dependencies = [ + "rust_decimal", + "schemars 1.2.1", + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -456,9 +440,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" dependencies = [ "serde", ] @@ -469,7 +453,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "cairo-sys-rs", "glib", "libc", @@ -490,9 +474,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.5" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +checksum = "5f2d30e4173c4026932d51d31d6b0613b1fd3014bf3f9f8943d4ba139c437ba0" dependencies = [ "serde_core", ] @@ -517,29 +501,26 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.19", + "thiserror 2.0.18", ] [[package]] name = "cargo_toml" -version = "1.0.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa61aec073ec94791433ddf3df2323ff9d1711557c2a0eefb0f99cb4f8dca520" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ - "semver", "serde", - "toml 1.1.4+spec-1.1.0", + "toml 0.9.12+spec-1.1.0", ] [[package]] name = "cc" -version = "1.4.0" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -578,44 +559,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.2.17", -] - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20 0.9.1", - "cipher", - "poly1305", - "zeroize", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -629,32 +575,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.7", - "inout", - "zeroize", -] - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "cmov" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" - [[package]] name = "combine" version = "4.6.7" @@ -674,12 +594,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - [[package]] name = "const-random" version = "0.1.18" @@ -732,7 +646,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "core-foundation", "core-graphics-types", "foreign-types", @@ -745,7 +659,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "core-foundation", "libc", ] @@ -759,15 +673,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - [[package]] name = "crc32fast" version = "1.5.0" @@ -779,18 +684,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -805,21 +710,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "getrandom 0.4.3", - "hybrid-array", - "rand_core 0.10.1", -] - [[package]] name = "cssparser" version = "0.36.0" @@ -840,7 +733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -853,69 +746,12 @@ dependencies = [ "dtor", ] -[[package]] -name = "ctor" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d83cb7e7a873830708d6b02a78cd36a592c6fa14bf267b68725103b85c0d77f" - [[package]] name = "ctor-proc-macro" version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" -[[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" -dependencies = [ - "cmov", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "fiat-crypto 0.2.9", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "curve25519-dalek-derive", - "digest 0.11.3", - "fiat-crypto 0.3.0", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "darling" version = "0.23.0" @@ -936,7 +772,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -947,51 +783,20 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.119", + "syn 2.0.118", ] -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - [[package]] name = "dbus" -version = "0.9.12" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73" dependencies = [ "libc", "libdbus-sys", "windows-sys 0.61.2", ] -[[package]] -name = "der" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - [[package]] name = "deranged" version = "0.5.8" @@ -1019,7 +824,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1028,20 +833,8 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", -] - -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "const-oid", - "crypto-common 0.2.2", - "ctutils", + "block-buffer", + "crypto-common", ] [[package]] @@ -1071,7 +864,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "block2", "libc", "objc2", @@ -1079,13 +872,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] @@ -1108,7 +901,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1186,31 +979,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ed25519" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" -dependencies = [ - "curve25519-dalek 5.0.0", - "ed25519", - "serde", - "sha2 0.11.0", - "signature", - "subtle", - "zeroize", -] - [[package]] name = "embed-resource" version = "3.0.11" @@ -1220,7 +988,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml 1.1.4+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "vswhom", "winreg", ] @@ -1255,7 +1023,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1292,15 +1060,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "event-listener" -version = "5.4.2" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ + "concurrent-queue", "parking", "pin-project-lite", ] @@ -1315,23 +1084,11 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastbloom" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" -dependencies = [ - "foldhash", - "libm", - "portable-atomic", - "siphasher", -] - [[package]] name = "fastrand" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" @@ -1351,18 +1108,6 @@ dependencies = [ "log", ] -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "fiat-crypto" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" - [[package]] name = "field-offset" version = "0.3.6" @@ -1413,13 +1158,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] @@ -1438,47 +1183,31 @@ dependencies = [ ] [[package]] -name = "fs_extra" -version = "1.3.0" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", - "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1487,9 +1216,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1506,34 +1235,33 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1660,10 +1388,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1685,11 +1411,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", ] [[package]] @@ -1730,7 +1453,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "futures-channel", "futures-core", "futures-executor", @@ -1758,7 +1481,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -1773,9 +1496,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gobject-sys" @@ -1837,82 +1560,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h3" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" -dependencies = [ - "bytes", - "fastrand", - "futures-util", - "http", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "h3-datagram" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" -dependencies = [ - "bytes", - "h3", - "pin-project-lite", -] - -[[package]] -name = "h3-quinn" -version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" -dependencies = [ - "bytes", - "futures", - "h3", - "h3-datagram", - "quinn", - "tokio", - "tokio-util", -] - -[[package]] -name = "h3-webtransport" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d91a50fd582a5d67b1f756fba3cd9c66367ff4f23e1017c882f664d63b350a7" -dependencies = [ - "bytes", - "futures-util", - "h3", - "h3-datagram", - "http", - "pin-project-lite", - "tokio", - "tracing", + "syn 2.0.118", ] [[package]] @@ -1920,6 +1568,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -1957,24 +1608,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", -] - [[package]] name = "html5ever" version = "0.38.0" @@ -1985,17 +1618,11 @@ dependencies = [ "markup5ever", ] -[[package]] -name = "httlib-huffman" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" - [[package]] name = "http" -version = "1.5.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2003,9 +1630,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -2013,9 +1640,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", @@ -2030,37 +1657,19 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" -dependencies = [ - "ctutils", - "typenum", -] - [[package]] name = "hyper" -version = "1.11.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2068,21 +1677,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.20" @@ -2118,7 +1712,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -2281,20 +1875,11 @@ dependencies = [ "cfb", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is-docker" @@ -2360,36 +1945,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.19", - "walkdir", - "windows-link 0.2.1", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - [[package]] name = "jni-sys" version = "0.3.1" @@ -2415,17 +1970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", + "syn 2.0.118", ] [[package]] @@ -2445,24 +1990,12 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" dependencies = [ - "jsonptr 0.6.3", + "jsonptr", "serde", "serde_json", "thiserror 1.0.69", ] -[[package]] -name = "json-patch" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" -dependencies = [ - "jsonptr 0.7.1", - "serde", - "serde_json", - "thiserror 2.0.19", -] - [[package]] name = "jsonptr" version = "0.6.3" @@ -2473,52 +2006,17 @@ dependencies = [ "serde_json", ] -[[package]] -name = "jsonptr" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keccak" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" -dependencies = [ - "cpufeatures 0.2.17", -] - -[[package]] -name = "keccak" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", -] - [[package]] name = "keyboard-types" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "serde", "unicode-segmentation", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libappindicator" version = "0.9.0" @@ -2545,9 +2043,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.189" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdbus-sys" @@ -2568,17 +2066,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - [[package]] name = "libredox" -version = "0.1.19" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "libc", ] @@ -2609,12 +2101,9 @@ name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +dependencies = [ + "value-bag", +] [[package]] name = "mac-notification-sys" @@ -2643,9 +2132,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.3" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memoffset" @@ -2662,12 +2151,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2680,216 +2163,15 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] -[[package]] -name = "ml-dsa" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" -dependencies = [ - "const-oid", - "crypto-common 0.2.2", - "ctutils", - "hybrid-array", - "module-lattice", - "pkcs8", - "shake", - "signature", -] - -[[package]] -name = "mlkem-rs" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" -dependencies = [ - "rand_core 0.6.4", - "serde", - "sha3", - "subtle", - "zeroize", -] - -[[package]] -name = "mlkem-tls" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" -dependencies = [ - "mlkem-rs", - "rand_core 0.6.4", - "subtle", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "module-lattice" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" -dependencies = [ - "ctutils", - "hybrid-array", - "num-traits", -] - -[[package]] -name = "mtp" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "mtp-client", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-host", - "mtp-transport", - "mtp-type-map", - "mtp-webserver", -] - -[[package]] -name = "mtp-client" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-transport", - "rand 0.10.2", - "tokio", -] - -[[package]] -name = "mtp-codec" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "base64 0.23.1", - "byteorder", - "mtp-common", - "mtp-crypto", - "mtp-type-map", - "rand 0.10.2", - "thiserror 2.0.19", -] - -[[package]] -name = "mtp-common" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "quinn", - "rustls", - "thiserror 2.0.19", - "wtransport", -] - -[[package]] -name = "mtp-crypto" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "base64 0.22.1", - "chacha20poly1305", - "ed25519-dalek", - "getrandom 0.4.3", - "hkdf", - "ml-dsa", - "mlkem-tls", - "rand 0.10.2", - "rand_core 0.6.4", - "rustls", - "serde", - "sha2 0.11.0", - "thiserror 1.0.69", - "tokio", - "zeroize", -] - -[[package]] -name = "mtp-host" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-transport", - "rand 0.10.2", - "thiserror 2.0.19", - "tokio", - "tracing", - "wtransport", -] - -[[package]] -name = "mtp-transport" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "async-trait", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "rand 0.10.2", - "rcgen", - "rustls", - "rustls-native-certs", - "sha2 0.11.0", - "tokio", - "tracing", - "wtransport", - "zeroize", -] - -[[package]] -name = "mtp-type-map" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "serde", - "serde_yaml", -] - -[[package]] -name = "mtp-webserver" -version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a" -dependencies = [ - "async-trait", - "bytes", - "h3", - "h3-quinn", - "h3-webtransport", - "http", - "http-body-util", - "hyper", - "hyper-util", - "mtp-codec", - "mtp-common", - "mtp-crypto", - "mtp-host", - "mtp-transport", - "quinn", - "rustls", - "thiserror 2.0.19", - "tokio", - "tokio-rustls", - "tokio-stream", - "tracing", -] - [[package]] name = "muda" version = "0.19.3" @@ -2907,7 +2189,7 @@ dependencies = [ "once_cell", "png 0.18.1", "serde", - "thiserror 2.0.19", + "thiserror 2.0.18", "windows-sys 0.61.2", ] @@ -2917,7 +2199,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "jni-sys 0.3.1", "log", "ndk-sys", @@ -2941,16 +2223,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "notify-rust" version = "4.18.0" @@ -2965,31 +2237,12 @@ dependencies = [ "zbus", ] -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -3018,7 +2271,7 @@ dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3046,7 +2299,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "block2", "objc2", "objc2-core-foundation", @@ -3059,7 +2312,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "objc2", "objc2-foundation", ] @@ -3080,7 +2333,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "dispatch2", "objc2", ] @@ -3091,7 +2344,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -3124,7 +2377,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -3151,7 +2404,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "block2", "libc", "objc2", @@ -3164,7 +2417,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -3185,7 +2438,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -3197,7 +2450,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "objc2", "objc2-core-foundation", ] @@ -3208,7 +2461,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "block2", "objc2", "objc2-cloud-kit", @@ -3239,7 +2492,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "block2", "objc2", "objc2-app-kit", @@ -3249,50 +2502,23 @@ dependencies = [ "objc2-security", ] -[[package]] -name = "octets" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866cb5af6f3aa3c1b44c3c2d79d22165fbb1b102e1b3fb499864bfe34736ec4b" - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "open" -version = "5.4.1" +version = "5.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cfef937e9c486488c7e3d949ae31c0f1d06bdacd75b99c086cb35356e30408" +checksum = "cd8d3b65c44123a56e0133d2cd06ce4361bd3ca99d41198b2f25e3c3db9b8b4a" dependencies = [ "dunce", "is-wsl", "libc", ] -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - [[package]] name = "option-ext" version = "0.2.0" @@ -3373,25 +2599,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -3439,7 +2646,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -3468,16 +2675,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs8" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.33" @@ -3486,9 +2683,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" dependencies = [ "base64 0.22.1", "indexmap 2.14.0", @@ -3516,7 +2713,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", @@ -3537,23 +2734,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "portable-atomic" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" - [[package]] name = "potential_utf" version = "0.1.5" @@ -3610,7 +2790,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -3639,87 +2819,47 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.107" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] -name = "quick-xml" -version = "0.41.0" +name = "ptr_meta" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", ] -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "bytes", - "cfg_aliases", - "futures-io", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.19", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" -dependencies = [ - "aws-lc-rs", - "bytes", - "fastbloom", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "slab", - "thiserror 2.0.19", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.52.0", -] - [[package]] name = "quote" -version = "1.0.47" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -3737,24 +2877,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "rand" -version = "0.9.5" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ - "rand_chacha", - "rand_core 0.9.5", + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.10.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "chacha20 0.10.1", - "getrandom 0.4.3", - "rand_core 0.10.1", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -3785,49 +2941,19 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core 0.10.1", -] - [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "rcgen" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" -dependencies = [ - "aws-lc-rs", - "pem", - "ring", - "rustls-pki-types", - "time", - "x509-parser", - "yasna", -] - [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", ] [[package]] @@ -3838,34 +2964,34 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.19", + "thiserror 2.0.18", ] [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] name = "regex" -version = "1.13.1" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3875,9 +3001,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.18" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3890,6 +3016,15 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.13.4" @@ -3904,21 +3039,15 @@ dependencies = [ "http-body", "http-body-util", "hyper", - "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", - "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3931,17 +3060,32 @@ dependencies = [ ] [[package]] -name = "ring" -version = "0.17.14" +name = "rkyv" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted 0.9.0", - "windows-sys 0.52.0", + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -3955,10 +3099,27 @@ dependencies = [ ] [[package]] -name = "rustc-hash" -version = "2.1.3" +name = "rust_decimal" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.6", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -3969,116 +3130,24 @@ dependencies = [ "semver", ] -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.23.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted 0.9.0", + "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.23" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" @@ -4089,15 +3158,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "schemars" version = "0.8.22" @@ -4127,13 +3187,13 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", - "schemars_derive 1.2.2", + "schemars_derive 1.2.1", "serde", "serde_json", "url", @@ -4148,20 +3208,20 @@ checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.29.1", - "syn 2.0.119", + "serde_derive_internals", + "syn 2.0.118", ] [[package]] name = "schemars_derive" -version = "1.2.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.30.0", - "syn 3.0.3", + "serde_derive_internals", + "syn 2.0.118", ] [[package]] @@ -4171,27 +3231,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework" -version = "3.7.0" +name = "seahash" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.1", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "selectors" @@ -4199,7 +3242,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "cssparser", "derive_more", "log", @@ -4224,9 +3267,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -4246,22 +3289,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.229" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] @@ -4272,25 +3315,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_derive_internals" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "indexmap 2.14.0", "itoa", @@ -4302,13 +3334,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.21" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] @@ -4342,7 +3374,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.14.0", "schemars 0.9.0", - "schemars 1.2.2", + "schemars 1.2.1", "serde_core", "serde_json", "serde_with_macros", @@ -4358,20 +3390,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.119", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.14.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "syn 2.0.118", ] [[package]] @@ -4393,7 +3412,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4412,40 +3431,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - -[[package]] -name = "sha3" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" -dependencies = [ - "digest 0.10.7", - "keccak 0.1.6", -] - -[[package]] -name = "shake" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" -dependencies = [ - "digest 0.11.3", - "keccak 0.2.1", - "sponge-cursor", + "cpufeatures", + "digest", ] [[package]] @@ -4464,31 +3451,11 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" -dependencies = [ - "digest 0.11.3", - "rand_core 0.10.1", -] - [[package]] name = "simd-adler32" -version = "0.3.10" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simdutf8" @@ -4516,9 +3483,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4572,22 +3539,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spki" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sponge-cursor" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -4624,12 +3575,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "swift-rs" version = "1.0.7" @@ -4646,16 +3591,6 @@ name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -4664,9 +3599,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -4690,7 +3625,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -4712,7 +3647,7 @@ version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "block2", "core-foundation", "core-graphics", @@ -4724,7 +3659,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni 0.21.1", + "jni", "libc", "log", "ndk", @@ -4741,22 +3676,28 @@ dependencies = [ "unicode-segmentation", "url", "windows", - "windows-core", + "windows-core 0.61.2", "windows-version", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4765,8 +3706,8 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.5" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.11.3" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "anyhow", "bytes", @@ -4779,7 +3720,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni 0.21.1", + "jni", "libc", "log", "mime", @@ -4802,8 +3743,8 @@ dependencies = [ "tauri-macros", "tauri-runtime", "tauri-runtime-wry", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", - "thiserror 2.0.19", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "thiserror 2.0.18", "tokio", "tray-icon", "url", @@ -4816,19 +3757,19 @@ dependencies = [ [[package]] name = "tauri-build" version = "2.6.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "anyhow", "cargo_toml", "dirs", "glob", "heck 0.5.0", - "json-patch 4.2.0", - "schemars 1.2.2", + "json-patch", + "schemars 1.2.1", "semver", "serde", "serde_json", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", "tauri-winres", "walkdir", ] @@ -4836,12 +3777,12 @@ dependencies = [ [[package]] name = "tauri-codegen" version = "2.6.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "base64 0.22.1", "brotli", "ico", - "json-patch 4.2.0", + "json-patch", "plist", "png 0.17.16", "proc-macro2", @@ -4849,10 +3790,10 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2 0.10.9", - "syn 2.0.119", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", - "thiserror 2.0.19", + "sha2", + "syn 2.0.118", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "thiserror 2.0.18", "time", "url", "uuid", @@ -4862,14 +3803,14 @@ dependencies = [ [[package]] name = "tauri-macros" version = "2.6.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "tauri-codegen", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", ] [[package]] @@ -4888,6 +3829,32 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-app-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e625f35abcfc422c7c4af8b348d2069cdf387a39c12429fdce570137d02f069" +dependencies = [ + "serde", + "tauri", + "tauri-plugin", + "thiserror 1.0.69", +] + +[[package]] +name = "tauri-plugin-barcode-scanner" +version = "2.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b68f0e3782b61a6e16a67380be40226e2b7233f3e36dadf6e5d03f07d2e7b3" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", +] + [[package]] name = "tauri-plugin-deep-link" version = "2.4.9" @@ -4902,20 +3869,21 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-utils 2.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 2.0.19", + "thiserror 2.0.18", "tracing", "url", "windows-registry", - "windows-result", + "windows-result 0.3.4", ] [[package]] name = "tauri-plugin-log" -version = "2.9.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6792296e6f389268016c77db21ebae1fc0568f2fccf88b1ec7e2ea71330afb4c" +checksum = "7545bd67f070a4500432c826e2e0682146a1d6712aee22a2786490156b574d93" dependencies = [ "android_logger", + "byte-unit", "fern", "log", "objc2", @@ -4926,7 +3894,7 @@ dependencies = [ "swift-rs", "tauri", "tauri-plugin", - "thiserror 2.0.19", + "thiserror 2.0.18", "time", ] @@ -4938,13 +3906,13 @@ checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" dependencies = [ "log", "notify-rust", - "rand 0.9.5", + "rand 0.9.4", "serde", "serde_json", "serde_repr", "tauri", "tauri-plugin", - "thiserror 2.0.19", + "thiserror 2.0.18", "time", "url", ] @@ -4965,7 +3933,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.19", + "thiserror 2.0.18", "url", "windows", "zbus", @@ -4974,32 +3942,32 @@ dependencies = [ [[package]] name = "tauri-runtime" version = "2.11.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "cookie", "dpi", "gtk", "http", - "jni 0.21.1", + "jni", "objc2", "objc2-ui-kit", "raw-window-handle", "serde", "serde_json", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", - "thiserror 2.0.19", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", + "thiserror 2.0.18", "url", "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.11.4" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +version = "2.11.3" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "gtk", "http", - "jni 0.21.1", + "jni", "log", "objc2", "objc2-app-kit", @@ -5010,7 +3978,7 @@ dependencies = [ "softbuffer", "tao", "tauri-runtime", - "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41)", + "tauri-utils 2.9.3 (git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef)", "url", "webkit2gtk", "webview2-com", @@ -5026,13 +3994,13 @@ checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" dependencies = [ "anyhow", "cargo_metadata", - "ctor 0.8.0", + "ctor", "dom_query", "dunce", "glob", "http", "infer", - "json-patch 3.0.1", + "json-patch", "log", "memchr", "phf", @@ -5047,28 +4015,28 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.19", - "toml 1.1.4+spec-1.1.0", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", "url", - "urlpattern 0.3.0", + "urlpattern", "uuid", ] [[package]] name = "tauri-utils" version = "2.9.3" -source = "git+https://github.com/tauri-apps/tauri?rev=4af26a3f7f8b692d62cca549bbacd93f5ce90b41#4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +source = "git+https://github.com/tauri-apps/tauri?branch=feat%2Fcef#20f3f2515c65ca1e115991c3ef3625e2c29c1bbb" dependencies = [ "anyhow", "brotli", "cargo_metadata", - "ctor 1.0.12", + "ctor", "dom_query", "dunce", "glob", "http", "infer", - "json-patch 4.2.0", + "json-patch", "log", "memchr", "phf", @@ -5076,17 +4044,17 @@ dependencies = [ "proc-macro2", "quote", "regex", - "schemars 1.2.2", + "schemars 1.2.1", "semver", "serde", "serde-untagged", "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.19", - "toml 1.1.4+spec-1.1.0", + "thiserror 2.0.18", + "toml 1.1.2+spec-1.1.0", "url", - "urlpattern 0.6.0", + "urlpattern", "uuid", "walkdir", ] @@ -5099,7 +4067,7 @@ checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" dependencies = [ "dunce", "embed-resource", - "toml 1.1.4+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", ] [[package]] @@ -5108,7 +4076,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed071c670382e85fc2f48ae706492d8c338f4f89bf72520d32f8abfe880aade" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.18", "windows", "windows-version", ] @@ -5120,39 +4088,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tendril" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" dependencies = [ "new_debug_unreachable", + "utf-8", ] [[package]] name = "tensamin" version = "0.0.0" dependencies = [ - "base64 0.22.1", - "jni 0.22.4", - "mtp", - "reqwest", "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-app-events", + "tauri-plugin-barcode-scanner", "tauri-plugin-deep-link", "tauri-plugin-log", "tauri-plugin-notification", "tauri-plugin-opener", - "tokio", - "webpki-root-certs", ] [[package]] @@ -5166,11 +4131,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.18", ] [[package]] @@ -5181,25 +4146,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 2.0.118", ] [[package]] name = "time" -version = "0.3.55" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "libc", @@ -5219,9 +4184,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.32" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -5248,9 +4213,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -5263,63 +4228,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", - "tokio-macros", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", - "libc", "pin-project-lite", "tokio", ] @@ -5338,9 +4267,24 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.4+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap 2.14.0", "serde_core", @@ -5348,7 +4292,7 @@ dependencies = [ "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.4", + "winnow 1.0.3", ] [[package]] @@ -5360,6 +4304,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -5395,30 +4348,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.13+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.4", + "winnow 1.0.3", ] [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.4", + "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -5441,7 +4394,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags 2.13.0", "bytes", "futures-util", "http", @@ -5471,7 +4424,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5485,7 +4437,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5499,9 +4451,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.24.2" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" dependencies = [ "crossbeam-channel", "dirs", @@ -5515,7 +4467,7 @@ dependencies = [ "once_cell", "png 0.18.1", "serde", - "thiserror 2.0.19", + "thiserror 2.0.18", "windows-sys 0.61.2", ] @@ -5601,34 +4553,6 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "url" version = "2.5.8" @@ -5655,16 +4579,16 @@ dependencies = [ ] [[package]] -name = "urlpattern" -version = "0.6.0" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" -dependencies = [ - "icu_properties", - "regex", - "serde", - "url", -] +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" [[package]] name = "utf8_iter" @@ -5674,9 +4598,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.0" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5684,6 +4608,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + [[package]] name = "version-compare" version = "0.2.1" @@ -5792,7 +4722,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -5828,16 +4758,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web_atoms" version = "0.2.5" @@ -5894,15 +4814,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webview2-com" version = "0.38.2" @@ -5912,7 +4823,7 @@ dependencies = [ "webview2-com-macros", "webview2-com-sys", "windows", - "windows-core", + "windows-core 0.61.2", "windows-implement", "windows-interface", ] @@ -5925,7 +4836,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -5934,9 +4845,9 @@ version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "thiserror 2.0.19", + "thiserror 2.0.18", "windows", - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -5961,7 +4872,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5992,7 +4903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.61.2", "windows-future", "windows-link 0.1.3", "windows-numerics", @@ -6004,7 +4915,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -6016,8 +4927,21 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -6026,7 +4950,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", "windows-threading", ] @@ -6039,7 +4963,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -6050,7 +4974,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -6071,7 +4995,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] @@ -6082,8 +5006,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -6095,6 +5019,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -6104,6 +5037,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -6113,15 +5055,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -6290,9 +5223,15 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.4" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -6337,7 +5276,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni 0.21.1", + "jni", "libc", "ndk", "objc2", @@ -6349,54 +5288,27 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2 0.10.9", + "sha2", "soup3", "tao-macros", - "thiserror 2.0.19", + "thiserror 2.0.18", "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", "windows", - "windows-core", + "windows-core 0.61.2", "windows-version", "x11-dl", ] [[package]] -name = "wtransport" -version = "0.7.1" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "bytes", - "pem", - "quinn", - "rcgen", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "sha2 0.11.0", - "socket2", - "thiserror 2.0.19", - "time", - "tokio", - "tracing", - "url", - "wtransport-proto", - "x509-parser", -] - -[[package]] -name = "wtransport-proto" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" -dependencies = [ - "httlib-huffman", - "octets", - "thiserror 2.0.19", - "url", + "tap", ] [[package]] @@ -6420,47 +5332,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek 4.1.3", - "rand_core 0.6.4", - "serde", - "zeroize", -] - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "aws-lc-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "ring", - "rusticata-macros", - "thiserror 2.0.19", - "time", -] - -[[package]] -name = "yasna" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" -dependencies = [ - "bit-vec 0.9.1", - "time", -] - [[package]] name = "yoke" version = "0.8.3" @@ -6480,15 +5351,15 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "synstructure", ] [[package]] name = "zbus" -version = "5.18.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ "async-broadcast", "async-executor", @@ -6513,7 +5384,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 1.0.4", + "winnow 1.0.3", "zbus_macros", "zbus_names", "zvariant", @@ -6521,14 +5392,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.18.0" +version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "zbus_names", "zvariant", "zvariant_utils", @@ -6536,33 +5407,33 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.4" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", - "winnow 1.0.4", + "winnow 1.0.3", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] @@ -6582,30 +5453,10 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "synstructure", ] -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "zerotrie" version = "0.2.4" @@ -6636,51 +5487,51 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", ] [[package]] name = "zmij" -version = "1.0.23" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zvariant" -version = "5.13.1" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" dependencies = [ "endi", "enumflags2", "serde", - "winnow 1.0.4", + "winnow 1.0.3", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.13.1" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" dependencies = [ "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.119", + "syn 2.0.118", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.119", - "winnow 1.0.4", + "syn 2.0.118", + "winnow 1.0.3", ] diff --git a/apps/tauri/src-tauri/Cargo.toml b/apps/tauri/src-tauri/Cargo.toml index 2a68e95..1d3bdf5 100644 --- a/apps/tauri/src-tauri/Cargo.toml +++ b/apps/tauri/src-tauri/Cargo.toml @@ -15,17 +15,12 @@ name = "mobile_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { git = "https://github.com/tauri-apps/tauri", rev = "4af26a3f7f8b692d62cca549bbacd93f5ce90b41", features = [] } +tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "feat/cef", features = [] } [dependencies] tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -base64 = "0.22" -reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } -tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } -mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "a5c8d4f0c898c78351e9d54124886c86e789a22a", features = ["client", "crypto"] } -webpki-root-certs = "1" tauri-plugin-deep-link = "2" tauri-plugin-notification = "2" tauri-plugin-log = "2" @@ -35,14 +30,10 @@ version = "2" features = [] default-features = true -[target.'cfg(not(target_os = "android"))'.dependencies.tauri] -version = "2" -features = [] -default-features = true - -[target.'cfg(target_os = "android")'.dependencies] -jni = "0.22" +[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] +tauri-plugin-barcode-scanner = "2" +tauri-plugin-app-events = "0.2" [patch.crates-io.tauri] git = "https://github.com/tauri-apps/tauri" -rev = "4af26a3f7f8b692d62cca549bbacd93f5ce90b41" +branch = "feat/cef" diff --git a/apps/tauri/src-tauri/capabilities/default.json b/apps/tauri/src-tauri/capabilities/default.json index 83f7620..73d510a 100644 --- a/apps/tauri/src-tauri/capabilities/default.json +++ b/apps/tauri/src-tauri/capabilities/default.json @@ -6,7 +6,10 @@ "main" ], "permissions": [ + "core:default", "opener:default", + "core:window:default", + "core:event:default", "deep-link:default", "notification:default", "log:default" diff --git a/apps/tauri/src-tauri/capabilities/mobile.json b/apps/tauri/src-tauri/capabilities/mobile.json index 91f1d4e..342dd9e 100644 --- a/apps/tauri/src-tauri/capabilities/mobile.json +++ b/apps/tauri/src-tauri/capabilities/mobile.json @@ -1,10 +1,18 @@ { "identifier": "mobile-capability", - "platforms": ["android", "iOS"], - "windows": ["main"], + "platforms": [ + "android", + "iOS" + ], + "windows": [ + "main" + ], "permissions": [ - "core:event:default", "deep-link:default", + "app-events:default", + "barcode-scanner:default", + "barcode-scanner:allow-scan", + "barcode-scanner:allow-cancel", "notification:default", "log:default" ] diff --git a/apps/tauri/src-tauri/gen/android/app/build.gradle.kts b/apps/tauri/src-tauri/gen/android/app/build.gradle.kts index 66d6289..3775319 100644 --- a/apps/tauri/src-tauri/gen/android/app/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/app/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.util.Properties import java.io.FileInputStream @@ -62,29 +61,26 @@ android { ) } } + kotlinOptions { + jvmTarget = "1.8" + } buildFeatures { buildConfig = true } } -kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_1_8 - } -} - rust { rootDirRel = "../../../" } dependencies { - implementation("androidx.webkit:webkit:1.16.0") + implementation("androidx.webkit:webkit:1.14.0") implementation("androidx.appcompat:appcompat:1.7.1") - implementation("androidx.activity:activity-ktx:1.13.0") - implementation("com.google.android.material:material:1.14.0") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.3.0") - androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") } -apply(from = "tauri.build.gradle.kts") +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro b/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro index f1b4245..481bb43 100644 --- a/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro +++ b/apps/tauri/src-tauri/gen/android/app/proguard-rules.pro @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml index 7d05c07..697b23b 100644 --- a/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml +++ b/apps/tauri/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -1,16 +1,6 @@ - - - - - - - - - - @@ -49,30 +39,6 @@ - - - - - - - - - - - - - val includeAudio = pendingScreenAudio - pendingScreenAudio = null - - if (includeAudio == null) return@registerForActivityResult - val data = result.data - if (result.resultCode != Activity.RESULT_OK || data == null) { - MobileMediaEvents.emitError("Screen capture permission was denied") - return@registerForActivityResult - } - - MediaProjectionService.start(this, result.resultCode, data, includeAudio) - } - - private val cameraPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions(), - ) { - emitCameraPermission() - } - - private val screenAudioPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestPermission(), - ) { - launchScreenCaptureIntent() - } - - override fun onWebViewCreate(webView: WebView) { - webView.settings.apply { - setSupportZoom(false) - builtInZoomControls = false - displayZoomControls = false - } - - var blockingMultiTouch = false - webView.setOnTouchListener { _, event -> - val shouldBlock = blockingMultiTouch || event.pointerCount > 1 - - when (event.actionMasked) { - MotionEvent.ACTION_POINTER_DOWN -> { - // Cancel the one-finger gesture before consuming the rest of the pinch. - MotionEvent.obtain(event).let { cancelEvent -> - cancelEvent.action = MotionEvent.ACTION_CANCEL - webView.onTouchEvent(cancelEvent) - cancelEvent.recycle() - } - blockingMultiTouch = true - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> blockingMultiTouch = false - } - - shouldBlock - } - - NativeAccessibilityBridge.attach(webView) - mediaWebView = webView - MobileMediaEvents.attach(webView) - webView.addJavascriptInterface(MobileMediaJavascriptInterface(), "tensaminMobileMedia") - } override fun onCreate(savedInstanceState: Bundle?) { WindowCompat.setDecorFitsSystemWindows(window, true) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) super.onCreate(savedInstanceState) - NativeMtpBridge.nativeAttach(applicationContext) - if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) { - NativeMtpBridge.startService(this) - } - NativeAccessibilityBridge.nativeAttach(applicationContext) installKeyboardResizeWorkaround() } - override fun onResume() { - super.onResume() - NativeMtpBridge.nativeSetUiState(true) - } - - override fun onPause() { - NativeMtpBridge.nativeSetUiState(false) - super.onPause() - } - override fun onDestroy() { attachLayoutListener?.let { listener -> contentRoot?.viewTreeObserver?.removeOnGlobalLayoutListener(listener) @@ -116,87 +30,9 @@ class MainActivity : TauriActivity() { attachLayoutListener = null contentRoot = null contentChild = null - mediaWebView?.let { - NativeAccessibilityBridge.detach(it) - it.removeJavascriptInterface("tensaminMobileMedia") - } - mediaWebView = null - MobileMediaEvents.detach() super.onDestroy() } - private fun startScreenShare(includeAudio: Boolean) { - runOnUiThread { - if (pendingScreenAudio != null) { - MobileMediaEvents.emitError("Screen capture permission is already pending") - return@runOnUiThread - } - - pendingScreenAudio = includeAudio - if ( - includeAudio && - ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED - ) { - screenAudioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - } else { - launchScreenCaptureIntent() - } - } - } - - private fun launchScreenCaptureIntent() { - val manager = getSystemService(MediaProjectionManager::class.java) - screenCaptureLauncher.launch(manager.createScreenCaptureIntent()) - } - - private fun stopScreenShare() { - runOnUiThread { - pendingScreenAudio = null - MediaProjectionService.stop(this) - } - } - - private fun requestCameraPermission() { - runOnUiThread { - cameraPermissionLauncher.launch( - arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), - ) - } - } - - private fun emitCameraPermission() { - val detail = JSONObject() - .put( - "camera", - ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == - PackageManager.PERMISSION_GRANTED, - ) - .put( - "microphone", - ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED, - ) - MobileMediaEvents.emit("tensamin-mobile-camera-permission", detail) - } - - private inner class MobileMediaJavascriptInterface { - @JavascriptInterface - fun startScreenShare(includeAudio: Boolean) { - this@MainActivity.startScreenShare(includeAudio) - } - - @JavascriptInterface - fun stopScreenShare() { - this@MainActivity.stopScreenShare() - } - - @JavascriptInterface - fun requestCameraPermission() { - this@MainActivity.requestCameraPermission() - } - } - private fun installKeyboardResizeWorkaround() { val content = window.decorView.findViewById(android.R.id.content) contentRoot = content @@ -232,20 +68,16 @@ class MainActivity : TauriActivity() { child: android.view.View, insets: WindowInsetsCompat? = ViewCompat.getRootWindowInsets(child), ) { + val visibleFrame = Rect() + child.getWindowVisibleDisplayFrame(visibleFrame) + val rootHeight = child.rootView.height if (rootHeight <= 0) return - val imeVisible = insets?.isVisible(WindowInsetsCompat.Type.ime()) == true - val imeHeight = if (imeVisible) { - insets.getInsets(WindowInsetsCompat.Type.ime()).bottom - } else { - 0 - } - val usableHeight = if (imeHeight in 1 until rootHeight) { - rootHeight - imeHeight - } else { - WindowManager.LayoutParams.MATCH_PARENT - } + val imeHeight = insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0 + val keyboardHeight = maxOf(imeHeight, rootHeight - visibleFrame.bottom) + val keyboardVisible = keyboardHeight > rootHeight * 0.15 + val usableHeight = if (keyboardVisible) rootHeight - keyboardHeight else ViewGroup.LayoutParams.MATCH_PARENT if (previousUsableHeight == usableHeight) return diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MediaProjectionService.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MediaProjectionService.kt deleted file mode 100644 index 3e961df..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MediaProjectionService.kt +++ /dev/null @@ -1,345 +0,0 @@ -package net.tensamin.client - -import android.Manifest -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.ServiceInfo -import android.graphics.Bitmap -import android.graphics.PixelFormat -import android.hardware.display.DisplayManager -import android.hardware.display.VirtualDisplay -import android.media.AudioAttributes -import android.media.AudioFormat -import android.media.AudioPlaybackCaptureConfiguration -import android.media.AudioRecord -import android.media.projection.MediaProjection -import android.media.projection.MediaProjectionManager -import android.media.ImageReader -import android.os.Build -import android.os.Handler -import android.os.HandlerThread -import android.os.IBinder -import android.util.Base64 -import android.util.DisplayMetrics -import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat -import java.io.ByteArrayOutputStream -import java.util.concurrent.atomic.AtomicBoolean -import org.json.JSONObject - -class MediaProjectionService : Service() { - private var projection: MediaProjection? = null - private var virtualDisplay: VirtualDisplay? = null - private var imageReader: ImageReader? = null - private var captureThread: HandlerThread? = null - private var audioRecord: AudioRecord? = null - private var audioThread: Thread? = null - private val captureActive = AtomicBoolean(false) - private var lastFrameAt = 0L - - private val projectionCallback = object : MediaProjection.Callback() { - override fun onStop() { - stopCapture(stopProjection = false, emitStopped = true) - stopSelf() - } - } - - override fun onBind(intent: Intent?): IBinder? = null - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (intent?.action == ACTION_STOP) { - stopCapture(stopProjection = true, emitStopped = true) - stopSelf() - return START_NOT_STICKY - } - - val permissionData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent?.getParcelableExtra(EXTRA_PERMISSION_DATA, Intent::class.java) - } else { - @Suppress("DEPRECATION") - intent?.getParcelableExtra(EXTRA_PERMISSION_DATA) - } - val resultCode = intent?.getIntExtra(EXTRA_RESULT_CODE, Int.MIN_VALUE) ?: Int.MIN_VALUE - if (permissionData == null || resultCode == Int.MIN_VALUE) { - MobileMediaEvents.emitError("Screen capture permission data is missing") - stopSelf() - return START_NOT_STICKY - } - - val includeAudio = intent?.getBooleanExtra(EXTRA_INCLUDE_AUDIO, false) ?: false - try { - startForegroundNotification() - startCapture(resultCode, permissionData, includeAudio) - } catch (error: Throwable) { - stopCapture(stopProjection = true, emitStopped = false) - stopForeground(STOP_FOREGROUND_REMOVE) - stopSelf() - MobileMediaEvents.emitError(error.message ?: "Unable to start screen capture") - } - return START_NOT_STICKY - } - - override fun onDestroy() { - stopCapture(stopProjection = true, emitStopped = true) - super.onDestroy() - } - - private fun startForegroundNotification() { - val notificationManager = getSystemService(NotificationManager::class.java) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - notificationManager.createNotificationChannel( - NotificationChannel( - NOTIFICATION_CHANNEL_ID, - "Screen sharing", - NotificationManager.IMPORTANCE_LOW, - ), - ) - } - - val stopIntent = Intent(this, MediaProjectionService::class.java).setAction(ACTION_STOP) - val stopPendingIntent = PendingIntent.getService( - this, - 0, - stopIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(android.R.drawable.ic_menu_share) - .setContentTitle("Tensamin is sharing your screen") - .setContentText("Tap Stop to end screen sharing") - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .addAction(android.R.drawable.ic_media_pause, "Stop", stopPendingIntent) - .build() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - startForeground( - NOTIFICATION_ID, - notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION, - ) - } else { - startForeground(NOTIFICATION_ID, notification) - } - } - - @Suppress("DEPRECATION") - private fun startCapture(resultCode: Int, permissionData: Intent, includeAudio: Boolean) { - stopCapture(stopProjection = true, emitStopped = false) - - val manager = getSystemService(MediaProjectionManager::class.java) - val newProjection = manager.getMediaProjection(resultCode, permissionData) - ?: error("Android did not provide a screen capture session") - projection = newProjection - - val thread = HandlerThread("tensamin-screen-capture").also { it.start() } - captureThread = thread - val handler = Handler(thread.looper) - newProjection.registerCallback(projectionCallback, handler) - - val metrics = DisplayMetrics() - getSystemService(android.view.WindowManager::class.java).defaultDisplay.getRealMetrics(metrics) - val displayWidth = metrics.widthPixels - val displayHeight = metrics.heightPixels - val width = minOf(displayWidth, MAX_FRAME_WIDTH) - val height = (displayHeight.toLong() * width / displayWidth).toInt() - val density = metrics.densityDpi - val reader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2) - imageReader = reader - reader.setOnImageAvailableListener({ source -> captureFrame(source, width, height) }, handler) - - virtualDisplay = newProjection.createVirtualDisplay( - "Tensamin screen sharing", - width, - height, - density, - DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, - reader.surface, - null, - handler, - ) - captureActive.set(true) - - val audioStarted = includeAudio && startAudioCapture(newProjection) - MobileMediaEvents.emit( - "tensamin-mobile-screen-started", - JSONObject().put("includeAudio", audioStarted), - ) - } - - private fun captureFrame(source: ImageReader, width: Int, height: Int) { - val image = source.acquireLatestImage() ?: return - try { - val now = System.currentTimeMillis() - if (!captureActive.get() || now - lastFrameAt < FRAME_INTERVAL_MS) return - lastFrameAt = now - - val plane = image.planes[0] - val paddedWidth = plane.rowStride / plane.pixelStride - val paddedBitmap = Bitmap.createBitmap(paddedWidth, height, Bitmap.Config.ARGB_8888) - paddedBitmap.copyPixelsFromBuffer(plane.buffer) - val croppedBitmap = if (paddedWidth == width) { - paddedBitmap - } else { - Bitmap.createBitmap(paddedBitmap, 0, 0, width, height).also { paddedBitmap.recycle() } - } - val outputBitmap = croppedBitmap - - val bytes = ByteArrayOutputStream() - outputBitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, bytes) - val outputWidth = outputBitmap.width - val outputHeight = outputBitmap.height - outputBitmap.recycle() - MobileMediaEvents.emit( - "tensamin-mobile-screen-frame", - JSONObject() - .put("data", Base64.encodeToString(bytes.toByteArray(), Base64.NO_WRAP)) - .put("mimeType", "image/jpeg") - .put("width", outputWidth) - .put("height", outputHeight), - ) - } catch (error: Throwable) { - if (captureActive.get()) { - MobileMediaEvents.emitError(error.message ?: "Unable to read a screen frame") - } - } finally { - image.close() - } - } - - private fun startAudioCapture(activeProjection: MediaProjection): Boolean { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - MobileMediaEvents.emitError("System audio capture requires Android 10 or newer") - return false - } - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != - PackageManager.PERMISSION_GRANTED - ) { - MobileMediaEvents.emitError("Microphone permission is required for system audio capture") - return false - } - - return try { - val format = AudioFormat.Builder() - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setSampleRate(AUDIO_SAMPLE_RATE) - .setChannelMask(AudioFormat.CHANNEL_IN_MONO) - .build() - val configuration = AudioPlaybackCaptureConfiguration.Builder(activeProjection) - .addMatchingUsage(AudioAttributes.USAGE_MEDIA) - .addMatchingUsage(AudioAttributes.USAGE_GAME) - .build() - val minimumBuffer = AudioRecord.getMinBufferSize( - AUDIO_SAMPLE_RATE, - AudioFormat.CHANNEL_IN_MONO, - AudioFormat.ENCODING_PCM_16BIT, - ) - check(minimumBuffer > 0) { "Android could not allocate a system audio buffer" } - val bufferSize = maxOf(minimumBuffer * 2, AUDIO_BATCH_BYTES) - val record = AudioRecord.Builder() - .setAudioFormat(format) - .setAudioPlaybackCaptureConfig(configuration) - .setBufferSizeInBytes(bufferSize) - .build() - check(record.state == AudioRecord.STATE_INITIALIZED) { - "Android could not initialize system audio capture" - } - audioRecord = record - record.startRecording() - audioThread = Thread({ readAudio(record) }, "tensamin-audio-capture").also { it.start() } - true - } catch (error: Throwable) { - audioRecord?.release() - audioRecord = null - MobileMediaEvents.emitError(error.message ?: "Unable to capture system audio") - false - } - } - - private fun readAudio(record: AudioRecord) { - val buffer = ByteArray(AUDIO_BATCH_BYTES) - while (captureActive.get() && !Thread.currentThread().isInterrupted) { - val read = record.read(buffer, 0, buffer.size, AudioRecord.READ_BLOCKING) - if (read > 0 && captureActive.get()) { - MobileMediaEvents.emit( - "tensamin-mobile-screen-audio", - JSONObject() - .put("data", Base64.encodeToString(buffer, 0, read, Base64.NO_WRAP)) - .put("sampleRate", AUDIO_SAMPLE_RATE) - .put("channelCount", 1) - .put("encoding", "pcm16le"), - ) - } else if (read < 0 && captureActive.get()) { - MobileMediaEvents.emitError("System audio capture stopped with code $read") - return - } - } - } - - @Synchronized - private fun stopCapture(stopProjection: Boolean, emitStopped: Boolean) { - val wasActive = captureActive.getAndSet(false) - - val record = audioRecord - audioRecord = null - try { - record?.stop() - } catch (_: IllegalStateException) { - } - record?.release() - audioThread?.interrupt() - audioThread = null - - imageReader?.setOnImageAvailableListener(null, null) - virtualDisplay?.release() - virtualDisplay = null - imageReader?.close() - imageReader = null - - val oldProjection = projection - projection = null - oldProjection?.unregisterCallback(projectionCallback) - if (stopProjection) oldProjection?.stop() - - captureThread?.quitSafely() - captureThread = null - lastFrameAt = 0L - - if (wasActive && emitStopped) { - MobileMediaEvents.emit("tensamin-mobile-screen-stopped") - } - } - - companion object { - private const val ACTION_STOP = "net.tensamin.client.STOP_SCREEN_SHARE" - private const val EXTRA_RESULT_CODE = "resultCode" - private const val EXTRA_PERMISSION_DATA = "permissionData" - private const val EXTRA_INCLUDE_AUDIO = "includeAudio" - private const val NOTIFICATION_CHANNEL_ID = "screen-sharing" - private const val NOTIFICATION_ID = 7314 - private const val FRAME_INTERVAL_MS = 75L - private const val MAX_FRAME_WIDTH = 1280 - private const val JPEG_QUALITY = 72 - private const val AUDIO_SAMPLE_RATE = 48_000 - private const val AUDIO_BATCH_BYTES = 9_600 - - fun start(context: Context, resultCode: Int, data: Intent, includeAudio: Boolean) { - val intent = Intent(context, MediaProjectionService::class.java) - .putExtra(EXTRA_RESULT_CODE, resultCode) - .putExtra(EXTRA_PERMISSION_DATA, data) - .putExtra(EXTRA_INCLUDE_AUDIO, includeAudio) - ContextCompat.startForegroundService(context, intent) - } - - fun stop(context: Context) { - context.startService( - Intent(context, MediaProjectionService::class.java).setAction(ACTION_STOP), - ) - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MobileMediaEvents.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MobileMediaEvents.kt deleted file mode 100644 index c8c377c..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MobileMediaEvents.kt +++ /dev/null @@ -1,29 +0,0 @@ -package net.tensamin.client - -import android.webkit.WebView -import java.lang.ref.WeakReference -import org.json.JSONObject - -object MobileMediaEvents { - private var webView = WeakReference(null) - - fun attach(value: WebView) { - webView = WeakReference(value) - } - - fun detach() { - webView.clear() - } - - fun emitError(message: String) { - emit("tensamin-mobile-screen-error", JSONObject().put("message", message)) - } - - fun emit(name: String, detail: JSONObject = JSONObject()) { - val view = webView.get() ?: return - val script = "window.dispatchEvent(new CustomEvent(" + - JSONObject.quote(name) + - ", { detail: " + detail.toString() + " }));" - view.post { view.evaluateJavascript(script, null) } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpBootReceiver.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpBootReceiver.kt deleted file mode 100644 index b96ae35..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpBootReceiver.kt +++ /dev/null @@ -1,17 +0,0 @@ -package net.tensamin.client - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent - -class MtpBootReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if ( - intent.action == Intent.ACTION_BOOT_COMPLETED && - MtpSecureStore.isEnabled(context) && - MtpSecureStore.hasConfig(context) - ) { - NativeMtpBridge.startService(context) - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpForegroundService.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpForegroundService.kt deleted file mode 100644 index 56cca97..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpForegroundService.kt +++ /dev/null @@ -1,167 +0,0 @@ -package net.tensamin.client - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.pm.ServiceInfo -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.os.Build -import android.os.IBinder -import android.os.Process -import android.os.SystemClock -import androidx.core.app.NotificationCompat - -class MtpForegroundService : Service() { - private var started = false - private val networkCallback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) = refreshNotification() - override fun onLost(network: Network) = refreshNotification() - override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) = - refreshNotification() - } - - override fun onBind(intent: Intent?): IBinder? = null - - override fun onCreate() { - super.onCreate() - getSystemService(ConnectivityManager::class.java) - .registerDefaultNetworkCallback(networkCallback) - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (intent?.action == ACTION_STOP) { - MtpSecureStore.setEnabled(this, false) - stopForeground(STOP_FOREGROUND_REMOVE) - stopSelf() - return START_NOT_STICKY - } - - if (started) return START_STICKY - - createChannel(this) - connectionStatus = "Connecting" - val notification = buildNotification(this, displayedStatus(this)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - startForeground( - NOTIFICATION_ID, - notification, - ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE, - ) - } else { - startForeground(NOTIFICATION_ID, notification) - } - val config = MtpSecureStore.loadConfig(this) - if (config == null || !MtpSecureStore.isEnabled(this)) { - stopForeground(STOP_FOREGROUND_REMOVE) - stopSelf() - return START_NOT_STICKY - } - - try { - NativeMtpBridge.nativeAttach(applicationContext) - NativeMtpBridge.nativeStart(config) - started = true - NativeMtpBridge.log(2, "Started MTP foreground service") - } catch (error: Throwable) { - NativeMtpBridge.log(0, "Failed to start MTP foreground service", error) - updateNotification(this, "Connection failed") - } - return START_STICKY - } - - override fun onTaskRemoved(rootIntent: Intent?) { - val preferences = getSharedPreferences(SERVICE_PREFERENCES, Context.MODE_PRIVATE) - val now = SystemClock.elapsedRealtime() - if (now - preferences.getLong(LAST_TASK_RESTART, 0) < TASK_RESTART_COOLDOWN_MS) { - super.onTaskRemoved(rootIntent) - return - } - preferences.edit().putLong(LAST_TASK_RESTART, now).commit() - if (MtpSecureStore.isEnabled(this) && MtpSecureStore.hasConfig(this)) { - startService(Intent(this, MtpForegroundService::class.java)) - } - super.onTaskRemoved(rootIntent) - // Tauri cannot recreate its WebView after the UI task is removed while this process survives. - Process.killProcess(Process.myPid()) - } - - override fun onDestroy() { - getSystemService(ConnectivityManager::class.java).unregisterNetworkCallback(networkCallback) - if (!MtpSecureStore.isEnabled(this)) NativeMtpBridge.nativeStop() - super.onDestroy() - } - - private fun refreshNotification() { - updateNotification(this, connectionStatus) - } - - companion object { - private const val CHANNEL_ID = "tensamin-connection" - private const val NOTIFICATION_ID = 2201 - private const val ACTION_STOP = "net.tensamin.client.STOP_MTP" - private const val SERVICE_PREFERENCES = "tensamin-service" - private const val LAST_TASK_RESTART = "last-task-restart" - private const val TASK_RESTART_COOLDOWN_MS = 15_000L - @Volatile private var connectionStatus = "Connecting" - - fun updateNotification(context: Context, status: String) { - if (!MtpSecureStore.isEnabled(context)) return - connectionStatus = status - createChannel(context) - context.getSystemService(NotificationManager::class.java) - .notify(NOTIFICATION_ID, buildNotification(context, displayedStatus(context))) - } - - private fun displayedStatus(context: Context): String { - val connectivity = context.getSystemService(ConnectivityManager::class.java) - val network = connectivity.activeNetwork ?: return "No network" - val capabilities = connectivity.getNetworkCapabilities(network) ?: return "No network" - return if ( - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - ) connectionStatus else "No network" - } - - private fun createChannel(context: Context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return - context.getSystemService(NotificationManager::class.java).createNotificationChannel( - NotificationChannel( - CHANNEL_ID, - "Background connection", - NotificationManager.IMPORTANCE_LOW, - ).apply { description = "Keeps Tensamin connected for incoming messages" }, - ) - } - - private fun buildNotification(context: Context, status: String): Notification { - val openIntent = PendingIntent.getActivity( - context, - 0, - Intent(context, MainActivity::class.java), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - val stopIntent = PendingIntent.getService( - context, - 1, - Intent(context, MtpForegroundService::class.java).setAction(ACTION_STOP), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - return NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(android.R.drawable.stat_notify_sync) - .setContentTitle("Tensamin") - .setContentText(status) - .setContentIntent(openIntent) - .setOngoing(true) - .setCategory(Notification.CATEGORY_SERVICE) - .setPriority(NotificationCompat.PRIORITY_LOW) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopIntent) - .build() - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpSecureStore.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpSecureStore.kt deleted file mode 100644 index bb03688..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/MtpSecureStore.kt +++ /dev/null @@ -1,74 +0,0 @@ -package net.tensamin.client - -import android.content.Context -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties -import android.util.Base64 -import java.security.KeyStore -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.SecretKey -import javax.crypto.spec.GCMParameterSpec - -object MtpSecureStore { - private const val KEY_ALIAS = "tensamin-mtp-config" - private const val PREFS = "tensamin-mtp" - private const val CONFIG = "config" - private const val ENABLED = "enabled" - - fun saveConfig(context: Context, config: String) { - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey()) - val encrypted = cipher.doFinal(config.toByteArray(Charsets.UTF_8)) - val payload = Base64.encodeToString(cipher.iv + encrypted, Base64.NO_WRAP) - preferences(context).edit().putString(CONFIG, payload).apply() - } - - fun loadConfig(context: Context): String? { - val payload = preferences(context).getString(CONFIG, null) ?: return null - return runCatching { - val bytes = Base64.decode(payload, Base64.NO_WRAP) - require(bytes.size > 12) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init( - Cipher.DECRYPT_MODE, - getOrCreateKey(), - GCMParameterSpec(128, bytes.copyOfRange(0, 12)), - ) - String(cipher.doFinal(bytes.copyOfRange(12, bytes.size)), Charsets.UTF_8) - }.getOrNull() - } - - fun hasConfig(context: Context): Boolean = loadConfig(context) != null - - fun setEnabled(context: Context, enabled: Boolean) { - preferences(context).edit().putBoolean(ENABLED, enabled).apply() - } - - fun isEnabled(context: Context): Boolean = - preferences(context).getBoolean(ENABLED, false) - - private fun preferences(context: Context) = - context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) - - private fun getOrCreateKey(): SecretKey { - val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } - (keyStore.getKey(KEY_ALIAS, null) as? SecretKey)?.let { return it } - - val generator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, - "AndroidKeyStore", - ) - generator.init( - KeyGenParameterSpec.Builder( - KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT, - ) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .setKeySize(256) - .build(), - ) - return generator.generateKey() - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeAccessibilityBridge.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeAccessibilityBridge.kt deleted file mode 100644 index e04365a..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeAccessibilityBridge.kt +++ /dev/null @@ -1,51 +0,0 @@ -package net.tensamin.client - -import android.content.Context -import android.webkit.WebView -import androidx.annotation.Keep -import java.lang.ref.WeakReference - -@Keep -object NativeAccessibilityBridge { - const val DEFAULT_INITIAL_SCALE = 290 - private const val MIN_INITIAL_SCALE = 210 - private const val MAX_INITIAL_SCALE = 500 - private const val PREFERENCES = "tensamin-accessibility" - private const val INITIAL_SCALE = "initial-scale" - - private var webView = WeakReference(null) - - init { - System.loadLibrary("mobile_lib") - } - - @JvmStatic external fun nativeAttach(context: Context) - - fun attach(webView: WebView) { - this.webView = WeakReference(webView) - webView.setInitialScale(getInitialScale(webView.context)) - } - - fun detach(webView: WebView) { - if (this.webView.get() === webView) this.webView.clear() - } - - fun getInitialScale(context: Context): Int { - val preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) - val storedScale = preferences.getInt(INITIAL_SCALE, DEFAULT_INITIAL_SCALE) - val scale = storedScale.coerceIn(MIN_INITIAL_SCALE, MAX_INITIAL_SCALE) - if (scale != storedScale) preferences.edit().putInt(INITIAL_SCALE, scale).apply() - return scale - } - - fun setInitialScale(context: Context, initialScale: Int) { - val nextScale = initialScale.coerceIn(MIN_INITIAL_SCALE, MAX_INITIAL_SCALE) - context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE) - .edit() - .putInt(INITIAL_SCALE, nextScale) - .apply() - webView.get()?.let { currentWebView -> - currentWebView.post { currentWebView.setInitialScale(nextScale) } - } - } -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeMtpBridge.kt b/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeMtpBridge.kt deleted file mode 100644 index ba9422d..0000000 --- a/apps/tauri/src-tauri/gen/android/app/src/main/java/net/tensamin/client/NativeMtpBridge.kt +++ /dev/null @@ -1,161 +0,0 @@ -package net.tensamin.client - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import android.net.Uri -import android.os.Build -import android.os.PowerManager -import android.provider.Settings -import android.util.Log -import androidx.annotation.Keep -import androidx.core.app.NotificationCompat -import androidx.core.app.Person -import androidx.core.content.LocusIdCompat -import androidx.core.content.ContextCompat -import androidx.core.content.pm.ShortcutInfoCompat -import androidx.core.content.pm.ShortcutManagerCompat -import androidx.core.graphics.drawable.IconCompat - -@Keep -object NativeMtpBridge { - private const val MESSAGE_CHANNEL = "tensamin-messages" - - init { - System.loadLibrary("mobile_lib") - } - - @JvmStatic external fun nativeAttach(context: Context) - @JvmStatic external fun nativeStart(config: String) - @JvmStatic external fun nativeStop() - @JvmStatic external fun nativeSetUiState(visible: Boolean) - @JvmStatic external fun nativeLog(level: Int, message: String, details: String) - - fun log(level: Int, message: String, error: Throwable? = null) { - val details = error?.stackTraceToString().orEmpty() - Log.println(if (level == 0) Log.ERROR else Log.INFO, "TensaminAndroid", "$message $details") - nativeLog(level, message, details) - } - - fun storeConfig(context: Context, config: String) { - try { - MtpSecureStore.saveConfig(context, config) - if (MtpSecureStore.isEnabled(context)) startService(context) - log(2, "Stored native MTP credentials") - } catch (error: Throwable) { - log(0, "Failed to store native MTP credentials", error) - throw error - } - } - - fun hasConfig(context: Context): Boolean = MtpSecureStore.hasConfig(context) - - fun setServiceEnabled(context: Context, enabled: Boolean) { - MtpSecureStore.setEnabled(context, enabled) - if (enabled && MtpSecureStore.hasConfig(context)) startService(context) else stopService(context) - } - - fun isIgnoringBatteryOptimizations(context: Context): Boolean = - context.getSystemService(PowerManager::class.java) - .isIgnoringBatteryOptimizations(context.packageName) - - fun requestBatteryExemption(context: Context) { - val intent = Intent( - Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, - Uri.parse("package:${context.packageName}"), - ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) - } - - fun startService(context: Context) { - ContextCompat.startForegroundService( - context, - Intent(context, MtpForegroundService::class.java), - ) - } - - fun stopService(context: Context) { - if (!context.stopService(Intent(context, MtpForegroundService::class.java))) nativeStop() - } - - fun updateServiceStatus(context: Context, status: String) { - MtpForegroundService.updateNotification(context, status) - } - - fun postMessageNotification( - context: Context, - senderId: Long, - sender: String, - body: String, - avatar: ByteArray, - ) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.getSystemService(NotificationManager::class.java).createNotificationChannel( - NotificationChannel( - MESSAGE_CHANNEL, - "Messages", - NotificationManager.IMPORTANCE_HIGH, - ).apply { description = "Incoming Tensamin messages" }, - ) - } - val openIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse("tensamin://chat?id=$senderId"), - context, - MainActivity::class.java, - ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) - val pendingIntent = PendingIntent.getActivity( - context, - senderId.hashCode(), - openIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, - ) - val avatarBitmap = avatar.takeIf { it.isNotEmpty() }?.let { - BitmapFactory.decodeByteArray(it, 0, it.size) - } - val avatarIcon = avatarBitmap?.let(IconCompat::createWithAdaptiveBitmap) - val person = Person.Builder() - .setName(sender) - .setKey(senderId.toString()) - .setIcon(avatarIcon) - .build() - val shortcutId = "chat-$senderId" - val shortcut = ShortcutInfoCompat.Builder(context, shortcutId) - .setShortLabel(sender) - .setLongLived(true) - .setPerson(person) - .setIntent(openIntent) - .apply { if (avatarIcon != null) setIcon(avatarIcon) } - .build() - ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) - - val style = NotificationCompat.MessagingStyle( - Person.Builder().setName("You").build(), - ).addMessage(body, System.currentTimeMillis(), person) - val notification = NotificationCompat.Builder(context, MESSAGE_CHANNEL) - .setSmallIcon(R.drawable.ic_notification_small) - .setContentTitle(sender) - .setContentText(body) - .setStyle(style) - .setShortcutId(shortcutId) - .setLocusId(LocusIdCompat(shortcutId)) - .setLargeIcon(avatarBitmap) - .setCategory(Notification.CATEGORY_MESSAGE) - .setAutoCancel(true) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .build() - context.getSystemService(NotificationManager::class.java) - .notify(senderId.hashCode(), notification) - } - - fun cancelMessageNotification(context: Context, senderId: Long) { - context.getSystemService(NotificationManager::class.java) - .cancel(senderId.hashCode()) - } - -} diff --git a/apps/tauri/src-tauri/gen/android/app/src/main/res/drawable/ic_notification_small.png b/apps/tauri/src-tauri/gen/android/app/src/main/res/drawable/ic_notification_small.png deleted file mode 100644 index bec055d..0000000 Binary files a/apps/tauri/src-tauri/gen/android/app/src/main/res/drawable/ic_notification_small.png and /dev/null differ diff --git a/apps/tauri/src-tauri/gen/android/build.gradle.kts b/apps/tauri/src-tauri/gen/android/build.gradle.kts index 1488b27..dddb93c 100644 --- a/apps/tauri/src-tauri/gen/android/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/build.gradle.kts @@ -1,4 +1,4 @@ -import com.android.build.api.dsl.LibraryExtension +import com.android.build.gradle.LibraryExtension buildscript { repositories { @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath("com.android.tools.build:gradle:8.11.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") } } diff --git a/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts b/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts index 09776c6..5c55bba 100644 --- a/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts +++ b/apps/tauri/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -20,3 +20,4 @@ dependencies { compileOnly(gradleApi()) implementation("com.android.tools.build:gradle:8.11.0") } + diff --git a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt index e2be2d1..a7e39eb 100644 --- a/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt +++ b/apps/tauri/src-tauri/gen/android/buildSrc/src/main/java/net/tensamin/client/kotlin/BuildTask.kt @@ -5,12 +5,8 @@ import org.gradle.api.GradleException import org.gradle.api.logging.LogLevel import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction -import org.gradle.process.ExecOperations -import javax.inject.Inject -open class BuildTask @Inject constructor( - private val execOperations: ExecOperations, -) : DefaultTask() { +open class BuildTask : DefaultTask() { @Input var rootDirRel: String? = null @Input @@ -54,7 +50,7 @@ open class BuildTask @Inject constructor( val release = release ?: throw GradleException("release cannot be null") val args = listOf("tauri", "android", "android-studio-script"); - execOperations.exec { + project.exec { workingDir(File(project.projectDir, rootDirRel)) executable(executable) args(args) @@ -69,4 +65,4 @@ open class BuildTask @Inject constructor( args(listOf("--target", target)) }.assertNormalExitValue() } -} +} \ No newline at end of file diff --git a/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties index 8e0bb4b..c5f9a53 100644 --- a/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/tauri/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue May 10 19:22:52 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/tauri/src-tauri/icons/icon.icns b/apps/tauri/src-tauri/icons/icon.icns index a7f0a1e..7f3b591 100644 Binary files a/apps/tauri/src-tauri/icons/icon.icns and b/apps/tauri/src-tauri/icons/icon.icns differ diff --git a/apps/tauri/src-tauri/src/accessibility_backend.rs b/apps/tauri/src-tauri/src/accessibility_backend.rs deleted file mode 100644 index 692b42e..0000000 --- a/apps/tauri/src-tauri/src/accessibility_backend.rs +++ /dev/null @@ -1,135 +0,0 @@ -#[cfg(not(target_os = "android"))] -const DEFAULT_INITIAL_SCALE: i32 = 290; -const MIN_INITIAL_SCALE: i32 = 210; -const MAX_INITIAL_SCALE: i32 = 500; - -#[tauri::command] -pub fn accessibility_get_initial_scale() -> Result { - android_get_initial_scale() -} - -#[tauri::command] -pub fn accessibility_set_initial_scale(initial_scale: i32) -> Result<(), String> { - if !(MIN_INITIAL_SCALE..=MAX_INITIAL_SCALE).contains(&initial_scale) { - return Err(format!( - "Initial scale must be between {MIN_INITIAL_SCALE} and {MAX_INITIAL_SCALE}" - )); - } - - android_set_initial_scale(initial_scale) -} - -#[cfg(not(target_os = "android"))] -fn android_get_initial_scale() -> Result { - Ok(DEFAULT_INITIAL_SCALE) -} - -#[cfg(not(target_os = "android"))] -fn android_set_initial_scale(_: i32) -> Result<(), String> { - Ok(()) -} - -#[cfg(target_os = "android")] -mod android { - use std::sync::OnceLock; - - use jni::{ - jni_sig, jni_str, - objects::{Global, JClass, JObject, JValue}, - Env, EnvUnowned, JavaVM, - }; - - struct Host { - vm: JavaVM, - context: Global>, - bridge: Global>, - } - - static HOST: OnceLock = OnceLock::new(); - - fn attach(env: &mut Env, context: JObject) -> Result<(), String> { - if HOST.get().is_some() { - return Ok(()); - } - - let class = env - .find_class(jni_str!("net/tensamin/client/NativeAccessibilityBridge")) - .map_err(|error| error.to_string())?; - let bridge = env - .get_static_field( - class, - jni_str!("INSTANCE"), - jni_sig!("Lnet/tensamin/client/NativeAccessibilityBridge;"), - ) - .and_then(|value| value.l()) - .map_err(|error| error.to_string())?; - - HOST.set(Host { - vm: env.get_java_vm().map_err(|error| error.to_string())?, - context: env - .new_global_ref(context) - .map_err(|error| error.to_string())?, - bridge: env - .new_global_ref(bridge) - .map_err(|error| error.to_string())?, - }) - .map_err(|_| "Android accessibility host is already attached".to_string()) - } - - fn with_env(call: impl FnOnce(&mut Env, &Host) -> Result) -> Result { - let host = HOST - .get() - .ok_or("Android accessibility host is not attached")?; - host.vm - .attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host))) - .map_err(|error| error.to_string())? - } - - pub fn get_initial_scale() -> Result { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("getInitialScale"), - jni_sig!("(Landroid/content/Context;)I"), - &[JValue::Object(host.context.as_obj())], - ) - .and_then(|value| value.i()) - .map_err(|error| error.to_string()) - }) - } - - pub fn set_initial_scale(initial_scale: i32) -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("setInitialScale"), - jni_sig!("(Landroid/content/Context;I)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Int(initial_scale), - ], - ) - .map_err(|error| error.to_string())?; - Ok(()) - }) - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeAccessibilityBridge_nativeAttach< - 'caller, - >( - mut env: EnvUnowned<'caller>, - _class: JClass, - context: JObject<'caller>, - ) { - let _ = env.with_env(|env| { - let _ = attach(env, context); - Ok::<_, jni::errors::Error>(()) - }); - } -} - -#[cfg(target_os = "android")] -use android::{ - get_initial_scale as android_get_initial_scale, set_initial_scale as android_set_initial_scale, -}; diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 6636682..2a51174 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -1,37 +1,21 @@ -mod accessibility_backend; -mod mtp_backend; - #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = tauri::Builder::default() - .plugin( - tauri_plugin_log::Builder::new() - .level(tauri_plugin_log::log::LevelFilter::Info) - .build(), - ) + .plugin(tauri_plugin_log::Builder::new().level(tauri_plugin_log::log::LevelFilter::Info).build()) .plugin(tauri_plugin_notification::init()); let builder = builder .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_opener::init()); - let app = builder - .invoke_handler(tauri::generate_handler![ - accessibility_backend::accessibility_get_initial_scale, - accessibility_backend::accessibility_set_initial_scale, - mtp_backend::mtp_request, - mtp_backend::mtp_status, - mtp_backend::mtp_store_credentials, - mtp_backend::mtp_has_credentials, - mtp_backend::mtp_load_keyring, - mtp_backend::mtp_set_enabled, - mtp_backend::mtp_set_ui_visible, - mtp_backend::mtp_post_message_notification, - mtp_backend::mtp_is_ignoring_battery_optimizations, - mtp_backend::mtp_request_battery_exemption, - ]) + #[cfg(any(target_os = "ios", target_os = "android"))] + let builder = builder.plugin(tauri_plugin_barcode_scanner::init()); + + #[cfg(any(target_os = "ios", target_os = "android"))] + let builder = builder.plugin(tauri_plugin_app_events::init()); + + if let Err(error) = builder .setup(|_app| { - mtp_backend::manager().attach_app(_app.handle().clone()); #[cfg(any(target_os = "linux", windows))] { use tauri_plugin_deep_link::DeepLinkExt; @@ -46,18 +30,9 @@ pub fn run() { } Ok(()) }) - .build(tauri::generate_context!()) - .expect("error while building tauri application"); - - app.run(|_app, event| { - #[cfg(target_os = "android")] - if let tauri::RunEvent::ExitRequested { - api, code: None, .. - } = event - { - if mtp_backend::manager().is_enabled() { - api.prevent_exit(); - } - } - }); + .run(tauri::generate_context!()) + { + eprintln!("error while running tauri application: {error}"); + panic!("error while running tauri application: {error}"); + } } diff --git a/apps/tauri/src-tauri/src/main.rs b/apps/tauri/src-tauri/src/main.rs index 272583a..e78a26a 100644 --- a/apps/tauri/src-tauri/src/main.rs +++ b/apps/tauri/src-tauri/src/main.rs @@ -1,6 +1,9 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use lib::log; + fn main() { mobile_lib::run(); + log("Test test 123") } diff --git a/apps/tauri/src-tauri/src/mtp_backend.rs b/apps/tauri/src-tauri/src/mtp_backend.rs deleted file mode 100644 index fa28597..0000000 --- a/apps/tauri/src-tauri/src/mtp_backend.rs +++ /dev/null @@ -1,1563 +0,0 @@ -use std::sync::{ - atomic::{AtomicBool, AtomicU64, Ordering}, - Arc, Mutex, OnceLock, RwLock, -}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use base64::{ - engine::general_purpose::{STANDARD, STANDARD_NO_PAD}, - Engine as _, -}; -use mtp::client::{ClientConfig, MTPClient, MTPConnection, Policy, SendMode}; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue, TypeMap}; -use mtp::crypto::{ - derive_encryption_key, AeadDecrypt, ChaCha20Poly1305, HybridKem, Keyring, PublicKeyBundle, -}; -use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; -use tauri::{AppHandle, Emitter}; -use tokio::sync::mpsc; - -const EVENT_NAME: &str = "mtp://event"; -const DISCONNECTED: u8 = 0; -const CONNECTING: u8 = 1; -const CONNECTED: u8 = 2; -const CHAT_SECRET_SALT: &[u8] = b"tensamin-chat-secret-v1"; -const CHAT_MESSAGE_SALT: &[u8] = b"tensamin-chat-message-v1"; -const CHAT_SECRET_SCHEME: &str = "mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1"; -const INITIAL_SYNC_TIMEOUT: Duration = Duration::from_secs(30); -const MAX_BUFFERED_INITIAL_FRAMES: usize = 1_000; -const NOTIFICATION_QUEUE_CAPACITY: usize = 32; -#[cfg(target_os = "android")] -const ROOT_YE_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\n\ -MIIB2TCCAWCgAwIBAgIRAKQCa6LvbHwg1AR+XmWmk4AwCgYIKoZIzj0EAwMwLjEL\n\ -MAkGA1UEBhMCVVMxDTALBgNVBAoTBElTUkcxEDAOBgNVBAMTB1Jvb3QgWUUwHhcN\n\ -MjUwOTAzMDAwMDAwWhcNNDUwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsG\n\ -A1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTB2MBAGByqGSM49AgEGBSuBBAAi\n\ -A2IABDwS/6vhrcVqcbBo+wgdI3fwn9x7DNJJOY/lTOti0vkwuRN87RhEhTH17E7X\n\ -yFjWsPYhIPt/wzOqxTd2b+4ZJNy9ID04YywF9U5zasDVyGSNErVNtz8uSGh5izW8\n\ -7j77GaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\n\ -BBYEFKPIJlqOoUzQNWP8myPIOq5W809WMAoGCCqGSM49BAMDA2cAMGQCMHhMr8N9\n\ -LdL1VQKs9BdV81r76eXRB6mtjuNjzk6/lBsPNToWLTDzGYgtQKO1jl63uAIwGV7m\n\ -onyF377c+MM1oqVNs17sgu7F9YKZwgLmVbeOMDbKAXHtKMDLbiGllCcs8f47\n\ ------END CERTIFICATE-----\n"; -#[cfg(target_os = "android")] -const YE1_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\n\ -MIICizCCAhGgAwIBAgIQXd1w3TH4AchcGGp6BLgK/jAKBggqhkjOPQQDAzAuMQsw\n\ -CQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZRTAeFw0y\n\ -NTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYD\n\ -VQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZRTEwdjAQBgcqhkjOPQIBBgUr\n\ -gQQAIgNiAAQHZVB1/mimla2hfSurylScjPMZaOJXLz/NnAc2sylm8WDyhU9Ccp+z\n\ -ASQi5vSwGGJjSGklkD9fdPR8GpyDIOIjCEfrnbt/v+ZSEPLLEGbaM6EccDbN7p9x\n\ -teIm2Avf+ryjge4wgeswDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF\n\ -BwMBMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLsgykcL/tflnPmPCSqj\n\ -jDdFsbzYMB8GA1UdIwQYMBaAFKPIJlqOoUzQNWP8myPIOq5W809WMDIGCCsGAQUF\n\ -BwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3llLmkubGVuY3Iub3JnLzATBgNV\n\ -HSAEDDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veWUuYy5s\n\ -ZW5jci5vcmcvMAoGCCqGSM49BAMDA2gAMGUCMQDgjUEahFT/h3DRakqiPZpLvPgf\n\ -Zwkt6K2EOMmh1nvEzl83eMLYcod4GCl3b0J1Nn0CMBNYmEQJb4CEG5WoOe7aRn/L\n\ -VKu6saHmHEynI7ysIPd8zQsK1HdmhlHKlw9Z5GpGvA==\n\ ------END CERTIFICATE-----\n"; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MtpConfig { - pub user_id: u64, - pub keyring: String, - pub omega_url: String, - pub forced_omikron_url: Option, - pub forced_omikron_public_key: Option, -} - -#[derive(Clone, Debug, Default, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MtpSnapshot { - pub generation: u64, - pub ready_state: u8, - pub identified: bool, - pub state: Option, - pub error: Option, -} - -#[derive(Clone, Serialize)] -#[serde(tag = "kind", rename_all = "camelCase")] -enum MtpEvent { - State { - snapshot: MtpSnapshot, - }, - Message { - generation: u64, - message: Value, - }, - Log { - level: u8, - message: String, - details: Option, - }, -} - -pub struct MtpManager { - runtime: tokio::runtime::Runtime, - config: RwLock>, - connection: RwLock>>, - snapshot: RwLock, - generation: AtomicU64, - enabled: AtomicBool, - ui_visible: AtomicBool, - app: RwLock>, - start_lock: Mutex<()>, -} - -struct RequestIdAllocator { - next: AtomicU64, -} - -impl RequestIdAllocator { - fn new() -> Self { - Self { - next: AtomicU64::new(1), - } - } - - fn next(&self) -> Result { - let value = self.next.fetch_add(1, Ordering::Relaxed); - u32::try_from(value) - .map_err(|_| "MTP request ID space exhausted for this connection".to_string()) - } -} - -struct ManagedConnection { - mtp: Arc, - request_ids: RequestIdAllocator, -} - -impl ManagedConnection { - async fn next_request_id(&self) -> Result { - let id = self.request_ids.next(); - if id.is_err() { - self.mtp.sender.close().await; - } - id - } -} - -struct PreparedConnection { - connection: MTPConnection, - request_ids: RequestIdAllocator, - initial_state: Value, - buffered_frames: Vec, -} - -static MANAGER: OnceLock = OnceLock::new(); - -pub fn manager() -> &'static MtpManager { - MANAGER.get_or_init(|| MtpManager { - runtime: tokio::runtime::Builder::new_multi_thread() - .enable_all() - .thread_name("tensamin-mtp") - .build() - .expect("failed to create MTP runtime"), - config: RwLock::new(None), - connection: RwLock::new(None), - snapshot: RwLock::new(MtpSnapshot { - ready_state: DISCONNECTED, - ..MtpSnapshot::default() - }), - generation: AtomicU64::new(0), - enabled: AtomicBool::new(false), - ui_visible: AtomicBool::new(false), - app: RwLock::new(None), - start_lock: Mutex::new(()), - }) -} - -impl MtpManager { - pub fn attach_app(&self, app: AppHandle) { - *self.app.write().expect("app lock poisoned") = Some(app); - } - - pub fn configure_and_start(&'static self, config: MtpConfig) { - let _guard = self.start_lock.lock().expect("start lock poisoned"); - if self.enabled.load(Ordering::SeqCst) - && self.config.read().expect("config lock poisoned").as_ref() == Some(&config) - { - return; - } - *self.config.write().expect("config lock poisoned") = Some(config); - self.enabled.store(true, Ordering::SeqCst); - let generation = self.generation.fetch_add(1, Ordering::SeqCst) + 1; - if let Some(connection) = self - .connection - .write() - .expect("connection lock poisoned") - .take() - { - self.runtime - .spawn(async move { connection.mtp.sender.close().await }); - } - self.set_snapshot(MtpSnapshot { - generation, - ready_state: CONNECTING, - identified: false, - state: None, - error: None, - }); - self.runtime.spawn(supervise(generation)); - } - - pub fn stop(&self) { - self.enabled.store(false, Ordering::SeqCst); - self.generation.fetch_add(1, Ordering::SeqCst); - if let Some(connection) = self - .connection - .write() - .expect("connection lock poisoned") - .take() - { - self.runtime - .spawn(async move { connection.mtp.sender.close().await }); - } - self.set_snapshot(MtpSnapshot { - generation: self.generation.load(Ordering::SeqCst), - ready_state: DISCONNECTED, - identified: false, - state: None, - error: None, - }); - } - - pub fn set_ui_visible(&self, visible: bool) { - self.ui_visible.store(visible, Ordering::SeqCst); - } - - pub fn is_enabled(&self) -> bool { - self.enabled.load(Ordering::SeqCst) - } - - pub fn snapshot(&self) -> MtpSnapshot { - self.snapshot - .read() - .expect("snapshot lock poisoned") - .clone() - } - - fn set_snapshot(&self, snapshot: MtpSnapshot) { - if let Some(error) = snapshot.error.as_ref() { - eprintln!("android: MTP connection failed: {error}"); - } - *self.snapshot.write().expect("snapshot lock poisoned") = snapshot.clone(); - self.emit(MtpEvent::State { snapshot }); - } - - fn emit(&self, event: MtpEvent) { - if let Some(app) = self.app.read().expect("app lock poisoned").as_ref() { - let _ = app.emit(EVENT_NAME, event); - } - } - - fn log(&self, level: u8, message: impl Into, details: Option) { - let message = message.into(); - eprintln!( - "android: {message}{}", - details - .as_ref() - .map(|value| format!(": {value}")) - .unwrap_or_default() - ); - self.emit(MtpEvent::Log { - level, - message, - details, - }); - } - - fn is_current(&self, generation: u64) -> bool { - self.enabled.load(Ordering::SeqCst) && self.generation.load(Ordering::SeqCst) == generation - } - - async fn request(&self, type_name: &str, data: Value) -> Result { - let connection = self - .connection - .read() - .map_err(|_| "MTP connection lock is unavailable")? - .clone() - .ok_or_else(|| "MTP is not connected".to_string())?; - let request = json_to_frame(type_name, data, connection.next_request_id().await?)?; - let response = connection - .mtp - .request(&request, None) - .await - .map_err(|error| error.to_string())?; - frame_to_json(&response) - } -} - -async fn supervise(generation: u64) { - let manager = manager(); - let mut delay = Duration::from_secs(1); - while manager.is_current(generation) { - let config = manager.config.read().expect("config lock poisoned").clone(); - let Some(config) = config else { return }; - manager.log(2, "Starting native MTP connection", None); - android_status("Connecting"); - match connect(&config).await { - Ok(prepared) => { - delay = Duration::from_secs(1); - let connection = Arc::new(ManagedConnection { - mtp: Arc::new(prepared.connection), - request_ids: prepared.request_ids, - }); - let stale = { - let _guard = manager.start_lock.lock().expect("start lock poisoned"); - let mut current = manager - .connection - .write() - .expect("connection lock poisoned"); - if !manager.is_current(generation) { - true - } else { - *current = Some(connection.clone()); - drop(current); - manager.set_snapshot(MtpSnapshot { - generation, - ready_state: CONNECTED, - identified: true, - state: Some(prepared.initial_state), - error: None, - }); - false - } - }; - if stale { - connection.mtp.sender.close().await; - break; - } - android_status("Connected"); - manager.log(2, "Native MTP connection established", None); - - let (notification_tx, mut notification_rx) = - mpsc::channel(NOTIFICATION_QUEUE_CAPACITY); - let notification_connection = connection.clone(); - let notification_config = config.clone(); - let notification_worker = tokio::spawn(async move { - while let Some(frame) = notification_rx.recv().await { - if !manager.is_current(generation) { - break; - } - if let Err(error) = notify_message( - ¬ification_config, - notification_connection.clone(), - &frame, - ) - .await - { - eprintln!("failed to create background message notification: {error}"); - } - } - }); - - for frame in prepared.buffered_frames { - handle_push(generation, ¬ification_tx, frame).await; - } - - while manager.is_current(generation) { - match connection.mtp.receive().await { - Ok(frame) => handle_push(generation, ¬ification_tx, frame).await, - Err(error) => { - let _guard = manager.start_lock.lock().expect("start lock poisoned"); - if manager.is_current(generation) { - manager.set_snapshot(MtpSnapshot { - generation, - ready_state: DISCONNECTED, - identified: false, - state: None, - error: Some(error.to_string()), - }); - } - break; - } - } - } - drop(notification_tx); - notification_worker.abort(); - if let Err(error) = notification_worker.await { - if !error.is_cancelled() { - eprintln!("background notification worker failed: {error}"); - } - } - let mut current = manager - .connection - .write() - .expect("connection lock poisoned"); - if current - .as_ref() - .is_some_and(|stored| Arc::ptr_eq(stored, &connection)) - { - current.take(); - } - } - Err(error) => { - let _guard = manager.start_lock.lock().expect("start lock poisoned"); - if manager.is_current(generation) { - manager.set_snapshot(MtpSnapshot { - generation, - ready_state: DISCONNECTED, - identified: false, - state: None, - error: Some(error), - }); - } - } - } - - if !manager.is_current(generation) { - break; - } - android_status("Reconnecting"); - tokio::time::sleep(jittered_retry_delay(delay)).await; - delay = (delay * 2).min(Duration::from_secs(60)); - } -} - -fn jittered_retry_delay(delay: Duration) -> Duration { - let entropy = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.subsec_nanos()) - .unwrap_or_default(); - let percent = 80 + entropy % 41; - delay.mul_f64(percent as f64 / 100.0) -} - -async fn connect(config: &MtpConfig) -> Result { - let (url, public_key) = resolve_endpoint(config) - .await - .map_err(|error| format!("endpoint discovery failed: {error}"))?; - manager().log( - 2, - "Resolved Omikron endpoint", - Some(Value::String(url.clone())), - ); - let keyring_bytes = decode_browser_base64(&config.keyring) - .map_err(|error| format!("invalid MTP keyring: {error}"))?; - let keyring = Keyring::from_bytes(&keyring_bytes) - .map_err(|error| format!("invalid MTP keyring: {error}"))?; - let host_key_bytes = decode_sdk_bytes(&public_key) - .map_err(|error| format!("invalid Omikron public key: {error}"))?; - let host_key = PublicKeyBundle::from_bytes(&host_key_bytes) - .map_err(|error| format!("invalid Omikron public key: {error}"))?; - host_key - .validate() - .map_err(|error| format!("invalid Omikron public key: {error}"))?; - let client_config = ClientConfig::new(url) - .with_client_id(config.user_id) - .with_description("client") - .with_policy(Policy::default().with_send_mode(SendMode::SingleStreamPerMessage)) - .with_ping_interval(Duration::from_secs(30)) - .with_max_missed_pings(3); - #[cfg(target_os = "android")] - let client_config = client_config.with_pinned_pem(android_root_certificates().clone()); - let connection = tokio::time::timeout( - Duration::from_secs(30), - MTPClient::auth_connect(client_config, &keyring, &host_key), - ) - .await - .map_err(|_| "transport authentication timed out".to_string())? - .map_err(|error| format!("transport authentication failed: {error}"))?; - manager().log(2, "Native MTP authentication completed", None); - - let (state, buffered_frames) = await_initial_state(&connection).await?; - let request_ids = RequestIdAllocator::new(); - let (initial_state, ack) = prepare_initial_state_ack(&state, &request_ids)?; - let response = tokio::time::timeout(INITIAL_SYNC_TIMEOUT, connection.request(&ack, None)) - .await - .map_err(|_| "state acknowledgement timed out".to_string())? - .map_err(|error| format!("state acknowledgement failed: {error}"))?; - if response - .get_type_name() - .is_some_and(|name| name.starts_with("Error")) - { - return Err(format!("ClientStateAck failed: {response}")); - } - Ok(PreparedConnection { - connection, - request_ids, - initial_state, - buffered_frames, - }) -} - -fn prepare_initial_state_ack( - state: &CommunicationValue, - request_ids: &RequestIdAllocator, -) -> Result<(Value, CommunicationValue), String> { - let initial_state = frame_data_to_json(state)?; - validate_client_state_sync(&initial_state)?; - let data = initial_state - .as_object() - .ok_or("ClientStateSync payload is not an object")?; - let session_id = required_integer(data, "SessionId")?; - let version = required_integer(data, "VersionNumber")?; - let ack = CommunicationValue::new(communication_type("ClientStateAck")?) - .with_id(request_ids.next()?) - .add_typed_default(DataType::SessionId, number_to_data(session_id)) - .add_typed_default(DataType::VersionNumber, number_to_data(version)); - Ok((initial_state, ack)) -} - -fn validate_client_state_sync(state: &Value) -> Result<(), String> { - let data = state - .as_object() - .ok_or("ClientStateSync payload is not an object")?; - if required_integer(data, "SessionId")? <= 0 { - return Err("ClientStateSync SessionId must be a positive integer".into()); - } - for field in ["VersionNumber", "CacheSchemaVersion"] { - if required_integer(data, field)? < 0 { - return Err(format!("ClientStateSync {field} must be nonnegative")); - } - } - match data.get("SyncMode").and_then(Value::as_str) { - Some("full" | "delta") => {} - _ => return Err("ClientStateSync SyncMode must be 'full' or 'delta'".into()), - } - for field in ["Contacts", "Communities", "Calls", "Messages"] { - if !data.get(field).is_some_and(Value::is_array) { - return Err(format!("ClientStateSync {field} must be an array")); - } - } - for field in ["DeletedMessageIds", "DeletedContactIds"] { - if let Some(value) = data.get(field) { - let values = value - .as_array() - .ok_or_else(|| format!("ClientStateSync {field} must be an array"))?; - if values.iter().any(|value| !value.is_number()) { - return Err(format!("ClientStateSync {field} must contain numbers")); - } - } - } - validate_object_array(data, "Communities", |_| Ok(()))?; - validate_object_array(data, "Contacts", validate_contact)?; - validate_object_array(data, "Calls", validate_call)?; - validate_object_array(data, "Messages", validate_message)?; - Ok(()) -} - -fn validate_object_array( - data: &Map, - field: &str, - validate: impl Fn(&Map) -> Result<(), String>, -) -> Result<(), String> { - let values = data - .get(field) - .and_then(Value::as_array) - .ok_or_else(|| format!("ClientStateSync {field} must be an array"))?; - for value in values { - let object = value - .as_object() - .ok_or_else(|| format!("ClientStateSync {field} entries must be objects"))?; - validate(object)?; - } - Ok(()) -} - -fn validate_contact(contact: &Map) -> Result<(), String> { - if !contact.get("UserId").is_some_and(Value::is_number) { - return Err("ClientStateSync contact omitted numeric UserId".into()); - } - if let Some(messages) = contact.get("Messages") { - let messages = messages - .as_array() - .ok_or("ClientStateSync contact Messages must be an array")?; - for message in messages { - validate_message( - message - .as_object() - .ok_or("ClientStateSync contact message must be an object")?, - )?; - } - } - Ok(()) -} - -fn validate_call(call: &Map) -> Result<(), String> { - if !call.get("CallId").is_some_and(Value::is_string) { - return Err("ClientStateSync call omitted string CallId".into()); - } - let members = call - .get("CallMembers") - .and_then(Value::as_array) - .ok_or("ClientStateSync call omitted CallMembers array")?; - if members.iter().any(|member| !member.is_number()) { - return Err("ClientStateSync CallMembers must contain numbers".into()); - } - Ok(()) -} - -fn validate_message(message: &Map) -> Result<(), String> { - for field in ["SenderId", "SendTime"] { - if !message.get(field).is_some_and(Value::is_number) { - return Err(format!("ClientStateSync message omitted numeric {field}")); - } - } - let content = message - .get("Content") - .and_then(Value::as_str) - .ok_or("ClientStateSync message omitted string Content")?; - STANDARD - .decode(content) - .or_else(|_| STANDARD_NO_PAD.decode(content)) - .map_err(|_| "ClientStateSync message Content must be base64".to_string())?; - if let Some(state) = message.get("MessageState") { - match state.as_str() { - Some("read" | "received" | "sent" | "sending" | "awaiting") => {} - _ => return Err("ClientStateSync message has invalid MessageState".into()), - } - } - Ok(()) -} - -fn required_integer(data: &Map, field: &str) -> Result { - let value = data - .get(field) - .ok_or_else(|| format!("ClientStateSync omitted {field}"))?; - if let Some(value) = value.as_i64() { - return Ok(value as i128); - } - value - .as_u64() - .map(|value| value as i128) - .ok_or_else(|| format!("ClientStateSync {field} must be an integer")) -} - -async fn await_initial_state( - connection: &MTPConnection, -) -> Result<(CommunicationValue, Vec), String> { - let mut buffered = Vec::new(); - let deadline = tokio::time::Instant::now() + INITIAL_SYNC_TIMEOUT; - - loop { - let frame = tokio::time::timeout_at(deadline, connection.receive()) - .await - .map_err(|_| "initial state synchronization timed out".to_string())? - .map_err(|error| format!("initial state synchronization failed: {error}"))?; - - if frame.is_type(CommunicationType::ErrorNoIota) { - return Err("No Iota is currently connected".into()); - } - - if frame.get_type_name() == Some("ClientStateSync") { - return Ok((frame, buffered)); - } - - if buffered.len() == MAX_BUFFERED_INITIAL_FRAMES { - return Err("initial state synchronization buffered too many frames".into()); - } - buffered.push(frame); - } -} - -async fn resolve_endpoint(config: &MtpConfig) -> Result<(String, String), String> { - if let (Some(url), Some(key)) = ( - config.forced_omikron_url.as_ref(), - config.forced_omikron_public_key.as_ref(), - ) { - return Ok((url.clone(), key.clone())); - } - #[derive(Deserialize)] - struct Omikron { - ip_address: String, - port: u16, - public_key: String, - } - let root = config.omega_url.trim_end_matches('/'); - let client = reqwest::Client::builder() - .connect_timeout(Duration::from_secs(10)) - .timeout(Duration::from_secs(20)); - #[cfg(target_os = "android")] - let client = client.tls_certs_only( - reqwest::Certificate::from_pem_bundle(android_root_certificates()) - .map_err(|error| format!("invalid bundled root certificates: {error}"))?, - ); - let response = client - .build() - .map_err(|error| error.to_string())? - .get(format!("{root}/api/get/omikron/{}", config.user_id)) - .send() - .await - .map_err(|error| error.to_string())?; - if !response.status().is_success() { - return Err(format!("Omikron lookup failed: {}", response.status())); - } - let data: Omikron = response.json().await.map_err(|error| error.to_string())?; - Ok(( - format!("https://{}:{}", data.ip_address, data.port), - data.public_key, - )) -} - -async fn handle_push( - generation: u64, - notification_tx: &mpsc::Sender, - frame: CommunicationValue, -) { - let manager = manager(); - if let Ok(message) = frame_to_json(&frame) { - manager.emit(MtpEvent::Message { - generation, - message, - }); - } - if frame.is_type(CommunicationType::MessageState) - && frame.get_str(DataType::MessageState) == Some("read") - { - if let Some(partner_id) = frame - .get_data(DataType::ChatPartnerId) - .and_then(DataValue::as_number) - .and_then(|value| u64::try_from(value).ok()) - { - if let Err(error) = android_cancel_notification(partner_id) { - eprintln!("failed to clear read message notification: {error}"); - } - } - } - if frame.is_type(CommunicationType::MessageLive) && !manager.ui_visible.load(Ordering::SeqCst) { - if notification_tx.try_send(frame).is_err() { - eprintln!("background message notification queue is full"); - } - } -} - -async fn notify_message( - config: &MtpConfig, - connection: Arc, - frame: &CommunicationValue, -) -> Result<(), String> { - let sender_id = frame - .get_data(DataType::SenderId) - .and_then(DataValue::as_number) - .and_then(|value| u64::try_from(value).ok()) - .ok_or("MessageLive omitted SenderId")?; - let message = frame - .get_data(DataType::Message) - .ok_or("MessageLive omitted Message")?; - let content = container_value_by_name(message, "Content") - .and_then(DataValue::as_str) - .ok_or("MessageLive omitted Content")?; - let keyring_bytes = decode_browser_base64(&config.keyring)?; - let keyring = Keyring::from_bytes(&keyring_bytes).map_err(|error| error.to_string())?; - let chat_id = derive_chat_id(config.user_id, sender_id); - let secret_id = format!("chat:{chat_id}:main"); - let secret_request = CommunicationValue::new(CommunicationType::GetChatSecret) - .with_id(connection.next_request_id().await?) - .add_typed_default(DataType::UserId, DataValue::Str(config.user_id.to_string())) - .add_typed_default(DataType::ChatId, DataValue::Str(chat_id.clone())) - .add_typed_default(DataType::SecretId, DataValue::Str(secret_id.clone())); - let secret = connection - .mtp - .request(&secret_request, None) - .await - .map_err(|error| error.to_string())?; - if !secret.is_type(CommunicationType::ChatSecretResponse) { - return Err(format!("GetChatSecret failed: {secret}")); - } - if secret.get_str(DataType::WrappingScheme) != Some(CHAT_SECRET_SCHEME) { - return Err("unsupported chat secret wrapping scheme".into()); - } - let version = secret - .get_data(DataType::VersionNumber) - .and_then(DataValue::as_number) - .ok_or("missing secret version")?; - let encrypted_secret = secret - .get_bytes(DataType::EncryptedSecret) - .ok_or("missing encrypted secret")?; - let kem_ciphertext = secret - .get_bytes(DataType::KemCiphertext) - .ok_or("missing KEM ciphertext")?; - let shared = HybridKem::decapsulate(&keyring.kem_secret_key, kem_ciphertext) - .map_err(|error| error.to_string())?; - let wrapping_key = derive_encryption_key( - &shared, - CHAT_SECRET_SALT, - format!("{chat_id}:{secret_id}:{version}").as_bytes(), - ) - .map_err(|error| error.to_string())?; - let chat_secret = ChaCha20Poly1305::new(wrapping_key) - .decrypt(encrypted_secret, b"") - .map_err(|error| error.to_string())?; - let message_key = derive_encryption_key(&chat_secret, CHAT_MESSAGE_SALT, b"message-content") - .map_err(|error| error.to_string())?; - let ciphertext = STANDARD - .decode(content) - .map_err(|error| error.to_string())?; - let plaintext = ChaCha20Poly1305::new(message_key) - .decrypt(&ciphertext, b"") - .map_err(|error| error.to_string())?; - - let user_request = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(connection.next_request_id().await?) - .add_typed_default( - DataType::UserId, - DataValue::UnsignedNumber(sender_id as u128), - ); - let user = connection - .mtp - .request(&user_request, None) - .await - .map_err(|error| error.to_string())?; - let sender = user - .get_str(DataType::Display) - .or_else(|| user.get_str(DataType::Username)) - .map(str::to_owned) - .unwrap_or_else(|| format!("User {sender_id}")); - let avatar = user - .get_str(DataType::Avatar) - .and_then(|avatar| decode_browser_base64(avatar).ok()); - android_notify( - sender_id, - &sender, - &String::from_utf8_lossy(&plaintext), - avatar.as_deref(), - )?; - Ok(()) -} - -fn derive_chat_id(own: u64, peer: u64) -> String { - format!("{}:{}", own.min(peer), own.max(peer)) -} - -fn decode_browser_base64(value: &str) -> Result, String> { - let normalized: String = value - .chars() - .filter(|character| !character.is_ascii_whitespace()) - .collect(); - if normalized.is_empty() { - return Err("value is empty".into()); - } - STANDARD - .decode(&normalized) - .or_else(|_| STANDARD_NO_PAD.decode(&normalized)) - .map_err(|_| "invalid base64 encoding".into()) -} - -fn decode_sdk_bytes(value: &str) -> Result, String> { - let trimmed = value.trim(); - let hex: String = trimmed - .strip_prefix("0x") - .or_else(|| trimmed.strip_prefix("0X")) - .unwrap_or(trimmed) - .chars() - .filter(|character| !matches!(character, ' ' | '\t' | '\r' | '\n' | ':' | '_' | '-')) - .collect(); - if !hex.is_empty() && hex.chars().all(|character| character.is_ascii_hexdigit()) { - if hex.len() % 2 != 0 { - return Err("hex string has an odd length".into()); - } - return (0..hex.len()) - .step_by(2) - .map(|offset| { - u8::from_str_radix(&hex[offset..offset + 2], 16) - .map_err(|_| "invalid hex encoding".to_string()) - }) - .collect(); - } - decode_browser_base64(trimmed) -} - -#[cfg(target_os = "android")] -fn android_root_certificates() -> &'static Vec { - static ROOTS: OnceLock> = OnceLock::new(); - ROOTS.get_or_init(|| { - let mut pem = Vec::new(); - for certificate in webpki_root_certs::TLS_SERVER_ROOT_CERTS { - pem.extend_from_slice(b"-----BEGIN CERTIFICATE-----\n"); - let encoded = STANDARD.encode(certificate.as_ref()); - for line in encoded.as_bytes().chunks(64) { - pem.extend_from_slice(line); - pem.push(b'\n'); - } - pem.extend_from_slice(b"-----END CERTIFICATE-----\n"); - } - pem.extend_from_slice(ROOT_YE_PEM); - pem.extend_from_slice(YE1_PEM); - pem - }) -} - -fn container_value(value: &DataValue, field: DataType) -> Option<&DataValue> { - let id = field.try_to_id(&TypeMap::latest())?; - value.get_field(id) -} - -fn container_value_by_name<'a>(value: &'a DataValue, field: &str) -> Option<&'a DataValue> { - container_value(value, DataType::from_name(field)?) -} - -fn communication_type(name: &str) -> Result { - CommunicationType::from_name(name).ok_or_else(|| format!("unknown communication type: {name}")) -} - -fn json_to_frame(type_name: &str, data: Value, id: u32) -> Result { - let comm_type = communication_type(type_name)?; - let mut frame = CommunicationValue::new(comm_type).with_id(id); - let Value::Object(fields) = data else { - return Err("MTP request data must be an object".into()); - }; - for (name, value) in fields { - let data_type = - DataType::from_name(&name).ok_or_else(|| format!("unknown data type: {name}"))?; - frame = frame.add_typed_default(data_type, json_to_data(&name, value)?); - } - Ok(frame) -} - -fn json_to_data(field: &str, value: Value) -> Result { - Ok(match value { - Value::Null => DataValue::Null, - Value::Bool(value) => DataValue::from(value), - Value::Number(value) => { - if let Some(integer) = value.as_u64() { - DataValue::UnsignedNumber(integer as u128) - } else if let Some(integer) = value.as_i64() { - DataValue::SignedNumber(integer as i128) - } else { - DataValue::Float(value.as_f64().ok_or("invalid number")?) - } - } - Value::String(value) => DataValue::Str(value), - Value::Array(values) if is_bytes_field(field) => DataValue::Bytes( - values - .into_iter() - .map(|value| { - value - .as_u64() - .and_then(|n| u8::try_from(n).ok()) - .ok_or("invalid byte") - }) - .collect::, _>>()?, - ), - Value::Array(values) => DataValue::Array( - values - .into_iter() - .map(|value| json_to_data(field, value)) - .collect::>()?, - ), - Value::Object(fields) => { - let mut entries = Vec::with_capacity(fields.len()); - for (name, value) in fields { - let data_type = DataType::from_name(&name) - .ok_or_else(|| format!("unknown nested data type: {name}"))?; - let id = data_type - .try_to_id(&TypeMap::latest()) - .ok_or_else(|| format!("unmapped data type: {name}"))?; - entries.push((id, json_to_data(&name, value)?)); - } - DataValue::Container(entries) - } - }) -} - -fn number_to_data(value: i128) -> DataValue { - if value >= 0 { - DataValue::UnsignedNumber(value as u128) - } else { - DataValue::SignedNumber(value) - } -} - -fn is_bytes_field(field: &str) -> bool { - matches!( - field, - "EncryptedSecret" | "KemCiphertext" | "Payload" | "PublicKeys" - ) -} - -fn frame_to_json(frame: &CommunicationValue) -> Result { - let mut result = Map::new(); - if let Some(id) = frame.id() { - result.insert("id".into(), Value::from(id)); - } - result.insert( - "type".into(), - Value::String(frame.get_type_name().unwrap_or("Unknown").to_owned()), - ); - result.insert("data".into(), frame_data_to_json(frame)?); - Ok(Value::Object(result)) -} - -fn frame_data_to_json(frame: &CommunicationValue) -> Result { - let map = frame.type_map().cloned().unwrap_or_else(TypeMap::latest); - let mut result = Map::new(); - let entries = frame - .data() - .ok_or("MTP frame payload is not a data container")?; - for (id, value) in entries { - let name = map - .data_type_name(id.0) - .ok_or_else(|| format!("unknown data type id: {}", id.0))?; - result.insert(name.to_owned(), data_to_json(value, &map)?); - } - Ok(Value::Object(result)) -} - -fn data_to_json(value: &DataValue, map: &TypeMap) -> Result { - Ok(match value { - DataValue::BoolTrue => Value::Bool(true), - DataValue::BoolFalse => Value::Bool(false), - DataValue::Bool(value) => Value::Bool(*value), - DataValue::SignedNumber(value) => number_to_json(*value)?, - DataValue::UnsignedNumber(value) => { - number_to_json(i128::try_from(*value).map_err(|_| "number exceeds JSON range")?)? - } - DataValue::Float(value) => serde_json::Number::from_f64(*value) - .map(Value::Number) - .ok_or("invalid float")?, - DataValue::Str(value) => Value::String(value.clone()), - DataValue::Bytes(value) => Value::Array(value.iter().copied().map(Value::from).collect()), - DataValue::Array(values) => Value::Array( - values - .iter() - .map(|value| data_to_json(value, map)) - .collect::>()?, - ), - DataValue::Container(entries) => { - let mut object = Map::new(); - for (id, value) in entries { - let name = map - .data_type_name(id.0) - .ok_or_else(|| format!("unknown nested data type id: {}", id.0))?; - object.insert(name.to_owned(), data_to_json(value, map)?); - } - Value::Object(object) - } - DataValue::Null => Value::Null, - _ => return Err("encrypted protocol values cannot cross the frontend bridge".into()), - }) -} - -fn number_to_json(value: i128) -> Result { - i64::try_from(value) - .map(Value::from) - .map_err(|_| "number exceeds JSON range".into()) -} - -#[cfg(test)] -mod tests { - use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; - use serde_json::json; - - use super::{ - container_value_by_name, decode_browser_base64, decode_sdk_bytes, frame_to_json, - jittered_retry_delay, json_to_frame, prepare_initial_state_ack, RequestIdAllocator, - }; - - #[test] - fn browser_base64_accepts_file_whitespace_and_missing_padding() { - assert_eq!(decode_browser_base64(" AQID\r\n").unwrap(), [1, 2, 3]); - assert_eq!(decode_browser_base64("AQI").unwrap(), [1, 2]); - } - - #[test] - fn sdk_bytes_accept_hex_and_base64() { - assert_eq!(decode_sdk_bytes("0x01:02-ff").unwrap(), [1, 2, 255]); - assert_eq!(decode_sdk_bytes("AQI=").unwrap(), [1, 2]); - } - - #[test] - fn request_ids_are_nonzero_and_monotonic() { - let ids = RequestIdAllocator::new(); - - assert_eq!(ids.next().unwrap(), 1); - assert_eq!(ids.next().unwrap(), 2); - } - - #[test] - fn retry_jitter_stays_within_policy_bounds() { - let delay = jittered_retry_delay(std::time::Duration::from_secs(10)); - - assert!(delay >= std::time::Duration::from_secs(8)); - assert!(delay <= std::time::Duration::from_secs(12)); - } - - #[test] - fn json_content_uses_content_wire_type() { - let frame = json_to_frame( - "MessageEdit", - json!({ - "Content": "ciphertext", - "ChatPartnerId": 42, - "SendTime": 10, - }), - 1, - ) - .unwrap(); - - assert_eq!( - frame - .get_data(DataType::Content) - .and_then(DataValue::as_str), - Some("ciphertext") - ); - } - - #[test] - fn nested_json_content_uses_content_wire_type() { - let frame = json_to_frame( - "MessageEdit", - json!({ - "Message": { "Content": "ciphertext" }, - }), - 1, - ) - .unwrap(); - let message = frame.get_data(DataType::Message).unwrap(); - - assert_eq!( - container_value_by_name(message, "Content").and_then(DataValue::as_str), - Some("ciphertext") - ); - } - - #[test] - fn content_is_exposed_to_frontend() { - let frame = CommunicationValue::new(CommunicationType::MessageEditLive) - .with_id(1) - .add_typed_default(DataType::Content, DataValue::Str("ciphertext".into())); - - let json = frame_to_json(&frame).unwrap(); - - assert_eq!(json["data"]["Content"], "ciphertext"); - } - - fn valid_initial_state() -> CommunicationValue { - CommunicationValue::new(communication_type("ClientStateSync").unwrap()) - .add_typed_default(DataType::SessionId, DataValue::UnsignedNumber(1)) - .add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::SyncMode, DataValue::Str("full".into())) - .add_typed_default(DataType::Contacts, DataValue::Array(vec![])) - .add_typed_default(DataType::Communities, DataValue::Array(vec![])) - .add_typed_default(DataType::Calls, DataValue::Array(vec![])) - .add_typed_default(DataType::Messages, DataValue::Array(vec![])) - } - - #[test] - fn valid_initial_state_is_prepared_before_ack() { - let ids = RequestIdAllocator::new(); - let (state, ack) = prepare_initial_state_ack(&valid_initial_state(), &ids).unwrap(); - - assert_eq!(state["SyncMode"], "full"); - assert_eq!(ack.get_type_name(), Some("ClientStateAck")); - assert_eq!(ack.id(), Some(1)); - } - - #[test] - fn malformed_initial_state_does_not_prepare_ack() { - let ids = RequestIdAllocator::new(); - let malformed = valid_initial_state() - .add_typed_default(DataType::SyncMode, DataValue::Str("invalid".into())); - - assert!(prepare_initial_state_ack(&malformed, &ids).is_err()); - assert_eq!( - ids.next().unwrap(), - 1, - "no acknowledgement ID was allocated" - ); - } - - #[test] - fn malformed_nested_initial_state_does_not_prepare_ack() { - let ids = RequestIdAllocator::new(); - let malformed = CommunicationValue::new(communication_type("ClientStateSync").unwrap()) - .add_typed_default(DataType::SessionId, DataValue::UnsignedNumber(1)) - .add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0)) - .add_typed_default(DataType::SyncMode, DataValue::Str("full".into())) - .add_typed_default( - DataType::Contacts, - DataValue::Array(vec![DataValue::Container(vec![])]), - ) - .add_typed_default(DataType::Communities, DataValue::Array(vec![])) - .add_typed_default(DataType::Calls, DataValue::Array(vec![])) - .add_typed_default(DataType::Messages, DataValue::Array(vec![])); - - assert!(prepare_initial_state_ack(&malformed, &ids).is_err()); - assert_eq!( - ids.next().unwrap(), - 1, - "no acknowledgement ID was allocated" - ); - } -} - -#[tauri::command] -pub async fn mtp_request(type_name: String, data: Value) -> Result { - manager().request(&type_name, data).await -} - -#[tauri::command] -pub fn mtp_status() -> MtpSnapshot { - manager().snapshot() -} - -#[tauri::command] -pub fn mtp_store_credentials(config: MtpConfig) -> Result<(), String> { - let serialized = serde_json::to_string(&config).map_err(|error| error.to_string())?; - android_store_config(&serialized)?; - *manager().config.write().expect("config lock poisoned") = Some(config); - Ok(()) -} - -#[tauri::command] -pub fn mtp_has_credentials() -> Result { - android_has_config() -} - -#[tauri::command] -pub fn mtp_load_keyring() -> Option { - manager() - .config - .read() - .expect("config lock poisoned") - .as_ref() - .map(|config| config.keyring.clone()) -} - -#[tauri::command] -pub fn mtp_set_enabled(enabled: bool) -> Result<(), String> { - android_set_enabled(enabled)?; - if !enabled { - manager().stop(); - } - Ok(()) -} - -#[tauri::command] -pub fn mtp_is_ignoring_battery_optimizations() -> Result { - android_is_ignoring_battery_optimizations() -} - -#[tauri::command] -pub fn mtp_request_battery_exemption() -> Result<(), String> { - android_request_battery_exemption() -} - -#[tauri::command] -pub fn mtp_set_ui_visible(visible: bool) { - manager().set_ui_visible(visible); -} - -#[tauri::command] -pub fn mtp_post_message_notification( - sender_id: u64, - sender: String, - body: String, - avatar: Option, -) -> Result { - #[cfg(target_os = "android")] - { - let avatar = avatar.as_deref().map(decode_browser_base64).transpose()?; - android_notify(sender_id, &sender, &body, avatar.as_deref())?; - Ok(true) - } - #[cfg(not(target_os = "android"))] - { - let _ = (sender_id, sender, body, avatar); - Ok(false) - } -} - -#[cfg(not(target_os = "android"))] -fn android_store_config(_: &str) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_has_config() -> Result { - Ok(manager() - .config - .read() - .expect("config lock poisoned") - .is_some()) -} -#[cfg(not(target_os = "android"))] -fn android_set_enabled(_: bool) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_status(_: &str) {} -#[cfg(not(target_os = "android"))] -fn android_notify(_: u64, _: &str, _: &str, _: Option<&[u8]>) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_cancel_notification(_: u64) -> Result<(), String> { - Ok(()) -} -#[cfg(not(target_os = "android"))] -fn android_is_ignoring_battery_optimizations() -> Result { - Ok(true) -} -#[cfg(not(target_os = "android"))] -fn android_request_battery_exemption() -> Result<(), String> { - Ok(()) -} - -#[cfg(target_os = "android")] -mod android { - use std::sync::OnceLock; - - use jni::{ - jni_sig, jni_str, - objects::{Global, JClass, JObject, JString, JValue}, - Env, EnvUnowned, JavaVM, - }; - use serde_json::Value; - - pub struct Host { - vm: JavaVM, - context: Global>, - bridge: Global>, - } - - static HOST: OnceLock = OnceLock::new(); - - pub fn attach(env: &mut Env, context: JObject) -> Result<(), String> { - if HOST.get().is_some() { - return Ok(()); - } - let class = env - .find_class(jni_str!("net/tensamin/client/NativeMtpBridge")) - .map_err(|e| e.to_string())?; - let bridge = env - .get_static_field( - class, - jni_str!("INSTANCE"), - jni_sig!("Lnet/tensamin/client/NativeMtpBridge;"), - ) - .and_then(|value| value.l()) - .map_err(|e| e.to_string())?; - HOST.set(Host { - vm: env.get_java_vm().map_err(|e| e.to_string())?, - context: env.new_global_ref(context).map_err(|e| e.to_string())?, - bridge: env.new_global_ref(bridge).map_err(|e| e.to_string())?, - }) - .map_err(|_| "Android MTP host is already attached".to_string()) - } - - fn with_env(call: impl FnOnce(&mut Env, &Host) -> Result) -> Result { - let host = HOST.get().ok_or("Android MTP host is not attached")?; - host.vm - .attach_current_thread(|env| Ok::<_, jni::errors::Error>(call(env, host))) - .map_err(|e| e.to_string())? - } - - pub fn store_config(config: &str) -> Result<(), String> { - with_env(|env, host| { - let value = env.new_string(config).map_err(|e| e.to_string())?; - env.call_method( - host.bridge.as_obj(), - jni_str!("storeConfig"), - jni_sig!("(Landroid/content/Context;Ljava/lang/String;)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Object(&value), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn has_config() -> Result { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("hasConfig"), - jni_sig!("(Landroid/content/Context;)Z"), - &[JValue::Object(host.context.as_obj())], - ) - .and_then(|value| value.z()) - .map_err(|e| e.to_string()) - }) - } - - pub fn set_enabled(enabled: bool) -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("setServiceEnabled"), - jni_sig!("(Landroid/content/Context;Z)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Bool(enabled.into()), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn status(status: &str) { - let _ = with_env(|env, host| { - let status = env.new_string(status).map_err(|e| e.to_string())?; - env.call_method( - host.bridge.as_obj(), - jni_str!("updateServiceStatus"), - jni_sig!("(Landroid/content/Context;Ljava/lang/String;)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Object(&status), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }); - } - - pub fn notify( - sender_id: u64, - sender: &str, - body: &str, - avatar: Option<&[u8]>, - ) -> Result<(), String> { - with_env(|env, host| { - let sender = env.new_string(sender).map_err(|e| e.to_string())?; - let body = env.new_string(body).map_err(|e| e.to_string())?; - let avatar = env - .byte_array_from_slice(avatar.unwrap_or_default()) - .map_err(|e| e.to_string())?; - env.call_method( - host.bridge.as_obj(), - jni_str!("postMessageNotification"), - jni_sig!("(Landroid/content/Context;JLjava/lang/String;Ljava/lang/String;[B)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Long(sender_id as i64), - JValue::Object(&sender), - JValue::Object(&body), - JValue::Object(&avatar), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn cancel_notification(sender_id: u64) -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("cancelMessageNotification"), - jni_sig!("(Landroid/content/Context;J)V"), - &[ - JValue::Object(host.context.as_obj()), - JValue::Long(sender_id as i64), - ], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - pub fn is_ignoring_battery_optimizations() -> Result { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("isIgnoringBatteryOptimizations"), - jni_sig!("(Landroid/content/Context;)Z"), - &[JValue::Object(host.context.as_obj())], - ) - .and_then(|value| value.z()) - .map_err(|e| e.to_string()) - }) - } - - pub fn request_battery_exemption() -> Result<(), String> { - with_env(|env, host| { - env.call_method( - host.bridge.as_obj(), - jni_str!("requestBatteryExemption"), - jni_sig!("(Landroid/content/Context;)V"), - &[JValue::Object(host.context.as_obj())], - ) - .map_err(|e| e.to_string())?; - Ok(()) - }) - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeAttach<'caller>( - mut env: EnvUnowned<'caller>, - _class: JClass, - context: JObject<'caller>, - ) { - let _ = env.with_env(|env| { - let _ = attach(env, context); - Ok::<_, jni::errors::Error>(()) - }); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStart<'caller>( - mut env: EnvUnowned<'caller>, - _class: JClass, - config: JString<'caller>, - ) { - let _ = env.with_env(|env| { - if let Ok(config) = config.mutf8_chars(env) { - if let Ok(config) = serde_json::from_str(config.to_str().as_ref()) { - super::manager().configure_and_start(config); - } - } - Ok::<_, jni::errors::Error>(()) - }); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeStop( - _env: EnvUnowned, - _class: JClass, - ) { - let _ = std::panic::catch_unwind(|| super::manager().stop()); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeSetUiState( - _env: EnvUnowned, - _class: JClass, - visible: jni::sys::jboolean, - ) { - super::manager().set_ui_visible(visible); - } - - #[no_mangle] - pub extern "system" fn Java_net_tensamin_client_NativeMtpBridge_nativeLog<'caller>( - mut env: EnvUnowned<'caller>, - _class: JClass, - level: jni::sys::jint, - message: JString<'caller>, - details: JString<'caller>, - ) { - let _ = env.with_env(|env| { - if let (Ok(message), Ok(details)) = (message.mutf8_chars(env), details.mutf8_chars(env)) - { - let message = message.to_str(); - let details = details.to_str(); - super::manager().log( - level.clamp(0, 3) as u8, - message.into_owned(), - (!details.is_empty()).then(|| Value::String(details.into_owned())), - ); - } - Ok::<_, jni::errors::Error>(()) - }); - } -} - -#[cfg(target_os = "android")] -use android::{ - cancel_notification as android_cancel_notification, has_config as android_has_config, - is_ignoring_battery_optimizations as android_is_ignoring_battery_optimizations, - notify as android_notify, request_battery_exemption as android_request_battery_exemption, - set_enabled as android_set_enabled, status as android_status, - store_config as android_store_config, -}; diff --git a/apps/tauri/src/deeplinkHandler.tsx b/apps/tauri/src/deeplinkHandler.tsx index dcbb5c7..2094b73 100644 --- a/apps/tauri/src/deeplinkHandler.tsx +++ b/apps/tauri/src/deeplinkHandler.tsx @@ -9,11 +9,11 @@ import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link"; import { isTauri } from "@tauri-apps/api/core"; import { useIsMobile } from "@methanium/ui"; - - -export const deeplinkContext = createContext<{ +type DeeplinkContextValue = { deeplinks: readonly string[]; -} | undefined>( +}; + +export const deeplinkContext = createContext( undefined, ); diff --git a/apps/tauri/src/qrCodeScanner.tsx b/apps/tauri/src/qrCodeScanner.tsx new file mode 100644 index 0000000..fa3f3c7 --- /dev/null +++ b/apps/tauri/src/qrCodeScanner.tsx @@ -0,0 +1,38 @@ +import { + scan, + Format, + requestPermissions, +} from "@tauri-apps/plugin-barcode-scanner"; +import { Button } from "@methanium/ui"; + +import { toast } from "@tensamin/shared/log"; + +export default function QrCodeScanner({ + onData, +}: { + onData: (data: string) => void; +}) { + return ( + + ); +} diff --git a/apps/web/index.html b/apps/web/index.html index 0c0644f..eef61d9 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -2,11 +2,8 @@ - - + + Tensamin diff --git a/apps/web/package.json b/apps/web/package.json index a6b3951..d95d914 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -6,58 +6,140 @@ "scripts": { "format": "pnpm exec prettier --write .", "lint": "eslint src", - "dev": "vite", + "dev": "vite --port 3000 --host 0.0.0.0", "test": "vitest run --passWithNoTests", - "build": "pnpm --filter @tensamin/pwa build && pnpm run test && tsc -b && vite build", + "build": "pnpm run test && tsc -b && vite build", "preview": "cd dist && nix-shell -p python3 --run 'python3 -m http.server 3000' && cd .." }, "dependencies": { - "@fontsource-variable/public-sans": "^5.3.0", - "@methanium/ui": "*", - "@tailwindcss/vite": "^4.3.3", - "@tanstack/react-router": "^1.170.21", - "@tanstack/react-virtual": "^3.14.9", - "@tauri-apps/api": "^2.11.1", - "@tensamin/pwa": "workspace:*", - "@tensamin/cache": "workspace:*", + "@base-ui/react": "^1.0.0", + "@base-ui/utils": "0.3.1", + "@babel/runtime": "^7.29.2", + "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/public-sans": "^5.2.7", + "@floating-ui/core": "^1.7.0", + "@floating-ui/dom": "^1.7.0", + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", + "@radix-ui/primitive": "^1.1.0", + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-context": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dismissable-layer": "^1.1.11", + "@radix-ui/react-focus-guards": "^1.1.4", + "@radix-ui/react-focus-scope": "^1.1.11", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-portal": "^1.1.13", + "@radix-ui/react-presence": "^1.1.6", + "@radix-ui/react-primitive": "^2.0.2", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-use-callback-ref": "^1.1.1", + "@radix-ui/react-use-controllable-state": "^1.2.3", + "@radix-ui/react-use-effect-event": "^0.0.2", + "@radix-ui/react-use-layout-effect": "^1.1.0", + "@reduxjs/toolkit": "^2.0.0", + "@tailwindcss/vite": "^4.2.4", + "@tanstack/devtools-event-client": "^0.3.0", + "@tanstack/query-core": "^5.0.0", + "@tanstack/react-router": "^1.169.1", + "@tanstack/history": "1.162.0", + "@tanstack/react-store": "^0.9.3", + "@tanstack/router-core": "^1.169.1", + "@tanstack/store": "^0.9.3", + "@tanstack/virtual-core": "^3.13.24", + "@tanstack/react-virtual": "^3.13.24", + "@tauri-apps/api": "^2", "@tensamin/call": "workspace:*", + "@tensamin/cache": "workspace:*", "@tensamin/chat": "workspace:*", "@tensamin/crypto": "workspace:*", - "@tensamin/hotkeys": "workspace:*", - "@tensamin/mtp": "workspace:*", - "@tensamin/notifications": "workspace:*", - "@tensamin/onboarding": "workspace:*", - "@tensamin/settings": "workspace:*", "@tensamin/shared": "workspace:*", + "@tensamin/settings": "workspace:*", "@tensamin/storage": "workspace:*", "@tensamin/tauri": "workspace:*", + "@tensamin/mtp": "workspace:*", "@tensamin/tauth": "workspace:*", + "@tensamin/markdown": "workspace:*", + "@methanium/ui": "*", "@tensamin/user": "workspace:*", + "@tensamin/notifications": "workspace:*", + "@tensamin/onboarding": "workspace:*", + "aria-hidden": "^1.2.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "cookie-es": "^3.0.0", + "d3-array": "^3.1.6", + "d3-color": "^3.1.0", + "d3-ease": "^3.0.1", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "date-fns": "^4.4.0", "decimal.js-light": "^2.5.1", - "eventemitter3": "^5.0.4", - "lucide-react": "^1.29.0", - "react": "^19.2.8", - "react-dom": "^19.2.8", - "react-is": "^19.2.8", - "react-redux": "^9.3.0", - "tailwind-scrollbar-hide": "^4.0.0", - "tailwindcss": "^4.3.3", + "detect-node-es": "^1.1.0", + "dijkstrajs": "^1.0.1", + "embla-carousel": "8.6.0", + "embla-carousel-react": "^8.6.0", + "embla-carousel-reactive-utils": "8.6.0", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "get-nonce": "^1.0.1", + "immer": "^10.1.1", + "input-otp": "^1.4.2", + "internmap": "^2.0.3", "tw-animate-css": "^1.4.0", - "use-sync-external-store": "^1.6.0", - "zod": "^4.4.3" + "lucide-react": "^1.14.0", + "next-themes": "^0.4.6", + "pngjs": "^5.0.0", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-day-picker": "^10.0.1", + "react-dom": "^19.2.0", + "react-is": "^19.0.0", + "react-redux": "^9.0.0", + "react-remove-scroll": "^2.7.2", + "react-remove-scroll-bar": "^2.3.7", + "react-resizable-panels": "^4.11.2", + "react-style-singleton": "^2.2.3", + "recharts": "3.8.1", + "redux": "^5.0.0", + "redux-thunk": "^3.1.0", + "reselect": "5.1.1", + "scheduler": "^0.27.0", + "seroval": "^1.5.4", + "seroval-plugins": "^1.5.4", + "shadcn": "^4.11.0", + "sonner": "^2.0.7", + "tailwindcss": "^4.2.4", + "tailwind-merge": "^3.6.0", + "tailwind-scrollbar-hide": "^4.0.0", + "tiny-invariant": "^1.3.3", + "tslib": "^2.8.1", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3", + "use-sync-external-store": "^1.2.2", + "vaul": "^1.1.2", + "victory-vendor": "^37.0.2", + "yargs": "^15.3.1", + "zod": "^4.3.6" }, "devDependencies": { "@eslint/js": "^10.0.1", "@types/qrcode": "^1.5.6", - "@types/react": "^19.2.18", - "@types/react-dom": "^19.2.4", - "@vitejs/plugin-react": "^6.0.5", - "esbuild": "^0.28.1", - "eslint": "^10.8.0", - "globals": "^17.9.0", - "mtp": "*", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^6.0.1", + "esbuild": "^0.25.11", + "eslint": "^10.0.3", + "globals": "^17.4.0", "typescript": "~6.0.3", - "typescript-eslint": "^8.66.0", - "vite": "^8.2.1" + "typescript-eslint": "^8.57.0", + "vite": "^8.0.10" } } diff --git a/apps/web/src/components/modals/basic.tsx b/apps/web/src/components/modals/basic.tsx index dc4e48a..46eaa45 100644 --- a/apps/web/src/components/modals/basic.tsx +++ b/apps/web/src/components/modals/basic.tsx @@ -15,10 +15,7 @@ export function Basic({ user, extra, }: { - user: Pick< - User, - "Display" | "Username" | "Avatar" | "OnlineStatus" | "Status" - >; + user: User; extra?: React.ReactNode; }) { const display = user.Display || user.Username || "Unknown"; @@ -60,9 +57,8 @@ export function Basic({ -
+

{display}

-

{user.Status}

{extra}
diff --git a/apps/web/src/components/modals/profile.tsx b/apps/web/src/components/modals/profile.tsx index a4bddb2..edd83a6 100644 --- a/apps/web/src/components/modals/profile.tsx +++ b/apps/web/src/components/modals/profile.tsx @@ -1,23 +1,10 @@ import type { User } from "@tensamin/user/context"; import { Avatar, AvatarFallback, AvatarImage, Button } from "@methanium/ui"; -import { Text } from "@methanium/ui/markdown"; +import Text from "@tensamin/markdown/text"; import { ChevronDown, ChevronUp } from "lucide-react"; import { useState } from "react"; -export default function Profile({ - user, -}: { - user: Pick< - User, - | "UserId" - | "Display" - | "Username" - | "Avatar" - | "About" - | "IotaId" - | "PublicKey" - >; -}) { +export default function Profile({ user }: { user: User }) { const [showAdvancedInformation, setShowAdvancedInformation] = useState(false); return ( diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 122f188..bafdfa8 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -5,14 +5,7 @@ import { PopoverTrigger, useIsMobile, } from "@methanium/ui"; -import { - ArrowLeft, - EllipsisVertical, - House, - Phone, - Settings, - User, -} from "lucide-react"; +import { ArrowLeft, House, Phone, Settings, User } from "lucide-react"; import { useLocation, useNavigate, useSearch } from "@tanstack/react-router"; import { joinCall, useCall } from "@tensamin/call/store"; import Wrapper from "@tensamin/user/wrapper"; @@ -47,12 +40,9 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { const [userInfoOpen, setUserInfoOpen] = useState(false); - const { callId } = useCall(); - return (
@@ -86,21 +76,9 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { )} - {isMobile && pathname === "/call" && callId && ( -

{displayCallId(callId)}

- )} {pathname === "/chat" && id && ( isMobile ? (

{user?.Display}

@@ -194,11 +172,6 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { )} )} - {isMobile && pathname === "/call" && callId && ( - - )}
diff --git a/apps/web/src/components/screens/login/form.tsx b/apps/web/src/components/screens/login/form.tsx index c9fa041..c81ef92 100644 --- a/apps/web/src/components/screens/login/form.tsx +++ b/apps/web/src/components/screens/login/form.tsx @@ -13,11 +13,8 @@ import { useState, } from "react"; import { z } from "zod"; -import { subscribeTuFileLaunch } from "@tensamin/pwa/runtime"; -import { - parseTuFileContent, - persistMtpCredentials, -} from "@tensamin/storage/credentials"; +import { isTauri } from "@tauri-apps/api/core"; +import QrCodeScanner from "@tensamin/tauri/qrCodeScanner"; import { useNavigate } from "@tanstack/react-router"; const fetchedUser = z.object({ @@ -38,11 +35,59 @@ const formSchema = z.object({ mtp_keyring: z.string().min(1).max(92), }); +/** + * Parses a .tu file payload into credentials. + * @param rawFileContent UTF-8 file content from an uploaded .tu file. + * @returns Parsed user id and private key credentials. + */ +function parseTuFileContent(rawFileContent: string): { + userId: number; + privateKey: string; + domain: string | null; +} { + if (rawFileContent.trim().length === 0) { + throw new Error("File is empty"); + } else if (!rawFileContent.includes("::")) { + throw new Error("Invalid file"); + } else if (rawFileContent.split("::").length !== 2) { + throw new Error("Invalid file"); + } + + const left = rawFileContent.split("::")[0]; + const right = rawFileContent.split("::")[1]; + + if (left.length === 0) { + throw new Error("Invalid file"); + } else if (right.length === 0) { + throw new Error("Invalid file"); + } else if (isNaN(Number(left)) && !left.includes("@")) { + throw new Error("Invalid file"); + } + + const [userIdString, privateKey] = rawFileContent.split("::"); + const userId = isNaN(Number(userIdString)) + ? Number(userIdString.split("@")[0]) + : Number(userIdString); + + const domain = userIdString.includes("@") ? userIdString.split("@")[1] : null; + + if (!userId || !privateKey) { + throw new Error("Invalid file"); + } + + console.log({ + domain, + userId, + }); + + return { userId, privateKey, domain }; +} + export default function Form() { const isMobile = useIsMobile(); const uploadRef = useRef(null); const [isDragging, setIsDragging] = useState(false); - const { load, save } = useStorage(); + const { save } = useStorage(); const navigate = useNavigate(); const loginPendingRef = useRef(false); @@ -51,19 +96,17 @@ export default function Form() { if (loginPendingRef.current) return false; loginPendingRef.current = true; try { - await persistMtpCredentials({ - storage: { load, save }, - userId, - keyring: privateKey, - domain, - }); + if (domain) await save("omega_url", `https://${domain}/`); + await save("mtp_keyring", privateKey, { secure: true }); + await save("session_id", Date.now()); + await save("user_id", userId); await navigate({ to: "/", replace: true }); return true; } finally { loginPendingRef.current = false; } }, - [load, navigate, save], + [navigate, save], ); // Process dropped files @@ -87,11 +130,6 @@ export default function Form() { [persistLogin], ); - useEffect( - () => subscribeTuFileLaunch(processDroppedFile), - [processDroppedFile], - ); - // Handle .tu files const handleFileInputChange = useCallback( async (event: ChangeEvent): Promise => { @@ -230,10 +268,32 @@ export default function Form() { return (
- {isMobile ? ( - + {isTauri() && isMobile ? ( + <> + { + if (!data.startsWith("tensamin://tu::")) { + toast("error", "Invalid QR code"); + return; + } + + const decoded = data.replace("tensamin://tu::", ""); + + try { + const { userId, privateKey, domain } = + parseTuFileContent(decoded); + + await persistLogin(userId, privateKey, domain); + } catch (error) { + log(0, "login", "red", error); + toast("error", "Failed to parse QR code data"); + } + }} + /> + + ) : (
uploadRef.current?.click()} diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx index d56a4f1..10baf34 100644 --- a/apps/web/src/components/sidebar.tsx +++ b/apps/web/src/components/sidebar.tsx @@ -27,6 +27,7 @@ import { SelectContent, SelectItem, } from "@methanium/ui"; +import { isTauri } from "@tauri-apps/api/core"; import { useIsMobile } from "@methanium/ui"; import { MobileNavbar } from "./navbar"; @@ -34,20 +35,24 @@ import SidebarBox from "@tensamin/call/sidebarBox"; import { useShowMobileNavbar } from "@/routes/app/useShowMobileNavbar"; import { Ellipsis, Check } from "lucide-react"; import { useState } from "react"; -import { useUser, type User } from "@tensamin/user/context"; -import { mtp, userPresencePreferenceSchema } from "@tensamin/shared/data"; +import type { User } from "@tensamin/user/context"; +import type z from "zod"; +import { mtp } from "@tensamin/shared/data"; import { useMTP } from "@tensamin/mtp"; -import { - onlineStatusLabels, - onlineStatusOptions, - type OnlineStatus, -} from "./status-options"; -function accountPreference(status: User["OnlineStatus"]): OnlineStatus { - return userPresencePreferenceSchema.safeParse(status).success - ? (status as OnlineStatus) - : "user_online"; -} +type OnlineStatus = z.infer; + +const onlineStatusLabels: Record = { + user_online: "Online", + user_offline: "Offline", + user_dnd: "Do not disturb", + user_idle: "Idle", + user_wc: "Away", + user_borked: "Borked", + iota_offline: "Iota offline", + iota_online: "Iota online", + iota_borked: "Iota borked", +}; function StatusDialog({ user, @@ -63,7 +68,7 @@ function StatusDialog({ saveSucceeded, setSaveSucceeded, }: { - user: Pick; + user: User; open: boolean; onOpenChange: (open: boolean) => void; send: ReturnType["send"]; @@ -76,15 +81,13 @@ function StatusDialog({ saveSucceeded: boolean; setSaveSucceeded: (value: boolean) => void; }) { - const { updateProfile, updateState } = useUser(); - return ( { if (!nextOpen) { setDraftStatus(user.Status ?? ""); - setDraftOnlineStatus(accountPreference(user.OnlineStatus)); + setDraftOnlineStatus(user.OnlineStatus); setErrorMessage(""); setSaveSucceeded(false); } @@ -123,11 +126,10 @@ function StatusDialog({ - {onlineStatusOptions.map((option) => ( - - {option.label} - - ))} + Online + Offline + Idle + Do not disturb
@@ -138,10 +140,12 @@ function StatusDialog({ Cancel} /> - )} - /> - - Hide users - - - )} -
- )} +
+
+ {view === "focused" && ( + + ( + + )} + /> + + Hide users + + + )} +
- - {!isMobile && ( -
- - ( - - )} - /> - - Popout - - - - ( - - )} - /> - - Fullscreen - - -
- )} +
+ + ( + + )} + /> + + Popout + + + + ( + + )} + /> + + Fullscreen + + +
); } diff --git a/packages/call/src/components/buttons/invite.tsx b/packages/call/src/components/buttons/invite.tsx index d49a643..b478529 100644 --- a/packages/call/src/components/buttons/invite.tsx +++ b/packages/call/src/components/buttons/invite.tsx @@ -63,7 +63,6 @@ export default function InviteButton({ Loading...
} component={(user) => ( - ); - - return ( - <> - - - - tooltip ? ( - ( - } - onClick={onClick} - className={className} - > - {trigger} - - )} - /> - ) : ( - - {trigger} - - ) - } - /> - - - - {isScreensharing ? ( - - ) : null} - {cameraEnabled ? ( - - ) : null} - - - {tooltip ? ( - - {tooltip} - - ) : null} - - - {dialogKind ? ( - !open && setDialogKind(null)} - portalContainer={portalContainer} - /> - ) : null} - - ); -} diff --git a/packages/call/src/components/buttons/screenshare.tsx b/packages/call/src/components/buttons/screenshare.tsx new file mode 100644 index 0000000..4312dda --- /dev/null +++ b/packages/call/src/components/buttons/screenshare.tsx @@ -0,0 +1,175 @@ +import { useEffect, useState } from "react"; +import { + Button, + Popover, + PopoverContent, + PopoverTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@methanium/ui"; +import { MonitorDot, ScreenShare } from "lucide-react"; +import { toast } from "@tensamin/shared/log"; +import { isTauri } from "@tauri-apps/api/core"; +import { setScreenShareEnabled, useCall } from "../../store"; +import ScreenShareDialog from "../screenshareDialog"; + +export default function ScreenshareButton({ + className, + iconSize, + tooltip, + defaultPortal, +}: { + className?: string; + iconSize?: number; + tooltip?: string; + defaultPortal?: boolean; +}) { + const isScreensharing = useCall((state) => state.screenShareEnabled); + const screenRef = useCall((state) => state.screenRef); + const [portalContainer, setPortalContainer] = useState(); + const [dialogOpen, setDialogOpen] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); + + useEffect(() => { + if (defaultPortal) return; + setPortalContainer(screenRef?.current ?? undefined); + }, [screenRef, defaultPortal]); + + async function startWebShare() { + try { + await setScreenShareEnabled(true, { + audio: true, + systemAudio: "include", + surfaceSwitching: "include", + video: true, + }); + } catch (error) { + console.error("Failed to start web screen share", error); + toast( + "error", + error instanceof Error + ? error.message + : "Failed to start screen sharing.", + ); + } + } + + async function stopShare() { + try { + await setScreenShareEnabled(false); + } catch (error) { + console.error("Failed to stop screen share", error); + toast("error", "Failed to stop screen sharing."); + } + } + + return ( + <> + + + + tooltip ? ( + ( + + )} + /> + ) : ( + + ) + } + /> + + + + + + + {tooltip && ( + + {tooltip} + + )} + + + {isTauri() && ( + + )} + + ); +} diff --git a/packages/call/src/components/invitePopup.tsx b/packages/call/src/components/invitePopup.tsx index 583c57d..ed26a1a 100644 --- a/packages/call/src/components/invitePopup.tsx +++ b/packages/call/src/components/invitePopup.tsx @@ -23,7 +23,6 @@ export default function InvitePopup({ return ( ( diff --git a/packages/call/src/components/mediaShareDialog.tsx b/packages/call/src/components/mediaShareDialog.tsx deleted file mode 100644 index 8e7b493..0000000 --- a/packages/call/src/components/mediaShareDialog.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { - Button, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - Label, - Switch, -} from "@methanium/ui"; -import { toast } from "@tensamin/shared/log"; -import { Loader2 } from "lucide-react"; -import { - getMediaShareAdapter, - type MediaShareKind, - type MediaShareSource, -} from "../mediaShare"; -import { startCameraShare, startScreenShare } from "../store"; -import MediaSourceCard from "./mediaSourceCard"; - -export default function MediaShareDialog({ - kind, - open, - onOpenChange, - portalContainer, -}: { - kind: MediaShareKind; - open: boolean; - onOpenChange: (open: boolean) => void; - portalContainer?: HTMLElement; -}) { - const [loading, setLoading] = useState(false); - const [sources, setSources] = useState([]); - const [selectedSourceId, setSelectedSourceId] = useState(null); - const [shareAudio, setShareAudio] = useState(true); - const [canShareAudio, setCanShareAudio] = useState(false); - const [cameraPreview, setCameraPreview] = useState(null); - const [cameraPreviewVersion, setCameraPreviewVersion] = useState(0); - const cameraPreviewRef = useRef(null); - - function stopCameraPreview() { - cameraPreviewRef.current?.getTracks().forEach((track) => track.stop()); - cameraPreviewRef.current = null; - setCameraPreview(null); - } - - useEffect(() => { - if (!open) return; - let active = true; - setLoading(true); - setSelectedSourceId(null); - - Promise.all([ - getMediaShareAdapter().listSources(kind), - getMediaShareAdapter().getCapabilities(), - ]) - .then(([nextSources, capabilities]) => { - if (!active) return; - const availableSources = - kind === "camera" && nextSources.length === 0 - ? [ - { - id: "__default_camera__", - kind: "camera" as const, - name: "Default camera", - }, - ] - : nextSources; - setSources(availableSources); - setSelectedSourceId(availableSources[0]?.id ?? null); - setCanShareAudio(kind === "screen" && capabilities.canShareScreenAudio); - }) - .catch((error) => { - console.error("Failed to load media share sources", error); - toast("error", "Failed to load media sources."); - }) - .finally(() => active && setLoading(false)); - - return () => { - active = false; - }; - }, [kind, open]); - - useEffect(() => { - if (kind !== "camera" || !open || !selectedSourceId) { - stopCameraPreview(); - return; - } - - let active = true; - stopCameraPreview(); - const sourceId = - selectedSourceId === "__default_camera__" ? undefined : selectedSourceId; - - void navigator.mediaDevices - .getUserMedia({ - audio: false, - video: sourceId ? { deviceId: { exact: sourceId } } : true, - }) - .then((stream) => { - if (!active) { - stream.getTracks().forEach((track) => track.stop()); - return; - } - cameraPreviewRef.current = stream; - setCameraPreview(stream); - }) - .catch((error) => { - console.error("Failed to preview camera", error); - }); - - return () => { - active = false; - stopCameraPreview(); - }; - }, [cameraPreviewVersion, kind, open, selectedSourceId]); - - async function startSharing() { - setLoading(true); - try { - if (kind === "camera") { - stopCameraPreview(); - await startCameraShare( - selectedSourceId === "__default_camera__" - ? undefined - : (selectedSourceId ?? undefined), - ); - } else { - await startScreenShare({ - sourceId: selectedSourceId ?? undefined, - includeAudio: canShareAudio && shareAudio, - }); - } - onOpenChange(false); - } catch (error) { - console.error(`Failed to share ${kind}`, error); - if (kind === "camera") { - setCameraPreviewVersion((version) => version + 1); - } - toast( - "error", - error instanceof Error ? error.message : `Failed to share ${kind}.`, - ); - } finally { - setLoading(false); - } - } - - return ( - - - - - {kind === "camera" ? "Share a camera" : "Share your screen"} - - - -
-
- {sources.map((source) => { - const selected = source.id === selectedSourceId; - return ( - setSelectedSourceId(source.id)} - /> - ); - })} -
- - {!loading && sources.length === 0 ? ( -

- No {kind === "camera" ? "cameras" : "windows or displays"} found. -

- ) : null} - - {canShareAudio ? ( -
-
- -

- Some apps and protected media do not allow audio capture. -

-
- -
- ) : null} - - {loading ? ( -
- - Loading sources... -
- ) : null} -
- - - - - -
-
- ); -} diff --git a/packages/call/src/components/mediaSourceCard.tsx b/packages/call/src/components/mediaSourceCard.tsx deleted file mode 100644 index f740dbc..0000000 --- a/packages/call/src/components/mediaSourceCard.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { cn } from "@methanium/ui"; -import { AppWindow, Camera, MonitorUp } from "lucide-react"; -import { useEffect, useRef } from "react"; -import type { MediaShareSource } from "../mediaShare"; - -function SourceIcon({ source }: { source: MediaShareSource }) { - const className = "size-8 text-muted-foreground"; - - if (source.kind === "camera") return ; - if (source.kind === "window") return ; - return ; -} - -function VideoPreview({ stream }: { stream: MediaStream }) { - const videoRef = useRef(null); - - useEffect(() => { - const video = videoRef.current; - if (!video) return; - video.srcObject = stream; - void video.play().catch(() => undefined); - - return () => { - video.srcObject = null; - }; - }, [stream]); - - return ( -