@prosopo/procaptcha 2.0.3 → 2.1.1

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.
@@ -2,19 +2,16 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const utilCrypto = require("@polkadot/util-crypto");
4
4
  const random = require("@polkadot/util-crypto/random");
5
- require("../account/dist/index.cjs");
5
+ const string = require("@polkadot/util/string");
6
+ const account = require("@prosopo/account");
6
7
  const api = require("@prosopo/api");
7
8
  const common = require("@prosopo/common");
8
- const loadBalancer = require("@prosopo/load-balancer");
9
9
  const procaptchaCommon = require("@prosopo/procaptcha-common");
10
10
  const types = require("@prosopo/types");
11
11
  const util = require("@prosopo/util");
12
12
  const utils = require("../utils/utils.cjs");
13
13
  const ProsopoCaptchaApi = require("./ProsopoCaptchaApi.cjs");
14
14
  const storage = require("./storage.cjs");
15
- const string = require("@polkadot/util/string");
16
- const ExtensionWeb2 = require("../account/dist/extension/ExtensionWeb2.cjs");
17
- const ExtensionWeb3 = require("../account/dist/extension/ExtensionWeb3.cjs");
18
15
  const defaultState = () => {
19
16
  return {
20
17
  // note order matters! see buildUpdateState. These fields are set in order, so disable modal first, then set loading to false, etc.
@@ -29,28 +26,8 @@ const defaultState = () => {
29
26
  // don't handle timeout here, this should be handled by the state management
30
27
  };
31
28
  };
32
- const getRandomActiveProvider = (config) => {
33
- const randomIntBetween = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
34
- const PROVIDERS = loadBalancer.loadBalancer(config.defaultEnvironment);
35
- const randomProvderObj = util.at(
36
- PROVIDERS,
37
- randomIntBetween(0, PROVIDERS.length - 1)
38
- );
39
- return {
40
- providerAccount: randomProvderObj.address,
41
- provider: {
42
- url: randomProvderObj.url,
43
- datasetId: randomProvderObj.datasetId,
44
- datasetIdContent: randomProvderObj.datasetIdContent
45
- }
46
- };
47
- };
48
29
  function Manager(configOptional, state, onStateUpdate, callbacks) {
49
30
  const events = procaptchaCommon.getDefaultEvents(onStateUpdate, state, callbacks);
50
- const dispatchErrorEvent = (err) => {
51
- const error = err instanceof Error ? err : new Error(String(err));
52
- events.onError(error);
53
- };
54
31
  const updateState = procaptchaCommon.buildUpdateState(state, onStateUpdate);
55
32
  const getConfig = () => {
56
33
  const config = {
@@ -62,152 +39,167 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
62
39
  }
63
40
  return types.ProcaptchaConfigSchema.parse(config);
64
41
  };
65
- const fallable = async (fn) => {
66
- try {
67
- await fn();
68
- } catch (err) {
69
- console.error(err);
70
- dispatchErrorEvent(err);
71
- updateState({ isHuman: false, showModal: false, loading: false });
72
- }
73
- };
74
42
  const start = async () => {
75
43
  events.onOpen();
76
- await fallable(async () => {
77
- if (state.loading) {
78
- return;
79
- }
80
- if (state.isHuman) {
81
- return;
82
- }
83
- await utilCrypto.cryptoWaitReady();
84
- resetState();
85
- updateState({ loading: true });
86
- const config = getConfig();
87
- updateState({ dappAccount: config.account.address });
88
- await utils.sleep(100);
89
- const account = await loadAccount();
90
- const getRandomProviderResponse = getRandomActiveProvider(getConfig());
91
- const providerUrl = getRandomProviderResponse.provider.url;
92
- const providerApi = await loadProviderApi(providerUrl);
93
- const captchaApi = new ProsopoCaptchaApi.ProsopoCaptchaApi(
94
- account.account.address,
95
- getRandomProviderResponse,
96
- providerApi,
97
- config.web2,
98
- config.account.address || ""
99
- );
100
- updateState({ captchaApi });
101
- const challenge = await captchaApi.getCaptchaChallenge();
102
- if (challenge.captchas.length <= 0) {
103
- throw new common.ProsopoDatasetError("DEVELOPER.PROVIDER_NO_CAPTCHA");
104
- }
105
- const timeMillis = challenge.captchas.map(
106
- (captcha) => captcha.timeLimitMs || config.captchas.image.challengeTimeout
107
- ).reduce((a, b) => a + b);
108
- const timeout = setTimeout(() => {
109
- events.onChallengeExpired();
110
- updateState({ isHuman: false, showModal: false, loading: false });
111
- }, timeMillis);
112
- updateState({
113
- index: 0,
114
- solutions: challenge.captchas.map(() => []),
115
- challenge,
116
- showModal: true,
117
- timeout
118
- });
119
- });
44
+ await procaptchaCommon.providerRetry(
45
+ async () => {
46
+ if (state.loading) {
47
+ return;
48
+ }
49
+ if (state.isHuman) {
50
+ return;
51
+ }
52
+ await utilCrypto.cryptoWaitReady();
53
+ resetState();
54
+ updateState({ loading: true });
55
+ updateState({
56
+ attemptCount: state.attemptCount ? state.attemptCount + 1 : 1
57
+ });
58
+ const config = getConfig();
59
+ updateState({ dappAccount: config.account.address });
60
+ await utils.sleep(100);
61
+ const account2 = await loadAccount();
62
+ const getRandomProviderResponse = await procaptchaCommon.getRandomActiveProvider(
63
+ getConfig()
64
+ );
65
+ const providerUrl = getRandomProviderResponse.provider.url;
66
+ const providerApi = await loadProviderApi(providerUrl);
67
+ const captchaApi = new ProsopoCaptchaApi.ProsopoCaptchaApi(
68
+ account2.account.address,
69
+ getRandomProviderResponse,
70
+ providerApi,
71
+ config.web2,
72
+ config.account.address || ""
73
+ );
74
+ updateState({ captchaApi });
75
+ const challenge = await captchaApi.getCaptchaChallenge();
76
+ if (challenge.error) {
77
+ updateState({
78
+ loading: false,
79
+ error: challenge.error
80
+ });
81
+ } else {
82
+ if (challenge.captchas.length <= 0) {
83
+ throw new common.ProsopoDatasetError("DEVELOPER.PROVIDER_NO_CAPTCHA");
84
+ }
85
+ const timeMillis = challenge.captchas.map(
86
+ (captcha) => captcha.timeLimitMs || config.captchas.image.challengeTimeout
87
+ ).reduce((a, b) => a + b);
88
+ const timeout = setTimeout(() => {
89
+ events.onChallengeExpired();
90
+ updateState({ isHuman: false, showModal: false, loading: false });
91
+ }, timeMillis);
92
+ updateState({
93
+ index: 0,
94
+ solutions: challenge.captchas.map(() => []),
95
+ challenge,
96
+ showModal: true,
97
+ timeout
98
+ });
99
+ }
100
+ },
101
+ start,
102
+ resetState,
103
+ state.attemptCount,
104
+ 10
105
+ );
120
106
  };
121
107
  const submit = async () => {
122
- await fallable(async () => {
123
- clearTimeout();
124
- if (!state.challenge) {
125
- throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
126
- context: { error: "Cannot submit, no Captcha found in state" }
127
- });
128
- }
129
- updateState({ showModal: false });
130
- const challenge = state.challenge;
131
- const salt = random.randomAsHex();
132
- const captchaSolution = state.challenge.captchas.map(
133
- (captcha, index) => {
134
- const solution = util.at(state.solutions, index);
135
- return {
136
- captchaId: captcha.captchaId,
137
- captchaContentId: captcha.captchaContentId,
138
- salt,
139
- solution
140
- };
108
+ await procaptchaCommon.providerRetry(
109
+ async () => {
110
+ clearTimeout();
111
+ if (!state.challenge) {
112
+ throw new common.ProsopoError("CAPTCHA.NO_CAPTCHA", {
113
+ context: { error: "Cannot submit, no Captcha found in state" }
114
+ });
141
115
  }
142
- );
143
- const account = getAccount();
144
- const signer = getExtension(account).signer;
145
- const first = util.at(challenge.captchas, 0);
146
- if (!first.datasetId) {
147
- throw new common.ProsopoDatasetError("CAPTCHA.INVALID_CAPTCHA_ID", {
148
- context: { error: "No datasetId set for challenge" }
149
- });
150
- }
151
- const captchaApi = state.captchaApi;
152
- if (!captchaApi) {
153
- throw new common.ProsopoError("CAPTCHA.INVALID_TOKEN", {
154
- context: { error: "No Captcha API found in state" }
155
- });
156
- }
157
- if (!signer || !signer.signRaw) {
158
- throw new common.ProsopoEnvError("GENERAL.CANT_FIND_KEYRINGPAIR", {
159
- context: {
160
- error: "Signer is not defined, cannot sign message to prove account ownership"
116
+ updateState({ showModal: false });
117
+ const challenge = state.challenge;
118
+ const salt = random.randomAsHex();
119
+ const captchaSolution = state.challenge.captchas.map(
120
+ (captcha, index) => {
121
+ const solution = util.at(state.solutions, index);
122
+ return {
123
+ captchaId: captcha.captchaId,
124
+ captchaContentId: captcha.captchaContentId,
125
+ salt,
126
+ solution
127
+ };
161
128
  }
129
+ );
130
+ const account2 = getAccount();
131
+ const signer = getExtension(account2).signer;
132
+ const first = util.at(challenge.captchas, 0);
133
+ if (!first.datasetId) {
134
+ throw new common.ProsopoDatasetError("CAPTCHA.INVALID_CAPTCHA_ID", {
135
+ context: { error: "No datasetId set for challenge" }
136
+ });
137
+ }
138
+ const captchaApi = state.captchaApi;
139
+ if (!captchaApi) {
140
+ throw new common.ProsopoError("CAPTCHA.INVALID_TOKEN", {
141
+ context: { error: "No Captcha API found in state" }
142
+ });
143
+ }
144
+ if (!signer || !signer.signRaw) {
145
+ throw new common.ProsopoEnvError("GENERAL.CANT_FIND_KEYRINGPAIR", {
146
+ context: {
147
+ error: "Signer is not defined, cannot sign message to prove account ownership"
148
+ }
149
+ });
150
+ }
151
+ const userRequestHashSignature = await signer.signRaw({
152
+ address: account2.account.address,
153
+ data: string.stringToHex(challenge.requestHash),
154
+ type: "bytes"
162
155
  });
163
- }
164
- const userRequestHashSignature = await signer.signRaw({
165
- address: account.account.address,
166
- data: string.stringToHex(challenge.requestHash),
167
- type: "bytes"
168
- });
169
- const submission = await captchaApi.submitCaptchaSolution(
170
- userRequestHashSignature.signature,
171
- challenge.requestHash,
172
- captchaSolution,
173
- challenge.timestamp,
174
- challenge.signature.provider.requestHash
175
- );
176
- const isHuman = submission[0].verified;
177
- if (!isHuman) {
178
- events.onFailed();
179
- }
180
- updateState({
181
- submission,
182
- isHuman,
183
- loading: false
184
- });
185
- if (state.isHuman) {
186
- const providerUrl = captchaApi.provider.provider.url;
187
- storage.setProcaptchaStorage({
188
- ...storage.getProcaptchaStorage(),
189
- providerUrl
156
+ const submission = await captchaApi.submitCaptchaSolution(
157
+ userRequestHashSignature.signature,
158
+ challenge.requestHash,
159
+ captchaSolution,
160
+ challenge.timestamp,
161
+ challenge.signature.provider.requestHash
162
+ );
163
+ const isHuman = submission[0].verified;
164
+ if (!isHuman) {
165
+ events.onFailed();
166
+ }
167
+ updateState({
168
+ submission,
169
+ isHuman,
170
+ loading: false
190
171
  });
191
- events.onHuman(
192
- types.encodeProcaptchaOutput({
193
- [types.ApiParams.providerUrl]: providerUrl,
194
- [types.ApiParams.user]: account.account.address,
195
- [types.ApiParams.dapp]: getDappAccount(),
196
- [types.ApiParams.commitmentId]: util.hashToHex(submission[1]),
197
- [types.ApiParams.timestamp]: challenge.timestamp,
198
- [types.ApiParams.signature]: {
199
- [types.ApiParams.provider]: {
200
- [types.ApiParams.requestHash]: challenge.signature.provider.requestHash
201
- },
202
- [types.ApiParams.user]: {
203
- [types.ApiParams.requestHash]: userRequestHashSignature.signature
172
+ if (state.isHuman) {
173
+ const providerUrl = captchaApi.provider.provider.url;
174
+ storage.setProcaptchaStorage({
175
+ ...storage.getProcaptchaStorage(),
176
+ providerUrl
177
+ });
178
+ events.onHuman(
179
+ types.encodeProcaptchaOutput({
180
+ [types.ApiParams.providerUrl]: providerUrl,
181
+ [types.ApiParams.user]: account2.account.address,
182
+ [types.ApiParams.dapp]: getDappAccount(),
183
+ [types.ApiParams.commitmentId]: util.hashToHex(submission[1]),
184
+ [types.ApiParams.timestamp]: challenge.timestamp,
185
+ [types.ApiParams.signature]: {
186
+ [types.ApiParams.provider]: {
187
+ [types.ApiParams.requestHash]: challenge.signature.provider.requestHash
188
+ },
189
+ [types.ApiParams.user]: {
190
+ [types.ApiParams.requestHash]: userRequestHashSignature.signature
191
+ }
204
192
  }
205
- }
206
- })
207
- );
208
- setValidChallengeTimeout();
209
- }
210
- });
193
+ })
194
+ );
195
+ setValidChallengeTimeout();
196
+ }
197
+ },
198
+ start,
199
+ resetState,
200
+ state.attemptCount,
201
+ 10
202
+ );
211
203
  };
212
204
  const cancel = async () => {
213
205
  clearTimeout();
@@ -282,10 +274,10 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
282
274
  context: { error: "Account address has not been set for web3 mode" }
283
275
  });
284
276
  }
285
- const ext = config.web2 ? new ExtensionWeb2.ExtensionWeb2() : new ExtensionWeb3.ExtensionWeb3();
286
- const account = await ext.getAccount(config);
287
- storage.setAccount(account.account.address);
288
- updateState({ account });
277
+ const ext = config.web2 ? new account.ExtensionWeb2() : new account.ExtensionWeb3();
278
+ const account$1 = await ext.getAccount(config);
279
+ storage.setAccount(account$1.account.address);
280
+ updateState({ account: account$1 });
289
281
  return getAccount();
290
282
  };
291
283
  const getAccount = () => {
@@ -294,8 +286,8 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
294
286
  context: { error: "Account not loaded" }
295
287
  });
296
288
  }
297
- const account = state.account;
298
- return account;
289
+ const account2 = state.account;
290
+ return account2;
299
291
  };
300
292
  const getDappAccount = () => {
301
293
  if (!state.dappAccount) {
@@ -305,13 +297,13 @@ function Manager(configOptional, state, onStateUpdate, callbacks) {
305
297
  return dappAccount;
306
298
  };
307
299
  const getExtension = (possiblyAccount) => {
308
- const account = possiblyAccount || getAccount();
309
- if (!account.extension) {
300
+ const account2 = possiblyAccount || getAccount();
301
+ if (!account2.extension) {
310
302
  throw new common.ProsopoEnvError("ACCOUNT.NO_POLKADOT_EXTENSION", {
311
303
  context: { error: "Extension not loaded" }
312
304
  });
313
305
  }
314
- return account.extension;
306
+ return account2.extension;
315
307
  };
316
308
  return {
317
309
  start,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
3
  const common = require("@prosopo/common");
4
4
  const datasets = require("@prosopo/datasets");
5
+ const types = require("@prosopo/types");
5
6
  class ProsopoCaptchaApi {
6
7
  constructor(userAccount, provider, providerApi, web2, dappAccount) {
7
8
  this.userAccount = userAccount;
@@ -19,6 +20,9 @@ class ProsopoCaptchaApi {
19
20
  this.userAccount,
20
21
  this.provider
21
22
  );
23
+ if (captchaChallenge[types.ApiParams.error]) {
24
+ return captchaChallenge;
25
+ }
22
26
  for (const captcha of captchaChallenge.captchas) {
23
27
  for (const item of captcha.items) {
24
28
  if (item.data) {
@@ -1 +1 @@
1
- {"version":3,"file":"Manager.d.ts","sourceRoot":"","sources":["../../src/modules/Manager.ts"],"names":[],"mappings":"AAwBA,OAAO,EAKL,KAAK,mBAAmB,EAExB,KAAK,4BAA4B,EAEjC,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAK7B,MAAM,gBAAgB,CAAC;AAkDxB,wBAAgB,OAAO,CACrB,cAAc,EAAE,4BAA4B,EAC5C,KAAK,EAAE,eAAe,EACtB,aAAa,EAAE,uBAAuB,EACtC,SAAS,EAAE,mBAAmB;;;;mBAuPR,MAAM;;EA2I7B"}
1
+ {"version":3,"file":"Manager.d.ts","sourceRoot":"","sources":["../../src/modules/Manager.ts"],"names":[],"mappings":"AA8BA,OAAO,EAKN,KAAK,mBAAmB,EAExB,KAAK,4BAA4B,EAEjC,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAG5B,MAAM,gBAAgB,CAAC;AAwBxB,wBAAgB,OAAO,CACtB,cAAc,EAAE,4BAA4B,EAC5C,KAAK,EAAE,eAAe,EACtB,aAAa,EAAE,uBAAuB,EACtC,SAAS,EAAE,mBAAmB;;;;mBA8PR,MAAM;;EA2I5B"}