Updated icons, new identification

This commit is contained in:
Alois 2026-04-06 00:22:30 +02:00
commit 7adb2dee05
34 changed files with 331 additions and 402 deletions

View file

@ -4,4 +4,5 @@ coverage
.tmp
*.tsbuildinfo
bun.lock
apps/desktop/build
apps/desktop/build
apps/mobile/src-tauri

View file

@ -1,6 +1,6 @@
import type { ElectrobunConfig } from "electrobun";
import { version } from "@tensamin/shared/package.json";
import { version } from "../../package.json";
export default {
app: {

View file

@ -1,7 +1,7 @@
{
"name": "@tensamin/mobile",
"private": true,
"version": "0.1.0",
"version": "0.0.0",
"type": "module",
"exports": {
"./context": "./src/context.tsx"

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>

View file

@ -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 -",

View file

@ -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);

View file

@ -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 (

View file

@ -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;
}

View file

@ -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>

View file

@ -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>;
}

View file

@ -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);

View file

@ -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>
<AppLayout>
<Outlet />
</AppLayout>
</CallContext>
</UserContext>
</SocketContext>

View file

@ -1,5 +1,6 @@
{
"name": "tensamin",
"version": "0.1",
"private": true,
"workspaces": [
"packages/*",

View file

@ -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,

View file

@ -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>;

View file

@ -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", [

View file

@ -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,
);
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<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>
);