@prosopo/procaptcha 2.10.57 → 2.10.59
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 +15 -14
- package/.turbo/turbo-build$colon$tsc.log +20 -20
- package/.turbo/turbo-build.log +17 -14
- package/CHANGELOG.md +48 -0
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/cjs/index.cjs +6 -8
- package/dist/cjs/modules/Manager.cjs +288 -384
- package/dist/cjs/modules/ProsopoCaptchaApi.cjs +41 -74
- package/dist/cjs/modules/collector.cjs +43 -73
- package/dist/cjs/modules/index.cjs +6 -8
- package/dist/index.js +3 -7
- package/dist/modules/Manager.d.ts.map +1 -1
- package/dist/modules/Manager.js +285 -382
- package/dist/modules/Manager.js.map +1 -1
- package/dist/modules/ProsopoCaptchaApi.d.ts.map +1 -1
- package/dist/modules/ProsopoCaptchaApi.js +40 -74
- package/dist/modules/ProsopoCaptchaApi.js.map +1 -1
- package/dist/modules/collector.js +44 -74
- package/dist/modules/index.js +2 -6
- package/dist/tests/collector.unit.test.d.ts +2 -0
- package/dist/tests/collector.unit.test.d.ts.map +1 -0
- package/dist/tests/collector.unit.test.js +173 -0
- package/dist/tests/collector.unit.test.js.map +1 -0
- package/dist/tests/manager.unit.test.d.ts +2 -0
- package/dist/tests/manager.unit.test.d.ts.map +1 -0
- package/dist/tests/manager.unit.test.js +788 -0
- package/dist/tests/manager.unit.test.js.map +1 -0
- package/dist/tests/managerHarness.d.ts +18 -0
- package/dist/tests/managerHarness.d.ts.map +1 -0
- package/dist/tests/managerHarness.js +94 -0
- package/dist/tests/managerHarness.js.map +1 -0
- package/dist/tests/procaptcha.test-d.d.ts +2 -0
- package/dist/tests/procaptcha.test-d.d.ts.map +1 -0
- package/dist/tests/procaptcha.test-d.js +78 -0
- package/dist/tests/procaptcha.test-d.js.map +1 -0
- package/dist/tests/prosopoCaptchaApi.unit.test.d.ts +2 -0
- package/dist/tests/prosopoCaptchaApi.unit.test.d.ts.map +1 -0
- package/dist/tests/prosopoCaptchaApi.unit.test.js +155 -0
- package/dist/tests/prosopoCaptchaApi.unit.test.js.map +1 -0
- package/package.json +17 -12
- package/vite.test.config.ts +25 -0
package/dist/modules/Manager.js
CHANGED
|
@@ -1,388 +1,291 @@
|
|
|
1
|
+
import { ProsopoCaptchaApi } from "./ProsopoCaptchaApi.js";
|
|
1
2
|
import { stringToHex } from "@polkadot/util/string";
|
|
2
3
|
import { ProviderApi } from "@prosopo/api";
|
|
3
|
-
import { ProsopoDatasetError,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { ProsopoDatasetError, ProsopoEnvError, ProsopoError } from "@prosopo/common";
|
|
5
|
+
import { ExtensionLoader, buildUpdateState, getDefaultEvents, getProcaptchaRandomActiveProvider, getSimdReadingsForSubmit, pickIpMode, providerRetry } from "@prosopo/procaptcha-common";
|
|
6
|
+
import { ApiParams, CaptchaType, ProcaptchaConfigSchema, encodeProcaptchaOutput } from "@prosopo/types";
|
|
7
|
+
import { at, embedData, hashToHex, sleep } from "@prosopo/util";
|
|
7
8
|
import { randomAsHex } from "@prosopo/util-crypto";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// don't handle timeout here, this should be handled by the state management
|
|
21
|
-
};
|
|
9
|
+
//#region src/modules/Manager.ts
|
|
10
|
+
var defaultState = () => {
|
|
11
|
+
return {
|
|
12
|
+
showModal: false,
|
|
13
|
+
loading: false,
|
|
14
|
+
index: 0,
|
|
15
|
+
challenge: void 0,
|
|
16
|
+
solutions: void 0,
|
|
17
|
+
isHuman: false,
|
|
18
|
+
captchaApi: void 0,
|
|
19
|
+
account: void 0
|
|
20
|
+
};
|
|
22
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* The state operator. This is used to mutate the state of Procaptcha during the captcha process. State updates are published via the onStateUpdate callback. This should be used by frontends, e.g. react, to maintain the state of Procaptcha across renders.
|
|
24
|
+
*/
|
|
23
25
|
function Manager(configOptional, state, onStateUpdate, callbacks, frictionlessState, getHoneypotValue) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
} else {
|
|
288
|
-
solutions[index] = [...solution, [hash, x || 0, y || 0]];
|
|
289
|
-
}
|
|
290
|
-
updateState({ solutions });
|
|
291
|
-
};
|
|
292
|
-
const nextRound = () => {
|
|
293
|
-
if (!state.challenge) {
|
|
294
|
-
throw new ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
295
|
-
context: { error: "Cannot select, no Captcha found in state" }
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
if (state.index + 1 >= state.challenge.captchas.length) {
|
|
299
|
-
throw new ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
300
|
-
context: {
|
|
301
|
-
error: "Cannot select, index is out of range for this Captcha"
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
updateState({ index: state.index + 1 });
|
|
306
|
-
};
|
|
307
|
-
const loadProviderApi = async (providerUrl) => {
|
|
308
|
-
const config = getConfig();
|
|
309
|
-
if (!config.account.address) {
|
|
310
|
-
throw new ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
311
|
-
}
|
|
312
|
-
return new ProviderApi(providerUrl, config.account.address);
|
|
313
|
-
};
|
|
314
|
-
const clearTimeout = () => {
|
|
315
|
-
window.clearTimeout(Number(state.timeout));
|
|
316
|
-
updateState({ timeout: void 0 });
|
|
317
|
-
};
|
|
318
|
-
const setValidChallengeTimeout = () => {
|
|
319
|
-
const timeMillis = configOptional.captchas.image.solutionTimeout;
|
|
320
|
-
const successfullChallengeTimeout = setTimeout(() => {
|
|
321
|
-
updateState({ isHuman: false });
|
|
322
|
-
events.onExpired();
|
|
323
|
-
}, timeMillis);
|
|
324
|
-
updateState({ successfullChallengeTimeout });
|
|
325
|
-
};
|
|
326
|
-
const resetState = (frictionlessRestart) => {
|
|
327
|
-
clearTimeout();
|
|
328
|
-
updateState(defaultState());
|
|
329
|
-
events.onReset();
|
|
330
|
-
if (frictionlessRestart) {
|
|
331
|
-
frictionlessRestart();
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
const loadAccount = async () => {
|
|
335
|
-
const config = getConfig();
|
|
336
|
-
if (!config.web2 && !config.userAccountAddress) {
|
|
337
|
-
throw new ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
338
|
-
context: { error: "Account address has not been set for web3 mode" }
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
const selectAccount = async () => {
|
|
342
|
-
const ext = new (await ExtensionLoader(config.web2))();
|
|
343
|
-
if (frictionlessState) {
|
|
344
|
-
return frictionlessState.userAccount;
|
|
345
|
-
}
|
|
346
|
-
return await ext.getAccount(config);
|
|
347
|
-
};
|
|
348
|
-
const account = await selectAccount();
|
|
349
|
-
updateState({ account });
|
|
350
|
-
return getAccount();
|
|
351
|
-
};
|
|
352
|
-
const getAccount = () => {
|
|
353
|
-
if (!state.account) {
|
|
354
|
-
throw new ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
355
|
-
context: { error: "Account not loaded" }
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
const account = state.account;
|
|
359
|
-
return account;
|
|
360
|
-
};
|
|
361
|
-
const getDappAccount = () => {
|
|
362
|
-
if (!state.dappAccount) {
|
|
363
|
-
throw new ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
364
|
-
}
|
|
365
|
-
const dappAccount = state.dappAccount;
|
|
366
|
-
return dappAccount;
|
|
367
|
-
};
|
|
368
|
-
const getExtension = (possiblyAccount) => {
|
|
369
|
-
const account = possiblyAccount || getAccount();
|
|
370
|
-
if (!account.extension) {
|
|
371
|
-
throw new ProsopoEnvError("ACCOUNT.NO_POLKADOT_EXTENSION", {
|
|
372
|
-
context: { error: "Extension not loaded" }
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
return account.extension;
|
|
376
|
-
};
|
|
377
|
-
return {
|
|
378
|
-
start,
|
|
379
|
-
cancel,
|
|
380
|
-
submit,
|
|
381
|
-
select,
|
|
382
|
-
nextRound,
|
|
383
|
-
reload
|
|
384
|
-
};
|
|
26
|
+
const events = getDefaultEvents(callbacks);
|
|
27
|
+
const updateState = buildUpdateState(state, onStateUpdate);
|
|
28
|
+
let checkboxClickX = 0;
|
|
29
|
+
let checkboxClickY = 0;
|
|
30
|
+
let previousProviderUrl;
|
|
31
|
+
/**
|
|
32
|
+
* Build the config on demand, using the optional config passed in from the outside. State may override various
|
|
33
|
+
* config values depending on the state of the captcha process. E.g. if the process has been started using account
|
|
34
|
+
* "ABC" and then the user changes account to "DEF" via the optional config prop, the account in use will not change.
|
|
35
|
+
* This is because the captcha process has already been started using account "ABC".
|
|
36
|
+
* @returns the config for procaptcha
|
|
37
|
+
*/
|
|
38
|
+
const getConfig = () => {
|
|
39
|
+
const config = {
|
|
40
|
+
userAccountAddress: "",
|
|
41
|
+
...configOptional
|
|
42
|
+
};
|
|
43
|
+
if (state.account) config.userAccountAddress = state.account.account.address;
|
|
44
|
+
return ProcaptchaConfigSchema.parse(config);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Called on start of user verification. This is when the user ticks the box to claim they are human.
|
|
48
|
+
*/
|
|
49
|
+
const start = async (checkboxX = 0, checkboxY = 0) => {
|
|
50
|
+
checkboxClickX = checkboxX;
|
|
51
|
+
checkboxClickY = checkboxY;
|
|
52
|
+
events.onOpen();
|
|
53
|
+
await providerRetry(async () => {
|
|
54
|
+
if (state.loading) return;
|
|
55
|
+
if (state.isHuman) return;
|
|
56
|
+
updateState({ loading: true });
|
|
57
|
+
updateState({ attemptCount: state.attemptCount ? state.attemptCount + 1 : 1 });
|
|
58
|
+
updateState({ sessionId: frictionlessState?.sessionId });
|
|
59
|
+
const config = getConfig();
|
|
60
|
+
updateState({ dappAccount: config.account.address });
|
|
61
|
+
await sleep(100);
|
|
62
|
+
const account = await loadAccount();
|
|
63
|
+
let captchaApi = state.captchaApi;
|
|
64
|
+
if (!frictionlessState?.provider) {
|
|
65
|
+
const currentConfig = getConfig();
|
|
66
|
+
const getRandomProviderResponse = await getProcaptchaRandomActiveProvider(currentConfig.defaultEnvironment, pickIpMode(currentConfig), {
|
|
67
|
+
attempt: state.attemptCount,
|
|
68
|
+
excludeUrl: previousProviderUrl
|
|
69
|
+
});
|
|
70
|
+
const providerUrl = getRandomProviderResponse.provider.url;
|
|
71
|
+
previousProviderUrl = providerUrl;
|
|
72
|
+
const { providerApi, siteKey } = await loadProviderApi(providerUrl);
|
|
73
|
+
captchaApi = new ProsopoCaptchaApi(account.account.address, getRandomProviderResponse, providerApi, config.web2, siteKey);
|
|
74
|
+
updateState({ captchaApi });
|
|
75
|
+
} else {
|
|
76
|
+
const providerUrl = frictionlessState.provider.provider.url;
|
|
77
|
+
const { providerApi, siteKey } = await loadProviderApi(providerUrl);
|
|
78
|
+
captchaApi = new ProsopoCaptchaApi(account.account.address, frictionlessState.provider, providerApi, config.web2, siteKey);
|
|
79
|
+
updateState({ captchaApi });
|
|
80
|
+
}
|
|
81
|
+
const simdReadingsOnChallenge = frictionlessState?.getSimdReadings ? await frictionlessState.getSimdReadings(0) : void 0;
|
|
82
|
+
const challenge = await captchaApi?.getCaptchaChallenge(state.sessionId, simdReadingsOnChallenge);
|
|
83
|
+
if (challenge.error) {
|
|
84
|
+
updateState({
|
|
85
|
+
loading: false,
|
|
86
|
+
error: {
|
|
87
|
+
message: challenge.error.message,
|
|
88
|
+
key: challenge.error.key || "API.UNKNOWN_ERROR"
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
events.onError(new Error(challenge.error?.message));
|
|
92
|
+
} else {
|
|
93
|
+
if (challenge.captchas.length <= 0) throw new ProsopoDatasetError("DEVELOPER.PROVIDER_NO_CAPTCHA");
|
|
94
|
+
const timeMillis = challenge.captchas.map((captcha) => captcha.timeLimitMs || config.captchas.image.challengeTimeout).reduce((a, b) => a + b);
|
|
95
|
+
const timeout = setTimeout(() => {
|
|
96
|
+
events.onChallengeExpired();
|
|
97
|
+
updateState({
|
|
98
|
+
isHuman: false,
|
|
99
|
+
showModal: false,
|
|
100
|
+
loading: false
|
|
101
|
+
});
|
|
102
|
+
}, timeMillis);
|
|
103
|
+
updateState({
|
|
104
|
+
index: 0,
|
|
105
|
+
solutions: challenge.captchas.map(() => []),
|
|
106
|
+
challenge,
|
|
107
|
+
showModal: true,
|
|
108
|
+
timeout,
|
|
109
|
+
loading: false
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}, start, resetState, state.attemptCount, 10);
|
|
113
|
+
};
|
|
114
|
+
const submit = async () => {
|
|
115
|
+
await providerRetry(async () => {
|
|
116
|
+
clearTimeout();
|
|
117
|
+
if (!state.challenge) throw new ProsopoError("CAPTCHA.NO_CAPTCHA", { context: { error: "Cannot submit, no Captcha found in state" } });
|
|
118
|
+
updateState({ showModal: false });
|
|
119
|
+
const challenge = state.challenge;
|
|
120
|
+
const captchaSolution = state.challenge.captchas.map((captcha, index) => {
|
|
121
|
+
const solution = at(state.solutions, index);
|
|
122
|
+
const shapeCoords = solution.flatMap(([_, x, y]) => [x, y]);
|
|
123
|
+
const coords = index === 0 ? [
|
|
124
|
+
checkboxClickX,
|
|
125
|
+
checkboxClickY,
|
|
126
|
+
...shapeCoords
|
|
127
|
+
] : shapeCoords;
|
|
128
|
+
const saltCoord = embedData(randomAsHex(coords.map((x) => x.toString(16).length + 4).reduce((acc, curr) => acc + curr, 0)), coords);
|
|
129
|
+
return {
|
|
130
|
+
captchaId: captcha.captchaId,
|
|
131
|
+
captchaContentId: captcha.captchaContentId,
|
|
132
|
+
salt: saltCoord,
|
|
133
|
+
solution: solution.flatMap((s) => s[0])
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
const account = getAccount();
|
|
137
|
+
const signer = getExtension(account).signer;
|
|
138
|
+
if (!at(challenge.captchas, 0).datasetId) throw new ProsopoDatasetError("CAPTCHA.INVALID_CAPTCHA_ID", { context: { error: "No datasetId set for challenge" } });
|
|
139
|
+
const captchaApi = state.captchaApi;
|
|
140
|
+
if (!captchaApi) throw new ProsopoError("CAPTCHA.INVALID_TOKEN", { context: { error: "No Captcha API found in state" } });
|
|
141
|
+
if (!signer || !signer.signRaw) throw new ProsopoEnvError("GENERAL.CANT_FIND_KEYRINGPAIR", { context: { error: "Signer is not defined, cannot sign message to prove account ownership" } });
|
|
142
|
+
const userTimestampSignature = await signer.signRaw({
|
|
143
|
+
address: account.account.address,
|
|
144
|
+
data: stringToHex(challenge[ApiParams.timestamp]),
|
|
145
|
+
type: "bytes"
|
|
146
|
+
});
|
|
147
|
+
let encryptedBehavioralData;
|
|
148
|
+
if (frictionlessState?.encryptBehavioralData && (frictionlessState?.behaviorCollector1 || frictionlessState?.behaviorCollector2 || frictionlessState?.behaviorCollector3)) try {
|
|
149
|
+
const behavioralData = {
|
|
150
|
+
collector1: frictionlessState.behaviorCollector1?.getData() || [],
|
|
151
|
+
collector2: frictionlessState.behaviorCollector2?.getData() || [],
|
|
152
|
+
collector3: frictionlessState.behaviorCollector3?.getData() || [],
|
|
153
|
+
deviceCapability: frictionlessState.deviceCapability || "unknown"
|
|
154
|
+
};
|
|
155
|
+
const dataToEncrypt = frictionlessState.packBehavioralData ? frictionlessState.packBehavioralData(behavioralData) : behavioralData;
|
|
156
|
+
encryptedBehavioralData = await frictionlessState.encryptBehavioralData(JSON.stringify(dataToEncrypt));
|
|
157
|
+
} catch {}
|
|
158
|
+
const simdReadings = await getSimdReadingsForSubmit(frictionlessState);
|
|
159
|
+
const hpValue = getHoneypotValue?.();
|
|
160
|
+
const clientMetaData = hpValue ? { hp: hpValue } : void 0;
|
|
161
|
+
const submission = await captchaApi.submitCaptchaSolution(userTimestampSignature.signature, challenge.requestHash, captchaSolution, challenge.timestamp, challenge.signature.provider.requestHash, encryptedBehavioralData, simdReadings, clientMetaData);
|
|
162
|
+
const isHuman = submission[0].verified;
|
|
163
|
+
updateState({
|
|
164
|
+
submission,
|
|
165
|
+
isHuman,
|
|
166
|
+
loading: false
|
|
167
|
+
});
|
|
168
|
+
if (state.isHuman) {
|
|
169
|
+
const providerUrl = captchaApi.provider.provider.url;
|
|
170
|
+
events.onHuman(encodeProcaptchaOutput({
|
|
171
|
+
[ApiParams.providerUrl]: providerUrl,
|
|
172
|
+
[ApiParams.user]: account.account.address,
|
|
173
|
+
[ApiParams.dapp]: getDappAccount(),
|
|
174
|
+
[ApiParams.commitmentId]: hashToHex(submission[1]),
|
|
175
|
+
[ApiParams.timestamp]: challenge.timestamp,
|
|
176
|
+
[ApiParams.signature]: {
|
|
177
|
+
[ApiParams.provider]: { [ApiParams.requestHash]: challenge.signature.provider.requestHash },
|
|
178
|
+
[ApiParams.user]: { [ApiParams.timestamp]: userTimestampSignature.signature }
|
|
179
|
+
},
|
|
180
|
+
[ApiParams.captchaType]: CaptchaType.image
|
|
181
|
+
}));
|
|
182
|
+
setValidChallengeTimeout();
|
|
183
|
+
} else {
|
|
184
|
+
events.onFailed();
|
|
185
|
+
resetState(frictionlessState?.restart);
|
|
186
|
+
}
|
|
187
|
+
}, start, resetState, state.attemptCount, 10);
|
|
188
|
+
};
|
|
189
|
+
const cancel = async () => {
|
|
190
|
+
clearTimeout();
|
|
191
|
+
resetState(frictionlessState?.restart);
|
|
192
|
+
events.onClose();
|
|
193
|
+
};
|
|
194
|
+
const reload = async () => {
|
|
195
|
+
clearTimeout();
|
|
196
|
+
events.onReload();
|
|
197
|
+
resetState(frictionlessState?.restart);
|
|
198
|
+
if (!frictionlessState?.restart) await start();
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* (De)Select an image from the solution for the current round. If the hash is already in the solutions list, it will be removed (deselected) and if not it will be added (selected).
|
|
202
|
+
* @param hash the hash of the image
|
|
203
|
+
* @param x
|
|
204
|
+
* @param y
|
|
205
|
+
*/
|
|
206
|
+
const select = (hash, x, y) => {
|
|
207
|
+
if (!state.challenge) throw new ProsopoError("CAPTCHA.NO_CAPTCHA", { context: { error: "Cannot select, no Captcha found in state" } });
|
|
208
|
+
if (state.index >= state.challenge.captchas.length || state.index < 0) throw new ProsopoError("CAPTCHA.NO_CAPTCHA", { context: { error: "Cannot select, index is out of range for this Captcha" } });
|
|
209
|
+
const index = state.index;
|
|
210
|
+
const solutions = state.solutions;
|
|
211
|
+
const solution = at(solutions, index);
|
|
212
|
+
if (solution.some((s) => s[0] === hash)) solutions[index] = solution.filter((s) => s[0] !== hash);
|
|
213
|
+
else solutions[index] = [...solution, [
|
|
214
|
+
hash,
|
|
215
|
+
x || 0,
|
|
216
|
+
y || 0
|
|
217
|
+
]];
|
|
218
|
+
updateState({ solutions });
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Proceed to the next round of the challenge.
|
|
222
|
+
*/
|
|
223
|
+
const nextRound = () => {
|
|
224
|
+
if (!state.challenge) throw new ProsopoError("CAPTCHA.NO_CAPTCHA", { context: { error: "Cannot select, no Captcha found in state" } });
|
|
225
|
+
if (state.index + 1 >= state.challenge.captchas.length) throw new ProsopoError("CAPTCHA.NO_CAPTCHA", { context: { error: "Cannot select, index is out of range for this Captcha" } });
|
|
226
|
+
updateState({ index: state.index + 1 });
|
|
227
|
+
};
|
|
228
|
+
const loadProviderApi = async (providerUrl) => {
|
|
229
|
+
const siteKey = getConfig().account.address;
|
|
230
|
+
if (!siteKey) throw new ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
231
|
+
return {
|
|
232
|
+
providerApi: new ProviderApi(providerUrl, siteKey),
|
|
233
|
+
siteKey
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
const clearTimeout = () => {
|
|
237
|
+
window.clearTimeout(Number(state.timeout));
|
|
238
|
+
updateState({ timeout: void 0 });
|
|
239
|
+
};
|
|
240
|
+
const setValidChallengeTimeout = () => {
|
|
241
|
+
const timeMillis = configOptional.captchas.image.solutionTimeout;
|
|
242
|
+
const successfullChallengeTimeout = setTimeout(() => {
|
|
243
|
+
updateState({ isHuman: false });
|
|
244
|
+
events.onExpired();
|
|
245
|
+
}, timeMillis);
|
|
246
|
+
updateState({ successfullChallengeTimeout });
|
|
247
|
+
};
|
|
248
|
+
const resetState = (frictionlessRestart) => {
|
|
249
|
+
clearTimeout();
|
|
250
|
+
updateState(defaultState());
|
|
251
|
+
events.onReset();
|
|
252
|
+
if (frictionlessRestart) frictionlessRestart();
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Load the account using address specified in config, or generate new address if not found in local storage for web2 mode.
|
|
256
|
+
*/
|
|
257
|
+
const loadAccount = async () => {
|
|
258
|
+
const config = getConfig();
|
|
259
|
+
if (!config.web2 && !config.userAccountAddress) throw new ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", { context: { error: "Account address has not been set for web3 mode" } });
|
|
260
|
+
const selectAccount = async () => {
|
|
261
|
+
const ext = new (await (ExtensionLoader(config.web2)))();
|
|
262
|
+
if (frictionlessState) return frictionlessState.userAccount;
|
|
263
|
+
return await ext.getAccount(config);
|
|
264
|
+
};
|
|
265
|
+
const account = await selectAccount();
|
|
266
|
+
updateState({ account });
|
|
267
|
+
return getAccount();
|
|
268
|
+
};
|
|
269
|
+
const getAccount = () => {
|
|
270
|
+
if (!state.account) throw new ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", { context: { error: "Account not loaded" } });
|
|
271
|
+
return state.account;
|
|
272
|
+
};
|
|
273
|
+
const getDappAccount = () => {
|
|
274
|
+
if (!state.dappAccount) throw new ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
275
|
+
return state.dappAccount;
|
|
276
|
+
};
|
|
277
|
+
const getExtension = (account) => {
|
|
278
|
+
if (!account.extension) throw new ProsopoEnvError("ACCOUNT.NO_POLKADOT_EXTENSION", { context: { error: "Extension not loaded" } });
|
|
279
|
+
return account.extension;
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
start,
|
|
283
|
+
cancel,
|
|
284
|
+
submit,
|
|
285
|
+
select,
|
|
286
|
+
nextRound,
|
|
287
|
+
reload
|
|
288
|
+
};
|
|
385
289
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
};
|
|
290
|
+
//#endregion
|
|
291
|
+
export { Manager };
|