@prosopo/procaptcha 2.5.5 → 2.6.2
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 +43 -0
- package/dist/cjs/index.cjs +9 -0
- package/dist/cjs/modules/Manager.cjs +348 -0
- package/dist/cjs/modules/ProsopoCaptchaApi.cjs +73 -0
- package/dist/cjs/modules/collector.cjs +80 -0
- package/dist/cjs/modules/index.cjs +8 -0
- package/package.json +12 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @prosopo/procaptcha
|
|
2
|
+
|
|
3
|
+
## 2.6.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [6ff193a]
|
|
8
|
+
- @prosopo/procaptcha-common@2.6.2
|
|
9
|
+
- @prosopo/datasets@2.6.2
|
|
10
|
+
- @prosopo/types@2.6.2
|
|
11
|
+
- @prosopo/account@2.6.2
|
|
12
|
+
- @prosopo/api@2.6.2
|
|
13
|
+
- @prosopo/load-balancer@2.6.2
|
|
14
|
+
|
|
15
|
+
## 2.6.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [52feffc]
|
|
20
|
+
- @prosopo/datasets@2.6.1
|
|
21
|
+
- @prosopo/types@2.6.1
|
|
22
|
+
- @prosopo/account@2.6.1
|
|
23
|
+
- @prosopo/api@2.6.1
|
|
24
|
+
- @prosopo/load-balancer@2.6.1
|
|
25
|
+
- @prosopo/procaptcha-common@2.6.1
|
|
26
|
+
|
|
27
|
+
## 2.6.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- a0bfc8a: bump all pkg versions since independent versioning applied
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies [a0bfc8a]
|
|
36
|
+
- @prosopo/account@2.6.0
|
|
37
|
+
- @prosopo/api@2.6.0
|
|
38
|
+
- @prosopo/common@2.6.0
|
|
39
|
+
- @prosopo/datasets@2.6.0
|
|
40
|
+
- @prosopo/load-balancer@2.6.0
|
|
41
|
+
- @prosopo/procaptcha-common@2.6.0
|
|
42
|
+
- @prosopo/types@2.6.0
|
|
43
|
+
- @prosopo/util@2.6.0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
require("./modules/index.cjs");
|
|
4
|
+
const Manager = require("./modules/Manager.cjs");
|
|
5
|
+
const ProsopoCaptchaApi = require("./modules/ProsopoCaptchaApi.cjs");
|
|
6
|
+
const collector = require("./modules/collector.cjs");
|
|
7
|
+
exports.Manager = Manager.Manager;
|
|
8
|
+
exports.ProsopoCaptchaApi = ProsopoCaptchaApi.ProsopoCaptchaApi;
|
|
9
|
+
exports.startCollector = collector.startCollector;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const random = require("@polkadot/util-crypto/random");
|
|
4
|
+
const string = require("@polkadot/util/string");
|
|
5
|
+
const api = require("@prosopo/api");
|
|
6
|
+
const common = require("@prosopo/common");
|
|
7
|
+
const procaptchaCommon = require("@prosopo/procaptcha-common");
|
|
8
|
+
const types = require("@prosopo/types");
|
|
9
|
+
const util = require("@prosopo/util");
|
|
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
|
+
};
|
|
24
|
+
};
|
|
25
|
+
function Manager(configOptional, state, onStateUpdate, callbacks, frictionlessState) {
|
|
26
|
+
const events = procaptchaCommon.getDefaultEvents(callbacks);
|
|
27
|
+
const updateState = procaptchaCommon.buildUpdateState(state, onStateUpdate);
|
|
28
|
+
const getConfig = () => {
|
|
29
|
+
const config = {
|
|
30
|
+
userAccountAddress: "",
|
|
31
|
+
...configOptional
|
|
32
|
+
};
|
|
33
|
+
if (state.account) {
|
|
34
|
+
config.userAccountAddress = state.account.account.address;
|
|
35
|
+
}
|
|
36
|
+
return types.ProcaptchaConfigSchema.parse(config);
|
|
37
|
+
};
|
|
38
|
+
const start = async () => {
|
|
39
|
+
events.onOpen();
|
|
40
|
+
await procaptchaCommon.providerRetry(
|
|
41
|
+
async () => {
|
|
42
|
+
if (state.loading) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (state.isHuman) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
updateState({ loading: true });
|
|
49
|
+
updateState({
|
|
50
|
+
attemptCount: state.attemptCount ? state.attemptCount + 1 : 1
|
|
51
|
+
});
|
|
52
|
+
updateState({
|
|
53
|
+
sessionId: frictionlessState?.sessionId
|
|
54
|
+
});
|
|
55
|
+
const config = getConfig();
|
|
56
|
+
updateState({ dappAccount: config.account.address });
|
|
57
|
+
await util.sleep(100);
|
|
58
|
+
const account = await loadAccount();
|
|
59
|
+
let captchaApi = state.captchaApi;
|
|
60
|
+
if (!frictionlessState?.provider) {
|
|
61
|
+
const getRandomProviderResponse = await procaptchaCommon.getRandomActiveProvider(
|
|
62
|
+
getConfig()
|
|
63
|
+
);
|
|
64
|
+
const providerUrl = getRandomProviderResponse.provider.url;
|
|
65
|
+
const providerApi = await loadProviderApi(providerUrl);
|
|
66
|
+
captchaApi = new ProsopoCaptchaApi.ProsopoCaptchaApi(
|
|
67
|
+
account.account.address,
|
|
68
|
+
getRandomProviderResponse,
|
|
69
|
+
providerApi,
|
|
70
|
+
config.web2,
|
|
71
|
+
config.account.address || ""
|
|
72
|
+
);
|
|
73
|
+
updateState({ captchaApi });
|
|
74
|
+
} else {
|
|
75
|
+
const providerUrl = frictionlessState.provider.provider.url;
|
|
76
|
+
const providerApi = await loadProviderApi(providerUrl);
|
|
77
|
+
captchaApi = new ProsopoCaptchaApi.ProsopoCaptchaApi(
|
|
78
|
+
account.account.address,
|
|
79
|
+
frictionlessState.provider,
|
|
80
|
+
providerApi,
|
|
81
|
+
config.web2,
|
|
82
|
+
config.account.address || ""
|
|
83
|
+
);
|
|
84
|
+
updateState({ captchaApi });
|
|
85
|
+
}
|
|
86
|
+
const challenge = await captchaApi?.getCaptchaChallenge(
|
|
87
|
+
state.sessionId
|
|
88
|
+
);
|
|
89
|
+
if (challenge.error) {
|
|
90
|
+
updateState({
|
|
91
|
+
loading: false,
|
|
92
|
+
error: {
|
|
93
|
+
message: challenge.error.message,
|
|
94
|
+
key: challenge.error.key || "API.UNKNOWN_ERROR"
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
events.onError(new Error(challenge.error?.message));
|
|
98
|
+
} else {
|
|
99
|
+
if (challenge.captchas.length <= 0) {
|
|
100
|
+
throw new common.ProsopoDatasetError("DEVELOPER.PROVIDER_NO_CAPTCHA");
|
|
101
|
+
}
|
|
102
|
+
const timeMillis = challenge.captchas.map(
|
|
103
|
+
(captcha) => captcha.timeLimitMs || config.captchas.image.challengeTimeout
|
|
104
|
+
).reduce((a, b) => a + b);
|
|
105
|
+
const timeout = setTimeout(() => {
|
|
106
|
+
events.onChallengeExpired();
|
|
107
|
+
updateState({ isHuman: false, showModal: false, loading: false });
|
|
108
|
+
}, timeMillis);
|
|
109
|
+
updateState({
|
|
110
|
+
index: 0,
|
|
111
|
+
solutions: challenge.captchas.map(() => []),
|
|
112
|
+
challenge,
|
|
113
|
+
showModal: true,
|
|
114
|
+
timeout,
|
|
115
|
+
loading: false
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
start,
|
|
120
|
+
resetState,
|
|
121
|
+
state.attemptCount,
|
|
122
|
+
10
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
const submit = async () => {
|
|
126
|
+
await procaptchaCommon.providerRetry(
|
|
127
|
+
async () => {
|
|
128
|
+
clearTimeout();
|
|
129
|
+
if (!state.challenge) {
|
|
130
|
+
throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
131
|
+
context: { error: "Cannot submit, no Captcha found in state" }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
updateState({ showModal: false });
|
|
135
|
+
const challenge = state.challenge;
|
|
136
|
+
const salt = random.randomAsHex();
|
|
137
|
+
const captchaSolution = state.challenge.captchas.map(
|
|
138
|
+
(captcha, index) => {
|
|
139
|
+
const solution = util.at(state.solutions, index);
|
|
140
|
+
return {
|
|
141
|
+
captchaId: captcha.captchaId,
|
|
142
|
+
captchaContentId: captcha.captchaContentId,
|
|
143
|
+
salt,
|
|
144
|
+
solution
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
const account = getAccount();
|
|
149
|
+
const signer = getExtension(account).signer;
|
|
150
|
+
const first = util.at(challenge.captchas, 0);
|
|
151
|
+
if (!first.datasetId) {
|
|
152
|
+
throw new common.ProsopoDatasetError("CAPTCHA.INVALID_CAPTCHA_ID", {
|
|
153
|
+
context: { error: "No datasetId set for challenge" }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const captchaApi = state.captchaApi;
|
|
157
|
+
if (!captchaApi) {
|
|
158
|
+
throw new common.ProsopoError("CAPTCHA.INVALID_TOKEN", {
|
|
159
|
+
context: { error: "No Captcha API found in state" }
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (!signer || !signer.signRaw) {
|
|
163
|
+
throw new common.ProsopoEnvError("GENERAL.CANT_FIND_KEYRINGPAIR", {
|
|
164
|
+
context: {
|
|
165
|
+
error: "Signer is not defined, cannot sign message to prove account ownership"
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const userTimestampSignature = await signer.signRaw({
|
|
170
|
+
address: account.account.address,
|
|
171
|
+
data: string.stringToHex(challenge[types.ApiParams.timestamp]),
|
|
172
|
+
type: "bytes"
|
|
173
|
+
});
|
|
174
|
+
const submission = await captchaApi.submitCaptchaSolution(
|
|
175
|
+
userTimestampSignature.signature,
|
|
176
|
+
challenge.requestHash,
|
|
177
|
+
captchaSolution,
|
|
178
|
+
challenge.timestamp,
|
|
179
|
+
challenge.signature.provider.requestHash
|
|
180
|
+
);
|
|
181
|
+
const isHuman = submission[0].verified;
|
|
182
|
+
updateState({
|
|
183
|
+
submission,
|
|
184
|
+
isHuman,
|
|
185
|
+
loading: false
|
|
186
|
+
});
|
|
187
|
+
if (state.isHuman) {
|
|
188
|
+
const providerUrl = captchaApi.provider.provider.url;
|
|
189
|
+
events.onHuman(
|
|
190
|
+
types.encodeProcaptchaOutput({
|
|
191
|
+
[types.ApiParams.providerUrl]: providerUrl,
|
|
192
|
+
[types.ApiParams.user]: account.account.address,
|
|
193
|
+
[types.ApiParams.dapp]: getDappAccount(),
|
|
194
|
+
[types.ApiParams.commitmentId]: util.hashToHex(submission[1]),
|
|
195
|
+
[types.ApiParams.timestamp]: challenge.timestamp,
|
|
196
|
+
[types.ApiParams.signature]: {
|
|
197
|
+
[types.ApiParams.provider]: {
|
|
198
|
+
[types.ApiParams.requestHash]: challenge.signature.provider.requestHash
|
|
199
|
+
},
|
|
200
|
+
[types.ApiParams.user]: {
|
|
201
|
+
[types.ApiParams.timestamp]: userTimestampSignature.signature
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
setValidChallengeTimeout();
|
|
207
|
+
} else {
|
|
208
|
+
events.onFailed();
|
|
209
|
+
resetState(frictionlessState?.restart);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
start,
|
|
213
|
+
resetState,
|
|
214
|
+
state.attemptCount,
|
|
215
|
+
10
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
const cancel = async () => {
|
|
219
|
+
clearTimeout();
|
|
220
|
+
resetState(frictionlessState?.restart);
|
|
221
|
+
events.onClose();
|
|
222
|
+
};
|
|
223
|
+
const reload = async () => {
|
|
224
|
+
clearTimeout();
|
|
225
|
+
events.onReload();
|
|
226
|
+
resetState(frictionlessState?.restart);
|
|
227
|
+
if (!frictionlessState?.restart) {
|
|
228
|
+
await start();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const select = (hash) => {
|
|
232
|
+
if (!state.challenge) {
|
|
233
|
+
throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
234
|
+
context: { error: "Cannot select, no Captcha found in state" }
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (state.index >= state.challenge.captchas.length || state.index < 0) {
|
|
238
|
+
throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
239
|
+
context: {
|
|
240
|
+
error: "Cannot select, index is out of range for this Captcha"
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const index = state.index;
|
|
245
|
+
const solutions = state.solutions;
|
|
246
|
+
const solution = util.at(solutions, index);
|
|
247
|
+
if (solution.includes(hash)) {
|
|
248
|
+
solution.splice(solution.indexOf(hash), 1);
|
|
249
|
+
} else {
|
|
250
|
+
solution.push(hash);
|
|
251
|
+
}
|
|
252
|
+
updateState({ solutions });
|
|
253
|
+
};
|
|
254
|
+
const nextRound = () => {
|
|
255
|
+
if (!state.challenge) {
|
|
256
|
+
throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
257
|
+
context: { error: "Cannot select, no Captcha found in state" }
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (state.index + 1 >= state.challenge.captchas.length) {
|
|
261
|
+
throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
|
|
262
|
+
context: {
|
|
263
|
+
error: "Cannot select, index is out of range for this Captcha"
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
updateState({ index: state.index + 1 });
|
|
268
|
+
};
|
|
269
|
+
const loadProviderApi = async (providerUrl) => {
|
|
270
|
+
const config = getConfig();
|
|
271
|
+
if (!config.account.address) {
|
|
272
|
+
throw new common.ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
273
|
+
}
|
|
274
|
+
return new api.ProviderApi(providerUrl, config.account.address);
|
|
275
|
+
};
|
|
276
|
+
const clearTimeout = () => {
|
|
277
|
+
window.clearTimeout(Number(state.timeout));
|
|
278
|
+
updateState({ timeout: void 0 });
|
|
279
|
+
};
|
|
280
|
+
const setValidChallengeTimeout = () => {
|
|
281
|
+
const timeMillis = configOptional.captchas.image.solutionTimeout;
|
|
282
|
+
const successfullChallengeTimeout = setTimeout(() => {
|
|
283
|
+
updateState({ isHuman: false });
|
|
284
|
+
events.onExpired();
|
|
285
|
+
}, timeMillis);
|
|
286
|
+
updateState({ successfullChallengeTimeout });
|
|
287
|
+
};
|
|
288
|
+
const resetState = (frictionlessRestart) => {
|
|
289
|
+
clearTimeout();
|
|
290
|
+
updateState(defaultState());
|
|
291
|
+
events.onReset();
|
|
292
|
+
if (frictionlessRestart) {
|
|
293
|
+
frictionlessRestart();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const loadAccount = async () => {
|
|
297
|
+
const config = getConfig();
|
|
298
|
+
if (!config.web2 && !config.userAccountAddress) {
|
|
299
|
+
throw new common.ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
300
|
+
context: { error: "Account address has not been set for web3 mode" }
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const selectAccount = async () => {
|
|
304
|
+
const ext = new (await procaptchaCommon.ExtensionLoader(config.web2))();
|
|
305
|
+
if (frictionlessState) {
|
|
306
|
+
return frictionlessState.userAccount;
|
|
307
|
+
}
|
|
308
|
+
return await ext.getAccount(config);
|
|
309
|
+
};
|
|
310
|
+
const account = await selectAccount();
|
|
311
|
+
updateState({ account });
|
|
312
|
+
return getAccount();
|
|
313
|
+
};
|
|
314
|
+
const getAccount = () => {
|
|
315
|
+
if (!state.account) {
|
|
316
|
+
throw new common.ProsopoEnvError("GENERAL.ACCOUNT_NOT_FOUND", {
|
|
317
|
+
context: { error: "Account not loaded" }
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const account = state.account;
|
|
321
|
+
return account;
|
|
322
|
+
};
|
|
323
|
+
const getDappAccount = () => {
|
|
324
|
+
if (!state.dappAccount) {
|
|
325
|
+
throw new common.ProsopoEnvError("GENERAL.SITE_KEY_MISSING");
|
|
326
|
+
}
|
|
327
|
+
const dappAccount = state.dappAccount;
|
|
328
|
+
return dappAccount;
|
|
329
|
+
};
|
|
330
|
+
const getExtension = (possiblyAccount) => {
|
|
331
|
+
const account = possiblyAccount || getAccount();
|
|
332
|
+
if (!account.extension) {
|
|
333
|
+
throw new common.ProsopoEnvError("ACCOUNT.NO_POLKADOT_EXTENSION", {
|
|
334
|
+
context: { error: "Extension not loaded" }
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return account.extension;
|
|
338
|
+
};
|
|
339
|
+
return {
|
|
340
|
+
start,
|
|
341
|
+
cancel,
|
|
342
|
+
submit,
|
|
343
|
+
select,
|
|
344
|
+
nextRound,
|
|
345
|
+
reload
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
exports.Manager = Manager;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
+
const common = require("@prosopo/common");
|
|
4
|
+
const datasets = require("@prosopo/datasets");
|
|
5
|
+
const types = require("@prosopo/types");
|
|
6
|
+
class ProsopoCaptchaApi {
|
|
7
|
+
constructor(userAccount, provider, providerApi, web2, dappAccount) {
|
|
8
|
+
this.userAccount = userAccount;
|
|
9
|
+
this.provider = provider;
|
|
10
|
+
this.providerApi = providerApi;
|
|
11
|
+
this._web2 = web2;
|
|
12
|
+
this.dappAccount = dappAccount;
|
|
13
|
+
}
|
|
14
|
+
get web2() {
|
|
15
|
+
return this._web2;
|
|
16
|
+
}
|
|
17
|
+
async getCaptchaChallenge(sessionId) {
|
|
18
|
+
try {
|
|
19
|
+
const captchaChallenge = await this.providerApi.getCaptchaChallenge(
|
|
20
|
+
this.userAccount,
|
|
21
|
+
this.provider,
|
|
22
|
+
sessionId
|
|
23
|
+
);
|
|
24
|
+
if (captchaChallenge[types.ApiParams.error]) {
|
|
25
|
+
return captchaChallenge;
|
|
26
|
+
}
|
|
27
|
+
for (const captcha of captchaChallenge.captchas) {
|
|
28
|
+
for (const item of captcha.items) {
|
|
29
|
+
if (item.data) {
|
|
30
|
+
item.data = `https://${item.data.replace(/^http(s)*:\/\//, "")}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return captchaChallenge;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new common.ProsopoEnvError("CAPTCHA.INVALID_CAPTCHA_CHALLENGE", {
|
|
37
|
+
context: { error }
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async submitCaptchaSolution(userTimestampSignature, requestHash, solutions, timestamp, providerRequestHashSignature) {
|
|
42
|
+
const tree = new datasets.CaptchaMerkleTree();
|
|
43
|
+
const captchasHashed = solutions.map(
|
|
44
|
+
(captcha) => datasets.computeCaptchaSolutionHash(captcha)
|
|
45
|
+
);
|
|
46
|
+
tree.build(captchasHashed);
|
|
47
|
+
if (!tree.root) {
|
|
48
|
+
throw new common.ProsopoDatasetError("CAPTCHA.INVALID_CAPTCHA_CHALLENGE", {
|
|
49
|
+
context: { error: "Merkle tree root is undefined" }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const commitmentId = tree.root.hash;
|
|
53
|
+
const tx = void 0;
|
|
54
|
+
let result;
|
|
55
|
+
try {
|
|
56
|
+
result = await this.providerApi.submitCaptchaSolution(
|
|
57
|
+
solutions,
|
|
58
|
+
requestHash,
|
|
59
|
+
this.userAccount,
|
|
60
|
+
timestamp,
|
|
61
|
+
providerRequestHashSignature,
|
|
62
|
+
userTimestampSignature
|
|
63
|
+
);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new common.ProsopoDatasetError("CAPTCHA.INVALID_CAPTCHA_CHALLENGE", {
|
|
66
|
+
context: { error }
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return [result, commitmentId, tx];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.ProsopoCaptchaApi = ProsopoCaptchaApi;
|
|
73
|
+
exports.default = ProsopoCaptchaApi;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const COLLECTOR_LIMIT = 1e4;
|
|
4
|
+
const storeLog = (event, setEvents) => {
|
|
5
|
+
setEvents((currentEvents) => {
|
|
6
|
+
let newEvents = [...currentEvents, event];
|
|
7
|
+
if (newEvents.length > COLLECTOR_LIMIT) {
|
|
8
|
+
newEvents = newEvents.slice(1);
|
|
9
|
+
}
|
|
10
|
+
return newEvents;
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
const logMouseEvent = (event, setMouseEvent) => {
|
|
14
|
+
const storedEvent = {
|
|
15
|
+
x: event.x,
|
|
16
|
+
y: event.y,
|
|
17
|
+
timestamp: event.timeStamp
|
|
18
|
+
};
|
|
19
|
+
storeLog(storedEvent, setMouseEvent);
|
|
20
|
+
};
|
|
21
|
+
const logKeyboardEvent = (event, setKeyboardEvent) => {
|
|
22
|
+
const storedEvent = {
|
|
23
|
+
key: event.key,
|
|
24
|
+
timestamp: event.timeStamp,
|
|
25
|
+
isShiftKey: event.shiftKey,
|
|
26
|
+
isCtrlKey: event.ctrlKey
|
|
27
|
+
};
|
|
28
|
+
storeLog(storedEvent, setKeyboardEvent);
|
|
29
|
+
};
|
|
30
|
+
const logTouchEvent = (event, setTouchEvent) => {
|
|
31
|
+
for (const touch of Array.from(event.touches)) {
|
|
32
|
+
storeLog(
|
|
33
|
+
{ x: touch.clientX, y: touch.clientY, timestamp: event.timeStamp },
|
|
34
|
+
setTouchEvent
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const startCollector = (setStoredMouseEvents, setStoredTouchEvents, setStoredKeyboardEvents, rootElement) => {
|
|
39
|
+
const form = findContainingForm(rootElement);
|
|
40
|
+
if (form) {
|
|
41
|
+
form.addEventListener(
|
|
42
|
+
"mousemove",
|
|
43
|
+
(e) => logMouseEvent(e, setStoredMouseEvents)
|
|
44
|
+
);
|
|
45
|
+
form.addEventListener(
|
|
46
|
+
"keydown",
|
|
47
|
+
(e) => logKeyboardEvent(e, setStoredKeyboardEvents)
|
|
48
|
+
);
|
|
49
|
+
form.addEventListener(
|
|
50
|
+
"keyup",
|
|
51
|
+
(e) => logKeyboardEvent(e, setStoredKeyboardEvents)
|
|
52
|
+
);
|
|
53
|
+
form.addEventListener(
|
|
54
|
+
"touchstart",
|
|
55
|
+
(e) => logTouchEvent(e, setStoredTouchEvents)
|
|
56
|
+
);
|
|
57
|
+
form.addEventListener(
|
|
58
|
+
"touchend",
|
|
59
|
+
(e) => logTouchEvent(e, setStoredTouchEvents)
|
|
60
|
+
);
|
|
61
|
+
form.addEventListener(
|
|
62
|
+
"touchcancel",
|
|
63
|
+
(e) => logTouchEvent(e, setStoredTouchEvents)
|
|
64
|
+
);
|
|
65
|
+
form.addEventListener(
|
|
66
|
+
"touchmove",
|
|
67
|
+
(e) => logTouchEvent(e, setStoredTouchEvents)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const findContainingForm = (element) => {
|
|
72
|
+
if (element.tagName === "FORM") {
|
|
73
|
+
return element;
|
|
74
|
+
}
|
|
75
|
+
if (element.parentElement) {
|
|
76
|
+
return findContainingForm(element.parentElement);
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
80
|
+
exports.startCollector = startCollector;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const Manager = require("./Manager.cjs");
|
|
4
|
+
const ProsopoCaptchaApi = require("./ProsopoCaptchaApi.cjs");
|
|
5
|
+
const collector = require("./collector.cjs");
|
|
6
|
+
exports.Manager = Manager.Manager;
|
|
7
|
+
exports.ProsopoCaptchaApi = ProsopoCaptchaApi.ProsopoCaptchaApi;
|
|
8
|
+
exports.startCollector = collector.startCollector;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/procaptcha",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"author": "PROSOPO LIMITED <info@prosopo.io>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
"@polkadot/api-contract": "10.13.1",
|
|
34
34
|
"@polkadot/util": "12.6.2",
|
|
35
35
|
"@polkadot/util-crypto": "12.6.2",
|
|
36
|
-
"@prosopo/account": "2.
|
|
37
|
-
"@prosopo/api": "2.
|
|
38
|
-
"@prosopo/common": "2.
|
|
39
|
-
"@prosopo/datasets": "2.
|
|
40
|
-
"@prosopo/load-balancer": "2.
|
|
41
|
-
"@prosopo/procaptcha-common": "2.
|
|
42
|
-
"@prosopo/types": "2.
|
|
43
|
-
"@prosopo/util": "2.
|
|
36
|
+
"@prosopo/account": "2.6.2",
|
|
37
|
+
"@prosopo/api": "2.6.2",
|
|
38
|
+
"@prosopo/common": "2.6.0",
|
|
39
|
+
"@prosopo/datasets": "2.6.2",
|
|
40
|
+
"@prosopo/load-balancer": "2.6.2",
|
|
41
|
+
"@prosopo/procaptcha-common": "2.6.2",
|
|
42
|
+
"@prosopo/types": "2.6.2",
|
|
43
|
+
"@prosopo/util": "2.6.0",
|
|
44
44
|
"express": "4.21.2",
|
|
45
45
|
"jsdom": "25.0.0"
|
|
46
46
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@prosopo/config": "2.
|
|
60
|
+
"@prosopo/config": "2.6.0",
|
|
61
61
|
"@vitest/coverage-v8": "3.0.9",
|
|
62
62
|
"concurrently": "9.0.1",
|
|
63
63
|
"del-cli": "6.0.0",
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
"keywords": [],
|
|
73
73
|
"repository": {
|
|
74
74
|
"type": "git",
|
|
75
|
-
"url": "git+https://github.com/prosopo/captcha.git"
|
|
75
|
+
"url": "git+https://github.com/prosopo/captcha.git",
|
|
76
|
+
"directory": "packages/procaptcha"
|
|
76
77
|
},
|
|
77
78
|
"bugs": {
|
|
78
79
|
"url": "https://github.com/prosopo/captcha/issues"
|