@descope/flow-scripts 1.0.4 → 1.0.5

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,3 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):((e="undefined"!=typeof globalThis?globalThis:e||self).descope=e.descope||{},e.descope.turnstile=t())}(this,(function(){"use strict";function e(e,t,n,o){return new(n||(n=Promise))((function(r,s){function c(e){try{a(o.next(e))}catch(e){s(e)}}function i(e){try{a(o.throw(e))}catch(e){s(e)}}function a(e){var t;e.done?r(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(c,i)}a((o=o.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;
2
+ /*! Source: (c) Descope and Cloudflare | https://developers.cloudflare.com/turnstile/ */
3
+ const t=105e3;return(n,o,r)=>{let s=(new Date).getTime();const c=()=>globalThis.turnstile;let i,a;const l=(e,t)=>{n.siteKey&&(null==e||e.execute(a,{action:"load"}),s=(new Date).getTime())},u=(e,n)=>{try{e.ready((()=>{l(e)}))}catch(t){try{l(e)}catch(e){console.warn("could not execute turnstile",e)}}finally{i=setTimeout((()=>{u(e,n)}),t)}},d=(()=>{const e=document.createElement("div");return e.style.display="none",e.id="descope-turnstile",e.className="cf-turnstile",document.body.appendChild(e)})(),p=()=>{const e=c();e&&(e.reset(a),u(e,d))};return window.onloadTurnstileCallback=()=>{const e=d;if(e.hasChildNodes())return;const t=c();t&&setTimeout((()=>{a=t.render(e,{sitekey:n.siteKey,execution:"execute",appearance:n.appearance||"execute",callback:e=>r(e)}),u(t,e)}),0)},(()=>{const e=document.createElement("script");e.src=(()=>{const e=new URL("https://challenges.cloudflare.com");return e.pathname+="turnstile/v0/api.js",e.searchParams.append("onload","onloadTurnstileCallback"),e.searchParams.append("render","explicit"),e.toString()})(),e.async=!0,e.id="turnstile-script",e.defer=!1,document.body.appendChild(e)})(),{stop:()=>{clearTimeout(i)},start:p,refresh:()=>e(void 0,void 0,void 0,(function*(){if(Date.now()-s>t){const e=s;return p(),new Promise((t=>{const n=setTimeout((()=>{console.warn("Turnstile token refresh timed out"),t()}),5e3),o=()=>{s!==e?(clearTimeout(n),t()):setTimeout(o,150)};o()}))}return Promise.resolve()}))}}}));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@descope/flow-scripts",
3
3
  "type": "module",
4
- "version": "1.0.4",
4
+ "version": "1.0.5",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/descope/content"
@@ -14,21 +14,21 @@
14
14
  "test": "jest"
15
15
  },
16
16
  "devDependencies": {
17
- "@rollup/plugin-commonjs": "28.0.2",
18
- "@rollup/plugin-node-resolve": "16.0.0",
17
+ "@rollup/plugin-commonjs": "28.0.3",
18
+ "@rollup/plugin-node-resolve": "16.0.1",
19
19
  "@rollup/plugin-terser": "0.4.4",
20
20
  "@rollup/plugin-typescript": "12.1.2",
21
21
  "@testing-library/dom": "10.4.0",
22
- "@testing-library/react": "16.2.0",
22
+ "@testing-library/react": "16.3.0",
23
23
  "@types/jest": "29.5.14",
24
- "eslint": "9.23.0",
24
+ "eslint": "9.25.1",
25
25
  "eslint-plugin-import": "2.31.0",
26
26
  "jest": "29.7.0",
27
27
  "jest-environment-jsdom": "29.7.0",
28
- "rollup": "4.34.7",
29
- "ts-jest": "29.3.0",
28
+ "rollup": "4.40.0",
29
+ "ts-jest": "29.3.2",
30
30
  "ts-node": "10.9.2",
31
- "typescript": "5.7.3"
31
+ "typescript": "5.8.3"
32
32
  },
33
33
  "dependencies": {
34
34
  "@fingerprintjs/fingerprintjs-pro": "3.11.8"
@@ -51,7 +51,6 @@ export const loadFingerprint = async (
51
51
  const { requestId } = await agent.get();
52
52
  onTokenReady(requestId);
53
53
  } catch (ex) {
54
- // eslint-disable-next-line no-console
55
54
  console.warn('Could not load fingerprint', ex);
56
55
  }
57
56
  };
@@ -57,7 +57,6 @@ export const loadFingerprint = async (
57
57
  const { requestId } = await agent.get();
58
58
  onTokenReady(requestId);
59
59
  } catch (ex) {
60
- // eslint-disable-next-line no-console
61
60
  console.warn('Could not load descope fingerprint', ex);
62
61
  }
63
62
  };
package/src/grecaptcha.ts CHANGED
@@ -59,7 +59,6 @@ const loadGRecaptcha = (
59
59
  ?.execute(recaptchaWidgetId, { action: 'load' })
60
60
  .then((token: string, e: any) => {
61
61
  if (e) {
62
- // eslint-disable-next-line no-console
63
62
  console.warn('could not execute recaptcha', e);
64
63
  } else {
65
64
  onTokenReady(token);
@@ -112,7 +111,6 @@ const loadGRecaptcha = (
112
111
  return new Promise<void>((resolve) => {
113
112
  // Set a timeout to prevent indefinite waiting
114
113
  const timeout = setTimeout(() => {
115
- // eslint-disable-next-line no-console
116
114
  console.warn('reCAPTCHA token refresh timed out');
117
115
  resolve(); // Resolve anyway to prevent blocking form submission
118
116
  }, 5000); // 5 second timeout for token refresh
package/src/index.spec.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/no-namespace */
2
1
  import { waitFor } from '@testing-library/dom';
3
2
  import * as fp from '@fingerprintjs/fingerprintjs-pro';
4
3
 
@@ -13,15 +12,12 @@ async function loadSdkScript(scriptId: string) {
13
12
  res = await import('./forter');
14
13
  return res.default;
15
14
  case 'fingerprint':
16
- // eslint-disable-next-line no-case-declarations
17
15
  res = await import('./fingerprint');
18
16
  return res.default;
19
17
  case 'fingerprintDescope':
20
- // eslint-disable-next-line no-case-declarations
21
18
  res = await import('./fingerprintDescope');
22
19
  return res.default;
23
20
  case 'grecaptcha':
24
- // eslint-disable-next-line no-case-declarations
25
21
  res = await import('./grecaptcha');
26
22
  return res.default;
27
23
  default:
@@ -45,7 +41,6 @@ describe('scripts', () => {
45
41
  // the script changes the id attribute to the siteId
46
42
  expect(script?.getAttribute('id')).toBeTruthy();
47
43
  // get textContent and ensure it contains the script id
48
- // eslint-disable-next-line jest-dom/prefer-to-have-text-content
49
44
  expect(script?.textContent).toContain('some-site-id');
50
45
 
51
46
  // trigger 'ftr:tokenReady' event and ensure that the tokenChangedFn is called
@@ -0,0 +1,180 @@
1
+ /*! Source: (c) Descope and Cloudflare | https://developers.cloudflare.com/turnstile/ */
2
+
3
+ // define the global interface for the turnstile object
4
+ declare global {
5
+ interface Window {
6
+ turnstile?: {
7
+ ready: (callback: () => void) => void;
8
+ execute: (widgetId: any, options: { action: string }) => Promise<string>;
9
+ render: (container: HTMLElement, parameters: any) => any;
10
+ reset: (widgetId: any) => void;
11
+ };
12
+ onloadTurnstileCallback: () => void;
13
+ }
14
+ }
15
+
16
+ // Token refresh time: 105 seconds (2 minutes minus 15 seconds)
17
+ // Set to refresh the token shortly before expiration to ensure
18
+ // we always have a valid token when submitting the form
19
+ const TOKEN_REFRESH_TIME_MS = 105000;
20
+
21
+ const loadTurnstile = (
22
+ initArgs: {
23
+ appearance: boolean;
24
+ siteKey: string;
25
+
26
+ },
27
+ _inputs: { baseUrl?: string },
28
+ onTokenReady: (token: string) => void,
29
+ ) => {
30
+ let lastTokenFetchTime = new Date().getTime();
31
+
32
+ const getScriptURL = () => {
33
+ const url = new URL('https://challenges.cloudflare.com');
34
+ url.pathname += 'turnstile/v0/api.js';
35
+ url.searchParams.append('onload', 'onloadTurnstileCallback');
36
+ url.searchParams.append('render', 'explicit');
37
+ return url.toString();
38
+ };
39
+
40
+ const loadScript = () => {
41
+ const script = document.createElement('script');
42
+ script.src = getScriptURL();
43
+ script.async = true;
44
+ script.id = 'turnstile-script';
45
+ script.defer = false;
46
+ document.body.appendChild(script);
47
+ };
48
+
49
+ const getInstance = () => globalThis.turnstile;
50
+
51
+ let timer;
52
+ let turnstileWidgetId;
53
+
54
+ const executeNewToken = (turnstileInstance: any, currentNode: HTMLDivElement) => {
55
+ if (!initArgs.siteKey) {
56
+ return;
57
+ }
58
+ turnstileInstance?.execute(turnstileWidgetId, { action: 'load' });
59
+ lastTokenFetchTime = new Date().getTime();
60
+ }
61
+
62
+ const getNewToken = (turnstileInstance: any, currentNode: HTMLDivElement) => {
63
+ // sometimes the turnstile instance may return configuration error
64
+ // on the first call, so we need to retry
65
+ try {
66
+ turnstileInstance.ready(() => {
67
+ executeNewToken(turnstileInstance, currentNode);
68
+ });
69
+ } catch (e) {
70
+ try {
71
+ executeNewToken(turnstileInstance, currentNode);
72
+ } catch (e) {
73
+ console.warn('could not execute turnstile', e);
74
+ }
75
+ } finally {
76
+ // Set a timeout to refresh the token before it expires
77
+ timer = setTimeout(() => {
78
+ getNewToken(turnstileInstance, currentNode);
79
+ }, TOKEN_REFRESH_TIME_MS);
80
+ }
81
+ };
82
+
83
+ const stopTimer = () => {
84
+ clearTimeout(timer);
85
+ };
86
+
87
+ const createTurnstileElement = () => {
88
+ const turnstileEle = document.createElement('div');
89
+ turnstileEle.style.display = 'none';
90
+ turnstileEle.id = 'descope-turnstile';
91
+ turnstileEle.className = 'cf-turnstile'
92
+ return document.body.appendChild(turnstileEle);
93
+ };
94
+
95
+ const elementRef = createTurnstileElement();
96
+
97
+ const resumeScriptExecution = () => {
98
+ const turnstileInstance = getInstance();
99
+ if (!turnstileInstance) {
100
+ return;
101
+ }
102
+ turnstileInstance.reset(turnstileWidgetId);
103
+ getNewToken(turnstileInstance, elementRef);
104
+ };
105
+
106
+ /**
107
+ * Checks if the turnstile token needs refreshing and refreshes it if necessary
108
+ * This is called before form submission to ensure we have a valid token
109
+ * @returns Promise that resolves when token is refreshed or if refresh isn't needed
110
+ */
111
+ const refreshIfTokenExpired = async (): Promise<void> => {
112
+ const currentTime = Date.now();
113
+ const timeDiff = currentTime - lastTokenFetchTime;
114
+
115
+ if (timeDiff > TOKEN_REFRESH_TIME_MS) {
116
+ const prev = lastTokenFetchTime;
117
+ resumeScriptExecution();
118
+
119
+ // Return a promise that resolves once the token is refreshed or times out
120
+ return new Promise<void>((resolve) => {
121
+ // Set a timeout to prevent indefinite waiting
122
+ const timeout = setTimeout(() => {
123
+ console.warn('Turnstile token refresh timed out');
124
+ resolve(); // Resolve anyway to prevent blocking form submission
125
+ }, 5000); // 5 second timeout for token refresh
126
+
127
+ const checkToken = () => {
128
+ if (lastTokenFetchTime !== prev) {
129
+ clearTimeout(timeout);
130
+ resolve();
131
+ } else {
132
+ setTimeout(checkToken, 150);
133
+ }
134
+ };
135
+
136
+ checkToken();
137
+ });
138
+ }
139
+
140
+ // If no refresh is needed, return a resolved promise
141
+ return Promise.resolve();
142
+ };
143
+
144
+ const createOnLoadScript = () => {
145
+ window.onloadTurnstileCallback = () => {
146
+ const currentNode = elementRef;
147
+
148
+ if (currentNode.hasChildNodes()) {
149
+ return;
150
+ }
151
+
152
+ const turnstileInstance = getInstance();
153
+
154
+ if (!turnstileInstance) {
155
+ return;
156
+ }
157
+
158
+ setTimeout(() => {
159
+ turnstileWidgetId = turnstileInstance.render(currentNode, {
160
+ sitekey: initArgs.siteKey,
161
+ execution: 'execute',
162
+ appearance: initArgs.appearance || 'execute',
163
+ callback: (token: string) => onTokenReady(token),
164
+ });
165
+ getNewToken(turnstileInstance, currentNode);
166
+ }, 0);
167
+ };
168
+ };
169
+
170
+ createOnLoadScript();
171
+ loadScript();
172
+
173
+ return {
174
+ stop: stopTimer,
175
+ start: resumeScriptExecution,
176
+ refresh: refreshIfTokenExpired,
177
+ };
178
+ };
179
+
180
+ export default loadTurnstile;