jdzcaptcha 2.0.0

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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/cli/jpack.js +3 -0
  4. package/config/jpack.js +129 -0
  5. package/config/jpack.template +5 -0
  6. package/config/jpack.wrapper.js +16 -0
  7. package/dist/assets/jdzcaptcha/placeholder.png +0 -0
  8. package/dist/assets/jdzcaptcha/streamline/light/icon-1.png +0 -0
  9. package/dist/assets/jdzcaptcha/streamline/light/icon-10.png +0 -0
  10. package/dist/assets/jdzcaptcha/streamline/light/icon-11.png +0 -0
  11. package/dist/assets/jdzcaptcha/streamline/light/icon-12.png +0 -0
  12. package/dist/assets/jdzcaptcha/streamline/light/icon-13.png +0 -0
  13. package/dist/assets/jdzcaptcha/streamline/light/icon-14.png +0 -0
  14. package/dist/assets/jdzcaptcha/streamline/light/icon-15.png +0 -0
  15. package/dist/assets/jdzcaptcha/streamline/light/icon-16.png +0 -0
  16. package/dist/assets/jdzcaptcha/streamline/light/icon-17.png +0 -0
  17. package/dist/assets/jdzcaptcha/streamline/light/icon-18.png +0 -0
  18. package/dist/assets/jdzcaptcha/streamline/light/icon-19.png +0 -0
  19. package/dist/assets/jdzcaptcha/streamline/light/icon-2.png +0 -0
  20. package/dist/assets/jdzcaptcha/streamline/light/icon-20.png +0 -0
  21. package/dist/assets/jdzcaptcha/streamline/light/icon-21.png +0 -0
  22. package/dist/assets/jdzcaptcha/streamline/light/icon-22.png +0 -0
  23. package/dist/assets/jdzcaptcha/streamline/light/icon-23.png +0 -0
  24. package/dist/assets/jdzcaptcha/streamline/light/icon-24.png +0 -0
  25. package/dist/assets/jdzcaptcha/streamline/light/icon-25.png +0 -0
  26. package/dist/assets/jdzcaptcha/streamline/light/icon-26.png +0 -0
  27. package/dist/assets/jdzcaptcha/streamline/light/icon-27.png +0 -0
  28. package/dist/assets/jdzcaptcha/streamline/light/icon-28.png +0 -0
  29. package/dist/assets/jdzcaptcha/streamline/light/icon-29.png +0 -0
  30. package/dist/assets/jdzcaptcha/streamline/light/icon-3.png +0 -0
  31. package/dist/assets/jdzcaptcha/streamline/light/icon-30.png +0 -0
  32. package/dist/assets/jdzcaptcha/streamline/light/icon-31.png +0 -0
  33. package/dist/assets/jdzcaptcha/streamline/light/icon-32.png +0 -0
  34. package/dist/assets/jdzcaptcha/streamline/light/icon-33.png +0 -0
  35. package/dist/assets/jdzcaptcha/streamline/light/icon-34.png +0 -0
  36. package/dist/assets/jdzcaptcha/streamline/light/icon-35.png +0 -0
  37. package/dist/assets/jdzcaptcha/streamline/light/icon-36.png +0 -0
  38. package/dist/assets/jdzcaptcha/streamline/light/icon-37.png +0 -0
  39. package/dist/assets/jdzcaptcha/streamline/light/icon-38.png +0 -0
  40. package/dist/assets/jdzcaptcha/streamline/light/icon-39.png +0 -0
  41. package/dist/assets/jdzcaptcha/streamline/light/icon-4.png +0 -0
  42. package/dist/assets/jdzcaptcha/streamline/light/icon-40.png +0 -0
  43. package/dist/assets/jdzcaptcha/streamline/light/icon-41.png +0 -0
  44. package/dist/assets/jdzcaptcha/streamline/light/icon-42.png +0 -0
  45. package/dist/assets/jdzcaptcha/streamline/light/icon-43.png +0 -0
  46. package/dist/assets/jdzcaptcha/streamline/light/icon-44.png +0 -0
  47. package/dist/assets/jdzcaptcha/streamline/light/icon-45.png +0 -0
  48. package/dist/assets/jdzcaptcha/streamline/light/icon-46.png +0 -0
  49. package/dist/assets/jdzcaptcha/streamline/light/icon-47.png +0 -0
  50. package/dist/assets/jdzcaptcha/streamline/light/icon-48.png +0 -0
  51. package/dist/assets/jdzcaptcha/streamline/light/icon-49.png +0 -0
  52. package/dist/assets/jdzcaptcha/streamline/light/icon-5.png +0 -0
  53. package/dist/assets/jdzcaptcha/streamline/light/icon-50.png +0 -0
  54. package/dist/assets/jdzcaptcha/streamline/light/icon-6.png +0 -0
  55. package/dist/assets/jdzcaptcha/streamline/light/icon-7.png +0 -0
  56. package/dist/assets/jdzcaptcha/streamline/light/icon-8.png +0 -0
  57. package/dist/assets/jdzcaptcha/streamline/light/icon-9.png +0 -0
  58. package/dist/public/css/jdzcaptcha.min.css +1 -0
  59. package/dist/public/js/jdzcaptcha.min.js +2 -0
  60. package/lib/iconsets/streamline/Icons by Streamline.txt +5 -0
  61. package/lib/iconsets/streamline/light/icon-1.png +0 -0
  62. package/lib/iconsets/streamline/light/icon-10.png +0 -0
  63. package/lib/iconsets/streamline/light/icon-11.png +0 -0
  64. package/lib/iconsets/streamline/light/icon-12.png +0 -0
  65. package/lib/iconsets/streamline/light/icon-13.png +0 -0
  66. package/lib/iconsets/streamline/light/icon-14.png +0 -0
  67. package/lib/iconsets/streamline/light/icon-15.png +0 -0
  68. package/lib/iconsets/streamline/light/icon-16.png +0 -0
  69. package/lib/iconsets/streamline/light/icon-17.png +0 -0
  70. package/lib/iconsets/streamline/light/icon-18.png +0 -0
  71. package/lib/iconsets/streamline/light/icon-19.png +0 -0
  72. package/lib/iconsets/streamline/light/icon-2.png +0 -0
  73. package/lib/iconsets/streamline/light/icon-20.png +0 -0
  74. package/lib/iconsets/streamline/light/icon-21.png +0 -0
  75. package/lib/iconsets/streamline/light/icon-22.png +0 -0
  76. package/lib/iconsets/streamline/light/icon-23.png +0 -0
  77. package/lib/iconsets/streamline/light/icon-24.png +0 -0
  78. package/lib/iconsets/streamline/light/icon-25.png +0 -0
  79. package/lib/iconsets/streamline/light/icon-26.png +0 -0
  80. package/lib/iconsets/streamline/light/icon-27.png +0 -0
  81. package/lib/iconsets/streamline/light/icon-28.png +0 -0
  82. package/lib/iconsets/streamline/light/icon-29.png +0 -0
  83. package/lib/iconsets/streamline/light/icon-3.png +0 -0
  84. package/lib/iconsets/streamline/light/icon-30.png +0 -0
  85. package/lib/iconsets/streamline/light/icon-31.png +0 -0
  86. package/lib/iconsets/streamline/light/icon-32.png +0 -0
  87. package/lib/iconsets/streamline/light/icon-33.png +0 -0
  88. package/lib/iconsets/streamline/light/icon-34.png +0 -0
  89. package/lib/iconsets/streamline/light/icon-35.png +0 -0
  90. package/lib/iconsets/streamline/light/icon-36.png +0 -0
  91. package/lib/iconsets/streamline/light/icon-37.png +0 -0
  92. package/lib/iconsets/streamline/light/icon-38.png +0 -0
  93. package/lib/iconsets/streamline/light/icon-39.png +0 -0
  94. package/lib/iconsets/streamline/light/icon-4.png +0 -0
  95. package/lib/iconsets/streamline/light/icon-40.png +0 -0
  96. package/lib/iconsets/streamline/light/icon-41.png +0 -0
  97. package/lib/iconsets/streamline/light/icon-42.png +0 -0
  98. package/lib/iconsets/streamline/light/icon-43.png +0 -0
  99. package/lib/iconsets/streamline/light/icon-44.png +0 -0
  100. package/lib/iconsets/streamline/light/icon-45.png +0 -0
  101. package/lib/iconsets/streamline/light/icon-46.png +0 -0
  102. package/lib/iconsets/streamline/light/icon-47.png +0 -0
  103. package/lib/iconsets/streamline/light/icon-48.png +0 -0
  104. package/lib/iconsets/streamline/light/icon-49.png +0 -0
  105. package/lib/iconsets/streamline/light/icon-5.png +0 -0
  106. package/lib/iconsets/streamline/light/icon-50.png +0 -0
  107. package/lib/iconsets/streamline/light/icon-6.png +0 -0
  108. package/lib/iconsets/streamline/light/icon-7.png +0 -0
  109. package/lib/iconsets/streamline/light/icon-8.png +0 -0
  110. package/lib/iconsets/streamline/light/icon-9.png +0 -0
  111. package/lib/index.less +5 -0
  112. package/lib/js/captcha.js +182 -0
  113. package/lib/js/constants.js +61 -0
  114. package/lib/js/fetch.js +51 -0
  115. package/lib/js/ui.js +117 -0
  116. package/lib/js/utils.js +159 -0
  117. package/lib/js/widget.js +624 -0
  118. package/lib/less/animations.less +45 -0
  119. package/lib/less/structure.less +259 -0
  120. package/lib/less/variables.less +2 -0
  121. package/lib/less/variants/dark.less +62 -0
  122. package/lib/less/variants/light.less +66 -0
  123. package/lib/placeholder.png +0 -0
  124. package/package.json +37 -0
@@ -0,0 +1,182 @@
1
+ import { VERSION, defaults } from './constants.js';
2
+ import { Utils } from './utils.js';
3
+ import { Widget } from './widget.js';
4
+ import Fetch from './fetch.js';
5
+
6
+ export const Captcha = {
7
+ NAME: 'JdzCaptcha',
8
+ VERSION: VERSION,
9
+ debugMode: window.JDZ_DEBUG_MODE || false,
10
+ debug: Utils.debug,
11
+ config: null,
12
+ initialized: false,
13
+ instances: [],
14
+
15
+ /**
16
+ * Intialize the captcha configuration and load the widgets.
17
+ * @param {string} loaderUrl The URL to fetch the captcha configuration from.
18
+ * @param {string} [selector=null] The CSS selector for the elements to initialize the captcha on.
19
+ * @param {Object} [options={}] Additional options to override the default configuration.
20
+ * @returns {Object} The Captcha instance.
21
+ */
22
+ initialize: function (loaderUrl, selector = null, options = {}) {
23
+ const loadSelector = (selector) => {
24
+ if (!selector) return;
25
+ if (document.readyState === 'loading') {
26
+ document.addEventListener('DOMContentLoaded', () => {
27
+ this.loadFromSelector(selector);
28
+ });
29
+ } else {
30
+ this.loadFromSelector(selector);
31
+ }
32
+ };
33
+
34
+ if (this.initialized) {
35
+ Utils.warning('Already initialized. Cannot use Captcha.initliaze() twice.');
36
+ loadSelector(selector);
37
+ return this;
38
+ }
39
+
40
+ if (!loaderUrl) {
41
+ // If no loaderUrl is provided, use the default options.
42
+ Utils.error('LoaderUrl is required to initialize the configuration.');
43
+ this.config = Utils.extend({}, defaults, options || {});
44
+ this.initialized = true;
45
+ loadSelector(selector);
46
+ return this;
47
+ }
48
+
49
+ Fetch({
50
+ url: loaderUrl,
51
+ type: 'POST',
52
+ success: (data) => {
53
+ this.config = Utils.extend({}, defaults, options || {}, data || {});
54
+ this.initialized = true;
55
+ loadSelector(selector);
56
+ },
57
+ error: () => {
58
+ Utils.error('Failed to load configuration. Using default options.');
59
+ this.config = Utils.extend({}, defaults, options || {});
60
+ this.initialized = true;
61
+ loadSelector(selector);
62
+ },
63
+ });
64
+
65
+ return this;
66
+ },
67
+
68
+ /**
69
+ * Initializes the captcha widgets for the specified elements.
70
+ * @param {string} selector The CSS selector for the elements to initialize the captcha on.
71
+ * @param {Object} [options={}] Additional options to override the config for the newInstances.
72
+ * @returns {Object} The Captcha instance.
73
+ */
74
+ loadFromSelector: function (selector, options = {}) {
75
+ if (!selector) {
76
+ return this;
77
+ }
78
+
79
+ // Merge config with the provided options, if available.
80
+ options = Utils.extend({}, this.config, options || {});
81
+
82
+ // Initialize the captcha widgets for the elements matching the selector.
83
+ const newInstances = Array.from(document.querySelectorAll(selector))
84
+ .map((element) => new Widget(element, options))
85
+ .filter((widget) => widget !== null);
86
+
87
+ if (newInstances.length === 0) {
88
+ Utils.warn('No valid elements found for the provided selector:', selector);
89
+ return this;
90
+ }
91
+
92
+ // Merge new instances with existing ones.
93
+ this.instances = [...this.instances, ...newInstances];
94
+
95
+ // Bind callbacks, if provided in the options, only to the new instances.
96
+ if (options.callbacks) {
97
+ newInstances.forEach((widget) => {
98
+ Object.entries(options.callbacks).forEach(([event, callback]) => {
99
+ widget.$element.addEventListener(event, callback);
100
+ });
101
+ });
102
+ }
103
+
104
+ return this;
105
+ },
106
+
107
+ checkIfValid: function () {
108
+ if (!this.initialized) {
109
+ Utils.warning('Captcha has not been initialized.');
110
+ return false;
111
+ }
112
+
113
+ if (this.instances.length === 0) {
114
+ Utils.warning('No instances found.');
115
+ return false;
116
+ }
117
+
118
+ return true;
119
+ },
120
+
121
+ /**
122
+ * Resets the specified widget, or all widgets if no widget identifier is provided.
123
+ * @param {string} widgetId The identifier of the widget to reset.
124
+ * @returns {Object} The Captcha instance (for method chaining).
125
+ */
126
+ reset: function (widgetId = null) {
127
+ if (!this.checkIfValid()) {
128
+ Utils.error('Cannot reset.');
129
+ return this;
130
+ }
131
+
132
+ // If no widgetId is provided, reset all instances.
133
+ if (!widgetId) {
134
+ this.instances.forEach((widget) => {
135
+ widget.reset();
136
+ });
137
+ return this;
138
+ }
139
+
140
+ // If widgetId is provided, reset the specific instance.
141
+ const widget = this.instances.find((w) => w.id === widgetId);
142
+ if (widget) {
143
+ widget.reset();
144
+ } else {
145
+ Utils.error(`No instance found with ID "${widgetId}".Cannot reset.`);
146
+ }
147
+
148
+ return this;
149
+ },
150
+
151
+ /**
152
+ * Binds an event listener to all widgets in the captcha instance.
153
+ * @param {string} event The name of the event to bind to.
154
+ * @param {Function} callback The function to be called when the event is triggered.
155
+ * @param {string} widgetId The identifier of the widget to reset.
156
+ * @returns {Object} The Captcha instance (for method chaining).
157
+ */
158
+ bind: function (event, callback, widgetId = null) {
159
+ if (!this.checkIfValid()) {
160
+ Utils.error('Cannot bind.');
161
+ return this;
162
+ }
163
+
164
+ // If no widgetId is provided, bind the event to all instances.
165
+ if (!widgetId) {
166
+ this.instances.forEach((widget) => {
167
+ widget.$element.addEventListener(event, callback);
168
+ });
169
+ return this;
170
+ }
171
+
172
+ // If widgetId is provided, bind the event to the specific instance.
173
+ const widget = this.instances.find((w) => w.id === widgetId);
174
+ if (widget) {
175
+ widget.$element.addEventListener(event, callback);
176
+ } else {
177
+ Utils.error(`No instance found with ID "${widgetId}".Cannot bind event.`);
178
+ }
179
+
180
+ return this;
181
+ }
182
+ };
@@ -0,0 +1,61 @@
1
+ export const VERSION = '2.0.0';
2
+
3
+ export const CSS = {
4
+ opacity: 'jdzc-opacity',
5
+ error: 'jdzc-error',
6
+ success: 'jdzc-success',
7
+ init: 'jdzc-init',
8
+ loader: 'jdzc-loader',
9
+ selection: 'jdzc-box-selection',
10
+ box: 'jdzc-box',
11
+ boxH: 'jdzc-box-h',
12
+ boxB: 'jdzc-box-b',
13
+ boxF: 'jdzc-box-f',
14
+ circle: 'jdzc-box-circle',
15
+ info: 'jdzc-box-info',
16
+ title: 'jdzc-box-title',
17
+ icons: 'jdzc-box-icons',
18
+ checkmark: 'jdzc-box-checkmark',
19
+ subtitle: 'jdzc-box-subtitle',
20
+ fields: 'jdzc-fields',
21
+ };
22
+
23
+ export const defaults = {
24
+ path: '/captcha/request/',
25
+ token: true,
26
+ fontFamily: '',
27
+ credits: 'show',
28
+ series: 'streamline',
29
+ theme: 'light',
30
+ security: {
31
+ clickDelay: 1500,
32
+ hoverDetection: true,
33
+ enableInitialMessage: true,
34
+ initializeDelay: 500,
35
+ selectionResetDelay: 3000,
36
+ loadingAnimationDelay: 1000,
37
+ invalidateTime: 1000 * 60 * 2,
38
+ },
39
+ fields: {
40
+ selection: '_jdzc-hf-se',
41
+ id: '_jdzc-hf-id',
42
+ honeypot: '_jdzc-hf-hp',
43
+ token: '_jdzc-token',
44
+ },
45
+ messages: {
46
+ initialization: {
47
+ loading: 'Loading challenge...',
48
+ verify: 'Verify that you are human.',
49
+ },
50
+ header: 'Select the image displayed the <u>least</u> amount of times',
51
+ correct: 'Verification complete.',
52
+ incorrect: {
53
+ title: 'Uh oh.',
54
+ subtitle: 'You’ve selected the wrong image.',
55
+ },
56
+ timeout: {
57
+ title: 'Please wait 60 sec.',
58
+ subtitle: 'You made too many incorrect selections.',
59
+ },
60
+ },
61
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Sends an HTTP request using the Fetch API.
3
+ * @param {Object} options The options for the request.
4
+ * @param {string} options.url The URL to send the request to.
5
+ * @param {string} [options.type='GET'] The HTTP method (e.g., 'GET', 'POST').
6
+ * @param {Object} [options.headers={}] The headers to include in the request.
7
+ * @param {Object|FormData} [options.data] The data to send with the request.
8
+ * @param {Function} [options.success] The callback function for a successful response.
9
+ * @param {Function} [options.error] The callback function for an error response.
10
+ */
11
+ const Fetch = async (options = {}) => {
12
+ const { url, type = 'GET', headers = {}, data, success, error } = options;
13
+
14
+ const fetchOptions = {
15
+ method: type.toUpperCase(),
16
+ headers: {
17
+ 'X-Requested-With': 'XMLHttpRequest',
18
+ ...headers,
19
+ },
20
+ };
21
+
22
+ if (data) {
23
+ if (data instanceof FormData) {
24
+ fetchOptions.body = data;
25
+ } else {
26
+ fetchOptions.headers['Content-Type'] = 'application/json';
27
+ fetchOptions.body = JSON.stringify(data);
28
+ }
29
+ }
30
+
31
+ try {
32
+ const response = await fetch(url, fetchOptions);
33
+
34
+ if (!response.ok) {
35
+ throw new Error(`Request failed with status ${response.status}`);
36
+ }
37
+
38
+ const text = await response.text();
39
+ let result;
40
+ try {
41
+ result = JSON.parse(text);
42
+ } catch (e) {
43
+ result = text;
44
+ }
45
+ if (success) success(result);
46
+ } catch (err) {
47
+ if (error) error(err);
48
+ }
49
+ };
50
+
51
+ export default Fetch;
package/lib/js/ui.js ADDED
@@ -0,0 +1,117 @@
1
+ import { CSS } from './constants.js';
2
+
3
+ export const UI = {
4
+ /**
5
+ * Builds the HTML for the credits section of the captcha.
6
+ * @returns {string} The HTML string for the credits section.
7
+ */
8
+ buildCredits: (options) => {
9
+ if (options.credits) {
10
+ return '<span' + (options.credits === 'hide' ? ' style="display:none;"' : '') + '>' +
11
+ '<a href="https://joffreydemetz.com/jdzcaptcha" target="_blank" rel="follow" title="JdzCaptcha by Joffrey Demetz">JdzCaptcha</a>' +
12
+ ' &copy;' +
13
+ '</span>';
14
+ }
15
+ return '';
16
+ },
17
+
18
+ /**
19
+ * Builds the HTML for the initial state of the captcha.
20
+ * @param {Object} options The configuration options for the captcha.
21
+ * @return {string} The HTML string for the captcha holder.
22
+ */
23
+ buildCaptchaInitialHolder: (options) => {
24
+ return '<div class="' + CSS.box + '">' +
25
+ '<div class="' + CSS.boxB + '">' +
26
+ '<div class="' + CSS.circle + '"></div>' +
27
+ '<div class="' + CSS.info + '">' + UI.buildCredits(options) + '</div>' +
28
+ '<div class="' + CSS.title + '">' + options.messages.initialization.verify + '</div>' +
29
+ '</div>' +
30
+ '</div>';
31
+ },
32
+
33
+ /**
34
+ * Builds the HTML for the challenge state of the captcha.
35
+ * @param {Object} options The configuration options for the captcha.
36
+ * @param {string} captchaId The captcha identifier.
37
+ * @return {string} The HTML string for the captcha holder.
38
+ */
39
+ buildCaptchaHolder: (options, captchaId) => {
40
+ return '<div class="' + CSS.box + '">' +
41
+ '<div class="' + CSS.boxH + '"><span>' + options.messages.header + '</span></div>' +
42
+ '<div class="' + CSS.boxB + '"><div class="' + CSS.icons + '"></div></div>' +
43
+ '<div class="' + CSS.boxF + '">' + UI.buildCredits(options) + '</div>' +
44
+ '<div class="' + CSS.fields + '">' +
45
+ '<input type="hidden" name="jdzc[' + options.fields.selection + ']" required />' +
46
+ '<input type="hidden" name="jdzc[' + options.fields.id + ']" value="' + captchaId + '" required />' +
47
+ '<input type="hidden" name="jdzc[' + options.fields.honeypot + ']" required />' +
48
+ '</div>' +
49
+ '</div>';
50
+ },
51
+
52
+ /**
53
+ * Builds the HTML for the checkmark icon.
54
+ * @return {string} The HTML string for the checkmark icon.
55
+ */
56
+ buildCheckmark: () => {
57
+ return '<svg viewBox="0 0 98.5 98.5" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">' +
58
+ '<path class="checkmark" d="M81.7 17.8C73.5 9.3 62 4 49.2 4 24.3 4 4 24.3 4 49.2s20.3 45.2 45.2 45.2 45.2-20.3 45.2-45.2c0-8.6-2.4-16.6-6.5-23.4L45.6 68.2 24.7 47.3" fill="none" stroke-miterlimit="10" stroke-width="8" />' +
59
+ '</svg>';
60
+ },
61
+
62
+ buildValidSelectionMessage: (options) => {
63
+ return '<div class="' + CSS.title + '">' + options.messages.correct + '</div>' +
64
+ '<div class="' + CSS.checkmark + '">' + UI.buildCheckmark() + '</div>';
65
+ },
66
+
67
+ buildInvalidSelectionMessage: (options) => {
68
+ return '<div class="' + CSS.title + '">' + options.messages.incorrect.title + '</div>' +
69
+ '<div class="' + CSS.subtitle + '">' + options.messages.incorrect.subtitle + '</div>';
70
+ },
71
+
72
+ buildErrorMessage: (topMessage, bottomMessage) => {
73
+ return '<div class="' + CSS.title + '"> ' + topMessage + '</div>' +
74
+ '<div class="' + CSS.subtitle + '">' + bottomMessage + '</div>';
75
+ },
76
+
77
+ /**
78
+ * Adds a loading spinner to the captcha holder element.
79
+ * @param {HTMLElement} $el The captcha icon holder element.
80
+ */
81
+ addLoadingSpinner: ($el) => {
82
+ $el.classList.add(CSS.opacity);
83
+ if (!$el.querySelector(`.${CSS.loader} `)) {
84
+ $el.insertAdjacentHTML('beforeend', `<div class="${CSS.loader}"></div>`);
85
+ }
86
+ },
87
+
88
+ /**
89
+ * Removes the loading spinner from the captcha holder element.
90
+ * @param {HTMLElement} $el The captcha icon holder element.
91
+ */
92
+ removeLoadingSpinner: ($el) => {
93
+ $el.classList.remove(CSS.opacity);
94
+
95
+ if ($el.querySelector(`.${CSS.loader} `)) {
96
+ $el.querySelector(`.${CSS.loader} `).remove();
97
+ }
98
+ },
99
+
100
+ /**
101
+ * Removes the loading spinner when the background image of the given DOM element is fully loaded.
102
+ * @param {HTMLElement} $el The DOM element with the background image.
103
+ * @param {Function} removeSpinnerCallback The callback to remove the spinner.
104
+ */
105
+ removeLoadingSpinnerOnImageLoad: ($el, removeSpinnerCallback) => {
106
+ const imageUrl = $el.style.backgroundImage.match(/\((.*?)\)/)?.[1]?.replace(/(['"])/g, '');
107
+ if (!imageUrl) return;
108
+
109
+ const imgObject = new Image();
110
+
111
+ // Remove the spinner once the image is loaded.
112
+ imgObject.onload = () => removeSpinnerCallback();
113
+
114
+ // Set the image source to trigger the `onload` event.
115
+ imgObject.src = imageUrl;
116
+ },
117
+ };
@@ -0,0 +1,159 @@
1
+ export const Utils = {
2
+ /**
3
+ * Extends an object with properties from one or more source objects.
4
+ * @param {Object} [out={}] The target object to extend.
5
+ * @param {...Object} sources The source objects to copy properties from.
6
+ * @returns {Object} The extended object.
7
+ */
8
+ extend: (out = {}, ...sources) => {
9
+ sources.forEach((source) => {
10
+ if (source && typeof source === 'object') {
11
+ Object.keys(source).forEach((key) => {
12
+ if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
13
+ out[key] = Utils.extend(out[key] || {}, source[key]);
14
+ } else {
15
+ out[key] = source[key];
16
+ }
17
+ });
18
+ }
19
+ });
20
+ return out;
21
+ },
22
+
23
+ thrown: (error) => {
24
+ const DEBUG_MODE = window.JdzCaptcha && window.JdzCaptcha.debugMode;
25
+ if (!DEBUG_MODE) return;
26
+
27
+ if (typeof error === 'string') {
28
+ console.warn('JdzCaptcha ERROR:', error);
29
+ } else if (error instanceof Error) {
30
+ console.error('JdzCaptcha ERROR:', error.message);
31
+ } else if (typeof error === 'object') {
32
+ console.dir('JdzCaptcha ERROR:', error);
33
+ } else {
34
+ console.warn('JdzCaptcha ERROR:', error);
35
+ }
36
+ },
37
+
38
+ /**
39
+ * Triggers a custom event on a DOM element.
40
+ * @param {HTMLElement} $el The DOM element to trigger the event on.
41
+ * @param {string} event The name of the event to trigger.
42
+ * @param {Object} [data] The data to pass with the event.
43
+ */
44
+ trigger: ($el, event, data) => {
45
+ const domEvent = new CustomEvent(event, { detail: data });
46
+ $el.dispatchEvent(domEvent);
47
+ },
48
+
49
+ /**
50
+ * Removes all child elements from a DOM element.
51
+ * @param {HTMLElement} $el The DOM element to empty.
52
+ */
53
+ empty: ($el) => {
54
+ while ($el.firstChild) $el.removeChild($el.firstChild);
55
+ },
56
+
57
+ /**
58
+ * Calculates the offset of a DOM element relative to the document.
59
+ * @param {HTMLElement} $el The DOM element to calculate the offset for.
60
+ * @returns {Object} An object containing the top and left offsets.
61
+ */
62
+ detectOffset: ($el) => {
63
+ if (!$el.getClientRects().length) return { top: 0, left: 0 };
64
+
65
+ const { top, left } = $el.getBoundingClientRect();
66
+ const { pageYOffset, pageXOffset } = $el.ownerDocument.defaultView;
67
+
68
+ return {
69
+ top: top + pageYOffset,
70
+ left: left + pageXOffset,
71
+ };
72
+ },
73
+
74
+ /**
75
+ * Gets the computed width of a DOM element in pixels.
76
+ * @param {HTMLElement} $el The DOM element to get the width for.
77
+ * @returns {number} The width of the element in pixels.
78
+ */
79
+ width: ($el) => {
80
+ return parseFloat(getComputedStyle($el, null).width.replace('px', ''));
81
+ },
82
+
83
+ /**
84
+ * Logs debug information to the console if debug mode is enabled.
85
+ * Supports multiple arguments and automatically uses `console.dir` for objects or arrays.
86
+ * @param {...any} args The data to log. The last argument can specify the console method (e.g., 'log', 'warn').
87
+ */
88
+ debug: (...args) => {
89
+ // Check if debug mode is enabled
90
+ const DEBUG_MODE = window.JdzCaptcha && window.JdzCaptcha.debugMode;
91
+ if (!DEBUG_MODE) return;
92
+
93
+ // Extract the last argument as the console method, defaulting to 'log'
94
+ const method = typeof args[args.length - 1] === 'string' && console[args[args.length - 1]] ? args.pop() : 'log';
95
+
96
+ args.forEach((arg) => {
97
+ // Check if the first argument is an object or array, and use `console.dir` if so
98
+ if (typeof arg === 'object' || Array.isArray(arg)) {
99
+ if (method === 'warn' || method === 'error') {
100
+ console[method]('JdzCaptcha: ' + method.toUpperCase() + ':');
101
+ }
102
+ console.dir(arg);
103
+ } else {
104
+ if ('dir' === method) {
105
+ console.log('JdzCaptcha: ' + arg);
106
+ }
107
+ else {
108
+ console[method](arg);
109
+ }
110
+ }
111
+ });
112
+ },
113
+
114
+ /**
115
+ * Logs a warning message to the console if debug mode is enabled.
116
+ * @param {...any} args The data to log as a warning.
117
+ */
118
+ warn: (...args) => {
119
+ Utils.debug(...args, 'warn');
120
+ },
121
+
122
+ /**
123
+ * Logs an error message to the console if debug mode is enabled.
124
+ * @param {...any} args The data to log as an error.
125
+ */
126
+ error: (...args) => {
127
+ Utils.debug(...args, 'error');
128
+ },
129
+
130
+ /**
131
+ * Creates a Base64 encoded JSON string from the given data parameter.
132
+ * @param {Object} data The payload object to encode.
133
+ * @returns {string} The encoded payload.
134
+ */
135
+ createPayload: (data) => {
136
+ return btoa(JSON.stringify({ ...data, ts: Date.now() }));
137
+ },
138
+
139
+ /**
140
+ * Checks if a string is a valid Base64 encoded string.
141
+ * @param {string} str The string to check.
142
+ * @returns {boolean} True if the string is valid Base64, false otherwise.
143
+ */
144
+ isBase64: (str) => {
145
+ try {
146
+ return btoa(atob(str)) === str;
147
+ } catch (err) {
148
+ return false;
149
+ }
150
+ },
151
+
152
+ /**
153
+ * Clears a timeout if the timeout ID is valid.
154
+ * @param {number|null} timeoutId The timeout ID to clear.
155
+ */
156
+ clearInvalidationTimeout: (timeoutId) => {
157
+ if (timeoutId) clearTimeout(timeoutId);
158
+ },
159
+ };