@klauswuestefeld/jux-web 2026.2.2 → 2026.3.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.
@@ -0,0 +1,11 @@
1
+ declare global {
2
+ interface Window {
3
+ turnstile: {
4
+ render: (el: HTMLElement, opts: {
5
+ sitekey: string;
6
+ callback: (token: string) => void;
7
+ }) => void;
8
+ };
9
+ }
10
+ }
11
+ export declare const captchaChallenge: () => Promise<string>;
@@ -1,4 +1,4 @@
1
- export declare const chosenLanguage: any;
1
+ export declare let chosenLanguage: any;
2
2
  export declare const reloadWithLanguageOverride: (lang: string) => void;
3
3
  export declare const getTranslation: (k: string) => string;
4
4
  export declare const initLanguage: (translations_: any, defaultLanguage?: string) => void;
package/dist/jux-web.js CHANGED
@@ -1,9 +1,22 @@
1
1
  var juxWeb = (function (exports) {
2
2
  'use strict';
3
3
 
4
- let urlPrefix;
5
- const setUrlPrefix = (s) => urlPrefix = s;
6
- const getLocalStorageKey = (k) => urlPrefix ? `${urlPrefix}_${k}` : k;
4
+ let prefix;
5
+ const setLocalStoragePrefix = (s) => {
6
+ if (prefix !== undefined) {
7
+ throw 'prefix was already set to ' + prefix;
8
+ }
9
+ if (!s) {
10
+ throw 'prefix argument is ' + s + ', use empty string if you want no prefix.';
11
+ }
12
+ prefix = s;
13
+ };
14
+ const getLocalStorageKey = (k) => {
15
+ if (prefix === undefined)
16
+ setLocalStoragePrefix('');
17
+ return prefix.length === 0 ? k : `${prefix}_${k}`;
18
+ };
19
+ // TODO: export this to the frontEnd and use it.
7
20
  const getLocalStorageItem = (k) => localStorage.getItem(getLocalStorageKey(k));
8
21
  const setLocalStorageItem = (k, v) => localStorage.setItem(getLocalStorageKey(k), v);
9
22
  const removeLocalStorageItem = (k) => localStorage.removeItem(getLocalStorageKey(k));
@@ -16,7 +29,7 @@ var juxWeb = (function (exports) {
16
29
  };
17
30
  const OVERRIDE_KEY = 'jux.language.override';
18
31
  let translations = {};
19
- const chosenLanguage = getLocalStorageItem(OVERRIDE_KEY) || getBrowserLanguage();
32
+ exports.chosenLanguage = getBrowserLanguage();
20
33
  const reloadWithLanguageOverride = (lang) => {
21
34
  setLocalStorageItem(OVERRIDE_KEY, lang);
22
35
  location.reload();
@@ -24,9 +37,10 @@ var juxWeb = (function (exports) {
24
37
  // @ts-ignore
25
38
  const getTranslation = (k) => translations[k] || k;
26
39
  const initLanguage = (translations_, defaultLanguage = 'en') => {
27
- translations = translations_[chosenLanguage] || translations_[defaultLanguage];
40
+ exports.chosenLanguage = getLocalStorageItem(OVERRIDE_KEY) || getBrowserLanguage();
41
+ translations = translations_[exports.chosenLanguage] || translations_[defaultLanguage];
28
42
  document.title = getTranslation('title');
29
- document.documentElement.lang = translations_[chosenLanguage] ? chosenLanguage : defaultLanguage;
43
+ document.documentElement.lang = translations_[exports.chosenLanguage] ? exports.chosenLanguage : defaultLanguage;
30
44
  };
31
45
 
32
46
  const THRESHOLD_WIDTH = 640;
@@ -443,6 +457,97 @@ var juxWeb = (function (exports) {
443
457
  return token;
444
458
  };
445
459
 
460
+ const createDisableBackground = () => {
461
+ const disableBackground = document.createElement('disable-background');
462
+ document.body.appendChild(disableBackground);
463
+ return disableBackground;
464
+ };
465
+ const reenableBela = (disableBackground) => {
466
+ disableBackground.remove();
467
+ };
468
+ const ensureTurnstileScript = () => new Promise((resolve, reject) => {
469
+ const existing = document.querySelector('script[data-turnstile-script]');
470
+ const onReady = () => (typeof window.turnstile !== 'undefined') ? resolve() : reject(new Error('Turnstile not available'));
471
+ if (existing) {
472
+ if (typeof window.turnstile !== 'undefined')
473
+ return resolve();
474
+ existing.addEventListener('load', onReady, { once: true });
475
+ existing.addEventListener('error', () => reject(new Error('Failed to load Turnstile')), { once: true });
476
+ return;
477
+ }
478
+ const script = document.createElement('script');
479
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
480
+ script.async = true;
481
+ script.defer = true;
482
+ script.dataset.turnstileScript = 'true';
483
+ script.addEventListener('load', onReady, { once: true });
484
+ script.addEventListener('error', () => reject(new Error('Failed to load Turnstile')), { once: true });
485
+ document.head.appendChild(script);
486
+ setTimeout(() => reject(new Error('Turnstile load timeout')), 15000);
487
+ });
488
+ let challengeInFlight = null;
489
+ const captchaChallenge = () => {
490
+ console.log('challange');
491
+ if (challengeInFlight)
492
+ return challengeInFlight;
493
+ challengeInFlight = new Promise((resolve, reject) => __awaiter$2(void 0, void 0, void 0, function* () {
494
+ const disableBackground = createDisableBackground();
495
+ let overlay = null;
496
+ const cleanup = () => {
497
+ try {
498
+ overlay === null || overlay === void 0 ? void 0 : overlay.remove();
499
+ }
500
+ catch (_a) { }
501
+ try {
502
+ reenableBela(disableBackground);
503
+ }
504
+ catch (_b) { }
505
+ challengeInFlight = null;
506
+ };
507
+ try {
508
+ yield ensureTurnstileScript();
509
+ overlay = document.createElement('captcha-overlay');
510
+ const container = document.createElement('turnstile-container');
511
+ container.id = 'cf-turnstile-container';
512
+ overlay.appendChild(container);
513
+ document.body.appendChild(overlay);
514
+ overlay.style.zIndex = '100';
515
+ overlay.style.width = '100%';
516
+ overlay.style.display = 'flex';
517
+ overlay.style.alignItems = 'center';
518
+ overlay.style.justifyContent = 'center';
519
+ window.turnstile.render(container, {
520
+ sitekey: '0x4AAAAAAB1GW9W6TaEdd7vi',
521
+ callback: (token) => {
522
+ console.log("turnstile token", token);
523
+ backendGet('validate-turnstile', { 'turnstile-token': token }, (res) => {
524
+ console.log(res);
525
+ const sessionToken = res === null || res === void 0 ? void 0 : res['session-token'];
526
+ if (sessionToken) {
527
+ try {
528
+ localStorage.setItem('session-token', String(sessionToken));
529
+ }
530
+ catch (_a) { }
531
+ }
532
+ cleanup();
533
+ resolve(token);
534
+ }, (err) => {
535
+ cleanup();
536
+ reject(err);
537
+ });
538
+ console.log("backendGet called");
539
+ }
540
+ });
541
+ }
542
+ catch (e) {
543
+ cleanup();
544
+ reject(e);
545
+ console.error("backendGet threw", e);
546
+ }
547
+ }));
548
+ return challengeInFlight;
549
+ };
550
+
446
551
  const setBackendUrl = (backendUrl) => setLocalStorageItem('backend-url', backendUrl);
447
552
  //@ts-ignore
448
553
  const getBackendUrl = () => process.env.BACKEND_URL || getLocalStorageItem('backend-url');
@@ -549,7 +654,7 @@ var juxWeb = (function (exports) {
549
654
  }
550
655
  };
551
656
  const backendRequest = (url_2, ...args_2) => __awaiter$2(void 0, [url_2, ...args_2], void 0, function* (url, options = {}, onSuccess, onError, onRedirect, requestType = 'query', handleUnauthorized = defaultHandleUnauthorized, retrial = false, retrialNum = 0, extras) {
552
- var _a;
657
+ var _a, _b;
553
658
  if (requestRunning && !retrial) { // only allow one request at a time
554
659
  setTimeout(() => backendRequest(url, options, onSuccess, onError, onRedirect, requestType, handleUnauthorized, false, retrialNum), 300);
555
660
  return;
@@ -630,6 +735,17 @@ var juxWeb = (function (exports) {
630
735
  handleUnauthorized(response);
631
736
  return;
632
737
  }
738
+ if (response.status === 429) {
739
+ try {
740
+ const token = yield captchaChallenge(); // shows overlay, disables background, validates, cleans up
741
+ return backendRequest(url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, options.headers), { 'cf-turnstile-token': token, 'cf-session-token': (_b = localStorage.getItem('session-token')) !== null && _b !== void 0 ? _b : '' }) }), onSuccess, onError, onRedirect, requestType, handleUnauthorized, true, retrialNum, extras);
742
+ }
743
+ catch (e) {
744
+ onError === null || onError === void 0 ? void 0 : onError({ error: 'Captcha challenge failed', detail: e });
745
+ requestRunning = false;
746
+ return;
747
+ }
748
+ }
633
749
  try {
634
750
  let jsonResponse = {};
635
751
  try {
@@ -14825,7 +14941,7 @@ var juxWeb = (function (exports) {
14825
14941
  const onError = () => onAuthenticationFailure('login-failed');
14826
14942
  getSSOAuthorizationEndpoint(onSuccess, onError);
14827
14943
  };
14828
- const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, urlPrefix }) => {
14944
+ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, }) => {
14829
14945
  // if (startNewDemo()) {
14830
14946
  // initDemo(setBackendToken, onAuthentication);
14831
14947
  // return;
@@ -14835,8 +14951,9 @@ var juxWeb = (function (exports) {
14835
14951
  window.juxWebGlobal.fetchUserBackendUrl = fetchUserBackendUrl;
14836
14952
  window.juxWebGlobal.magicLinkRequestEndpoint = magicLinkRequestEndpoint;
14837
14953
  window.juxWebGlobal.magicLinkAuthEndpoint = magicLinkAuthEndpoint;
14838
- if (urlPrefix)
14839
- setUrlPrefix(urlPrefix);
14954
+ const supportedLoginTypesArray = Array.isArray(supportedLoginTypes)
14955
+ ? supportedLoginTypes
14956
+ : [supportedLoginTypes];
14840
14957
  const signInToken = extractTokenFromWindowLocation('sign-in') || extractTokenFromWindowLocation('magic-link');
14841
14958
  if (signInToken) {
14842
14959
  const onOpenMagicLink = () => {
@@ -14870,9 +14987,6 @@ var juxWeb = (function (exports) {
14870
14987
  }));
14871
14988
  return;
14872
14989
  }
14873
- const supportedLoginTypesArray = Array.isArray(supportedLoginTypes)
14874
- ? supportedLoginTypes
14875
- : [supportedLoginTypes];
14876
14990
  displayPage(clientApp, loginPage(clientApp, backgroundImage, onUserLogin, onLoginError, supportedLoginTypesArray, handleSSOLogin, { onAuthPasswordLogin, onGoogleSignIn, onMicrosoftSignIn, handleMagicLinkRequest }));
14877
14991
  // validateThirdPartyCookies(initGapi, () => displayPage(Page.LOGIN));
14878
14992
  // checkMixpanel(() => mixpanel.track('View Login Page'));
@@ -14986,7 +15100,6 @@ var juxWeb = (function (exports) {
14986
15100
  element.dispatchEvent(new JuxEvent('command', 'POST', endpoint, onResult, params, onError, onRedirect));
14987
15101
  };
14988
15102
 
14989
- exports.chosenLanguage = chosenLanguage;
14990
15103
  exports.clearSession = clearSession;
14991
15104
  exports.command = command;
14992
15105
  exports.getCurrentUser = getCurrentUser;
@@ -15003,6 +15116,7 @@ var juxWeb = (function (exports) {
15003
15116
  exports.reloadWithLanguageOverride = reloadWithLanguageOverride;
15004
15117
  exports.requestMagicLink = requestMagicLink;
15005
15118
  exports.resetJuxWebGlobal = resetJuxWebGlobal;
15119
+ exports.setLocalStoragePrefix = setLocalStoragePrefix;
15006
15120
  exports.upload = upload;
15007
15121
 
15008
15122
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -1,4 +1,4 @@
1
- export declare const setUrlPrefix: (s: string) => string;
1
+ export declare const setLocalStoragePrefix: (s: string) => void;
2
2
  export declare const getLocalStorageItem: (k: string) => string | null;
3
3
  export declare const setLocalStorageItem: (k: string, v: string) => void;
4
4
  export declare const removeLocalStorageItem: (k: string) => void;
@@ -7,9 +7,8 @@ interface InitSessionParams {
7
7
  fetchUserBackendUrl: any;
8
8
  magicLinkRequestEndpoint?: string;
9
9
  magicLinkAuthEndpoint?: string;
10
- urlPrefix: string;
11
10
  }
12
- export declare const initSession: ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, urlPrefix }: InitSessionParams) => void;
11
+ export declare const initSession: ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, }: InitSessionParams) => void;
13
12
  export declare const onMicrosoftSignIn: (onUserLogin: any) => Promise<void>;
14
13
  export declare const onGoogleSignIn: (onUserLogin: any) => void;
15
14
  export declare const onAuthPasswordLogin: (credentials: any, onUserLogin: any, onLoginError: any, onReturn: any, clientApp: HTMLElement, backgroundImg: string) => void;
package/dist/main.d.ts CHANGED
@@ -7,6 +7,7 @@ export { magicLinkModal } from './login/magic-link-modal';
7
7
  export { clearSession } from './login/session-actions';
8
8
  export { command, query } from './jux/jux-event';
9
9
  export { getCurrentUser, resetJuxWebGlobal } from './jux/jux-web-global/utils';
10
+ export { setLocalStoragePrefix } from './local-storage/utils';
10
11
  declare global {
11
12
  interface Window {
12
13
  submitCaptcha: any;
package/dist/main.esm.js CHANGED
@@ -1,8 +1,21 @@
1
1
  import * as msal from '@azure/msal-browser';
2
2
 
3
- let urlPrefix;
4
- const setUrlPrefix = (s) => urlPrefix = s;
5
- const getLocalStorageKey = (k) => urlPrefix ? `${urlPrefix}_${k}` : k;
3
+ let prefix;
4
+ const setLocalStoragePrefix = (s) => {
5
+ if (prefix !== undefined) {
6
+ throw 'prefix was already set to ' + prefix;
7
+ }
8
+ if (!s) {
9
+ throw 'prefix argument is ' + s + ', use empty string if you want no prefix.';
10
+ }
11
+ prefix = s;
12
+ };
13
+ const getLocalStorageKey = (k) => {
14
+ if (prefix === undefined)
15
+ setLocalStoragePrefix('');
16
+ return prefix.length === 0 ? k : `${prefix}_${k}`;
17
+ };
18
+ // TODO: export this to the frontEnd and use it.
6
19
  const getLocalStorageItem = (k) => localStorage.getItem(getLocalStorageKey(k));
7
20
  const setLocalStorageItem = (k, v) => localStorage.setItem(getLocalStorageKey(k), v);
8
21
  const removeLocalStorageItem = (k) => localStorage.removeItem(getLocalStorageKey(k));
@@ -15,7 +28,7 @@ const getBrowserLanguage = () => {
15
28
  };
16
29
  const OVERRIDE_KEY = 'jux.language.override';
17
30
  let translations = {};
18
- const chosenLanguage = getLocalStorageItem(OVERRIDE_KEY) || getBrowserLanguage();
31
+ let chosenLanguage = getBrowserLanguage();
19
32
  const reloadWithLanguageOverride = (lang) => {
20
33
  setLocalStorageItem(OVERRIDE_KEY, lang);
21
34
  location.reload();
@@ -23,6 +36,7 @@ const reloadWithLanguageOverride = (lang) => {
23
36
  // @ts-ignore
24
37
  const getTranslation = (k) => translations[k] || k;
25
38
  const initLanguage = (translations_, defaultLanguage = 'en') => {
39
+ chosenLanguage = getLocalStorageItem(OVERRIDE_KEY) || getBrowserLanguage();
26
40
  translations = translations_[chosenLanguage] || translations_[defaultLanguage];
27
41
  document.title = getTranslation('title');
28
42
  document.documentElement.lang = translations_[chosenLanguage] ? chosenLanguage : defaultLanguage;
@@ -442,6 +456,97 @@ const extractTokenFromWindowLocation = (tokenParam, additionalParam = '') => {
442
456
  return token;
443
457
  };
444
458
 
459
+ const createDisableBackground = () => {
460
+ const disableBackground = document.createElement('disable-background');
461
+ document.body.appendChild(disableBackground);
462
+ return disableBackground;
463
+ };
464
+ const reenableBela = (disableBackground) => {
465
+ disableBackground.remove();
466
+ };
467
+ const ensureTurnstileScript = () => new Promise((resolve, reject) => {
468
+ const existing = document.querySelector('script[data-turnstile-script]');
469
+ const onReady = () => (typeof window.turnstile !== 'undefined') ? resolve() : reject(new Error('Turnstile not available'));
470
+ if (existing) {
471
+ if (typeof window.turnstile !== 'undefined')
472
+ return resolve();
473
+ existing.addEventListener('load', onReady, { once: true });
474
+ existing.addEventListener('error', () => reject(new Error('Failed to load Turnstile')), { once: true });
475
+ return;
476
+ }
477
+ const script = document.createElement('script');
478
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
479
+ script.async = true;
480
+ script.defer = true;
481
+ script.dataset.turnstileScript = 'true';
482
+ script.addEventListener('load', onReady, { once: true });
483
+ script.addEventListener('error', () => reject(new Error('Failed to load Turnstile')), { once: true });
484
+ document.head.appendChild(script);
485
+ setTimeout(() => reject(new Error('Turnstile load timeout')), 15000);
486
+ });
487
+ let challengeInFlight = null;
488
+ const captchaChallenge = () => {
489
+ console.log('challange');
490
+ if (challengeInFlight)
491
+ return challengeInFlight;
492
+ challengeInFlight = new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
493
+ const disableBackground = createDisableBackground();
494
+ let overlay = null;
495
+ const cleanup = () => {
496
+ try {
497
+ overlay === null || overlay === void 0 ? void 0 : overlay.remove();
498
+ }
499
+ catch (_a) { }
500
+ try {
501
+ reenableBela(disableBackground);
502
+ }
503
+ catch (_b) { }
504
+ challengeInFlight = null;
505
+ };
506
+ try {
507
+ yield ensureTurnstileScript();
508
+ overlay = document.createElement('captcha-overlay');
509
+ const container = document.createElement('turnstile-container');
510
+ container.id = 'cf-turnstile-container';
511
+ overlay.appendChild(container);
512
+ document.body.appendChild(overlay);
513
+ overlay.style.zIndex = '100';
514
+ overlay.style.width = '100%';
515
+ overlay.style.display = 'flex';
516
+ overlay.style.alignItems = 'center';
517
+ overlay.style.justifyContent = 'center';
518
+ window.turnstile.render(container, {
519
+ sitekey: '0x4AAAAAAB1GW9W6TaEdd7vi',
520
+ callback: (token) => {
521
+ console.log("turnstile token", token);
522
+ backendGet('validate-turnstile', { 'turnstile-token': token }, (res) => {
523
+ console.log(res);
524
+ const sessionToken = res === null || res === void 0 ? void 0 : res['session-token'];
525
+ if (sessionToken) {
526
+ try {
527
+ localStorage.setItem('session-token', String(sessionToken));
528
+ }
529
+ catch (_a) { }
530
+ }
531
+ cleanup();
532
+ resolve(token);
533
+ }, (err) => {
534
+ cleanup();
535
+ reject(err);
536
+ });
537
+ console.log("backendGet called");
538
+ }
539
+ });
540
+ }
541
+ catch (e) {
542
+ cleanup();
543
+ reject(e);
544
+ console.error("backendGet threw", e);
545
+ }
546
+ }));
547
+ return challengeInFlight;
548
+ };
549
+
445
550
  const setBackendUrl = (backendUrl) => setLocalStorageItem('backend-url', backendUrl);
446
551
  //@ts-ignore
447
552
  const getBackendUrl = () => process.env.BACKEND_URL || getLocalStorageItem('backend-url');
@@ -548,7 +653,7 @@ const uploadRequest = (url, file, onSuccess, onError, extras) => {
548
653
  }
549
654
  };
550
655
  const backendRequest = (url_2, ...args_2) => __awaiter(void 0, [url_2, ...args_2], void 0, function* (url, options = {}, onSuccess, onError, onRedirect, requestType = 'query', handleUnauthorized = defaultHandleUnauthorized, retrial = false, retrialNum = 0, extras) {
551
- var _a;
656
+ var _a, _b;
552
657
  if (requestRunning && !retrial) { // only allow one request at a time
553
658
  setTimeout(() => backendRequest(url, options, onSuccess, onError, onRedirect, requestType, handleUnauthorized, false, retrialNum), 300);
554
659
  return;
@@ -629,6 +734,17 @@ const backendRequest = (url_2, ...args_2) => __awaiter(void 0, [url_2, ...args_2
629
734
  handleUnauthorized(response);
630
735
  return;
631
736
  }
737
+ if (response.status === 429) {
738
+ try {
739
+ const token = yield captchaChallenge(); // shows overlay, disables background, validates, cleans up
740
+ return backendRequest(url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, options.headers), { 'cf-turnstile-token': token, 'cf-session-token': (_b = localStorage.getItem('session-token')) !== null && _b !== void 0 ? _b : '' }) }), onSuccess, onError, onRedirect, requestType, handleUnauthorized, true, retrialNum, extras);
741
+ }
742
+ catch (e) {
743
+ onError === null || onError === void 0 ? void 0 : onError({ error: 'Captcha challenge failed', detail: e });
744
+ requestRunning = false;
745
+ return;
746
+ }
747
+ }
632
748
  try {
633
749
  let jsonResponse = {};
634
750
  try {
@@ -1505,7 +1621,7 @@ const handleSSOLogin = () => {
1505
1621
  const onError = () => onAuthenticationFailure('login-failed');
1506
1622
  getSSOAuthorizationEndpoint(onSuccess, onError);
1507
1623
  };
1508
- const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, urlPrefix }) => {
1624
+ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, }) => {
1509
1625
  // if (startNewDemo()) {
1510
1626
  // initDemo(setBackendToken, onAuthentication);
1511
1627
  // return;
@@ -1515,8 +1631,9 @@ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError
1515
1631
  window.juxWebGlobal.fetchUserBackendUrl = fetchUserBackendUrl;
1516
1632
  window.juxWebGlobal.magicLinkRequestEndpoint = magicLinkRequestEndpoint;
1517
1633
  window.juxWebGlobal.magicLinkAuthEndpoint = magicLinkAuthEndpoint;
1518
- if (urlPrefix)
1519
- setUrlPrefix(urlPrefix);
1634
+ const supportedLoginTypesArray = Array.isArray(supportedLoginTypes)
1635
+ ? supportedLoginTypes
1636
+ : [supportedLoginTypes];
1520
1637
  const signInToken = extractTokenFromWindowLocation('sign-in') || extractTokenFromWindowLocation('magic-link');
1521
1638
  if (signInToken) {
1522
1639
  const onOpenMagicLink = () => {
@@ -1550,9 +1667,6 @@ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError
1550
1667
  }));
1551
1668
  return;
1552
1669
  }
1553
- const supportedLoginTypesArray = Array.isArray(supportedLoginTypes)
1554
- ? supportedLoginTypes
1555
- : [supportedLoginTypes];
1556
1670
  displayPage(clientApp, loginPage(clientApp, backgroundImage, onUserLogin, onLoginError, supportedLoginTypesArray, handleSSOLogin, { onAuthPasswordLogin, onGoogleSignIn, onMicrosoftSignIn, handleMagicLinkRequest }));
1557
1671
  // validateThirdPartyCookies(initGapi, () => displayPage(Page.LOGIN));
1558
1672
  // checkMixpanel(() => mixpanel.track('View Login Page'));
@@ -1666,4 +1780,4 @@ const command = (element, endpoint, onResult, params, onError, onRedirect) => {
1666
1780
  element.dispatchEvent(new JuxEvent('command', 'POST', endpoint, onResult, params, onError, onRedirect));
1667
1781
  };
1668
1782
 
1669
- export { chosenLanguage, clearSession, command, getCurrentUser, getTranslation, handleJuxEvent, handleMagicLinkRequest, initLanguage, initSession, loginButton, loginPage, magicLinkModal, onMicrosoftSignIn, query, reloadWithLanguageOverride, requestMagicLink, resetJuxWebGlobal, upload };
1783
+ export { chosenLanguage, clearSession, command, getCurrentUser, getTranslation, handleJuxEvent, handleMagicLinkRequest, initLanguage, initSession, loginButton, loginPage, magicLinkModal, onMicrosoftSignIn, query, reloadWithLanguageOverride, requestMagicLink, resetJuxWebGlobal, setLocalStoragePrefix, upload };
package/dist/main.js CHANGED
@@ -24,9 +24,22 @@ function _interopNamespace(e) {
24
24
 
25
25
  var msal__namespace = /*#__PURE__*/_interopNamespace(msal);
26
26
 
27
- let urlPrefix;
28
- const setUrlPrefix = (s) => urlPrefix = s;
29
- const getLocalStorageKey = (k) => urlPrefix ? `${urlPrefix}_${k}` : k;
27
+ let prefix;
28
+ const setLocalStoragePrefix = (s) => {
29
+ if (prefix !== undefined) {
30
+ throw 'prefix was already set to ' + prefix;
31
+ }
32
+ if (!s) {
33
+ throw 'prefix argument is ' + s + ', use empty string if you want no prefix.';
34
+ }
35
+ prefix = s;
36
+ };
37
+ const getLocalStorageKey = (k) => {
38
+ if (prefix === undefined)
39
+ setLocalStoragePrefix('');
40
+ return prefix.length === 0 ? k : `${prefix}_${k}`;
41
+ };
42
+ // TODO: export this to the frontEnd and use it.
30
43
  const getLocalStorageItem = (k) => localStorage.getItem(getLocalStorageKey(k));
31
44
  const setLocalStorageItem = (k, v) => localStorage.setItem(getLocalStorageKey(k), v);
32
45
  const removeLocalStorageItem = (k) => localStorage.removeItem(getLocalStorageKey(k));
@@ -39,7 +52,7 @@ const getBrowserLanguage = () => {
39
52
  };
40
53
  const OVERRIDE_KEY = 'jux.language.override';
41
54
  let translations = {};
42
- const chosenLanguage = getLocalStorageItem(OVERRIDE_KEY) || getBrowserLanguage();
55
+ exports.chosenLanguage = getBrowserLanguage();
43
56
  const reloadWithLanguageOverride = (lang) => {
44
57
  setLocalStorageItem(OVERRIDE_KEY, lang);
45
58
  location.reload();
@@ -47,9 +60,10 @@ const reloadWithLanguageOverride = (lang) => {
47
60
  // @ts-ignore
48
61
  const getTranslation = (k) => translations[k] || k;
49
62
  const initLanguage = (translations_, defaultLanguage = 'en') => {
50
- translations = translations_[chosenLanguage] || translations_[defaultLanguage];
63
+ exports.chosenLanguage = getLocalStorageItem(OVERRIDE_KEY) || getBrowserLanguage();
64
+ translations = translations_[exports.chosenLanguage] || translations_[defaultLanguage];
51
65
  document.title = getTranslation('title');
52
- document.documentElement.lang = translations_[chosenLanguage] ? chosenLanguage : defaultLanguage;
66
+ document.documentElement.lang = translations_[exports.chosenLanguage] ? exports.chosenLanguage : defaultLanguage;
53
67
  };
54
68
 
55
69
  const THRESHOLD_WIDTH = 640;
@@ -466,6 +480,97 @@ const extractTokenFromWindowLocation = (tokenParam, additionalParam = '') => {
466
480
  return token;
467
481
  };
468
482
 
483
+ const createDisableBackground = () => {
484
+ const disableBackground = document.createElement('disable-background');
485
+ document.body.appendChild(disableBackground);
486
+ return disableBackground;
487
+ };
488
+ const reenableBela = (disableBackground) => {
489
+ disableBackground.remove();
490
+ };
491
+ const ensureTurnstileScript = () => new Promise((resolve, reject) => {
492
+ const existing = document.querySelector('script[data-turnstile-script]');
493
+ const onReady = () => (typeof window.turnstile !== 'undefined') ? resolve() : reject(new Error('Turnstile not available'));
494
+ if (existing) {
495
+ if (typeof window.turnstile !== 'undefined')
496
+ return resolve();
497
+ existing.addEventListener('load', onReady, { once: true });
498
+ existing.addEventListener('error', () => reject(new Error('Failed to load Turnstile')), { once: true });
499
+ return;
500
+ }
501
+ const script = document.createElement('script');
502
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
503
+ script.async = true;
504
+ script.defer = true;
505
+ script.dataset.turnstileScript = 'true';
506
+ script.addEventListener('load', onReady, { once: true });
507
+ script.addEventListener('error', () => reject(new Error('Failed to load Turnstile')), { once: true });
508
+ document.head.appendChild(script);
509
+ setTimeout(() => reject(new Error('Turnstile load timeout')), 15000);
510
+ });
511
+ let challengeInFlight = null;
512
+ const captchaChallenge = () => {
513
+ console.log('challange');
514
+ if (challengeInFlight)
515
+ return challengeInFlight;
516
+ challengeInFlight = new Promise((resolve, reject) => __awaiter(void 0, void 0, void 0, function* () {
517
+ const disableBackground = createDisableBackground();
518
+ let overlay = null;
519
+ const cleanup = () => {
520
+ try {
521
+ overlay === null || overlay === void 0 ? void 0 : overlay.remove();
522
+ }
523
+ catch (_a) { }
524
+ try {
525
+ reenableBela(disableBackground);
526
+ }
527
+ catch (_b) { }
528
+ challengeInFlight = null;
529
+ };
530
+ try {
531
+ yield ensureTurnstileScript();
532
+ overlay = document.createElement('captcha-overlay');
533
+ const container = document.createElement('turnstile-container');
534
+ container.id = 'cf-turnstile-container';
535
+ overlay.appendChild(container);
536
+ document.body.appendChild(overlay);
537
+ overlay.style.zIndex = '100';
538
+ overlay.style.width = '100%';
539
+ overlay.style.display = 'flex';
540
+ overlay.style.alignItems = 'center';
541
+ overlay.style.justifyContent = 'center';
542
+ window.turnstile.render(container, {
543
+ sitekey: '0x4AAAAAAB1GW9W6TaEdd7vi',
544
+ callback: (token) => {
545
+ console.log("turnstile token", token);
546
+ backendGet('validate-turnstile', { 'turnstile-token': token }, (res) => {
547
+ console.log(res);
548
+ const sessionToken = res === null || res === void 0 ? void 0 : res['session-token'];
549
+ if (sessionToken) {
550
+ try {
551
+ localStorage.setItem('session-token', String(sessionToken));
552
+ }
553
+ catch (_a) { }
554
+ }
555
+ cleanup();
556
+ resolve(token);
557
+ }, (err) => {
558
+ cleanup();
559
+ reject(err);
560
+ });
561
+ console.log("backendGet called");
562
+ }
563
+ });
564
+ }
565
+ catch (e) {
566
+ cleanup();
567
+ reject(e);
568
+ console.error("backendGet threw", e);
569
+ }
570
+ }));
571
+ return challengeInFlight;
572
+ };
573
+
469
574
  const setBackendUrl = (backendUrl) => setLocalStorageItem('backend-url', backendUrl);
470
575
  //@ts-ignore
471
576
  const getBackendUrl = () => process.env.BACKEND_URL || getLocalStorageItem('backend-url');
@@ -572,7 +677,7 @@ const uploadRequest = (url, file, onSuccess, onError, extras) => {
572
677
  }
573
678
  };
574
679
  const backendRequest = (url_2, ...args_2) => __awaiter(void 0, [url_2, ...args_2], void 0, function* (url, options = {}, onSuccess, onError, onRedirect, requestType = 'query', handleUnauthorized = defaultHandleUnauthorized, retrial = false, retrialNum = 0, extras) {
575
- var _a;
680
+ var _a, _b;
576
681
  if (requestRunning && !retrial) { // only allow one request at a time
577
682
  setTimeout(() => backendRequest(url, options, onSuccess, onError, onRedirect, requestType, handleUnauthorized, false, retrialNum), 300);
578
683
  return;
@@ -653,6 +758,17 @@ const backendRequest = (url_2, ...args_2) => __awaiter(void 0, [url_2, ...args_2
653
758
  handleUnauthorized(response);
654
759
  return;
655
760
  }
761
+ if (response.status === 429) {
762
+ try {
763
+ const token = yield captchaChallenge(); // shows overlay, disables background, validates, cleans up
764
+ return backendRequest(url, Object.assign(Object.assign({}, options), { headers: Object.assign(Object.assign({}, options.headers), { 'cf-turnstile-token': token, 'cf-session-token': (_b = localStorage.getItem('session-token')) !== null && _b !== void 0 ? _b : '' }) }), onSuccess, onError, onRedirect, requestType, handleUnauthorized, true, retrialNum, extras);
765
+ }
766
+ catch (e) {
767
+ onError === null || onError === void 0 ? void 0 : onError({ error: 'Captcha challenge failed', detail: e });
768
+ requestRunning = false;
769
+ return;
770
+ }
771
+ }
656
772
  try {
657
773
  let jsonResponse = {};
658
774
  try {
@@ -1529,7 +1645,7 @@ const handleSSOLogin = () => {
1529
1645
  const onError = () => onAuthenticationFailure('login-failed');
1530
1646
  getSSOAuthorizationEndpoint(onSuccess, onError);
1531
1647
  };
1532
- const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, urlPrefix }) => {
1648
+ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError, backgroundImage, fetchUserBackendUrl, magicLinkRequestEndpoint, magicLinkAuthEndpoint, }) => {
1533
1649
  // if (startNewDemo()) {
1534
1650
  // initDemo(setBackendToken, onAuthentication);
1535
1651
  // return;
@@ -1539,8 +1655,9 @@ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError
1539
1655
  window.juxWebGlobal.fetchUserBackendUrl = fetchUserBackendUrl;
1540
1656
  window.juxWebGlobal.magicLinkRequestEndpoint = magicLinkRequestEndpoint;
1541
1657
  window.juxWebGlobal.magicLinkAuthEndpoint = magicLinkAuthEndpoint;
1542
- if (urlPrefix)
1543
- setUrlPrefix(urlPrefix);
1658
+ const supportedLoginTypesArray = Array.isArray(supportedLoginTypes)
1659
+ ? supportedLoginTypes
1660
+ : [supportedLoginTypes];
1544
1661
  const signInToken = extractTokenFromWindowLocation('sign-in') || extractTokenFromWindowLocation('magic-link');
1545
1662
  if (signInToken) {
1546
1663
  const onOpenMagicLink = () => {
@@ -1574,9 +1691,6 @@ const initSession = ({ clientApp, supportedLoginTypes, onUserLogin, onLoginError
1574
1691
  }));
1575
1692
  return;
1576
1693
  }
1577
- const supportedLoginTypesArray = Array.isArray(supportedLoginTypes)
1578
- ? supportedLoginTypes
1579
- : [supportedLoginTypes];
1580
1694
  displayPage(clientApp, loginPage(clientApp, backgroundImage, onUserLogin, onLoginError, supportedLoginTypesArray, handleSSOLogin, { onAuthPasswordLogin, onGoogleSignIn, onMicrosoftSignIn, handleMagicLinkRequest }));
1581
1695
  // validateThirdPartyCookies(initGapi, () => displayPage(Page.LOGIN));
1582
1696
  // checkMixpanel(() => mixpanel.track('View Login Page'));
@@ -1690,7 +1804,6 @@ const command = (element, endpoint, onResult, params, onError, onRedirect) => {
1690
1804
  element.dispatchEvent(new JuxEvent('command', 'POST', endpoint, onResult, params, onError, onRedirect));
1691
1805
  };
1692
1806
 
1693
- exports.chosenLanguage = chosenLanguage;
1694
1807
  exports.clearSession = clearSession;
1695
1808
  exports.command = command;
1696
1809
  exports.getCurrentUser = getCurrentUser;
@@ -1707,4 +1820,5 @@ exports.query = query;
1707
1820
  exports.reloadWithLanguageOverride = reloadWithLanguageOverride;
1708
1821
  exports.requestMagicLink = requestMagicLink;
1709
1822
  exports.resetJuxWebGlobal = resetJuxWebGlobal;
1823
+ exports.setLocalStoragePrefix = setLocalStoragePrefix;
1710
1824
  exports.upload = upload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klauswuestefeld/jux-web",
3
- "version": "2026.2.2",
3
+ "version": "2026.3.6",
4
4
  "author": "Jux.House",
5
5
  "license": "Copyrighted",
6
6
  "description": "",