system: move pow function and error overlay to system

type and move powEvent and its error overlay to system.ts
pull/72/head
OFF0 1 year ago
parent d654028a86
commit b44fe10870
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

@ -1,6 +1,6 @@
import {generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent} from 'nostr-tools';
import {generatePrivateKey, getPublicKey, nip19, signEvent} from 'nostr-tools';
import {zeroLeadingBitsCount} from './utils/crypto';
import {elem, elemCanvas, elemShrink, lockScroll, parseTextContent, unlockScroll} from './utils/dom';
import {elem, elemCanvas, elemShrink, parseTextContent} from './utils/dom';
import {bounce, dateTime, formatTime} from './utils/time';
import {getHost, getNoxyUrl, isWssUrl} from './utils/url';
import {sub24hFeed, subNote, subProfile} from './subscriptions'
@ -8,6 +8,7 @@ import {publish} from './relays';
import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt, validatePow} from './events';
import {clearView, getViewContent, getViewElem, setViewElem, view} from './view';
import {config} from './settings';
import {powEvent} from './system';
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
function onEvent(evt, relay) {
@ -835,92 +836,3 @@ profileForm.addEventListener('submit', async (e) => {
});
}
});
const errorOverlay = document.querySelector('#errorOverlay');
function promptError(error, options = {}) {
const {onAgain, onCancel} = 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') : '',
onAgain ? elem('button', {data: {action: 'again'}}, 'try again') : '',
]),
);
const handleOverlayClick = (e) => {
const button = e.target.closest('button');
if (button) {
switch(button.dataset.action) {
case 'close':
onCancel();
break;
case 'again':
onAgain();
break;
}
errorOverlay.removeEventListener('click', handleOverlayClick);
errorOverlay.hidden = true;
unlockScroll();
}
};
errorOverlay.addEventListener('click', handleOverlayClick);
errorOverlay.hidden = false;
}
/**
* 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'.
*/
function powEvent(evt, options) {
const {difficulty, statusElem, timeout} = options;
if (difficulty === 0) {
return Promise.resolve({
id: getEventHash(evt),
...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('canceled');
};
cancelBtn.addEventListener('click', onCancel);
worker.onmessage = (msg) => {
worker.terminate();
cancelBtn.removeEventListener('click', onCancel);
if (msg.data.error) {
promptError(msg.data.error, {
onCancel: () => reject('canceled'),
onAgain: 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});
});
}

@ -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…
Cancel
Save