(feat): add cache package
(feat): improve local storage security (feat): move settings to dedicated settings package
This commit is contained in:
parent
fb095db7a6
commit
790a1db788
54 changed files with 1984 additions and 947 deletions
|
|
@ -16,6 +16,7 @@
|
|||
"build": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tensamin/cache": "workspace:*",
|
||||
"@tanstack/pacer": "^0.21.1",
|
||||
"@tanstack/react-query": "^5.0.0",
|
||||
"@tanstack/react-router": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import { useMTP } from "@tensamin/mtp";
|
|||
import { log, toast } from "@tensamin/shared/log";
|
||||
import { useSession } from "@tensamin/storage/session";
|
||||
import { useUser } from "@tensamin/user/context";
|
||||
import { createCache } from "@tensamin/cache";
|
||||
import { secureValueCodec } from "@tensamin/storage/secure";
|
||||
|
||||
export const context = createContext<contextType | undefined>(undefined);
|
||||
|
||||
|
|
@ -158,6 +160,7 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
const [errorDescription, setErrorDescription] = useState("");
|
||||
|
||||
const [liveMessagesState, setLiveMessagesState] = useState<LiveMessage[]>([]);
|
||||
const [ownId, setOwnId] = useState(0);
|
||||
const [currentChatSecretState, setCurrentChatSecretState] = useState<{
|
||||
userId: number;
|
||||
value: Uint8Array | null;
|
||||
|
|
@ -186,6 +189,10 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
return currentChatSecretState.value;
|
||||
}, [currentChatSecretState, userIdValue]);
|
||||
|
||||
useEffect(() => {
|
||||
load("user_id").then(setOwnId);
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userIdValue) return;
|
||||
|
||||
|
|
@ -328,6 +335,44 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
[load, send],
|
||||
);
|
||||
|
||||
const decryptMessages = useCallback(
|
||||
async (messages: RawMessages) => {
|
||||
if (!currentChatSecret) return [];
|
||||
return Promise.all(
|
||||
messages.map(async (message) => {
|
||||
try {
|
||||
return {
|
||||
...message,
|
||||
Content: await decryptChatText(
|
||||
currentChatSecret,
|
||||
message.Content,
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
log(1, "chat", "red", "Failed to decrypt historical message", err, {
|
||||
SendTime: message.SendTime,
|
||||
});
|
||||
return {
|
||||
...message,
|
||||
Content: "Failed to decrypt message",
|
||||
decryptionFailed: true,
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
[currentChatSecret],
|
||||
);
|
||||
|
||||
const getCachedMessages = useCallback(async () => {
|
||||
if (!ownId || !userIdValue || !currentChatSecret) return [];
|
||||
const cache = createCache(String(ownId), {
|
||||
codec: secureValueCodec,
|
||||
});
|
||||
const window = await cache.conversations.get(userIdValue);
|
||||
return decryptMessages(window?.Messages ?? []);
|
||||
}, [currentChatSecret, decryptMessages, ownId, userIdValue]);
|
||||
|
||||
const getMessages = useCallback(
|
||||
async (amount: number, offset: number) => {
|
||||
if (!currentChatSecret) {
|
||||
|
|
@ -359,36 +404,22 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
|
||||
return await Promise.all(
|
||||
sorted.map(async (message) => {
|
||||
try {
|
||||
return {
|
||||
...message,
|
||||
Content: await decryptChatText(
|
||||
currentChatSecret,
|
||||
message.Content,
|
||||
),
|
||||
};
|
||||
} catch (err) {
|
||||
log(1, "chat", "red", "Failed to decrypt historical message", err, {
|
||||
SendTime: message.SendTime,
|
||||
});
|
||||
return {
|
||||
...message,
|
||||
Content: "Failed to decrypt message",
|
||||
decryptionFailed: true,
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
return decryptMessages(sorted);
|
||||
},
|
||||
[currentChatSecret, send, userIdValue],
|
||||
[currentChatSecret, decryptMessages, send, userIdValue],
|
||||
);
|
||||
|
||||
const [ownId, setOwnId] = useState(0);
|
||||
useEffect(() => {
|
||||
load("user_id").then(setOwnId);
|
||||
}, [load]);
|
||||
if (!currentChatSecret || !ownId || !userIdValue) return;
|
||||
const queryKey = ["chat-messages", String(userIdValue), true] as const;
|
||||
void getCachedMessages().then((messages) => {
|
||||
if (messages.length === 0 || queryClient.getQueryData(queryKey)) return;
|
||||
queryClient.setQueryData<InfiniteData<RawMessages>>(queryKey, {
|
||||
pages: [messages],
|
||||
pageParams: [0],
|
||||
});
|
||||
});
|
||||
}, [currentChatSecret, getCachedMessages, ownId, userIdValue]);
|
||||
|
||||
const editMessage = useCallback(
|
||||
(sendTime: number, edit: MessageEdit) => {
|
||||
|
|
@ -452,7 +483,6 @@ export default function Provider({ children }: { children: ReactNode }) {
|
|||
setLiveMessagesState((prev) =>
|
||||
prev.filter((message) => message.SendTime !== sendTime),
|
||||
);
|
||||
|
||||
const queryKey = [
|
||||
"chat-messages",
|
||||
String(userIdValue),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
- Placeholder image if media fails to load
|
||||
- Signature verifications via ed25519 key
|
||||
- Confirmation when exiting with text in the input box.
|
||||
- Add arrow up hotkey to edit last message
|
||||
- Add arrow up hotkey to edit last message (req: packages/hotkeys)
|
||||
- Drop any unique reactions above 10
|
||||
- Make the emoji picker not get moved with the mini context menu
|
||||
- Add proper loading skeleton
|
||||
|
|
|
|||
Loading…
Reference in a new issue