Fixed lint problems, fixed builds

This commit is contained in:
Alois 2026-04-12 00:20:24 +02:00
commit f018dafa4f
7 changed files with 87 additions and 61 deletions

View file

@ -36,7 +36,7 @@ function getSafeMessageHeight(height: number | undefined) {
* @param props Parameter props.
* @returns unknown.
*/
function MessageComponent(props: {
function MessageComponent({ message, notEncrypted, measureRef }: {
message: RawMessage & {
failed?: boolean;
};
@ -50,16 +50,16 @@ function MessageComponent(props: {
const [decodedContent, setDecodedContent] = React.useState("");
const [isReady, setIsReady] = React.useState(false);
const safeMessageHeight = getSafeMessageHeight(props.message.height);
const safeMessageHeight = getSafeMessageHeight(message.height);
React.useEffect(() => {
if (props.notEncrypted) {
setDecodedContent(props.message.content);
if (notEncrypted) {
setDecodedContent(message.content);
setIsReady(true);
return;
}
const content = props.message.content;
const content = message.content;
const secret = sharedSecret;
let active = true;
@ -94,27 +94,27 @@ function MessageComponent(props: {
return () => {
active = false;
};
}, [decrypt, props.message.content, props.notEncrypted, sharedSecret]);
}, [decrypt, message.content, notEncrypted, sharedSecret]);
return (
<div
ref={props.measureRef}
className={`w-full flex justify-start transition-opacity duration-150 ${(props.message.failed || props.message.message_state === "awaiting") && "opacity-50"}`}
ref={measureRef}
className={`w-full flex justify-start transition-opacity duration-150 ${(message.failed || message.message_state === "awaiting") && "opacity-50"}`}
>
<ContextMenu>
<ContextMenuTrigger
render={
<div
className={`selection-foreground flex gap-1 items-center justify-center max-w-[80%] rounded-lg px-2 py-px whitespace-pre-wrap break-all ${
props.message.sent_by_self
? props.message.failed
message.sent_by_self
? message.failed
? "bg-destructive/75 text-destructive-foreground"
: "bg-primary text-primary-foreground"
: "bg-muted"
} ${!isReady && "animate-pulse"} ${isMobile && "select-none"}`}
>
{props.message.failed &&
props.message.message_state === "awaiting" && (
{message.failed &&
message.message_state === "awaiting" && (
<Tooltip>
<TooltipContent>
<p>Failed to send message</p>
@ -122,8 +122,8 @@ function MessageComponent(props: {
<TooltipTrigger render={<AlertTriangle size={17} />} />
</Tooltip>
)}
{!props.message.failed &&
props.message.message_state === "awaiting" && (
{!message.failed &&
message.message_state === "awaiting" && (
<Clock size={17} />
)}
{isReady ? (
@ -133,7 +133,7 @@ function MessageComponent(props: {
className="block rounded-sm"
style={{
width: generateFixedLoadingSize(
props.message.content.length,
message.content.length,
safeMessageHeight,
),
minHeight: safeMessageHeight,

View file

@ -61,7 +61,13 @@ export default function Provider(props: { children: ReactNode }) {
const { send, subscribePush } = useTTP();
const [liveMessagesState, setLiveMessagesState] = useState<LiveMessage[]>([]);
const [currentSharedSecret, setCurrentSharedSecret] = useState("");
const [currentSharedSecretState, setCurrentSharedSecretState] = useState<{
userId: number;
value: string;
}>({
userId: 0,
value: "",
});
const inputBoxRef = useRef<HTMLDivElement>(null);
@ -75,16 +81,13 @@ export default function Provider(props: { children: ReactNode }) {
return Number(rawId ?? 0);
}, [locationSearch]);
const userIdValueFromLastRender = useRef(userIdValue);
useEffect(() => {
if (userIdValue === userIdValueFromLastRender.current) {
return;
const currentSharedSecret = useMemo(() => {
if (currentSharedSecretState.userId !== userIdValue) {
return "";
}
userIdValueFromLastRender.current = userIdValue;
setCurrentSharedSecret("");
}, [userIdValue]);
return currentSharedSecretState.value;
}, [currentSharedSecretState, userIdValue]);
// Load shared secret
useEffect(() => {
@ -105,11 +108,17 @@ export default function Provider(props: { children: ReactNode }) {
);
if (active) {
setCurrentSharedSecret(sharedSecret);
setCurrentSharedSecretState({
userId: userIdValue,
value: sharedSecret,
});
}
} catch {
if (active) {
setCurrentSharedSecret("");
setCurrentSharedSecretState({
userId: userIdValue,
value: "",
});
}
}
})();

View file

@ -100,6 +100,7 @@ export default function Screen() {
return messagesRef.current[index]?.timestamp ?? index;
}, []);
// eslint-disable-next-line react-hooks/incompatible-library
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => scrollRef.current,

View file

@ -33,8 +33,28 @@ export const context = React.createContext<CryptoContextType | undefined>(
export default function Provider(props: { children: React.ReactNode }) {
const apiRef = React.useRef<ApiRef | null>(null);
const value = React.useMemo(
() => createCryptoActions(() => apiRef.current),
const value = React.useMemo<CryptoContextType>(
() => ({
encrypt: async (secret, plaintext) => {
const api = apiRef.current;
if (!api) throw new Error("API not initialized");
return await api.encrypt(secret, plaintext);
},
decrypt: async (secret, ciphertext) => {
const api = apiRef.current;
if (!api) throw new Error("API not initialized");
return await api.decrypt(secret, ciphertext);
},
getSharedSecret: async (ownPrivateKey, ownPublicKey, otherPublicKey) => {
const api = apiRef.current;
if (!api) throw new Error("API not initialized");
return await api.getSharedSecret(
ownPrivateKey,
ownPublicKey,
otherPublicKey,
);
},
}),
[],
);
@ -56,11 +76,11 @@ export default function Provider(props: { children: React.ReactNode }) {
/**
* Creates crypto action functions that safely delegate to the worker API.
* @param getApiRef Function that returns worker API reference when initialized.
* @param apiRef Worker API reference object.
* @returns Typed crypto action functions.
*/
export function createCryptoActions(
getApiRef: () => ApiRef | null,
apiRef: React.RefObject<ApiRef | null>,
): CryptoContextType {
/**
* Encrypts plaintext by delegating to the crypto worker API.
@ -72,9 +92,9 @@ export function createCryptoActions(
secret: string,
plaintext: string,
): Promise<string> => {
const apiRef = getApiRef();
if (!apiRef) throw new Error("API not initialized");
return await apiRef.encrypt(secret, plaintext);
const api = apiRef.current;
if (!api) throw new Error("API not initialized");
return await api.encrypt(secret, plaintext);
};
/**
@ -87,9 +107,9 @@ export function createCryptoActions(
secret: string,
ciphertext: string,
): Promise<string> => {
const apiRef = getApiRef();
if (!apiRef) throw new Error("API not initialized");
return await apiRef.decrypt(secret, ciphertext);
const api = apiRef.current;
if (!api) throw new Error("API not initialized");
return await api.decrypt(secret, ciphertext);
};
/**
@ -104,9 +124,9 @@ export function createCryptoActions(
ownPublicKey: string,
otherPublicKey: string,
): Promise<string> => {
const apiRef = getApiRef();
if (!apiRef) throw new Error("API not initialized");
return await apiRef.getSharedSecret(
const api = apiRef.current;
if (!api) throw new Error("API not initialized");
return await api.getSharedSecret(
ownPrivateKey,
ownPublicKey,
otherPublicKey,

View file

@ -6,7 +6,7 @@ import { useStorage } from "@tensamin/storage/context";
// Wrapper function to pass user data to some component
export default function Wrapper(props: {
userId?: number | "own";
userId: number | "own";
loading: React.ReactNode;
component: (user: User) => React.ReactNode;
}) {
@ -15,40 +15,34 @@ export default function Wrapper(props: {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
if (props.userId == null) {
setUser(failedUser);
return;
}
if (props.userId == null) return;
let active = true;
if (props.userId === "own") {
load("user_id")
.then((id) => {
get(id)
.then((user) => (active ? setUser(user) : null))
.catch(() => setUser(failedUser));
})
.catch(() => setUser(failedUser));
return;
}
void (async () => {
try {
const userId =
props.userId === "own" ? await load("user_id") : props.userId;
const value = await get(userId);
get(props.userId)
.then((value) => {
if (active) {
setUser(value);
}
})
.catch(() => {
} catch {
if (active) {
setUser(failedUser);
}
});
}
})();
return () => {
active = false;
};
}, [load, get, props.userId]);
if (props.userId == null) {
return <>{props.component(failedUser)}</>;
}
return <>{user ? props.component(user) : props.loading}</>;
}