Skip to content

Commit

Permalink
client-web: tweak user profile handling
Browse files Browse the repository at this point in the history
  • Loading branch information
franzos committed Sep 30, 2023
1 parent 2a15503 commit 10c0eb2
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 65 deletions.
23 changes: 20 additions & 3 deletions client-web/src/layouts/primary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,18 @@ import FormatListBulletedIcon from "mdi-react/FormatListBulletedIcon";
import AccountKeyIcon from "mdi-react/AccountKeyIcon";
import AccountMultipleIcon from "mdi-react/AccountMultipleIcon";
import PlaylistEditIcon from "mdi-react/PlaylistEditIcon";
import AccountEditIcon from "mdi-react/AccountEditIcon";
import { ConnectModal } from "../components/connect-modal";
import Logo from "../assets/logo.svg";
import { DEFAULT_RELAYS } from "../defaults";
import { objectOfRelaysToArray } from "../lib/object-of-relays-to-array";
import { UserIcon } from "../components/user-icon";

export function PrimaryLayout() {
const [connected, keystore, publicKey] = useNClient((state) => [
const [connected, keystore, publicKey, activeUser] = useNClient((state) => [
state.connected,
state.keystore,
state.keypair?.publicKey || "",
state.activeUser,
]);
const [followingUsersCount, setFollowingUsersCount] = useState<number>(0);

Expand Down Expand Up @@ -64,6 +65,12 @@ export function PrimaryLayout() {

{connected && (
<>
{/* <MenuItem
label="Notifications"
to="/notifications"
leftIcon={<Icon as={AccountMultipleIcon} marginRight={1} />}
/> */}

<MenuItem
label="Following"
value={followingUsersCount}
Expand All @@ -81,7 +88,17 @@ export function PrimaryLayout() {
<MenuItem
label="Profile"
to="/profile"
leftIcon={<Icon as={AccountEditIcon} marginRight={1} />}
leftIcon={
<Icon
as={UserIcon}
user={activeUser}
opts={{
avatarSize: "xs",
relayUrls: [],
}}
marginRight={1}
/>
}
/>
)}
</>
Expand Down
54 changes: 13 additions & 41 deletions client-web/src/routes/user-profile.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,28 @@
import {
CLIENT_MESSAGE_TYPE,
NEVENT_KIND,
NFilters,
UserBase,
} from "@nostr-ts/common";
import { UserProfileForm } from "../components/user-profile-form";
import { useNClient } from "../state/client";
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { User } from "../components/user";
import { Box, Text, Button, useToast, Heading } from "@chakra-ui/react";

export function UserProfileRoute() {
const [pubkey, isOnline] = useNClient((state) => [
const [pubkey, isOnline, activeUser] = useNClient((state) => [
state.keypair?.publicKey || "",
state.connected,
state.activeUser,
]);
const [user, setUser] = useState<UserBase | null>(null);
const [relayUrls, setrelayUrls] = useState<string[]>([]);

const toast = useToast();

const init = async () => {
if (!pubkey || pubkey === "") return;
const userRecord = await useNClient.getState().getUser(pubkey);
if (userRecord) {
setUser(userRecord.user);
setrelayUrls(userRecord.relayUrls);
}
await useNClient.getState().getAndSetActiveUser({
retry: true,
});
};

const refreshData = async () => {
if (!pubkey || pubkey === "") {
toast({
title: "Not found",
description:
"No user data found yet. Did you publish your profile to one of the connected relays?",
status: "error",
duration: 5000,
isClosable: true,
});
return;
}
await useNClient.getState().subscribe({
type: CLIENT_MESSAGE_TYPE.REQ,
filters: new NFilters({
authors: [pubkey],
kinds: [NEVENT_KIND.METADATA],
}),
options: {
timeoutIn: 10000,
view: "user-profile",
},
await useNClient.getState().getAndSetActiveUser({
retry: false,
updateFromRelays: true,
});
toast({
title: "Refreshing ...",
Expand All @@ -73,11 +45,11 @@ export function UserProfileRoute() {

return (
<Box>
{user ? (
{activeUser ? (
<User
user={user}
user={activeUser}
opts={{
relayUrls,
relayUrls: [],
}}
/>
) : (
Expand All @@ -96,7 +68,7 @@ export function UserProfileRoute() {
<UserProfileForm
props={{
pubkey,
metadata: user?.data,
metadata: activeUser?.data,
}}
/>
</Box>
Expand Down
16 changes: 15 additions & 1 deletion client-web/src/state/base-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
UserRecord,
UserPublicKeyAndRelays,
LightProcessedEvent,
UserBase,
} from "@nostr-ts/common";

export interface NClientBase {
Expand All @@ -43,10 +44,23 @@ export interface NClientBase {
) => Promise<Subscription[] | undefined>;

maxEvents: number;
getUser: (pubkey: string) => Promise<UserRecord | undefined>;
getUser: (
pubkey: string,
options?: {
view?: string;
retryCount?: number;
relayUrls?: string[];
}
) => Promise<UserRecord | undefined>;
addUser: (payload: ProcessedUserBase) => Promise<void>;
updateUser: (pubkey: string, payload: ProcessedUserBase) => Promise<void>;
countUsers: () => Promise<number>;
getAndSetActiveUser: (options: {
retry?: boolean;
retryCount?: number;
updateFromRelays?: boolean;
}) => Promise<void>;
activeUser: UserBase | undefined;
/**
* Process websocket events
*/
Expand Down
4 changes: 2 additions & 2 deletions client-web/src/state/client-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "@nostr-ts/common";
import { Remote } from "comlink";
import { NClientBase } from "./base-types";
import { NClientKeystore } from "./keystore";
import { NClientLocalStore, NClientNos2xStore } from "./keystore";
import {
NWorker,
StorageEventsQuery,
Expand Down Expand Up @@ -50,7 +50,7 @@ export interface NClient extends NClientBase {
loadKeyStore: () => void;
saveKeyStore: () => void;
resetKeyStore: () => void;
setKeyStore: (config: NClientKeystore) => void;
setKeyStore: (config: NClientLocalStore | NClientNos2xStore) => void;
keypair: { publicKey: string; privateKey?: string };
keypairIsLoaded: boolean;

Expand Down
124 changes: 107 additions & 17 deletions client-web/src/state/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import { create } from "zustand";
import { MAX_EVENTS } from "../defaults";
import { NClient } from "./client-types";
import {
NClientKeystore,
NClientLocalStore,
NClientNos2xStore,
loadKeyStoreConfig,
saveKeyStoreConfig,
} from "./keystore";
Expand Down Expand Up @@ -284,34 +285,43 @@ export const useNClient = create<NClient>((set, get) => ({
});
get().store.setUserPubkey("");
},
setKeyStore: (config: NClientKeystore) => {
setKeyStore: (config: NClientLocalStore | NClientNos2xStore) => {
if (config.keystore === "localstore") {
if (config.publicKey) {
set({
keystore: config.keystore,
keypair: {
publicKey: config.publicKey,
privateKey: config.privateKey || "",
},
keypairIsLoaded: true,
});
get().saveKeyStore();
set({
keystore: config.keystore,
keypair: {
publicKey: config.publicKey,
privateKey: config.privateKey,
},
keypairIsLoaded: true,
activeUser: {
pubkey: config.publicKey,
},
});
get().saveKeyStore();

get().store.setUserPubkey(config.publicKey);
}
get().store.setUserPubkey(config.publicKey);
} else if (config.keystore === "nos2x") {
set({
keystore: config.keystore,
keypair: {
publicKey: config.publicKey || "",
publicKey: config.publicKey,
},
keypairIsLoaded: true,
activeUser: {
pubkey: config.publicKey,
},
});

get().store.setUserPubkey(config.publicKey || "");
get().store.setUserPubkey(config.publicKey);
} else {
console.error(`Unknown keystore ${config.keystore}`);
console.error(`Unsupported keystore options`);
return;
}

get().getAndSetActiveUser({
retry: true,
});
},
keypair: { publicKey: "", privateKey: "" },
keypairIsLoaded: false,
Expand Down Expand Up @@ -651,6 +661,86 @@ export const useNClient = create<NClient>((set, get) => ({
countUsers: async () => {
return get().store.countUsers();
},
getAndSetActiveUser: async ({
retry,
retryCount,
updateFromRelays,
}: {
retry?: boolean;
retryCount?: number;
updateFromRelays?: boolean;
}) => {
// Get public key
const key = get().keypair.publicKey;
if (!key) {
console.warn(`No keypair found; User not logged-in.`);
return;
}

// Abort if tried < 10 times
if (retryCount && retryCount > 10) {
console.warn(`Exceeded 10 retries to fetch user info from relays.`);
return;
}

// Get user from DB
const userData = await get().getUser(key);
if (userData) {
set({
activeUser: userData.user,
});
} else {
console.log(`Keypair found, but user record not available offline.`);
}

// Check if update has been requested
if (updateFromRelays) {
await get().requestInformation(
{
idsOrKeys: [key],
source: "users",
},
{
timeoutIn: 10000,
}
);

setTimeout(async () => {
await get().getAndSetActiveUser({
retry,
retryCount,
});
}, 5000);
return;
} else if (!updateFromRelays && userData) {
// If not update has been requested, and we got the user data
return;
}

// Retry, if enabled
if (retry) {
console.log(`Retrying ... ${retryCount} / 10`);
// If 2nd try, fetch user info from relays
if (retryCount && retryCount === 2) {
await get().requestInformation(
{
idsOrKeys: [key],
source: "users",
},
{
timeoutIn: 10000,
}
);
}
setTimeout(async () => {
await get().getAndSetActiveUser({
retry,
retryCount: retryCount ? retryCount + 1 : undefined,
});
}, 1000);
}
},
activeUser: undefined,
eventProofOfWork: async (event: NEvent, bits: number) => {
return new Promise((resolve) => {
const worker = new Worker(new URL("./pow-worker.ts", import.meta.url), {
Expand Down
12 changes: 11 additions & 1 deletion client-web/src/state/keystore.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
export interface NClientKeystore {
interface NClientKeystore {
keystore: "none" | "localstore" | "nos2x" | "download";
publicKey?: string;
privateKey?: string;
}

export interface NClientLocalStore extends NClientKeystore {
keystore: "localstore";
publicKey: string;
}

export interface NClientNos2xStore extends NClientKeystore {
keystore: "nos2x";
publicKey: string;
}

export function loadKeyStoreConfig(): NClientKeystore {
const keystore = localStorage.getItem("nostr-client:keystore:keystore");
if (keystore) {
Expand Down

0 comments on commit 10c0eb2

Please sign in to comment.