From 6b1633726008baa2b818a746d5dd0b966ab6ad12 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Fri, 17 Dec 2021 00:04:56 +0400 Subject: [PATCH] HTTP transport fixes Some fixes on auth pages Fix messageActionInviteToGroupCall Yesterday user's status Fix 12-hour format time in user status --- src/components/passwordInputField.ts | 12 +- src/config/modes.ts | 19 +- src/helpers/date.ts | 20 +- src/lang.ts | 6 +- src/langSign.ts | 2 +- src/lib/appManagers/appMessagesManager.ts | 33 +-- src/lib/appManagers/appUsersManager.ts | 21 +- src/lib/mtproto/apiManager.ts | 96 ++++++-- src/lib/mtproto/authorizer.ts | 61 +++-- src/lib/mtproto/dcConfigurator.ts | 43 ++-- src/lib/mtproto/networker.ts | 256 ++++++++++++-------- src/lib/mtproto/transports/controller.ts | 25 ++ src/lib/mtproto/transports/http.ts | 74 +++++- src/lib/mtproto/transports/tcpObfuscated.ts | 19 +- src/lib/mtproto/transports/transport.ts | 2 + src/pages/pageIm.ts | 1 + src/pages/pagePassword.ts | 3 + src/scss/partials/_input.scss | 3 + src/scss/style.scss | 8 + 19 files changed, 468 insertions(+), 236 deletions(-) create mode 100644 src/lib/mtproto/transports/controller.ts diff --git a/src/components/passwordInputField.ts b/src/components/passwordInputField.ts index 6c948b47..ff6cb370 100644 --- a/src/components/passwordInputField.ts +++ b/src/components/passwordInputField.ts @@ -4,6 +4,7 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +// import { IS_MOBILE_SAFARI, IS_SAFARI } from "../environment/userAgent"; import { cancelEvent } from "../helpers/dom/cancelEvent"; import InputField, { InputFieldOptions } from "./inputField"; @@ -21,12 +22,15 @@ export default class PasswordInputField extends InputField { const input = this.input as HTMLInputElement; input.type = 'password'; input.setAttribute('required', ''); + input.name = 'notsearch_password'; input.autocomplete = 'off'; - /* input.readOnly = true; - input.addEventListener('focus', () => { - input.removeAttribute('readonly'); - }, {once: true}); */ + /* if(IS_SAFARI && !IS_MOBILE_SAFARI) { + input.setAttribute('readonly', ''); + input.addEventListener('focus', () => { + input.removeAttribute('readonly'); + }, {once: true}); + } */ // * https://stackoverflow.com/a/35949954/6758968 const stealthy = document.createElement('input'); diff --git a/src/config/modes.ts b/src/config/modes.ts index a8cbc7b6..3b975964 100644 --- a/src/config/modes.ts +++ b/src/config/modes.ts @@ -9,15 +9,30 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ +import type { TransportType } from "../lib/mtproto/dcConfigurator"; + const Modes = { test: location.search.indexOf('test=1') > 0/* || true */, debug: location.search.indexOf('debug=1') > 0, - http: false, //location.search.indexOf('http=1') > 0, + http: false, ssl: true, // location.search.indexOf('ssl=1') > 0 || location.protocol === 'https:' && location.search.indexOf('ssl=0') === -1, multipleConnections: true, - asServiceWorker: false + asServiceWorker: false, + transport: 'https' as TransportType }; +/// #if MTPROTO_HAS_HTTP +Modes.http = location.search.indexOf('http=1') > 0; +/// #endif + +/// #if MTPROTO_HTTP || !MTPROTO_HAS_WS +Modes.http = true; +/// #endif + +if(Modes.http) { + Modes.transport = 'https'; +} + /// #if MTPROTO_SW Modes.asServiceWorker = true; /// #endif diff --git a/src/helpers/date.ts b/src/helpers/date.ts index 435baf70..c459d017 100644 --- a/src/helpers/date.ts +++ b/src/helpers/date.ts @@ -47,7 +47,9 @@ export function formatDateAccordingToTodayNew(time: Date) { }).element; } -export function formatFullSentTime(timestamp: number) { +export function formatFullSentTimeRaw(timestamp: number, options: { + capitalize?: boolean +} = {}) { const date = new Date(); const time = new Date(timestamp * 1000); const now = date.getTime() / 1000; @@ -56,9 +58,13 @@ export function formatFullSentTime(timestamp: number) { let dateEl: Node | string; if((now - timestamp) < ONE_DAY && date.getDate() === time.getDate()) { // if the same day - dateEl = i18n('Date.Today'); + dateEl = i18n(options.capitalize ? 'Date.Today' : 'Peer.Status.Today'); } else if((now - timestamp) < (ONE_DAY * 2) && (date.getDate() - 1) === time.getDate()) { // yesterday - dateEl = capitalizeFirstLetter(I18n.format('Yesterday', true)); + dateEl = i18n(options.capitalize ? 'Yesterday' : 'Peer.Status.Yesterday'); + + if(options.capitalize) { + (dateEl as HTMLElement).style.textTransform = 'capitalize'; + } } else if(date.getFullYear() !== time.getFullYear()) { // different year dateEl = new I18n.IntlDateElement({ date: time, @@ -80,6 +86,14 @@ export function formatFullSentTime(timestamp: number) { // dateStr = months[time.getMonth()].slice(0, 3) + ' ' + time.getDate(); } + return {dateEl, timeEl}; +} + +export function formatFullSentTime(timestamp: number) { + const {dateEl, timeEl} = formatFullSentTimeRaw(timestamp, { + capitalize: true + }); + const fragment = document.createDocumentFragment(); fragment.append(dateEl, ' ', i18n('ScheduleController.at'), ' ', timeEl); return fragment; diff --git a/src/lang.ts b/src/lang.ts index d95dbfa7..fd1df318 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -480,9 +480,6 @@ const lang = { "Emoji": "Emoji", "AddContactTitle": "Add Contact", "HiddenName": "Deleted Account", - "ActionGroupCallInvited": "un1 invited un2 to the voice chat", - "ActionGroupCallYouInvited": "You invited un2 to the voice chat", - "ActionGroupCallInvitedYou": "un1 invited you to the voice chat", "Seconds": { "one_value": "%1$d second", "other_value": "%1$d seconds" @@ -655,6 +652,9 @@ const lang = { "Chat.Service.VoiceChatFinished": "%1$@ ended the video chat (%2$@)", "Chat.Service.VoiceChatFinishedYou": "You ended the video chat (%@)", "Chat.Service.VoiceChatFinished.Channel": "Live Stream ended (%1$@)", + "Chat.Service.VoiceChatInvitation": "%1$@ invited %2$@ to the [video chat](open)", + "Chat.Service.VoiceChatInvitationByYou": "You invited %1$@ to the [video chat](open)", + "Chat.Service.VoiceChatInvitationForYou": "%1$@ invited you to the [video chat](open)", "ChatList.Service.VoiceChatScheduled": "%1$@ scheduled a video chat for %2$@", "ChatList.Service.VoiceChatScheduledYou": "You scheduled a video chat for %2$@", "Chat.Poll.Unvote": "Retract Vote", diff --git a/src/langSign.ts b/src/langSign.ts index 06d91fba..098b8a4f 100644 --- a/src/langSign.ts +++ b/src/langSign.ts @@ -28,7 +28,7 @@ const lang = { "Login.ContinueOnLanguage": "Continue in English", "Login.QR.Title": "Log in to Telegram by QR Code", "Login.QR.Help1": "Open Telegram on your phone", - "Login.QR.Help2": "Go to **Settings** > **Devices** > **Scan QR**", + "Login.QR.Help2": "Go to **Settings** > **Devices** > **Link Desktop Device**", "Login.QR.Help3": "Point your phone at this screen to confirm login", "Login.QR.Cancel": "Log in by phone Number", "Login.QR.Login": "Log in by QR Code", diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index f51a8da9..d53509fd 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -2933,6 +2933,20 @@ export class AppMessagesManager { return el; } + private wrapJoinVoiceChatAnchor(message: Message.messageService) { + const action = message.action as MessageAction.messageActionInviteToGroupCall; + const {onclick, url} = RichTextProcessor.wrapUrl(`tg://voicechat?chat_id=${message.peerId.toChatId()}&id=${action.call.id}&access_hash=${action.call.access_hash}`); + if(!onclick) { + return document.createElement('span'); + } + + const a = document.createElement('a'); + a.href = url; + a.setAttribute('onclick', onclick + '(this)'); + + return a; + } + public wrapMessageActionTextNew(message: MyMessage, plain: true): string; public wrapMessageActionTextNew(message: MyMessage, plain?: false): HTMLElement; public wrapMessageActionTextNew(message: MyMessage, plain: boolean): HTMLElement | string; @@ -2979,16 +2993,7 @@ export class AppMessagesManager { if(action.duration !== undefined) { args.push(formatCallDuration(action.duration, plain)); } else { - const {onclick, url} = RichTextProcessor.wrapUrl(`tg://voicechat?chat_id=${message.peerId.toChatId()}&id=${action.call.id}&access_hash=${action.call.access_hash}`); - if(!onclick) { - args.push(document.createElement('span')); - break; - } - - const a = document.createElement('a'); - a.href = url; - a.setAttribute('onclick', onclick + '(this)'); - args.push(a); + args.push(this.wrapJoinVoiceChatAnchor(message as any)); } break; @@ -2996,15 +3001,15 @@ export class AppMessagesManager { case 'messageActionInviteToGroupCall': { const peerIds = [message.fromId, action.users[0].toPeerId()]; - let a = 'ActionGroupCall'; + let a = 'Chat.Service.VoiceChatInvitation'; const myId = appUsersManager.getSelf().id; - if(peerIds[0] === myId) a += 'You'; - a += 'Invited'; - if(peerIds[1] === myId) a += 'You'; + if(peerIds[0] === myId) a += 'ByYou'; + else if(peerIds[1] === myId) a += 'ForYou'; indexOfAndSplice(peerIds, myId); langPackKey = a as LangPackKey; args = peerIds.map(peerId => getNameDivHTML(peerId, plain)); + args.push(this.wrapJoinVoiceChatAnchor(message as any)); break; } diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index c855025c..4b5b694b 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -14,7 +14,7 @@ import { filterUnique, indexOfAndSplice } from "../../helpers/array"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import cleanSearchText from "../../helpers/cleanSearchText"; import cleanUsername from "../../helpers/cleanUsername"; -import { tsNow } from "../../helpers/date"; +import { formatFullSentTimeRaw, tsNow } from "../../helpers/date"; import { formatPhoneNumber } from "../../helpers/formatPhoneNumber"; import { safeReplaceObject, isObject } from "../../helpers/object"; import { Chat, InputContact, InputMedia, InputPeer, InputUser, User as MTUser, UserProfilePhoto, UserStatus } from "../../layer"; @@ -581,23 +581,24 @@ export class AppUsersManager { case 'userStatusOffline': { const date = user.status.was_online; - const now = Date.now() / 1000; + const today = new Date(); + const now = today.getTime() / 1000 | 0; - if((now - date) < 60) { + const diff = now - date; + if(diff < 60) { key = 'Peer.Status.justNow'; - } else if((now - date) < 3600) { + } else if(diff < 3600) { key = 'Peer.Status.minAgo'; - const c = (now - date) / 60 | 0; + const c = diff / 60 | 0; args = [c]; - } else if(now - date < 86400) { + } else if(diff < 86400 && today.getDate() === new Date(date * 1000).getDate()) { key = 'LastSeen.HoursAgo'; - const c = (now - date) / 3600 | 0; + const c = diff / 3600 | 0; args = [c]; } else { key = 'Peer.Status.LastSeenAt'; - const d = new Date(date * 1000); - args = [('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2), - ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2)]; + const {dateEl, timeEl} = formatFullSentTimeRaw(date); + args = [dateEl, timeEl]; } break; diff --git a/src/lib/mtproto/apiManager.ts b/src/lib/mtproto/apiManager.ts index 02c9c923..f11ae3c6 100644 --- a/src/lib/mtproto/apiManager.ts +++ b/src/lib/mtproto/apiManager.ts @@ -30,6 +30,7 @@ import IDBStorage from '../idb'; import CryptoWorker from "../crypto/cryptoworker"; import ctx from '../../environment/ctx'; import noop from '../../helpers/noop'; +import Modes from '../../config/modes'; /// #if !MTPROTO_WORKER import rootScope from '../rootScope'; @@ -73,25 +74,39 @@ export class ApiManager { private cachedNetworkers: { [transportType in TransportType]: { [connectionType in ConnectionType]: { - [dcId: number]: MTPNetworker[] + [dcId: DcId]: MTPNetworker[] } } - } = {} as any; + }; - private cachedExportPromise: {[x: number]: Promise} = {}; - private gettingNetworkers: {[dcIdAndType: string]: Promise} = {}; - private baseDcId: DcId = 0 as DcId; + private cachedExportPromise: {[x: number]: Promise}; + private gettingNetworkers: {[dcIdAndType: string]: Promise}; + private baseDcId: DcId; //public telegramMeNotified = false; - private log: ReturnType = logger('API'); + private log: ReturnType; private afterMessageTempIds: { [tempId: string]: { messageId: string, promise: Promise } - } = {}; + }; + + private transportType: TransportType; + + constructor() { + this.log = logger('API'); + + this.cachedNetworkers = {} as any; + this.cachedExportPromise = {}; + this.gettingNetworkers = {}; + this.baseDcId = 0; + this.afterMessageTempIds = {}; + + this.transportType = Modes.transport; + } //private lol = false; @@ -112,6 +127,61 @@ export class ApiManager { } } */ + private getTransportType(connectionType: ConnectionType) { + /// #if MTPROTO_HTTP_UPLOAD + // @ts-ignore + const transportType: TransportType = connectionType === 'upload' && IS_SAFARI ? 'https' : 'websocket'; + //const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket'; + /// #else + // @ts-ignore + const transportType: TransportType = this.transportType; + /// #endif + + return transportType; + } + + private iterateNetworkers(callback: (o: {networker: MTPNetworker, dcId: DcId, connectionType: ConnectionType, transportType: TransportType, index: number, array: MTPNetworker[]}) => void) { + for(const transportType in this.cachedNetworkers) { + const connections = this.cachedNetworkers[transportType as TransportType]; + for(const connectionType in connections) { + const dcs = connections[connectionType as ConnectionType]; + for(const dcId in dcs) { + const networkers = dcs[dcId as any as DcId]; + networkers.forEach((networker, idx, arr) => { + callback({ + networker, + dcId: +dcId as DcId, + connectionType: connectionType as ConnectionType, + transportType: transportType as TransportType, + index: idx, + array: arr + }); + }); + } + } + } + } + + private chooseServer(dcId: DcId, connectionType: ConnectionType, transportType: TransportType) { + return dcConfigurator.chooseServer(dcId, connectionType, transportType, connectionType === 'client'); + } + + public changeTransportType(transportType: TransportType) { + const oldTransportType = this.transportType; + const oldObject = this.cachedNetworkers[oldTransportType]; + const newObject = this.cachedNetworkers[transportType]; + this.cachedNetworkers[transportType] = oldObject; + this.cachedNetworkers[oldTransportType] = newObject; + + this.transportType = transportType; + + this.iterateNetworkers((info) => { + const transportType = this.getTransportType(info.connectionType); + const transport = this.chooseServer(info.dcId, info.connectionType, transportType); + info.networker.changeTransport(transport); + }); + } + public async getBaseDcId() { if(this.baseDcId) { return this.baseDcId; @@ -203,15 +273,7 @@ export class ApiManager { const connectionType: ConnectionType = options.fileDownload ? 'download' : (options.fileUpload ? 'upload' : 'client'); //const connectionType: ConnectionType = 'client'; - /// #if MTPROTO_HTTP_UPLOAD - // @ts-ignore - const transportType: TransportType = connectionType === 'upload' && IS_SAFARI ? 'https' : 'websocket'; - //const transportType: TransportType = connectionType !== 'client' ? 'https' : 'websocket'; - /// #else - // @ts-ignore - const transportType = 'websocket'; - /// #endif - + const transportType = this.getTransportType(connectionType); if(!this.cachedNetworkers.hasOwnProperty(transportType)) { this.cachedNetworkers[transportType] = { client: {}, @@ -250,9 +312,9 @@ export class ApiManager { const ak: DcAuthKey = `dc${dcId}_auth_key` as any; const ss: DcServerSalt = `dc${dcId}_server_salt` as any; + const transport = this.chooseServer(dcId, connectionType, transportType); return this.gettingNetworkers[getKey] = Promise.all([ak, ss].map(key => sessionStorage.get(key))) .then(async([authKeyHex, serverSaltHex]) => { - const transport = dcConfigurator.chooseServer(dcId, connectionType, transportType, connectionType === 'client'); let networker: MTPNetworker; if(authKeyHex && authKeyHex.length === 512) { if(!serverSaltHex || serverSaltHex.length !== 16) { diff --git a/src/lib/mtproto/authorizer.ts b/src/lib/mtproto/authorizer.ts index 9a0b85a5..7d28e7bd 100644 --- a/src/lib/mtproto/authorizer.ts +++ b/src/lib/mtproto/authorizer.ts @@ -21,7 +21,8 @@ import { bytesCmp, bytesToHex, bytesFromHex, bytesXor } from "../../helpers/byte import DEBUG from "../../config/debug"; import { cmp, int2bigInt, one, pow, str2bigInt, sub } from "../../vendor/leemon"; import { addPadding } from "./bin_utils"; -import { Awaited } from "../../types"; +import { Awaited, DcId } from "../../types"; +import { ApiError } from "./apiManager"; /* let fNewNonce: any = bytesFromHex('8761970c24cb2329b5b2459752c502f3057cb7e8dbab200e526e8767fdc73b3c').reverse(); let fNonce: any = bytesFromHex('b597720d11faa5914ef485c529cde414').reverse(); @@ -66,8 +67,6 @@ type AuthOptions = { localTime?: number, serverTime?: any, - - localTry?: number }; type ResPQ = { @@ -100,16 +99,17 @@ type req_DH_params = { export class Authorizer { private cached: { - [dcId: number]: Promise - } = {}; + [dcId: DcId]: Promise + }; private log: ReturnType; constructor() { + this.cached = {}; this.log = logger(`AUTHORIZER`, LogTypes.Error | LogTypes.Log); } - private sendPlainRequest(dcId: number, requestArray: Uint8Array) { + private sendPlainRequest(dcId: DcId, requestArray: Uint8Array) { const requestLength = requestArray.byteLength; const header = new TLSerialization(); @@ -571,39 +571,38 @@ export class Authorizer { } } - public async auth(dcId: number): Promise { - if(dcId in this.cached) { - return this.cached[dcId]; + public auth(dcId: DcId) { + let promise = this.cached[dcId]; + if(promise) { + return promise; } - const nonce = /* fNonce ? fNonce : */new Uint8Array(16).randomize(); - /* const nonce = new Array(16); - MTProto.secureRandom.nextBytes(nonce); */ - if(!dcConfigurator.chooseServer(dcId)) { throw new Error('[MT] No server found for dc ' + dcId); } - // await new Promise((resolve) => setTimeout(resolve, 2e3)); - - const auth: AuthOptions = {dcId, nonce, localTry: 1}; - - try { - const promise = this.sendReqPQ(auth); - this.cached[dcId] = promise; - return await promise; - } catch(err) { - if(/* err.originalError === -404 && */auth.localTry <= 3) { - return this.sendReqPQ({ - dcId: auth.dcId, - nonce: new Uint8Array(16).randomize(), - localTry: auth.localTry + 1 - }); + promise = new Promise(async(resolve, reject) => { + let error: ApiError; + let _try = 1; + while(_try++ <= 3) { + try { + const auth: AuthOptions = { + dcId, + nonce: new Uint8Array(16).randomize() + }; + + const promise = this.sendReqPQ(auth); + resolve(await promise); + return; + } catch(err) { + error = err; + } } - delete this.cached[dcId]; - throw err; - } + reject(error); + }); + + return this.cached[dcId] = promise; } } diff --git a/src/lib/mtproto/dcConfigurator.ts b/src/lib/mtproto/dcConfigurator.ts index 9e509031..14d0bd8e 100644 --- a/src/lib/mtproto/dcConfigurator.ts +++ b/src/lib/mtproto/dcConfigurator.ts @@ -11,18 +11,20 @@ import MTTransport, { MTConnectionConstructable } from './transports/transport'; import Modes from '../../config/modes'; +import { indexOfAndSplice } from '../../helpers/array'; +import App from '../../config/app'; -/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD +/// #if MTPROTO_HAS_HTTP import HTTP from './transports/http'; /// #endif -/// #if !MTPROTO_HTTP +/// #if MTPROTO_HAS_WS import Socket from './transports/websocket'; import TcpObfuscated from './transports/tcpObfuscated'; import { IS_SAFARI } from '../../environment/userAgent'; import { IS_WEB_WORKER } from '../../helpers/context'; import SocketProxied from './transports/socketProxied'; -import App from '../../config/app'; +import { DcId } from '../../types'; /// #endif export type TransportType = 'websocket' | 'https' | 'http'; @@ -30,7 +32,7 @@ export type ConnectionType = 'client' | 'download' | 'upload'; type Servers = { [transportType in TransportType]: { [connectionType in ConnectionType]: { - [dcId: number]: MTTransport[] + [dcId: DcId]: MTTransport[] } } }; @@ -56,8 +58,8 @@ export class DcConfigurator { public chosenServers: Servers = {} as any; - /// #if !MTPROTO_HTTP - private transportSocket = (dcId: number, connectionType: ConnectionType, suffix: string) => { + /// #if MTPROTO_HAS_WS + private transportSocket = (dcId: DcId, connectionType: ConnectionType, suffix: string) => { const path = 'apiws' + TEST_SUFFIX; const chosenServer = `wss://${App.suffix.toLowerCase()}ws${dcId}${suffix}.web.telegram.org/${path}`; const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : ''; @@ -70,25 +72,33 @@ export class DcConfigurator { }; /// #endif - /// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP - private transportHTTP = (dcId: number, connectionType: ConnectionType, suffix: string) => { + /// #if MTPROTO_HAS_HTTP + private transportHTTP = (dcId: DcId, connectionType: ConnectionType, suffix: string) => { + let chosenServer: string; if(Modes.ssl || !Modes.http) { const subdomain = this.sslSubdomains[dcId - 1] + (connectionType !== 'client' ? '-1' : ''); const path = Modes.test ? 'apiw_test1' : 'apiw1'; - const chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path; - return new HTTP(dcId, chosenServer); + chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path; } else { for(let dcOption of this.dcOptions) { if(dcOption.id === dcId) { - const chosenServer = 'http://' + dcOption.host + (dcOption.port !== 80 ? ':' + dcOption.port : '') + '/apiw1'; - return new HTTP(dcId, chosenServer); + chosenServer = 'http://' + dcOption.host + (dcOption.port !== 80 ? ':' + dcOption.port : '') + '/apiw1'; + break; } } } + + const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : ''; + return new HTTP(dcId, chosenServer, logSuffix); }; /// #endif - public chooseServer(dcId: number, connectionType: ConnectionType = 'client', transportType: TransportType = 'websocket', reuse = true) { + public chooseServer( + dcId: DcId, + connectionType: ConnectionType = 'client', + transportType: TransportType = Modes.transport, + reuse = true + ) { /* if(transportType === 'websocket' && !Modes.multipleConnections) { connectionType = 'client'; } */ @@ -114,7 +124,7 @@ export class DcConfigurator { const suffix = connectionType === 'client' ? '' : '-1'; - /// #if MTPROTO_HTTP_UPLOAD + /// #if MTPROTO_HAS_WS && MTPROTO_HAS_HTTP transport = (transportType === 'websocket' ? this.transportSocket : this.transportHTTP)(dcId, connectionType, suffix); /// #elif !MTPROTO_HTTP transport = this.transportSocket(dcId, connectionType, suffix); @@ -145,10 +155,7 @@ export class DcConfigurator { for(const dcId in obj[transportType][connectionType]) { // @ts-ignore const transports: T[] = obj[transportType][connectionType][dcId]; - const idx = transports.indexOf(transport); - if(idx !== -1) { - transports.splice(idx, 1); - } + indexOfAndSplice(transports, transport); } } } diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts index b2770e6f..f05717d5 100644 --- a/src/lib/mtproto/networker.ts +++ b/src/lib/mtproto/networker.ts @@ -27,7 +27,7 @@ import DEBUG from '../../config/debug'; import Modes from '../../config/modes'; import noop from '../../helpers/noop'; -/// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP +/// #if MTPROTO_HAS_HTTP import HTTP from './transports/http'; /// #endif @@ -35,6 +35,7 @@ import type TcpObfuscated from './transports/tcpObfuscated'; import { bigInt2str, rightShift_, str2bigInt } from '../../vendor/leemon'; import { forEachReverse } from '../../helpers/array'; import { ConnectionStatus } from './connectionStatus'; +import ctx from '../../environment/ctx'; //console.error('networker included!', new Error().stack); @@ -95,21 +96,22 @@ export default class MTPNetworker { private pendingMessages: {[msgId: MTLong]: number} = {}; private pendingAcks: Array = []; private pendingResends: Array = []; - public connectionInited = false; + public connectionInited: boolean; private nextReqTimeout: number; private nextReq: number = 0; - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD - //private longPollInt: number; - private longPollPending = 0; + /// #if MTPROTO_HAS_HTTP + private longPollInterval: number; + private longPollPending: number; private checkConnectionTimeout: number; private checkConnectionPeriod = 0; - private sleepAfter = 0; + private sleepAfter: number; private offline = false; + private sendingLongPoll: boolean; /// #endif - private seqNo: number = 0; + private seqNo: number; private prevSessionId: Uint8Array; private sessionId: Uint8Array; private serverSalt: Uint8Array; @@ -133,14 +135,22 @@ export default class MTPNetworker { public onDrain: () => void; private onDrainTimeout: number; + public transport: MTTransport; + //private disconnectDelay: number; //private pingPromise: CancellablePromise; //public onConnectionStatusChange: (online: boolean) => void; //private debugRequests: Array<{before: Uint8Array, after: Uint8Array}> = []; - constructor(public dcId: number, private authKey: Uint8Array, private authKeyId: Uint8Array, - serverSalt: Uint8Array, public transport: MTTransport, options: InvokeApiOptions = {}) { + constructor( + public dcId: number, + private authKey: Uint8Array, + private authKeyId: Uint8Array, + serverSalt: Uint8Array, + transport: MTTransport, + options: InvokeApiOptions = {} + ) { this.authKeyUint8 = convertToUint8Array(this.authKey); this.serverSalt = convertToUint8Array(serverSalt); @@ -151,7 +161,7 @@ export default class MTPNetworker { const suffix = this.isFileUpload ? '-U' : this.isFileDownload ? '-D' : ''; this.name = 'NET-' + dcId + suffix; //this.log = logger(this.name, this.upload && this.dcId === 2 ? LogLevels.debug | LogLevels.warn | LogLevels.log | LogLevels.error : LogLevels.error); - this.log = logger(this.name, LogTypes.Log | /* LogTypes.Debug | */LogTypes.Error | LogTypes.Warn, undefined); + this.log = logger(this.name, LogTypes.Log | LogTypes.Debug | LogTypes.Error | LogTypes.Warn, undefined); this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */); // Test resend after bad_server_salt @@ -168,22 +178,7 @@ export default class MTPNetworker { // rootScope.offlineConnecting = true */ // } - /// #if MTPROTO_HTTP_UPLOAD - if(this.transport instanceof HTTP) { - /* this.longPollInt = */setInterval(this.checkLongPoll, 10000); - this.checkLongPoll(); - } else { - (this.transport as TcpObfuscated).networker = this; - } - /// #elif MTPROTO_HTTP - //if(this.transport instanceof HTTP) { - /* this.longPollInt = */setInterval(this.checkLongPoll, 10000); - this.checkLongPoll(); - /// #else - //} else { - (this.transport as TcpObfuscated).networker = this; - //} - /// #endif + this.changeTransport(transport); // * handle outcoming dead socket, server will close the connection // if((this.transport as TcpObfuscated).networker) { @@ -191,10 +186,6 @@ export default class MTPNetworker { // //setInterval(this.sendPingDelayDisconnect, (this.disconnectDelay - 5) * 1000); // this.sendPingDelayDisconnect(); // } - - if((this.transport as TcpObfuscated).connected) { - this.setConnectionStatus(ConnectionStatus.Connected); - } } private updateSession() { @@ -232,9 +223,9 @@ export default class MTPNetworker { sentMessage.msg_id = timeManager.generateId(); sentMessage.seq_no = this.generateSeqNo(sentMessage.notContentRelated || sentMessage.container); - /* if(DEBUG) { - this.log('updateSentMessage', sentMessage.msg_id, sentMessageId); - } */ + if(this.debug) { + this.log(`updateSentMessage, old=${sentMessageId}, new=${sentMessage.msg_id}`); + } this.sentMessages[sentMessage.msg_id] = sentMessage; delete this.sentMessages[sentMessageId]; @@ -372,9 +363,57 @@ export default class MTPNetworker { return this.pushMessage(message, options); } + public changeTransport(transport?: MTTransport) { + const oldTransport = this.transport; + if(oldTransport) { + if((oldTransport as TcpObfuscated).destroy) { + (oldTransport as TcpObfuscated).destroy(); + } + + if(this.nextReqTimeout) { + clearTimeout(this.nextReqTimeout); + this.nextReqTimeout = 0; + this.nextReq = 0; + } + + /// #if MTPROTO_HAS_HTTP + if(this.longPollInterval !== undefined) { + clearInterval(this.longPollInterval); + this.longPollInterval = undefined; + } + + if(this.checkConnectionTimeout !== undefined) { + clearTimeout(this.checkConnectionTimeout); + this.checkConnectionTimeout = undefined; + } + /// #endif + } + + this.transport = transport; + if(!transport) { + return; + } + + /// #if MTPROTO_HAS_HTTP + /// #if MTPROTO_HAS_WS + if(transport instanceof HTTP) { + /// #endif + this.longPollInterval = ctx.setInterval(this.checkLongPoll, 10000); + this.checkLongPoll(); + /// #if MTPROTO_HAS_WS + } + /// #endif + /// #endif + + transport.networker = this; + + if((transport as TcpObfuscated).connected) { + this.setConnectionStatus(ConnectionStatus.Connected); + } + } + public destroy() { - //assumeType(this.transport); - (this.transport as TcpObfuscated).destroy(); + this.changeTransport(); } public forceReconnectTimeout() { @@ -496,13 +535,14 @@ export default class MTPNetworker { // }); // }; - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD + /// #if MTPROTO_HAS_HTTP private checkLongPoll = () => { const isClean = this.cleanupSent(); //this.log.error('Check lp', this.longPollPending, this.dcId, isClean, this); if((this.longPollPending && Date.now() < this.longPollPending) || this.offline || - this.isStopped()) { + this.isStopped() || + this.isFileNetworker) { //this.log('No lp this time'); return false; } @@ -510,7 +550,6 @@ export default class MTPNetworker { sessionStorage.get('dc').then((baseDcId) => { if(isClean && ( baseDcId !== this.dcId || - this.isFileNetworker || (this.sleepAfter && Date.now() > this.sleepAfter) )) { //console.warn(dT(), 'Send long-poll for DC is delayed', this.dcId, this.sleepAfter); @@ -522,10 +561,12 @@ export default class MTPNetworker { }; private sendLongPoll() { + if(this.sendingLongPoll) return; + this.sendingLongPoll = true; const maxWait = 25000; this.longPollPending = Date.now() + maxWait; - //this.log('Set lp', this.longPollPending, tsNow()) + this.debug && this.log.debug('sendLongPoll', this.longPollPending); this.wrapMtpCall('http_wait', { max_delay: 500, @@ -535,19 +576,19 @@ export default class MTPNetworker { noResponse: true, longPoll: true }).then(() => { - this.longPollPending = 0; + this.longPollPending = undefined; setTimeout(this.checkLongPoll, 0); }, (error: ErrorEvent) => { this.log('Long-poll failed', error); + }).finally(() => { + this.sendingLongPoll = undefined; }); } private checkConnection = (event: Event | string) => { - /* rootScope.offlineConnecting = true */ - - this.log('Check connection', event); + this.debug && this.log('Check connection', event); clearTimeout(this.checkConnectionTimeout); - this.checkConnectionTimeout = 0; + this.checkConnectionTimeout = undefined; const serializer = new TLSerialization({mtproto: true}); const pingId = randomLong(); @@ -562,16 +603,12 @@ export default class MTPNetworker { body: serializer.getBytes(true) }; - this.sendEncryptedRequest(pingMessage).then((result) => { - /* delete rootScope.offlineConnecting */ + this.sendEncryptedRequest(pingMessage).then(() => { this.toggleOffline(false); }, () => { - this.log('Delay ', this.checkConnectionPeriod * 1000); - this.checkConnectionTimeout = setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0); + this.debug && this.log('Delay ', this.checkConnectionPeriod * 1000); + this.checkConnectionTimeout = ctx.setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0); this.checkConnectionPeriod = Math.min(60, this.checkConnectionPeriod * 1.5); - /* setTimeout(function() { - delete rootScope.offlineConnecting - }, 1000); */ }); }; @@ -582,8 +619,6 @@ export default class MTPNetworker { } this.offline = enabled; - /* rootScope.offline = enabled; - rootScope.offlineConnecting = false; */ if(this.offline) { clearTimeout(this.nextReqTimeout); @@ -594,7 +629,7 @@ export default class MTPNetworker { this.checkConnectionPeriod = 0; } - this.checkConnectionTimeout = setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0); + this.checkConnectionTimeout = ctx.setTimeout(this.checkConnection, this.checkConnectionPeriod * 1000 | 0); this.checkConnectionPeriod = Math.min(30, (1 + this.checkConnectionPeriod) * 1.5); /// #if !MTPROTO_WORKER @@ -612,16 +647,14 @@ export default class MTPNetworker { /// #endif clearTimeout(this.checkConnectionTimeout); - this.checkConnectionTimeout = 0; + this.checkConnectionTimeout = undefined; } - } private handleSentEncryptedRequestHTTP(promise: ReturnType, message: MTMessage, noResponseMsgs: string[]) { - promise - .then((result) => { + promise.then((result) => { this.toggleOffline(false); - // this.log('parse for', message) + // this.log('parse for', message); this.parseResponse(result).then((response) => { if(Modes.debug) { this.log.debug('Server response', response); @@ -645,7 +678,7 @@ export default class MTPNetworker { this.log.error('Encrypted request failed', error, message); if(message.container) { - message.inner.forEach((msgId: string) => { + message.inner.forEach((msgId) => { this.pendingMessages[msgId] = 0; }); @@ -661,7 +694,7 @@ export default class MTPNetworker { delete this.pendingMessages[msgId]; deferred.reject(); } - }) + }); this.toggleOffline(true); }); @@ -732,7 +765,7 @@ export default class MTPNetworker { public setDrainTimeout() { if(!this.activeRequests && this.onDrain && this.onDrainTimeout === undefined) { - this.onDrainTimeout = self.setTimeout(() => { + this.onDrainTimeout = ctx.setTimeout(() => { this.onDrainTimeout = undefined; this.log('drain'); this.onDrain(); @@ -890,7 +923,7 @@ export default class MTPNetworker { //const currentTime = Date.now(); let messagesByteLen = 0; - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD + /// #if MTPROTO_HAS_HTTP let hasApiCall = false; let hasHttpWait = false; /// #endif @@ -923,7 +956,7 @@ export default class MTPNetworker { messages.push(message); messagesByteLen += messageByteLength; - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD + /// #if MTPROTO_HAS_HTTP if(message.isAPI) { hasApiCall = true; } else if(message.longPoll) { @@ -940,10 +973,10 @@ export default class MTPNetworker { //} } - /// #if MTPROTO_HTTP_UPLOAD + /// #if MTPROTO_HAS_HTTP + /// #if MTPROTO_HAS_WS if(this.transport instanceof HTTP) /// #endif - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD if(hasApiCall && !hasHttpWait) { const serializer = new TLSerialization({mtproto: true}); serializer.storeMethod('http_wait', { @@ -965,7 +998,7 @@ export default class MTPNetworker { return; } - /// #if MTPROTO_HTTP_UPLOAD || MTPROTO_HTTP + /// #if MTPROTO_HAS_HTTP const noResponseMsgs: Array = messages.filter(message => message.noResponse).map(message => message.msg_id); /// #endif @@ -981,20 +1014,20 @@ export default class MTPNetworker { this.pendingAcks = []; const promise = this.sendEncryptedRequest(outMessage); - - /// #if MTPROTO_HTTP_UPLOAD - if(!(this.transport instanceof HTTP)) { - //if(noResponseMsgs.length) this.log.error('noResponseMsgs length!', noResponseMsgs); - this.cleanupSent(); // ! WARNING - } else { - this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs); - } - /// #elif !MTPROTO_HTTP - this.cleanupSent(); // ! WARNING - /// #else + + /// #if MTPROTO_HAS_HTTP + /// #if MTPROTO_HAS_WS + if(this.transport instanceof HTTP) + /// #endif this.handleSentEncryptedRequestHTTP(promise, outMessage, noResponseMsgs); - //} - /// #endif + /// #endif + + /// #if MTPROTO_HAS_WS + /// #if MTPROTO_HAS_HTTP + if(!(this.transport instanceof HTTP)) + /// #endif + this.cleanupSent(); // ! WARNING + /// #endif if(lengthOverflow) { this.scheduleRequest(); @@ -1157,10 +1190,14 @@ export default class MTPNetworker { this.debug && this.log.debug('sendEncryptedRequest: launching message into space:', message, [message.msg_id].concat(message.inner || [])); const promise: Promise = this.transport.send(requestData) as any; - /// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD + + /// #if !MTPROTO_HAS_HTTP return promise; /// #else + + /// #if MTPROTO_HAS_WS if(!(this.transport instanceof HTTP)) return promise; + /// #endif const baseError = { code: 406, @@ -1322,14 +1359,19 @@ export default class MTPNetworker { return; } */ - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD - if(!(this.transport instanceof HTTP)) { - this.performScheduledRequest(); - return; - } else if(this.offline) { - this.checkConnection('forced schedule'); + /// #if MTPROTO_HAS_HTTP + /// #if MTPROTO_HAS_WS + if(this.transport instanceof HTTP) { + /// #endif + if(this.offline) { + this.checkConnection('forced schedule'); + } + + delay ||= 0; // set zero timeout to pack other messages too + /// #if MTPROTO_HAS_WS } /// #endif + /// #endif const nextReq = Date.now() + (delay || 0); if(this.nextReq && (delay === undefined || this.nextReq <= nextReq)) { @@ -1354,23 +1396,22 @@ export default class MTPNetworker { this.nextReqTimeout = 0; this.nextReq = 0; - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD + /// #if MTPROTO_HAS_HTTP + /// #if MTPROTO_HAS_WS + if(this.transport instanceof HTTP) + /// #endif if(this.offline) { //this.log('Cancel scheduled'); return; } - /// #else - /* if(!this.isOnline) { - return; - } */ + /// #endif this.performScheduledRequest(); - /// #endif }; this.nextReq = nextReq; - if(delay) { + if(delay !== undefined) { this.nextReqTimeout = self.setTimeout(cb, delay); } else { cb(); @@ -1381,11 +1422,16 @@ export default class MTPNetworker { // this.log('ack message', msgID) this.pendingAcks.push(msgId); - /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD - this.scheduleRequest(30000); - /// #else - this.scheduleRequest(); + let delay: number; + + /// #if MTPROTO_HAS_HTTP + /// #if MTPROTO_HAS_WS + if(this.transport instanceof HTTP) /// #endif + delay = 30000; + /// #endif + + this.scheduleRequest(delay); } private reqResendMessage(msgId: MTLong) { @@ -1680,15 +1726,13 @@ export default class MTPNetworker { break; } - case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages doesn't require acknowledgments - if((this.transport as TcpObfuscated).networker) { - const sentMessageId = message.msg_id; - const sentMessage = this.sentMessages[sentMessageId]; + case 'pong': { // * https://core.telegram.org/mtproto/service_messages#ping-messages-pingpong - These messages don't require acknowledgments + const sentMessageId = message.msg_id; + const sentMessage = this.sentMessages[sentMessageId]; - if(sentMessage) { - sentMessage.deferred.resolve(message); - delete this.sentMessages[sentMessageId]; - } + if(sentMessage) { + sentMessage.deferred.resolve(message); + delete this.sentMessages[sentMessageId]; } break; diff --git a/src/lib/mtproto/transports/controller.ts b/src/lib/mtproto/transports/controller.ts new file mode 100644 index 00000000..1c4aef26 --- /dev/null +++ b/src/lib/mtproto/transports/controller.ts @@ -0,0 +1,25 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { TransportType } from "../dcConfigurator"; + +export class MTTransportController { + private opened: Map; + + constructor() { + this.opened = new Map(); + } + + public setTransportOpened(type: TransportType, value: boolean) { + let length = this.opened.get(type) || 0; + + length += value ? 1 : -1; + + this.opened.set(type, length); + } +} + +export default new MTTransportController(); \ No newline at end of file diff --git a/src/lib/mtproto/transports/http.ts b/src/lib/mtproto/transports/http.ts index 888319af..a692e85e 100644 --- a/src/lib/mtproto/transports/http.ts +++ b/src/lib/mtproto/transports/http.ts @@ -4,28 +4,84 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import { pause } from '../../../helpers/schedulers/pause'; +import { DcId } from '../../../types'; +import { logger } from '../../logger'; +import type MTPNetworker from '../networker'; import MTTransport from './transport'; export default class HTTP implements MTTransport { - constructor(protected dcId: number, protected url: string) { - } + public networker: MTPNetworker; + private log: ReturnType; - public send(data: Uint8Array) { - return fetch(this.url, {method: 'POST', body: data}).then(response => { - //console.log('http response', response/* , response.arrayBuffer() */); + private pending: Array<{ + resolve: (body: Uint8Array) => void, + reject: any, + body: Uint8Array + }> = []; + private releasing: boolean; + + constructor(protected dcId: DcId, protected url: string, logSuffix: string) { + this.log = logger(`HTTP-${dcId}` + logSuffix); + } + private _send(body: Uint8Array) { + return fetch(this.url, {method: 'POST', body}).then(response => { if(response.status !== 200) { response.arrayBuffer().then(buffer => { - console.log('not 200', - new TextDecoder("utf-8").decode(new Uint8Array(buffer))); - }) + this.log.error('not 200', + new TextDecoder("utf-8").decode(new Uint8Array(buffer))); + }); throw response; - } + } + + // * test resending by dropping random request + // if(Math.random() > .5) { + // throw 'asd'; + // } return response.arrayBuffer().then(buffer => { return new Uint8Array(buffer); }); }); } + + public send(body: Uint8Array) { + if(this.networker) { + return this._send(body); + } else { + const promise = new Promise((resolve, reject) => { + this.pending.push({resolve, reject, body}); + }); + + this.releasePending(); + + return promise; + } + } + + private async releasePending() { + if(this.releasing) return; + + this.releasing = true; + // this.log('-> messages to send:', this.pending.length); + for(let i = 0; i < this.pending.length; ++i) { + const pending = this.pending[i]; + const {body, resolve} = pending; + + try { + const result = await this._send(body); + resolve(result); + this.pending.splice(i, 1); + } catch(err) { + this.log.error('Send plain request error:', err); + await pause(5000); + } + + --i; + } + + this.releasing = false; + } } diff --git a/src/lib/mtproto/transports/tcpObfuscated.ts b/src/lib/mtproto/transports/tcpObfuscated.ts index 9354b323..793f9380 100644 --- a/src/lib/mtproto/transports/tcpObfuscated.ts +++ b/src/lib/mtproto/transports/tcpObfuscated.ts @@ -274,10 +274,6 @@ export default class TcpObfuscated implements MTTransport { let length = this.pending.length; //for(let i = length - 1; i >= 0; --i) { for(let i = 0; i < length; ++i) { - /* if(this.ws.bufferedAmount) { - break; - } */ - const pending = this.pending[i]; const {body, bodySent} = pending; let encoded = pending.encoded; @@ -286,25 +282,12 @@ export default class TcpObfuscated implements MTTransport { //this.debugPayloads.push({before: body.slice(), after: enc}); this.debug && this.log.debug('-> body length to send:', body.length); - /* if(this.ws.bufferedAmount) { - this.log.error('bufferedAmount:', this.ws.bufferedAmount); - } */ - - /* if(this.ws.readyState !== this.ws.OPEN) { - this.log.error('ws is closed?'); - this.connected = false; - break; - } */ if(!encoded) { encoded = pending.encoded = this.encodeBody(body); } - //this.lol.push(body); - //setTimeout(() => { - this.connection.send(encoded); - //}, 100); - //this.dd(); + this.connection.send(encoded); if(!pending.resolve) { // remove if no response needed this.pending.splice(i--, 1); diff --git a/src/lib/mtproto/transports/transport.ts b/src/lib/mtproto/transports/transport.ts index 16a18087..e22b5abe 100644 --- a/src/lib/mtproto/transports/transport.ts +++ b/src/lib/mtproto/transports/transport.ts @@ -5,8 +5,10 @@ */ import type EventListenerBase from "../../../helpers/eventListenerBase"; +import type MTPNetworker from "../networker"; export default interface MTTransport { + networker: MTPNetworker; send: (data: Uint8Array) => void; } diff --git a/src/pages/pageIm.ts b/src/pages/pageIm.ts index b5e59b7c..acc2f6c8 100644 --- a/src/pages/pageIm.ts +++ b/src/pages/pageIm.ts @@ -35,6 +35,7 @@ let onFirstMount = () => { // setTimeout(() => { const promise = import('../lib/appManagers/appDialogsManager'); promise.finally(async() => { + document.getElementById('auth-pages').remove(); //alert('pageIm!'); resolve(); diff --git a/src/pages/pagePassword.ts b/src/pages/pagePassword.ts index b2e34f14..705bbbd8 100644 --- a/src/pages/pagePassword.ts +++ b/src/pages/pagePassword.ts @@ -84,6 +84,9 @@ let onFirstMount = (): Promise => { btnNextI18n.update({key: 'PleaseWait'}); const preloader = putPreloader(btnNext); + passwordInputField.setValueSilently('' + Math.random()); // prevent saving suggestion + passwordInputField.setValueSilently(value); // prevent saving suggestion + passwordManager.check(value, state).then((response) => { //console.log('passwordManager response:', response); diff --git a/src/scss/partials/_input.scss b/src/scss/partials/_input.scss index 5d768de4..ddfab5ed 100644 --- a/src/scss/partials/_input.scss +++ b/src/scss/partials/_input.scss @@ -393,6 +393,9 @@ input:focus, button:focus { &[type="password"] { font-size: 2.25rem; padding-left: calc(.875rem - var(--border-width)); + line-height: 1; + padding-top: 0; + padding-bottom: 0; @media (-webkit-min-device-pixel-ratio: 2) { font-size: 1.75rem; diff --git a/src/scss/style.scss b/src/scss/style.scss index adb401ac..b5993bcb 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -555,6 +555,14 @@ input, textarea { -webkit-appearance: none; } +// Possible fix Safari's password autocomplete +input::-webkit-contacts-auto-fill-button, +input::-webkit-credentials-auto-fill-button { + visibility: hidden; + position: absolute; + right: 0; +} + /* input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus,