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