@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.
Files changed (72) hide show
  1. package/dist/api/Extension.d.ts +0 -10
  2. package/dist/api/Extension.d.ts.map +1 -1
  3. package/dist/api/Extension.js +0 -3
  4. package/dist/api/Extension.js.map +1 -1
  5. package/dist/api/ExtensionWeb2.d.ts +0 -3
  6. package/dist/api/ExtensionWeb2.d.ts.map +1 -1
  7. package/dist/api/ExtensionWeb2.js +9 -17
  8. package/dist/api/ExtensionWeb2.js.map +1 -1
  9. package/dist/api/ExtensionWeb3.d.ts +0 -3
  10. package/dist/api/ExtensionWeb3.d.ts.map +1 -1
  11. package/dist/api/ExtensionWeb3.js +0 -5
  12. package/dist/api/ExtensionWeb3.js.map +1 -1
  13. package/dist/api/errors.js +0 -13
  14. package/dist/api/errors.js.map +1 -1
  15. package/dist/api/handlers.js +0 -15
  16. package/dist/api/handlers.js.map +1 -1
  17. package/dist/api/index.js +0 -13
  18. package/dist/api/index.js.map +1 -1
  19. package/dist/api/sign.js +2 -15
  20. package/dist/api/sign.js.map +1 -1
  21. package/dist/cjs/api/ExtensionWeb2.cjs +17 -16
  22. package/dist/cjs/modules/Manager.cjs +217 -212
  23. package/dist/cjs/modules/ProsopoCaptchaApi.cjs +10 -12
  24. package/dist/index.js +0 -13
  25. package/dist/index.js.map +1 -1
  26. package/dist/modules/Manager.d.ts +0 -3
  27. package/dist/modules/Manager.d.ts.map +1 -1
  28. package/dist/modules/Manager.js +195 -287
  29. package/dist/modules/Manager.js.map +1 -1
  30. package/dist/modules/ProsopoCaptchaApi.d.ts +1 -1
  31. package/dist/modules/ProsopoCaptchaApi.d.ts.map +1 -1
  32. package/dist/modules/ProsopoCaptchaApi.js +1 -28
  33. package/dist/modules/ProsopoCaptchaApi.js.map +1 -1
  34. package/dist/modules/canvas.js +0 -14
  35. package/dist/modules/canvas.js.map +1 -1
  36. package/dist/modules/index.js +0 -13
  37. package/dist/modules/index.js.map +1 -1
  38. package/dist/modules/storage.d.ts +0 -12
  39. package/dist/modules/storage.d.ts.map +1 -1
  40. package/dist/modules/storage.js +0 -25
  41. package/dist/modules/storage.js.map +1 -1
  42. package/dist/tests/mocks/browser.js +0 -13
  43. package/dist/tests/mocks/browser.js.map +1 -1
  44. package/dist/types/api.js +0 -19
  45. package/dist/types/api.js.map +1 -1
  46. package/dist/types/index.js +0 -13
  47. package/dist/types/index.js.map +1 -1
  48. package/dist/types/manager.d.ts +0 -24
  49. package/dist/types/manager.d.ts.map +1 -1
  50. package/dist/utils/index.js +0 -13
  51. package/dist/utils/index.js.map +1 -1
  52. package/dist/utils/utils.js +0 -13
  53. package/dist/utils/utils.js.map +1 -1
  54. package/package.json +10 -8
  55. package/dist/cjs/contracts/captcha/dist/build-extrinsic/captcha.cjs +0 -339
  56. package/dist/cjs/contracts/captcha/dist/contract-info/captcha.cjs +0 -6
  57. package/dist/cjs/contracts/captcha/dist/contracts/captcha.cjs +0 -79
  58. package/dist/cjs/contracts/captcha/dist/data/captcha.json.cjs +0 -3374
  59. package/dist/cjs/contracts/captcha/dist/event-data/captcha.json.cjs +0 -3
  60. package/dist/cjs/contracts/captcha/dist/events/captcha.cjs +0 -23
  61. package/dist/cjs/contracts/captcha/dist/index.cjs +0 -44
  62. package/dist/cjs/contracts/captcha/dist/mixed-methods/captcha.cjs +0 -470
  63. package/dist/cjs/contracts/captcha/dist/query/captcha.cjs +0 -468
  64. package/dist/cjs/contracts/captcha/dist/shared/utils.cjs +0 -31
  65. package/dist/cjs/contracts/captcha/dist/tx-sign-and-send/captcha.cjs +0 -426
  66. package/dist/cjs/contracts/captcha/dist/types-arguments/captcha.cjs +0 -62
  67. package/dist/cjs/packages/datasets/dist/captcha/captcha.cjs +0 -143
  68. package/dist/cjs/packages/datasets/dist/captcha/dataset.cjs +0 -87
  69. package/dist/cjs/packages/datasets/dist/captcha/index.cjs +0 -27
  70. package/dist/cjs/packages/datasets/dist/captcha/merkle.cjs +0 -106
  71. package/dist/cjs/packages/datasets/dist/captcha/util.cjs +0 -16
  72. package/dist/cjs/packages/datasets/dist/index.cjs +0 -2
@@ -1,31 +1,39 @@
1
1
  import { AccountNotFoundError } from '../api/errors.js';
2
- import { ApiPromise, Keyring, WsProvider } from '@polkadot/api';
3
- import { ProcaptchaConfigSchema } from '@prosopo/types';
4
- import { Observable, from, lastValueFrom } from 'rxjs';
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 { ProviderApi } from '@prosopo/api';
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 { retry, take } from 'rxjs/operators';
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
- showModal: false,
18
- loading: false,
19
- index: 0,
20
- challenge: undefined,
21
- solutions: undefined,
22
- isHuman: false,
23
- captchaApi: undefined,
24
- account: undefined,
25
- });
26
- const buildUpdateState = (state, onStateUpdate) => (nextState) => {
27
- Object.assign(state, nextState);
28
- onStateUpdate(nextState);
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.error(error);
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.info('onHuman event triggered', output);
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.info('onOpen event triggered');
69
+ console.log('onOpen event triggered');
65
70
  },
66
71
  onClose: () => {
67
- console.info('onClose event triggered');
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
- * Build the config on demand, using the optional config passed in from the outside. State may override various
82
- * config values depending on the state of the captcha process. E.g. if the process has been started using account
83
- * "ABC" and then the user changes account to "DEF" via the optional config prop, the account in use will not change.
84
- * This is because the captcha process has already been started using account "ABC".
85
- * @returns the config for procaptcha
86
- */
87
- const getConfig = () => ProcaptchaConfigSchema.parse({
88
- ...configOptional,
89
- userAccountAddress: state.account ? state.account.account.address : configOptional.userAccountAddress || '',
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 || state.isHuman)
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({ dappAccount: getConfig().account.address, loading: true });
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
- if (await checkHumanInContract(contract, account)) {
114
- handleHumanInContract(account);
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
- // Verify cached provider is legitimate
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
- handleHumanInCachedProvider(providerUrlFromStorage, account, verifyDappUserResponse);
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
- // If not human in contract or cached provider, get new captcha from a random provider
134
- const randomProviderResponse = await getRandomProviderResponse(contract, account);
135
- const challenge = await getChallenge(randomProviderResponse, contract);
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: setTimeToComplete(challenge),
145
- blockNumber: getBlockNumberFromProvider(randomProviderResponse),
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
- const datasetId = getDatasetId();
209
+ updateState({ showModal: false });
210
+ const challenge = state.challenge;
160
211
  const salt = randomAsHex();
161
- // Build the captcha solution
162
- const submission = await getCaptchaApi().submitCaptchaSolution(getAccount().extension.signer, state.challenge.requestHash, datasetId, getSolutionsFromState(salt), salt);
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(getCaptchaApi().provider.provider.url.toString());
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: getAccount().account.address,
244
+ user: account.account.address,
178
245
  dapp: getDappAccount(),
179
246
  commitmentId: submission[1],
180
- blockNumber: getBlockNumberFromState(),
247
+ blockNumber,
181
248
  });
182
249
  setValidChallengeTimeout();
183
250
  }
184
251
  });
185
252
  };
186
- /**
187
- * (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).
188
- * @param hash the hash of the image
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 solution = at(state.solutions, state.index);
198
- handleIsSelected(solution, hash);
199
- updateState({ solutions: state.solutions });
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
- if (!config.web2 && !config.userAccountAddress) {
244
- throw new Error('Account address has not been set for web3 mode');
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(getNetwork(config), providerUrl, config.account.address);
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
- return state.account;
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
- return state.dappAccount;
350
+ const dappAccount = state.dappAccount;
351
+ return dappAccount;
440
352
  };
441
- const getBlockNumberFromState = () => {
353
+ const getBlockNumber = () => {
442
354
  if (!state.blockNumber) {
443
355
  throw new Error('Account not loaded');
444
356
  }
445
- return state.blockNumber;
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,