Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b8d0d85f2 | |||
|
|
6e31989262 |
||
|
|
c155bbb534 |
4 changed files with 61 additions and 38 deletions
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import MessageContextMenu from "./messageContextMenu";
|
import MessageContextMenu from "./messageContextMenu";
|
||||||
import Media from "./media/media";
|
import Media from "./media/media";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import { useMTP } from "@tensamin/mtp";
|
import { requireRelaySuccess, useMTP } from "@tensamin/mtp";
|
||||||
import { getMessage, useChat } from "../context";
|
import { getMessage, useChat } from "../context";
|
||||||
import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret";
|
import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret";
|
||||||
import { log, toast } from "@tensamin/shared/log";
|
import { log, toast } from "@tensamin/shared/log";
|
||||||
|
|
@ -42,6 +42,7 @@ function MessageComponent({
|
||||||
user: SelectedUser<readonly ["UserId", "Avatar", "Display"]> | null;
|
user: SelectedUser<readonly ["UserId", "Avatar", "Display"]> | null;
|
||||||
}) {
|
}) {
|
||||||
const actuallyFailed =
|
const actuallyFailed =
|
||||||
|
Boolean(message.ErrorType) ||
|
||||||
(message.failed && message.MessageState === "awaiting") ||
|
(message.failed && message.MessageState === "awaiting") ||
|
||||||
message.decryptionFailed;
|
message.decryptionFailed;
|
||||||
|
|
||||||
|
|
@ -65,7 +66,7 @@ function MessageComponent({
|
||||||
|
|
||||||
// Message states
|
// Message states
|
||||||
const { load } = useStorage();
|
const { load } = useStorage();
|
||||||
const { send } = useMTP();
|
const { send, sendSealedRelay } = useMTP();
|
||||||
const [ownId, setOwnId] = useState(0);
|
const [ownId, setOwnId] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load("user_id").then(setOwnId);
|
load("user_id").then(setOwnId);
|
||||||
|
|
@ -73,22 +74,38 @@ function MessageComponent({
|
||||||
const messageStateReadUpdate = useCallback(async () => {
|
const messageStateReadUpdate = useCallback(async () => {
|
||||||
const readConfirmations = await load("settings.read_confirmations");
|
const readConfirmations = await load("settings.read_confirmations");
|
||||||
|
|
||||||
if (readConfirmations) {
|
if (!user?.UserId || !message.RelayMessageId || ownId === 0) return;
|
||||||
if (!user?.UserId) return;
|
const [ownIota, peerIota] = await Promise.all([
|
||||||
|
send("GetIotaData", { UserId: ownId }),
|
||||||
await send("MessageState", {
|
send("GetIotaData", { UserId: user.UserId }),
|
||||||
ChatPartnerId: user?.UserId,
|
]);
|
||||||
SendTime: message.SendTime,
|
if (ownIota.type !== "GetIotaData" || peerIota.type !== "GetIotaData") {
|
||||||
MessageState: "read",
|
throw new Error("Could not resolve an Iota for this receipt");
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await send("MessageState", {
|
|
||||||
ChatPartnerId: user?.UserId,
|
|
||||||
SendTime: message.SendTime,
|
|
||||||
MessageState: "received",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [load, message.SendTime, user?.UserId, send]);
|
const response = await sendSealedRelay(
|
||||||
|
"MessageState",
|
||||||
|
{
|
||||||
|
ChatPartnerId: user.UserId,
|
||||||
|
RelayMessageId: message.RelayMessageId,
|
||||||
|
EventAt: Date.now(),
|
||||||
|
MessageState: readConfirmations ? "read" : "received",
|
||||||
|
ReceiverId: user.UserId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nextHop: { kind: "iota", id: ownIota.data.IotaId },
|
||||||
|
finalRecipientId: user.UserId,
|
||||||
|
metadataRecipients: [
|
||||||
|
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||||
|
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||||
|
],
|
||||||
|
contentRecipients: [
|
||||||
|
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||||
|
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
requireRelaySuccess(response);
|
||||||
|
}, [load, message.RelayMessageId, ownId, send, sendSealedRelay, user]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (message.SenderId === ownId || ownId === 0) return;
|
if (message.SenderId === ownId || ownId === 0) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,14 @@ type MessageEdit = Partial<
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const messageStateRank: Record<RawMessage["MessageState"], number> = {
|
||||||
|
awaiting: 0,
|
||||||
|
sending: 0,
|
||||||
|
sent: 1,
|
||||||
|
received: 2,
|
||||||
|
read: 3,
|
||||||
|
};
|
||||||
|
|
||||||
function updateMessagesBySendTime<T extends EditableMessage>(
|
function updateMessagesBySendTime<T extends EditableMessage>(
|
||||||
messages: T[],
|
messages: T[],
|
||||||
sendTime: number,
|
sendTime: number,
|
||||||
|
|
@ -81,6 +89,13 @@ function updateMessagesBySendTime<T extends EditableMessage>(
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
edit.MessageState !== undefined &&
|
||||||
|
messageStateRank[edit.MessageState] < messageStateRank[item.MessageState]
|
||||||
|
) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
const entries = Object.entries(edit) as Array<
|
const entries = Object.entries(edit) as Array<
|
||||||
[keyof MessageEdit, MessageEdit[keyof MessageEdit]]
|
[keyof MessageEdit, MessageEdit[keyof MessageEdit]]
|
||||||
>;
|
>;
|
||||||
|
|
@ -983,9 +998,7 @@ export default function Provider({ children }: { children: ReactNode }) {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editMessage(data.SendTime, {
|
editMessage(data.SendTime, { MessageState: data.MessageState });
|
||||||
MessageState: data.MessageState,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribeEdit();
|
unsubscribeEdit();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ async function requestNotificationPermission() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Provider(props: { children: React.ReactNode }) {
|
export default function Provider(props: { children: React.ReactNode }) {
|
||||||
const { subscribe, send } = useMTP();
|
const { subscribe } = useMTP();
|
||||||
const { load } = useStorage();
|
const { load } = useStorage();
|
||||||
const { get } = useUser();
|
const { get } = useUser();
|
||||||
const { addLiveMessage, chatSecret, getChatSecret, userId } = useChat();
|
const { addLiveMessage, chatSecret, getChatSecret, userId } = useChat();
|
||||||
|
|
@ -79,11 +79,6 @@ export default function Provider(props: { children: React.ReactNode }) {
|
||||||
// todo: add notification symbol to conversation cards (incl. message start)
|
// todo: add notification symbol to conversation cards (incl. message start)
|
||||||
moveUserIdToTop(data.SenderId);
|
moveUserIdToTop(data.SenderId);
|
||||||
|
|
||||||
if (await load("settings.receive_confirmations")) {
|
|
||||||
void send("MessageState", {
|
|
||||||
MessageState: "received",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await get(data.SenderId, [
|
const user = await get(data.SenderId, [
|
||||||
|
|
@ -180,7 +175,6 @@ export default function Provider(props: { children: React.ReactNode }) {
|
||||||
load,
|
load,
|
||||||
location.pathname,
|
location.pathname,
|
||||||
navigate,
|
navigate,
|
||||||
send,
|
|
||||||
moveUserIdToTop,
|
moveUserIdToTop,
|
||||||
subscribe,
|
subscribe,
|
||||||
getChatSecret,
|
getChatSecret,
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ const callSecretEnvelopeRequest = z.object({
|
||||||
export const Reaction = z.object({
|
export const Reaction = z.object({
|
||||||
Reaction: z.string(),
|
Reaction: z.string(),
|
||||||
SenderId: z.number(),
|
SenderId: z.number(),
|
||||||
|
RelayMessageId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Message = z.object({
|
export const Message = z.object({
|
||||||
|
|
@ -82,6 +83,8 @@ export const Message = z.object({
|
||||||
Avatar: z.boolean().optional(),
|
Avatar: z.boolean().optional(),
|
||||||
Display: z.boolean().optional(),
|
Display: z.boolean().optional(),
|
||||||
ReplyId: z.number().optional(),
|
ReplyId: z.number().optional(),
|
||||||
|
UpdatedAt: z.number().optional(),
|
||||||
|
ErrorType: z.string().optional(),
|
||||||
MessageState: z
|
MessageState: z
|
||||||
.enum(["read", "received", "sent", "sending", "awaiting"]) // awaiting for 'internal' use
|
.enum(["read", "received", "sent", "sending", "awaiting"]) // awaiting for 'internal' use
|
||||||
.default("received"),
|
.default("received"),
|
||||||
|
|
@ -107,6 +110,7 @@ const authPayload = z.object({
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
LastMessageAt: z.number().default(0),
|
LastMessageAt: z.number().default(0),
|
||||||
|
CreatedAt: z.number().optional(),
|
||||||
UserId: z.number(),
|
UserId: z.number(),
|
||||||
LastMessage: z
|
LastMessage: z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -397,17 +401,12 @@ export const mtp = {
|
||||||
response: z.object({}),
|
response: z.object({}),
|
||||||
},
|
},
|
||||||
MessageState: {
|
MessageState: {
|
||||||
request: z
|
request: z.object({
|
||||||
.object({
|
ChatPartnerId: z.number(),
|
||||||
ChatPartnerId: z.number(),
|
RelayMessageId: z.string(),
|
||||||
SendTime: z.number(),
|
EventAt: z.number(),
|
||||||
MessageState: Message.shape.MessageState,
|
MessageState: z.enum(["received", "read"]),
|
||||||
})
|
}),
|
||||||
.or(
|
|
||||||
z.object({
|
|
||||||
MessageState: Message.shape.MessageState,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
response: z.object({
|
response: z.object({
|
||||||
ChatPartnerId: z.number(),
|
ChatPartnerId: z.number(),
|
||||||
MessageState: Message.shape.MessageState,
|
MessageState: Message.shape.MessageState,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue