Support server time offset (#1186)
This commit is contained in:
parent
e39d723b70
commit
dcd3803263
@ -57,12 +57,13 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
export function buildApiChatFromDialog(
|
export function buildApiChatFromDialog(
|
||||||
dialog: GramJs.Dialog,
|
dialog: GramJs.Dialog,
|
||||||
peerEntity: GramJs.TypeUser | GramJs.TypeChat,
|
peerEntity: GramJs.TypeUser | GramJs.TypeChat,
|
||||||
|
serverTimeOffset: number,
|
||||||
): ApiChat {
|
): ApiChat {
|
||||||
const {
|
const {
|
||||||
peer, folderId, unreadMark, unreadCount, unreadMentionsCount, notifySettings: { silent, muteUntil },
|
peer, folderId, unreadMark, unreadCount, unreadMentionsCount, notifySettings: { silent, muteUntil },
|
||||||
readOutboxMaxId, readInboxMaxId,
|
readOutboxMaxId, readInboxMaxId,
|
||||||
} = dialog;
|
} = dialog;
|
||||||
const isMuted = silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000);
|
const isMuted = silent || (typeof muteUntil === 'number' && Date.now() + serverTimeOffset * 1000 < muteUntil * 1000);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: getApiChatIdFromMtpPeer(peer),
|
id: getApiChatIdFromMtpPeer(peer),
|
||||||
@ -314,6 +315,7 @@ export function buildChatMembers(
|
|||||||
|
|
||||||
export function buildChatTypingStatus(
|
export function buildChatTypingStatus(
|
||||||
update: GramJs.UpdateUserTyping | GramJs.UpdateChatUserTyping | GramJs.UpdateChannelUserTyping,
|
update: GramJs.UpdateUserTyping | GramJs.UpdateChatUserTyping | GramJs.UpdateChannelUserTyping,
|
||||||
|
serverTimeOffset: number,
|
||||||
) {
|
) {
|
||||||
let action: string = '';
|
let action: string = '';
|
||||||
if (update.action instanceof GramJs.SendMessageCancelAction) {
|
if (update.action instanceof GramJs.SendMessageCancelAction) {
|
||||||
@ -347,7 +349,7 @@ export function buildChatTypingStatus(
|
|||||||
return {
|
return {
|
||||||
action,
|
action,
|
||||||
...(!(update instanceof GramJs.UpdateUserTyping) && { userId: getApiChatIdFromMtpPeer(update.fromId) }),
|
...(!(update instanceof GramJs.UpdateUserTyping) && { userId: getApiChatIdFromMtpPeer(update.fromId) }),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now() + serverTimeOffset * 1000,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -96,7 +96,7 @@ export function buildApiMessageFromNotification(
|
|||||||
return {
|
return {
|
||||||
id: localId,
|
id: localId,
|
||||||
chatId: SERVICE_NOTIFICATIONS_USER_ID,
|
chatId: SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
date: notification.inboxDate || (currentDate / 1000),
|
date: notification.inboxDate || currentDate,
|
||||||
content,
|
content,
|
||||||
isOutgoing: false,
|
isOutgoing: false,
|
||||||
};
|
};
|
||||||
@ -715,6 +715,7 @@ export function buildLocalMessage(
|
|||||||
poll?: ApiNewPoll,
|
poll?: ApiNewPoll,
|
||||||
groupedId?: string,
|
groupedId?: string,
|
||||||
scheduledAt?: number,
|
scheduledAt?: number,
|
||||||
|
serverTimeOffset = 0,
|
||||||
): ApiMessage {
|
): ApiMessage {
|
||||||
const localId = localMessageCounter++;
|
const localId = localMessageCounter++;
|
||||||
const media = attachment && buildUploadingMedia(attachment);
|
const media = attachment && buildUploadingMedia(attachment);
|
||||||
@ -735,7 +736,7 @@ export function buildLocalMessage(
|
|||||||
...(gif && { video: gif }),
|
...(gif && { video: gif }),
|
||||||
...(poll && buildNewPoll(poll, localId)),
|
...(poll && buildNewPoll(poll, localId)),
|
||||||
},
|
},
|
||||||
date: scheduledAt || Math.round(Date.now() / 1000),
|
date: scheduledAt || Math.round(Date.now() / 1000) + serverTimeOffset,
|
||||||
isOutgoing: !isChannel,
|
isOutgoing: !isChannel,
|
||||||
senderId: currentUserId,
|
senderId: currentUserId,
|
||||||
...(replyingTo && { replyToMessageId: replyingTo }),
|
...(replyingTo && { replyToMessageId: replyingTo }),
|
||||||
@ -750,6 +751,7 @@ export function buildLocalMessage(
|
|||||||
export function buildForwardedMessage(
|
export function buildForwardedMessage(
|
||||||
toChat: ApiChat,
|
toChat: ApiChat,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
|
serverTimeOffset: number,
|
||||||
): ApiMessage {
|
): ApiMessage {
|
||||||
const localId = localMessageCounter++;
|
const localId = localMessageCounter++;
|
||||||
const {
|
const {
|
||||||
@ -770,7 +772,7 @@ export function buildForwardedMessage(
|
|||||||
id: localId,
|
id: localId,
|
||||||
chatId: toChat.id,
|
chatId: toChat.id,
|
||||||
content,
|
content,
|
||||||
date: Math.round(Date.now() / 1000),
|
date: Math.round(Date.now() / 1000) + serverTimeOffset,
|
||||||
isOutgoing: !asIncomingInChatWithSelf && toChat.type !== 'chatTypeChannel',
|
isOutgoing: !asIncomingInChatWithSelf && toChat.type !== 'chatTypeChannel',
|
||||||
senderId: currentUserId,
|
senderId: currentUserId,
|
||||||
sendingState: 'messageSendingStatePending',
|
sendingState: 'messageSendingStatePending',
|
||||||
|
|||||||
@ -50,11 +50,13 @@ export async function fetchChats({
|
|||||||
offsetDate,
|
offsetDate,
|
||||||
archived,
|
archived,
|
||||||
withPinned,
|
withPinned,
|
||||||
|
serverTimeOffset,
|
||||||
}: {
|
}: {
|
||||||
limit: number;
|
limit: number;
|
||||||
offsetDate?: number;
|
offsetDate?: number;
|
||||||
archived?: boolean;
|
archived?: boolean;
|
||||||
withPinned?: boolean;
|
withPinned?: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.GetDialogs({
|
const result = await invokeRequest(new GramJs.messages.GetDialogs({
|
||||||
offsetPeer: new GramJs.InputPeerEmpty(),
|
offsetPeer: new GramJs.InputPeerEmpty(),
|
||||||
@ -110,7 +112,7 @@ export async function fetchChats({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
||||||
const chat = buildApiChatFromDialog(dialog, peerEntity);
|
const chat = buildApiChatFromDialog(dialog, peerEntity, serverTimeOffset);
|
||||||
chat.lastMessage = lastMessagesByChatId[chat.id];
|
chat.lastMessage = lastMessagesByChatId[chat.id];
|
||||||
chats.push(chat);
|
chats.push(chat);
|
||||||
|
|
||||||
@ -228,7 +230,12 @@ export async function fetchChat({
|
|||||||
return { chatId: chat.id };
|
return { chatId: chat.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestChatUpdate(chat: ApiChat) {
|
export async function requestChatUpdate({
|
||||||
|
chat,
|
||||||
|
serverTimeOffset,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat; serverTimeOffset: number;
|
||||||
|
}) {
|
||||||
const { id, accessHash } = chat;
|
const { id, accessHash } = chat;
|
||||||
|
|
||||||
const result = await invokeRequest(new GramJs.messages.GetPeerDialogs({
|
const result = await invokeRequest(new GramJs.messages.GetPeerDialogs({
|
||||||
@ -260,7 +267,7 @@ export async function requestChatUpdate(chat: ApiChat) {
|
|||||||
'@type': 'updateChat',
|
'@type': 'updateChat',
|
||||||
id,
|
id,
|
||||||
chat: {
|
chat: {
|
||||||
...buildApiChatFromDialog(dialog, peerEntity),
|
...buildApiChatFromDialog(dialog, peerEntity, serverTimeOffset),
|
||||||
lastMessage,
|
lastMessage,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -396,9 +403,9 @@ async function getFullChannelInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateChatMutedState({
|
export async function updateChatMutedState({
|
||||||
chat, isMuted,
|
chat, isMuted, serverTimeOffset,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; isMuted: boolean;
|
chat: ApiChat; isMuted: boolean; serverTimeOffset: number;
|
||||||
}) {
|
}) {
|
||||||
await invokeRequest(new GramJs.account.UpdateNotifySettings({
|
await invokeRequest(new GramJs.account.UpdateNotifySettings({
|
||||||
peer: new GramJs.InputNotifyPeer({
|
peer: new GramJs.InputNotifyPeer({
|
||||||
@ -407,7 +414,10 @@ export async function updateChatMutedState({
|
|||||||
settings: new GramJs.InputPeerNotifySettings({ muteUntil: isMuted ? MAX_INT_32 : undefined }),
|
settings: new GramJs.InputPeerNotifySettings({ muteUntil: isMuted ? MAX_INT_32 : undefined }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
void requestChatUpdate(chat);
|
void requestChatUpdate({
|
||||||
|
chat,
|
||||||
|
serverTimeOffset,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createChannel({
|
export async function createChannel({
|
||||||
|
|||||||
@ -117,6 +117,11 @@ function handleGramJsUpdate(update: any) {
|
|||||||
isConnected = update.state === connection.UpdateConnectionState.connected;
|
isConnected = update.state === connection.UpdateConnectionState.connected;
|
||||||
} else if (update instanceof GramJs.UpdatesTooLong) {
|
} else if (update instanceof GramJs.UpdatesTooLong) {
|
||||||
void handleTerminatedSession();
|
void handleTerminatedSession();
|
||||||
|
} else if (update instanceof connection.UpdateServerTimeOffset) {
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updateServerTimeOffset',
|
||||||
|
serverTimeOffset: update.timeOffset,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -168,6 +168,7 @@ export function sendMessage(
|
|||||||
scheduledAt,
|
scheduledAt,
|
||||||
groupedId,
|
groupedId,
|
||||||
noWebPage,
|
noWebPage,
|
||||||
|
serverTimeOffset,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
text?: string;
|
text?: string;
|
||||||
@ -181,11 +182,12 @@ export function sendMessage(
|
|||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
groupedId?: string;
|
groupedId?: string;
|
||||||
noWebPage?: boolean;
|
noWebPage?: boolean;
|
||||||
|
serverTimeOffset?: number;
|
||||||
},
|
},
|
||||||
onProgress?: ApiOnProgress,
|
onProgress?: ApiOnProgress,
|
||||||
) {
|
) {
|
||||||
const localMessage = buildLocalMessage(
|
const localMessage = buildLocalMessage(
|
||||||
chat, text, entities, replyingTo, attachment, sticker, gif, poll, groupedId, scheduledAt,
|
chat, text, entities, replyingTo, attachment, sticker, gif, poll, groupedId, scheduledAt, serverTimeOffset,
|
||||||
);
|
);
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
||||||
@ -405,14 +407,16 @@ export async function editMessage({
|
|||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
noWebPage,
|
noWebPage,
|
||||||
|
serverTimeOffset,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
text: string;
|
text: string;
|
||||||
entities?: ApiMessageEntity[];
|
entities?: ApiMessageEntity[];
|
||||||
noWebPage?: boolean;
|
noWebPage?: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
}) {
|
}) {
|
||||||
const isScheduled = message.date * 1000 > Date.now();
|
const isScheduled = message.date * 1000 > Date.now() + serverTimeOffset * 1000;
|
||||||
const messageUpdate: Partial<ApiMessage> = {
|
const messageUpdate: Partial<ApiMessage> = {
|
||||||
content: {
|
content: {
|
||||||
...message.content,
|
...message.content,
|
||||||
@ -613,9 +617,9 @@ export async function deleteHistory({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function markMessageListRead({
|
export async function markMessageListRead({
|
||||||
chat, threadId, maxId,
|
chat, threadId, maxId, serverTimeOffset,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; threadId: number; maxId?: number;
|
chat: ApiChat; threadId: number; maxId?: number; serverTimeOffset: number;
|
||||||
}) {
|
}) {
|
||||||
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
||||||
|
|
||||||
@ -638,7 +642,7 @@ export async function markMessageListRead({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
void requestChatUpdate(chat);
|
void requestChatUpdate({ chat, serverTimeOffset });
|
||||||
} else {
|
} else {
|
||||||
void requestThreadInfoUpdate({ chat, threadId });
|
void requestThreadInfoUpdate({ chat, threadId });
|
||||||
}
|
}
|
||||||
@ -968,16 +972,18 @@ export async function forwardMessages({
|
|||||||
fromChat,
|
fromChat,
|
||||||
toChat,
|
toChat,
|
||||||
messages,
|
messages,
|
||||||
|
serverTimeOffset,
|
||||||
}: {
|
}: {
|
||||||
fromChat: ApiChat;
|
fromChat: ApiChat;
|
||||||
toChat: ApiChat;
|
toChat: ApiChat;
|
||||||
messages: ApiMessage[];
|
messages: ApiMessage[];
|
||||||
|
serverTimeOffset: number;
|
||||||
}) {
|
}) {
|
||||||
const messageIds = messages.map(({ id }) => id);
|
const messageIds = messages.map(({ id }) => id);
|
||||||
const randomIds = messages.map(generateRandomBigInt);
|
const randomIds = messages.map(generateRandomBigInt);
|
||||||
|
|
||||||
messages.forEach((message, index) => {
|
messages.forEach((message, index) => {
|
||||||
const localMessage = buildForwardedMessage(toChat, message);
|
const localMessage = buildForwardedMessage(toChat, message, serverTimeOffset);
|
||||||
localDb.localMessages[String(randomIds[index])] = localMessage;
|
localDb.localMessages[String(randomIds[index])] = localMessage;
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
|
|||||||
@ -164,7 +164,9 @@ export async function fetchNotificationExceptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchNotificationSettings() {
|
export async function fetchNotificationSettings({
|
||||||
|
serverTimeOffset,
|
||||||
|
}: { serverTimeOffset: number }) {
|
||||||
const [
|
const [
|
||||||
isMutedContactSignUpNotification,
|
isMutedContactSignUpNotification,
|
||||||
privateContactNotificationsSettings,
|
privateContactNotificationsSettings,
|
||||||
@ -200,15 +202,18 @@ export async function fetchNotificationSettings() {
|
|||||||
return {
|
return {
|
||||||
hasContactJoinedNotifications: !isMutedContactSignUpNotification,
|
hasContactJoinedNotifications: !isMutedContactSignUpNotification,
|
||||||
hasPrivateChatsNotifications: !(
|
hasPrivateChatsNotifications: !(
|
||||||
privateSilent || (typeof privateMuteUntil === 'number' && Date.now() < privateMuteUntil * 1000)
|
privateSilent
|
||||||
|
|| (typeof privateMuteUntil === 'number' && Date.now() + serverTimeOffset * 1000 < privateMuteUntil * 1000)
|
||||||
),
|
),
|
||||||
hasPrivateChatsMessagePreview: privateShowPreviews,
|
hasPrivateChatsMessagePreview: privateShowPreviews,
|
||||||
hasGroupNotifications: !(
|
hasGroupNotifications: !(
|
||||||
groupSilent || (typeof groupMuteUntil === 'number' && Date.now() < groupMuteUntil * 1000)
|
groupSilent || (typeof groupMuteUntil === 'number'
|
||||||
|
&& Date.now() + serverTimeOffset * 1000 < groupMuteUntil * 1000)
|
||||||
),
|
),
|
||||||
hasGroupMessagePreview: groupShowPreviews,
|
hasGroupMessagePreview: groupShowPreviews,
|
||||||
hasBroadcastNotifications: !(
|
hasBroadcastNotifications: !(
|
||||||
broadcastSilent || (typeof broadcastMuteUntil === 'number' && Date.now() < broadcastMuteUntil * 1000)
|
broadcastSilent || (typeof broadcastMuteUntil === 'number'
|
||||||
|
&& Date.now() + serverTimeOffset * 1000 < broadcastMuteUntil * 1000)
|
||||||
),
|
),
|
||||||
hasBroadcastMessagePreview: broadcastShowPreviews,
|
hasBroadcastMessagePreview: broadcastShowPreviews,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -49,9 +49,12 @@ export function init(_onUpdate: OnApiUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sentMessageIds = new Set();
|
const sentMessageIds = new Set();
|
||||||
|
let serverTimeOffset = 0;
|
||||||
|
|
||||||
export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
||||||
if (update instanceof connection.UpdateConnectionState) {
|
if (update instanceof connection.UpdateServerTimeOffset) {
|
||||||
|
serverTimeOffset = update.timeOffset;
|
||||||
|
} else if (update instanceof connection.UpdateConnectionState) {
|
||||||
let connectionState: ApiUpdateConnectionStateType;
|
let connectionState: ApiUpdateConnectionStateType;
|
||||||
|
|
||||||
switch (update.state) {
|
switch (update.state) {
|
||||||
@ -88,7 +91,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
} else if (update instanceof GramJs.UpdateShortMessage) {
|
} else if (update instanceof GramJs.UpdateShortMessage) {
|
||||||
message = buildApiMessageFromShort(update);
|
message = buildApiMessageFromShort(update);
|
||||||
} else if (update instanceof GramJs.UpdateServiceNotification) {
|
} else if (update instanceof GramJs.UpdateServiceNotification) {
|
||||||
const currentDate = Date.now();
|
const currentDate = Date.now() / 1000 + serverTimeOffset;
|
||||||
message = buildApiMessageFromNotification(update, currentDate);
|
message = buildApiMessageFromNotification(update, currentDate);
|
||||||
|
|
||||||
if (isMessageWithMedia(update)) {
|
if (isMessageWithMedia(update)) {
|
||||||
@ -347,7 +350,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
sentMessageIds.add(update.id);
|
sentMessageIds.add(update.id);
|
||||||
|
|
||||||
// Edge case for "Send When Online"
|
// Edge case for "Send When Online"
|
||||||
const isAlreadySent = 'date' in update && update.date * 1000 < Date.now();
|
const isAlreadySent = 'date' in update && update.date * 1000 < Date.now() + serverTimeOffset * 1000;
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': localMessage.isScheduled && !isAlreadySent
|
'@type': localMessage.isScheduled && !isAlreadySent
|
||||||
@ -549,7 +552,8 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
silent, muteUntil, showPreviews, sound,
|
silent, muteUntil, showPreviews, sound,
|
||||||
} = update.notifySettings;
|
} = update.notifySettings;
|
||||||
|
|
||||||
const isMuted = silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000);
|
const isMuted = silent
|
||||||
|
|| (typeof muteUntil === 'number' && Date.now() + serverTimeOffset * 1000 < muteUntil * 1000);
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateNotifyExceptions',
|
'@type': 'updateNotifyExceptions',
|
||||||
@ -569,7 +573,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateChatTypingStatus',
|
'@type': 'updateChatTypingStatus',
|
||||||
id,
|
id,
|
||||||
typingStatus: buildChatTypingStatus(update),
|
typingStatus: buildChatTypingStatus(update, serverTimeOffset),
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateChannelUserTyping) {
|
} else if (update instanceof GramJs.UpdateChannelUserTyping) {
|
||||||
const id = getApiChatIdFromMtpPeer({ channelId: update.channelId } as GramJs.PeerChannel);
|
const id = getApiChatIdFromMtpPeer({ channelId: update.channelId } as GramJs.PeerChannel);
|
||||||
@ -577,7 +581,7 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateChatTypingStatus',
|
'@type': 'updateChatTypingStatus',
|
||||||
id,
|
id,
|
||||||
typingStatus: buildChatTypingStatus(update),
|
typingStatus: buildChatTypingStatus(update, serverTimeOffset),
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateChannel) {
|
} else if (update instanceof GramJs.UpdateChannel) {
|
||||||
const { _entities } = update;
|
const { _entities } = update;
|
||||||
@ -745,7 +749,8 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateNotifySettings',
|
'@type': 'updateNotifySettings',
|
||||||
peerType,
|
peerType,
|
||||||
isSilent: Boolean(silent || (typeof muteUntil === 'number' && Date.now() < muteUntil * 1000)),
|
isSilent: Boolean(silent
|
||||||
|
|| (typeof muteUntil === 'number' && Date.now() + serverTimeOffset * 1000 < muteUntil * 1000)),
|
||||||
shouldShowPreviews: Boolean(showPreviews),
|
shouldShowPreviews: Boolean(showPreviews),
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdatePeerBlocked) {
|
} else if (update instanceof GramJs.UpdatePeerBlocked) {
|
||||||
|
|||||||
@ -373,6 +373,11 @@ export type ApiUpdatePrivacy = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateServerTimeOffset = {
|
||||||
|
'@type': 'updateServerTimeOffset';
|
||||||
|
serverTimeOffset: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdate = (
|
export type ApiUpdate = (
|
||||||
ApiUpdateReady | ApiUpdateSession |
|
ApiUpdateReady | ApiUpdateSession |
|
||||||
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
|
||||||
@ -389,7 +394,8 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateNewScheduledMessage | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage |
|
ApiUpdateNewScheduledMessage | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage |
|
||||||
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages |
|
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages |
|
||||||
ApiUpdateTwoFaError | updateTwoFaStateWaitCode |
|
ApiUpdateTwoFaError | updateTwoFaStateWaitCode |
|
||||||
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy
|
ApiUpdateNotifySettings | ApiUpdateNotifyExceptions | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
||||||
|
ApiUpdateServerTimeOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type StateProps = {
|
|||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
areMessagesLoaded: boolean;
|
areMessagesLoaded: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
} & Pick<GlobalState, 'lastSyncTime'>;
|
} & Pick<GlobalState, 'lastSyncTime'>;
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
|
type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
|
||||||
@ -56,6 +57,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const { id: userId } = user || {};
|
const { id: userId } = user || {};
|
||||||
const fullName = getUserFullName(user);
|
const fullName = getUserFullName(user);
|
||||||
@ -106,7 +108,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
||||||
{withUsername && user.username && <span className="handle">{user.username}</span>}
|
{withUsername && user.username && <span className="handle">{user.username}</span>}
|
||||||
<span className="user-status" dir="auto">{getUserStatus(lang, user)}</span>
|
<span className="user-status" dir="auto">{getUserStatus(lang, user, serverTimeOffset)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -139,13 +141,13 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { userId, forceShowSelf }): StateProps => {
|
(global, { userId, forceShowSelf }): StateProps => {
|
||||||
const { lastSyncTime } = global;
|
const { lastSyncTime, serverTimeOffset } = global;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lastSyncTime, user, isSavedMessages, areMessagesLoaded,
|
lastSyncTime, user, isSavedMessages, areMessagesLoaded, serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
contactIds?: number[];
|
contactIds?: number[];
|
||||||
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'openChat'>;
|
type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'openChat'>;
|
||||||
@ -32,7 +33,7 @@ type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'openChat'>;
|
|||||||
const runThrottled = throttle((cb) => cb(), 60000, true);
|
const runThrottled = throttle((cb) => cb(), 60000, true);
|
||||||
|
|
||||||
const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
filter, usersById, contactIds, loadContactList, openChat,
|
filter, usersById, contactIds, loadContactList, openChat, serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
// Due to the parent Transition, this component never gets unmounted,
|
// Due to the parent Transition, this component never gets unmounted,
|
||||||
// that's why we use throttled API call on every update.
|
// that's why we use throttled API call on every update.
|
||||||
@ -63,8 +64,8 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return fullName && searchWords(fullName, filter);
|
return fullName && searchWords(fullName, filter);
|
||||||
}) : contactIds;
|
}) : contactIds;
|
||||||
|
|
||||||
return sortUserIds(resultIds, usersById);
|
return sortUserIds(resultIds, usersById, undefined, serverTimeOffset);
|
||||||
}, [filter, usersById, contactIds]);
|
}, [contactIds, filter, usersById, serverTimeOffset]);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
usersById,
|
usersById,
|
||||||
contactIds,
|
contactIds,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadContactList', 'openChat']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadContactList', 'openChat']),
|
||||||
|
|||||||
@ -33,6 +33,7 @@ type StateProps = {
|
|||||||
isSearching?: boolean;
|
isSearching?: boolean;
|
||||||
localUserIds?: number[];
|
localUserIds?: number[];
|
||||||
globalUserIds?: number[];
|
globalUserIds?: number[];
|
||||||
|
serverTimeOffset?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'setGlobalSearchQuery'>;
|
type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'setGlobalSearchQuery'>;
|
||||||
@ -53,6 +54,7 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isSearching,
|
isSearching,
|
||||||
localUserIds,
|
localUserIds,
|
||||||
globalUserIds,
|
globalUserIds,
|
||||||
|
serverTimeOffset,
|
||||||
loadContactList,
|
loadContactList,
|
||||||
setGlobalSearchQuery,
|
setGlobalSearchQuery,
|
||||||
}) => {
|
}) => {
|
||||||
@ -70,7 +72,8 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
const contactIds = localContactIds
|
const contactIds = localContactIds
|
||||||
? sortChatIds(localContactIds.filter((id) => id !== currentUserId), chatsById)
|
? sortChatIds(localContactIds.filter((id) => id !== currentUserId), chatsById,
|
||||||
|
undefined, undefined, serverTimeOffset)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
@ -95,9 +98,11 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
chatsById,
|
chatsById,
|
||||||
false,
|
false,
|
||||||
selectedMemberIds,
|
selectedMemberIds,
|
||||||
|
serverTimeOffset,
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
localContactIds, searchQuery, localUserIds, globalUserIds, usersById, chatsById, selectedMemberIds, currentUserId,
|
localContactIds, chatsById, serverTimeOffset, searchQuery, localUserIds, globalUserIds, selectedMemberIds,
|
||||||
|
currentUserId, usersById,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleNextStep = useCallback(() => {
|
const handleNextStep = useCallback(() => {
|
||||||
@ -152,7 +157,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { userIds: localContactIds } = global.contactList || {};
|
const { userIds: localContactIds } = global.contactList || {};
|
||||||
const { byId: usersById } = global.users;
|
const { byId: usersById } = global.users;
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
const { currentUserId } = global;
|
const { currentUserId, serverTimeOffset } = global;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: searchQuery,
|
query: searchQuery,
|
||||||
@ -172,6 +177,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isSearching: fetchingStatus && fetchingStatus.chats,
|
isSearching: fetchingStatus && fetchingStatus.chats,
|
||||||
globalUserIds,
|
globalUserIds,
|
||||||
localUserIds,
|
localUserIds,
|
||||||
|
serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadContactList', 'setGlobalSearchQuery']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadContactList', 'setGlobalSearchQuery']),
|
||||||
|
|||||||
@ -45,6 +45,7 @@ type StateProps = {
|
|||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
fetchingStatus?: { chats?: boolean; messages?: boolean };
|
fetchingStatus?: { chats?: boolean; messages?: boolean };
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
|
serverTimeOffset?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
@ -61,6 +62,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds,
|
localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds,
|
||||||
foundIds, globalMessagesByChatId, chatsById, usersById, fetchingStatus, lastSyncTime,
|
foundIds, globalMessagesByChatId, chatsById, usersById, fetchingStatus, lastSyncTime,
|
||||||
onReset, onSearchDateSelect, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
|
onReset, onSearchDateSelect, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -120,17 +122,21 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
...foundContactIds,
|
...foundContactIds,
|
||||||
...(localChatIds || []),
|
...(localChatIds || []),
|
||||||
...(localUserIds || []),
|
...(localUserIds || []),
|
||||||
]), chatsById),
|
]), chatsById, undefined, undefined, serverTimeOffset),
|
||||||
];
|
];
|
||||||
}, [searchQuery, localContactIds, currentUserId, lang, localChatIds, localUserIds, chatsById, usersById]);
|
}, [
|
||||||
|
searchQuery, localContactIds, currentUserId, lang, localChatIds, localUserIds, chatsById,
|
||||||
|
serverTimeOffset, usersById,
|
||||||
|
]);
|
||||||
|
|
||||||
const globalResults = useMemo(() => {
|
const globalResults = useMemo(() => {
|
||||||
if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) {
|
if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) {
|
||||||
return MEMO_EMPTY_ARRAY;
|
return MEMO_EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortChatIds(unique([...globalChatIds, ...globalUserIds]), chatsById, true);
|
return sortChatIds(unique([...globalChatIds, ...globalUserIds]),
|
||||||
}, [chatsById, globalChatIds, globalUserIds, searchQuery]);
|
chatsById, true, undefined, serverTimeOffset);
|
||||||
|
}, [chatsById, globalChatIds, globalUserIds, searchQuery, serverTimeOffset]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) {
|
if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) {
|
||||||
@ -288,7 +294,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currentUserId, messages, lastSyncTime } = global;
|
const {
|
||||||
|
currentUserId, messages, lastSyncTime, serverTimeOffset,
|
||||||
|
} = global;
|
||||||
const {
|
const {
|
||||||
fetchingStatus, globalResults, localResults, resultsByType,
|
fetchingStatus, globalResults, localResults, resultsByType,
|
||||||
} = global.globalSearch;
|
} = global.globalSearch;
|
||||||
@ -310,6 +318,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
usersById,
|
usersById,
|
||||||
fetchingStatus,
|
fetchingStatus,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
|
serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
|
|||||||
@ -37,6 +37,7 @@ type StateProps = {
|
|||||||
archivedListIds?: number[];
|
archivedListIds?: number[];
|
||||||
orderedPinnedIds?: number[];
|
orderedPinnedIds?: number[];
|
||||||
currentUserId?: number;
|
currentUserId?: number;
|
||||||
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'setForwardChatId' | 'exitForwardMode' | 'loadMoreChats'>;
|
type DispatchProps = Pick<GlobalActions, 'setForwardChatId' | 'exitForwardMode' | 'loadMoreChats'>;
|
||||||
@ -50,6 +51,7 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
activeListIds,
|
activeListIds,
|
||||||
archivedListIds,
|
archivedListIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
serverTimeOffset,
|
||||||
isOpen,
|
isOpen,
|
||||||
setForwardChatId,
|
setForwardChatId,
|
||||||
exitForwardMode,
|
exitForwardMode,
|
||||||
@ -106,8 +108,8 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
return searchWords(getChatTitle(lang, chatsById[id], undefined, id === currentUserId), filter);
|
return searchWords(getChatTitle(lang, chatsById[id], undefined, id === currentUserId), filter);
|
||||||
}),
|
}),
|
||||||
], chatsById, undefined, currentUserId ? [currentUserId] : undefined);
|
], chatsById, undefined, currentUserId ? [currentUserId] : undefined, serverTimeOffset);
|
||||||
}, [activeListIds, archivedListIds, chatsById, currentUserId, filter, lang]);
|
}, [activeListIds, archivedListIds, chatsById, currentUserId, filter, lang, serverTimeOffset]);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(loadMoreChats, chatIds, Boolean(filter));
|
const [viewportIds, getMore] = useInfiniteScroll(loadMoreChats, chatIds, Boolean(filter));
|
||||||
|
|
||||||
@ -186,6 +188,7 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const {
|
const {
|
||||||
|
serverTimeOffset,
|
||||||
chats: {
|
chats: {
|
||||||
byId: chatsById,
|
byId: chatsById,
|
||||||
listIds,
|
listIds,
|
||||||
@ -198,6 +201,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
activeListIds: listIds.active,
|
activeListIds: listIds.active,
|
||||||
archivedListIds: listIds.archived,
|
archivedListIds: listIds.archived,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['setForwardChatId', 'exitForwardMode', 'loadMoreChats']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['setForwardChatId', 'exitForwardMode', 'loadMoreChats']),
|
||||||
|
|||||||
@ -122,6 +122,7 @@ type StateProps = {
|
|||||||
shouldSuggestStickers?: boolean;
|
shouldSuggestStickers?: boolean;
|
||||||
language: LangCode;
|
language: LangCode;
|
||||||
emojiKeywords?: Record<string, string[]>;
|
emojiKeywords?: Record<string, string[]>;
|
||||||
|
serverTimeOffset: number;
|
||||||
} & Pick<GlobalState, 'connectionState'>;
|
} & Pick<GlobalState, 'connectionState'>;
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
@ -180,6 +181,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
shouldSuggestStickers,
|
shouldSuggestStickers,
|
||||||
language,
|
language,
|
||||||
emojiKeywords,
|
emojiKeywords,
|
||||||
|
serverTimeOffset,
|
||||||
recentEmojis,
|
recentEmojis,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
editMessage,
|
editMessage,
|
||||||
@ -442,7 +444,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
if (currentAttachments.length || text) {
|
if (currentAttachments.length || text) {
|
||||||
if (slowMode && !isAdmin) {
|
if (slowMode && !isAdmin) {
|
||||||
const nowSeconds = Math.floor(Date.now() / 1000);
|
const nowSeconds = Math.floor(Date.now() / 1000) + serverTimeOffset;
|
||||||
const secondsSinceLastMessage = lastMessageSendTimeSeconds.current
|
const secondsSinceLastMessage = lastMessageSendTimeSeconds.current
|
||||||
&& Math.floor(nowSeconds - lastMessageSendTimeSeconds.current);
|
&& Math.floor(nowSeconds - lastMessageSendTimeSeconds.current);
|
||||||
const nextSendDateNotReached = slowMode.nextSendDate && slowMode.nextSendDate > nowSeconds;
|
const nextSendDateNotReached = slowMode.nextSendDate && slowMode.nextSendDate > nowSeconds;
|
||||||
@ -480,15 +482,15 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
forwardMessages();
|
forwardMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
lastMessageSendTimeSeconds.current = Math.floor(Date.now() / 1000);
|
lastMessageSendTimeSeconds.current = Math.floor(Date.now() / 1000) + serverTimeOffset;
|
||||||
|
|
||||||
clearDraft({ chatId, localOnly: true });
|
clearDraft({ chatId, localOnly: true });
|
||||||
|
|
||||||
// Wait until message animation starts
|
// Wait until message animation starts
|
||||||
requestAnimationFrame(resetComposer);
|
requestAnimationFrame(resetComposer);
|
||||||
}, [
|
}, [
|
||||||
activeVoiceRecording, attachments, connectionState, chatId, slowMode, isForwarding, isAdmin,
|
connectionState, attachments, activeVoiceRecording, isForwarding, serverTimeOffset, clearDraft, chatId,
|
||||||
sendMessage, stopRecordingVoice, resetComposer, clearDraft, showError, forwardMessages,
|
resetComposer, stopRecordingVoice, showError, slowMode, isAdmin, sendMessage, forwardMessages,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((sticker: ApiSticker) => {
|
const handleStickerSelect = useCallback((sticker: ApiSticker) => {
|
||||||
@ -536,11 +538,12 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
}, [handleSend, openCalendar, shouldSchedule]);
|
}, [handleSend, openCalendar, shouldSchedule]);
|
||||||
|
|
||||||
const handleMessageSchedule = useCallback((date: Date) => {
|
const handleMessageSchedule = useCallback((date: Date, isWhenOnline = false) => {
|
||||||
const { isSilent, ...restArgs } = scheduledMessageArgs || {};
|
const { isSilent, ...restArgs } = scheduledMessageArgs || {};
|
||||||
|
|
||||||
// Scheduled time can not be less than 10 seconds in future
|
// Scheduled time can not be less than 10 seconds in future
|
||||||
const scheduledAt = Math.round(Math.max(date.getTime(), Date.now() + 60 * 1000) / 1000);
|
const scheduledAt = Math.round(Math.max(date.getTime(), Date.now() + 60 * 1000) / 1000)
|
||||||
|
+ (isWhenOnline ? 0 : serverTimeOffset);
|
||||||
|
|
||||||
if (!scheduledMessageArgs || Object.keys(restArgs).length === 0) {
|
if (!scheduledMessageArgs || Object.keys(restArgs).length === 0) {
|
||||||
handleSend(!!isSilent, scheduledAt);
|
handleSend(!!isSilent, scheduledAt);
|
||||||
@ -552,10 +555,10 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
requestAnimationFrame(resetComposer);
|
requestAnimationFrame(resetComposer);
|
||||||
}
|
}
|
||||||
closeCalendar();
|
closeCalendar();
|
||||||
}, [closeCalendar, handleSend, resetComposer, scheduledMessageArgs, sendMessage]);
|
}, [closeCalendar, handleSend, resetComposer, scheduledMessageArgs, sendMessage, serverTimeOffset]);
|
||||||
|
|
||||||
const handleMessageScheduleUntilOnline = useCallback(() => {
|
const handleMessageScheduleUntilOnline = useCallback(() => {
|
||||||
handleMessageSchedule(new Date(SCHEDULED_WHEN_ONLINE * 1000));
|
handleMessageSchedule(new Date(SCHEDULED_WHEN_ONLINE * 1000), true);
|
||||||
}, [handleMessageSchedule]);
|
}, [handleMessageSchedule]);
|
||||||
|
|
||||||
const handleCloseCalendar = useCallback(() => {
|
const handleCloseCalendar = useCallback(() => {
|
||||||
@ -961,6 +964,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
recentEmojis: global.recentEmojis,
|
recentEmojis: global.recentEmojis,
|
||||||
language,
|
language,
|
||||||
emojiKeywords: emojiKeywords ? emojiKeywords.keywords : undefined,
|
emojiKeywords: emojiKeywords ? emojiKeywords.keywords : undefined,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
|
|||||||
@ -38,6 +38,7 @@ type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
recentVoterIds?: number[];
|
recentVoterIds?: number[];
|
||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, ('loadMessage' | 'openPollResults')>;
|
type DispatchProps = Pick<GlobalActions, ('loadMessage' | 'openPollResults')>;
|
||||||
@ -54,6 +55,7 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
loadMessage,
|
loadMessage,
|
||||||
onSendVote,
|
onSendVote,
|
||||||
openPollResults,
|
openPollResults,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const { id: messageId, chatId } = message;
|
const { id: messageId, chatId } = message;
|
||||||
const { summary, results } = poll;
|
const { summary, results } = poll;
|
||||||
@ -63,7 +65,7 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const [wasSubmitted, setWasSubmitted] = useState<boolean>(false);
|
const [wasSubmitted, setWasSubmitted] = useState<boolean>(false);
|
||||||
const [closePeriod, setClosePeriod] = useState<number>(
|
const [closePeriod, setClosePeriod] = useState<number>(
|
||||||
!summary.closed && summary.closeDate && summary.closeDate > 0
|
!summary.closed && summary.closeDate && summary.closeDate > 0
|
||||||
? Math.min(summary.closeDate - Math.floor(Date.now() / 1000), summary.closePeriod!)
|
? Math.min(summary.closeDate - Math.floor(Date.now() / 1000) + serverTimeOffset, summary.closePeriod!)
|
||||||
: 0,
|
: 0,
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -359,7 +361,7 @@ function getReadableVotersCount(lang: LangFn, isQuiz: true | undefined, count?:
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { poll }) => {
|
(global, { poll }) => {
|
||||||
const { recentVoterIds } = poll.results;
|
const { recentVoterIds } = poll.results;
|
||||||
const { byId: usersById } = global.users;
|
const { serverTimeOffset, users: { byId: usersById } } = global;
|
||||||
if (!recentVoterIds || recentVoterIds.length === 0) {
|
if (!recentVoterIds || recentVoterIds.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -367,6 +369,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
recentVoterIds,
|
recentVoterIds,
|
||||||
usersById,
|
usersById,
|
||||||
|
serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadMessage', 'openPollResults']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadMessage', 'openPollResults']),
|
||||||
|
|||||||
@ -75,6 +75,7 @@ type StateProps = {
|
|||||||
isRightColumnShown: boolean;
|
isRightColumnShown: boolean;
|
||||||
isRestricted?: boolean;
|
isRestricted?: boolean;
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
@ -115,6 +116,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
openUserInfo,
|
openUserInfo,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
loadProfilePhotos,
|
loadProfilePhotos,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -133,7 +135,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
|
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
|
||||||
isRightColumnShown, loadMoreMembers, searchMediaMessagesLocal, tabType, mediaSearchType, members,
|
isRightColumnShown, loadMoreMembers, searchMediaMessagesLocal, tabType, mediaSearchType, members,
|
||||||
usersById, chatMessages, foundIds, chatId, lastSyncTime,
|
usersById, chatMessages, foundIds, chatId, lastSyncTime, serverTimeOffset,
|
||||||
);
|
);
|
||||||
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
||||||
|
|
||||||
@ -406,6 +408,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isRightColumnShown: selectIsRightColumnShown(global),
|
isRightColumnShown: selectIsRightColumnShown(global),
|
||||||
isRestricted: chat && chat.isRestricted,
|
isRestricted: chat && chat.isRestricted,
|
||||||
lastSyncTime: global.lastSyncTime,
|
lastSyncTime: global.lastSyncTime,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
|
|||||||
@ -34,6 +34,7 @@ type StateProps = {
|
|||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
animationLevel: 0 | 1 | 2;
|
animationLevel: 0 | 1 | 2;
|
||||||
|
serverTimeOffset: number;
|
||||||
} & Pick<GlobalState, 'lastSyncTime'>;
|
} & Pick<GlobalState, 'lastSyncTime'>;
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
|
type DispatchProps = Pick<GlobalActions, 'loadFullUser' | 'openMediaViewer'>;
|
||||||
@ -46,6 +47,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
animationLevel,
|
animationLevel,
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const { id: userId } = user || {};
|
const { id: userId } = user || {};
|
||||||
const { id: chatId } = chat || {};
|
const { id: chatId } = chat || {};
|
||||||
@ -157,7 +159,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
if (user) {
|
if (user) {
|
||||||
return (
|
return (
|
||||||
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
|
||||||
<span className="user-status" dir="auto">{getUserStatus(lang, user)}</span>
|
<span className="user-status" dir="auto">{getUserStatus(lang, user, serverTimeOffset)}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -219,14 +221,14 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { userId, forceShowSelf }): StateProps => {
|
(global, { userId, forceShowSelf }): StateProps => {
|
||||||
const { lastSyncTime } = global;
|
const { lastSyncTime, serverTimeOffset } = global;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
const chat = selectChat(global, userId);
|
const chat = selectChat(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const { animationLevel } = global.settings.byKey;
|
const { animationLevel } = global.settings.byKey;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lastSyncTime, user, chat, isSavedMessages, animationLevel,
|
lastSyncTime, user, chat, isSavedMessages, animationLevel, serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
||||||
|
|||||||
@ -20,6 +20,7 @@ export default function useProfileViewportIds(
|
|||||||
foundIds?: number[],
|
foundIds?: number[],
|
||||||
chatId?: number,
|
chatId?: number,
|
||||||
lastSyncTime?: number,
|
lastSyncTime?: number,
|
||||||
|
serverTimeOffset = 0,
|
||||||
) {
|
) {
|
||||||
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
|
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ export default function useProfileViewportIds(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortUserIds(groupChatMembers.map(({ userId }) => userId), usersById);
|
return sortUserIds(groupChatMembers.map(({ userId }) => userId), usersById, undefined, serverTimeOffset);
|
||||||
}, [groupChatMembers, usersById]);
|
}, [groupChatMembers, serverTimeOffset, usersById]);
|
||||||
|
|
||||||
const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForMembers(
|
const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForMembers(
|
||||||
resultType, loadMoreMembers, lastSyncTime, memberIds,
|
resultType, loadMoreMembers, lastSyncTime, memberIds,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type StateProps = {
|
|||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, 'openUserInfo'>;
|
type DispatchProps = Pick<GlobalActions, 'openUserInfo'>;
|
||||||
@ -30,14 +31,15 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
usersById,
|
usersById,
|
||||||
isChannel,
|
isChannel,
|
||||||
openUserInfo,
|
openUserInfo,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const memberIds = useMemo(() => {
|
const memberIds = useMemo(() => {
|
||||||
if (!members || !usersById) {
|
if (!members || !usersById) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortUserIds(members.map(({ userId }) => userId), usersById);
|
return sortUserIds(members.map(({ userId }) => userId), usersById, undefined, serverTimeOffset);
|
||||||
}, [members, usersById]);
|
}, [members, serverTimeOffset, usersById]);
|
||||||
|
|
||||||
const handleMemberClick = useCallback((id: number) => {
|
const handleMemberClick = useCallback((id: number) => {
|
||||||
openUserInfo({ id });
|
openUserInfo({ id });
|
||||||
@ -82,6 +84,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
members,
|
members,
|
||||||
usersById,
|
usersById,
|
||||||
isChannel,
|
isChannel,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
|
|||||||
@ -23,6 +23,7 @@ type StateProps = {
|
|||||||
usersById: Record<number, ApiUser>;
|
usersById: Record<number, ApiUser>;
|
||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
|
serverTimeOffset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
||||||
@ -31,14 +32,18 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
|
|||||||
isChannel,
|
isChannel,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
onChatMemberSelect,
|
onChatMemberSelect,
|
||||||
|
serverTimeOffset,
|
||||||
}) => {
|
}) => {
|
||||||
const memberIds = useMemo(() => {
|
const memberIds = useMemo(() => {
|
||||||
if (!members || !usersById) {
|
if (!members || !usersById) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortUserIds(members.filter((member) => !member.isOwner).map(({ userId }) => userId), usersById);
|
return sortUserIds(
|
||||||
}, [members, usersById]);
|
members.filter((member) => !member.isOwner).map(({ userId }) => userId),
|
||||||
|
usersById, undefined, serverTimeOffset,
|
||||||
|
);
|
||||||
|
}, [members, serverTimeOffset, usersById]);
|
||||||
|
|
||||||
const handleExceptionMemberClick = useCallback((memberId: number) => {
|
const handleExceptionMemberClick = useCallback((memberId: number) => {
|
||||||
onChatMemberSelect(memberId);
|
onChatMemberSelect(memberId);
|
||||||
@ -85,6 +90,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
members,
|
members,
|
||||||
usersById,
|
usersById,
|
||||||
isChannel,
|
isChannel,
|
||||||
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(ManageGroupUserPermissionsCreate));
|
)(ManageGroupUserPermissionsCreate));
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
isLeftColumnShown: true,
|
isLeftColumnShown: true,
|
||||||
isChatInfoShown: false,
|
isChatInfoShown: false,
|
||||||
uiReadyState: 0,
|
uiReadyState: 0,
|
||||||
|
serverTimeOffset: 0,
|
||||||
|
|
||||||
authRememberMe: true,
|
authRememberMe: true,
|
||||||
|
|
||||||
|
|||||||
@ -67,6 +67,7 @@ export type GlobalState = {
|
|||||||
connectionState?: ApiUpdateConnectionStateType;
|
connectionState?: ApiUpdateConnectionStateType;
|
||||||
currentUserId?: number;
|
currentUserId?: number;
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
|
serverTimeOffset: number;
|
||||||
|
|
||||||
// TODO Move to `auth`.
|
// TODO Move to `auth`.
|
||||||
isLoggingOut?: boolean;
|
isLoggingOut?: boolean;
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const {
|
|||||||
} = require('../tl').constructors;
|
} = require('../tl').constructors;
|
||||||
const MessagePacker = require('../extensions/MessagePacker');
|
const MessagePacker = require('../extensions/MessagePacker');
|
||||||
const BinaryReader = require('../extensions/BinaryReader');
|
const BinaryReader = require('../extensions/BinaryReader');
|
||||||
const { UpdateConnectionState } = require('./index');
|
const { UpdateConnectionState, UpdateServerTimeOffset } = require('./index');
|
||||||
const { BadMessageError } = require('../errors/Common');
|
const { BadMessageError } = require('../errors/Common');
|
||||||
const {
|
const {
|
||||||
BadServerSalt,
|
BadServerSalt,
|
||||||
@ -259,6 +259,10 @@ class MTProtoSender {
|
|||||||
|
|
||||||
this._state.time_offset = res.timeOffset;
|
this._state.time_offset = res.timeOffset;
|
||||||
|
|
||||||
|
if (this._updateCallback) {
|
||||||
|
this._updateCallback(new UpdateServerTimeOffset(this._state.time_offset));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is *EXTREMELY* important since we don't control
|
* This is *EXTREMELY* important since we don't control
|
||||||
* external references to the authorization key, we must
|
* external references to the authorization key, we must
|
||||||
@ -630,6 +634,11 @@ class MTProtoSender {
|
|||||||
// Sent msg_id too low or too high (respectively).
|
// Sent msg_id too low or too high (respectively).
|
||||||
// Use the current msg_id to determine the right time offset.
|
// Use the current msg_id to determine the right time offset.
|
||||||
const to = this._state.updateTimeOffset(message.msgId);
|
const to = this._state.updateTimeOffset(message.msgId);
|
||||||
|
|
||||||
|
if (this._updateCallback) {
|
||||||
|
this._updateCallback(new UpdateServerTimeOffset(to));
|
||||||
|
}
|
||||||
|
|
||||||
this._log.info(`System clock is wrong, set time offset to ${to}s`);
|
this._log.info(`System clock is wrong, set time offset to ${to}s`);
|
||||||
} else if (badMsg.errorCode === 32) {
|
} else if (badMsg.errorCode === 32) {
|
||||||
// msg_seqno too low, so just pump it up by some "large" amount
|
// msg_seqno too low, so just pump it up by some "large" amount
|
||||||
|
|||||||
@ -14,6 +14,12 @@ class UpdateConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpdateServerTimeOffset {
|
||||||
|
constructor(timeOffset) {
|
||||||
|
this.timeOffset = timeOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionTCPFull,
|
ConnectionTCPFull,
|
||||||
@ -30,4 +36,5 @@ module.exports = {
|
|||||||
doAuthentication,
|
doAuthentication,
|
||||||
MTProtoSender,
|
MTProtoSender,
|
||||||
UpdateConnectionState,
|
UpdateConnectionState,
|
||||||
|
UpdateServerTimeOffset,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -177,23 +177,28 @@ addReducer('loadTopChats', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addReducer('requestChatUpdate', (global, actions, payload) => {
|
addReducer('requestChatUpdate', (global, actions, payload) => {
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
const { chatId } = payload!;
|
const { chatId } = payload!;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void callApi('requestChatUpdate', chat);
|
void callApi('requestChatUpdate', {
|
||||||
|
chat,
|
||||||
|
serverTimeOffset,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('updateChatMutedState', (global, actions, payload) => {
|
addReducer('updateChatMutedState', (global, actions, payload) => {
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
const { chatId, isMuted } = payload!;
|
const { chatId, isMuted } = payload!;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void callApi('updateChatMutedState', { chat, isMuted });
|
void callApi('updateChatMutedState', { chat, isMuted, serverTimeOffset });
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('createChannel', (global, actions, payload) => {
|
addReducer('createChannel', (global, actions, payload) => {
|
||||||
@ -358,10 +363,11 @@ addReducer('deleteChatFolder', (global, actions, payload) => {
|
|||||||
|
|
||||||
addReducer('toggleChatUnread', (global, actions, payload) => {
|
addReducer('toggleChatUnread', (global, actions, payload) => {
|
||||||
const { id } = payload!;
|
const { id } = payload!;
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
const chat = selectChat(global, id);
|
const chat = selectChat(global, id);
|
||||||
if (chat) {
|
if (chat) {
|
||||||
if (chat.unreadCount) {
|
if (chat.unreadCount) {
|
||||||
void callApi('markMessageListRead', { chat, threadId: MAIN_THREAD_ID });
|
void callApi('markMessageListRead', { serverTimeOffset, chat, threadId: MAIN_THREAD_ID });
|
||||||
} else {
|
} else {
|
||||||
void callApi('toggleDialogUnread', {
|
void callApi('toggleDialogUnread', {
|
||||||
chat,
|
chat,
|
||||||
@ -723,6 +729,7 @@ async function loadChats(listType: 'active' | 'archived', offsetId?: number, off
|
|||||||
offsetDate,
|
offsetDate,
|
||||||
archived: listType === 'archived',
|
archived: listType === 'archived',
|
||||||
withPinned: getGlobal().chats.orderedPinnedIds[listType] === undefined,
|
withPinned: getGlobal().chats.orderedPinnedIds[listType] === undefined,
|
||||||
|
serverTimeOffset: getGlobal().serverTimeOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
@ -243,6 +243,7 @@ addReducer('sendMessage', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addReducer('editMessage', (global, actions, payload) => {
|
addReducer('editMessage', (global, actions, payload) => {
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
const { text, entities } = payload!;
|
const { text, entities } = payload!;
|
||||||
|
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
@ -258,7 +259,7 @@ addReducer('editMessage', (global, actions, payload) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void callApi('editMessage', {
|
void callApi('editMessage', {
|
||||||
chat, message, text, entities, noWebPage: selectNoWebPage(global, chatId, threadId),
|
chat, message, text, entities, noWebPage: selectNoWebPage(global, chatId, threadId), serverTimeOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
actions.setEditingId({ messageId: undefined });
|
actions.setEditingId({ messageId: undefined });
|
||||||
@ -404,6 +405,7 @@ addReducer('deleteHistory', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addReducer('markMessageListRead', (global, actions, payload) => {
|
addReducer('markMessageListRead', (global, actions, payload) => {
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
if (!currentMessageList) {
|
if (!currentMessageList) {
|
||||||
return;
|
return;
|
||||||
@ -418,7 +420,9 @@ addReducer('markMessageListRead', (global, actions, payload) => {
|
|||||||
const { maxId } = payload!;
|
const { maxId } = payload!;
|
||||||
|
|
||||||
runThrottledForMarkRead(() => {
|
runThrottledForMarkRead(() => {
|
||||||
void callApi('markMessageListRead', { chat, threadId, maxId });
|
void callApi('markMessageListRead', {
|
||||||
|
serverTimeOffset, chat, threadId, maxId,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -703,6 +707,7 @@ async function sendMessage(params: {
|
|||||||
sticker: ApiSticker;
|
sticker: ApiSticker;
|
||||||
gif: ApiVideo;
|
gif: ApiVideo;
|
||||||
poll: ApiNewPoll;
|
poll: ApiNewPoll;
|
||||||
|
serverTimeOffset?: number;
|
||||||
}) {
|
}) {
|
||||||
let localId: number | undefined;
|
let localId: number | undefined;
|
||||||
const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => {
|
const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => {
|
||||||
@ -730,6 +735,7 @@ async function sendMessage(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
|
params.serverTimeOffset = global.serverTimeOffset;
|
||||||
const currentMessageList = selectCurrentMessageList(global);
|
const currentMessageList = selectCurrentMessageList(global);
|
||||||
if (!currentMessageList) {
|
if (!currentMessageList) {
|
||||||
return;
|
return;
|
||||||
@ -756,6 +762,7 @@ function forwardMessages(
|
|||||||
fromChat,
|
fromChat,
|
||||||
toChat,
|
toChat,
|
||||||
messages,
|
messages,
|
||||||
|
serverTimeOffset: getGlobal().serverTimeOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
setGlobal({
|
setGlobal({
|
||||||
|
|||||||
@ -308,9 +308,12 @@ addReducer('loadNotificationExceptions', () => {
|
|||||||
callApi('fetchNotificationExceptions');
|
callApi('fetchNotificationExceptions');
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('loadNotificationSettings', () => {
|
addReducer('loadNotificationSettings', (global) => {
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
(async () => {
|
(async () => {
|
||||||
const result = await callApi('fetchNotificationSettings');
|
const result = await callApi('fetchNotificationSettings', {
|
||||||
|
serverTimeOffset,
|
||||||
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,7 @@ async function loadAndReplaceChats() {
|
|||||||
const result = await callApi('fetchChats', {
|
const result = await callApi('fetchChats', {
|
||||||
limit: CHAT_LIST_LOAD_SLICE,
|
limit: CHAT_LIST_LOAD_SLICE,
|
||||||
withPinned: true,
|
withPinned: true,
|
||||||
|
serverTimeOffset: getGlobal().serverTimeOffset,
|
||||||
});
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -166,7 +167,9 @@ async function loadAndReplaceArchivedChats() {
|
|||||||
limit: CHAT_LIST_LOAD_SLICE,
|
limit: CHAT_LIST_LOAD_SLICE,
|
||||||
archived: true,
|
archived: true,
|
||||||
withPinned: true,
|
withPinned: true,
|
||||||
|
serverTimeOffset: getGlobal().serverTimeOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
|
|
||||||
const runDebouncedForFetchFullUser = debounce((cb) => cb(), 500, false, true);
|
const runDebouncedForFetchFullUser = debounce((cb) => cb(), 500, false, true);
|
||||||
const TOP_PEERS_REQUEST_COOLDOWN = 60000; // 1 min
|
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||||
|
|
||||||
addReducer('loadFullUser', (global, actions, payload) => {
|
addReducer('loadFullUser', (global, actions, payload) => {
|
||||||
const { userId } = payload!;
|
const { userId } = payload!;
|
||||||
@ -49,9 +49,14 @@ addReducer('loadUser', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addReducer('loadTopUsers', (global) => {
|
addReducer('loadTopUsers', (global) => {
|
||||||
const { hash, lastRequestedAt } = global.topPeers;
|
const {
|
||||||
|
serverTimeOffset,
|
||||||
|
topPeers: {
|
||||||
|
hash, lastRequestedAt,
|
||||||
|
},
|
||||||
|
} = global;
|
||||||
|
|
||||||
if (!lastRequestedAt || Date.now() - lastRequestedAt > TOP_PEERS_REQUEST_COOLDOWN) {
|
if (!lastRequestedAt || Date.now() / 1000 + serverTimeOffset - lastRequestedAt > TOP_PEERS_REQUEST_COOLDOWN) {
|
||||||
void loadTopUsers(hash);
|
void loadTopUsers(hash);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -95,7 +100,7 @@ async function loadTopUsers(usersHash?: number) {
|
|||||||
...global.topPeers,
|
...global.topPeers,
|
||||||
hash,
|
hash,
|
||||||
userIds: ids,
|
userIds: ids,
|
||||||
lastRequestedAt: Date.now(),
|
lastRequestedAt: Date.now() / 1000 + global.serverTimeOffset,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
ApiUpdateAuthorizationError,
|
ApiUpdateAuthorizationError,
|
||||||
ApiUpdateConnectionState,
|
ApiUpdateConnectionState,
|
||||||
ApiUpdateSession,
|
ApiUpdateSession,
|
||||||
ApiUpdateCurrentUser,
|
ApiUpdateCurrentUser, ApiUpdateServerTimeOffset,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { DEBUG, SESSION_USER_KEY } from '../../../config';
|
import { DEBUG, SESSION_USER_KEY } from '../../../config';
|
||||||
import { subscribe } from '../../../util/notifications';
|
import { subscribe } from '../../../util/notifications';
|
||||||
@ -46,6 +46,10 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
onUpdateSession(update);
|
onUpdateSession(update);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'updateServerTimeOffset':
|
||||||
|
onUpdateServerTimeOffset(update);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'updateCurrentUser':
|
case 'updateCurrentUser':
|
||||||
onUpdateCurrentUser(update);
|
onUpdateCurrentUser(update);
|
||||||
break;
|
break;
|
||||||
@ -155,6 +159,13 @@ function onUpdateSession(update: ApiUpdateSession) {
|
|||||||
getDispatch().saveSession({ sessionData });
|
getDispatch().saveSession({ sessionData });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdateServerTimeOffset(update: ApiUpdateServerTimeOffset) {
|
||||||
|
setGlobal({
|
||||||
|
...getGlobal(),
|
||||||
|
serverTimeOffset: update.serverTimeOffset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function onUpdateCurrentUser(update: ApiUpdateCurrentUser) {
|
function onUpdateCurrentUser(update: ApiUpdateCurrentUser) {
|
||||||
const { currentUser } = update;
|
const { currentUser } = update;
|
||||||
|
|
||||||
|
|||||||
@ -469,6 +469,7 @@ export function sortChatIds(
|
|||||||
chatsById: Record<number, ApiChat>,
|
chatsById: Record<number, ApiChat>,
|
||||||
shouldPrioritizeVerified = false,
|
shouldPrioritizeVerified = false,
|
||||||
priorityIds?: number[],
|
priorityIds?: number[],
|
||||||
|
serverTimeOffset = 0,
|
||||||
) {
|
) {
|
||||||
return orderBy(chatIds, (id) => {
|
return orderBy(chatIds, (id) => {
|
||||||
const chat = chatsById[id];
|
const chat = chatsById[id];
|
||||||
@ -490,7 +491,7 @@ export function sortChatIds(
|
|||||||
// Assuming that last message date can't be less than now,
|
// Assuming that last message date can't be less than now,
|
||||||
// this should place prioritized on top of the list.
|
// this should place prioritized on top of the list.
|
||||||
// Then we subtract index of `id` in `priorityIds` to preserve selected order
|
// Then we subtract index of `id` in `priorityIds` to preserve selected order
|
||||||
priority += Date.now() + (priorityIds.length - priorityIds.indexOf(id));
|
priority += Date.now() + serverTimeOffset * 1000 + (priorityIds.length - priorityIds.indexOf(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return priority;
|
return priority;
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export function getUserFullName(user?: ApiUser) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserStatus(lang: LangFn, user: ApiUser) {
|
export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: number) {
|
||||||
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
if (user.id === SERVICE_NOTIFICATIONS_USER_ID) {
|
||||||
return lang('ServiceNotifications').toLowerCase();
|
return lang('ServiceNotifications').toLowerCase();
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ export function getUserStatus(lang: LangFn, user: ApiUser) {
|
|||||||
|
|
||||||
if (!wasOnline) return lang('LastSeen.Offline');
|
if (!wasOnline) return lang('LastSeen.Offline');
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date(new Date().getTime() + serverTimeOffset * 1000);
|
||||||
const wasOnlineDate = new Date(wasOnline * 1000);
|
const wasOnlineDate = new Date(wasOnline * 1000);
|
||||||
|
|
||||||
if (wasOnlineDate >= now) {
|
if (wasOnlineDate >= now) {
|
||||||
@ -118,7 +118,8 @@ export function getUserStatus(lang: LangFn, user: ApiUser) {
|
|||||||
// today
|
// today
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
if (wasOnlineDate > today) {
|
const serverToday = new Date(today.getTime() + serverTimeOffset * 1000);
|
||||||
|
if (wasOnlineDate > serverToday) {
|
||||||
// up to 6 hours ago
|
// up to 6 hours ago
|
||||||
if (diff.getTime() / 1000 < 6 * 60 * 60) {
|
if (diff.getTime() / 1000 < 6 * 60 * 60) {
|
||||||
const hours = Math.floor(diff.getTime() / 1000 / 60 / 60);
|
const hours = Math.floor(diff.getTime() / 1000 / 60 / 60);
|
||||||
@ -132,8 +133,9 @@ export function getUserStatus(lang: LangFn, user: ApiUser) {
|
|||||||
// yesterday
|
// yesterday
|
||||||
const yesterday = new Date();
|
const yesterday = new Date();
|
||||||
yesterday.setDate(now.getDate() - 1);
|
yesterday.setDate(now.getDate() - 1);
|
||||||
today.setHours(0, 0, 0, 0);
|
yesterday.setHours(0, 0, 0, 0);
|
||||||
if (wasOnlineDate > yesterday) {
|
const serverYesterday = new Date(yesterday.getTime() + serverTimeOffset * 1000);
|
||||||
|
if (wasOnlineDate > serverYesterday) {
|
||||||
return lang('LastSeen.YesterdayAt', formatTime(wasOnlineDate));
|
return lang('LastSeen.YesterdayAt', formatTime(wasOnlineDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,9 +186,10 @@ export function sortUserIds(
|
|||||||
userIds: number[],
|
userIds: number[],
|
||||||
usersById: Record<number, ApiUser>,
|
usersById: Record<number, ApiUser>,
|
||||||
priorityIds?: number[],
|
priorityIds?: number[],
|
||||||
|
serverTimeOffset = 0,
|
||||||
) {
|
) {
|
||||||
return orderBy(userIds, (id) => {
|
return orderBy(userIds, (id) => {
|
||||||
const now = Date.now() / 1000;
|
const now = Date.now() / 1000 + serverTimeOffset;
|
||||||
|
|
||||||
if (priorityIds && priorityIds.includes(id)) {
|
if (priorityIds && priorityIds.includes(id)) {
|
||||||
// Assuming that online status expiration date can't be as far as two days from now,
|
// Assuming that online status expiration date can't be as far as two days from now,
|
||||||
|
|||||||
@ -335,6 +335,7 @@ export function selectForwardedSender(global: GlobalState, message: ApiMessage):
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectAllowedMessageActions(global: GlobalState, message: ApiMessage, threadId: number) {
|
export function selectAllowedMessageActions(global: GlobalState, message: ApiMessage, threadId: number) {
|
||||||
|
const { serverTimeOffset } = global;
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
if (!chat || chat.isRestricted) {
|
if (!chat || chat.isRestricted) {
|
||||||
return {};
|
return {};
|
||||||
@ -351,7 +352,7 @@ export function selectAllowedMessageActions(global: GlobalState, message: ApiMes
|
|||||||
const isAction = isActionMessage(message);
|
const isAction = isActionMessage(message);
|
||||||
const { content } = message;
|
const { content } = message;
|
||||||
const isMessageEditable = (
|
const isMessageEditable = (
|
||||||
(isChatWithSelf || Date.now() - message.date * 1000 < MESSAGE_EDIT_ALLOWED_TIME_MS)
|
(isChatWithSelf || Date.now() + serverTimeOffset * 1000 - message.date * 1000 < MESSAGE_EDIT_ALLOWED_TIME_MS)
|
||||||
&& !(
|
&& !(
|
||||||
content.sticker || content.contact || content.poll || content.action || content.audio
|
content.sticker || content.contact || content.poll || content.action || content.audio
|
||||||
|| (content.video && content.video.isRound)
|
|| (content.video && content.video.isRound)
|
||||||
|
|||||||
@ -133,10 +133,13 @@ let areSettingsLoaded = false;
|
|||||||
async function loadNotificationSettings() {
|
async function loadNotificationSettings() {
|
||||||
if (areSettingsLoaded) return;
|
if (areSettingsLoaded) return;
|
||||||
const [result] = await Promise.all([
|
const [result] = await Promise.all([
|
||||||
callApi('fetchNotificationSettings'),
|
callApi('fetchNotificationSettings', {
|
||||||
|
serverTimeOffset: getGlobal().serverTimeOffset,
|
||||||
|
}),
|
||||||
callApi('fetchNotificationExceptions'),
|
callApi('fetchNotificationExceptions'),
|
||||||
]);
|
]);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
setGlobal(replaceSettings(getGlobal(), result));
|
setGlobal(replaceSettings(getGlobal(), result));
|
||||||
areSettingsLoaded = true;
|
areSettingsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user