Browse Source

Service worker changes:

Moved whole apiFileManager
TO-DO:
Preloader progress
File downloads
Webp converter in SW
master
morethanwords 4 years ago
parent
commit
23c91473f6
  1. 7
      src/components/popupAvatar.ts
  2. 17
      src/components/wrappers.ts
  3. 235
      src/lib/appManagers/appDocsManager.ts
  4. 4
      src/lib/appManagers/appImManager.ts
  5. 2
      src/lib/appManagers/appMediaViewer.ts
  6. 22
      src/lib/appManagers/appMessagesManager.ts
  7. 36
      src/lib/appManagers/appPhotosManager.ts
  8. 17
      src/lib/appManagers/appStickersManager.ts
  9. 5
      src/lib/appManagers/appWebpManager.ts
  10. 28
      src/lib/bin_utils.ts
  11. 4
      src/lib/cacheStorage.ts
  12. 249
      src/lib/mtproto/apiFileManager.ts
  13. 126
      src/lib/mtproto/mtproto.service.ts
  14. 54
      src/lib/mtproto/mtprotoworker.ts
  15. 17
      src/lib/utils.ts
  16. 46
      src/types.d.ts

7
src/components/popupAvatar.ts

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
import resizeableImage from "../lib/cropper";
import apiFileManager from "../lib/mtproto/apiFileManager";
export class PopupAvatar {
private container = document.getElementById('popup-avatar');
@ -77,7 +76,11 @@ export class PopupAvatar { @@ -77,7 +76,11 @@ export class PopupAvatar {
private resolve() {
this.onCrop(() => {
return apiFileManager.uploadFile(this.blob);
//return apiFileManager.uploadFile(this.blob);
return fetch('/upload', {
method: 'POST',
body: this.blob
}).then(res => res.json());
});
}

17
src/components/wrappers.ts

@ -18,7 +18,6 @@ import { mediaSizes } from '../lib/config'; @@ -18,7 +18,6 @@ import { mediaSizes } from '../lib/config';
import { MTDocument, MTPhotoSize } from '../types';
import animationIntersector from './animationIntersector';
import AudioElement from './audio';
import MP4Source from '../lib/MP4Source';
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: {
doc: MTDocument,
@ -90,26 +89,18 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -90,26 +89,18 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
container.append(video);
}
let loadVideo = async() => {
let url: string;
const loadVideo = async() => {
if(message.media.preloader) { // means upload
(message.media.preloader as ProgressivePreloader).attach(container, undefined, undefined, false);
} else if(!doc.downloaded) {
const promise = appDocsManager.downloadVideo(doc.id);
const promise = appDocsManager.downloadDoc(doc.id);
//if(!doc.supportsStreaming) {
const preloader = new ProgressivePreloader(container, true);
preloader.attach(container, true, promise, false);
//}
const mp4Source: MP4Source = await (promise as Promise<MP4Source>);
if(mp4Source instanceof MP4Source) {
url = mp4Source.getURL();
}
}
if(!url) {
url = doc.url;
await promise;
}
if(middleware && !middleware()) {
@ -131,7 +122,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -131,7 +122,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
}, {once: true});
}
renderImageFromUrl(source, url);
renderImageFromUrl(source, doc.url);
source.type = doc.mime_type;
video.append(source);
video.setAttribute('playsinline', '');

235
src/lib/appManagers/appDocsManager.ts

@ -1,24 +1,15 @@ @@ -1,24 +1,15 @@
import apiFileManager from '../mtproto/apiFileManager';
import FileManager from '../filemanager';
import {RichTextProcessor} from '../richtextprocessor';
import { CancellablePromise, deferredPromise } from '../polyfill';
import { isObject } from '../utils';
import { isObject, getFileURL } from '../utils';
import opusDecodeController from '../opusDecodeController';
import { MTDocument } from '../../types';
import MP4Source from '../MP4Source';
import { bufferConcat } from '../bin_utils';
import { MTDocument, inputDocumentFileLocation } from '../../types';
class AppDocsManager {
private docs: {[docID: string]: MTDocument} = {};
private thumbs: {[docIDAndSize: string]: Promise<string>} = {};
private downloadPromises: {[docID: string]: CancellablePromise<Blob>} = {};
private videoChunks: {[docID: string]: CancellablePromise<ArrayBuffer>[]} = {};
private videoChunksQueue: {[docID: string]: {offset: number}[]} = {};
private loadedMP4Box: Promise<void>;
private mp4Source: MP4Source;
public saveDoc(apiDoc: MTDocument, context?: any) {
//console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
if(this.docs[apiDoc.id]) {
@ -41,16 +32,6 @@ class AppDocsManager { @@ -41,16 +32,6 @@ class AppDocsManager {
this.docs[apiDoc.id] = apiDoc;
if(apiDoc.thumb && apiDoc.thumb._ == 'photoCachedSize') {
console.warn('this will happen!!!');
apiFileManager.saveSmallFile(apiDoc.thumb.location, apiDoc.thumb.bytes);
// Memory
apiDoc.thumb.size = apiDoc.thumb.bytes.length;
delete apiDoc.thumb.bytes;
apiDoc.thumb._ = 'photoSize';
}
if(apiDoc.thumb && apiDoc.thumb._ == 'photoSizeEmpty') {
delete apiDoc.thumb;
}
@ -156,8 +137,8 @@ class AppDocsManager { @@ -156,8 +137,8 @@ class AppDocsManager {
return apiDoc;
}
public getDoc(docID: any): MTDocument {
return isObject(docID) ? docID : this.docs[docID];
public getDoc(docID: string | MTDocument): MTDocument {
return isObject(docID) && typeof(docID) !== 'string' ? docID : this.docs[docID as string];
}
public getMediaInputByID(docID: any) {
@ -175,7 +156,7 @@ class AppDocsManager { @@ -175,7 +156,7 @@ class AppDocsManager {
};
}
public getInputByID(docID: any, thumbSize?: string) {
public getInputByID(docID: any, thumbSize?: string): inputDocumentFileLocation {
let doc = this.getDoc(docID);
return {
@ -200,189 +181,40 @@ class AppDocsManager { @@ -200,189 +181,40 @@ class AppDocsManager {
return 't_' + (doc.type || 'file') + doc.id + fileExt;
}
private loadMP4Box() {
if(this.loadedMP4Box) return this.loadedMP4Box;
return this.loadedMP4Box = new Promise((resolve, reject) => {
(window as any).mp4BoxLoaded = () => {
//console.log('webpHero loaded');
this.mp4Source = (window as any).MP4Source;
resolve();
};
let sc = document.createElement('script');
sc.src = 'mp4box.all.min.js';
sc.async = true;
sc.onload = (window as any).mp4BoxLoaded;
document.body.appendChild(sc);
});
}
private createMP4Stream(doc: MTDocument) {
const limitPart = 524288;
const chunks = this.videoChunks[doc.id];
const queue = this.videoChunksQueue[doc.id];
//let mp4Source = new MP4Source({duration: doc.duration, video: {expected_size: doc.size}}, (offset: number, end: number) => {
let mp4Source = new (this.mp4Source as any)({duration: doc.duration, video: {expected_size: doc.size}}, (offset: number, end: number) => {
const chunkStart = offset - (offset % limitPart);
const sorted: typeof queue = [];
const lower: typeof queue = [];
for(let i = 0; i < queue.length; ++i) {
if(queue[i].offset >= chunkStart) {
sorted.push(queue[i]);
} else {
lower.push(queue[i]);
}
}
sorted.sort((a, b) => a.offset - b.offset).concat(lower).forEach((q, i) => {
queue[i] = q;
});
const index1 = offset / limitPart | 0;
const index2 = end / limitPart | 0;
const p = chunks.slice(index1, index2 + 1);
//console.log('MP4Source getBuffer:', offset, end, index1, index2, doc.size, JSON.stringify(queue));
if(offset % limitPart == 0) {
return p[0];
} else {
return Promise.all(p).then(buffers => {
const buffer = buffers.length > 1 ? bufferConcat(buffers[0], buffers[1]) : buffers[0];
const start = (offset % limitPart);
const _end = start + (end - offset);
const sliced = buffer.slice(start, _end);
//console.log('slice buffer:', sliced);
return sliced;
});
}
});
return mp4Source;
}
private mp4Stream(doc: MTDocument, deferred: CancellablePromise<Blob>) {
const limitPart = 524288;
const promises = this.videoChunks[doc.id] ?? (this.videoChunks[doc.id] = []);
if(!promises.length) {
for(let offset = 0; offset < doc.size; offset += limitPart) {
const deferred = deferredPromise<ArrayBuffer>();
promises.push(deferred);
}
}
let good = false;
return async(bytes: Uint8Array, offset: number, queue: {offset: number}[]) => {
if(!deferred.isFulfilled && !deferred.isRejected/* && offset == 0 */) {
this.videoChunksQueue[doc.id] = queue;
console.log('stream:', doc, doc.url, deferred);
//doc.url = mp4Source.getURL();
//deferred.resolve(mp4Source);
deferred.resolve();
good = true;
} else if(!good) {
//mp4Source.stop();
//mp4Source = null;
promises.length = 0;
return;
}
const index = offset % limitPart == 0 ? offset / limitPart : promises.length - 1;
promises[index].resolve(bytes.slice().buffer);
//console.log('i wont believe in you', doc, bytes, offset, promises, bytes.length, bytes.buffer.byteLength, bytes.slice().buffer);
//console.log('i wont believe in you', bytes, doc, bytes.length, offset);
};
}
public downloadVideo(docID: string): CancellablePromise<MP4Source | Blob> {
const doc = this.getDoc(docID);
if(!doc.supportsStreaming || doc.url) {
return this.downloadDoc(docID);
}
const deferred = deferredPromise<Blob>();
let canceled = false;
deferred.cancel = () => {
canceled = true;
};
this.loadMP4Box().then(() => {
if(canceled) {
throw 'canceled';
}
const promise = this.downloadDoc(docID);
deferred.cancel = () => {
promise.cancel();
};
promise.notify = (...args) => {
deferred.notify && deferred.notify(...args);
};
promise.then(() => {
if(doc.url) { // может быть уже загружен из кэша
deferred.resolve();
} else {
deferred.resolve(this.createMP4Stream(doc));
}
});
}, deferred.reject);
return deferred;
public getFileURLByDoc(doc: MTDocument) {
const inputFileLocation = this.getInputByID(doc);
return getFileURL('document', {dcID: doc.dc_id, location: inputFileLocation, size: doc.size, mimeType: doc.mime_type || 'application/octet-stream'});
}
public downloadDoc(docID: any, toFileEntry?: any): CancellablePromise<Blob> {
public downloadDoc(docID: string | MTDocument, toFileEntry?: any): CancellablePromise<Blob> {
const doc = this.getDoc(docID);
if(doc._ == 'documentEmpty') {
return Promise.reject();
}
const inputFileLocation = this.getInputByID(doc);
if(doc.downloaded && !toFileEntry) {
if(doc.url) return Promise.resolve(null);
const cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
/* const cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
if(cachedBlob) {
return Promise.resolve(cachedBlob);
}
} */
}
if(this.downloadPromises[doc.id]) {
return this.downloadPromises[doc.id];
}
//historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size};
const deferred = deferredPromise<Blob>();
deferred.cancel = () => {
downloadPromise.cancel();
};
const processPart = doc.supportsStreaming ? this.mp4Stream(doc, deferred) : undefined;
// нет смысла делать объект с выполняющимися промисами, нижняя строка и так вернёт загружающийся
const downloadPromise = apiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
mimeType: doc.mime_type || 'application/octet-stream',
toFileEntry: toFileEntry,
stickerType: doc.sticker,
processPart
});
downloadPromise.notify = (...args) => {
deferred.notify && deferred.notify(...args);
};
//deferred.notify = downloadPromise.notify;
/* if(doc.supportsStreaming) {
doc.url = '/stream/' + '';
} */
downloadPromise.then((blob) => {
const url = this.getFileURLByDoc(doc);
fetch(url).then(res => res.blob())
/* downloadPromise */.then((blob) => {
if(blob) {
doc.downloaded = true;
@ -405,11 +237,7 @@ class AppDocsManager { @@ -405,11 +237,7 @@ class AppDocsManager {
return;
} else if(doc.type && doc.sticker != 2) {
/* if(processPart) {
console.log('stream after:', doc, doc.url, deferred);
} */
doc.url = URL.createObjectURL(blob);
doc.url = url;
}
}
@ -417,23 +245,10 @@ class AppDocsManager { @@ -417,23 +245,10 @@ class AppDocsManager {
}, (e) => {
console.log('document download failed', e);
deferred.reject(e);
//historyDoc.progress.enabled = false;
}).finally(() => {
deferred.notify = downloadPromise.notify = deferred.cancel = downloadPromise.cancel = null;
//deferred.notify = downloadPromise.notify = deferred.cancel = downloadPromise.cancel = null;
});
/* downloadPromise.notify = (progress) => {
console.log('dl progress', progress);
historyDoc.progress.enabled = true;
historyDoc.progress.done = progress.done;
historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
$rootScope.$broadcast('history_update');
}; */
//historyDoc.progress.cancel = downloadPromise.cancel;
//console.log('return downloadPromise:', downloadPromise);
return this.downloadPromises[doc.id] = deferred;
}
@ -451,16 +266,8 @@ class AppDocsManager { @@ -451,16 +266,8 @@ class AppDocsManager {
return Promise.reject();
}
let mimeType = doc.sticker ? 'image/webp' : doc.mime_type;
let promise = apiFileManager.downloadSmallFile(input, {
dcID: doc.dc_id,
stickerType: doc.sticker ? 1 : undefined,
mimeType: mimeType
});
return this.thumbs[key] = promise.then((blob) => {
return URL.createObjectURL(blob);
});
const url = getFileURL('thumb', {dcID: doc.dc_id, location: input, mimeType: doc.sticker ? 'image/webp' : doc.mime_type});
return this.thumbs[key] = Promise.resolve(url);
}
public hasDownloadedThumb(docID: string, thumbSize: string) {

4
src/lib/appManagers/appImManager.ts

@ -549,7 +549,7 @@ export class AppImManager { @@ -549,7 +549,7 @@ export class AppImManager {
private closeBtn = this.topbar.querySelector('.sidebar-close-button') as HTMLButtonElement;
constructor() {
this.log = logger('IM', LogLevels.log | LogLevels.error | LogLevels.warn | LogLevels.debug);
this.log = logger('IM', /* LogLevels.log | LogLevels.warn | LogLevels.debug | */ LogLevels.error);
this.chatInputC = new ChatInput();
this.preloader = new ProgressivePreloader(null, false);
this.selectTab(0);
@ -2641,7 +2641,7 @@ export class AppImManager { @@ -2641,7 +2641,7 @@ export class AppImManager {
avatarElem.setAttribute('peer', '' + ((message.fwd_from && this.peerID == this.myID ? message.fwdFromID : message.fromID) || 0));
this.log('exec loadDialogPhoto', message);
//this.log('exec loadDialogPhoto', message);
bubbleContainer.append(avatarElem);
}

2
src/lib/appManagers/appMediaViewer.ts

@ -826,7 +826,7 @@ export class AppMediaViewer { @@ -826,7 +826,7 @@ export class AppMediaViewer {
if(!source.src || (media.url && media.url != source.src)) {
const load = () => {
const promise = appDocsManager.downloadVideo(media.id);
const promise = appDocsManager.downloadDoc(media.id);
const streamable = media.supportsStreaming && !media.url;
//if(!streamable) {

22
src/lib/appManagers/appMessagesManager.ts

@ -11,7 +11,6 @@ import appPhotosManager from "./appPhotosManager"; @@ -11,7 +11,6 @@ import appPhotosManager from "./appPhotosManager";
import AppStorage from '../storage';
import appPeersManager from "./appPeersManager";
import ServerTimeManager from "../mtproto/serverTimeManager";
import apiFileManager from "../mtproto/apiFileManager";
import appDocsManager from "./appDocsManager";
import ProgressivePreloader from "../../components/preloader";
import serverTimeManager from "../mtproto/serverTimeManager";
@ -22,7 +21,8 @@ import { CancellablePromise, deferredPromise } from "../polyfill"; @@ -22,7 +21,8 @@ import { CancellablePromise, deferredPromise } from "../polyfill";
import appPollsManager from "./appPollsManager";
import searchIndexManager from '../searchIndexManager';
import { MTDocument, MTPhotoSize } from "../../types";
import { logger } from "../logger";
import { logger, LogLevels } from "../logger";
import type {ApiFileManager} from '../mtproto/apiFileManager';
//console.trace('include');
@ -595,7 +595,7 @@ export class AppMessagesManager { @@ -595,7 +595,7 @@ export class AppMessagesManager {
dialogs: []
};
private log = logger('MESSAGES'/* , LogLevels.error */);
private log = logger('MESSAGES', LogLevels.error);
public dialogsStorage = new DialogsStorage();
public filtersStorage = new FiltersStorage();
@ -1179,7 +1179,7 @@ export class AppMessagesManager { @@ -1179,7 +1179,7 @@ export class AppMessagesManager {
};
var uploaded = false,
uploadPromise: ReturnType<typeof apiFileManager.uploadFile> = null;
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
let invoke = (flags: number, inputMedia: any) => {
this.setTyping('sendMessageCancelAction');
@ -1241,7 +1241,11 @@ export class AppMessagesManager { @@ -1241,7 +1241,11 @@ export class AppMessagesManager {
this.sendFilePromise.then(() => {
if(!uploaded || message.error) {
uploaded = false;
uploadPromise = apiFileManager.uploadFile(file);
//uploadPromise = apiFileManager.uploadFile(file);
uploadPromise = fetch('/upload', {
method: 'POST',
body: file
}).then(res => res.json());
}
uploadPromise && uploadPromise.then((inputFile) => {
@ -1485,7 +1489,7 @@ export class AppMessagesManager { @@ -1485,7 +1489,7 @@ export class AppMessagesManager {
};
let uploaded = false,
uploadPromise: ReturnType<typeof apiFileManager.uploadFile> = null;
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
let inputPeer = appPeersManager.getInputPeerByID(peerID);
let invoke = (multiMedia: any[]) => {
@ -1517,7 +1521,11 @@ export class AppMessagesManager { @@ -1517,7 +1521,11 @@ export class AppMessagesManager {
if(!uploaded || message.error) {
uploaded = false;
uploadPromise = apiFileManager.uploadFile(file);
//uploadPromise = apiFileManager.uploadFile(file);
uploadPromise = fetch('/upload', {
method: 'POST',
body: file
}).then(res => res.json());
}
uploadPromise.notify = (progress: {done: number, total: number}) => {

36
src/lib/appManagers/appPhotosManager.ts

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
import appUsersManager from "./appUsersManager";
import { calcImageInBox, isObject } from "../utils";
import { calcImageInBox, isObject, getFileURL } from "../utils";
import fileManager from '../filemanager';
import { bytesFromHex } from "../bin_utils";
import apiFileManager from "../mtproto/apiFileManager";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import { MTPhotoSize } from "../../types";
import { MTPhotoSize, inputPhotoFileLocation, inputDocumentFileLocation, InputFileLocation, FileLocation } from "../../types";
export type MTPhoto = {
_: 'photo' | 'photoEmpty' | string,
@ -250,17 +249,17 @@ export class AppPhotosManager { @@ -250,17 +249,17 @@ export class AppPhotosManager {
}
public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): Promise<Blob | void> {
let photo = this.getPhoto(photoID);
const photo = this.getPhoto(photoID);
if(!photoSize) {
let fullWidth = this.windowW;
let fullHeight = this.windowH;
const fullWidth = this.windowW;
const fullHeight = this.windowH;
photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
}
let isDocument = photo._ == 'document';
let cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo;
const isDocument = photo._ == 'document';
const cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo;
if(cacheContext.downloaded >= photoSize.size && cacheContext.url) {
return Promise.resolve();
@ -273,7 +272,7 @@ export class AppPhotosManager { @@ -273,7 +272,7 @@ export class AppPhotosManager {
// maybe it's a thumb
let isPhoto = photoSize.size && photo.access_hash && photo.file_reference;
let location = isPhoto ? {
let location: inputPhotoFileLocation | inputDocumentFileLocation | FileLocation = isPhoto ? {
_: isDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
id: photo.id,
access_hash: photo.access_hash,
@ -281,19 +280,20 @@ export class AppPhotosManager { @@ -281,19 +280,20 @@ export class AppPhotosManager {
thumb_size: photoSize.type
} : photoSize.location;
const url = getFileURL('photo', {dcID: photo.dc_id, location, size: isPhoto ? photoSize.size : undefined});
let promise: Promise<Blob>;
if(isPhoto/* && photoSize.size >= 1e6 */) {
//console.log('Photos downloadFile exec', photo);
promise = apiFileManager.downloadFile(photo.dc_id, location, photoSize.size);
promise = fetch(url).then(res => res.blob());
} else {
//console.log('Photos downloadSmallFile exec', photo, location);
promise = apiFileManager.downloadSmallFile(location);
promise = fetch(url).then(res => res.blob());
}
promise.then(blob => {
if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) {
cacheContext.downloaded = blob.size;
cacheContext.url = URL.createObjectURL(blob);
//cacheContext.url = URL.createObjectURL(blob);
cacheContext.url = url;
//console.log('wrote photo:', photo, photoSize, cacheContext, blob);
}
@ -333,7 +333,7 @@ export class AppPhotosManager { @@ -333,7 +333,7 @@ export class AppPhotosManager {
var fullWidth = this.windowW;
var fullHeight = this.windowH;
var fullPhotoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
var inputFileLocation = {
var inputFileLocation: inputDocumentFileLocation | inputPhotoFileLocation = {
// @ts-ignore
_: photo._ == 'document' ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
id: photo.id,
@ -346,7 +346,7 @@ export class AppPhotosManager { @@ -346,7 +346,7 @@ export class AppPhotosManager {
let writer = fileManager.chooseSaveFile(fileName, ext, mimeType, fullPhotoSize.size);
writer.ready.then(() => {
console.log('ready');
apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {
apiManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {
mimeType: mimeType,
toFileEntry: writer
}).then(() => {
@ -360,12 +360,12 @@ export class AppPhotosManager { @@ -360,12 +360,12 @@ export class AppPhotosManager {
} catch(err) {
console.error('err', err);
var cachedBlob = apiFileManager.getCachedFile(inputFileLocation)
/* var cachedBlob = apiFileManager.getCachedFile(inputFileLocation)
if (cachedBlob) {
return fileManager.download(cachedBlob, mimeType, fileName);
}
} */
apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {mimeType: mimeType})
apiManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {mimeType: mimeType})
.then((blob: Blob) => {
fileManager.download(blob, mimeType, fileName);
}, (e: any) => {

17
src/lib/appManagers/appStickersManager.ts

@ -1,10 +1,9 @@ @@ -1,10 +1,9 @@
import AppStorage from '../storage';
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import apiFileManager from '../mtproto/apiFileManager';
import appDocsManager from './appDocsManager';
import { MTDocument } from '../../types';
import { $rootScope } from '../utils';
import { MTDocument, inputStickerSetThumb } from '../../types';
import { $rootScope, getFileURL } from '../utils';
export type MTStickerSet = {
_: 'stickerSet',
@ -224,17 +223,17 @@ class AppStickersManager { @@ -224,17 +223,17 @@ class AppStickersManager {
const isAnimated = stickerSet.pFlags?.animated;
const promise = apiFileManager.downloadFile(dcID, {
const input: inputStickerSetThumb = {
_: 'inputStickerSetThumb',
stickerset: this.getStickerSetInput(stickerSet),
volume_id: thumb.location.volume_id,
local_id: thumb.location.local_id
}, thumb.size, {
stickerType: isAnimated ? 2 : 1,
mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'
});
};
const url = getFileURL('document', {dcID, location: input, size: thumb.size, mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'});
return fetch(url).then(res => res.blob());
return promise;
//return promise;
}
public getStickerSetInput(set: {id: string, access_hash: string}) {

5
src/lib/appManagers/appWebpManager.ts

@ -71,6 +71,7 @@ class AppWebpManager { @@ -71,6 +71,7 @@ class AppWebpManager {
if(this.testPromise) return this.testPromise;
return this.testPromise = new Promise((resolve, reject) => {
return resolve(this.webpSupport = true);
let webP = new Image();
webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' +
'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';
@ -95,8 +96,8 @@ class AppWebpManager { @@ -95,8 +96,8 @@ class AppWebpManager {
}
const appWebpManager = new AppWebpManager();
// @ts-ignore
/* // @ts-ignore
if(process.env.NODE_ENV != 'production') {
(window as any).appWebpManager = appWebpManager;
}
} */
export default appWebpManager;

28
src/lib/bin_utils.ts

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
// @ts-ignore
import {BigInteger, SecureRandom} from 'jsbn';
import { InputFileLocation, FileLocation } from '../types';
/// #if !MTPROTO_WORKER
// @ts-ignore
@ -380,3 +381,30 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean, @@ -380,3 +381,30 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
export function nextRandomInt(maxValue: number) {
return Math.floor(Math.random() * maxValue);
}
export function getFileNameByLocation(location: InputFileLocation | FileLocation, options?: Partial<{
fileName: string
}>) {
const fileName = (options?.fileName || '').split('.');
const ext = fileName[fileName.length - 1] || '';
switch(location._) {
case 'inputPhotoFileLocation':
case 'inputDocumentFileLocation': {
const thumbPart = location.thumb_size ? '_' + location.thumb_size : '';
return (fileName[0] ? fileName[0] + '_' : '') + location.id + thumbPart + (ext ? '.' + ext : ext);
}
case 'fileLocationToBeDeprecated':
case 'inputPeerPhotoFileLocation':
case 'inputStickerSetThumb':
case 'inputFileLocation': {
return location.volume_id + '_' + location.local_id + (ext ? '.' + ext : ext);
}
default: {
console.error('Unrecognized location:', location);
return '';
}
}
}

4
src/lib/cacheStorage.ts

@ -95,8 +95,8 @@ class CacheStorageController { @@ -95,8 +95,8 @@ class CacheStorageController {
}
const cacheStorage = new CacheStorageController();
// @ts-ignore
/* // @ts-ignore
if(process.env.NODE_ENV != 'production') {
(window as any).cacheStorage = cacheStorage;
}
} */
export default cacheStorage;

249
src/lib/mtproto/apiFileManager.ts

@ -1,13 +1,12 @@ @@ -1,13 +1,12 @@
import { nextRandomInt } from "../bin_utils";
import { nextRandomInt, getFileNameByLocation } from "../bin_utils";
//import IdbFileStorage from "../idb";
import cacheStorage from "../cacheStorage";
import FileManager from "../filemanager";
//import apiManager from "./apiManager";
import apiManager from "./mtprotoworker";
import apiManager from "./apiManager";
import { deferredPromise, CancellablePromise } from "../polyfill";
import appWebpManager from "../appManagers/appWebpManager";
import { logger } from "../logger";
import { InputFileLocation, FileLocation } from "../../types";
type Delayed = {
offset: number,
@ -15,20 +14,15 @@ type Delayed = { @@ -15,20 +14,15 @@ type Delayed = {
writeFileDeferred: CancellablePromise<unknown>
};
type DownloadOptions = Partial<{
}>;
export class ApiFileManager {
public cachedSavePromises: {
[fileName: string]: Promise<Blob>
} = {};
public cachedDownloadPromises: {
[fileName: string]: any
} = {};
public cachedDownloads: {
[fileName: string]: Blob
[fileName: string]: Promise<Blob>
} = {};
/* public indexedKeys: Set<string> = new Set();
private keysLoaded = false; */
public downloadPulls: {
[x: string]: Array<{
cb: () => Promise<unknown>,
@ -95,194 +89,26 @@ export class ApiFileManager { @@ -95,194 +89,26 @@ export class ApiFileManager {
});
}
public getFileName(location: any, options?: Partial<{
stickerType: number
}>) {
switch(location._) {
case 'inputDocumentFileLocation': {
let fileName = (location.file_name as string || '').split('.');
let ext = fileName[fileName.length - 1] || '';
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
ext += '.png'
}
let thumbPart = location.thumb_size ? '_' + location.thumb_size : '';
return (fileName[0] ? fileName[0] + '_' : '') + location.id + thumbPart + (ext ? '.' + ext : ext);
}
default: {
if(!location.volume_id && !location.file_reference) {
this.log.trace('Empty location', location);
}
let ext = 'jpg';
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
ext += '.png'
}
if(location.volume_id) {
return location.volume_id + '_' + location.local_id + '.' + ext;
} else {
return location.id + '_' + location.access_hash + '.' + ext;
}
}
}
}
public getTempFileName(file: any) {
const size = file.size || -1;
const random = nextRandomInt(0xFFFFFFFF);
return '_temp' + random + '_' + size;
}
public getCachedFile(location: any) {
if(!location) {
return false;
}
const fileName = this.getFileName(location);
return this.cachedDownloads[fileName] || false;
}
public getFileStorage() {
return cacheStorage;
}
/* public isFileExists(location: any) {
var fileName = this.getFileName(location);
return this.cachedDownloads[fileName] || this.indexedKeys.has(fileName);
//return this.cachedDownloads[fileName] || this.indexedKeys.has(fileName) ? Promise.resolve(true) : this.getFileStorage().isFileExists(fileName);
} */
public saveSmallFile(location: any, bytes: Uint8Array) {
var fileName = this.getFileName(location);
if(!this.cachedSavePromises[fileName]) {
this.cachedSavePromises[fileName] = this.getFileStorage().saveFile(fileName, bytes).then((blob: any) => {
return this.cachedDownloads[fileName] = blob;
}, (error: any) => {
delete this.cachedSavePromises[fileName];
});
}
return this.cachedSavePromises[fileName];
}
public downloadSmallFile(location: any, options: Partial<{
mimeType: string,
dcID: number,
stickerType: number
}> = {}): Promise<Blob> {
public downloadFile(options: {
dcID: number,
location: InputFileLocation | FileLocation,
size: number,
mimeType?: string,
toFileEntry?: any,
limitPart?: number,
stickerType?: number,
processPart?: (bytes: Uint8Array, offset: number, queue: Delayed[]) => Promise<any>
}): CancellablePromise<Blob> {
if(!FileManager.isAvailable()) {
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
}
/* if(!this.keysLoaded) {
this.getIndexedKeys();
} */
//this.log('downloadSmallFile', location, options);
let processSticker = false;
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
processSticker = true;
options.mimeType = 'image/png';
}
let dcID = options.dcID || location.dc_id;
let mimeType = options.mimeType || 'image/jpeg';
let fileName = this.getFileName(location, options);
let cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
//this.log('downloadSmallFile!', location, options, fileName, cachedPromise);
if(cachedPromise) {
return cachedPromise;
}
let fileStorage = this.getFileStorage();
return this.cachedDownloadPromises[fileName] = fileStorage.getFile(fileName).then((blob) => {
//throw '';
//this.log('downloadSmallFile found photo by fileName:', fileName);
return this.cachedDownloads[fileName] = blob;
}).catch(() => {
//this.log.warn('downloadSmallFile found no photo by fileName:', fileName);
let downloadPromise = this.downloadRequest(dcID, () => {
let inputLocation = location;
if(!inputLocation._ || inputLocation._ == 'fileLocation') {
inputLocation = Object.assign({}, location, {_: 'inputFileLocation'});
}
let params = {
flags: 0,
location: inputLocation,
offset: 0,
limit: 1024 * 1024
};
//this.log('next small promise', params);
return apiManager.invokeApi('upload.getFile', params, {
dcID: dcID,
fileDownload: true,
noErrorBox: true
});
}, dcID);
let processDownloaded = (bytes: Uint8Array) => {
//this.log('processDownloaded', location, bytes);
if(processSticker) {
return appWebpManager.convertToPng(bytes);
}
return Promise.resolve(bytes);
};
return fileStorage.getFileWriter(fileName, mimeType).then(fileWriter => {
return downloadPromise.then((result: any) => {
return processDownloaded(result.bytes).then((proccessedResult) => {
return FileManager.write(fileWriter, proccessedResult).then(() => {
return this.cachedDownloads[fileName] = fileWriter.finalize();
});
});
});
});
});
}
public getDownloadedFile(location: any) {
var fileStorage = this.getFileStorage();
var fileName = typeof(location) !== 'string' ? this.getFileName(location) : location;
//console.log('getDownloadedFile', location, fileName);
return fileStorage.getFile(fileName);
}
/* public getIndexedKeys() {
this.keysLoaded = true;
this.getFileStorage().getAllKeys().then(keys => {
this.indexedKeys.clear();
this.indexedKeys = new Set(keys);
});
} */
public downloadFile(dcID: number, location: any, size: number, options: Partial<{
mimeType: string,
toFileEntry: any,
limitPart: number,
stickerType: number,
processPart: (bytes: Uint8Array, offset: number, queue: Delayed[]) => Promise<any>
}> = {}): CancellablePromise<Blob> {
if(!FileManager.isAvailable()) {
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
}
/* if(!this.keysLoaded) {
this.getIndexedKeys();
} */
let size = options.size ?? 0;
let {dcID, location} = options;
let processSticker = false;
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
@ -295,12 +121,12 @@ export class ApiFileManager { @@ -295,12 +121,12 @@ export class ApiFileManager {
}
// this.log('Dload file', dcID, location, size)
const fileName = this.getFileName(location, options);
const fileName = getFileNameByLocation(location);
const toFileEntry = options.toFileEntry || null;
const cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
const cachedPromise = this.cachedDownloadPromises[fileName];
const fileStorage = this.getFileStorage();
//this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments);
//this.log('downloadFile', fileName, fileName.length, location, arguments);
if(cachedPromise) {
if(toFileEntry) {
@ -317,9 +143,9 @@ export class ApiFileManager { @@ -317,9 +143,9 @@ export class ApiFileManager {
this.log('downloadFile need to deleteFile, wrong size:', blob.size, size);
return this.deleteFile(fileName).then(() => {
return this.downloadFile(dcID, location, size, options);
return this.downloadFile(options);
}).catch(() => {
return this.downloadFile(dcID, location, size, options);
return this.downloadFile(options);
});
} else {
return blob;
@ -346,7 +172,7 @@ export class ApiFileManager { @@ -346,7 +172,7 @@ export class ApiFileManager {
};
fileStorage.getFile(fileName).then(async(blob: Blob) => {
//this.log('is that i wanted');
//this.log('maybe cached', fileName);
//throw '';
if(blob.size < size) {
@ -358,10 +184,10 @@ export class ApiFileManager { @@ -358,10 +184,10 @@ export class ApiFileManager {
if(toFileEntry) {
FileManager.copy(blob, toFileEntry).then(deferred.resolve, errorHandler);
} else {
deferred.resolve(this.cachedDownloads[fileName] = blob);
deferred.resolve(blob);
}
}).catch(() => {
//this.log('not i wanted');
//this.log('not cached', fileName);
//var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
const fileWriterPromise = toFileEntry ? Promise.resolve(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
@ -374,6 +200,10 @@ export class ApiFileManager { @@ -374,6 +200,10 @@ export class ApiFileManager {
writeFileDeferred: CancellablePromise<unknown>;
const maxRequests = options.processPart ? 5 : 5;
if(!size) {
size = limit;
}
if(fileWriter.length) {
startOffset = fileWriter.length;
@ -381,7 +211,7 @@ export class ApiFileManager { @@ -381,7 +211,7 @@ export class ApiFileManager {
if(toFileEntry) {
deferred.resolve();
} else {
deferred.resolve(this.cachedDownloads[fileName] = fileWriter.finalize());
deferred.resolve(fileWriter.finalize());
}
return;
@ -475,7 +305,7 @@ export class ApiFileManager { @@ -475,7 +305,7 @@ export class ApiFileManager {
if(toFileEntry) {
deferred.resolve();
} else {
deferred.resolve(this.cachedDownloads[fileName] = fileWriter.finalize());
deferred.resolve(fileWriter.finalize());
}
}
} catch(err) {
@ -511,11 +341,7 @@ export class ApiFileManager { @@ -511,11 +341,7 @@ export class ApiFileManager {
public deleteFile(fileName: string) {
//this.log('will delete file:', fileName);
delete this.cachedDownloadPromises[fileName];
delete this.cachedDownloads[fileName];
delete this.cachedSavePromises[fileName];
return this.getFileStorage().deleteFile(fileName);
}
@ -638,4 +464,9 @@ export class ApiFileManager { @@ -638,4 +464,9 @@ export class ApiFileManager {
}
}
export default new ApiFileManager();
const apiFileManager = new ApiFileManager();
// @ts-ignore
if(process.env.NODE_ENV != 'production') {
(self as any).apiFileManager = apiFileManager;
}
export default apiFileManager;

126
src/lib/mtproto/mtproto.service.ts

@ -6,6 +6,7 @@ import apiManager from "./apiManager"; @@ -6,6 +6,7 @@ import apiManager from "./apiManager";
import AppStorage from '../storage';
import cryptoWorker from "../crypto/cryptoworker";
import networkerFactory from "./networkerFactory";
import apiFileManager from './apiFileManager';
const ctx = self as any as ServiceWorkerGlobalScope;
@ -85,14 +86,16 @@ networkerFactory.setUpdatesProcessor((obj, bool) => { @@ -85,14 +86,16 @@ networkerFactory.setUpdatesProcessor((obj, bool) => {
return;
}
listeners[0].postMessage({update: {obj, bool}});
listeners.forEach(listener => {
listener.postMessage({update: {obj, bool}});
});
});
});
ctx.addEventListener('message', async(e) => {
const taskID = e.data.taskID;
console.log('[SW] Got message:', taskID, e, e.data);
//console.log('[SW] Got message:', taskID, e, e.data);
if(e.data.useLs) {
AppStorage.finishTask(e.data.taskID, e.data.args);
@ -107,6 +110,24 @@ ctx.addEventListener('message', async(e) => { @@ -107,6 +110,24 @@ ctx.addEventListener('message', async(e) => {
respond(e.source, {taskID: taskID, result: result});
});
case 'downloadFile': {
/* // @ts-ignore
return apiFileManager.downloadFile(...e.data.args); */
try {
// @ts-ignore
let result = apiFileManager[e.data.task].apply(apiFileManager, e.data.args);
if(result instanceof Promise) {
result = await result;
}
respond(e.source, {taskID: taskID, result: result});
} catch(err) {
respond(e.source, {taskID: taskID, error: err});
}
}
default: {
try {
// @ts-ignore
@ -151,3 +172,104 @@ ctx.addEventListener('activate', (event) => { @@ -151,3 +172,104 @@ ctx.addEventListener('activate', (event) => {
event.waitUntil(ctx.clients.claim());
});
function timeout(delay: number): Promise<Response> {
return new Promise(((resolve) => {
setTimeout(() => {
resolve(new Response('', {
status: 408,
statusText: 'Request timed out.',
}));
}, delay);
}));
}
/**
* Fetch requests
*/
ctx.addEventListener('fetch', (event: FetchEvent): void => {
const [, url, scope, fileName] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || [];
//console.log('[SW] fetch:', event, event.request, url, scope, fileName);
switch(scope) {
case 'thumb':
case 'document':
case 'photo': {
const info = JSON.parse(decodeURIComponent(fileName));
//console.log('[SW] fetch cachedDownloadPromises:', info/* apiFileManager.cachedDownloadPromises, apiFileManager.cachedDownloadPromises.hasOwnProperty(fileName) */);
const promise = apiFileManager.downloadFile(info).then(b => new Response(b));
event.respondWith(promise);
break;
}
case 'upload': {
if(event.request.method == 'POST') {
event.respondWith(event.request.blob().then(blob => {
return apiFileManager.uploadFile(blob).then(v => new Response(JSON.stringify(v), {headers: {'Content-Type': 'application/json'}}));
}));
}
break;
}
/* default: {
break;
}
case 'documents':
case 'photos':
case 'profiles':
// direct download
if (event.request.method === 'POST') {
event.respondWith(// download(url, 'unknown file.txt', getFilePartRequest));
event.request.text()
.then((text) => {
const [, filename] = text.split('=');
return download(url, filename ? filename.toString() : 'unknown file', getFilePartRequest);
}),
);
// inline
} else {
event.respondWith(
ctx.cache.match(url).then((cached) => {
if (cached) return cached;
return Promise.race([
timeout(45 * 1000), // safari fix
new Promise<Response>((resolve) => {
fetchRequest(url, resolve, getFilePartRequest, ctx.cache, fileProgress);
}),
]);
}),
);
}
break;
case 'stream': {
const [offset, end] = parseRange(event.request.headers.get('Range') || '');
log('stream', url, offset, end);
event.respondWith(new Promise((resolve) => {
fetchStreamRequest(url, offset, end, resolve, getFilePartRequest);
}));
break;
}
case 'stripped':
case 'cached': {
const bytes = getThumb(url) || null;
event.respondWith(new Response(bytes, { headers: { 'Content-Type': 'image/jpg' } }));
break;
}
default:
if (url && url.endsWith('.tgs')) event.respondWith(fetchTGS(url));
else event.respondWith(fetch(event.request.url)); */
}
});

54
src/lib/mtproto/mtprotoworker.ts

@ -2,6 +2,7 @@ import {dT, isObject, $rootScope} from '../utils'; @@ -2,6 +2,7 @@ import {dT, isObject, $rootScope} from '../utils';
import AppStorage from '../storage';
import CryptoWorkerMethods from '../crypto/crypto_methods';
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
import { InputFileLocation, FileLocation } from '../../types';
type Task = {
taskID: number,
@ -45,14 +46,14 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -45,14 +46,14 @@ class ApiManagerProxy extends CryptoWorkerMethods {
this.releasePending();
});
navigator.serviceWorker.oncontrollerchange = () => {
console.error('oncontrollerchange');
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.warn(dT(), 'ApiManagerProxy controllerchange');
this.releasePending();
navigator.serviceWorker.controller.addEventListener('error', (e) => {
console.error('controller error:', e);
});
};
});
/**
* Message resolver
@ -75,41 +76,6 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -75,41 +76,6 @@ class ApiManagerProxy extends CryptoWorkerMethods {
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
}
});
/* if(window.Worker) {
import('./mtproto_service.js').then((worker: any) => {
var tmpWorker = new worker.default();
tmpWorker.onmessage = (e: any) => {
if(!this.webWorker) {
this.webWorker = tmpWorker;
console.info(dT(), 'ApiManagerProxy set webWorker');
this.releasePending();
}
if(!isObject(e.data)) {
return;
}
if(e.data.useLs) {
// @ts-ignore
AppStorage[e.data.task](...e.data.args).then(res => {
(this.webWorker as Worker).postMessage({useLs: true, taskID: e.data.taskID, args: res});
});
} else if(e.data.update) {
if(this.updatesProcessor) {
this.updatesProcessor(e.data.update.obj, e.data.update.bool);
}
} else {
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
}
};
tmpWorker.onerror = (error: any) => {
console.error('ApiManagerProxy error', error);
this.webWorker = false;
};
});
} */
}
private finalizeTask(taskID: number, result: any, error: any) {
@ -127,13 +93,12 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -127,13 +93,12 @@ class ApiManagerProxy extends CryptoWorkerMethods {
return new Promise<T>((resolve, reject) => {
this.awaiting[this.taskID] = {resolve, reject, taskName: task};
let params = {
const params = {
task,
taskID: this.taskID,
args
};
//(this.webWorker as Worker).postMessage(params);
this.pending.push(params);
this.releasePending();
@ -194,6 +159,15 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -194,6 +159,15 @@ class ApiManagerProxy extends CryptoWorkerMethods {
public logOut(): Promise<void> {
return this.performTaskWorker('logOut');
}
public downloadFile(dcID: number, location: InputFileLocation | FileLocation, size: number = 0, options: Partial<{
mimeType: string,
toFileEntry: any,
limitPart: number,
stickerType: number
}> = {}): Promise<Blob> {
return this.performTaskWorker('downloadFile', dcID, location, size, options);
}
}
const apiManagerProxy = new ApiManagerProxy();

17
src/lib/utils.ts

@ -4,6 +4,9 @@ @@ -4,6 +4,9 @@
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import { InputFileLocation, FileLocation } from "../types";
var _logTimer = Date.now();
export function dT () {
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']';
@ -519,3 +522,17 @@ export function getEmojiToneIndex(input: string) { @@ -519,3 +522,17 @@ export function getEmojiToneIndex(input: string) {
let match = input.match(/[\uDFFB-\uDFFF]/);
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
}
export function getFileURL(type: 'photo' | 'thumb' | 'document', options: {
dcID: number,
location: InputFileLocation | FileLocation,
size?: number,
mimeType?: string
}) {
//console.log('getFileURL', location);
//const perf = performance.now();
const encoded = encodeURIComponent(JSON.stringify(options));
//console.log('getFileURL encode:', performance.now() - perf, encoded);
return '/' + type + '/' + encoded;
}

46
src/types.d.ts vendored

@ -41,7 +41,7 @@ export type MTPhotoSize = { @@ -41,7 +41,7 @@ export type MTPhotoSize = {
h?: number,
size?: number,
type?: string, // i, m, x, y, w by asc
location?: any,
location?: FileLocation,
bytes?: Uint8Array // if type == 'i'
};
@ -87,4 +87,46 @@ export type AccountPassword = { @@ -87,4 +87,46 @@ export type AccountPassword = {
srp_B?: Uint8Array,
srp_id?: string,
secure_random: Uint8Array,
};
};
export type FileLocation = {
_: 'fileLocationToBeDeprecated',
volume_id: string,
local_id: number
};
export type inputFileLocation = {
_: 'inputFileLocation',
volume_id: string,
local_id: number,
secret: string,
file_reference: Uint8Array | number[]
};
export type inputDocumentFileLocation = {
_: 'inputDocumentFileLocation',
id: string,
access_hash: string,
file_reference: Uint8Array | number[],
thumb_size: string
};
export type inputPhotoFileLocation = Omit<inputDocumentFileLocation, '_'> & {_: 'inputPhotoFileLocation'};
export type inputPeerPhotoFileLocation = {
_: 'inputPeerPhotoFileLocation',
flags: number,
big?: true,
peer: any,
volume_id: string,
local_id: number
};
export type inputStickerSetThumb = {
_: 'inputStickerSetThumb',
stickerset: any,
volume_id: string,
local_id: number
};
export type InputFileLocation = inputFileLocation | inputDocumentFileLocation | inputPhotoFileLocation | inputPeerPhotoFileLocation | inputStickerSetThumb;
Loading…
Cancel
Save