diff --git a/.prettierignore b/.prettierignore index f001f4a..df2ca1c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,5 @@ coverage .tmp *.tsbuildinfo bun.lock -apps/desktop/build \ No newline at end of file +apps/desktop/build +apps/mobile/src-tauri diff --git a/apps/desktop/electrobun.config.ts b/apps/desktop/electrobun.config.ts index 6433cf7..dcc0bc6 100644 --- a/apps/desktop/electrobun.config.ts +++ b/apps/desktop/electrobun.config.ts @@ -1,6 +1,6 @@ import type { ElectrobunConfig } from "electrobun"; -import { version } from "@tensamin/shared/package.json"; +import { version } from "../../package.json"; export default { app: { diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 3bd7416..91d171c 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -1,7 +1,7 @@ { "name": "@tensamin/mobile", "private": true, - "version": "0.1.0", + "version": "0.0.0", "type": "module", "exports": { "./context": "./src/context.tsx" diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 28f1aa1..6de2c44 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 85d0c88..c6da8ae 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 28f1aa1..c42517c 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 73e48db..1d61eb2 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 13dd214..eceb132 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 73e48db..be24593 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 1d98044..5aeb1dc 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index a888b33..cebe007 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 1d98044..1b7aa3d 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 0818324..14abb66 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index a2a838e..a918033 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 0818324..5bd12fa 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index b18bceb..4583924 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index 3f8a57f..f480384 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index b18bceb..17279d6 100644 Binary files a/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/apps/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/apps/mobile/src-tauri/gen/android/app/src/main/res/values/ic_launcher_background.xml b/apps/mobile/src-tauri/gen/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/apps/mobile/src-tauri/gen/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/apps/mobile/src-tauri/icons/icon.icns b/apps/mobile/src-tauri/icons/icon.icns index 21497ae..dbdc5e3 100644 Binary files a/apps/mobile/src-tauri/icons/icon.icns and b/apps/mobile/src-tauri/icons/icon.icns differ diff --git a/apps/mobile/src-tauri/tauri.conf.json b/apps/mobile/src-tauri/tauri.conf.json index 99d080e..5db8cf4 100644 --- a/apps/mobile/src-tauri/tauri.conf.json +++ b/apps/mobile/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "tensamin", - "version": "0.1.0", + "version": "../../../package.json", "identifier": "net.tensamin.client", "build": { "beforeDevCommand": "cd ../web && bun run dev && cd -", diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index 483f26e..82366e4 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -4,7 +4,6 @@ import { useLocation, useNavigate, useSearch } from "@tanstack/react-router"; import { useCall } from "@tensamin/call/context"; import Wrapper from "@tensamin/user/wrapper"; import { Skeleton } from "@tensamin/ui/cmp/skeleton"; -import { useConversation } from "@/features/conversation/context"; import { Select, SelectContent, @@ -14,16 +13,17 @@ import { import { displayCallId } from "@tensamin/call/utils"; import { useState } from "react"; import { SidebarTrigger, useSidebar } from "@tensamin/ui/cmp/sidebar"; +import { useTTP } from "@tensamin/ttp/context"; export default function Navbar({ forMobile }: { forMobile: boolean }) { const navigate = useNavigate(); const { joinCall, state } = useCall(); - const { conversations } = useConversation(); + const { contacts } = useTTP(); const { pathname } = useLocation(); const { id } = useSearch({ strict: false }); - const currentCalls = id ? conversations?.[id]?.calls || [] : []; + const currentCalls = id ? contacts?.[id]?.calls || [] : []; //const currentCalls = ["leck", "schleck", "und", "eck"]; const [selectOpen, setSelectOpen] = useState(false); diff --git a/apps/web/src/components/screens/login/form.tsx b/apps/web/src/components/screens/login/form.tsx index 32b2235..3fa8fa7 100644 --- a/apps/web/src/components/screens/login/form.tsx +++ b/apps/web/src/components/screens/login/form.tsx @@ -3,7 +3,6 @@ import { Input } from "@tensamin/ui/cmp/input"; import { Label } from "@tensamin/ui/cmp/label"; import { useStorage } from "@tensamin/storage/context"; import { log, toast } from "@tensamin/shared/log"; -import { useNavigate } from "@tanstack/react-router"; import { Upload } from "lucide-react"; import * as React from "react"; import { z } from "zod"; @@ -55,7 +54,6 @@ function parseTuFileContent(rawFileContent: string): { export default function Form() { const uploadRef = React.useRef(null); const { save } = useStorage(); - const navigate = useNavigate(); /** * Handles uploaded .tu files and stores resolved credentials. @@ -73,16 +71,17 @@ export default function Form() { const raw = await file.text(); const parsed = parseTuFileContent(raw); + await save("session_id", Date.now()); await save("user_id", parsed.userId); await save("private_key", parsed.privateKey); - void navigate({ to: "/" }); + location.href = "/"; } catch (error) { log(0, "Login", "red", error); toast("error", "Failed to load file"); } }, - [navigate, save], + [save], ); /** @@ -123,16 +122,17 @@ export default function Form() { const user = parse.data; + await save("session_id", Date.now()); await save("user_id", user.user_id); await save("private_key", inputParse.data.private_key); - navigate({ to: "/" }); + location.href = "/"; } catch (error) { log(0, "Login", "red", error); toast("error", "Failed to fetch user data"); } }, - [navigate, save], + [save], ); return ( diff --git a/apps/web/src/features/conversation/context.tsx b/apps/web/src/features/conversation/context.tsx deleted file mode 100644 index 11ae41d..0000000 --- a/apps/web/src/features/conversation/context.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useTTP } from "@tensamin/ttp/context"; -import { - createContext, - useContext, - useEffect, - useState, - type ReactNode, -} from "react"; - -import { toast } from "@tensamin/shared/log"; -import type { - Community, - Conversation, -} from "@tensamin/shared/features/conversation/schema"; - -interface contextValue { - conversations: Conversation[] | null; - communities: Community[] | null; -} - -const ConversationContext = createContext(undefined); - -/** - * Executes ConversationProvider. - * @param props Parameter props. - * @returns unknown. - */ -export default function ConversationProvider(props: { children: ReactNode }) { - const [conversations, setConversations] = useState( - null, - ); - const [communities, setCommunities] = useState(null); - - const { send } = useTTP(); - - useEffect(() => { - let active = true; - - send("get_conversations", {}) - .then((data) => { - if (active) { - setConversations(data.data.user_ids); - } - }) - .catch(() => { - setConversations([ - { - last_message_at: 0, - user_id: 0, - }, - ]); - toast("error", "Failed to load conversations"); - }); - - send("get_communities", {}) - .then((data) => { - if (active) { - setCommunities(data.data.communities); - } - }) - .catch(() => { - setCommunities([ - { - community_address: "none", - community_title: "Failed", - position: "", - }, - ]); - toast("error", "Failed to load communities"); - }); - - return () => { - active = false; - }; - }, [send]); - - return ( - - {props.children} - - ); -} - -/** - * Executes useConversation. - * @param none This function has no parameters. - * @returns contextValue. - */ -export function useConversation(): contextValue { - const context = useContext(ConversationContext); - if (!context) { - throw new Error( - "useConversation must be used within a ConversationProvider", - ); - } - return context; -} diff --git a/apps/web/src/features/conversation/list/body.tsx b/apps/web/src/features/conversation/list/body.tsx index b552c7e..7ed6665 100644 --- a/apps/web/src/features/conversation/list/body.tsx +++ b/apps/web/src/features/conversation/list/body.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { useVirtualizer } from "@tanstack/react-virtual"; -import { useConversation } from "../context"; +import { useTTP } from "@tensamin/ttp/context"; import Switch from "./switch"; import ConversationModal from "../modal/conversation"; @@ -17,8 +17,8 @@ export default function List() { "conversations" | "communities" >("conversations"); - const { conversations, communities } = useConversation(); - const items = category === "conversations" ? conversations : communities; + const { contacts, communities } = useTTP(); + const items = category === "conversations" ? contacts : communities; const scrollRef = React.useRef(null); @@ -66,14 +66,14 @@ export default function List() { }} > {category === "conversations" ? ( - conversations?.[virtualItem.index] ? ( + contacts?.[virtualItem.index] ? ( ) : null ) : communities?.[virtualItem.index] ? ( ) : null} diff --git a/apps/web/src/features/conversation/modal/community.tsx b/apps/web/src/features/conversation/modal/community.tsx index a7286e0..8da377e 100644 --- a/apps/web/src/features/conversation/modal/community.tsx +++ b/apps/web/src/features/conversation/modal/community.tsx @@ -1,10 +1,3 @@ -import type { Community } from "@tensamin/shared/features/conversation/schema"; - -/** - * Executes CommunityModal. - * @param props Parameter props. - * @returns unknown. - */ -export default function CommunityModal(props: { community: Community }) { - return
{props.community.community_title}
; +export default function CommunityModal() { + return
Not implemented
; } diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 254b332..8bfa19b 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -109,7 +109,7 @@ --chart-3: oklch(0.6 0.118 184.704); --chart-4: oklch(0.511 0.096 186.391); --chart-5: oklch(0.437 0.078 188.216); - --sidebar: oklch(0.190 0 0); + --sidebar: oklch(0.19 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.704 0.14 182.503); --sidebar-primary-foreground: oklch(0.277 0.046 192.524); diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index 7d420ae..6e38e58 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -19,7 +19,6 @@ import CallScreen from "@tensamin/call/screen"; import Login from "@/routes/screens/login"; import Signup from "@/routes/screens/signup"; -import ConversationContext from "@/features/conversation/context"; import ChatContext from "@tensamin/chat/context"; import CallContext from "@tensamin/call/context"; import SocketContext from "@tensamin/ttp/context"; @@ -57,11 +56,9 @@ function AppShell() { - - - - - + + + diff --git a/package.json b/package.json index 5be24bf..15bec0c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "tensamin", + "version": "0.1", "private": true, "workspaces": [ "packages/*", diff --git a/packages/shared/src/data.ts b/packages/shared/src/data.ts index 5143412..acece20 100644 --- a/packages/shared/src/data.ts +++ b/packages/shared/src/data.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { legalDocsSchema } from "./features/legal/schema"; -import { community, conversation } from "./features/conversation/schema"; const fileFromMessage = z.object({ name: z.string(), @@ -37,24 +36,38 @@ export const failedUser = { // TTP export const ttp = { identification: { - request: z - .object({ - user_id: z.number().optional(), - iota_id: z.number().optional(), - }) - .refine( - (value) => - (typeof value.user_id === "number") !== - (typeof value.iota_id === "number"), - { - message: "Either user_id or iota_id must be provided", - }, - ), + request: z.object({ + version: z.string(), + session_id: z.number(), + user_id: z.number(), + }), response: z.object({ challenge: z.string(), public_key: z.base64(), }), }, + challenge_response: { + request: z.object({ + challenge: z.base64(), + }), + response: z.object({ + communities: z.array(z.object({})).optional(), + contacts: z.array( + z.object({ + calls: z.array(z.uuidv4()).optional(), + last_message_at: z.number(), + user_id: z.number(), + last_message: z + .object({ + content: z.base64(), + sender_id: z.number(), + }) + .optional(), + messages: z.array(message), + }), + ), + }), + }, get_user_data: { request: z.object({ user_id: z.number(), @@ -85,14 +98,6 @@ export const ttp = { username: z.string().max(15), }), }, - challenge_response: { - request: z.object({ - challenge: z.base64(), - }), - response: z.object({ - accepted: z.boolean(), - }), - }, ping: { request: z.object({ last_ping: z.number(), @@ -101,18 +106,6 @@ export const ttp = { ping_iota: z.number(), }), }, - get_conversations: { - request: z.object({}), - response: z.object({ - user_ids: z.array(conversation), - }), - }, - get_communities: { - request: z.object({}), - response: z.object({ - communities: z.array(community), - }), - }, message_live: { request: z.object({}), response: z.object({ @@ -166,6 +159,7 @@ export type TTP = typeof ttp; // Storage export interface Storage { + session_id: number; user_id: number; private_key: string; ppandtos_done: boolean; @@ -179,6 +173,7 @@ export interface Storage { } export const storageDefaults: Storage = { + session_id: 0, user_id: 0, private_key: "", ppandtos_done: false, diff --git a/packages/shared/src/features/conversation/schema.ts b/packages/shared/src/features/conversation/schema.ts deleted file mode 100644 index ddf4686..0000000 --- a/packages/shared/src/features/conversation/schema.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { z } from "zod"; - -export const conversation = z.object({ - user_id: z.number(), - calls: z.array(z.uuidv4()).optional(), - last_message_at: z.number(), - username: z.string().optional(), // Todo: Remove this -}); - -export const community = z.object({ - community_address: z.string(), - community_title: z.string(), - position: z.string(), -}); - -export type Conversation = z.infer; -export type Community = z.infer; diff --git a/packages/ttp/src/codec.ts b/packages/ttp/src/codec.ts index 26d5b61..b42d566 100644 --- a/packages/ttp/src/codec.ts +++ b/packages/ttp/src/codec.ts @@ -15,189 +15,193 @@ type DataKind = | { array: PrimitiveDataKind | "container" | "null" }; const COMMUNICATION_TYPES = [ - "error", - "error_protocol", - "error_anonymous", - "error_internal", - "error_invalid_data", - "error_invalid_user_id", - "error_invalid_omikron_id", - "error_not_found", - "error_not_authenticated", - "error_no_iota", - "error_invalid_challenge", - "error_invalid_secret", - "error_invalid_private_key", - "error_invalid_public_key", - "error_no_user_id", - "error_no_call_id", - "error_invalid_call_id", - "success", - "shorten_link", - "settings_save", - "settings_load", - "settings_list", - "message", - "message_state", - "message_send", - "message_live", - "message_other_iota", - "message_chunk", - "messages_get", - "push_notification", - "read_notification", - "get_notifications", - "change_confirm", - "confirm_receive", - "confirm_read", - "get_conversations", - "get_states", - "add_community", - "remove_community", - "get_communities", - "challenge", - "challenge_response", - "register", - "register_response", - "identification", - "identification_response", - "register_iota", - "register_iota_success", - "ping", - "pong", - "add_conversation", - "send_chat", - "client_changed", - "client_connected", - "client_disconnected", - "client_closed", - "public_key", - "private_key", - "webrtc_sdp", - "webrtc_ice", - "start_stream", - "end_stream", - "watch_stream", - "call_token", - "call_invite", - "call_disconnect_user", - "call_timeout_user", - "call_set_anonymous_joining", - "end_call", - "function", - "update", - "create_user", - "rho_update", - "user_connected", - "user_disconnected", - "iota_connected", - "iota_disconnected", - "sync_client_iota_status", - "get_user_data", - "get_iota_data", - "iota_user_data", - "change_user_data", - "change_iota_data", - "get_register", - "complete_register_user", - "complete_register_iota", - "delete_user", - "delete_iota", - "start_register", - "complete_register", + "error", // 0 + "error_protocol", // 1 + "error_anonymous", // 2 + "error_internal", // 3 + "error_invalid_data", // 4 + "error_invalid_user_id", // 5 + "error_invalid_omikron_id", // 6 + "error_not_found", // 7 + "error_not_authenticated", // 8 + "error_no_iota", // 9 + "error_invalid_challenge", // 10 + "error_invalid_secret", // 11 + "error_invalid_private_key", // 12 + "error_invalid_public_key", // 13 + "error_no_user_id", // 14 + "error_no_call_id", // 15 + "error_invalid_call_id", // 16 + "success", // 17 + "shorten_link", // 18 + "settings_save", // 19 + "settings_load", // 20 + "settings_list", // 21 + "message", // 22 + "message_state", // 23 + "message_send", // 24 + "message_live", // 25 + "message_other_iota", // 26 + "message_chunk", // 27 + "messages_get", // 28 + "push_notification", // 29 + "read_notification", // 30 + "get_notifications", // 31 + "change_confirm", // 32 + "confirm_receive", // 33 + "confirm_read", // 34 + "get_conversations", // 35 + "get_states", // 36 + "add_community", // 37 + "remove_community", // 38 + "get_communities", // 39 + "challenge", // 40 + "challenge_response", // 41 + "register", // 42 + "register_response", // 43 + "identification", // 44 + "identification_response", // 45 + "register_iota", // 46 + "register_iota_success", // 47 + "ping", // 48 + "pong", // 49 + "add_conversation", // 50 + "send_chat", // 51 + "client_changed", // 52 + "client_connected", // 53 + "client_disconnected", // 54 + "client_closed", // 55 + "public_key", // 56 + "private_key", // 57 + "webrtc_sdp", // 58 + "webrtc_ice", // 59 + "start_stream", // 60 + "end_stream", // 61 + "watch_stream", // 62 + "call_token", // 63 + "call_invite", // 64 + "call_disconnect_user", // 65 + "call_timeout_user", // 66 + "call_set_anonymous_joining", // 67 + "end_call", // 68 + "function", // 69 + "update", // 70 + "create_user", // 71 + "rho_update", // 72 + "user_connected", // 73 + "user_disconnected", // 74 + "iota_connected", // 75 + "iota_disconnected", // 76 + "sync_client_iota_status", // 77 + "get_user_data", // 78 + "get_iota_data", // 79 + "iota_user_data", // 80 + "change_user_data", // 81 + "change_iota_data", // 82 + "get_register", // 83 + "complete_register_user", // 84 + "complete_register_iota", // 85 + "delete_user", // 86 + "delete_iota", // 87 + "start_register", // 88 + "complete_register", // 89 ] as const; const DATA_TYPES = [ - "error_type", - "error_protocol", - "accepted_ids", - "uuid", - "register_id", - "link", - "settings", - "settings_name", - "chat_partner_id", - "chat_partner_name", - "iota_id", - "user_id", - "user_ids", - "iota_ids", - "user_state", - "user_states", - "user_pings", - "call_state", - "screen_share", - "private_key_hash", - "accepted", - "accepted_profiles", - "denied_profiles", - "content", - "messages", - "notifications", - "timestamp", - "get_time", - "get_variant", - "shared_secret_own", - "shared_secret_other", - "shared_secret_sign", - "shared_secret", - "call_id", - "call_token", - "untill", - "enabled", - "start_date", - "end_date", - "receiver_id", - "sender_id", - "signature", - "signed", - "message", - "message_state", - "last_ping", - "ping_iota", - "ping_clients", - "matches", - "omikron", - "offset", - "amount", - "position", - "name", - "path", - "codec", - "function", - "payload", - "result", - "interactables", - "want_to_watch", - "watcher", - "created_at", - "username", - "display", - "avatar", - "about", - "status", - "public_key", - "sub_level", - "sub_end", - "community_address", - "challenge", - "community_title", - "communities", - "rho_connections", - "user", - "online_status", - "omikron_id", - "omikron_connections", - "reset_token", - "new_token", - "call_invited", - "call_members", - "calls", - "timeout", - "has_admin", - "last_message_at", - "height", - "sent_by_self", + "error_type", // 0 + "error_protocol", // 1 + "accepted_ids", // 2 + "uuid", // 3 + "register_id", // 4 + "link", // 5 + "settings", // 6 + "settings_name", // 7 + "chat_partner_id", // 8 + "chat_partner_name", // 9 + "iota_id", // 10 + "user_id", // 11 + "user_ids", // 12 + "iota_ids", // 13 + "user_state", // 14 + "user_states", // 15 + "user_pings", // 16 + "call_state", // 17 + "screen_share", // 18 + "private_key_hash", // 19 + "accepted", // 20 + "accepted_profiles", // 21 + "denied_profiles", // 22 + "content", // 23 + "messages", // 24 + "notifications", // 25 + "timestamp", // 26 + "get_time", // 27 + "get_variant", // 28 + "shared_secret_own", // 29 + "shared_secret_other", // 30 + "shared_secret_sign", // 31 + "shared_secret", // 32 + "call_id", // 33 + "call_token", // 34 + "untill", // 35 + "enabled", // 36 + "start_date", // 37 + "end_date", // 38 + "receiver_id", // 39 + "sender_id", // 40 + "signature", // 41 + "signed", // 42 + "message", // 43 + "message_state", // 44 + "last_ping", // 45 + "ping_iota", // 46 + "ping_clients", // 47 + "matches", // 48 + "omikron", // 49 + "offset", // 50 + "amount", // 51 + "position", // 52 + "name", // 53 + "path", // 54 + "codec", // 55 + "function", // 56 + "payload", // 57 + "result", // 58 + "interactables", // 59 + "want_to_watch", // 60 + "watcher", // 61 + "created_at", // 62 + "username", // 63 + "display", // 64 + "avatar", // 65 + "about", // 66 + "status", // 67 + "public_key", // 68 + "sub_level", // 69 + "sub_end", // 70 + "community_address", // 71 + "challenge", // 72 + "community_title", // 73 + "communities", // 74 + "rho_connections", // 75 + "user", // 76 + "online_status", // 77 + "omikron_id", // 78 + "omikron_connections", // 79 + "reset_token", // 80 + "new_token", // 81 + "call_invited", // 82 + "call_members", // 83 + "calls", // 84 + "timeout", // 85 + "has_admin", // 86 + "last_message_at", // 87 + "height", // 88 + "sent_by_self", // 89 + "session_id", // 90 + "contacts", // 91 + "last_message", // 92 + "version", // 93 ] as const; const COMMUNICATION_TYPE_BY_NAME = createIndexMap(COMMUNICATION_TYPES); @@ -227,6 +231,7 @@ registerDataKinds("number", [ "sub_end", "last_message_at", "height", + "session_id", ]); registerDataKinds("string", [ @@ -260,6 +265,7 @@ registerDataKinds("string", [ "call_token", "challenge", "online_status", + "version", ]); registerDataKinds({ array: "container" }, [ @@ -269,6 +275,7 @@ registerDataKinds({ array: "container" }, [ "matches", "get_conversations", "get_communities", + "contacts", ]); registerDataKinds({ array: "number" }, [ @@ -289,6 +296,7 @@ registerDataKinds("container", [ "result", "ping_clients", "user_pings", + "last_message", ]); registerDataKinds("bool", [ diff --git a/packages/ttp/src/context.tsx b/packages/ttp/src/context.tsx index 64ad375..8a25933 100644 --- a/packages/ttp/src/context.tsx +++ b/packages/ttp/src/context.tsx @@ -20,10 +20,17 @@ import { RETRY_INTERVAL, TRANSPORT_URL, } from "./values"; -import { ttp as schemas, type TTP as Schemas } from "@tensamin/shared/data"; +import { + ttp as schemas, + ttp, + type TTP as Schemas, +} from "@tensamin/shared/data"; import Loading from "@tensamin/ui/screens/loading"; import ErrorScreen from "@tensamin/ui/screens/error"; +import { version } from "../../../package.json"; +import z from "zod"; + const FATAL_IDENTIFICATION_ERROR_TYPES = new Set([ "error", "error_invalid_user_id", @@ -131,10 +138,14 @@ function getProtocolErrorDetails(error: unknown) { type ContextType = { send: BoundSendFn; subscribePush: (handler: PushHandler) => () => void; - readyState: () => number; - ownPing: () => number; - iotaPing: () => number; - identified: () => boolean; + readyState: number; + ownPing: number; + iotaPing: number; + identified: boolean; + contacts: z.infer; + communities: z.infer< + typeof ttp.challenge_response.response.shape.communities + >; }; const TTPContext = createContext(undefined); @@ -162,6 +173,13 @@ export default function Provider(props: { const [error, setError] = useState(""); const [errorDescription, setErrorDescription] = useState(""); + const [communities, setCommunities] = useState< + z.infer + >([]); + const [contacts, setContacts] = useState< + z.infer + >([]); + const clientRef = useRef > | null>(null); @@ -352,9 +370,7 @@ export default function Provider(props: { return; } - log(0, "ttp", "red", "Disconnected", closeError, { - stopSending: isStopSendingError(closeError), - }); + log(0, "ttp", "red", "Disconnected", closeError); scheduleReconnect(closeError); }, }) @@ -426,40 +442,69 @@ export default function Provider(props: { */ const identify = async () => { try { + const sessionId = await load("session_id"); const userId = await load("user_id"); const privateKey = await load("private_key"); - if (!Number.isSafeInteger(userId) || userId <= 0) { - throw new Error("Missing or invalid user id for identification"); - } - - if (privateKey.trim() === "") { - throw new Error("Missing private key for identification"); + if ( + !Number.isSafeInteger(userId) || + userId <= 0 || + privateKey.trim() === "" || + !Number.isSafeInteger(sessionId) || + sessionId <= 0 + ) { + throw new Error("Invalid credentials"); } + // Challenge Request const challengeEnvelope = await send("identification", { + version, + session_id: sessionId, user_id: userId, + }).catch((challengeError) => { + throw new Error( + "Failed to obtain identification challenge from server", + challengeError, + ); }); + // Shared Secret const sharedSecret = await getSharedSecret( privateKey, "", challengeEnvelope.data.public_key, - ); + ).catch((secretError) => { + throw new Error( + "Failed to derive shared secret for identification", + secretError, + ); + }); + // Challenge Decryption const decryptedChallenge = await decrypt( sharedSecret, challengeEnvelope.data.challenge, - ); - - const verification = await send("challenge_response", { - challenge: decryptedChallenge, + ).catch((decryptionError) => { + throw new Error( + "Failed to decrypt identification challenge", + decryptionError, + ); }); - if (verification.data.accepted !== true) { - throw new Error("Identification challenge was rejected"); - } + // Challenge Response + const finalResponse = await send("challenge_response", { + challenge: decryptedChallenge, + }).catch((error) => { + setError("Identification Failed"); + setErrorDescription( + "Unable to complete secure identification. Please verify your credentials and try again.", + ); + throw error; + }); + // Data handling + setContacts(finalResponse.data.contacts); + setCommunities(finalResponse.data.communities); if (cancelled) { return; } @@ -547,18 +592,6 @@ export default function Provider(props: { return undefined; }, [connected, identified, identifying, readyState]); - const contextValue = useMemo( - () => ({ - send, - subscribePush, - readyState: () => readyState, - ownPing: () => ownPing, - iotaPing: () => iotaPing, - identified: () => identified, - }), - [identified, iotaPing, ownPing, readyState, send, subscribePush], - ); - if (error !== "" && errorDescription !== "") { return ; } @@ -575,7 +608,18 @@ export default function Provider(props: { } return ( - + {props.children} );