altcha 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -228,6 +228,74 @@ For simple implementations, the widget supports a subset of configuration option
228
228
  - **`timeout`**: Verification timeout in milliseconds (Default: `90_000`).
229
229
  - **`verifyFunction`**: A custom verification handler that overrides default network verification.
230
230
 
231
+ ## Algorithms
232
+
233
+ ALTCHA supports multiple proof-of-work algorithms. `PBKDF2/*` and `SHA-*` are bundled with the main widget by default. `Argon2` and `Scrypt` are memory-bound algorithms that resist hardware acceleration (ASICs/GPUs) but require importing their workers separately.
234
+
235
+ **Supported algorithms:**
236
+
237
+ - `PBKDF2/SHA-256` (default, bundled)
238
+ - `PBKDF2/SHA-384` (bundled)
239
+ - `PBKDF2/SHA-512` (bundled)
240
+ - `SHA-256` (bundled)
241
+ - `SHA-384` (bundled)
242
+ - `SHA-512` (bundled)
243
+ - `ARGON2ID` (requires separate worker import)
244
+ - `SCRYPT` (requires separate worker import)
245
+
246
+ If you use `Argon2` or `Scrypt`, import their workers and register them via the `$altcha.algorithms` global before the widget initializes.
247
+
248
+ ### Adding Argon2 / Scrypt Workers (Vite)
249
+
250
+ Works with both `altcha` and `altcha/external`:
251
+
252
+ ```ts
253
+ import 'altcha'; // or 'altcha/external'
254
+ import Argon2idWorker from 'altcha/workers/argon2id?worker';
255
+ import ScryptWorker from 'altcha/workers/scrypt?worker';
256
+
257
+ $altcha.algorithms.set('ARGON2ID', () => new Argon2idWorker());
258
+ $altcha.algorithms.set('SCRYPT', () => new ScryptWorker());
259
+ ```
260
+
261
+ ### Without Bundler Worker Import Support
262
+
263
+ If your environment does not support `?worker` imports, load the prebuilt worker files directly:
264
+
265
+ ```ts
266
+ import 'altcha';
267
+
268
+ $altcha.algorithms.set(
269
+ 'ARGON2ID',
270
+ () => new Worker('/path/to/node_modules/altcha/dist/workers/argon2id.js')
271
+ );
272
+ $altcha.algorithms.set(
273
+ 'SCRYPT',
274
+ () => new Worker('/path/to/node_modules/altcha/dist/workers/scrypt.js')
275
+ );
276
+ ```
277
+
278
+ ### Using `altcha/external`
279
+
280
+ `altcha/external` excludes all bundled workers, requiring you to register every algorithm explicitly. Use this for full control over which workers are loaded:
281
+
282
+ ```ts
283
+ import 'altcha/external';
284
+ import Argon2idWorker from 'altcha/workers/argon2id?worker';
285
+ import Pbkdf2Worker from 'altcha/workers/pbkdf2?worker';
286
+ import ScryptWorker from 'altcha/workers/scrypt?worker';
287
+ import ShaWorker from 'altcha/workers/sha?worker';
288
+
289
+ $altcha.algorithms.set('PBKDF2/SHA-256', () => new Pbkdf2Worker());
290
+ $altcha.algorithms.set('PBKDF2/SHA-384', () => new Pbkdf2Worker());
291
+ $altcha.algorithms.set('PBKDF2/SHA-512', () => new Pbkdf2Worker());
292
+ $altcha.algorithms.set('SHA-256', () => new ShaWorker());
293
+ $altcha.algorithms.set('SHA-384', () => new ShaWorker());
294
+ $altcha.algorithms.set('SHA-512', () => new ShaWorker());
295
+ $altcha.algorithms.set('ARGON2ID', () => new Argon2idWorker());
296
+ $altcha.algorithms.set('SCRYPT', () => new ScryptWorker());
297
+ ```
298
+
231
299
  ## Cookies
232
300
 
233
301
  By default, the widget sends the ALTCHA payload as a form field by creating a hidden input. It can also be configured to send the payload via a cookie.
@@ -5363,7 +5363,7 @@ function Code($$anchor, $$props) {
5363
5363
  button.disabled = get(audioState) === AudioState.LOADING || get(audioState) === AudioState.ERROR;
5364
5364
  set_attribute(button, "aria-label", get(audioState) === AudioState.LOADING ? strings().loading : strings().getAudioChallenge);
5365
5365
  });
5366
- delegated("click", button, () => onPlayAudio());
5366
+ event("click", button, () => onPlayAudio(), true);
5367
5367
  append($$anchor2, button);
5368
5368
  };
5369
5369
  if_block(node_1, ($$render) => {
@@ -5418,12 +5418,12 @@ function Code($$anchor, $$props) {
5418
5418
  event("submit", form, onSubmitCapture, true);
5419
5419
  delegated("keydown", input, onInputKeyDown);
5420
5420
  bind_value(input, () => get(code), ($$value) => set(code, $$value));
5421
- delegated("click", button_1, () => onReload()?.());
5422
- delegated("click", button_3, () => onCancel()?.());
5421
+ event("click", button_1, () => onReload()?.(), true);
5422
+ event("click", button_3, () => onCancel()?.(), true);
5423
5423
  append($$anchor, div);
5424
5424
  return pop($$exports);
5425
5425
  }
5426
- delegate(["keydown", "click"]);
5426
+ delegate(["keydown"]);
5427
5427
  create_custom_element(
5428
5428
  Code,
5429
5429
  {
@@ -5446,7 +5446,7 @@ var root_3$1 = /* @__PURE__ */ from_html(`<div role="button" class="altcha-popov
5446
5446
  var root$1 = /* @__PURE__ */ from_html(`<!> <div><!> <!> <div class="altcha-popover-content"><!></div></div>`, 1);
5447
5447
  function Popover($$anchor, $$props) {
5448
5448
  push($$props, true);
5449
- let anchor = prop($$props, "anchor"), children = prop($$props, "children"), display = prop($$props, "display", 7, "standard"), backdrop = prop($$props, "backdrop", 7, false), onClickOutside = prop($$props, "onClickOutside"), onClickOutsideDelay = prop($$props, "onClickOutsideDelay", 7, 600), onClose = prop($$props, "onClose"), placement = prop($$props, "placement", 7, "auto"), variant = prop($$props, "variant", 7, "neutral"), rest = /* @__PURE__ */ rest_props($$props, [
5449
+ let anchor = prop($$props, "anchor"), children = prop($$props, "children"), display = prop($$props, "display", 7, "standard"), backdrop = prop($$props, "backdrop", 7, false), onClickOutside = prop($$props, "onClickOutside"), onClickOutsideDelay = prop($$props, "onClickOutsideDelay", 7, 600), onClose = prop($$props, "onClose"), placement = prop($$props, "placement", 7, "auto"), updateUISignal = prop($$props, "updateUISignal"), variant = prop($$props, "variant", 7, "neutral"), rest = /* @__PURE__ */ rest_props($$props, [
5450
5450
  "$$slots",
5451
5451
  "$$events",
5452
5452
  "$$legacy",
@@ -5459,6 +5459,7 @@ function Popover($$anchor, $$props) {
5459
5459
  "onClickOutsideDelay",
5460
5460
  "onClose",
5461
5461
  "placement",
5462
+ "updateUISignal",
5462
5463
  "variant"
5463
5464
  ]);
5464
5465
  let el = /* @__PURE__ */ state(void 0);
@@ -5470,6 +5471,11 @@ function Popover($$anchor, $$props) {
5470
5471
  set(top, placement() === "top");
5471
5472
  }
5472
5473
  });
5474
+ user_effect(() => {
5475
+ if (updateUISignal()) {
5476
+ reposition();
5477
+ }
5478
+ });
5473
5479
  onMount(() => {
5474
5480
  const moveToBody = display() === "bottomsheet" || display() === "overlay";
5475
5481
  if (moveToBody) {
@@ -5492,7 +5498,7 @@ function Popover($$anchor, $$props) {
5492
5498
  }
5493
5499
  function onWindowClick(ev) {
5494
5500
  const target = ev.target;
5495
- if (!get(el)?.contains(target) && get(mountedAt) && get(mountedAt) + onClickOutsideDelay() < Date.now()) {
5501
+ if (!get(el)?.contains(target) && (!onClickOutsideDelay() || get(mountedAt) + onClickOutsideDelay() < Date.now())) {
5496
5502
  onClickOutside()?.();
5497
5503
  }
5498
5504
  }
@@ -5569,6 +5575,13 @@ function Popover($$anchor, $$props) {
5569
5575
  placement($$value);
5570
5576
  flushSync();
5571
5577
  },
5578
+ get updateUISignal() {
5579
+ return updateUISignal();
5580
+ },
5581
+ set updateUISignal($$value) {
5582
+ updateUISignal($$value);
5583
+ flushSync();
5584
+ },
5572
5585
  get variant() {
5573
5586
  return variant();
5574
5587
  },
@@ -5578,7 +5591,7 @@ function Popover($$anchor, $$props) {
5578
5591
  }
5579
5592
  };
5580
5593
  var fragment = root$1();
5581
- event("click", $window, onWindowClick);
5594
+ event("click", $window, onWindowClick, true);
5582
5595
  event("resize", $window, onWindowResize);
5583
5596
  event("scroll", $window, onWindowScroll);
5584
5597
  var node = first_child(fragment);
@@ -5615,7 +5628,7 @@ function Popover($$anchor, $$props) {
5615
5628
  {
5616
5629
  var consequent_2 = ($$anchor2) => {
5617
5630
  var div_3 = root_3$1();
5618
- delegated("click", div_3, onCloseClick);
5631
+ event("click", div_3, onCloseClick, true);
5619
5632
  append($$anchor2, div_3);
5620
5633
  };
5621
5634
  if_block(node_2, ($$render) => {
@@ -5631,7 +5644,6 @@ function Popover($$anchor, $$props) {
5631
5644
  append($$anchor, fragment);
5632
5645
  return pop($$exports);
5633
5646
  }
5634
- delegate(["click"]);
5635
5647
  create_custom_element(
5636
5648
  Popover,
5637
5649
  {
@@ -5643,6 +5655,7 @@ create_custom_element(
5643
5655
  onClickOutsideDelay: {},
5644
5656
  onClose: {},
5645
5657
  placement: {},
5658
+ updateUISignal: {},
5646
5659
  variant: {}
5647
5660
  },
5648
5661
  [],
@@ -5909,6 +5922,7 @@ function Widget($$anchor, $$props) {
5909
5922
  let checked = /* @__PURE__ */ state(false);
5910
5923
  let codeChallenge = /* @__PURE__ */ state(null);
5911
5924
  let currentController = /* @__PURE__ */ state(null);
5925
+ let currentDisplay = /* @__PURE__ */ state(null);
5912
5926
  let currentState = /* @__PURE__ */ state(proxy(State.UNVERIFIED));
5913
5927
  let elAnchorArrow = /* @__PURE__ */ state(void 0);
5914
5928
  let elFloatingAnchor = /* @__PURE__ */ state(void 0);
@@ -5919,6 +5933,7 @@ function Widget($$anchor, $$props) {
5919
5933
  let expirationTimeout = /* @__PURE__ */ state(null);
5920
5934
  let payload = /* @__PURE__ */ state(null);
5921
5935
  let plugins = /* @__PURE__ */ state(proxy([]));
5936
+ let updateUISignal = /* @__PURE__ */ state(0);
5922
5937
  let userConfig = /* @__PURE__ */ state(proxy({}));
5923
5938
  let visible = /* @__PURE__ */ state(true);
5924
5939
  const config = /* @__PURE__ */ user_derived(() => ({
@@ -5998,7 +6013,9 @@ function Widget($$anchor, $$props) {
5998
6013
  }
5999
6014
  });
6000
6015
  user_effect(() => {
6001
- setDisplay(get(config).display);
6016
+ if (get(currentDisplay) !== get(config).display) {
6017
+ setDisplay(get(config).display);
6018
+ }
6002
6019
  });
6003
6020
  user_effect(() => {
6004
6021
  if (get(checked) && get(currentState) === State.VERIFYING) {
@@ -6049,7 +6066,7 @@ function Widget($$anchor, $$props) {
6049
6066
  }
6050
6067
  });
6051
6068
  onMount(() => {
6052
- log("mounted", "3.0.4");
6069
+ log("mounted", "3.0.6");
6053
6070
  if (instance) {
6054
6071
  globalThis.$altcha.instances.add(instance);
6055
6072
  }
@@ -6300,9 +6317,6 @@ function Widget($$anchor, $$props) {
6300
6317
  }
6301
6318
  }
6302
6319
  function onCloseClick() {
6303
- if (get(currentController)) {
6304
- get(currentController).abort();
6305
- }
6306
6320
  setDisplay(get(config).display);
6307
6321
  reset$1();
6308
6322
  }
@@ -6321,9 +6335,6 @@ function Widget($$anchor, $$props) {
6321
6335
  }
6322
6336
  }
6323
6337
  function onFormReset() {
6324
- if (get(currentController)) {
6325
- get(currentController).abort();
6326
- }
6327
6338
  setDisplay(get(config).display);
6328
6339
  reset$1();
6329
6340
  }
@@ -6342,8 +6353,11 @@ function Widget($$anchor, $$props) {
6342
6353
  });
6343
6354
  }
6344
6355
  }
6345
- function onWindowPageshow() {
6346
- reset$1();
6356
+ function onWindowPageshow(ev) {
6357
+ if (ev.persisted) {
6358
+ setDisplay(get(config).display);
6359
+ reset$1();
6360
+ }
6347
6361
  }
6348
6362
  function onWindowResize() {
6349
6363
  updateUI();
@@ -6463,6 +6477,9 @@ function Widget($$anchor, $$props) {
6463
6477
  case "standard":
6464
6478
  show();
6465
6479
  }
6480
+ if (get(currentDisplay) !== value) {
6481
+ set(currentDisplay, value, true);
6482
+ }
6466
6483
  }
6467
6484
  function setChallengeExpiration(expiresAt) {
6468
6485
  if (get(expirationTimeout)) {
@@ -6561,6 +6578,9 @@ function Widget($$anchor, $$props) {
6561
6578
  set(checked, false);
6562
6579
  set(error, err, true);
6563
6580
  set(payload, null);
6581
+ if (get(currentController)) {
6582
+ get(currentController).abort();
6583
+ }
6564
6584
  if (get(expirationTimeout)) {
6565
6585
  clearTimeout(get(expirationTimeout));
6566
6586
  set(expirationTimeout, null);
@@ -6583,6 +6603,7 @@ function Widget($$anchor, $$props) {
6583
6603
  case "floating":
6584
6604
  return repositionFloating();
6585
6605
  }
6606
+ set(updateUISignal, get(updateUISignal) + 1);
6586
6607
  }
6587
6608
  async function verify(options = {}) {
6588
6609
  const {
@@ -6598,8 +6619,8 @@ function Widget($$anchor, $$props) {
6598
6619
  if (hook !== void 0) {
6599
6620
  return hook;
6600
6621
  }
6601
- set(currentController, controller, true);
6602
6622
  reset$1(State.VERIFYING);
6623
+ set(currentController, controller, true);
6603
6624
  try {
6604
6625
  if (!isSecureContext) {
6605
6626
  throw new Error("Secure context (HTTPS) required.");
@@ -6610,6 +6631,10 @@ function Widget($$anchor, $$props) {
6610
6631
  if (get(config).test) {
6611
6632
  log("running test mode with null challenge");
6612
6633
  await delay(Math.max(0, minDuration - (performance.now() - start)));
6634
+ if (get(currentController)?.signal.aborted) {
6635
+ reset$1();
6636
+ return null;
6637
+ }
6613
6638
  set(payload, btoa(JSON.stringify({ challenge: null, solution: null, test: true })), true);
6614
6639
  log("verified");
6615
6640
  setState(State.VERIFIED);
@@ -6740,7 +6765,7 @@ function Widget($$anchor, $$props) {
6740
6765
  if (get(config).overlayContent) $$render(consequent_1);
6741
6766
  });
6742
6767
  }
6743
- delegated("click", div_2, onCloseClick);
6768
+ event("click", div_2, onCloseClick, true);
6744
6769
  append($$anchor2, fragment_1);
6745
6770
  };
6746
6771
  if_block(node_1, ($$render) => {
@@ -6885,6 +6910,9 @@ function Widget($$anchor, $$props) {
6885
6910
  get dir() {
6886
6911
  return get(dir);
6887
6912
  },
6913
+ get updateUISignal() {
6914
+ return get(updateUISignal);
6915
+ },
6888
6916
  children: ($$anchor3, $$slotProps) => {
6889
6917
  var fragment_9 = comment();
6890
6918
  var node_10 = first_child(fragment_9);
@@ -6950,6 +6978,9 @@ function Widget($$anchor, $$props) {
6950
6978
  get dir() {
6951
6979
  return get(dir);
6952
6980
  },
6981
+ get updateUISignal() {
6982
+ return get(updateUISignal);
6983
+ },
6953
6984
  children: ($$anchor4, $$slotProps) => {
6954
6985
  var fragment_12 = root_19();
6955
6986
  var node_12 = first_child(fragment_12);
@@ -7021,7 +7052,6 @@ function Widget($$anchor, $$props) {
7021
7052
  $$cleanup();
7022
7053
  return $$pop;
7023
7054
  }
7024
- delegate(["click"]);
7025
7055
  if (typeof window !== "undefined" && window.customElements) customElements.define("altcha-widget", create_custom_element(
7026
7056
  Widget,
7027
7057
  {