Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ license:
LICENSE.FULL:
pnpm tsx scripts/merge-license.ts

.PHONY: version
version:
pnpm tsx scripts/bump-version.ts


CORE_NAME = core
CORE_PKG_DIR = packages/core
CORE_WASM_FILE = $(CORE_PKG_DIR)/$(CORE_NAME)_bg.wasm $(CORE_PKG_DIR)/$(CORE_NAME)_bg.wasm.d.ts
Expand Down
4 changes: 3 additions & 1 deletion dev-utility-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ pub fn run() {
Ok(())
})
.invoke_handler(tauri::generate_handler![
#[cfg(desktop)]
// #[cfg(desktop)]
// dev_utility_core::hardware::list_hid_devices,
dev_utility_core::codec::decode_base64,
dev_utility_core::codec::encode_base64,
#[cfg(desktop)]
dev_utility_core::codec::decode_jwt,
dev_utility_core::cryptography::generate_rsa_key,
dev_utility_core::cryptography::analyze_rsa_key,
dev_utility_core::cryptography::generate_hashes,
Expand Down
4 changes: 3 additions & 1 deletion dev-utility-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "DevUtility",
"version": "0.1.9",
"version": "1.0.0-alpha.2",
"identifier": "dev.utility.app",
"build": {
"beforeDevCommand": "pnpm --filter @dev-utility/frontend dev",
Expand All @@ -17,6 +17,8 @@
"titleBarStyle": "Overlay",
"width": 1200,
"height": 800,
"minWidth": 375,
"minHeight": 800,
"transparent": true,
"trafficLightPosition": {
"x": 18,
Expand Down
2 changes: 1 addition & 1 deletion dev-utility-workers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dev-utility-workers"
version = "0.1.9"
version = "1.0.0-alpha.2"
edition = "2021"
authors = ["AprilNEA <github@sku.moe>"]

Expand Down
3 changes: 2 additions & 1 deletion dev-utility/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dev-utility-core"
version = "0.1.9"
version = "1.0.0-alpha.2"
authors = ["AprilNEA <github@sku.moe>"]
description = "⚡ Universal developer toolkit for software, hardware, and security professionals."
license-file = "../LICENSE"
Expand All @@ -23,6 +23,7 @@ getrandom = { version = "0.3", features = ["wasm_js"] }

# Codec
base64 = "0.22.1"
jsonwebtoken = "9"

# Cryptography
rsa = "0.9.8"
Expand Down
101 changes: 96 additions & 5 deletions dev-utility/src/core/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,29 @@
// See LICENSE file for details or contact admin@aprilnea.com

use crate::error::UtilityError;
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use base64::{
engine::general_purpose::STANDARD as BASE64,
engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_SAFE, Engine as _,
};
use jsonwebtoken::Algorithm;
use serde::{Deserialize, Serialize};
use universal_function_macro::universal_function;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Base64Engine {
Standard,
UrlSafe,
}

#[universal_function]
pub fn decode_base64(input: &str) -> Result<String, UtilityError> {
BASE64
pub async fn decode_base64(input: &str, engine: Base64Engine) -> Result<String, UtilityError> {
let base64_engine = match engine {
Base64Engine::Standard => BASE64,
Base64Engine::UrlSafe => BASE64_URL_SAFE,
};

base64_engine
.decode(input)
.map_err(|e| UtilityError::DecodeError(e.to_string()))
.and_then(|bytes| {
Expand All @@ -26,6 +43,80 @@ pub fn decode_base64(input: &str) -> Result<String, UtilityError> {
}

#[universal_function]
pub fn encode_base64(input: &str) -> String {
BASE64.encode(input.as_bytes())
pub async fn encode_base64(input: &str) -> Result<String, UtilityError> {
Ok(BASE64.encode(input.as_bytes()))
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum JwtDecodeStatus {
Valid,
Invalid,
Unverified,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JwtDecodeResult {
pub header: String,
pub payload: String,
pub signature: String,
pub status: JwtDecodeStatus,
}

async fn decode_jwt_with_secret(input: &str) -> Result<JwtDecodeResult, UtilityError> {
// If verification fails, try to decode without verification
let parts: Vec<&str> = input.split('.').collect();
if parts.len() != 3 {
return Err(UtilityError::DecodeError("Invalid JWT format".to_string()));
}

// Decode base64 parts, but handle signature as raw bytes since it may not be valid UTF-8
let header = decode_base64(parts[0], Base64Engine::UrlSafe).await?;
let payload = decode_base64(parts[1], Base64Engine::UrlSafe).await?;

// For signature, decode to bytes and convert to hex string to avoid UTF-8 issues
let base64_engine = BASE64_URL_SAFE;
let signature_bytes = base64_engine
.decode(parts[2])
.map_err(|e| UtilityError::DecodeError(e.to_string()))?;
let signature = hex::encode(signature_bytes);

Ok(JwtDecodeResult {
header,
payload,
signature,
status: JwtDecodeStatus::Unverified,
})
}

#[universal_function(desktop_only)]
pub async fn decode_jwt(
input: &str,
algorithm: Algorithm,
secret: Option<&str>,
) -> Result<JwtDecodeResult, UtilityError> {
let parts = decode_jwt_with_secret(input).await?;
match secret {
Some(secret_key) => {
let validation = jsonwebtoken::Validation::new(algorithm);
match jsonwebtoken::decode::<serde_json::Value>(
input,
&jsonwebtoken::DecodingKey::from_secret(secret_key.as_bytes()),
&validation,
) {
Ok(_) => Ok(JwtDecodeResult {
header: parts.header,
payload: parts.payload,
signature: parts.signature,
status: JwtDecodeStatus::Valid,
}),
Err(_) => {
let mut result = decode_jwt_with_secret(input).await?;
result.status = JwtDecodeStatus::Invalid;
Ok(result)
}
}
}
None => decode_jwt_with_secret(input).await,
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "dev-utility",
"private": true,
"version": "0.1.9",
"version": "1.0.0-alpha.2",
"type": "module",
"scripts": {
"tauri": "tauri",
"dev": "pnpm tauri dev",
"web": "pnpm --filter @dev-utility/frontend dev:wasm"
"web": "pnpm --filter @dev-utility/frontend dev:wasm",
"bump-version": "pnpm tsx scripts/bump-version.ts"
},
"dependencies": {
"react": "^19.1.0",
Expand Down
28 changes: 15 additions & 13 deletions packages/frontend/src/components/layout/two-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,21 @@ const TwoSectionLayout = ({
classNames?.secondSection,
)}
>
<div
className={cn(
"flex items-center justify-between gap-2 mb-2",
classNames?.secondSectionToolbar,
)}
>
{secondLabel && (
<Label className="text-sm font-medium text-foreground/80">
{t(secondLabel)}
</Label>
)}
<div className="flex items-center gap-1">{secondToolbar}</div>
</div>
{secondToolbar && (
<div
className={cn(
"flex items-center justify-between gap-2 mb-2",
classNames?.secondSectionToolbar,
)}
>
{secondLabel && (
<Label className="text-sm font-medium text-foreground/80">
{t(secondLabel)}
</Label>
)}
<div className="flex items-center gap-1">{secondToolbar}</div>
</div>
)}
{secondContent}
</div>
</div>
Expand Down
27 changes: 23 additions & 4 deletions packages/frontend/src/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import { SearchForm } from "./search-form";
import { ThemeSwitcher } from "./theme-switcher";

const InsetHeader: React.FC<{ title: string }> = ({ title }) => {
const { open } = useSidebar();
const { open, isMobile } = useSidebar();

const setOnTop = async () => {
await getCurrentWindow().setAlwaysOnTop(!isOnTop);
Expand All @@ -71,7 +71,9 @@ const InsetHeader: React.FC<{ title: string }> = ({ title }) => {
data-tauri-drag-region
className="flex h-9 shrink-0 items-center gap-2 px-4"
>
<SidebarTrigger className={cn("-ml-1", !open && "ml-16")} />
<SidebarTrigger
className={cn("-ml-1", !open && "ml-16", isMobile && "ml-16")}
/>
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
Expand All @@ -94,6 +96,23 @@ const InsetHeader: React.FC<{ title: string }> = ({ title }) => {
);
};

const InsetContent: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const { isMobile } = useSidebar();

return (
<SidebarInset
className={cn(
"bg-background rounded-lg m-2 overflow-hidden",
isMobile && "m-0",
)}
>
{children}
</SidebarInset>
);
};

export default function AppSidebar({
children,
...props
Expand Down Expand Up @@ -241,12 +260,12 @@ export default function AppSidebar({
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset className="bg-background rounded-lg m-2 overflow-hidden">
<InsetContent>
<InsetHeader title={title} />
<main className="@container/main flex-1 max-h-[calc(100vh-3rem)] px-4 pb-2 overflow-hidden">
{children}
</main>
</SidebarInset>
</InsetContent>
</SidebarProvider>
);
}
21 changes: 9 additions & 12 deletions packages/frontend/src/pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ const UpdateItem = () => {
const { data: update, isLoading } = useSWR("updater_check", check);

const [isUpdating, setIsUpdating] = useState(false);
const { data: process } = useSWRSubscription(

useSWRSubscription(
isUpdating ? "updater_download" : null,
(
_,
Expand All @@ -81,7 +82,7 @@ const UpdateItem = () => {
contentLength?: number;
},
Error
>,
>
) => {
next(null, {
status: "pending",
Expand All @@ -98,7 +99,7 @@ const UpdateItem = () => {
}));
}
console.log(
`started downloading ${event.data.contentLength} bytes`,
`started downloading ${event.data.contentLength} bytes`
);
break;
}
Expand All @@ -120,6 +121,8 @@ const UpdateItem = () => {
status: "finished",
}));
console.log("download finished");
relaunch();
setIsUpdating(false);
break;
}
}
Expand All @@ -130,16 +133,10 @@ const UpdateItem = () => {
};
},
{
onSuccess(data) {
console.log(data);
if (data.status === "finished") {
relaunch();
}
},
fallbackData: {
status: "pending",
},
},
}
);

return (
Expand Down Expand Up @@ -409,7 +406,7 @@ export default function SettingsPage() {
onClick={() => {
window.open(
"https://github.com/aprilnea/devutility/releases",
"_blank",
"_blank"
);
}}
className="flex items-center gap-1 hover:text-foreground transition-colors"
Expand All @@ -422,7 +419,7 @@ export default function SettingsPage() {
onClick={() => {
window.open(
"https://github.com/aprilnea/devutility/issues",
"_blank",
"_blank"
);
}}
className="flex items-center gap-1 hover:text-foreground transition-colors"
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/utilities/codec/base64.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from "@/components/tools";
import { Button } from "@/components/ui/button";
import { useUtilityInvoke } from "../invoke";
import { InvokeFunction } from "../types";
import { Base64Engine, InvokeFunction } from "../types";

export default function Base64CodecPage() {
const [mode, setMode] = useState<CodecMode>(CodecMode.Encode);
Expand Down Expand Up @@ -56,7 +56,7 @@ export default function Base64CodecPage() {
(input: string, mode: CodecMode) => {
setError("");
if (mode === CodecMode.Decode) {
decode.trigger({ input });
decode.trigger({ input, engine: Base64Engine.Standard });
} else {
encode.trigger({ input });
}
Expand Down
Loading
Loading