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