@arnaudw38/nodebb-plugin-spam-be-gone 1.0.7 → 1.0.8
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/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/public/js/scripts.js +95 -38
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.9] - 2026-02-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Fixed login success flow causing an unnecessary Turnstile refresh shortly before redirect.
|
|
9
|
+
- Login retry reset now triggers only when a login error is actually detected on the page.
|
|
10
|
+
|
|
11
|
+
### Improved
|
|
12
|
+
- Added navigation-aware cleanup (`beforeunload`/`pagehide`) to avoid reset races during successful login redirects.
|
|
13
|
+
- Reworked login retry watcher to use short-lived DOM error detection + fallback polling instead of blind timed resets.
|
|
14
|
+
|
|
5
15
|
## [1.0.8] - 2026-02-25
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arnaudw38/nodebb-plugin-spam-be-gone",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Anti-spam plugin for NodeBB 4.x using Akismet, StopForumSpam API, ProjectHoneyPot, and Cloudflare Turnstile (Turnstile-only fork)",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"scripts": {},
|
package/public/js/scripts.js
CHANGED
|
@@ -9,6 +9,9 @@ $(function () {
|
|
|
9
9
|
widgets: {},
|
|
10
10
|
loginBindingsAttached: false,
|
|
11
11
|
loginResetTimers: [],
|
|
12
|
+
loginErrorObserver: null,
|
|
13
|
+
loginAttemptArmed: false,
|
|
14
|
+
navigatingAway: false,
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
function getTurnstileArgs() {
|
|
@@ -61,30 +64,109 @@ $(function () {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
function onLoginPageNow() {
|
|
68
|
+
return !ajaxify.data || !ajaxify.data.tpl_url || ajaxify.data.tpl_url === 'login';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasLoginErrorVisible() {
|
|
72
|
+
var selectors = [
|
|
73
|
+
'.alert-danger',
|
|
74
|
+
'.alert-error',
|
|
75
|
+
'[component="alerts"] .alert.alert-danger',
|
|
76
|
+
'[component="alerts/error"]',
|
|
77
|
+
'.login-error',
|
|
78
|
+
'.text-danger',
|
|
79
|
+
];
|
|
80
|
+
for (var i = 0; i < selectors.length; i += 1) {
|
|
81
|
+
var nodes = document.querySelectorAll(selectors[i]);
|
|
82
|
+
for (var j = 0; j < nodes.length; j += 1) {
|
|
83
|
+
var n = nodes[j];
|
|
84
|
+
if (n && (n.offsetParent !== null || (n.textContent && n.textContent.trim()))) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function clearLoginResetTimers() {
|
|
67
93
|
turnstileState.loginResetTimers.forEach(function (timerId) {
|
|
68
94
|
window.clearTimeout(timerId);
|
|
69
95
|
});
|
|
70
96
|
turnstileState.loginResetTimers = [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function disarmLoginAttemptWatch() {
|
|
100
|
+
turnstileState.loginAttemptArmed = false;
|
|
101
|
+
clearLoginResetTimers();
|
|
102
|
+
if (turnstileState.loginErrorObserver) {
|
|
103
|
+
try {
|
|
104
|
+
turnstileState.loginErrorObserver.disconnect();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
// noop
|
|
107
|
+
}
|
|
108
|
+
turnstileState.loginErrorObserver = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resetLoginTurnstileOnDetectedError() {
|
|
113
|
+
if (!turnstileState.loginAttemptArmed) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (turnstileState.navigatingAway || !onLoginPageNow()) {
|
|
117
|
+
disarmLoginAttemptWatch();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!hasLoginErrorVisible()) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
var targetId = getCurrentLoginTurnstileTargetId();
|
|
124
|
+
if (targetId && document.getElementById(targetId)) {
|
|
125
|
+
resetTurnstileWidget(targetId);
|
|
126
|
+
}
|
|
127
|
+
disarmLoginAttemptWatch();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function scheduleLoginTurnstileReset() {
|
|
131
|
+
// Arm a short-lived watcher and reset only when an actual login error is rendered.
|
|
132
|
+
turnstileState.navigatingAway = false;
|
|
133
|
+
disarmLoginAttemptWatch();
|
|
134
|
+
turnstileState.loginAttemptArmed = true;
|
|
135
|
+
|
|
136
|
+
if (window.MutationObserver) {
|
|
137
|
+
turnstileState.loginErrorObserver = new MutationObserver(function () {
|
|
138
|
+
resetLoginTurnstileOnDetectedError();
|
|
139
|
+
});
|
|
140
|
+
turnstileState.loginErrorObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
141
|
+
}
|
|
71
142
|
|
|
72
|
-
|
|
143
|
+
// Poll briefly as a fallback for themes that toggle classes/text without obvious DOM insertions.
|
|
144
|
+
[250, 600, 1000, 1600, 2400, 3400].forEach(function (delay) {
|
|
73
145
|
var timerId = window.setTimeout(function () {
|
|
74
|
-
|
|
75
|
-
if (!onLoginPage) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
var targetId = getCurrentLoginTurnstileTargetId();
|
|
79
|
-
if (!targetId || !document.getElementById(targetId)) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
resetTurnstileWidget(targetId);
|
|
146
|
+
resetLoginTurnstileOnDetectedError();
|
|
83
147
|
}, delay);
|
|
84
148
|
turnstileState.loginResetTimers.push(timerId);
|
|
85
149
|
});
|
|
150
|
+
|
|
151
|
+
// Stop watching after a while to avoid stale observers.
|
|
152
|
+
var cleanupTimerId = window.setTimeout(function () {
|
|
153
|
+
if (!turnstileState.navigatingAway && onLoginPageNow()) {
|
|
154
|
+
// no-op: just let the user try again manually if no explicit error was detected
|
|
155
|
+
}
|
|
156
|
+
disarmLoginAttemptWatch();
|
|
157
|
+
}, 6000);
|
|
158
|
+
turnstileState.loginResetTimers.push(cleanupTimerId);
|
|
86
159
|
}
|
|
87
160
|
|
|
161
|
+
window.addEventListener('beforeunload', function () {
|
|
162
|
+
turnstileState.navigatingAway = true;
|
|
163
|
+
disarmLoginAttemptWatch();
|
|
164
|
+
});
|
|
165
|
+
window.addEventListener('pagehide', function () {
|
|
166
|
+
turnstileState.navigatingAway = true;
|
|
167
|
+
disarmLoginAttemptWatch();
|
|
168
|
+
});
|
|
169
|
+
|
|
88
170
|
function bindLoginRetryReset() {
|
|
89
171
|
if (turnstileState.loginBindingsAttached) {
|
|
90
172
|
return;
|
|
@@ -122,31 +204,6 @@ $(function () {
|
|
|
122
204
|
}
|
|
123
205
|
}, true);
|
|
124
206
|
|
|
125
|
-
// Extra safety: reset again when a login error alert appears (covers AJAX flows that do not
|
|
126
|
-
// reliably trigger the expected submit/click path in some themes).
|
|
127
|
-
if (window.MutationObserver) {
|
|
128
|
-
var observer = new MutationObserver(function (mutations) {
|
|
129
|
-
var onLoginPage = !ajaxify.data || !ajaxify.data.tpl_url || ajaxify.data.tpl_url === 'login';
|
|
130
|
-
if (!onLoginPage) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
for (var i = 0; i < mutations.length; i += 1) {
|
|
134
|
-
for (var j = 0; j < mutations[i].addedNodes.length; j += 1) {
|
|
135
|
-
var n = mutations[i].addedNodes[j];
|
|
136
|
-
if (!n || n.nodeType !== 1) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
var hasError = (n.matches && n.matches('.alert-danger, .alert-error, .text-danger, [component="alerts/error"]')) ||
|
|
140
|
-
(n.querySelector && n.querySelector('.alert-danger, .alert-error, .text-danger, [component="alerts/error"]'));
|
|
141
|
-
if (hasError) {
|
|
142
|
-
scheduleLoginTurnstileReset();
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
149
|
-
}
|
|
150
207
|
}
|
|
151
208
|
|
|
152
209
|
function renderTurnstileIfNeeded(isLoginPage) {
|