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