Files
clean-copy/app/README.md
T

343 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CleanCopy
CleanCopy is a lightweight, headless Windows utility that sits silently in the system tray, automatically sanitizing your clipboard text on trigger. Designed with a strict focus on privacy, developer ergonomics, and performance, CleanCopy operates with a minimal footprint and compiles to a native installer.
## Features
- Automatically removes marketing/analytics trackers (e.g., `utm_*`, `gclid`, `fbclid`, `si`, `msclkid`) from copied URLs while keeping functional query arguments. Users can also define **custom tracking parameters** via a comma-separated input in the Rules tab.
- Converts curly quotes (`"`/`"`/`'`/`'`), en-dashes (``), and em-dashes (`—`) to standard straight quotes (`"`, `'`) and double hyphens (`--`).
- Joins single-newline wraps (frequently introduced by chat apps like WhatsApp or Messenger) into continuous lines while preserving double-newline paragraphs and Markdown layout structure. Also handles **hyphenated line breaks** (e.g., `"inno-\nvation"``"innovation"`).
- Removes zero-width spaces, null bytes, and other invisible Unicode characters that can cause issues when pasting.
- Plays a system beep and shows a Windows notification each time the clipboard is sanitized.
- Optional autostart toggle via `tauri-plugin-autostart`.
- A dark-themed settings panel styled around the VS Code _Lavender Dimmed_ color scheme, using the local _CaskaydiaCove Nerd Font_.
- Integrates secure Ed25519-signature-based offline licensing tied directly to a hardware-fingerprinted machine ID (HWID).
## Getting Started
### Prerequisites
1. **Node.js** (v18+)
2. **Rust & Cargo** (latest stable release)
3. **Windows C++ Build Tools** (Visual Studio Build Tools with C++ desktop workload enabled)
### Installation
Clone the repository and install npm packages from the app directory:
```bash
cd app
npm install
```
### Running in Development
Start the development server (starts Vite and launches the Tauri background tray app):
```bash
npm run tauri dev
```
## Testing & Code Quality
### Running Frontend Tests
Run the Vitest test suite covering React components, state management, and UI behavior:
```bash
npm run test
```
### Linting Frontend Code
Run ESLint across all frontend TypeScript/TSX files:
```bash
npm run lint
```
To auto-fix fixable issues:
```bash
npm run lint:fix
```
### Formatting Frontend Code
Run Prettier to format all frontend files:
```bash
npm run format
```
To check formatting without writing (useful for CI):
```bash
npm run format:check
```
### Running Rust Tests
Run unit tests for clipboard cleansing rules and cryptographic license validation:
```bash
cargo test --workspace
```
### Running Rust Linter
Verify code quality and check for warnings using Cargo Clippy:
```bash
cargo clippy --workspace -- -D warnings
```
### Building Frontend
Verify that the TypeScript compilation and Vite assets packager complete without warning:
```bash
npm run build
```
## Architecture Notes
### Clipboard Simulation
When the global hotkey is pressed (`Alt + Shift + C` by default), CleanCopy uses the Windows `keybd_event` API to simulate a `Ctrl+C` keystroke. It releases the Alt/Shift modifiers first to avoid polluting the copied selection, then waits 150ms for the OS clipboard to update before running the sanitization pipeline.
### Profile-Based Licensing
The file `licensing_profiles.json` is compiled directly into the Rust binary via `include_str!`. Profile selection at runtime:
| Build mode | Profile | Activation URL |
| ----------------------- | ------------- | --------------------------------------------------- |
| `cargo test` | `testing` | `http://localhost:3000/api/activate` |
| `cargo build` (debug) | `development` | `http://localhost:3000/api/activate` |
| `cargo build` (release) | `production` | `https://cleancopy.stevanfreeborn.com/api/activate` |
### License Enforcement
When no valid license is found, CleanCopy locks the UI to the License tab only — the General and Rules tabs are disabled. If the global hotkey is pressed while unlicensed, it opens the settings window to the License tab instead of cleaning the clipboard.
## Licensing System
CleanCopy utilizes a unified, database-free Stripe licensing proxy designed to support a suite of multiple desktop applications.
### The Activation Flow
1. The user buys CleanCopy on your landing page via Stripe Checkout.
2. The user copies their Stripe Payment ID (e.g. `pi_3MtgK...`) or Charge ID (e.g. `ch_3MtgK...`) from their Stripe receipt.
3. The user pastes the Stripe ID into CleanCopy's License tab.
4. CleanCopy pings server. The server:
- Verifies the purchase with Stripe's API.
- Checks that the payment is for the correct `product_id`.
- Signs the user's Hardware ID (HWID) using a private Ed25519 signing key.
- Returns a signed activation token.
5. CleanCopy verifies the signature using the embedded public key, saving it to `license.lic`. On subsequent startups, CleanCopy validates this file offline with zero network requests.
### Local Development Testing
To test the activation flow locally without deploying a live server:
#### Option A: Run the Local Mock Server (Recommended)
1. Start the mock licensing server:
```bash
cd app/src-tauri
cargo run --bin mock_license_server
```
- CleanCopy detects debug mode and automatically redirects its requests to `http://localhost:3000`.
- In the Settings -> License UI, enter any key (e.g. `pi_test_12345`) and click **Activate**.
- To test purchase failures, enter a key starting with `pi_invalid` (e.g. `pi_invalid_999`).
#### Option B: Offline Sign & Bypass
- Copy the HWID displayed in Settings -> License.
- Run the license tool to sign it manually with the private key:
```bash
cd app/src-tauri
cargo run --bin license_tool issue 04a0664dfa85f71119e9f4fc971a953ad49f82b752087deab6bee816c1d37d6d <YOUR_HWID>
```
- Paste the returned token into the activation UI.
- Delete the `license.lic` file from your local AppData directory:
- **Path:** `C:\Users\<user>\AppData\Roaming\com.cleancopy.app\license.lic`
### The CLI License Tool (`license_tool`)
CleanCopy includes a built-in Rust command-line utility `license_tool.rs` for key generation and offline license issuance.
#### Generating Keypairs (`generate-keys`)
Generate fresh, cryptographically secure Ed25519 public/private key pairs. The tool uses `getrandom` to extract secure entropy from the host OS (Windows, macOS, or Linux).
Run the tool with an optional profile name (prefix) and optional target folder:
```bash
cd app/src-tauri
cargo run --bin license_tool generate-keys [profile_name] [output_dir]
```
Example:
```bash
cargo run --bin license_tool generate-keys development
```
- Generates keys and automatically persists them as `licensing/development.priv` and `licensing/development.pub` under your `.gitignored` project root folder.
- Outputs the key hex values directly to the console.
#### Issuing Offline Activation Keys (`issue`)
Issue signed license tokens manually for air-gapped users, secure enterprise environments, or local automated virtual-machine testing.
Run the tool specifying the profile's private key and the user's Hardware ID (HWID):
```bash
cd app/src-tauri
cargo run --bin license_tool issue <private-key-hex> <device-hwid> [product_id] [license_key]
```
Example:
```bash
cargo run --bin license_tool issue 04a0664dfa85f71119e9f4fc971a953ad49f82b752087deab6bee816c1d37d6d 9a0b396c42da0860b246aaa3b8e0cafb86789e062a8947957bb88a46f08477b6
```
- Outputs a period-separated activation token: `PAYLOAD_B64.SIGNATURE_B64`. The customer pastes this token into their Settings panel, which is verified offline against the compiled public key.
#### Purging License Status (`deactivate`)
Easily reset your development machine back to the "Unregistered" state to verify and test the activation UI/flow repeatedly without manually locating files.
Run the tool with the `deactivate` subcommand:
```bash
cd app/src-tauri
cargo run --bin license_tool deactivate
```
- Locates the local AppData directory, checks if `license.lic` is present, and deletes it.
#### Diagnostics: Manually Querying a User's HWID
If a customer is offline and cannot run the app to copy their Hardware ID from the License tab, can instruct them to run this single line in **PowerShell**:
```powershell
$guid = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Cryptography').MachineGuid;
$serial = (Get-CimInstance -ClassName Win32_BaseBoard).SerialNumber;
$hasher = [System.Security.Cryptography.HashAlgorithm]::Create('SHA256');
$bytes = [System.Text.Encoding]::UTF8.GetBytes("$guid$serial");
$hash = $hasher.ComputeHash($bytes);
-join ($hash | ForEach-Object { "{0:x2}" -f $_ })
```
This queries the registry and bios serial, SHA-256 hashes them, and prints the matching HWID.
### Key Rotation
If your signing key is compromised or you need to rotate for any reason, CleanCopy supports seamless key rotation via the `deprecated_public_keys` field in `licensing_profiles.json`.
#### How It Works
Each profile contains:
- `public_key_hex`: The current signing key (used for all new activations)
- `deprecated_public_keys`: Array of previous keys that remain valid for existing users
When verifying a license, CleanCopy tries the current key first, then falls back to each deprecated key. Users with tokens signed by an old key continue to work without re-activation.
#### Rotation Steps
- Generate a new keypair:
```bash
cd app/src-tauri
cargo run --bin license_tool generate-keys production
```
- Update `licensing_profiles.json`:
```json
{
"production": {
"activation_url": "https://cleancopy.stevanfreeborn.com/api/activate",
"public_key_hex": "<new key>",
"deprecated_public_keys": ["<old key>"]
}
}
```
- Update the signing key on your activation server to use the new private key.
- Rebuild and distribute the updated app. Existing users' licenses validate against the deprecated key. New activations use the current key.
- After the migration period, remove the old key from `deprecated_public_keys` and rebuild.
## Logging and Diagnostics
CleanCopy uses the `tauri-plugin-log` crate for structured logging at the `Info` level.
### Log Output
Logs are written to two destinations simultaneously:
| Destination | Purpose |
| ------------ | -------------------------------------------------------- |
| **Stdout** | Visible in the terminal when running `npm run tauri dev` |
| **Log file** | Persistent file for production builds |
### Log File Location
The log file is named `app.log` and stored in the Tauri log directory:
| Platform | Path |
| -------- | ------------------------------------------ |
| Windows | `%APPDATA%/com.cleancopy.app/logs/app.log` |
### What Gets Logged
- **Startup**: Config loading, license validation, hotkey registration
- **Clipboard events**: Each clean operation with input/output text
- **License activation**: Proxy requests, token verification results
- **Errors**: File I/O failures, signature verification failures, network errors
### Viewing Logs
**Real-time (development):**
```bash
npm run tauri dev
# Logs appear in the terminal
```
**Log file (production):**
```powershell
# Windows PowerShell
Get-Content "$env:APPDATA\com.cleancopy.app\logs\app.log" -Tail 50 -Wait
```
### Log Level
The log level is hardcoded to `Info`. To change it for debugging, modify the `.level()` call in `app/src-tauri/src/lib.rs`:
```rust
.level(log::LevelFilter::Debug)
.level(log::LevelFilter::Warn)
```
## Production Distribution
To bundle a production-ready MSI installer and release binary:
```bash
npm run tauri build
```
The compiled assets will be bundled in:
`app/src-tauri/target/release/bundle/msi/`