Browse Source

WebP new converter

master
morethanwords 4 years ago
parent
commit
c33748eb5c
  1. 23
      package-lock.json
  2. 1
      package.json
  3. 2
      server.js
  4. 2
      src/components/scrollable_new.ts
  5. 9
      src/components/wrappers.ts
  6. 28
      src/dialogsBug.js
  7. 7
      src/global.d.ts
  8. 15
      src/helpers/userAgent.ts
  9. 103
      src/lib/appManagers/appWebpManager.ts
  10. 17
      src/lib/config.ts
  11. 18
      src/lib/filemanager.ts
  12. 41
      src/lib/mtproto/apiFileManager.ts
  13. 39
      src/lib/mtproto/mtproto.service.ts
  14. 13
      src/lib/mtproto/mtprotoworker.ts
  15. 27
      src/lib/storage.ts
  16. 55
      src/lib/webp.ts
  17. 42
      src/lib/webp/webp.ts
  18. 50
      src/lib/webp/webp.worker.ts
  19. 53
      src/lib/webp/webpWorkerController.ts
  20. 0
      src/scripts/countries.dat
  21. 0
      src/scripts/countries.json
  22. 0
      src/scripts/countries_pretty.json
  23. 0
      src/scripts/emoji.json
  24. 0
      src/scripts/emoji_pretty.json
  25. 0
      src/scripts/format_jsons.js
  26. 0
      src/scripts/format_schema.js
  27. 0
      src/scripts/schema.json
  28. 0
      src/scripts/schema_pretty.json
  29. 0
      src/vendor/StackBlur.js
  30. 4082
      src/vendor/libwebp-0.2.0.js
  31. 0
      src/vendor/smoothscroll.js
  32. 4
      tsconfig.json
  33. 65
      webpack.common.js

23
package-lock.json generated

@ -3171,6 +3171,12 @@ @@ -3171,6 +3171,12 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.4.tgz",
"integrity": "sha512-k3NqigXWRzQZVBDS5D1U70A5E8Qk4Kh+Ha/x4M8Bt9pF0X05eggfnC9+63Usc9Q928hRUIpIhTQaXsZwZBl4Ew=="
},
"@types/pako": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.1.tgz",
"integrity": "sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg==",
"dev": true
},
"@types/puppeteer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-3.0.1.tgz",
@ -6444,6 +6450,17 @@ @@ -6444,6 +6450,17 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fast-png": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-5.0.2.tgz",
"integrity": "sha512-75j60BAZP5R3eTHslW5o8apfuAtxRONfY3gGt3yQlsm46Xz+uRCjguBFFr5SMNGPJ7MjYFBsE1dlwPLnTA4t6w==",
"dev": true,
"requires": {
"@types/pako": "^1.0.1",
"iobuffer": "^5.0.2",
"pako": "^1.0.10"
}
},
"fastdom": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.9.tgz",
@ -8082,6 +8099,12 @@ @@ -8082,6 +8099,12 @@
"integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
"dev": true
},
"iobuffer": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.0.2.tgz",
"integrity": "sha512-FwwzAUZkapfHq76amLGSaEigB2nRSZR+ADc+zRcIRm2MsofDJAiIEPGbw+is/lI+kW32fM5jezmJ8dVQnd1UbA==",
"dev": true
},
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",

1
package.json

@ -38,6 +38,7 @@ @@ -38,6 +38,7 @@
"compression-webpack-plugin": "^3.1.0",
"css-loader": "^3.5.3",
"express": "^4.17.1",
"fast-png": "^5.0.2",
"fastdom": "^1.0.9",
"file-loader": "^4.3.0",
"handlebars": "^4.7.6",

2
server.js

@ -15,6 +15,6 @@ app.get('/', (req, res) => { @@ -15,6 +15,6 @@ app.get('/', (req, res) => {
https.createServer({
key: fs.readFileSync(__dirname + '/certs/server-key.pem'),
cert: fs.readFileSync(__dirname + '/certs/server-cert.pem')
}, app).listen(9001, () => {
}, app).listen(443/* 9001 */, () => {
console.log('Listening...');
});

2
src/components/scrollable_new.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { logger, LogLevels } from "../lib/logger";
import smoothscroll from '../lib/smoothscroll';
import smoothscroll from '../vendor/smoothscroll';
import { touchSupport, isSafari } from "../lib/config";
//import { isInDOM } from "../lib/utils";
(window as any).__forceSmoothScrollPolyfill__ = true;

9
src/components/wrappers.ts

@ -9,17 +9,16 @@ import ProgressivePreloaderNew from './preloader_new'; @@ -9,17 +9,16 @@ import ProgressivePreloaderNew from './preloader_new';
import LazyLoadQueue from './lazyLoadQueue';
import VideoPlayer from '../lib/mediaPlayer';
import { RichTextProcessor } from '../lib/richtextprocessor';
import { CancellablePromise } from '../lib/polyfill';
import { renderImageFromUrl } from './misc';
import appMessagesManager from '../lib/appManagers/appMessagesManager';
import { Layouter, RectPart } from './groupedLayout';
import PollElement from './poll';
import appWebpManager from '../lib/appManagers/appWebpManager';
import { mediaSizes } from '../lib/config';
import { mediaSizes, isSafari } from '../lib/config';
import { MTDocument, MTPhotoSize } from '../types';
import animationIntersector from './animationIntersector';
import AudioElement from './audio';
import { Download } from '../lib/appManagers/appDownloadManager';
import { webpWorkerController } from '../lib/webp/webpWorkerController';
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: {
doc: MTDocument,
@ -420,12 +419,12 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -420,12 +419,12 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
if(thumb.bytes) {
let img = new Image();
if((appWebpManager.isSupported() || doc.stickerThumbConverted)/* && false */) {
if((!isSafari || doc.stickerThumbConverted)/* && false */) {
renderImageFromUrl(img, appPhotosManager.getPreviewURLFromThumb(thumb, true));
div.append(img);
} else {
appWebpManager.convertToPng(thumb.bytes).then(bytes => {
webpWorkerController.convert(doc.id, thumb.bytes).then(bytes => {
if(middleware && !middleware()) return;
thumb.bytes = bytes;

28
src/dialogsBug.js

@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
MTProto.apiManager.invokeApi('messages.getPeerDialogs', {
peers: [
{
_: 'inputDialogPeer',
peer: {_: 'inputPeerUser', user_id: 296814355, access_hash: '7461657386624868366'}
}
]
}).then(dialogs => console.log(dialogs));
MTProto.apiManager.invokeApi('messages.getPinnedDialogs', {
folder_id: 0
}).then(dialogs => console.log(dialogs));
// read_outbox_max_id && read_inbox_max_id are 0!
MTProto.apiManager.invokeApi('messages.getDialogs', {
flags: 0 | 1,
exclude_pinned: true,
folder_id: 0,
offset_date: 0,
offset_id: 0,
offset_peer: {_: 'inputPeerEmpty'},
limit: 6,
hash: Date.now() * 5
}).then(dialogs => console.log(dialogs));
// [109, 188, 177, 157, 19, 7, 177, 17, 49, 155, 9, 0, 44, 155, 9, 0, 237, 154, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 157, 80, 175] - works
// [109, 188, 177, 157, 19, 7, 177, 17, 49, 155, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 157, 80, 175] - pinned

7
src/global.d.ts vendored

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
declare module 'worker-loader!*' {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}

15
src/helpers/userAgent.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
export const userAgent = navigator ? navigator.userAgent : null;
export const isApple = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) != -1;
export const isAndroid = navigator.userAgent.toLowerCase().indexOf('android') != -1;
/**
* Returns true when run in WebKit derived browsers.
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
* transfer data between WebWorkers and the main thread.
* https://github.com/mapbox/mapbox-gl-js/issues/8771
*
* This should be removed once the underlying Safari issue is fixed.
*/
const ctx = typeof(window) !== 'undefined' ? window : self;
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));

103
src/lib/appManagers/appWebpManager.ts

@ -1,103 +0,0 @@ @@ -1,103 +0,0 @@
class AppWebpManager {
private webpMachine: any = null;
private loaded: Promise<void>;
private busyPromise: Promise<Uint8Array | void>;
private queue: {bytes: Uint8Array, callback: (res: Uint8Array) => void}[] = [];
private testPromise: Promise<boolean> = null;
public webpSupport = false;
constructor() {
this.testWebpSupport();
}
private loadWebpHero() {
if(this.loaded) return this.loaded;
this.loaded = new Promise(async(resolve, reject) => {
let res = await this.testWebpSupport();
if(!res) {
(window as any).webpLoaded = () => {
//console.log('webpHero loaded');
this.webpMachine = new (window as any).WebpMachine();
resolve();
};
let sc = document.createElement('script');
sc.src = 'npm.webp-hero.chunk.js';
sc.async = true;
sc.onload = (window as any).webpLoaded;
document.body.appendChild(sc);
} else {
resolve();
}
});
}
private convert(bytes: Uint8Array): AppWebpManager['busyPromise'] {
return this.webpMachine.decode(bytes);
}
private async processQueue() {
if(this.busyPromise) return;
this.busyPromise = Promise.resolve();
let {bytes, callback} = this.queue.pop();
if(!this.loaded) {
this.loadWebpHero();
}
await this.loaded;
this.busyPromise = this.convert(bytes);
let res = await this.busyPromise;
//console.log('converted webp', res);
callback(res as Uint8Array);
this.busyPromise = null;
if(this.queue.length) {
this.processQueue();
}
}
public testWebpSupport() {
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';
webP.onload = webP.onerror = () => {
resolve(this.webpSupport = webP.height === 2/* && false */);
};
});
}
public isSupported() {
return this.webpSupport;
}
public convertToPng(bytes: Uint8Array) {
//console.warn('convertToPng!');
return new Promise<Uint8Array>((resolve, reject) => {
// @ts-ignore
this.queue.push({bytes, callback: resolve});
this.processQueue();
});
}
}
const appWebpManager = new AppWebpManager();
/* // @ts-ignore
if(process.env.NODE_ENV != 'production') {
(window as any).appWebpManager = appWebpManager;
} */
export default appWebpManager;

17
src/lib/config.ts

File diff suppressed because one or more lines are too long

18
src/lib/filemanager.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import {blobConstruct} from './bin_utils';
class FileManager {
export class FileManager {
public blobSupported = true;
constructor() {
@ -14,22 +14,6 @@ class FileManager { @@ -14,22 +14,6 @@ class FileManager {
public isAvailable() {
return this.blobSupported;
}
public copy(fromFileEntry: any, toFileEntry: any) {
return this.write(toFileEntry, fromFileEntry).then(() => {
console.log('copy success');
return toFileEntry;
}, (error: any) => {
console.error('copy error 1:', error);
try {
toFileEntry.truncate(0);
} catch(e) {
console.error('copy error', e);
}
return Promise.reject(error);
});
}
public write(fileWriter: ReturnType<FileManager['getFakeFileWriter']>, bytes: Uint8Array | Blob | {file: any}): Promise<void> {
if('file' in bytes) {

41
src/lib/mtproto/apiFileManager.ts

@ -4,9 +4,9 @@ import cacheStorage from "../cacheStorage"; @@ -4,9 +4,9 @@ import cacheStorage from "../cacheStorage";
import FileManager from "../filemanager";
import apiManager from "./apiManager";
import { deferredPromise, CancellablePromise } from "../polyfill";
import appWebpManager from "../appManagers/appWebpManager";
import { logger } from "../logger";
import { InputFileLocation, FileLocation, UploadFile } from "../../types";
import { isSafari } from "../../helpers/userAgent";
type Delayed = {
offset: number,
@ -21,7 +21,6 @@ export type DownloadOptions = { @@ -21,7 +21,6 @@ export type DownloadOptions = {
fileName?: string,
mimeType?: string,
limitPart?: number,
stickerType?: number,
processPart?: (bytes: Uint8Array, offset?: number, queue?: Delayed[]) => Promise<any>
};
@ -42,6 +41,8 @@ export class ApiFileManager { @@ -42,6 +41,8 @@ export class ApiFileManager {
} = {};
public downloadActives: {[dcID: string]: number} = {};
public webpConvertPromises: {[fileName: string]: CancellablePromise<Uint8Array>} = {};
private log: ReturnType<typeof logger> = logger('AFM');
public downloadRequest(dcID: 'upload', cb: () => Promise<void>, activeDelta: number): Promise<void>;
@ -151,14 +152,10 @@ export class ApiFileManager { @@ -151,14 +152,10 @@ export class ApiFileManager {
let size = options.size ?? 0;
let {dcID, location} = options;
let processSticker = false;
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
if(size > 524288) {
delete options.stickerType;
} else {
processSticker = true;
options.mimeType = 'image/png';
}
let processWebp = false;
if(options.mimeType == 'image/webp' && isSafari) {
processWebp = true;
options.mimeType = 'image/png';
}
// this.log('Dload file', dcID, location, size)
@ -172,7 +169,7 @@ export class ApiFileManager { @@ -172,7 +169,7 @@ export class ApiFileManager {
if(options.processPart) {
return cachedPromise.then((blob) => {
return this.convertBlobToBytes(blob).then(bytes => {
options.processPart(bytes)
options.processPart(bytes);
return blob;
});
});
@ -204,13 +201,13 @@ export class ApiFileManager { @@ -204,13 +201,13 @@ export class ApiFileManager {
let canceled = false;
let resolved = false;
let cacheFileWriter: any;
let cacheFileWriter: ReturnType<typeof FileManager['getFakeFileWriter']>;
let errorHandler = (error: any) => {
deferred.reject(error);
errorHandler = () => {};
if(cacheFileWriter && (!error || error.type != 'DOWNLOAD_CANCELED')) {
cacheFileWriter.truncate(0);
cacheFileWriter.truncate();
}
};
@ -269,8 +266,22 @@ export class ApiFileManager { @@ -269,8 +266,22 @@ export class ApiFileManager {
await options.processPart(bytes, offset, delayed);
}
if(processSticker) {
return appWebpManager.convertToPng(bytes);
if(processWebp) {
const convertPromise = deferredPromise<Uint8Array>();
(self as any as ServiceWorkerGlobalScope)
.clients
.matchAll({includeUncontrolled: false, type: 'window'})
.then((listeners) => {
if(!listeners.length) {
return;
}
listeners[0].postMessage({type: 'convertWebp', payload: {fileName, bytes}});
});
return await (this.webpConvertPromises[fileName] = convertPromise);
//return appWebpManager.convertToPng(bytes);
}
return bytes;

39
src/lib/mtproto/mtproto.service.ts

@ -9,6 +9,7 @@ import networkerFactory from "./networkerFactory"; @@ -9,6 +9,7 @@ import networkerFactory from "./networkerFactory";
import apiFileManager, { DownloadOptions } from './apiFileManager';
import { getFileNameByLocation } from '../bin_utils';
import { logger, LogLevels } from '../logger';
import { isSafari } from '../../helpers/userAgent';
const log = logger('SW'/* , LogLevels.error */);
@ -16,34 +17,11 @@ const ctx = self as any as ServiceWorkerGlobalScope; @@ -16,34 +17,11 @@ const ctx = self as any as ServiceWorkerGlobalScope;
//console.error('INCLUDE !!!', new Error().stack);
/**
* Returns true when run in WebKit derived browsers.
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
* transfer data between WebWorkers and the main thread.
* https://github.com/mapbox/mapbox-gl-js/issues/8771
*
* This should be removed once the underlying Safari issue is fixed.
*
* @private
* @param scope {WindowOrWorkerGlobalScope} Since this function is used both on the main thread and WebWorker context,
* let the calling scope pass in the global scope object.
* @returns {boolean}
*/
var _isSafari: boolean = null;
function isSafari(scope: any) {
if(_isSafari == null) {
var userAgent = scope.navigator ? scope.navigator.userAgent : null;
_isSafari = !!scope.safari ||
!!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
}
return _isSafari;
}
function isObject(object: any) {
return typeof(object) === 'object' && object !== null;
}
function fillTransfer(transfer: any, obj: any) {
/* function fillTransfer(transfer: any, obj: any) {
if(!obj) return;
if(obj instanceof ArrayBuffer) {
@ -59,7 +37,7 @@ function fillTransfer(transfer: any, obj: any) { @@ -59,7 +37,7 @@ function fillTransfer(transfer: any, obj: any) {
fillTransfer(transfer, value);
});
}
}
} */
/**
* Respond to request
@ -83,7 +61,7 @@ function respond(client: Client | ServiceWorker | MessagePort, ...args: any[]) { @@ -83,7 +61,7 @@ function respond(client: Client | ServiceWorker | MessagePort, ...args: any[]) {
* Broadcast Notification
*/
function notify(...args: any[]) {
ctx.clients.matchAll({ includeUncontrolled: false, type: 'window' }).then((listeners) => {
ctx.clients.matchAll({includeUncontrolled: false, type: 'window'}).then((listeners) => {
if(!listeners.length) {
//console.trace('no listeners?', self, listeners);
return;
@ -108,6 +86,13 @@ ctx.addEventListener('message', async(e) => { @@ -108,6 +86,13 @@ ctx.addEventListener('message', async(e) => {
if(e.data.useLs) {
AppStorage.finishTask(e.data.taskID, e.data.args);
return;
} else if(e.data.type == 'convertWebp') {
const {fileName, bytes} = e.data.payload;
const deferred = apiFileManager.webpConvertPromises[fileName];
if(deferred) {
deferred.resolve(bytes);
delete apiFileManager.webpConvertPromises[fileName];
}
}
switch(e.data.task) {
@ -288,7 +273,7 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => { @@ -288,7 +273,7 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => {
if(info.mimeType) headers['Content-Type'] = info.mimeType;
if(isSafari(ctx)) {
if(isSafari) {
ab = ab.slice(offset - alignedOffset, end - alignedOffset + 1);
headers['Content-Range'] = `bytes ${offset}-${offset + ab.byteLength - 1}/${info.size || '*'}`;
headers['Content-Length'] = `${ab.byteLength}`;

13
src/lib/mtproto/mtprotoworker.ts

@ -2,8 +2,8 @@ import {isObject, $rootScope} from '../utils'; @@ -2,8 +2,8 @@ import {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';
import { logger } from '../logger';
import { webpWorkerController } from '../webp/webpWorkerController';
type Task = {
taskID: number,
@ -74,6 +74,8 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -74,6 +74,8 @@ class ApiManagerProxy extends CryptoWorkerMethods {
}
} else if(e.data.progress) {
$rootScope.$broadcast('download_progress', e.data.progress);
} else if(e.data.type == 'convertWebp') {
webpWorkerController.postMessage(e.data);
} else {
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
}
@ -162,15 +164,6 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -162,15 +164,6 @@ class ApiManagerProxy extends CryptoWorkerMethods {
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);
}
public cancelDownload(fileName: string) {
return this.performTaskWorker('cancelDownload', fileName);
}

27
src/lib/storage.ts

@ -136,15 +136,15 @@ class ConfigStorage { @@ -136,15 +136,15 @@ class ConfigStorage {
}
}
const configStorage = new ConfigStorage();
class AppStorage {
private isWebWorker: boolean;
private isWorker: boolean;
private taskID = 0;
private tasks: {[taskID: number]: (result: any) => void} = {};
//private log = (...args: any[]) => console.log('[SW LS]', ...args);
private log = (...args: any[]) => {};
private configStorage: ConfigStorage;
constructor() {
if(Modes.test) {
this.setPrefix('t_');
@ -152,15 +152,23 @@ class AppStorage { @@ -152,15 +152,23 @@ class AppStorage {
// @ts-ignore
//this.isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
this.isWebWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
this.isWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope;
if(!this.isWorker) {
this.configStorage = new ConfigStorage();
}
}
public setPrefix(newPrefix: string) {
configStorage.keyPrefix = newPrefix;
if(this.configStorage) {
this.configStorage.keyPrefix = newPrefix;
}
}
public noPrefix() {
configStorage.noPrefix = true;
if(this.configStorage) {
this.configStorage.noPrefix = true;
}
}
public finishTask(taskID: number, result: any) {
@ -175,9 +183,9 @@ class AppStorage { @@ -175,9 +183,9 @@ class AppStorage {
delete this.tasks[taskID];
}
private proxy<T>(methodName: string, ..._args: any[]) {
private proxy<T>(methodName: 'set' | 'get' | 'remove' | 'clear', ..._args: any[]) {
return new Promise<T>((resolve, reject) => {
if(this.isWebWorker) {
if(this.isWorker) {
const taskID = this.taskID++;
this.tasks[taskID] = resolve;
@ -203,8 +211,7 @@ class AppStorage { @@ -203,8 +211,7 @@ class AppStorage {
resolve(result);
});
// @ts-ignore
configStorage[methodName].apply(configStorage, args);
this.configStorage[methodName].apply(this.configStorage, args as any);
}
});
}

55
src/lib/webp.ts

@ -1,55 +0,0 @@ @@ -1,55 +0,0 @@
import {Webp} from "webp-hero/libwebp/dist/webp.js"
const relax = () => new Promise(resolve => requestAnimationFrame(resolve));
export class WebpMachineError extends Error {}
/**
* Webp Machine
* - decode and polyfill webp images
* - can only decode images one-at-a-time (otherwise will throw busy error)
*/
export class WebpMachine {
private readonly webp: Webp;
private busy = false;
constructor() {
this.webp = new Webp();
this.webp.Module.doNotCaptureKeyboard = true;
}
/**
* Decode raw webp data into a png data url
*/
decode(webpData: Uint8Array): Promise<Uint8Array> {
if(this.busy) throw new WebpMachineError("cannot decode when already busy");
this.busy = true;
try {
return relax().then(() => {
const canvas = document.createElement("canvas");
this.webp.setCanvas(canvas);
this.webp.webpToSdl(webpData, webpData.length);
this.busy = false;
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
let reader = new FileReader();
reader.onload = (event) => {
resolve(new Uint8Array(event.target.result as ArrayBuffer));
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
}, 'image/png', 1);
});
});
} catch(error) {
this.busy = false;
error.name = WebpMachineError.name;
error.message = `failed to decode webp image: ${error.message}`;
throw error;
}
}
}
(window as any).WebpMachine = WebpMachine;

42
src/lib/webp/webp.ts

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
import { WebPDecoder } from '../../vendor/libwebp-0.2.0';
import { encode } from 'fast-png';
export function webp2png(data: Uint8Array) {
const decoder = new WebPDecoder();
const config: any = decoder.WebPDecoderConfig;
const buffer = config.j || config.output;
const bitstream = config.input;
decoder.WebPInitDecoderConfig(config);
decoder.WebPGetFeatures(data, data.length, bitstream);
/** MODE_RGBA = 1 MODE_ARGB = 4, */
buffer.J = 1;
let status;
try {
status = decoder.WebPDecode(data, data.length, config);
} catch(e) {
status = e;
}
if(status === 0) {
const rgbaData = buffer.Jb;
const pngData = encode({
data: rgbaData,
width: buffer.width,
height: buffer.height,
channels: 4,
depth: 8,
});
return {status, bytes: pngData};
}
return {status, bytes: data};
}
export function webp2pngAsBlob(data: Uint8Array) {
const {status, bytes} = webp2png(data);
return new Blob([bytes], {type: status === 0 ? 'image/png' : 'image/webp'});
}

50
src/lib/webp/webp.worker.ts

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
import { webp2png } from './webp';
import type { WebpConvertTask } from './webpWorkerController';
const ctx = self as any as DedicatedWorkerGlobalScope;
const tasks: WebpConvertTask[] = [];
let isProcessing = false;
function finishTask() {
isProcessing = false;
processTasks();
}
function processTasks() {
if(isProcessing) return;
const task = tasks.shift();
if(!task) return;
isProcessing = true;
switch(task.type) {
case 'convertWebp': {
const {fileName, bytes} = task.payload;
ctx.postMessage({
type: 'convertWebp',
payload: {
fileName,
bytes: webp2png(bytes).bytes
}
});
finishTask();
break;
}
default:
finishTask();
}
}
function scheduleTask(task: WebpConvertTask) {
tasks.push(task);
processTasks();
}
ctx.addEventListener('message', (event) => {
scheduleTask(event.data);
});

53
src/lib/webp/webpWorkerController.ts

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
import WebpWorker from 'worker-loader!./webp.worker';
import { CancellablePromise, deferredPromise } from '../polyfill';
export type WebpConvertTask = {
type: 'convertWebp',
payload: {
fileName: string,
bytes: Uint8Array
}
};
export class WebpWorkerController {
private worker: Worker;
private convertPromises: {[fileName: string]: CancellablePromise<Uint8Array>} = {};
init() {
this.worker = new WebpWorker();
this.worker.addEventListener('message', (e) => {
const payload = (e.data as WebpConvertTask).payload;
if(payload.fileName.indexOf('main-') === 0) {
const promise = this.convertPromises[payload.fileName];
if(promise) {
promise.resolve(payload.bytes);
delete this.convertPromises[payload.fileName];
}
} else {
navigator.serviceWorker.controller.postMessage(e.data);
}
});
}
postMessage(data: WebpConvertTask) {
if(this.init) {
this.init();
this.init = null;
}
this.worker.postMessage(data);
}
convert(fileName: string, bytes: Uint8Array) {
const convertPromise = deferredPromise<Uint8Array>();
fileName = 'main-' + fileName;
this.postMessage({type: 'convertWebp', payload: {fileName, bytes}});
return this.convertPromises[fileName] = convertPromise;
}
}
export const webpWorkerController = new WebpWorkerController();

0
src/countries.dat → src/scripts/countries.dat

0
src/countries.json → src/scripts/countries.json

0
src/countries_pretty.json → src/scripts/countries_pretty.json

0
src/emoji.json → src/scripts/emoji.json

0
src/emoji_pretty.json → src/scripts/emoji_pretty.json

0
src/format_jsons.js → src/scripts/format_jsons.js

0
src/format_schema.js → src/scripts/format_schema.js

0
src/schema.json → src/scripts/schema.json

0
src/schema_pretty.json → src/scripts/schema_pretty.json

0
src/lib/StackBlur.js → src/vendor/StackBlur.js vendored

4082
src/vendor/libwebp-0.2.0.js vendored

File diff suppressed because it is too large Load Diff

0
src/lib/smoothscroll.js → src/vendor/smoothscroll.js vendored

4
tsconfig.json

@ -65,10 +65,8 @@ @@ -65,10 +65,8 @@
"node_modules",
"public",
"coverage",
"./src/lib/ckin.js",
"./src/lib/crypto/crypto.worker.js",
"src/lib/config.ts",
"./src/lib/StackBlur.js",
"src/vendor/StackBlur.js",
"./src/lib/*.js",
"./src/*.js",
"*.js",

65
webpack.common.js

@ -51,29 +51,6 @@ module.exports = { @@ -51,29 +51,6 @@ module.exports = {
}
].filter(l => !!l)
},
/* {
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
{
loader: 'resolve-url-loader'
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
},
],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
}, */
{
test: /\.ts?$/,
use: [
@ -82,21 +59,6 @@ module.exports = { @@ -82,21 +59,6 @@ module.exports = {
],
exclude: /node_modules/,
},
/* {
test: /\.(woff2?|ttf|otf|eot|svg|jpg)$/,
exclude: /node_modules/,
loader: 'file-loader',
options: {
outputPath: 'assets/',
publicPath: 'assets/',
name: '[folder]/[name].[ext]'
}
}, */
{
test: /\.worker\.(js|ts)$/,
use: { loader: 'worker-loader' }
},
{
test: /\.hbs$/,
use: [
@ -105,28 +67,23 @@ module.exports = { @@ -105,28 +67,23 @@ module.exports = {
}
],
},
resolve: {
extensions: [ '.ts', '.js' ],
},
//entry: './src/index.ts',
entry: {
index: './src/index.ts',
//mp4: './src/lib/MP4Source.ts',
webp: './src/lib/webp.ts'/* ,
lottie: './src/lib/lottie.ts' */
extensions: ['.ts', '.js'],
},
entry: './src/index.ts',
/* entry: {
index: './src/index.ts',
'lottie-web': ['lottie-web']
//lottieLoader: './src/lib/lottieLoader.ts'
index: './src/index.ts'
}, */
//devtool: 'inline-source-map',
output: {
path: path.resolve(__dirname, 'public'),
//filename: 'bundle.js',
filename: "[name].bundle.js",
chunkFilename: "[name].chunk.js"
},
devServer: {
contentBase: path.join(__dirname, 'public'),
watchContentBase: true,
@ -165,19 +122,19 @@ module.exports = { @@ -165,19 +122,19 @@ module.exports = {
},
sockHost: useLocal ? undefined : 'tweb.enko.club',
},
plugins: [
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, 'src/lib/mtproto/mtproto.service.ts'),
filename: 'sw.js',
excludes: ['**/*'],
}),
new HtmlWebpackPlugin({
filename: `index.html`,
//template: 'public/index_template.html',
template: 'src/index.hbs',
//inject: true,
inject: false,
//inject: 'head',
inject: false, // true, 'head'
minify: {
removeComments: true,
collapseWhitespace: true,
@ -191,7 +148,7 @@ module.exports = { @@ -191,7 +148,7 @@ module.exports = {
minifyURLs: true
},
chunks: "all",
excludeChunks: ['npm.webp-hero'/* , 'mp4' *//* , 'npm.lottie-web' */]
excludeChunks: []
}),
new MiniCssExtractPlugin({

Loading…
Cancel
Save