Updated icons, new identification
|
|
@ -5,3 +5,4 @@ coverage
|
|||
*.tsbuildinfo
|
||||
bun.lock
|
||||
apps/desktop/build
|
||||
apps/mobile/src-tauri
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { ElectrobunConfig } from "electrobun";
|
||||
|
||||
import { version } from "@tensamin/shared/package.json";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
export default {
|
||||
app: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@tensamin/mobile",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./context": "./src/context.tsx"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
</resources>
|
||||
|
|
@ -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 -",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<HTMLInputElement | null>(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 (
|
||||
|
|
|
|||
|
|
@ -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<contextValue | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Executes ConversationProvider.
|
||||
* @param props Parameter props.
|
||||
* @returns unknown.
|
||||
*/
|
||||
export default function ConversationProvider(props: { children: ReactNode }) {
|
||||
const [conversations, setConversations] = useState<Conversation[] | null>(
|
||||
null,
|
||||
);
|
||||
const [communities, setCommunities] = useState<Community[] | null>(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 (
|
||||
<ConversationContext.Provider
|
||||
value={{
|
||||
conversations,
|
||||
communities,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</ConversationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
|
@ -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<HTMLDivElement | null>(null);
|
||||
|
||||
|
|
@ -66,14 +66,14 @@ export default function List() {
|
|||
}}
|
||||
>
|
||||
{category === "conversations" ? (
|
||||
conversations?.[virtualItem.index] ? (
|
||||
contacts?.[virtualItem.index] ? (
|
||||
<ConversationModal
|
||||
userId={conversations[virtualItem.index].user_id}
|
||||
userId={contacts[virtualItem.index].user_id}
|
||||
/>
|
||||
) : null
|
||||
) : communities?.[virtualItem.index] ? (
|
||||
<CommunityModal
|
||||
community={communities[virtualItem.index]}
|
||||
//community={communities[virtualItem.index]}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 <div>{props.community.community_title}</div>;
|
||||
export default function CommunityModal() {
|
||||
return <div>Not implemented</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<SocketContext>
|
||||
<UserContext>
|
||||
<CallContext>
|
||||
<ConversationContext>
|
||||
<AppLayout>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
</ConversationContext>
|
||||
</CallContext>
|
||||
</UserContext>
|
||||
</SocketContext>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "tensamin",
|
||||
"version": "0.1",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<typeof conversation>;
|
||||
export type Community = z.infer<typeof community>;
|
||||
|
|
@ -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", [
|
||||
|
|
|
|||
|
|
@ -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<Schemas>;
|
||||
subscribePush: (handler: PushHandler) => () => void;
|
||||
readyState: () => number;
|
||||
ownPing: () => number;
|
||||
iotaPing: () => number;
|
||||
identified: () => boolean;
|
||||
readyState: number;
|
||||
ownPing: number;
|
||||
iotaPing: number;
|
||||
identified: boolean;
|
||||
contacts: z.infer<typeof ttp.challenge_response.response.shape.contacts>;
|
||||
communities: z.infer<
|
||||
typeof ttp.challenge_response.response.shape.communities
|
||||
>;
|
||||
};
|
||||
|
||||
const TTPContext = createContext<ContextType | undefined>(undefined);
|
||||
|
|
@ -162,6 +173,13 @@ export default function Provider(props: {
|
|||
const [error, setError] = useState("");
|
||||
const [errorDescription, setErrorDescription] = useState("");
|
||||
|
||||
const [communities, setCommunities] = useState<
|
||||
z.infer<typeof ttp.challenge_response.response.shape.communities>
|
||||
>([]);
|
||||
const [contacts, setContacts] = useState<
|
||||
z.infer<typeof ttp.challenge_response.response.shape.contacts>
|
||||
>([]);
|
||||
|
||||
const clientRef = useRef<ReturnType<
|
||||
typeof createTransportClient<Schemas>
|
||||
> | 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,
|
||||
).catch((decryptionError) => {
|
||||
throw new Error(
|
||||
"Failed to decrypt identification challenge",
|
||||
decryptionError,
|
||||
);
|
||||
|
||||
const verification = await send("challenge_response", {
|
||||
challenge: decryptedChallenge,
|
||||
});
|
||||
|
||||
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<ContextType>(
|
||||
() => ({
|
||||
send,
|
||||
subscribePush,
|
||||
readyState: () => readyState,
|
||||
ownPing: () => ownPing,
|
||||
iotaPing: () => iotaPing,
|
||||
identified: () => identified,
|
||||
}),
|
||||
[identified, iotaPing, ownPing, readyState, send, subscribePush],
|
||||
);
|
||||
|
||||
if (error !== "" && errorDescription !== "") {
|
||||
return <ErrorScreen error={error} description={errorDescription} />;
|
||||
}
|
||||
|
|
@ -575,7 +608,18 @@ export default function Provider(props: {
|
|||
}
|
||||
|
||||
return (
|
||||
<TTPContext.Provider value={contextValue}>
|
||||
<TTPContext.Provider
|
||||
value={{
|
||||
send,
|
||||
subscribePush,
|
||||
readyState,
|
||||
ownPing,
|
||||
iotaPing,
|
||||
identified,
|
||||
contacts,
|
||||
communities,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</TTPContext.Provider>
|
||||
);
|
||||
|
|
|
|||