system: move pow function and error overlay to system
type and move powEvent and its error overlay to system.tspull/72/head
parent
d654028a86
commit
b44fe10870
@ -0,0 +1,124 @@
|
||||
import {Event, getEventHash, UnsignedEvent} from 'nostr-tools';
|
||||
import {elem, lockScroll, unlockScroll} from './utils/dom';
|
||||
|
||||
const errorOverlay = document.querySelector('section#errorOverlay') as HTMLElement;
|
||||
|
||||
type PromptErrorOptions = {
|
||||
onCancel?: () => void;
|
||||
onRetry?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an error overlay, currently with hardcoded POW related message, this could be come a generic prompt
|
||||
* @param error message
|
||||
* @param options {onRetry, onCancel} callbacks
|
||||
*/
|
||||
const promptError = (
|
||||
error: string,
|
||||
options: PromptErrorOptions,
|
||||
) => {
|
||||
const {onCancel, onRetry} = options;
|
||||
lockScroll();
|
||||
errorOverlay.replaceChildren(
|
||||
elem('h1', {className: 'error-title'}, error),
|
||||
elem('p', {}, 'time ran out finding a proof with the desired mining difficulty. either try again, lower the mining difficulty or increase the timeout in profile settings.'),
|
||||
elem('div', {className: 'buttons'}, [
|
||||
onCancel ? elem('button', {data: {action: 'close'}}, 'close') : '',
|
||||
onRetry ? elem('button', {data: {action: 'again'}}, 'try again') : '',
|
||||
]),
|
||||
);
|
||||
const handleOverlayClick = (e: MouseEvent) => {
|
||||
if (e.target instanceof Element) {
|
||||
const button = e.target.closest('button');
|
||||
if (button) {
|
||||
switch(button.dataset.action) {
|
||||
case 'close':
|
||||
onCancel && onCancel();
|
||||
break;
|
||||
case 'again':
|
||||
onRetry && onRetry();
|
||||
break;
|
||||
}
|
||||
errorOverlay.removeEventListener('click', handleOverlayClick);
|
||||
errorOverlay.hidden = true;
|
||||
unlockScroll();
|
||||
}
|
||||
}
|
||||
};
|
||||
errorOverlay.addEventListener('click', handleOverlayClick);
|
||||
errorOverlay.hidden = false;
|
||||
}
|
||||
|
||||
type PowEventOptions = {
|
||||
difficulty: number;
|
||||
statusElem: HTMLElement;
|
||||
timeout: number;
|
||||
};
|
||||
|
||||
type WorkerResponse = {
|
||||
error: string;
|
||||
event: Event;
|
||||
};
|
||||
|
||||
type HashedEvent = UnsignedEvent & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* run proof of work in a worker until at least the specified difficulty.
|
||||
* if succcessful, the returned event contains the 'nonce' tag
|
||||
* and the updated created_at timestamp.
|
||||
*
|
||||
* powEvent returns a rejected promise if the funtion runs for longer than timeout.
|
||||
* a zero timeout makes mineEvent run without a time limit.
|
||||
* a zero mining target just resolves the promise without trying to find a 'nonce'.
|
||||
*/
|
||||
export const powEvent = (
|
||||
evt: UnsignedEvent,
|
||||
options: PowEventOptions
|
||||
): Promise<HashedEvent | void> => {
|
||||
const {difficulty, statusElem, timeout} = options;
|
||||
if (difficulty === 0) {
|
||||
return Promise.resolve({
|
||||
...evt,
|
||||
id: getEventHash(evt),
|
||||
});
|
||||
}
|
||||
const cancelBtn = elem('button', {className: 'btn-inline'}, [elem('small', {}, 'cancel')]);
|
||||
statusElem.replaceChildren('working…', cancelBtn);
|
||||
statusElem.hidden = false;
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker('./worker.js');
|
||||
|
||||
const onCancel = () => {
|
||||
worker.terminate();
|
||||
reject(`mining kind ${evt.kind} event canceled`);
|
||||
};
|
||||
cancelBtn.addEventListener('click', onCancel);
|
||||
|
||||
worker.onmessage = (msg: MessageEvent<WorkerResponse>) => {
|
||||
worker.terminate();
|
||||
cancelBtn.removeEventListener('click', onCancel);
|
||||
if (msg.data.error) {
|
||||
promptError(msg.data.error, {
|
||||
onCancel: () => reject(`mining kind ${evt.kind} event canceled`),
|
||||
onRetry: async () => {
|
||||
const result = await powEvent(evt, {difficulty, statusElem, timeout}).catch(console.warn);
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
resolve(msg.data.event);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (err) => {
|
||||
worker.terminate();
|
||||
// promptError(msg.data.error, {});
|
||||
cancelBtn.removeEventListener('click', onCancel);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
worker.postMessage({event: evt, difficulty, timeout});
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue