diff --git a/.forgejo/workflows/deploy-dev.yml b/.forgejo/workflows/deploy-dev.yml index 1a022c2..26053ff 100644 --- a/.forgejo/workflows/deploy-dev.yml +++ b/.forgejo/workflows/deploy-dev.yml @@ -10,101 +10,18 @@ jobs: steps: - name: Check out repo uses: https://data.forgejo.org/actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install Packages - run: apt-get update && apt-get install -y sudo curl jq - - - name: Install Nix - uses: https://github.com/cachix/install-nix-action@v30 - - - name: Install Bun + - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - - name: Setup Android Keystore - env: - KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} - KEYSTORE_PROPERTIES: ${{ secrets.ANDROID_KEYSTORE_PROPERTIES }} - run: | - bun -e "require('fs').writeFileSync('keystore.jks', Buffer.from(process.env.KEYSTORE_BASE64.replace(/\s+/g, ''), 'base64'))" - bun -e "const content = process.env.KEYSTORE_PROPERTIES.replace(/\\\\n/g, '\n').replace(/\\r/g, '').split('\n').map(l => l.trim()).filter(l => l).join('\n'); require('fs').writeFileSync('keystore.properties', content)" - - name: Build - run: bun run build:apps + run: bun run build:web - name: Install rsync run: apt-get update && apt-get install -y rsync - name: Deploy run: rsync -a --delete apps/web/dist/ /var/lib/www/tensamin-web-dev/ - - - name: Read version and hash - id: version - run: | - VERSION="$(node -p "require('./package.json').version")" - SHORT_SHA="$(git rev-parse --short HEAD)" - echo "version=$VERSION" >> "$FORGEJO_OUTPUT" - echo "short_sha=$SHORT_SHA" >> "$FORGEJO_OUTPUT" - echo "tag=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" - echo "title=${VERSION}-dev-${SHORT_SHA}" >> "$FORGEJO_OUTPUT" - - - name: Create pre-release and upload files - env: - TOKEN: ${{ forgejo.token }} - API: ${{ forgejo.api_url }} - REPO: ${{ forgejo.repository }} - SHA: ${{ forgejo.sha }} - TAG: ${{ steps.version.outputs.tag }} - TITLE: ${{ steps.version.outputs.title }} - run: | - set -eu - - test -d releases - find releases -type f | grep -q . - - LATEST_PROD_TAG="$(git tag --list 'v*' --sort=-v:refname | head -n 1 || true)" - if [ -n "$LATEST_PROD_TAG" ]; then - LOG="$(git log "$LATEST_PROD_TAG"..HEAD --pretty=format:'- %s')" - SERVER_URL="$(echo "$API" | sed 's|/api/v1.*||')" - COMPARE_URL="${SERVER_URL}/${REPO}/compare/${LATEST_PROD_TAG}...${TAG}" - BODY="$(printf '%s\n\n[View changes](%s)' "$LOG" "$COMPARE_URL")" - else - BODY="$(git log --pretty=format:'- %s')" - fi - - HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") - - if [ "$HTTP_STATUS" = "200" ]; then - echo "Release $TAG already exists." - RELEASE_ID="$(jq -r .id release_out.json)" - else - echo "Creating new pre-release for $TAG" - RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg tag "$TAG" \ - --arg name "$TITLE" \ - --arg body "$BODY" \ - --arg target "$SHA" \ - '{ - tag_name: $tag, - name: $name, - body: $body, - target_commitish: $target, - draft: false, - prerelease: true - }')")" - RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" - fi - - find releases -type f -print0 | while IFS= read -r -d '' file; do - name="$(basename "$file")" - curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ - -H "Authorization: token $TOKEN" \ - -F "attachment=@$file" - done diff --git a/.forgejo/workflows/deploy-prod.yml b/.forgejo/workflows/deploy-prod.yml index 8207e6a..1df5b60 100644 --- a/.forgejo/workflows/deploy-prod.yml +++ b/.forgejo/workflows/deploy-prod.yml @@ -45,7 +45,7 @@ jobs: run: | VERSION="$(node -p "require('./package.json').version")" echo "version=$VERSION" >> "$FORGEJO_OUTPUT" - echo "tag=${VERSION}-prod" >> "$FORGEJO_OUTPUT" + echo "tag=v$VERSION" >> "$FORGEJO_OUTPUT" - name: Create release and upload files env: @@ -54,40 +54,39 @@ jobs: REPO: ${{ forgejo.repository }} SHA: ${{ forgejo.sha }} TAG: ${{ steps.version.outputs.tag }} + VERSION: ${{ steps.version.outputs.version }} run: | set -eu test -d releases find releases -type f | grep -q . - COMMIT_MSG="$(git log -1 --pretty=%B | sed 's/$/ /')" - HTTP_STATUS=$(curl -s -w "%{http_code}" -o release_out.json -H "Authorization: token $TOKEN" "$API/repos/$REPO/releases/tags/$TAG") if [ "$HTTP_STATUS" = "200" ]; then echo "Release $TAG already exists." - exit 0 + RELEASE_ID="$(jq -r .id release_out.json)" + else + echo "Creating new release for $TAG" + RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg tag "$TAG" \ + --arg name "$TAG" \ + --arg body "Release $VERSION" \ + --arg target "$SHA" \ + '{ + tag_name: $tag, + name: $name, + body: $body, + target_commitish: $target, + draft: false, + prerelease: false + }')")" + RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" fi - echo "Creating new release for $TAG" - RELEASE_JSON="$(curl -f -sS -X POST "$API/repos/$REPO/releases" \ - -H "Authorization: token $TOKEN" \ - -H "Content-Type: application/json" \ - -d "$(jq -n \ - --arg tag "$TAG" \ - --arg name "$TAG" \ - --arg body "$COMMIT_MSG" \ - --arg target "$SHA" \ - '{ - tag_name: $tag, - name: $name, - body: $body, - target_commitish: $target, - draft: false, - prerelease: false - }')")" - RELEASE_ID="$(echo "$RELEASE_JSON" | jq -r .id)" - find releases -type f -print0 | while IFS= read -r -d '' file; do name="$(basename "$file")" curl -fsS -X POST "$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$name" \ diff --git a/apps/web/src/components/screens/login/form.tsx b/apps/web/src/components/screens/login/form.tsx index ff76307..da5726c 100644 --- a/apps/web/src/components/screens/login/form.tsx +++ b/apps/web/src/components/screens/login/form.tsx @@ -23,7 +23,7 @@ const fetchedUser = z.object({ }); const formSchema = z.object({ - username: z.string().min(1).max(255), + username: z.string().min(1).max(15), private_key: z.string().min(1).max(92), }); @@ -35,7 +35,6 @@ const formSchema = z.object({ function parseTuFileContent(rawFileContent: string): { userId: number; privateKey: string; - domain: string | null; } { if (rawFileContent.trim().length === 0) { throw new Error("File is empty"); @@ -43,44 +42,21 @@ function parseTuFileContent(rawFileContent: string): { 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) { + } else if (rawFileContent.split("::")[0].length === 0) { throw new Error("Invalid file"); - } else if (right.length === 0) { + } else if (rawFileContent.split("::")[1].length === 0) { throw new Error("Invalid file"); - } else if (isNaN(Number(left)) && !left.includes("@")) { + } else if (isNaN(Number(rawFileContent.split("::")[0]))) { throw new Error("Invalid file"); } const [userIdString, privateKey] = rawFileContent.split("::"); - const userId = isNaN(Number(userIdString)) - ? Number(userIdString.split("@")[0]) - : Number(userIdString); - - const rawDomain = userIdString.includes("@") - ? userIdString.split("@")[1] - : null; - const domain = rawDomain - ? rawDomain.includes(":") - ? rawDomain - : rawDomain + ":1984" - : null; - + const userId = Number(userIdString); if (!userId || !privateKey) { throw new Error("Invalid file"); } - console.log({ - domain, - rawDomain, - userId, - }); - - return { userId, privateKey, domain }; + return { userId, privateKey }; } /** @@ -110,9 +86,6 @@ export default function Form() { await save("session_id", Date.now()); await save("user_id", parsed.userId); await save("private_key", parsed.privateKey); - if (parsed.domain) { - await save("ttp_url", `https://${parsed.domain}/`); - } location.href = "/"; } catch (error) { @@ -141,22 +114,9 @@ export default function Form() { return; } - const inputUsername = inputParse.data.username; - const actualUsername = inputUsername.includes("@") - ? inputUsername.split("@")[0] - : inputUsername; - const rawDomain = inputUsername.includes("@") - ? inputUsername.split("@")[1] - : null; - const domain = rawDomain - ? rawDomain.includes(":") - ? rawDomain - : rawDomain + ":1984" - : null; - try { const response = await fetch( - `https://omega.tensamin.net/api/get/id/${actualUsername}`, + `https://omega.tensamin.net/api/get/id/${inputParse.data.username}`, ); const rawData = await response.arrayBuffer(); @@ -177,9 +137,6 @@ export default function Form() { await save("session_id", Date.now()); await save("user_id", user.user_id); await save("private_key", inputParse.data.private_key); - if (domain) { - await save("ttp_url", `https://${domain}/`); - } location.href = "/"; } catch (error) { @@ -197,7 +154,7 @@ export default function Form() { {isTauriEnv ? ( <> { + onData={(data) => { if (!data.startsWith("tensamin://tu::")) { toast("error", "Invalid QR code"); return; @@ -205,14 +162,10 @@ export default function Form() { const decoded = data.replace("tensamin://tu::", ""); try { - const { userId, privateKey, domain } = - parseTuFileContent(decoded); - await save("session_id", Date.now()); - await save("user_id", userId); - await save("private_key", privateKey); - if (domain) { - await save("ttp_url", `https://${domain}/`); - } + const { userId, privateKey } = parseTuFileContent(decoded); + save("session_id", Date.now()); + save("user_id", userId); + save("private_key", privateKey); location.href = "/"; } catch (error) { log(0, "login", "red", error); diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx index 6b4b6cd..ce9bb6b 100644 --- a/apps/web/src/components/sidebar.tsx +++ b/apps/web/src/components/sidebar.tsx @@ -6,7 +6,6 @@ import { Sidebar as SidebarRoot, SidebarContent, SidebarFooter, - useSidebar, } from "@tensamin/ui"; import { isTauri } from "@tauri-apps/api/core"; import { useIsMobile } from "@tensamin/ui"; @@ -14,21 +13,17 @@ import { MobileNavbar } from "./navbar"; import SidebarBox from "@tensamin/call/sidebarBox"; import { useShowMobileNavbar } from "@/routes/app/layout"; + /** * Renders the conversation sidebar with account summary and conversation list. - * On mobile the sidebar is always kept in the DOM and hidden via CSS - * (opacity + translateX) instead of being unmounted. This keeps the DOM and - * React state alive while the drawer is closed, allowing the sidebar to open - * instantly on subsequent toggles. * @returns Sidebar JSX. */ export default function Sidebar() { const isMobile = useIsMobile(); const showMobileNavbar = useShowMobileNavbar(); - const { openMobile, setOpenMobile } = useSidebar(); - const content = ( - <> + return ( + )} - + ); - - if (isMobile) { - return ( - <> - {openMobile && ( -
setOpenMobile(false)} - /> - )} -
-
{content}
-
- - ); - } - - return {content}; } diff --git a/apps/web/src/features/settings/layout.tsx b/apps/web/src/features/settings/layout.tsx index 36d366a..fa27444 100644 --- a/apps/web/src/features/settings/layout.tsx +++ b/apps/web/src/features/settings/layout.tsx @@ -1,4 +1,4 @@ -import { Button, ClearStorageButton } from "@tensamin/ui"; +import { Button } from "@tensamin/ui"; import options from "@tensamin/shared/settings"; import { cn, useIsMobile } from "@tensamin/ui"; @@ -69,9 +69,6 @@ export function SettingsSidebar() { ))}
))} -
- -
); } diff --git a/bun.lock b/bun.lock index 325be20..5b4b4cd 100644 --- a/bun.lock +++ b/bun.lock @@ -259,7 +259,7 @@ }, "overrides": { "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz", - "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.34.tar.gz", + "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.31.tar.gz", }, "packages": { "@antfu/ni": ["@antfu/ni@25.0.0", "", { "dependencies": { "ansis": "^4.0.0", "fzf": "^0.5.2", "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" }, "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs", "nup": "bin/nup.mjs" } }, "sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA=="], @@ -700,7 +700,7 @@ "@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.59.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.1", "zod": "^4.4.1" } }, "sha512-smyx+04hSnWM1oyWJfJrRWmxu7OInwyHeIlaD1h3tUrkrSxiKWHl1qCmxVO1bC+cDwyUXhJ5HUnNCbx1aWx7Dg=="], - "@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.34.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-hp7rV0a0gfD/rNw9et+PqM1PPkgFC6/Z7eYfza6NIp/m+a8/r5Um+S/8tzBDCgZmQC9Y1sJsjesH7mXZz6Jmuw=="], + "@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.31.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-I2FhsDtR5ElHwCasf6CfFCiyr5T2Cp9MQ2uAhcMnoNZ0tsK0mJhs+Rz9nUDmysydNyqOj8j/uC0CYXnnG1pCxQ=="], "@tensamin/user": ["@tensamin/user@workspace:packages/user"], diff --git a/package.json b/package.json index 7f969e8..40e19a8 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "jsonc-parser": "^3.3.1" }, "overrides": { - "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.34.tar.gz", + "@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.31.tar.gz", "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.17.tar.gz" }, "dependencies": { diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index e03ca3d..a059e9c 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -244,7 +244,6 @@ export interface Storage extends SettingsStorageDefaults { legal_docs: z.infer; cached_contacts: Contacts; cached_communities: Communities; - ttp_url: string; } export const storageDefaults: Storage = { @@ -277,5 +276,4 @@ export const storageDefaults: Storage = { }, cached_contacts: [], cached_communities: [], - ttp_url: "https://tensamin.net:959", }; diff --git a/packages/ttp/src/context.tsx b/packages/ttp/src/context.tsx index 0a2ea2c..f1c30aa 100644 --- a/packages/ttp/src/context.tsx +++ b/packages/ttp/src/context.tsx @@ -24,6 +24,7 @@ import { RECONNECT_RESET, RECONNECT_TRIES, RETRY_INTERVAL, + TRANSPORT_URL, } from "./values"; import { type Calls, @@ -192,14 +193,6 @@ export function Provider(props: { > | null>(null); const identificationStartedRef = useRef(false); - // Load ttp url - const [ttpUrl, setTtpUrl] = useState(null); - useEffect(() => { - load("ttp_url").then((url) => { - setTtpUrl(url); - }); - }, [load]); - /** * Sends typed protocol messages through the active transport client. * @param type Protocol message type. @@ -273,10 +266,6 @@ export function Provider(props: { }, [connected, identified, send]); useEffect(() => { - if (!ttpUrl) { - return; - } - let attempts = 0; let reconnectTimer: ReturnType | null = null; let reconnectResetTimer: ReturnType | null = null; @@ -354,7 +343,7 @@ export function Provider(props: { const transportClient = !props.blockConnection ? createTransportClient(schemas, { - url: ttpUrl, + url: TRANSPORT_URL, onReadyStateChange: (state) => { currentReadyState = state; setReadyState(state); @@ -403,12 +392,8 @@ export function Provider(props: { return; } - if (!ttpUrl) { - return; - } - try { - await transportClient?.connect(ttpUrl); + await transportClient?.connect(TRANSPORT_URL); } catch (connectError) { if (disposed) { return; @@ -475,7 +460,7 @@ export function Provider(props: { setIdentifying(false); identificationStartedRef.current = false; }; - }, [props.blockConnection, ttpUrl]); + }, [props.blockConnection]); useEffect(() => { if (!connected) { @@ -618,19 +603,14 @@ export function Provider(props: { }, [connected, decrypt, getSharedSecret, load, send]); const progress = useMemo(() => { - if (!ttpUrl) return 10; if (readyState === READY_STATE.CONNECTING) return 30; if (!connected) return 45; if (identifying) return 75; if (!identified) return 90; return 100; - }, [connected, identified, identifying, readyState, ttpUrl]); + }, [connected, identified, identifying, readyState]); const loadingTitle = useMemo(() => { - if (!ttpUrl) { - return "Looking up configuration"; - } - if (readyState === READY_STATE.CONNECTING || !connected) { return "Connecting to Tensamin"; } @@ -640,13 +620,9 @@ export function Provider(props: { } return "Loading"; - }, [connected, identified, identifying, readyState, ttpUrl]); + }, [connected, identified, identifying, readyState]); const loadingDescription = useMemo(() => { - if (!ttpUrl) { - return "Loading connection details"; - } - if (readyState === READY_STATE.CONNECTING || !connected) { return "Establishing transport channel"; } @@ -656,13 +632,13 @@ export function Provider(props: { } return undefined; - }, [connected, identified, identifying, readyState, ttpUrl]); + }, [connected, identified, identifying, readyState]); if (error !== "" && errorDescription !== "") { return ; } - if (!connected || !identified || !ttpUrl) { + if (!connected || !identified) { return (