fast.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. type ProgressCallback = (nonce: number) => void;
  2. interface ProcessOptions {
  3. basePrefix: string;
  4. version: string;
  5. }
  6. const getHardwareConcurrency = () =>
  7. navigator.hardwareConcurrency !== undefined
  8. ? navigator.hardwareConcurrency
  9. : 1;
  10. export default function process(
  11. options: ProcessOptions,
  12. data: string,
  13. difficulty: number = 5,
  14. signal: AbortSignal | null = null,
  15. progressCallback?: ProgressCallback,
  16. threads: number = Math.trunc(Math.max(getHardwareConcurrency() / 2, 1)),
  17. ): Promise<string> {
  18. console.debug("fast algo");
  19. // Choose worker based on secure context.
  20. // Use the WebCrypto worker if the page is a secure context; otherwise fall back to pure‑JS.
  21. let workerMethod: "webcrypto" | "purejs" = "purejs";
  22. if (window.isSecureContext) {
  23. workerMethod = "webcrypto";
  24. }
  25. if (
  26. navigator.userAgent.includes("Firefox") ||
  27. navigator.userAgent.includes("Goanna")
  28. ) {
  29. console.log("Firefox detected, using pure-JS fallback");
  30. workerMethod = "purejs";
  31. }
  32. return new Promise((resolve, reject) => {
  33. let webWorkerURL = `${options.basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${options.version}`;
  34. const workers: Worker[] = [];
  35. let settled = false;
  36. const onAbort = () => {
  37. console.log("PoW aborted");
  38. cleanup();
  39. reject(new DOMException("Aborted", "AbortError"));
  40. };
  41. const cleanup = () => {
  42. if (settled) {
  43. return;
  44. }
  45. settled = true;
  46. workers.forEach((w) => w.terminate());
  47. if (signal != null) {
  48. signal.removeEventListener("abort", onAbort);
  49. }
  50. };
  51. if (signal != null) {
  52. if (signal.aborted) {
  53. return onAbort();
  54. }
  55. signal.addEventListener("abort", onAbort, { once: true });
  56. }
  57. for (let i = 0; i < threads; i++) {
  58. let worker = new Worker(webWorkerURL);
  59. worker.onmessage = (event) => {
  60. if (typeof event.data === "number") {
  61. progressCallback?.(event.data);
  62. } else {
  63. cleanup();
  64. resolve(event.data);
  65. }
  66. };
  67. worker.onerror = (event) => {
  68. cleanup();
  69. reject(event);
  70. };
  71. worker.postMessage({
  72. data,
  73. difficulty,
  74. nonce: i,
  75. threads,
  76. });
  77. workers.push(worker);
  78. }
  79. });
  80. }