@prosopo/procaptcha-pow 2.13.6 → 2.13.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/.turbo/turbo-build$colon$cjs.log +5 -8
- package/.turbo/turbo-build$colon$tsc.log +19 -22
- package/.turbo/turbo-build.log +5 -11
- package/CHANGELOG.md +59 -0
- package/LICENSE +201 -0
- package/dist/cjs/components/procaptchaWidget.cjs +2 -1
- package/dist/cjs/services/Manager.cjs +16 -1
- package/dist/components/procaptchaWidget.d.ts.map +1 -1
- package/dist/components/procaptchaWidget.js +3 -2
- package/dist/components/procaptchaWidget.js.map +1 -1
- package/dist/services/Manager.d.ts +1 -0
- package/dist/services/Manager.d.ts.map +1 -1
- package/dist/services/Manager.js +16 -1
- package/dist/services/Manager.js.map +1 -1
- package/dist/tests/manager.unit.test.js +28 -0
- package/dist/tests/manager.unit.test.js.map +1 -1
- package/dist/tests/procaptchaPow.test-d.js +1 -1
- package/dist/tests/procaptchaPow.test-d.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +42 -2
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/package.json +73 -72
- package/src/components/procaptchaPoW.ts +0 -53
- package/src/components/procaptchaWidget.ts +0 -294
- package/src/index.ts +0 -15
- package/src/services/Manager.ts +0 -462
- package/src/tests/domHarness.ts +0 -122
- package/src/tests/manager.unit.test.ts +0 -1105
- package/src/tests/managerHarness.ts +0 -135
- package/src/tests/procaptchaPoW.unit.test.ts +0 -143
- package/src/tests/procaptchaPow.test-d.ts +0 -128
- package/src/tests/procaptchaWidget.unit.test.ts +0 -531
- package/tsconfig.cjs.json +0 -43
- package/tsconfig.json +0 -47
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.types.json +0 -9
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
|
|
15
|
-
import { createTranslator, loadI18next } from "@prosopo/locale";
|
|
16
|
-
import {
|
|
17
|
-
type CheckboxProps,
|
|
18
|
-
type Component,
|
|
19
|
-
type HoneypotComponent,
|
|
20
|
-
type ProcaptchaStateHandle,
|
|
21
|
-
Teardown,
|
|
22
|
-
buildUpdateState,
|
|
23
|
-
createElement,
|
|
24
|
-
createProcaptchaState,
|
|
25
|
-
createRenderScheduler,
|
|
26
|
-
isEventTrusted,
|
|
27
|
-
mountCheckbox,
|
|
28
|
-
mountHoneypot,
|
|
29
|
-
} from "@prosopo/procaptcha-common";
|
|
30
|
-
import {
|
|
31
|
-
ModeEnum,
|
|
32
|
-
type ProcaptchaProps,
|
|
33
|
-
type ProcaptchaState,
|
|
34
|
-
} from "@prosopo/types";
|
|
35
|
-
import { darkTheme, lightTheme } from "@prosopo/widget-skeleton";
|
|
36
|
-
import { Manager } from "../services/Manager.js";
|
|
37
|
-
|
|
38
|
-
// Define the same event name as in the bundle for consistency
|
|
39
|
-
const PROCAPTCHA_EXECUTE_EVENT = "procaptcha:execute";
|
|
40
|
-
|
|
41
|
-
export interface ProcaptchaPowHandle {
|
|
42
|
-
destroy(): void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const mountProcaptchaPowWidget = (
|
|
46
|
-
container: HTMLElement,
|
|
47
|
-
props: ProcaptchaProps,
|
|
48
|
-
): ProcaptchaPowHandle => {
|
|
49
|
-
const teardown = new Teardown();
|
|
50
|
-
const config = props.config;
|
|
51
|
-
const i18n = props.i18n;
|
|
52
|
-
const frictionlessState = props.frictionlessState; // Set up Session ID and Provider if they exist
|
|
53
|
-
const callbacks = props.callbacks || {};
|
|
54
|
-
const translator = createTranslator(i18n);
|
|
55
|
-
const isInvisible = ModeEnum.invisible === config.mode;
|
|
56
|
-
|
|
57
|
-
const store: ProcaptchaStateHandle = createProcaptchaState();
|
|
58
|
-
let loading = false;
|
|
59
|
-
let lastError: ProcaptchaState["error"] = store.state.error;
|
|
60
|
-
// Coordinates of the last `manager.start(x, y)` invocation from this widget
|
|
61
|
-
// instance — either the checkbox click, or a `startCoords` handoff from a
|
|
62
|
-
// prior `onSessionInvalidated` cycle.
|
|
63
|
-
let lastCoords: { x: number; y: number } | null = null;
|
|
64
|
-
// One-shot guard: a widget instance only escalates a single NO_SESSION_FOUND
|
|
65
|
-
// to `onSessionInvalidated`. If the retry also fails, we fall through to
|
|
66
|
-
// the pre-existing `frictionlessState.restart()` path instead of looping.
|
|
67
|
-
let sessionInvalidatedFired = false;
|
|
68
|
-
// A bound button can be clicked again while the first run is still going,
|
|
69
|
-
// and in invisible mode there is no spinner standing in the way of it.
|
|
70
|
-
let executeRunning = false;
|
|
71
|
-
|
|
72
|
-
let honeypot: HoneypotComponent | undefined;
|
|
73
|
-
let checkbox: Component<CheckboxProps> | undefined;
|
|
74
|
-
|
|
75
|
-
// get the state update mechanism
|
|
76
|
-
const updateState = buildUpdateState(store.state, store.update);
|
|
77
|
-
|
|
78
|
-
const manager = Manager(
|
|
79
|
-
config,
|
|
80
|
-
store.state,
|
|
81
|
-
updateState,
|
|
82
|
-
callbacks,
|
|
83
|
-
frictionlessState,
|
|
84
|
-
props.onEscalate,
|
|
85
|
-
() => honeypot?.getValue(),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const root = createElement("div");
|
|
89
|
-
container.appendChild(root);
|
|
90
|
-
|
|
91
|
-
if (frictionlessState?.hp) {
|
|
92
|
-
// Invisible mode renders no checkbox, but we still render the honeypot
|
|
93
|
-
// so bots that scan the DOM for inputs find a tempting target.
|
|
94
|
-
honeypot = mountHoneypot(root, { encodedQuestion: frictionlessState.hp });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const runErrorEffect = () => {
|
|
98
|
-
if (store.state.error === lastError) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
lastError = store.state.error;
|
|
102
|
-
if (!store.state.error) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
loading = false;
|
|
106
|
-
if ("CAPTCHA.NO_SESSION_FOUND" !== store.state.error.key) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
// Suppressed only when something is actually going to re-mint: this is
|
|
110
|
-
// an internal recovery signal, not something the user should read, so
|
|
111
|
-
// hold the spinner rather than paint a support code that is about to
|
|
112
|
-
// stop being true. Clearing it re-enters this effect once, which returns
|
|
113
|
-
// at the `!error` guard. With no recovery route the error stands — a
|
|
114
|
-
// spinner that never resolves is worse than a message.
|
|
115
|
-
const willRecover =
|
|
116
|
-
(props.onSessionInvalidated && !sessionInvalidatedFired) ||
|
|
117
|
-
undefined !== frictionlessState;
|
|
118
|
-
if (willRecover) {
|
|
119
|
-
loading = true;
|
|
120
|
-
store.update({ error: undefined });
|
|
121
|
-
}
|
|
122
|
-
if (props.onSessionInvalidated && !sessionInvalidatedFired) {
|
|
123
|
-
// Preserve the checkbox coords across the retry so the resumed
|
|
124
|
-
// submit still carries the real entry-point telemetry. The
|
|
125
|
-
// frictionless wrapper mints a fresh session and re-mounts this
|
|
126
|
-
// widget with matching `autoStart` + `startCoords`.
|
|
127
|
-
sessionInvalidatedFired = true;
|
|
128
|
-
props.onSessionInvalidated(lastCoords?.x, lastCoords?.y);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (frictionlessState) {
|
|
132
|
-
// Fallback for widgets not mounted under a recovery-aware parent,
|
|
133
|
-
// or a second NO_SESSION_FOUND after we've already retried once.
|
|
134
|
-
const timer = setTimeout(() => {
|
|
135
|
-
frictionlessState.restart();
|
|
136
|
-
}, 100);
|
|
137
|
-
teardown.add(() => clearTimeout(timer));
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const render = () => {
|
|
142
|
-
runErrorEffect();
|
|
143
|
-
checkbox?.update(checkboxProps());
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const scheduler = createRenderScheduler(render);
|
|
147
|
-
|
|
148
|
-
const checkboxProps = (): CheckboxProps => ({
|
|
149
|
-
checked: store.state.isHuman,
|
|
150
|
-
theme: "light" === config.theme ? lightTheme : darkTheme,
|
|
151
|
-
labelText: translator.isReady() ? translator.t("WIDGET.I_AM_HUMAN") : "",
|
|
152
|
-
error: store.state.error?.message,
|
|
153
|
-
loadingText: translator.t("WIDGET.CHECKING", {
|
|
154
|
-
defaultValue: "Checking that you are human",
|
|
155
|
-
}),
|
|
156
|
-
loading,
|
|
157
|
-
onChange: async (
|
|
158
|
-
event: MouseEvent | KeyboardEvent | TouchEvent,
|
|
159
|
-
): Promise<void> => {
|
|
160
|
-
if (loading) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
loading = true;
|
|
164
|
-
scheduler.schedule();
|
|
165
|
-
|
|
166
|
-
// Capture click coordinates. Untrusted events are left at (0, 0) so a
|
|
167
|
-
// synthesised click can't seed the solution salt.
|
|
168
|
-
let x = 0;
|
|
169
|
-
let y = 0;
|
|
170
|
-
if (!isEventTrusted(event)) {
|
|
171
|
-
// Don't capture coordinates for non-trusted events
|
|
172
|
-
} else if ("touches" in event && event.touches.length > 0) {
|
|
173
|
-
const touch = event.touches[0];
|
|
174
|
-
if (touch) {
|
|
175
|
-
x = touch.clientX;
|
|
176
|
-
y = touch.clientY;
|
|
177
|
-
}
|
|
178
|
-
} else if ("clientX" in event && "clientY" in event) {
|
|
179
|
-
x = event.clientX;
|
|
180
|
-
y = event.clientY;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
lastCoords = { x, y };
|
|
184
|
-
try {
|
|
185
|
-
await manager.start(x, y);
|
|
186
|
-
} catch (error) {
|
|
187
|
-
// Nothing awaits this handler, so a rejection here would surface as
|
|
188
|
-
// an unhandled rejection and the spinner would never clear.
|
|
189
|
-
console.error("Error starting PoW verification:", error);
|
|
190
|
-
}
|
|
191
|
-
loading = false;
|
|
192
|
-
scheduler.schedule();
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
if (!isInvisible) {
|
|
197
|
-
checkbox = mountCheckbox(root, checkboxProps());
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
teardown.add(scheduler.cancel);
|
|
201
|
-
teardown.add(store.subscribe(scheduler.schedule));
|
|
202
|
-
teardown.add(translator.subscribe(scheduler.schedule));
|
|
203
|
-
|
|
204
|
-
// A bare execute() reaches every invisible widget via document. A targeted
|
|
205
|
-
// execute() is dispatched on this widget's container and works in either
|
|
206
|
-
// mode, which is what lets a bound button drive a visible widget.
|
|
207
|
-
const handleExecute = () => {
|
|
208
|
-
if (executeRunning) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
executeRunning = true;
|
|
212
|
-
if (!isInvisible) {
|
|
213
|
-
loading = true;
|
|
214
|
-
scheduler.schedule();
|
|
215
|
-
}
|
|
216
|
-
const logError = (error: unknown) => {
|
|
217
|
-
console.error("Error starting PoW verification:", error);
|
|
218
|
-
};
|
|
219
|
-
const done = () => {
|
|
220
|
-
executeRunning = false;
|
|
221
|
-
if (!isInvisible) {
|
|
222
|
-
loading = false;
|
|
223
|
-
scheduler.schedule();
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
try {
|
|
227
|
-
void manager.start().catch(logError).finally(done);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
logError(error);
|
|
230
|
-
done();
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
if (props.container) {
|
|
235
|
-
teardown.addEventListener(
|
|
236
|
-
props.container,
|
|
237
|
-
PROCAPTCHA_EXECUTE_EVENT,
|
|
238
|
-
handleExecute,
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
if (isInvisible) {
|
|
242
|
-
teardown.addEventListener(
|
|
243
|
-
document,
|
|
244
|
-
PROCAPTCHA_EXECUTE_EVENT,
|
|
245
|
-
handleExecute,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (config.language) {
|
|
250
|
-
if (i18n) {
|
|
251
|
-
if (i18n.language !== config.language) {
|
|
252
|
-
void i18n.changeLanguage(config.language);
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
// Direct consumers don't go through WidgetFactory, so pass the language
|
|
256
|
-
// into loadI18next — first init boots with the right language (skipping
|
|
257
|
-
// browser detection), and subsequent calls reconcile via changeLanguage
|
|
258
|
-
// inside loadI18next.
|
|
259
|
-
void loadI18next(false, config.language);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (props.autoStart) {
|
|
264
|
-
loading = true;
|
|
265
|
-
const coords = props.startCoords;
|
|
266
|
-
lastCoords = coords ?? null;
|
|
267
|
-
scheduler.schedule();
|
|
268
|
-
manager
|
|
269
|
-
.start(coords?.x ?? 0, coords?.y ?? 0)
|
|
270
|
-
// The manager handles its own failures, but anything it doesn't (a
|
|
271
|
-
// reset that throws, say) would otherwise escape as an unhandled
|
|
272
|
-
// rejection and leave the widget stuck showing a spinner.
|
|
273
|
-
.catch((error: unknown) => {
|
|
274
|
-
console.error("Error starting PoW verification:", error);
|
|
275
|
-
})
|
|
276
|
-
.finally(() => {
|
|
277
|
-
loading = false;
|
|
278
|
-
scheduler.schedule();
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
render();
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
destroy: () => {
|
|
286
|
-
teardown.run();
|
|
287
|
-
checkbox?.destroy();
|
|
288
|
-
honeypot?.destroy();
|
|
289
|
-
root.parentNode?.removeChild(root);
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
export default mountProcaptchaPowWidget;
|
package/src/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
-
//
|
|
3
|
-
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
// you may not use this file except in compliance with the License.
|
|
5
|
-
// You may obtain a copy of the License at
|
|
6
|
-
//
|
|
7
|
-
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
//
|
|
9
|
-
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
// See the License for the specific language governing permissions and
|
|
13
|
-
// limitations under the License.
|
|
14
|
-
export * from "./components/procaptchaWidget.js";
|
|
15
|
-
export * from "./components/procaptchaPoW.js";
|