@prosopo/procaptcha 0.2.13 → 0.2.15
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/dist/api/Extension.d.ts +0 -10
- package/dist/api/Extension.d.ts.map +1 -1
- package/dist/api/Extension.js +0 -3
- package/dist/api/Extension.js.map +1 -1
- package/dist/api/ExtensionWeb2.d.ts +0 -3
- package/dist/api/ExtensionWeb2.d.ts.map +1 -1
- package/dist/api/ExtensionWeb2.js +9 -17
- package/dist/api/ExtensionWeb2.js.map +1 -1
- package/dist/api/ExtensionWeb3.d.ts +0 -3
- package/dist/api/ExtensionWeb3.d.ts.map +1 -1
- package/dist/api/ExtensionWeb3.js +0 -5
- package/dist/api/ExtensionWeb3.js.map +1 -1
- package/dist/api/errors.js +0 -13
- package/dist/api/errors.js.map +1 -1
- package/dist/api/handlers.js +0 -15
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/index.js +0 -13
- package/dist/api/index.js.map +1 -1
- package/dist/api/sign.js +2 -15
- package/dist/api/sign.js.map +1 -1
- package/dist/cjs/api/ExtensionWeb2.cjs +17 -16
- package/dist/cjs/modules/Manager.cjs +217 -212
- package/dist/cjs/modules/ProsopoCaptchaApi.cjs +10 -12
- package/dist/index.js +0 -13
- package/dist/index.js.map +1 -1
- package/dist/modules/Manager.d.ts +0 -3
- package/dist/modules/Manager.d.ts.map +1 -1
- package/dist/modules/Manager.js +195 -287
- package/dist/modules/Manager.js.map +1 -1
- package/dist/modules/ProsopoCaptchaApi.d.ts +1 -1
- package/dist/modules/ProsopoCaptchaApi.d.ts.map +1 -1
- package/dist/modules/ProsopoCaptchaApi.js +1 -28
- package/dist/modules/ProsopoCaptchaApi.js.map +1 -1
- package/dist/modules/canvas.js +0 -14
- package/dist/modules/canvas.js.map +1 -1
- package/dist/modules/index.js +0 -13
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/storage.d.ts +0 -12
- package/dist/modules/storage.d.ts.map +1 -1
- package/dist/modules/storage.js +0 -25
- package/dist/modules/storage.js.map +1 -1
- package/dist/tests/mocks/browser.js +0 -13
- package/dist/tests/mocks/browser.js.map +1 -1
- package/dist/types/api.js +0 -19
- package/dist/types/api.js.map +1 -1
- package/dist/types/index.js +0 -13
- package/dist/types/index.js.map +1 -1
- package/dist/types/manager.d.ts +0 -24
- package/dist/types/manager.d.ts.map +1 -1
- package/dist/utils/index.js +0 -13
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/utils.js +0 -13
- package/dist/utils/utils.js.map +1 -1
- package/package.json +10 -8
- package/dist/cjs/contracts/captcha/dist/build-extrinsic/captcha.cjs +0 -339
- package/dist/cjs/contracts/captcha/dist/contract-info/captcha.cjs +0 -6
- package/dist/cjs/contracts/captcha/dist/contracts/captcha.cjs +0 -79
- package/dist/cjs/contracts/captcha/dist/data/captcha.json.cjs +0 -3374
- package/dist/cjs/contracts/captcha/dist/event-data/captcha.json.cjs +0 -3
- package/dist/cjs/contracts/captcha/dist/events/captcha.cjs +0 -23
- package/dist/cjs/contracts/captcha/dist/index.cjs +0 -44
- package/dist/cjs/contracts/captcha/dist/mixed-methods/captcha.cjs +0 -470
- package/dist/cjs/contracts/captcha/dist/query/captcha.cjs +0 -468
- package/dist/cjs/contracts/captcha/dist/shared/utils.cjs +0 -31
- package/dist/cjs/contracts/captcha/dist/tx-sign-and-send/captcha.cjs +0 -426
- package/dist/cjs/contracts/captcha/dist/types-arguments/captcha.cjs +0 -62
- package/dist/cjs/packages/datasets/dist/captcha/captcha.cjs +0 -143
- package/dist/cjs/packages/datasets/dist/captcha/dataset.cjs +0 -87
- package/dist/cjs/packages/datasets/dist/captcha/index.cjs +0 -27
- package/dist/cjs/packages/datasets/dist/captcha/merkle.cjs +0 -106
- package/dist/cjs/packages/datasets/dist/captcha/util.cjs +0 -16
- package/dist/cjs/packages/datasets/dist/index.cjs +0 -2
package/dist/modules/Manager.js
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
import { AccountNotFoundError } from '../api/errors.js';
|
|
2
|
-
import { ApiPromise
|
|
3
|
-
import { ProcaptchaConfigSchema } from '@prosopo/types';
|
|
4
|
-
import {
|
|
2
|
+
import { ApiPromise } from '@polkadot/api/promise/Api';
|
|
3
|
+
import { ProcaptchaConfigSchema, } from '@prosopo/types';
|
|
4
|
+
import { ProviderApi } from '@prosopo/api';
|
|
5
|
+
import { Keyring } from '@polkadot/keyring';
|
|
5
6
|
import { ProsopoCaptchaContract, wrapQuery } from '@prosopo/contract';
|
|
6
7
|
import { ProsopoEnvError, trimProviderUrl } from '@prosopo/common';
|
|
7
|
-
import {
|
|
8
|
-
import { ContractAbi as abiJson } from '@prosopo/captcha-contract';
|
|
8
|
+
import { WsProvider } from '@polkadot/rpc-provider/ws';
|
|
9
|
+
import { ContractAbi as abiJson } from '@prosopo/captcha-contract/contract-info';
|
|
9
10
|
import { at } from '@prosopo/util';
|
|
10
|
-
import { randomAsHex } from '@polkadot/util-crypto';
|
|
11
|
-
import {
|
|
11
|
+
import { randomAsHex } from '@polkadot/util-crypto/random';
|
|
12
|
+
import { sleep } from '../utils/utils.js';
|
|
13
|
+
import { stringToU8a } from '@polkadot/util/string';
|
|
12
14
|
import ExtensionWeb2 from '../api/ExtensionWeb2.js';
|
|
13
15
|
import ExtensionWeb3 from '../api/ExtensionWeb3.js';
|
|
14
16
|
import ProsopoCaptchaApi from './ProsopoCaptchaApi.js';
|
|
15
17
|
import storage from './storage.js';
|
|
16
|
-
export const defaultState = () =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
export const defaultState = () => {
|
|
19
|
+
return {
|
|
20
|
+
showModal: false,
|
|
21
|
+
loading: false,
|
|
22
|
+
index: 0,
|
|
23
|
+
challenge: undefined,
|
|
24
|
+
solutions: undefined,
|
|
25
|
+
isHuman: false,
|
|
26
|
+
captchaApi: undefined,
|
|
27
|
+
account: undefined,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const buildUpdateState = (state, onStateUpdate) => {
|
|
31
|
+
const updateCurrentState = (nextState) => {
|
|
32
|
+
Object.assign(state, nextState);
|
|
33
|
+
onStateUpdate(nextState);
|
|
34
|
+
console.log('Procaptcha state update:', nextState, '\nResult:', state);
|
|
35
|
+
};
|
|
36
|
+
return updateCurrentState;
|
|
29
37
|
};
|
|
30
38
|
export const getNetwork = (config) => {
|
|
31
39
|
const network = config.networks[config.defaultNetwork];
|
|
@@ -34,19 +42,16 @@ export const getNetwork = (config) => {
|
|
|
34
42
|
}
|
|
35
43
|
return network;
|
|
36
44
|
};
|
|
37
|
-
/**
|
|
38
|
-
* 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.
|
|
39
|
-
*/
|
|
40
45
|
export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
41
46
|
const alertError = (error) => {
|
|
42
|
-
console.
|
|
47
|
+
console.log(error);
|
|
43
48
|
alert(error.message);
|
|
44
49
|
};
|
|
45
50
|
const events = Object.assign({
|
|
46
51
|
onAccountNotFound: alertError,
|
|
47
52
|
onError: alertError,
|
|
48
53
|
onHuman: (output) => {
|
|
49
|
-
console.
|
|
54
|
+
console.log('onHuman event triggered', output);
|
|
50
55
|
},
|
|
51
56
|
onExtensionNotFound: () => {
|
|
52
57
|
alert('No extension found');
|
|
@@ -61,10 +66,10 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
61
66
|
alert('Uncompleted challenge has expired, please try again');
|
|
62
67
|
},
|
|
63
68
|
onOpen: () => {
|
|
64
|
-
console.
|
|
69
|
+
console.log('onOpen event triggered');
|
|
65
70
|
},
|
|
66
71
|
onClose: () => {
|
|
67
|
-
console.
|
|
72
|
+
console.log('onClose event triggered');
|
|
68
73
|
},
|
|
69
74
|
}, callbacks);
|
|
70
75
|
const dispatchErrorEvent = (err) => {
|
|
@@ -77,17 +82,16 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
77
82
|
}
|
|
78
83
|
};
|
|
79
84
|
const updateState = buildUpdateState(state, onStateUpdate);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
85
|
+
const getConfig = () => {
|
|
86
|
+
const config = {
|
|
87
|
+
userAccountAddress: '',
|
|
88
|
+
...configOptional,
|
|
89
|
+
};
|
|
90
|
+
if (state.account) {
|
|
91
|
+
config.userAccountAddress = state.account.account.address;
|
|
92
|
+
}
|
|
93
|
+
return ProcaptchaConfigSchema.parse(config);
|
|
94
|
+
};
|
|
91
95
|
const fallable = async (fn) => {
|
|
92
96
|
try {
|
|
93
97
|
await fn();
|
|
@@ -98,31 +102,58 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
98
102
|
updateState({ isHuman: false, showModal: false, loading: false });
|
|
99
103
|
}
|
|
100
104
|
};
|
|
101
|
-
/**
|
|
102
|
-
* Called on start of user verification. This is when the user ticks the box to claim they are human.
|
|
103
|
-
*/
|
|
104
105
|
const start = async () => {
|
|
106
|
+
console.log('Starting procaptcha');
|
|
105
107
|
events.onOpen();
|
|
106
108
|
await fallable(async () => {
|
|
107
|
-
if (state.loading
|
|
109
|
+
if (state.loading) {
|
|
110
|
+
console.log('Procaptcha already loading');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (state.isHuman) {
|
|
114
|
+
console.log('already human');
|
|
108
115
|
return;
|
|
116
|
+
}
|
|
109
117
|
resetState();
|
|
110
|
-
updateState({
|
|
118
|
+
updateState({ loading: true });
|
|
119
|
+
const config = getConfig();
|
|
120
|
+
updateState({ dappAccount: config.account.address });
|
|
121
|
+
await sleep(100);
|
|
111
122
|
const account = await loadAccount();
|
|
112
123
|
const contract = await loadContract();
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
let contractIsHuman = false;
|
|
125
|
+
try {
|
|
126
|
+
contractIsHuman = (await contract.query.dappOperatorIsHumanUser(account.account.address, config.solutionThreshold)).value
|
|
127
|
+
.unwrap()
|
|
128
|
+
.unwrap();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.warn(error);
|
|
132
|
+
}
|
|
133
|
+
if (contractIsHuman) {
|
|
134
|
+
updateState({ isHuman: true, loading: false });
|
|
135
|
+
events.onHuman({
|
|
136
|
+
user: account.account.address,
|
|
137
|
+
dapp: getDappAccount(),
|
|
138
|
+
});
|
|
139
|
+
setValidChallengeTimeout();
|
|
115
140
|
return;
|
|
116
141
|
}
|
|
117
|
-
// Check if a provider is cached in local storage
|
|
118
142
|
const providerUrlFromStorage = storage.getProviderUrl();
|
|
143
|
+
let providerApi;
|
|
119
144
|
if (providerUrlFromStorage) {
|
|
145
|
+
providerApi = await loadProviderApi(providerUrlFromStorage);
|
|
120
146
|
try {
|
|
121
|
-
|
|
122
|
-
const verifyDappUserResponse = await getVerifyDappUserFunction(providerUrlFromStorage, account);
|
|
123
|
-
// If legitimate cached provider, check if human in cached provider
|
|
147
|
+
const verifyDappUserResponse = await providerApi.verifyDappUser(account.account.address, undefined, configOptional.challengeValidLength);
|
|
124
148
|
if (verifyDappUserResponse.solutionApproved) {
|
|
125
|
-
|
|
149
|
+
updateState({ isHuman: true, loading: false });
|
|
150
|
+
events.onHuman({
|
|
151
|
+
providerUrl: providerUrlFromStorage,
|
|
152
|
+
user: account.account.address,
|
|
153
|
+
dapp: getDappAccount(),
|
|
154
|
+
commitmentId: verifyDappUserResponse.commitmentId,
|
|
155
|
+
});
|
|
156
|
+
setValidChallengeTimeout();
|
|
126
157
|
return;
|
|
127
158
|
}
|
|
128
159
|
}
|
|
@@ -130,36 +161,72 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
130
161
|
console.error('Error contacting provider from storage', providerUrlFromStorage);
|
|
131
162
|
}
|
|
132
163
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
const payload = {
|
|
165
|
+
address: account.account.address,
|
|
166
|
+
data: stringToU8a('message'),
|
|
167
|
+
type: 'bytes',
|
|
168
|
+
};
|
|
169
|
+
const signed = await account.extension.signer.signRaw(payload);
|
|
170
|
+
console.log('Signature:', signed);
|
|
171
|
+
const getRandomProviderResponse = await wrapQuery(contract.query.getRandomActiveProvider, contract.query)(account.account.address, getDappAccount());
|
|
172
|
+
const blockNumber = parseInt(getRandomProviderResponse.blockNumber.toString());
|
|
173
|
+
console.log('provider', getRandomProviderResponse);
|
|
174
|
+
const providerUrl = trimProviderUrl(getRandomProviderResponse.provider.url.toString());
|
|
175
|
+
providerApi = await loadProviderApi(providerUrl);
|
|
176
|
+
console.log('providerApi', providerApi);
|
|
177
|
+
const captchaApi = await loadCaptchaApi(contract, getRandomProviderResponse, providerApi);
|
|
178
|
+
console.log('captchaApi', captchaApi);
|
|
179
|
+
const challenge = await captchaApi.getCaptchaChallenge();
|
|
180
|
+
console.log('challenge', challenge);
|
|
136
181
|
if (challenge.captchas.length <= 0) {
|
|
137
182
|
throw new Error('No captchas returned from provider');
|
|
138
183
|
}
|
|
184
|
+
const timeMillis = challenge.captchas
|
|
185
|
+
.map((captcha) => captcha.captcha.timeLimitMs || 30 * 1000)
|
|
186
|
+
.reduce((a, b) => a + b);
|
|
187
|
+
const timeout = setTimeout(() => {
|
|
188
|
+
console.log('challenge expired after ' + timeMillis + 'ms');
|
|
189
|
+
events.onChallengeExpired();
|
|
190
|
+
updateState({ isHuman: false, showModal: false, loading: false });
|
|
191
|
+
}, timeMillis);
|
|
139
192
|
updateState({
|
|
140
|
-
challenge,
|
|
141
193
|
index: 0,
|
|
142
194
|
solutions: challenge.captchas.map(() => []),
|
|
195
|
+
challenge,
|
|
143
196
|
showModal: true,
|
|
144
|
-
timeout
|
|
145
|
-
blockNumber
|
|
197
|
+
timeout,
|
|
198
|
+
blockNumber,
|
|
146
199
|
});
|
|
147
200
|
});
|
|
148
201
|
};
|
|
149
|
-
/**
|
|
150
|
-
* Submit the captcha solution.
|
|
151
|
-
*/
|
|
152
202
|
const submit = async () => {
|
|
153
203
|
await fallable(async () => {
|
|
204
|
+
console.log('submitting solutions');
|
|
154
205
|
clearTimeout();
|
|
155
|
-
updateState({ showModal: false });
|
|
156
206
|
if (!state.challenge) {
|
|
157
207
|
throw new Error('cannot submit, no challenge found');
|
|
158
208
|
}
|
|
159
|
-
|
|
209
|
+
updateState({ showModal: false });
|
|
210
|
+
const challenge = state.challenge;
|
|
160
211
|
const salt = randomAsHex();
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
const captchaSolution = state.challenge.captchas.map((captcha, index) => {
|
|
213
|
+
const solution = at(state.solutions, index);
|
|
214
|
+
return {
|
|
215
|
+
captchaId: captcha.captcha.captchaId,
|
|
216
|
+
captchaContentId: captcha.captcha.captchaContentId,
|
|
217
|
+
salt,
|
|
218
|
+
solution,
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
const account = getAccount();
|
|
222
|
+
const blockNumber = getBlockNumber();
|
|
223
|
+
const signer = account.extension.signer;
|
|
224
|
+
const first = at(challenge.captchas, 0);
|
|
225
|
+
if (!first.captcha.datasetId) {
|
|
226
|
+
throw new Error('No datasetId set for challenge');
|
|
227
|
+
}
|
|
228
|
+
const captchaApi = getCaptchaApi();
|
|
229
|
+
const submission = await captchaApi.submitCaptchaSolution(signer, challenge.requestHash, first.captcha.datasetId, captchaSolution, salt);
|
|
163
230
|
const isHuman = submission[0].solutionApproved;
|
|
164
231
|
if (!isHuman) {
|
|
165
232
|
events.onFailed();
|
|
@@ -169,24 +236,26 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
169
236
|
isHuman,
|
|
170
237
|
loading: false,
|
|
171
238
|
});
|
|
172
|
-
if (isHuman) {
|
|
173
|
-
const trimmedUrl = trimProviderUrl(
|
|
239
|
+
if (state.isHuman) {
|
|
240
|
+
const trimmedUrl = trimProviderUrl(captchaApi.provider.provider.url.toString());
|
|
174
241
|
storage.setProviderUrl(trimmedUrl);
|
|
175
242
|
events.onHuman({
|
|
176
243
|
providerUrl: trimmedUrl,
|
|
177
|
-
user:
|
|
244
|
+
user: account.account.address,
|
|
178
245
|
dapp: getDappAccount(),
|
|
179
246
|
commitmentId: submission[1],
|
|
180
|
-
blockNumber
|
|
247
|
+
blockNumber,
|
|
181
248
|
});
|
|
182
249
|
setValidChallengeTimeout();
|
|
183
250
|
}
|
|
184
251
|
});
|
|
185
252
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
253
|
+
const cancel = async () => {
|
|
254
|
+
console.log('cancel');
|
|
255
|
+
clearTimeout();
|
|
256
|
+
resetState();
|
|
257
|
+
events.onClose();
|
|
258
|
+
};
|
|
190
259
|
const select = (hash) => {
|
|
191
260
|
if (!state.challenge) {
|
|
192
261
|
throw new Error('cannot select, no challenge found');
|
|
@@ -194,13 +263,19 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
194
263
|
if (state.index >= state.challenge.captchas.length || state.index < 0) {
|
|
195
264
|
throw new Error('cannot select, round index out of range');
|
|
196
265
|
}
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
266
|
+
const index = state.index;
|
|
267
|
+
const solutions = state.solutions;
|
|
268
|
+
const solution = at(solutions, index);
|
|
269
|
+
if (solution.includes(hash)) {
|
|
270
|
+
console.log('deselecting', hash);
|
|
271
|
+
solution.splice(solution.indexOf(hash), 1);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
console.log('selecting', hash);
|
|
275
|
+
solution.push(hash);
|
|
276
|
+
}
|
|
277
|
+
updateState({ solutions });
|
|
200
278
|
};
|
|
201
|
-
/**
|
|
202
|
-
* Proceed to the next round of the challenge.
|
|
203
|
-
*/
|
|
204
279
|
const nextRound = () => {
|
|
205
280
|
if (!state.challenge) {
|
|
206
281
|
throw new Error('cannot proceed to next round, no challenge found');
|
|
@@ -208,214 +283,37 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
208
283
|
if (state.index + 1 >= state.challenge.captchas.length) {
|
|
209
284
|
throw new Error('cannot proceed to next round, already at last round');
|
|
210
285
|
}
|
|
286
|
+
console.log('proceeding to next round');
|
|
211
287
|
updateState({ index: state.index + 1 });
|
|
212
288
|
};
|
|
213
|
-
/**
|
|
214
|
-
* Load the captcha api using the contract and provider.
|
|
215
|
-
* @param contract the contract instance
|
|
216
|
-
* @param provider the provider instance
|
|
217
|
-
* @param providerApi the provider api instance
|
|
218
|
-
*/
|
|
219
289
|
const loadCaptchaApi = async (contract, provider, providerApi) => {
|
|
220
|
-
updateState({
|
|
221
|
-
captchaApi: new ProsopoCaptchaApi(getAccount().account.address, contract, provider, providerApi, getConfig().web2, getDappAccount()),
|
|
222
|
-
});
|
|
223
|
-
return getCaptchaApi();
|
|
224
|
-
};
|
|
225
|
-
/**
|
|
226
|
-
* Create an observable that emits on every new block.
|
|
227
|
-
* Used for retrying random provider requests.
|
|
228
|
-
*/
|
|
229
|
-
const createBlockObservable = () => new Observable((subscriber) => () => ApiPromise.create({ provider: new WsProvider(getNetwork(getConfig()).endpoint) })
|
|
230
|
-
.then((api) => {
|
|
231
|
-
api.rpc.chain.subscribeNewHeads((header) => {
|
|
232
|
-
subscriber.next(header);
|
|
233
|
-
});
|
|
234
|
-
})
|
|
235
|
-
.catch((error) => {
|
|
236
|
-
subscriber.error(error);
|
|
237
|
-
}));
|
|
238
|
-
/**
|
|
239
|
-
* Load the account using address specified in config, or generate new address if not found in local storage for web2 mode.
|
|
240
|
-
*/
|
|
241
|
-
const loadAccount = async () => {
|
|
242
290
|
const config = getConfig();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const ext = config.web2 ? new ExtensionWeb2() : new ExtensionWeb3();
|
|
247
|
-
const account = await ext.getAccount(config);
|
|
248
|
-
storage.setAccount(account.account.address);
|
|
249
|
-
updateState({ account });
|
|
250
|
-
return getAccount();
|
|
291
|
+
const captchaApi = new ProsopoCaptchaApi(getAccount().account.address, contract, provider, providerApi, config.web2, getDappAccount());
|
|
292
|
+
updateState({ captchaApi });
|
|
293
|
+
return getCaptchaApi();
|
|
251
294
|
};
|
|
252
|
-
/**
|
|
253
|
-
* Load the provider api
|
|
254
|
-
* @param providerUrl
|
|
255
|
-
*/
|
|
256
295
|
const loadProviderApi = async (providerUrl) => {
|
|
257
296
|
const config = getConfig();
|
|
297
|
+
const network = getNetwork(config);
|
|
258
298
|
if (!config.account.address) {
|
|
259
299
|
throw new ProsopoEnvError('GENERAL.SITE_KEY_MISSING');
|
|
260
300
|
}
|
|
261
|
-
return new ProviderApi(
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* Load the contract instance using addresses from config.
|
|
265
|
-
*/
|
|
266
|
-
const loadContract = async () => {
|
|
267
|
-
const network = getNetwork(getConfig());
|
|
268
|
-
const api = await ApiPromise.create({ provider: new WsProvider(network.endpoint) });
|
|
269
|
-
const type = 'sr25519';
|
|
270
|
-
return new ProsopoCaptchaContract(api, JSON.parse(abiJson), network.contract.address, 'prosopo', 0, new Keyring({ type, ss58Format: api.registry.chainSS58 }).addFromAddress(getAccount().account.address));
|
|
271
|
-
};
|
|
272
|
-
/**
|
|
273
|
-
* Handles whether clicking on an image should select or deselect it.
|
|
274
|
-
* @param solution
|
|
275
|
-
* @param hash
|
|
276
|
-
*/
|
|
277
|
-
function handleIsSelected(solution, hash) {
|
|
278
|
-
if (solution.includes(hash)) {
|
|
279
|
-
solution.splice(solution.indexOf(hash), 1);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
solution.push(hash);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Get the solutions from the state, with the salt added.
|
|
287
|
-
* @param salt
|
|
288
|
-
* @returns
|
|
289
|
-
*/
|
|
290
|
-
function getSolutionsFromState(salt) {
|
|
291
|
-
if (!state.challenge) {
|
|
292
|
-
throw new Error('cannot get solutions, no challenge found');
|
|
293
|
-
}
|
|
294
|
-
return state.challenge.captchas.map((captcha, index) => ({
|
|
295
|
-
captchaId: captcha.captcha.captchaId,
|
|
296
|
-
captchaContentId: captcha.captcha.captchaContentId,
|
|
297
|
-
salt,
|
|
298
|
-
solution: at(state.solutions, index),
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Handle the case where the user is human and the provider is cached in local storage
|
|
303
|
-
* @param providerUrlFromStorage
|
|
304
|
-
* @param account
|
|
305
|
-
* @param verifyDappUserResponse
|
|
306
|
-
*/
|
|
307
|
-
function handleHumanInCachedProvider(providerUrlFromStorage, account, verifyDappUserResponse) {
|
|
308
|
-
updateState({ isHuman: true, loading: false });
|
|
309
|
-
events.onHuman({
|
|
310
|
-
providerUrl: providerUrlFromStorage,
|
|
311
|
-
user: account.account.address,
|
|
312
|
-
dapp: getDappAccount(),
|
|
313
|
-
commitmentId: verifyDappUserResponse.commitmentId,
|
|
314
|
-
});
|
|
315
|
-
setValidChallengeTimeout();
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Get the verifyDappUser function from the provider api
|
|
319
|
-
* @param providerUrlFromStorage
|
|
320
|
-
* @param account
|
|
321
|
-
* @returns
|
|
322
|
-
*/
|
|
323
|
-
function getVerifyDappUserFunction(providerUrlFromStorage, account) {
|
|
324
|
-
return loadProviderApi(providerUrlFromStorage).then((providerApi) => providerApi.verifyDappUser(account.account.address));
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Handle the case where the user is human and the provider is cached in the contract
|
|
328
|
-
* @param account
|
|
329
|
-
*/
|
|
330
|
-
function handleHumanInContract(account) {
|
|
331
|
-
updateState({ isHuman: true, loading: false });
|
|
332
|
-
events.onHuman({
|
|
333
|
-
user: account.account.address,
|
|
334
|
-
dapp: getDappAccount(),
|
|
335
|
-
});
|
|
336
|
-
setValidChallengeTimeout();
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Check if the user is human in the contract
|
|
340
|
-
* @param contract
|
|
341
|
-
* @param account
|
|
342
|
-
* @returns
|
|
343
|
-
*/
|
|
344
|
-
async function checkHumanInContract(contract, account) {
|
|
345
|
-
try {
|
|
346
|
-
return await contract.query
|
|
347
|
-
.dappOperatorIsHumanUser(account.account.address, getConfig().solutionThreshold)
|
|
348
|
-
.then((res) => res.value.unwrap().unwrap());
|
|
349
|
-
}
|
|
350
|
-
catch (err) {
|
|
351
|
-
console.error(err);
|
|
352
|
-
return false;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Get the captcha challenge from the provider api
|
|
357
|
-
* @param getRandomProviderResponse
|
|
358
|
-
* @param contract
|
|
359
|
-
* @returns
|
|
360
|
-
*/
|
|
361
|
-
function getChallenge(getRandomProviderResponse, contract) {
|
|
362
|
-
return loadProviderApi(trimProviderUrl(getRandomProviderResponse.provider.url.toString()))
|
|
363
|
-
.then((api) => loadCaptchaApi(contract, getRandomProviderResponse, api))
|
|
364
|
-
.then((captchaApi) => captchaApi.getCaptchaChallenge());
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Get a random provider from the contract
|
|
368
|
-
* Uses retry to handle the case where the provider is not available on the first attempt
|
|
369
|
-
* Waits for block rollover to ensure new provider selected
|
|
370
|
-
* Returns promise
|
|
371
|
-
*
|
|
372
|
-
* @param contract
|
|
373
|
-
* @param account
|
|
374
|
-
* @returns
|
|
375
|
-
*/
|
|
376
|
-
function getRandomProviderResponse(contract, account) {
|
|
377
|
-
return lastValueFrom(from(wrapQuery(contract.query.getRandomActiveProvider, contract.query)(account.account.address, getDappAccount())).pipe(retry({
|
|
378
|
-
count: 3,
|
|
379
|
-
delay: (error, retryCount) => {
|
|
380
|
-
console.error(`Attempt ${retryCount} failed. Retrying on next block. Error: ${error}`);
|
|
381
|
-
return createBlockObservable().pipe(take(1));
|
|
382
|
-
},
|
|
383
|
-
resetOnSuccess: true,
|
|
384
|
-
})));
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Verify the captcha data
|
|
388
|
-
* @param challenge
|
|
389
|
-
* @returns
|
|
390
|
-
*/
|
|
391
|
-
function setTimeToComplete(challenge) {
|
|
392
|
-
return setTimeout(() => {
|
|
393
|
-
events.onChallengeExpired();
|
|
394
|
-
updateState({ isHuman: false, showModal: false, loading: false });
|
|
395
|
-
}, challenge.captchas.map((captcha) => captcha.captcha.timeLimitMs || 30 * 1000).reduce((a, b) => a + b));
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* The timeout for the challenge to be completed
|
|
399
|
-
* Defaults to 2 minutes
|
|
400
|
-
* @returns
|
|
401
|
-
*/
|
|
402
|
-
const setValidChallengeTimeout = () => {
|
|
403
|
-
updateState({
|
|
404
|
-
successfullChallengeTimeout: setTimeout(() => {
|
|
405
|
-
updateState({ isHuman: false });
|
|
406
|
-
events.onExpired();
|
|
407
|
-
}, configOptional.challengeValidLength || 120 * 1000),
|
|
408
|
-
});
|
|
409
|
-
};
|
|
410
|
-
const cancel = async () => {
|
|
411
|
-
clearTimeout();
|
|
412
|
-
resetState();
|
|
413
|
-
events.onClose();
|
|
301
|
+
return new ProviderApi(network, providerUrl, config.account.address);
|
|
414
302
|
};
|
|
415
303
|
const clearTimeout = () => {
|
|
416
304
|
window.clearTimeout(state.timeout);
|
|
417
305
|
updateState({ timeout: undefined });
|
|
418
306
|
};
|
|
307
|
+
const setValidChallengeTimeout = () => {
|
|
308
|
+
console.log('setting valid challenge timeout');
|
|
309
|
+
const timeMillis = configOptional.challengeValidLength || 120 * 1000;
|
|
310
|
+
const successfullChallengeTimeout = setTimeout(() => {
|
|
311
|
+
console.log('valid challenge expired after ' + timeMillis + 'ms');
|
|
312
|
+
updateState({ isHuman: false });
|
|
313
|
+
events.onExpired();
|
|
314
|
+
}, timeMillis);
|
|
315
|
+
updateState({ successfullChallengeTimeout });
|
|
316
|
+
};
|
|
419
317
|
const resetState = () => {
|
|
420
318
|
clearTimeout();
|
|
421
319
|
updateState(defaultState());
|
|
@@ -426,37 +324,47 @@ export function Manager(configOptional, state, onStateUpdate, callbacks) {
|
|
|
426
324
|
}
|
|
427
325
|
return state.captchaApi;
|
|
428
326
|
};
|
|
327
|
+
const loadAccount = async () => {
|
|
328
|
+
const config = getConfig();
|
|
329
|
+
if (!config.web2 && !config.userAccountAddress) {
|
|
330
|
+
throw new Error('Account address has not been set for web3 mode');
|
|
331
|
+
}
|
|
332
|
+
const ext = config.web2 ? new ExtensionWeb2() : new ExtensionWeb3();
|
|
333
|
+
const account = await ext.getAccount(config);
|
|
334
|
+
storage.setAccount(account.account.address);
|
|
335
|
+
console.log('Using account:', account);
|
|
336
|
+
updateState({ account });
|
|
337
|
+
return getAccount();
|
|
338
|
+
};
|
|
429
339
|
const getAccount = () => {
|
|
430
340
|
if (!state.account) {
|
|
431
341
|
throw new Error('Account not loaded');
|
|
432
342
|
}
|
|
433
|
-
|
|
343
|
+
const account = state.account;
|
|
344
|
+
return account;
|
|
434
345
|
};
|
|
435
346
|
const getDappAccount = () => {
|
|
436
347
|
if (!state.dappAccount) {
|
|
437
348
|
throw new ProsopoEnvError('GENERAL.SITE_KEY_MISSING');
|
|
438
349
|
}
|
|
439
|
-
|
|
350
|
+
const dappAccount = state.dappAccount;
|
|
351
|
+
return dappAccount;
|
|
440
352
|
};
|
|
441
|
-
const
|
|
353
|
+
const getBlockNumber = () => {
|
|
442
354
|
if (!state.blockNumber) {
|
|
443
355
|
throw new Error('Account not loaded');
|
|
444
356
|
}
|
|
445
|
-
|
|
357
|
+
const blockNumber = state.blockNumber;
|
|
358
|
+
return blockNumber;
|
|
359
|
+
};
|
|
360
|
+
const loadContract = async () => {
|
|
361
|
+
const config = getConfig();
|
|
362
|
+
const network = getNetwork(config);
|
|
363
|
+
const api = await ApiPromise.create({ provider: new WsProvider(network.endpoint), initWasm: false });
|
|
364
|
+
const type = 'sr25519';
|
|
365
|
+
const keyring = new Keyring({ type, ss58Format: api.registry.chainSS58 });
|
|
366
|
+
return new ProsopoCaptchaContract(api, JSON.parse(abiJson), network.contract.address, 'prosopo', 0, keyring.addFromAddress(getAccount().account.address));
|
|
446
367
|
};
|
|
447
|
-
function getBlockNumberFromProvider(getRandomProviderResponse) {
|
|
448
|
-
return parseInt(getRandomProviderResponse.blockNumber.toString());
|
|
449
|
-
}
|
|
450
|
-
function getDatasetId() {
|
|
451
|
-
if (!state.challenge) {
|
|
452
|
-
throw new Error('cannot get datasetId, no challenge found');
|
|
453
|
-
}
|
|
454
|
-
const datasetId = at(state.challenge.captchas, 0).captcha.datasetId;
|
|
455
|
-
if (!datasetId) {
|
|
456
|
-
throw new Error('No datasetId set for challenge');
|
|
457
|
-
}
|
|
458
|
-
return datasetId;
|
|
459
|
-
}
|
|
460
368
|
return {
|
|
461
369
|
start,
|
|
462
370
|
cancel,
|