Browse Source

ESG GIFs try

Websocket fix instant connect retry
Possible fix to stream FILE_REFERENCE_EXPIRED
master
morethanwords 4 years ago
parent
commit
097a55850c
  1. 22
      src/components/emoticonsDropdown/index.ts
  2. 8
      src/components/emoticonsDropdown/tabs/gifs.ts
  3. 18
      src/components/emoticonsDropdown/tabs/stickers.ts
  4. 10
      src/components/gifsMasonry.ts
  5. 2
      src/components/sidebarRight/forward.ts
  6. 9
      src/components/sidebarRight/sharedMedia.ts
  7. 10
      src/components/wrappers.ts
  8. 21
      src/lib/appManagers/appDownloadManager.ts
  9. 2
      src/lib/appManagers/appStickersManager.ts
  10. 2
      src/lib/crypto/crypto_utils.ts
  11. 9
      src/lib/mtproto/mtproto.service.ts
  12. 6
      src/lib/mtproto/mtproto.worker.ts
  13. 30
      src/lib/mtproto/mtprotoworker.ts
  14. 29
      src/lib/mtproto/referenceDatabase.ts
  15. 40
      src/lib/mtproto/transports/websocket.ts
  16. 18
      src/scss/partials/_rightSidebar.scss
  17. 2
      src/scss/style.scss
  18. 5
      src/types.d.ts

22
src/components/emoticonsDropdown/index.ts

@ -1,4 +1,4 @@
import LazyLoadQueue from "../lazyLoadQueue"; import LazyLoadQueue, { LazyLoadQueueIntersector } from "../lazyLoadQueue";
import GifsTab from "./tabs/gifs"; import GifsTab from "./tabs/gifs";
import { findUpClassName, findUpTag, whichChild } from "../../lib/utils"; import { findUpClassName, findUpTag, whichChild } from "../../lib/utils";
import { horizontalMenu } from "../horizontalMenu"; import { horizontalMenu } from "../horizontalMenu";
@ -324,6 +324,26 @@ export class EmoticonsDropdown {
console.warn('got no doc by id:', fileID); console.warn('got no doc by id:', fileID);
} }
}; };
public addLazyLoadQueueRepeat(lazyLoadQueue: LazyLoadQueueIntersector, processInvisibleDiv: (div: HTMLElement) => void) {
this.events.onClose.push(() => {
lazyLoadQueue.lock();
});
this.events.onCloseAfter.push(() => {
const divs = lazyLoadQueue.intersector.getVisible();
for(const div of divs) {
processInvisibleDiv(div);
}
lazyLoadQueue.intersector.clearVisible();
});
this.events.onOpenAfter.push(() => {
lazyLoadQueue.unlockAndRefresh();
});
}
} }
const emoticonsDropdown = new EmoticonsDropdown(); const emoticonsDropdown = new EmoticonsDropdown();

8
src/components/emoticonsDropdown/tabs/gifs.ts

@ -1,4 +1,4 @@
import { EmoticonsDropdown, EmoticonsTab, EMOTICONSSTICKERGROUP } from ".."; import emoticonsDropdown, { EmoticonsDropdown, EmoticonsTab, EMOTICONSSTICKERGROUP } from "..";
import GifsMasonry from "../../gifsMasonry"; import GifsMasonry from "../../gifsMasonry";
import Scrollable from "../../scrollable_new"; import Scrollable from "../../scrollable_new";
import { putPreloader } from "../../misc"; import { putPreloader } from "../../misc";
@ -24,15 +24,15 @@ export default class GifsTab implements EmoticonsTab {
res.gifs.forEach((doc, idx) => { res.gifs.forEach((doc, idx) => {
res.gifs[idx] = doc = appDocsManager.saveDoc(doc); res.gifs[idx] = doc = appDocsManager.saveDoc(doc);
//if(doc._ == 'documentEmpty') return; //if(doc._ == 'documentEmpty') return;
//masonry.add(doc as MyDocument); masonry.add(doc as MyDocument);
}); });
} }
//let line: MTDocument[] = [];
preloader.remove(); preloader.remove();
}); });
emoticonsDropdown.addLazyLoadQueueRepeat(masonry.lazyLoadQueue, masonry.processInvisibleDiv);
this.init = null; this.init = null;
} }

18
src/components/emoticonsDropdown/tabs/stickers.ts

@ -321,23 +321,7 @@ export default class StickersTab implements EmoticonsTab {
} }
}); });
emoticonsDropdown.events.onClose.push(() => { emoticonsDropdown.addLazyLoadQueueRepeat(this.lazyLoadQueue, this.processInvisibleDiv);
this.lazyLoadQueue.lock();
});
emoticonsDropdown.events.onCloseAfter.push(() => {
const divs = this.lazyLoadQueue.intersector.getVisible();
for(const div of divs) {
this.processInvisibleDiv(div);
}
this.lazyLoadQueue.intersector.clearVisible();
});
emoticonsDropdown.events.onOpenAfter.push(() => {
this.lazyLoadQueue.unlockAndRefresh();
});
/* setInterval(() => { /* setInterval(() => {
// @ts-ignore // @ts-ignore

10
src/components/gifsMasonry.ts

@ -24,14 +24,14 @@ export default class GifsMasonry {
} }
}); });
setInterval(() => { /* setInterval(() => {
// @ts-ignore // @ts-ignore
const players = animationIntersector.byGroups[group]; const players = animationIntersector.byGroups[group];
if(players) { if(players) {
console.log(`GIFS RENDERED IN ${group}:`, players.length, players.filter(p => !p.animation.paused).length, this.lazyLoadQueue.intersector.getVisible().length); console.log(`GIFS RENDERED IN ${group}:`, players.length, players.filter(p => !p.animation.paused).length, this.lazyLoadQueue.intersector.getVisible().length);
} }
}, .25e3); }, .25e3); */
let timeout = 0; let timeout = 0;
// memory leak // memory leak
@ -51,12 +51,14 @@ export default class GifsMasonry {
}); });
} }
private processVisibleDiv = (div: HTMLElement) => { processVisibleDiv = (div: HTMLElement) => {
const video = div.querySelector('video'); const video = div.querySelector('video');
if(video) { if(video) {
return; return;
} }
//console.log('processVisibleDiv');
const load = () => { const load = () => {
const docID = div.dataset.docID; const docID = div.dataset.docID;
const doc = appDocsManager.getDoc(docID); const doc = appDocsManager.getDoc(docID);
@ -110,7 +112,7 @@ export default class GifsMasonry {
this.lazyLoadQueue.push({div, load}); this.lazyLoadQueue.push({div, load});
}; };
private processInvisibleDiv = async(div: HTMLElement) => { processInvisibleDiv = async(div: HTMLElement) => {
return this.scrollPromise.then(async() => { return this.scrollPromise.then(async() => {
//return; //return;

2
src/components/sidebarRight/forward.ts

@ -13,6 +13,7 @@ export default class AppForwardTab implements SliderTab {
private msgIDs: number[] = []; private msgIDs: number[] = [];
onCloseAfterTimeout() { onCloseAfterTimeout() {
document.body.classList.remove('is-forward-active');
this.cleanup(); this.cleanup();
} }
@ -79,6 +80,7 @@ export default class AppForwardTab implements SliderTab {
//console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount); //console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount);
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.forward); appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.forward);
appSidebarRight.toggleSidebar(true); appSidebarRight.toggleSidebar(true);
document.body.classList.add('is-forward-active');
}); });
} }
} }

9
src/components/sidebarRight/sharedMedia.ts

@ -263,17 +263,10 @@ export default class AppSharedMediaTab implements SliderTab {
case 'inputMessagesFilterDocument': { case 'inputMessagesFilterDocument': {
for(let message of messages) { for(let message of messages) {
if(!message.media.document || ['voice', 'audio', 'gif'].includes(message.media.document.type)) { if(!message.media.document || ['voice', 'audio', 'gif', 'sticker'].includes(message.media.document.type)) {
continue; continue;
} }
let doc = message.media.document;
if(doc.attributes) {
if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) {
continue;
}
}
filtered.push(message); filtered.push(message);
} }
break; break;

10
src/components/wrappers.ts

@ -234,7 +234,15 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
}, {once: true}); }, {once: true});
//} //}
video.addEventListener('error', deferred.reject); video.addEventListener('error', (e) => {
deferred.resolve();
/* console.error('video error', e, video.src);
if(video.src) { // if wasn't cleaned
deferred.reject(e);
} else {
deferred.resolve();
} */
});
//if(doc.type != 'round') { //if(doc.type != 'round') {
renderImageFromUrl(video, doc.url); renderImageFromUrl(video, doc.url);

21
src/lib/appManagers/appDownloadManager.ts

@ -5,7 +5,7 @@ import type { DownloadOptions } from "../mtproto/apiFileManager";
import { getFileNameByLocation } from "../bin_utils"; import { getFileNameByLocation } from "../bin_utils";
import { InputFile } from "../../layer"; import { InputFile } from "../../layer";
import referenceDatabase, {ReferenceBytes} from "../mtproto/referenceDatabase"; import referenceDatabase, {ReferenceBytes} from "../mtproto/referenceDatabase";
import appMessagesManager from "./appMessagesManager"; import type { ApiError } from "../mtproto/apiManager";
export type ResponseMethodBlob = 'blob'; export type ResponseMethodBlob = 'blob';
export type ResponseMethodJson = 'json'; export type ResponseMethodJson = 'json';
@ -77,30 +77,17 @@ export class AppDownloadManager {
const deferred = this.getNewDeferred(fileName); const deferred = this.getNewDeferred(fileName);
const onError = (err: any) => { const onError = (err: ApiError) => {
switch(err.type) { switch(err.type) {
case 'FILE_REFERENCE_EXPIRED': { case 'FILE_REFERENCE_EXPIRED': {
// @ts-ignore // @ts-ignore
const bytes: ReferenceBytes = options?.location?.file_reference; const bytes: ReferenceBytes = options?.location?.file_reference;
if(bytes) { if(bytes) {
const context = referenceDatabase.getContext(bytes); referenceDatabase.refreshReference(bytes).then(tryDownload);
switch(context?.type) { break;
case 'message': {
return appMessagesManager.wrapSingleMessage(context.messageID, true).then(() => {
//console.log('FILE_REFERENCE_EXPIRED: got message', context, options, appMessagesManager.getMessage(context.messageID).media);
return tryDownload();
});
}
default: {
console.warn('FILE_REFERENCE_EXPIRED: not implemented context', context);
}
}
} else { } else {
console.warn('FILE_REFERENCE_EXPIRED: no context for bytes:', bytes); console.warn('FILE_REFERENCE_EXPIRED: no context for bytes:', bytes);
} }
break;
} }
default: default:

2
src/lib/appManagers/appStickersManager.ts

@ -6,6 +6,8 @@ import { Modify } from '../../types';
import appStateManager from './appStateManager'; import appStateManager from './appStateManager';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
// TODO: если пак будет сохранён и потом обновлён, то недостающие стикеры не подгрузит
export class AppStickersManager { export class AppStickersManager {
private stickerSets: { private stickerSets: {
[stickerSetID: string]: MessagesStickerSet [stickerSetID: string]: MessagesStickerSet

2
src/lib/crypto/crypto_utils.ts

@ -29,7 +29,7 @@ export function longToBytes(sLong: string) {
} }
console.log('longToBytes LEEMON', sLong, performance.now() - perf); */ console.log('longToBytes LEEMON', sLong, performance.now() - perf); */
const bytes = bigInt2bytes(str2bigInt(sLong, 10), false); const bytes = addPadding(bigInt2bytes(str2bigInt(sLong, 10), false), 8, true, false, false);
//console.log('longToBytes', bytes, b); //console.log('longToBytes', bytes, b);
return bytes; return bytes;

9
src/lib/mtproto/mtproto.service.ts

@ -15,10 +15,10 @@ ctx.addEventListener('message', (e) => {
const task = e.data as ServiceWorkerTaskResponse; const task = e.data as ServiceWorkerTaskResponse;
const promise = deferredPromises[task.id]; const promise = deferredPromises[task.id];
if(task.payload) { if(task.error) {
promise.resolve(task.payload); promise.reject(task.error);
} else { } else {
promise.reject(); promise.resolve(task.payload);
} }
delete deferredPromises[task.id]; delete deferredPromises[task.id];
@ -33,7 +33,8 @@ export interface ServiceWorkerTask extends WorkerTaskTemplate {
export interface ServiceWorkerTaskResponse extends WorkerTaskTemplate { export interface ServiceWorkerTaskResponse extends WorkerTaskTemplate {
type: 'requestFilePart', type: 'requestFilePart',
payload: UploadFile.uploadFile payload?: UploadFile.uploadFile,
originalPayload?: ServiceWorkerTask['payload']
}; };
const onFetch = (event: FetchEvent): void => { const onFetch = (event: FetchEvent): void => {

6
src/lib/mtproto/mtproto.worker.ts

@ -82,15 +82,15 @@ ctx.addEventListener('message', async(e) => {
const task = e.data as ServiceWorkerTask; const task = e.data as ServiceWorkerTask;
const responseTask: ServiceWorkerTaskResponse = { const responseTask: ServiceWorkerTaskResponse = {
type: task.type, type: task.type,
id: task.id, id: task.id
payload: null
}; };
try { try {
const res = await apiFileManager.requestFilePart(...task.payload); const res = await apiFileManager.requestFilePart(...task.payload);
responseTask.payload = res; responseTask.payload = res;
} catch(err) { } catch(err) {
responseTask.originalPayload = task.payload;
responseTask.error = err;
} }
respond(responseTask); respond(responseTask);

30
src/lib/mtproto/mtprotoworker.ts

@ -9,6 +9,8 @@ import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.ser
import { MethodDeclMap } from '../../layer'; import { MethodDeclMap } from '../../layer';
import { MOUNT_CLASS_TO } from './mtproto_config'; import { MOUNT_CLASS_TO } from './mtproto_config';
import $rootScope from '../rootScope'; import $rootScope from '../rootScope';
import referenceDatabase from './referenceDatabase';
import { ApiError } from './apiManager';
type Task = { type Task = {
taskID: number, taskID: number,
@ -126,7 +128,31 @@ class ApiManagerProxy extends CryptoWorkerMethods {
} else if(task.type == 'convertWebp') { } else if(task.type == 'convertWebp') {
webpWorkerController.postMessage(task); webpWorkerController.postMessage(task);
} else if((task as ServiceWorkerTaskResponse).type == 'requestFilePart') { } else if((task as ServiceWorkerTaskResponse).type == 'requestFilePart') {
navigator.serviceWorker.controller.postMessage(task); const _task = task as ServiceWorkerTaskResponse;
if(_task.error) {
const onError = (error: ApiError) => {
if(error?.type == 'FILE_REFERENCE_EXPIRED') {
// @ts-ignore
const bytes = _task.originalPayload[1].file_reference;
referenceDatabase.refreshReference(bytes).then(() => {
const newTask: ServiceWorkerTask = {
type: _task.type,
id: _task.id,
payload: _task.originalPayload
};
this.postMessage(newTask);
}).catch(onError);
} else {
navigator.serviceWorker.controller.postMessage(task);
}
};
onError(_task.error);
} else {
navigator.serviceWorker.controller.postMessage(task);
}
} else { } else {
this.finalizeTask(task.taskID, task.result, task.error); this.finalizeTask(task.taskID, task.result, task.error);
} }
@ -137,7 +163,7 @@ class ApiManagerProxy extends CryptoWorkerMethods {
const deferred = this.awaiting[taskID]; const deferred = this.awaiting[taskID];
if(deferred !== undefined) { if(deferred !== undefined) {
this.log.debug('done', deferred.taskName, result, error); this.log.debug('done', deferred.taskName, result, error);
result === undefined ? deferred.reject(error) : deferred.resolve(result); error ? deferred.reject(error) : deferred.resolve(result);
delete this.awaiting[taskID]; delete this.awaiting[taskID];
} }
} }

29
src/lib/mtproto/referenceDatabase.ts

@ -1,3 +1,4 @@
import appMessagesManager from "../appManagers/appMessagesManager";
import { Photo } from "../../layer"; import { Photo } from "../../layer";
import { deepEqual } from "../utils"; import { deepEqual } from "../utils";
import { MOUNT_CLASS_TO } from "./mtproto_config"; import { MOUNT_CLASS_TO } from "./mtproto_config";
@ -57,6 +58,34 @@ class ReferenceDatabase {
return false; return false;
} }
public refreshReference(reference: ReferenceBytes): Promise<void> {
const context = this.getContext(reference);
switch(context?.type) {
case 'message': {
return appMessagesManager.wrapSingleMessage(context.messageID, true);
// .then(() => {
// console.log('FILE_REFERENCE_EXPIRED: got message', context, options, appMessagesManager.getMessage(context.messageID).media);
// });
}
default: {
console.warn('FILE_REFERENCE_EXPIRED: not implemented context', context);
return Promise.reject();
}
}
}
/* handleReferenceError = (reference: ReferenceBytes, error: ApiError) => {
switch(error.type) {
case 'FILE_REFERENCE_EXPIRED': {
return this.refreshReference(reference);
}
default:
return Promise.reject(error);
}
}; */
/* public replaceReference(oldReference: ReferenceBytes, newReference: ReferenceBytes) { /* public replaceReference(oldReference: ReferenceBytes, newReference: ReferenceBytes) {
const contexts = this.contexts.get(oldReference); const contexts = this.contexts.get(oldReference);
if(contexts) { if(contexts) {

40
src/lib/mtproto/transports/websocket.ts

@ -107,6 +107,8 @@ export class Obfuscation {
} }
} }
const CONNECTION_RETRY_TIMEOUT = 30000;
export default class Socket extends MTTransport { export default class Socket extends MTTransport {
ws: WebSocket; ws: WebSocket;
@ -129,6 +131,8 @@ export default class Socket extends MTTransport {
codec = intermediatePacketCodec; codec = intermediatePacketCodec;
lastCloseTime: number;
constructor(dcID: number, url: string) { constructor(dcID: number, url: string) {
super(dcID, url); super(dcID, url);
@ -169,27 +173,35 @@ export default class Socket extends MTTransport {
this.log('closed', event, this.pending); this.log('closed', event, this.pending);
this.connected = false; this.connected = false;
const time = Date.now();
const diff = time - this.lastCloseTime;
let needTimeout = !isNaN(diff) && diff < CONNECTION_RETRY_TIMEOUT ? CONNECTION_RETRY_TIMEOUT - diff : 0;
//this.pending.length = 0; //this.pending.length = 0;
/* if(this.networker) { /* if(this.networker) {
this.networker.resend(); this.networker.resend();
this.networker.cleanupSent(); this.networker.cleanupSent();
} */ } */
this.log('trying to reconnect...'); this.log('will try to reconnect after timeout:', needTimeout / 1000);
this.connect(); setTimeout(() => {
this.log('trying to reconnect...');
for(let pending of this.pending) { this.lastCloseTime = Date.now();
if(pending.bodySent) { this.connect();
pending.bodySent = false;
for(let pending of this.pending) {
if(pending.bodySent) {
pending.bodySent = false;
}
} }
}
if(this.networker) {
if(this.networker) { this.ws.addEventListener('open', () => {
this.ws.addEventListener('open', () => { this.networker.resend();
this.networker.resend(); this.networker.cleanupSent();
this.networker.cleanupSent(); }, {once: true});
}, {once: true}); }
} }, needTimeout);
}; };
handleMessage = (event: MessageEvent) => { handleMessage = (event: MessageEvent) => {

18
src/scss/partials/_rightSidebar.scss

@ -34,6 +34,10 @@
border-left: 1px solid #DADCE0; border-left: 1px solid #DADCE0;
} }
body.is-forward-active & {
z-index: 4;
}
.sidebar-header { .sidebar-header {
flex: 0 0 auto; flex: 0 0 auto;
@ -315,12 +319,16 @@
color: white; color: white;
} }
.grid-item-media { .grid-item {
opacity: 1; overflow: hidden;
transition: opacity .2s ease;
html:not(.is-mac) &.thumbnail { &-media {
filter: blur(7px); opacity: 1;
transition: opacity .2s ease;
html:not(.is-mac) &.thumbnail {
filter: blur(7px);
}
} }
} }

2
src/scss/style.scss

@ -1150,7 +1150,7 @@ img.emoji {
.grid-item { .grid-item {
height: 0; height: 0;
padding-bottom: 100%; padding-bottom: 100%;
overflow: hidden; //overflow: hidden;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;

5
src/types.d.ts vendored

@ -1,3 +1,5 @@
import type { ApiError } from "./lib/mtproto/apiManager";
export type InvokeApiOptions = Partial<{ export type InvokeApiOptions = Partial<{
dcID: number, dcID: number,
timeout: number, timeout: number,
@ -20,7 +22,8 @@ export type InvokeApiOptions = Partial<{
export type WorkerTaskTemplate = { export type WorkerTaskTemplate = {
type: string, type: string,
id: number, id: number,
payload: any payload?: any,
error?: ApiError
}; };
export type Modify<T, R> = Omit<T, keyof R> & R; export type Modify<T, R> = Omit<T, keyof R> & R;

Loading…
Cancel
Save