Browse Source

debug

master
morethanwords 4 years ago
parent
commit
357ff5ffef
  1. 5
      src/components/wrappers.ts
  2. 8
      src/lib/logger.ts
  3. 275
      src/lib/mediaPlayer.ts
  4. 591
      src/lib/mtproto/mtproto.service.ts
  5. 10
      src/lib/mtproto/mtprotoworker.ts

5
src/components/wrappers.ts

@ -123,6 +123,11 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
}, {once: true}); */ }, {once: true}); */
await promise; await promise;
} else if(doc.supportsStreaming) {
preloader = new ProgressivePreloader(container, false);
video.addEventListener('canplay', () => {
preloader.detach();
}, {once: true});
} }
if(middleware && !middleware()) { if(middleware && !middleware()) {

8
src/lib/logger.ts

@ -16,6 +16,8 @@ export function logger(prefix: string, level = LogLevels.log | LogLevels.warn |
level = LogLevels.error; level = LogLevels.error;
} }
level = LogLevels.log | LogLevels.warn | LogLevels.error | LogLevels.debug
function Log(...args: any[]) { function Log(...args: any[]) {
return level & LogLevels.log && console.log(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.log && console.log(dT(), '[' + prefix + ']:', ...args);
} }
@ -37,8 +39,12 @@ export function logger(prefix: string, level = LogLevels.log | LogLevels.warn |
}; };
Log.debug = function(...args: any[]) { Log.debug = function(...args: any[]) {
return level & LogLevels.debug && console.debug(dT(), '[' + prefix + ']:', ...args); return level & LogLevels.debug && console.log(dT(), '[' + prefix + ']:', ...args);
}; };
/* Log.debug = function(...args: any[]) {
return level & LogLevels.debug && console.debug(dT(), '[' + prefix + ']:', ...args);
}; */
return Log; return Log;
}; };

275
src/lib/mediaPlayer.ts

@ -1,46 +1,132 @@
export class MediaProgressLine { export class ProgressLine {
public container: HTMLDivElement; public container: HTMLDivElement;
private filled: HTMLDivElement; protected filled: HTMLDivElement;
private filledLoad: HTMLDivElement; protected seek: HTMLInputElement;
private seek: HTMLInputElement;
private duration = 0; protected duration = 100;
private mousedown = false; protected mousedown = false;
private stopAndScrubTimeout = 0;
private progressRAF = 0;
public onSeek: (time: number) => void; private events: Partial<{
//onMouseMove: ProgressLine['onMouseMove'],
onMouseDown: ProgressLine['onMouseDown'],
onMouseUp: ProgressLine['onMouseUp'],
onScrub: (scrubTime: number) => void
}> = {};
constructor(private media: HTMLAudioElement | HTMLVideoElement, private streamable = false) { constructor() {
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('media-progress'); this.container.classList.add('media-progress');
this.filled = document.createElement('div'); this.filled = document.createElement('div');
this.filled.classList.add('media-progress__filled'); this.filled.classList.add('media-progress__filled');
if(streamable) { const seek = this.seek = document.createElement('input');
this.filledLoad = document.createElement('div');
this.filledLoad.classList.add('media-progress__filled', 'media-progress__loaded');
this.container.append(this.filledLoad);
//this.setLoadProgress();
}
let seek = this.seek = document.createElement('input');
seek.classList.add('media-progress__seek'); seek.classList.add('media-progress__seek');
seek.value = '0'; seek.value = '0';
seek.setAttribute('min', '0'); seek.setAttribute('min', '0');
seek.setAttribute('max', '0'); seek.setAttribute('max', '0');
seek.type = 'range'; seek.type = 'range';
seek.step = '0.1'; seek.step = '0.1';
seek.max = '' + (this.duration * 1000);
this.setSeekMax(); //this.setListeners();
this.setListeners();
this.container.append(this.filled, seek);
}
public setHandlers(events: ProgressLine['events']) {
this.events = events;
}
onMouseMove = (e: MouseEvent) => {
this.mousedown && this.scrub(e);
};
onMouseDown = (e: MouseEvent) => {
this.scrub(e);
this.mousedown = true;
this.events?.onMouseDown(e);
};
onMouseUp = (e: MouseEvent) => {
this.mousedown = false;
this.events?.onMouseUp(e);
};
protected setListeners() {
this.container.addEventListener('mousemove', this.onMouseMove);
this.container.addEventListener('mousedown', this.onMouseDown);
this.container.addEventListener('mouseup', this.onMouseUp);
}
protected scrub(e: MouseEvent) {
const scrubTime = e.offsetX / this.container.offsetWidth * this.duration;
let scaleX = scrubTime / this.duration;
scaleX = Math.max(0, Math.min(1, scaleX));
this.filled.style.transform = 'scaleX(' + scaleX + ')';
//this.events?.onScrub(scrubTime);
return scrubTime;
}
public removeListeners() {
this.container.removeEventListener('mousemove', this.onMouseMove);
this.container.removeEventListener('mousedown', this.onMouseDown);
this.container.removeEventListener('mouseup', this.onMouseUp);
this.events = {};
}
}
export class MediaProgressLine extends ProgressLine {
private filledLoad: HTMLDivElement;
private stopAndScrubTimeout = 0;
private progressRAF = 0;
constructor(private media: HTMLAudioElement | HTMLVideoElement, private streamable = false) {
super();
if(streamable) {
this.filledLoad = document.createElement('div');
this.filledLoad.classList.add('media-progress__filled', 'media-progress__loaded');
this.container.prepend(this.filledLoad);
//this.setLoadProgress();
}
if(!media.paused || media.currentTime > 0) { if(!media.paused || media.currentTime > 0) {
this.onPlay(); this.onPlay();
} }
this.container.append(this.filled, seek); this.setSeekMax();
this.setListeners();
this.setHandlers({
onMouseDown: (e: MouseEvent) => {
//super.onMouseDown(e);
//Таймер для того, чтобы стопать видео, если зажал мышку и не отпустил клик
if(this.stopAndScrubTimeout) { // возможно лишнее
clearTimeout(this.stopAndScrubTimeout);
}
this.stopAndScrubTimeout = setTimeout(() => {
!this.media.paused && this.media.pause();
this.stopAndScrubTimeout = 0;
}, 150);
},
onMouseUp: (e: MouseEvent) => {
//super.onMouseUp(e);
if(this.stopAndScrubTimeout) {
clearTimeout(this.stopAndScrubTimeout);
this.stopAndScrubTimeout = 0;
}
this.media.paused && this.media.play();
}
})
} }
onLoadedData = () => { onLoadedData = () => {
@ -70,42 +156,17 @@ export class MediaProgressLine {
this.progressRAF = window.requestAnimationFrame(r); this.progressRAF = window.requestAnimationFrame(r);
}; };
onMouseMove = (e: MouseEvent) => {
this.mousedown && this.scrub(e);
};
onMouseDown = (e: MouseEvent) => {
this.media.pause();
this.scrub(e);
//Таймер для того, чтобы стопать видео, если зажал мышку и не отпустил клик
if(this.stopAndScrubTimeout) { // возможно лишнее
clearTimeout(this.stopAndScrubTimeout);
}
this.stopAndScrubTimeout = setTimeout(() => {
!this.media.paused && this.media.pause();
this.stopAndScrubTimeout = 0;
}, 150);
this.mousedown = true;
};
onMouseUp = (e: MouseEvent) => {
if(this.stopAndScrubTimeout) {
clearTimeout(this.stopAndScrubTimeout);
this.stopAndScrubTimeout = 0;
}
this.media.paused && this.media.play();
this.mousedown = false;
};
onProgress = (e: Event) => { onProgress = (e: Event) => {
this.setLoadProgress(); this.setLoadProgress();
}; };
private setLoadProgress() { protected scrub(e: MouseEvent) {
const scrubTime = super.scrub(e);
this.media.currentTime = scrubTime;
return scrubTime;
};
protected setLoadProgress() {
const buf = this.media.buffered; const buf = this.media.buffered;
const numRanges = buf.length; const numRanges = buf.length;
@ -127,7 +188,7 @@ export class MediaProgressLine {
this.filledLoad.style.transform = 'scaleX(' + percents + ')'; this.filledLoad.style.transform = 'scaleX(' + percents + ')';
} }
private setSeekMax() { protected setSeekMax() {
this.duration = this.media.duration; this.duration = this.media.duration;
if(this.duration > 0) { if(this.duration > 0) {
this.onLoadedData(); this.onLoadedData();
@ -136,7 +197,7 @@ export class MediaProgressLine {
} }
} }
private setProgress() { protected setProgress() {
const currentTime = this.media.currentTime; const currentTime = this.media.currentTime;
const scaleX = (currentTime / this.duration); const scaleX = (currentTime / this.duration);
@ -144,41 +205,21 @@ export class MediaProgressLine {
this.seek.value = '' + currentTime * 1000; this.seek.value = '' + currentTime * 1000;
} }
private setListeners() { protected setListeners() {
super.setListeners();
this.media.addEventListener('ended', this.onEnded); this.media.addEventListener('ended', this.onEnded);
this.media.addEventListener('play', this.onPlay); this.media.addEventListener('play', this.onPlay);
this.streamable && this.media.addEventListener('progress', this.onProgress); this.streamable && this.media.addEventListener('progress', this.onProgress);
this.container.addEventListener('mousemove', this.onMouseMove);
this.container.addEventListener('mousedown', this.onMouseDown);
this.container.addEventListener('mouseup', this.onMouseUp);
}
private scrub(e: MouseEvent) {
const scrubTime = e.offsetX / this.container.offsetWidth * this.duration;
this.media.currentTime = scrubTime;
if(this.onSeek) {
this.onSeek(scrubTime);
}
let scaleX = scrubTime / this.duration;
scaleX = Math.max(0, Math.min(1, scaleX));
this.filled.style.transform = 'scaleX(' + scaleX + ')';
} }
public removeListeners() { public removeListeners() {
super.removeListeners();
this.media.removeEventListener('loadeddata', this.onLoadedData); this.media.removeEventListener('loadeddata', this.onLoadedData);
this.media.removeEventListener('ended', this.onEnded); this.media.removeEventListener('ended', this.onEnded);
this.media.removeEventListener('play', this.onPlay); this.media.removeEventListener('play', this.onPlay);
this.streamable && this.media.removeEventListener('progress', this.onProgress); this.streamable && this.media.removeEventListener('progress', this.onProgress);
this.container.removeEventListener('mousemove', this.onMouseMove);
this.container.removeEventListener('mousedown', this.onMouseDown);
this.container.removeEventListener('mouseup', this.onMouseUp);
this.onSeek = null;
if(this.stopAndScrubTimeout) { if(this.stopAndScrubTimeout) {
clearTimeout(this.stopAndScrubTimeout); clearTimeout(this.stopAndScrubTimeout);
} }
@ -217,21 +258,19 @@ export default class VideoPlayer {
} }
private stylePlayer() { private stylePlayer() {
let player = this.wrapper; const {wrapper: player, video, skin} = this;
let video = this.video;
let skin = this.skin;
player.classList.add(skin); player.classList.add(skin);
let html = this.buildControls(); const html = this.buildControls();
player.insertAdjacentHTML('beforeend', html); player.insertAdjacentHTML('beforeend', html);
let updateInterval = 0; let updateInterval = 0;
let elapsed = 0; let elapsed = 0;
let prevTime = 0; let prevTime = 0;
if(skin === 'default') { if(skin === 'default') {
var toggle = player.querySelectorAll('.toggle') as NodeListOf<HTMLElement>; const toggle = player.querySelectorAll('.toggle') as NodeListOf<HTMLElement>;
var fullScreenButton = player.querySelector('.fullscreen') as HTMLElement; const fullScreenButton = player.querySelector('.fullscreen') as HTMLElement;
var timeElapsed = player.querySelector('#time-elapsed'); var timeElapsed = player.querySelector('#time-elapsed');
var timeDuration = player.querySelector('#time-duration') as HTMLElement; var timeDuration = player.querySelector('#time-duration') as HTMLElement;
timeDuration.innerHTML = String(video.duration | 0).toHHMMSS(); timeDuration.innerHTML = String(video.duration | 0).toHHMMSS();
@ -263,23 +302,20 @@ export default class VideoPlayer {
return this.toggleFullScreen(fullScreenButton); return this.toggleFullScreen(fullScreenButton);
}); });
let b = () => this.onFullScreen();
'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'.split(' ').forEach(eventName => { 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'.split(' ').forEach(eventName => {
player.addEventListener(eventName, b, false); player.addEventListener(eventName, this.onFullScreen, false);
}); });
} } else if(skin === 'circle') {
const wrapper = document.createElement('div');
if(skin === 'circle') {
let wrapper = document.createElement('div');
wrapper.classList.add('circle-time-left'); wrapper.classList.add('circle-time-left');
video.parentNode.insertBefore(wrapper, video); video.parentNode.insertBefore(wrapper, video);
wrapper.innerHTML = '<div class="circle-time"></div><div class="iconVolume tgico-nosound"></div>'; wrapper.innerHTML = '<div class="circle-time"></div><div class="iconVolume tgico-nosound"></div>';
var circle = player.querySelector('.progress-ring__circle') as SVGCircleElement; var circle = player.querySelector('.progress-ring__circle') as SVGCircleElement;
var radius = circle.r.baseVal.value; const radius = circle.r.baseVal.value;
var circumference = 2 * Math.PI * radius; var circumference = 2 * Math.PI * radius;
var timeDuration = player.querySelector('.circle-time') as HTMLElement; var timeDuration = player.querySelector('.circle-time') as HTMLElement;
var iconVolume = player.querySelector('.iconVolume') as HTMLDivElement; const iconVolume = player.querySelector('.iconVolume') as HTMLDivElement;
circle.style.strokeDasharray = circumference + ' ' + circumference; circle.style.strokeDasharray = circumference + ' ' + circumference;
circle.style.strokeDashoffset = '' + circumference; circle.style.strokeDashoffset = '' + circumference;
circle.addEventListener('click', () => { circle.addEventListener('click', () => {
@ -295,7 +331,7 @@ export default class VideoPlayer {
prevTime = video.currentTime; prevTime = video.currentTime;
} }
let offset = circumference - elapsed / video.duration * circumference; const offset = circumference - elapsed / video.duration * circumference;
circle.style.strokeDashoffset = '' + offset; circle.style.strokeDashoffset = '' + offset;
if(video.paused) clearInterval(updateInterval); if(video.paused) clearInterval(updateInterval);
}, 20); }, 20);
@ -339,8 +375,7 @@ export default class VideoPlayer {
} }
private handleProgress(timeDuration: HTMLElement, circumference: number, circle: SVGCircleElement, updateInterval: number) { private handleProgress(timeDuration: HTMLElement, circumference: number, circle: SVGCircleElement, updateInterval: number) {
let video = this.video; const {video, skin} = this;
let skin = this.skin;
clearInterval(updateInterval); clearInterval(updateInterval);
let elapsed = 0; let elapsed = 0;
@ -352,12 +387,13 @@ export default class VideoPlayer {
elapsed = video.currentTime; // Update if getCurrentTime was changed elapsed = video.currentTime; // Update if getCurrentTime was changed
prevTime = video.currentTime; prevTime = video.currentTime;
} }
let offset = circumference - elapsed / video.duration * circumference;
const offset = circumference - elapsed / video.duration * circumference;
circle.style.strokeDashoffset = '' + offset; circle.style.strokeDashoffset = '' + offset;
if(video.paused) clearInterval(updateInterval); if(video.paused) clearInterval(updateInterval);
}, 20); }, 20);
let timeLeft = String((video.duration - video.currentTime) | 0).toHHMMSS(); const timeLeft = String((video.duration - video.currentTime) | 0).toHHMMSS();
if(timeLeft != '0') timeDuration.innerHTML = timeLeft; if(timeLeft != '0') timeDuration.innerHTML = timeLeft;
return updateInterval; return updateInterval;
@ -365,22 +401,27 @@ export default class VideoPlayer {
} }
private buildControls() { private buildControls() {
let skin = this.skin; const skin = this.skin;
let html = []; const html: string[] = [];
if(skin === 'default') { if(skin === 'default') {
html.push('<button class="' + skin + '__button--big toggle tgico-largeplay" title="Toggle Play"></button>'); html.push(`
html.push('<div class="' + skin + '__gradient-bottom ckin__controls"></div>'); <button class="${skin}__button--big toggle tgico-largeplay" title="Toggle Play"></button>
html.push('<div class="' + skin + '__controls ckin__controls">'); <div class="${skin}__gradient-bottom ckin__controls"></div>
html.push('<div class="bottom-controls">', <div class="${skin}__controls ckin__controls">
'<div class="left-controls"><button class="' + skin + '__button toggle tgico-play" title="Toggle Video"></button>', <div class="bottom-controls">
'<div class="time">', <div class="left-controls">
'<time id="time-elapsed">0:00</time>', <button class="${skin}__button toggle tgico-play" title="Toggle Video"></button>
'<span> / </span>', <div class="time">
'<time id="time-duration">0:00</time>', <time id="time-elapsed">0:00</time>
'</div>', <span> / </span>
'</div>', <time id="time-duration">0:00</time>
'<div class="right-controls"><button class="' + skin + '__button fullscreen tgico-fullscreen" title="Full Screen"></button></div></div>'); </div>
html.push('</div>'); </div>
<div class="right-controls">
<button class="${skin}__button fullscreen tgico-fullscreen" title="Full Screen"></button>
</div>
</div>
</div>`);
} else if(skin === 'circle') { } else if(skin === 'circle') {
html.push('<svg class="progress-ring" width="200px" height="200px">', html.push('<svg class="progress-ring" width="200px" height="200px">',
'<circle class="progress-ring__circle" stroke="white" stroke-opacity="0.3" stroke-width="3.5" cx="100" cy="100" r="93" fill="transparent" transform="rotate(-90, 100, 100)"/>', '<circle class="progress-ring__circle" stroke="white" stroke-opacity="0.3" stroke-width="3.5" cx="100" cy="100" r="93" fill="transparent" transform="rotate(-90, 100, 100)"/>',
@ -391,7 +432,7 @@ export default class VideoPlayer {
} }
public updateButton(toggle: NodeListOf<HTMLElement>) { public updateButton(toggle: NodeListOf<HTMLElement>) {
let icon = this.video.paused ? 'tgico-play' : 'tgico-pause'; const icon = this.video.paused ? 'tgico-play' : 'tgico-pause';
Array.from(toggle).forEach((button) => { Array.from(toggle).forEach((button) => {
button.classList.remove('tgico-play', 'tgico-pause'); button.classList.remove('tgico-play', 'tgico-pause');
button.classList.add(icon); button.classList.add(icon);
@ -452,12 +493,12 @@ export default class VideoPlayer {
} }
} }
public onFullScreen() { onFullScreen = () => {
// @ts-ignore // @ts-ignore
let isFullscreenNow = document.webkitFullscreenElement !== null; const isFullscreenNow = document.webkitFullscreenElement !== null;
if(!isFullscreenNow) { if(!isFullscreenNow) {
this.wrapper.classList.remove('ckin__fullscreen'); this.wrapper.classList.remove('ckin__fullscreen');
} else { } else {
} }
} };
} }

591
src/lib/mtproto/mtproto.service.ts

@ -17,9 +17,9 @@ const ctx = self as any as ServiceWorkerGlobalScope;
//console.error('INCLUDE !!!', new Error().stack); //console.error('INCLUDE !!!', new Error().stack);
function isObject(object: any) { /* function isObject(object: any) {
return typeof(object) === 'object' && object !== null; return typeof(object) === 'object' && object !== null;
} } */
/* function fillTransfer(transfer: any, obj: any) { /* function fillTransfer(transfer: any, obj: any) {
if(!obj) return; if(!obj) return;
@ -79,70 +79,72 @@ networkerFactory.setUpdatesProcessor((obj, bool) => {
}); });
const onMessage = async(e: ExtendableMessageEvent) => { const onMessage = async(e: ExtendableMessageEvent) => {
const taskID = e.data.taskID; try {
const taskID = e.data.taskID;
log.debug('got message:', taskID, e, e.data);
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) {
case 'computeSRP':
case 'gzipUncompress':
// @ts-ignore
return cryptoWorker[e.data.task].apply(cryptoWorker, e.data.args).then(result => {
respond(e.source, {taskID: taskID, result: result});
});
case 'cancelDownload':
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}); log.debug('got message:', taskID, e, e.data);
} catch(err) {
respond(e.source, {taskID: taskID, error: err}); 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];
} }
} }
default: { switch(e.data.task) {
try { case 'computeSRP':
case 'gzipUncompress':
// @ts-ignore // @ts-ignore
let result = apiManager[e.data.task].apply(apiManager, e.data.args); return cryptoWorker[e.data.task].apply(cryptoWorker, e.data.args).then(result => {
respond(e.source, {taskID: taskID, result: result});
if(result instanceof Promise) { });
result = await result;
case 'cancelDownload':
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});
} }
respond(e.source, {taskID: taskID, result: result});
} catch(err) {
respond(e.source, {taskID: taskID, error: err});
} }
//throw new Error('Unknown task: ' + e.data.task); default: {
try {
// @ts-ignore
let result = apiManager[e.data.task].apply(apiManager, 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});
}
//throw new Error('Unknown task: ' + e.data.task);
}
} }
} catch(err) {
} }
}; };
ctx.onmessage = onMessage;
/** /**
* Service Worker Installation * Service Worker Installation
*/ */
@ -201,257 +203,274 @@ ctx.onerror = (error) => {
log.error('error:', error); log.error('error:', error);
}; };
const onFetch = (event: FetchEvent): void => { ctx.onunhandledrejection = (error) => {
const [, url, scope, params] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || []; log.error('onunhandledrejection:', error);
};
log.debug('[fetch]:', event);
switch(scope) {
case 'download':
case 'thumb':
case 'document':
case 'photo': {
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
const rangeHeader = event.request.headers.get('Range');
if(rangeHeader && info.mimeType && info.size) { // maybe safari
const range = parseRange(event.request.headers.get('Range'));
const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
if(possibleResponse) {
return event.respondWith(possibleResponse);
}
}
const fileName = getFileNameByLocation(info.location, {fileName: info.fileName});
/* event.request.signal.addEventListener('abort', (e) => {
console.log('[SW] user aborted request:', fileName);
cancellablePromise.cancel();
});
event.request.signal.onabort = (e) => {
console.log('[SW] user aborted request:', fileName);
cancellablePromise.cancel();
};
if(fileName == '5452060085729624717') {
setInterval(() => {
console.log('[SW] request status:', fileName, event.request.signal.aborted);
}, 1000);
} */
const cancellablePromise = apiFileManager.downloadFile(info);
cancellablePromise.notify = (progress: {done: number, total: number, offset: number}) => {
notify({progress: {fileName, ...progress}});
};
log.debug('[fetch] file:', /* info, */fileName);
const promise = cancellablePromise.then(b => {
const responseInit: ResponseInit = {};
if(rangeHeader) {
responseInit.headers = {
'Accept-Ranges': 'bytes',
'Content-Range': `bytes 0-${info.size - 1}/${info.size || '*'}`,
'Content-Length': `${info.size}`,
}
}
return new Response(b, responseInit);
});
event.respondWith(Promise.race([
timeout(45 * 1000),
promise
]));
break; const onChangeState = () => {
} ctx.onmessage = onMessage;
ctx.onfetch = onFetch;
};
case 'stream': { onChangeState();
const range = parseRange(event.request.headers.get('Range'));
const [offset, end] = range;
const info: DownloadOptions = JSON.parse(decodeURIComponent(params)); ctx.onoffline = ctx.ononline = onChangeState;
//const fileName = getFileNameByLocation(info.location);
log.debug('[stream]', url, offset, end); const onFetch = (event: FetchEvent): void => {
try {
const [, url, scope, params] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || [];
event.respondWith(Promise.race([ log.debug('[fetch]:', event);
timeout(45 * 1000),
new Promise<Response>((resolve, reject) => { switch(scope) {
// safari workaround case 'download':
case 'thumb':
case 'document':
case 'photo': {
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
const rangeHeader = event.request.headers.get('Range');
if(rangeHeader && info.mimeType && info.size) { // maybe safari
const range = parseRange(event.request.headers.get('Range'));
const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size); const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
if(possibleResponse) { if(possibleResponse) {
return resolve(possibleResponse); return event.respondWith(possibleResponse);
} }
}
const limit = end && end < STREAM_CHUNK_UPPER_LIMIT ? alignLimit(end - offset + 1) : STREAM_CHUNK_UPPER_LIMIT;
const alignedOffset = alignOffset(offset, limit); const fileName = getFileNameByLocation(info.location, {fileName: info.fileName});
//log.debug('[stream] requestFilePart:', info.dcID, info.location, alignedOffset, limit); /* event.request.signal.addEventListener('abort', (e) => {
console.log('[SW] user aborted request:', fileName);
apiFileManager.requestFilePart(info.dcID, info.location, alignedOffset, limit).then(result => { cancellablePromise.cancel();
let ab = result.bytes; });
//log.debug('[stream] requestFilePart result:', result); event.request.signal.onabort = (e) => {
console.log('[SW] user aborted request:', fileName);
const headers: Record<string, string> = { cancellablePromise.cancel();
};
if(fileName == '5452060085729624717') {
setInterval(() => {
console.log('[SW] request status:', fileName, event.request.signal.aborted);
}, 1000);
} */
const cancellablePromise = apiFileManager.downloadFile(info);
cancellablePromise.notify = (progress: {done: number, total: number, offset: number}) => {
notify({progress: {fileName, ...progress}});
};
log.debug('[fetch] file:', /* info, */fileName);
const promise = cancellablePromise.then(b => {
const responseInit: ResponseInit = {};
if(rangeHeader) {
responseInit.headers = {
'Accept-Ranges': 'bytes', 'Accept-Ranges': 'bytes',
'Content-Range': `bytes ${alignedOffset}-${alignedOffset + ab.byteLength - 1}/${info.size || '*'}`, 'Content-Range': `bytes 0-${info.size - 1}/${info.size || '*'}`,
'Content-Length': `${ab.byteLength}`, 'Content-Length': `${info.size}`,
};
if(info.mimeType) headers['Content-Type'] = info.mimeType;
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}`;
} }
}
resolve(new Response(ab, {
status: 206, return new Response(b, responseInit);
statusText: 'Partial Content', });
headers,
})); event.respondWith(Promise.race([
}); timeout(45 * 1000),
}) promise
])); ]));
break;
} break;
}
/* case 'download': {
const info: DownloadOptions = JSON.parse(decodeURIComponent(params)); case 'stream': {
const range = parseRange(event.request.headers.get('Range'));
const promise = new Promise<Response>((resolve) => { const [offset, end] = range;
const headers: Record<string, string> = {
'Content-Disposition': `attachment; filename="${info.fileName}"`, const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
}; //const fileName = getFileNameByLocation(info.location);
if(info.size) headers['Content-Length'] = info.size.toString(); log.debug('[stream]', url, offset, end);
if(info.mimeType) headers['Content-Type'] = info.mimeType;
event.respondWith(Promise.race([
log('[download] file:', info); timeout(45 * 1000),
new Promise<Response>((resolve, reject) => {
const stream = new ReadableStream({ // safari workaround
start(controller: ReadableStreamDefaultController) { const possibleResponse = responseForSafariFirstRange(range, info.mimeType, info.size);
const limitPart = DOWNLOAD_CHUNK_LIMIT; if(possibleResponse) {
return resolve(possibleResponse);
apiFileManager.downloadFile({ }
...info,
limitPart, const limit = end && end < STREAM_CHUNK_UPPER_LIMIT ? alignLimit(end - offset + 1) : STREAM_CHUNK_UPPER_LIMIT;
processPart: (bytes, offset) => { const alignedOffset = alignOffset(offset, limit);
log('[download] file processPart:', bytes, offset);
//log.debug('[stream] requestFilePart:', info.dcID, info.location, alignedOffset, limit);
controller.enqueue(new Uint8Array(bytes));
apiFileManager.requestFilePart(info.dcID, info.location, alignedOffset, limit).then(result => {
const isFinal = offset + limitPart >= info.size; let ab = result.bytes;
if(isFinal) {
controller.close(); //log.debug('[stream] requestFilePart result:', result);
}
const headers: Record<string, string> = {
return Promise.resolve(); 'Accept-Ranges': 'bytes',
'Content-Range': `bytes ${alignedOffset}-${alignedOffset + ab.byteLength - 1}/${info.size || '*'}`,
'Content-Length': `${ab.byteLength}`,
};
if(info.mimeType) headers['Content-Type'] = info.mimeType;
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}`;
} }
}).catch(err => {
log.error('[download] error:', err); resolve(new Response(ab, {
controller.error(err); status: 206,
statusText: 'Partial Content',
headers,
}));
}); });
}, })
]));
cancel() { break;
log.error('[download] file canceled:', info); }
}
/* case 'download': {
const info: DownloadOptions = JSON.parse(decodeURIComponent(params));
const promise = new Promise<Response>((resolve) => {
const headers: Record<string, string> = {
'Content-Disposition': `attachment; filename="${info.fileName}"`,
};
if(info.size) headers['Content-Length'] = info.size.toString();
if(info.mimeType) headers['Content-Type'] = info.mimeType;
log('[download] file:', info);
const stream = new ReadableStream({
start(controller: ReadableStreamDefaultController) {
const limitPart = DOWNLOAD_CHUNK_LIMIT;
apiFileManager.downloadFile({
...info,
limitPart,
processPart: (bytes, offset) => {
log('[download] file processPart:', bytes, offset);
controller.enqueue(new Uint8Array(bytes));
const isFinal = offset + limitPart >= info.size;
if(isFinal) {
controller.close();
}
return Promise.resolve();
}
}).catch(err => {
log.error('[download] error:', err);
controller.error(err);
});
},
cancel() {
log.error('[download] file canceled:', info);
}
});
resolve(new Response(stream, {headers}));
}); });
resolve(new Response(stream, {headers})); event.respondWith(promise);
});
break;
event.respondWith(promise); } */
break; case 'upload': {
} */ if(event.request.method == 'POST') {
event.respondWith(event.request.blob().then(blob => {
case 'upload': { return apiFileManager.uploadFile(blob).then(v => new Response(JSON.stringify(v), {headers: {'Content-Type': 'application/json'}}));
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;
} }
break; /* default: {
}
break;
/* default: { }
case 'documents':
break; case 'photos':
} case 'profiles':
case 'documents': // direct download
case 'photos': if (event.request.method === 'POST') {
case 'profiles': event.respondWith(// download(url, 'unknown file.txt', getFilePartRequest));
// direct download event.request.text()
if (event.request.method === 'POST') { .then((text) => {
event.respondWith(// download(url, 'unknown file.txt', getFilePartRequest)); const [, filename] = text.split('=');
event.request.text() return download(url, filename ? filename.toString() : 'unknown file', getFilePartRequest);
.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);
}), }),
]); );
}),
); // 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;
} }
break;
case 'stripped':
case 'stream': { case 'cached': {
const [offset, end] = parseRange(event.request.headers.get('Range') || ''); const bytes = getThumb(url) || null;
event.respondWith(new Response(bytes, { headers: { 'Content-Type': 'image/jpg' } }));
log('stream', url, offset, end); break;
}
event.respondWith(new Promise((resolve) => {
fetchStreamRequest(url, offset, end, resolve, getFilePartRequest); default:
})); if (url && url.endsWith('.tgs')) event.respondWith(fetchTGS(url));
break; else event.respondWith(fetch(event.request.url)); */
}
case 'stripped':
case 'cached': {
const bytes = getThumb(url) || null;
event.respondWith(new Response(bytes, { headers: { 'Content-Type': 'image/jpg' } }));
break;
} }
} catch(err) {
default: event.respondWith(new Response('', {
if (url && url.endsWith('.tgs')) event.respondWith(fetchTGS(url)); status: 500,
else event.respondWith(fetch(event.request.url)); */ statusText: 'Internal Server Error',
}));
} }
}; };
/**
* Fetch requests
*/
//ctx.addEventListener('fetch', );
ctx.onfetch = onFetch;
const DOWNLOAD_CHUNK_LIMIT = 512 * 1024; const DOWNLOAD_CHUNK_LIMIT = 512 * 1024;
//const STREAM_CHUNK_UPPER_LIMIT = 256 * 1024;
//const SMALLEST_CHUNK_LIMIT = 256 * 4; /* const STREAM_CHUNK_UPPER_LIMIT = 256 * 1024;
const STREAM_CHUNK_UPPER_LIMIT = 1024 * 1024; const SMALLEST_CHUNK_LIMIT = 256 * 4; */
const SMALLEST_CHUNK_LIMIT = 1024 * 4; /* const STREAM_CHUNK_UPPER_LIMIT = 1024 * 1024;
const SMALLEST_CHUNK_LIMIT = 1024 * 4; */
const STREAM_CHUNK_UPPER_LIMIT = 512 * 1024;
const SMALLEST_CHUNK_LIMIT = 512 * 4;
function parseRange(header: string): [number, number] { function parseRange(header: string): [number, number] {
if(!header) return [0, 0]; if(!header) return [0, 0];

10
src/lib/mtproto/mtprotoworker.ts

@ -81,10 +81,14 @@ class ApiManagerProxy extends CryptoWorkerMethods {
this.finalizeTask(e.data.taskID, e.data.result, e.data.error); this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
} }
}); });
navigator.serviceWorker.addEventListener('messageerror', (e) => {
this.log.error('SW messageerror:', e);
});
} }
private finalizeTask(taskID: number, result: any, error: any) { private finalizeTask(taskID: number, result: any, error: any) {
let 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); result === undefined ? deferred.reject(error) : deferred.resolve(result);
@ -113,10 +117,12 @@ class ApiManagerProxy extends CryptoWorkerMethods {
private releasePending() { private releasePending() {
if(navigator.serviceWorker.controller) { if(navigator.serviceWorker.controller) {
this.log.debug('releasing tasks, length:', this.pending.length);
this.pending.forEach(pending => { this.pending.forEach(pending => {
navigator.serviceWorker.controller.postMessage(pending); navigator.serviceWorker.controller.postMessage(pending);
}); });
this.log.debug('released tasks');
this.pending.length = 0; this.pending.length = 0;
} }
} }

Loading…
Cancel
Save