@audius/sdk 0.0.0

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 (278) hide show
  1. package/.eslintrc +38 -0
  2. package/.prettierrc.js +1 -0
  3. package/.python-version +1 -0
  4. package/Dockerfile +15 -0
  5. package/README.md +3 -0
  6. package/babel.config.js +3 -0
  7. package/data-contracts/ABIs/AdminUpgradeabilityProxy.json +132 -0
  8. package/data-contracts/ABIs/BaseAdminUpgradeabilityProxy.json +113 -0
  9. package/data-contracts/ABIs/BaseUpgradeabilityProxy.json +22 -0
  10. package/data-contracts/ABIs/DiscoveryProviderFactory.json +189 -0
  11. package/data-contracts/ABIs/DiscoveryProviderFactoryInterface.json +61 -0
  12. package/data-contracts/ABIs/DiscoveryProviderStorage.json +205 -0
  13. package/data-contracts/ABIs/DiscoveryProviderStorageInterface.json +65 -0
  14. package/data-contracts/ABIs/ECDSA.json +4 -0
  15. package/data-contracts/ABIs/IPLDBlacklistFactory.json +168 -0
  16. package/data-contracts/ABIs/Initializable.json +4 -0
  17. package/data-contracts/ABIs/Migrations.json +67 -0
  18. package/data-contracts/ABIs/OpenZeppelinUpgradesAddress.json +4 -0
  19. package/data-contracts/ABIs/Ownable.json +79 -0
  20. package/data-contracts/ABIs/PlaylistFactory.json +669 -0
  21. package/data-contracts/ABIs/PlaylistFactoryInterface.json +42 -0
  22. package/data-contracts/ABIs/PlaylistStorage.json +250 -0
  23. package/data-contracts/ABIs/PlaylistStorageInterface.json +129 -0
  24. package/data-contracts/ABIs/Proxy.json +10 -0
  25. package/data-contracts/ABIs/Registry.json +240 -0
  26. package/data-contracts/ABIs/RegistryContract.json +102 -0
  27. package/data-contracts/ABIs/RegistryContractInterface.json +28 -0
  28. package/data-contracts/ABIs/RegistryInterface.json +66 -0
  29. package/data-contracts/ABIs/SigningLogic.json +43 -0
  30. package/data-contracts/ABIs/SigningLogicInitializable.json +46 -0
  31. package/data-contracts/ABIs/SocialFeatureFactory.json +460 -0
  32. package/data-contracts/ABIs/SocialFeatureStorage.json +225 -0
  33. package/data-contracts/ABIs/SocialFeatureStorageInterface.json +123 -0
  34. package/data-contracts/ABIs/TestContract.json +135 -0
  35. package/data-contracts/ABIs/TestContractInterface.json +19 -0
  36. package/data-contracts/ABIs/TestContractWithStorage.json +165 -0
  37. package/data-contracts/ABIs/TestContractWithStorageInterface.json +24 -0
  38. package/data-contracts/ABIs/TestStorage.json +144 -0
  39. package/data-contracts/ABIs/TestStorageInterface.json +42 -0
  40. package/data-contracts/ABIs/TestUserReplicaSetManager.json +432 -0
  41. package/data-contracts/ABIs/TrackFactory.json +391 -0
  42. package/data-contracts/ABIs/TrackFactoryInterface.json +73 -0
  43. package/data-contracts/ABIs/TrackStorage.json +223 -0
  44. package/data-contracts/ABIs/TrackStorageInterface.json +121 -0
  45. package/data-contracts/ABIs/UpgradeabilityProxy.json +37 -0
  46. package/data-contracts/ABIs/UserFactory.json +657 -0
  47. package/data-contracts/ABIs/UserFactoryInterface.json +65 -0
  48. package/data-contracts/ABIs/UserLibraryFactory.json +334 -0
  49. package/data-contracts/ABIs/UserReplicaSetManager.json +418 -0
  50. package/data-contracts/ABIs/UserStorage.json +233 -0
  51. package/data-contracts/ABIs/UserStorageInterface.json +93 -0
  52. package/data-contracts/signatureSchemas.ts +1236 -0
  53. package/dist/core.d.ts +446 -0
  54. package/dist/core.js +769 -0
  55. package/dist/core.js.map +1 -0
  56. package/dist/index.d.ts +689 -0
  57. package/dist/index.js +72850 -0
  58. package/dist/index.js.map +1 -0
  59. package/eth-contracts/ABIs/Address.json +4 -0
  60. package/eth-contracts/ABIs/AudiusAdminUpgradeabilityProxy.json +105 -0
  61. package/eth-contracts/ABIs/AudiusClaimDistributor.json +4968 -0
  62. package/eth-contracts/ABIs/AudiusToken.json +724 -0
  63. package/eth-contracts/ABIs/BaseUpgradeabilityProxy.json +23 -0
  64. package/eth-contracts/ABIs/Checkpointing.json +4 -0
  65. package/eth-contracts/ABIs/ClaimsManager.json +539 -0
  66. package/eth-contracts/ABIs/Context.json +11 -0
  67. package/eth-contracts/ABIs/DelegateManager.json +989 -0
  68. package/eth-contracts/ABIs/DelegateManagerV2.json +1049 -0
  69. package/eth-contracts/ABIs/DelegateManagerV2Bad.json +1049 -0
  70. package/eth-contracts/ABIs/ERC20.json +252 -0
  71. package/eth-contracts/ABIs/ERC20Burnable.json +287 -0
  72. package/eth-contracts/ABIs/ERC20Detailed.json +270 -0
  73. package/eth-contracts/ABIs/ERC20Mintable.json +364 -0
  74. package/eth-contracts/ABIs/ERC20Pausable.json +397 -0
  75. package/eth-contracts/ABIs/EthRewardsManager.json +174 -0
  76. package/eth-contracts/ABIs/Governance.json +938 -0
  77. package/eth-contracts/ABIs/GovernanceUpgraded.json +953 -0
  78. package/eth-contracts/ABIs/GovernanceV2.json +938 -0
  79. package/eth-contracts/ABIs/IERC20.json +200 -0
  80. package/eth-contracts/ABIs/Initializable.json +4 -0
  81. package/eth-contracts/ABIs/InitializableV2.json +14 -0
  82. package/eth-contracts/ABIs/Migrations.json +71 -0
  83. package/eth-contracts/ABIs/MinterRole.json +91 -0
  84. package/eth-contracts/ABIs/MockAccount.json +62 -0
  85. package/eth-contracts/ABIs/MockDelegateManager.json +55 -0
  86. package/eth-contracts/ABIs/MockStakingCaller.json +259 -0
  87. package/eth-contracts/ABIs/MockWormhole.json +106 -0
  88. package/eth-contracts/ABIs/OpenZeppelinUpgradesAddress.json +4 -0
  89. package/eth-contracts/ABIs/Ownable.json +93 -0
  90. package/eth-contracts/ABIs/Pausable.json +150 -0
  91. package/eth-contracts/ABIs/PauserRole.json +91 -0
  92. package/eth-contracts/ABIs/Proxy.json +10 -0
  93. package/eth-contracts/ABIs/Registry.json +288 -0
  94. package/eth-contracts/ABIs/Roles.json +4 -0
  95. package/eth-contracts/ABIs/SafeERC20.json +4 -0
  96. package/eth-contracts/ABIs/SafeMath.json +4 -0
  97. package/eth-contracts/ABIs/ServiceProviderFactory.json +1153 -0
  98. package/eth-contracts/ABIs/ServiceTypeManager.json +337 -0
  99. package/eth-contracts/ABIs/Staking.json +555 -0
  100. package/eth-contracts/ABIs/StakingUpgraded.json +570 -0
  101. package/eth-contracts/ABIs/TestContract.json +44 -0
  102. package/eth-contracts/ABIs/TrustedNotifierManager.json +265 -0
  103. package/eth-contracts/ABIs/Uint256Helpers.json +4 -0
  104. package/eth-contracts/ABIs/UpgradeabilityProxy.json +40 -0
  105. package/eth-contracts/ABIs/Wormhole.json +45 -0
  106. package/eth-contracts/ABIs/WormholeClient.json +155 -0
  107. package/examples/file.mp3 +0 -0
  108. package/examples/initAudiusLibs.js +86 -0
  109. package/examples/initializeVersions.js +95 -0
  110. package/examples/pic.jpg +0 -0
  111. package/initScripts/configureLocalDiscProv.js +167 -0
  112. package/initScripts/helpers/claim.js +43 -0
  113. package/initScripts/helpers/distributeTokens.js +24 -0
  114. package/initScripts/helpers/spRegistration.js +138 -0
  115. package/initScripts/helpers/utils.js +34 -0
  116. package/initScripts/helpers/version.js +93 -0
  117. package/initScripts/local.js +617 -0
  118. package/initScripts/mainnet.js +131 -0
  119. package/initScripts/manageProdRelayerWallets.js +191 -0
  120. package/package.json +125 -0
  121. package/rollup.config.js +164 -0
  122. package/scripts/AudiusClaimDistributor.json +4968 -0
  123. package/scripts/Wormhole.json +155 -0
  124. package/scripts/addCIDToIpldBlacklist.js +124 -0
  125. package/scripts/circleci-test.sh +53 -0
  126. package/scripts/communityRewards/transferCommunityRewardsToSolana.js +222 -0
  127. package/scripts/ipfs.sh +58 -0
  128. package/scripts/migrate_contracts.sh +25 -0
  129. package/scripts/reset.sh +65 -0
  130. package/scripts/test.sh +77 -0
  131. package/src/api/account.js +670 -0
  132. package/src/api/base.js +122 -0
  133. package/src/api/file.js +168 -0
  134. package/src/api/playlist.js +328 -0
  135. package/src/api/rewards.d.ts +4 -0
  136. package/src/api/rewards.js +682 -0
  137. package/src/api/serviceProvider.js +154 -0
  138. package/src/api/track.js +604 -0
  139. package/src/api/user.js +888 -0
  140. package/src/api/user.test.js +172 -0
  141. package/src/constants.ts +7 -0
  142. package/src/core.ts +3 -0
  143. package/src/index.js +6 -0
  144. package/src/libs.d.ts +3 -0
  145. package/src/libs.js +619 -0
  146. package/src/sanityChecks/addSecondaries.js +40 -0
  147. package/src/sanityChecks/assignReplicaSetIfNecessary.js +10 -0
  148. package/src/sanityChecks/index.d.ts +9 -0
  149. package/src/sanityChecks/index.js +31 -0
  150. package/src/sanityChecks/isCreator.js +73 -0
  151. package/src/sanityChecks/needsRecoveryEmail.js +20 -0
  152. package/src/sanityChecks/rolloverNodes.js +74 -0
  153. package/src/sanityChecks/sanitizeNodes.js +24 -0
  154. package/src/sanityChecks/syncNodes.js +28 -0
  155. package/src/sdk/constants.ts +10 -0
  156. package/src/sdk/index.ts +1 -0
  157. package/src/sdk/oauth/Oauth.ts +265 -0
  158. package/src/sdk/oauth/index.ts +1 -0
  159. package/src/sdk/sdk.ts +102 -0
  160. package/src/service-selection/ServiceSelection.test.ts +320 -0
  161. package/src/service-selection/ServiceSelection.ts +460 -0
  162. package/src/service-selection/constants.ts +14 -0
  163. package/src/service-selection/index.ts +1 -0
  164. package/src/services/ABIDecoder/AudiusABIDecoder.ts +71 -0
  165. package/src/services/ABIDecoder/index.ts +1 -0
  166. package/src/services/comstock/Comstock.ts +39 -0
  167. package/src/services/comstock/index.ts +1 -0
  168. package/src/services/contracts/ContractClient.ts +227 -0
  169. package/src/services/contracts/GovernedContractClient.ts +53 -0
  170. package/src/services/contracts/ProviderSelection.ts +42 -0
  171. package/src/services/creatorNode/CreatorNode.ts +1065 -0
  172. package/src/services/creatorNode/CreatorNodeSelection.test.ts +997 -0
  173. package/src/services/creatorNode/CreatorNodeSelection.ts +488 -0
  174. package/src/services/creatorNode/constants.ts +10 -0
  175. package/src/services/creatorNode/index.ts +2 -0
  176. package/src/services/dataContracts/AudiusContracts.ts +234 -0
  177. package/src/services/dataContracts/IPLDBlacklistFactoryClient.ts +73 -0
  178. package/src/services/dataContracts/PlaylistFactoryClient.ts +370 -0
  179. package/src/services/dataContracts/RegistryClient.ts +95 -0
  180. package/src/services/dataContracts/SocialFeatureFactoryClient.ts +196 -0
  181. package/src/services/dataContracts/TrackFactoryClient.ts +131 -0
  182. package/src/services/dataContracts/UserFactoryClient.ts +351 -0
  183. package/src/services/dataContracts/UserLibraryFactoryClient.ts +115 -0
  184. package/src/services/dataContracts/UserReplicaSetManagerClient.ts +206 -0
  185. package/src/services/dataContracts/index.ts +1 -0
  186. package/src/services/discoveryProvider/DiscoveryProvider.ts +1168 -0
  187. package/src/services/discoveryProvider/DiscoveryProviderSelection.test.ts +536 -0
  188. package/src/services/discoveryProvider/DiscoveryProviderSelection.ts +383 -0
  189. package/src/services/discoveryProvider/constants.ts +13 -0
  190. package/src/services/discoveryProvider/index.ts +1 -0
  191. package/src/services/discoveryProvider/requests.ts +629 -0
  192. package/src/services/ethContracts/AudiusTokenClient.ts +163 -0
  193. package/src/services/ethContracts/ClaimDistributionClient.ts +45 -0
  194. package/src/services/ethContracts/ClaimsManagerClient.ts +102 -0
  195. package/src/services/ethContracts/DelegateManagerClient.ts +480 -0
  196. package/src/services/ethContracts/EthContracts.ts +359 -0
  197. package/src/services/ethContracts/EthRewardsManagerClient.ts +33 -0
  198. package/src/services/ethContracts/GovernanceClient.ts +451 -0
  199. package/src/services/ethContracts/RegistryClient.ts +33 -0
  200. package/src/services/ethContracts/ServiceProviderFactoryClient.ts +691 -0
  201. package/src/services/ethContracts/ServiceTypeManagerClient.ts +112 -0
  202. package/src/services/ethContracts/StakingProxyClient.ts +97 -0
  203. package/src/services/ethContracts/TrustedNotifierManagerClient.ts +101 -0
  204. package/src/services/ethContracts/WormholeClient.ts +97 -0
  205. package/src/services/ethContracts/index.ts +1 -0
  206. package/src/services/ethWeb3Manager/EthWeb3Manager.ts +239 -0
  207. package/src/services/ethWeb3Manager/index.ts +1 -0
  208. package/src/services/hedgehog/Hedgehog.ts +96 -0
  209. package/src/services/hedgehog/index.ts +1 -0
  210. package/src/services/identity/IdentityService.ts +551 -0
  211. package/src/services/identity/index.ts +1 -0
  212. package/src/services/identity/requests.ts +65 -0
  213. package/src/services/schemaValidator/SchemaValidator.ts +105 -0
  214. package/src/services/schemaValidator/index.ts +1 -0
  215. package/src/services/schemaValidator/schemas/trackSchema.json +267 -0
  216. package/src/services/schemaValidator/schemas/userSchema.json +230 -0
  217. package/src/services/solanaAudiusData/errors.ts +20 -0
  218. package/src/services/solanaAudiusData/index.ts +1189 -0
  219. package/src/services/solanaWeb3Manager/errors.js +101 -0
  220. package/src/services/solanaWeb3Manager/index.d.ts +46 -0
  221. package/src/services/solanaWeb3Manager/index.js +655 -0
  222. package/src/services/solanaWeb3Manager/padBNToUint8Array.ts +7 -0
  223. package/src/services/solanaWeb3Manager/rewards.js +941 -0
  224. package/src/services/solanaWeb3Manager/rewardsAttester.ts +1093 -0
  225. package/src/services/solanaWeb3Manager/tokenAccount.js +149 -0
  226. package/src/services/solanaWeb3Manager/transactionHandler.js +345 -0
  227. package/src/services/solanaWeb3Manager/transfer.js +272 -0
  228. package/src/services/solanaWeb3Manager/userBank.js +160 -0
  229. package/src/services/solanaWeb3Manager/utils.d.ts +31 -0
  230. package/src/services/solanaWeb3Manager/utils.js +163 -0
  231. package/src/services/solanaWeb3Manager/wAudio.js +28 -0
  232. package/src/services/solanaWeb3Manager/wAudio.test.js +30 -0
  233. package/src/services/web3Manager/Web3Config.ts +14 -0
  234. package/src/services/web3Manager/Web3Manager.ts +360 -0
  235. package/src/services/web3Manager/XMLHttpRequest.ts +11 -0
  236. package/src/services/web3Manager/index.ts +2 -0
  237. package/src/services/wormhole/index.js +424 -0
  238. package/src/types.ts +8 -0
  239. package/src/userStateManager.ts +53 -0
  240. package/src/utils/apiSigning.ts +51 -0
  241. package/src/utils/captcha.ts +97 -0
  242. package/src/utils/estimateGas.ts +64 -0
  243. package/src/utils/fileHasher.ts +278 -0
  244. package/src/utils/importContractABI.d.ts +9 -0
  245. package/src/utils/importContractABI.js +19 -0
  246. package/src/utils/index.ts +11 -0
  247. package/src/utils/multiProvider.ts +72 -0
  248. package/src/utils/network.test.ts +127 -0
  249. package/src/utils/network.ts +308 -0
  250. package/src/utils/promiseFight.test.ts +87 -0
  251. package/src/utils/promiseFight.ts +36 -0
  252. package/src/utils/signatures.ts +139 -0
  253. package/src/utils/types.ts +34 -0
  254. package/src/utils/utils.test.ts +36 -0
  255. package/src/utils/utils.ts +235 -0
  256. package/src/utils/uuid.ts +14 -0
  257. package/src/web3.d.ts +9 -0
  258. package/src/web3.js +8 -0
  259. package/tests/assets/static_image.png +0 -0
  260. package/tests/assets/static_text.txt +1 -0
  261. package/tests/audiusTokenClientTest.js +37 -0
  262. package/tests/creatorNodeTest.js +19 -0
  263. package/tests/fileHasherTest.js +125 -0
  264. package/tests/governanceTest.js +382 -0
  265. package/tests/helpers.js +105 -0
  266. package/tests/index.js +14 -0
  267. package/tests/playlistClientTest.js +157 -0
  268. package/tests/providerSelectionTest.js +241 -0
  269. package/tests/registryClientTest.js +19 -0
  270. package/tests/rewardsAttesterTest.js +373 -0
  271. package/tests/serviceTypeManagerClientTest.js +33 -0
  272. package/tests/socialFeatureClientTest.js +79 -0
  273. package/tests/stakingTest.js +302 -0
  274. package/tests/trackClientTest.js +86 -0
  275. package/tests/userClientTest.js +121 -0
  276. package/tsconfig.json +10 -0
  277. package/types/@audius-hedgehog/index.d.ts +39 -0
  278. package/types/abi-decoder/index.d.ts +41 -0
@@ -0,0 +1,1168 @@
1
+ // TODO: strictly type each method with the models defined in audius-client
2
+ import axios, { AxiosError, AxiosRequestConfig, Method } from 'axios'
3
+
4
+ import { Utils } from '../../utils'
5
+
6
+ import { DEFAULT_UNHEALTHY_BLOCK_DIFF, REQUEST_TIMEOUT_MS } from './constants'
7
+
8
+ import * as Requests from './requests'
9
+
10
+ import urlJoin, { PathArg } from 'proper-url-join'
11
+ import {
12
+ DiscoveryProviderSelection,
13
+ DiscoveryProviderSelectionConfig
14
+ } from './DiscoveryProviderSelection'
15
+ import type { CurrentUser, UserStateManager } from '../../userStateManager'
16
+ import type { EthContracts } from '../ethContracts'
17
+ import type { Web3Manager } from '../web3Manager'
18
+
19
+ const MAX_MAKE_REQUEST_RETRY_COUNT = 5
20
+ const MAX_MAKE_REQUEST_RETRIES_WITH_404 = 2
21
+
22
+ type RequestParams = {
23
+ queryParams: Record<string, string>
24
+ endpoint: string
25
+ timeout?: number
26
+ method?: Method
27
+ urlParams?: PathArg
28
+ headers?: Record<string, string>
29
+ data?: Record<string, unknown>
30
+ }
31
+
32
+ export type DiscoveryProviderConfig = {
33
+ whitelist?: Set<string>
34
+ blacklist?: Set<string>
35
+ userStateManager: UserStateManager
36
+ ethContracts: EthContracts
37
+ web3Manager?: Web3Manager
38
+ reselectTimeout?: number
39
+ selectionCallback?: DiscoveryProviderSelectionConfig['selectionCallback']
40
+ monitoringCallbacks?: DiscoveryProviderSelectionConfig['monitoringCallbacks']
41
+ selectionRequestTimeout?: number
42
+ selectionRequestRetries?: number
43
+ unhealthySlotDiffPlays?: number
44
+ unhealthyBlockDiff?: number
45
+ }
46
+
47
+ export type UserProfile = {
48
+ userId: number
49
+ email: string
50
+ name: string
51
+ handle: string
52
+ verified: boolean
53
+ imageURL?: string
54
+ sub: number
55
+ iat: string
56
+ }
57
+
58
+ /**
59
+ * Constructs a service class for a discovery node
60
+ * @param whitelist whether or not to only include specified nodes in selection
61
+ * @param userStateManager singleton UserStateManager instance
62
+ * @param ethContracts singleton EthContracts instance
63
+ * @param web3Manager
64
+ * @param reselectTimeout timeout to clear locally cached discovery providers
65
+ * @param selectionCallback invoked when a discovery node is selected
66
+ * @param monitoringCallbacks callbacks to be invoked with metrics from requests sent to a service
67
+ * @param monitoringCallbacks.request
68
+ * @param monitoringCallbacks.healthCheck
69
+ * @param selectionRequestTimeout the amount of time (ms) an individual request should take before reselecting
70
+ * @param selectionRequestRetries the number of retries to a given discovery node we make before reselecting
71
+ * @param unhealthySlotDiffPlays the number of slots we would consider a discovery node unhealthy
72
+ * @param unhealthyBlockDiff the number of missed blocks after which we would consider a discovery node unhealthy
73
+ */
74
+ export class DiscoveryProvider {
75
+ whitelist: Set<string> | undefined
76
+ blacklist: Set<string> | undefined
77
+ userStateManager: UserStateManager
78
+ ethContracts: EthContracts
79
+ web3Manager: Web3Manager | undefined
80
+ unhealthyBlockDiff: number
81
+ serviceSelector: DiscoveryProviderSelection
82
+ selectionRequestTimeout: number
83
+ selectionRequestRetries: number
84
+ unhealthySlotDiffPlays: number | undefined
85
+ request404Count: number
86
+ maxRequestsForTrue404: number
87
+ monitoringCallbacks:
88
+ | DiscoveryProviderSelection['monitoringCallbacks']
89
+ | undefined
90
+
91
+ discoveryProviderEndpoint: string | undefined
92
+
93
+ constructor({
94
+ whitelist,
95
+ blacklist,
96
+ userStateManager,
97
+ ethContracts,
98
+ web3Manager,
99
+ reselectTimeout,
100
+ selectionCallback,
101
+ monitoringCallbacks,
102
+ selectionRequestTimeout = REQUEST_TIMEOUT_MS,
103
+ selectionRequestRetries = MAX_MAKE_REQUEST_RETRY_COUNT,
104
+ unhealthySlotDiffPlays,
105
+ unhealthyBlockDiff
106
+ }: DiscoveryProviderConfig) {
107
+ this.whitelist = whitelist
108
+ this.blacklist = blacklist
109
+ this.userStateManager = userStateManager
110
+ this.ethContracts = ethContracts
111
+ this.web3Manager = web3Manager
112
+
113
+ this.unhealthyBlockDiff = unhealthyBlockDiff ?? DEFAULT_UNHEALTHY_BLOCK_DIFF
114
+ this.serviceSelector = new DiscoveryProviderSelection(
115
+ {
116
+ whitelist: this.whitelist,
117
+ blacklist: this.blacklist,
118
+ reselectTimeout,
119
+ selectionCallback,
120
+ monitoringCallbacks,
121
+ requestTimeout: selectionRequestTimeout,
122
+ unhealthySlotDiffPlays: unhealthySlotDiffPlays,
123
+ unhealthyBlockDiff: this.unhealthyBlockDiff
124
+ },
125
+ this.ethContracts
126
+ )
127
+ this.selectionRequestTimeout = selectionRequestTimeout
128
+ this.selectionRequestRetries = selectionRequestRetries
129
+ this.unhealthySlotDiffPlays = unhealthySlotDiffPlays
130
+
131
+ // Keep track of the number of times a request 404s so we know when a true 404 occurs
132
+ // Due to incident where some discovery nodes may erroneously be missing content #flare-51,
133
+ // we treat 404s differently than generic 4xx's or other 5xx errors.
134
+ // In the case of a 404, try a few other nodes
135
+ this.request404Count = 0
136
+ this.maxRequestsForTrue404 = MAX_MAKE_REQUEST_RETRIES_WITH_404
137
+
138
+ this.monitoringCallbacks = monitoringCallbacks
139
+ }
140
+
141
+ async init() {
142
+ const endpoint = await this.serviceSelector.select()
143
+ this.setEndpoint(endpoint)
144
+
145
+ if (endpoint && this.web3Manager && this.web3Manager.web3) {
146
+ // Set current user if it exists
147
+ const userAccount = await this.getUserAccount(
148
+ this.web3Manager.getWalletAddress()
149
+ )
150
+ if (userAccount) this.userStateManager.setCurrentUser(userAccount)
151
+ }
152
+ }
153
+
154
+ setEndpoint(endpoint: string) {
155
+ this.discoveryProviderEndpoint = endpoint
156
+ }
157
+
158
+ setUnhealthyBlockDiff(updatedBlockDiff = DEFAULT_UNHEALTHY_BLOCK_DIFF) {
159
+ this.unhealthyBlockDiff = updatedBlockDiff
160
+ this.serviceSelector.setUnhealthyBlockDiff(updatedBlockDiff)
161
+ }
162
+
163
+ setUnhealthySlotDiffPlays(updatedDiff: number) {
164
+ this.unhealthySlotDiffPlays = updatedDiff
165
+ this.serviceSelector.setUnhealthySlotDiffPlays(updatedDiff)
166
+ }
167
+
168
+ /**
169
+ * Get users with all relevant user data
170
+ * can be filtered by providing an integer array of ids
171
+ * @param limit
172
+ * @param offset
173
+ * @param idsArray
174
+ * @param walletAddress
175
+ * @param handle
176
+ * @param isCreator null returns all users, true returns creators only, false returns users only
177
+ * @returns {Object} {Array of User metadata Objects}
178
+ * additional metadata fields on user objects:
179
+ * {Integer} track_count - track count for given user
180
+ * {Integer} playlist_count - playlist count for given user
181
+ * {Integer} album_count - album count for given user
182
+ * {Integer} follower_count - follower count for given user
183
+ * {Integer} followee_count - followee count for given user
184
+ * {Integer} repost_count - repost count for given user
185
+ * {Integer} track_blocknumber - blocknumber of latest track for user
186
+ * {Boolean} does_current_user_follow - does current user follow given user
187
+ * {Array} followee_follows - followees of current user that follow given user
188
+ * @example
189
+ * await getUsers()
190
+ * await getUsers(100, 0, [3,2,6]) - Invalid user ids will not be accepted
191
+ */
192
+ async getUsers(
193
+ limit = 100,
194
+ offset = 0,
195
+ idsArray?: string[],
196
+ walletAddress?: string,
197
+ handle?: string,
198
+ isCreator = null,
199
+ minBlockNumber?: number
200
+ ) {
201
+ const req = Requests.getUsers(
202
+ limit,
203
+ offset,
204
+ idsArray,
205
+ walletAddress,
206
+ handle,
207
+ isCreator,
208
+ minBlockNumber
209
+ )
210
+ return await this._makeRequest(req)
211
+ }
212
+
213
+ /**
214
+ * get tracks with all relevant track data
215
+ * can be filtered by providing an integer array of ids
216
+ * @param limit
217
+ * @param offset
218
+ * @param idsArray
219
+ * @param targetUserId the owner of the tracks being queried
220
+ * @param sort a string of form eg. blocknumber:asc,timestamp:desc describing a sort path
221
+ * @param minBlockNumber The min block number
222
+ * @param filterDeleted If set to true, filters the deleted tracks
223
+ * @returns {Object} {Array of track metadata Objects}
224
+ * additional metadata fields on track objects:
225
+ * {Integer} repost_count - repost count for given track
226
+ * {Integer} save_count - save count for given track
227
+ * {Array} followee_reposts - followees of current user that have reposted given track
228
+ * {Boolean} has_current_user_reposted - has current user reposted given track
229
+ * {Boolean} has_current_user_saved - has current user saved given track
230
+ * @example
231
+ * await getTracks()
232
+ * await getTracks(100, 0, [3,2,6]) - Invalid track ids will not be accepted
233
+ */
234
+ async getTracks(
235
+ limit = 100,
236
+ offset = 0,
237
+ idsArray?: string[],
238
+ targetUserId?: string,
239
+ sort?: boolean,
240
+ minBlockNumber?: number,
241
+ filterDeleted?: boolean,
242
+ withUsers?: boolean
243
+ ) {
244
+ const req = Requests.getTracks(
245
+ limit,
246
+ offset,
247
+ idsArray,
248
+ targetUserId,
249
+ sort,
250
+ minBlockNumber,
251
+ filterDeleted,
252
+ withUsers
253
+ )
254
+
255
+ return await this._makeRequest(req)
256
+ }
257
+
258
+ /**
259
+ * Gets a particular track by its creator's handle and the track's URL slug
260
+ * @param handle the handle of the owner of the track
261
+ * @param slug the URL slug of the track, generally the title urlized
262
+ * @returns {Object} the requested track's metadata
263
+ */
264
+ async getTracksByHandleAndSlug(handle: string, slug: string) {
265
+ // Note: retries are disabled here because the v1 API response returns a 404 instead
266
+ // of an empty array, which can cause a retry storm.
267
+ // TODO: Rewrite this API with something more effective, change makeRequest to
268
+ // support 404s and not retry & use AudiusAPIClient.
269
+ return await this._makeRequest(
270
+ Requests.getTracksByHandleAndSlug(handle, slug),
271
+ /* retry */ false
272
+ )
273
+ }
274
+
275
+ /**
276
+ * @typedef {Object} getTracksIdentifier
277
+ * @property {string} handle
278
+ * @property {number} id
279
+ * @property {string} url_title
280
+ */
281
+
282
+ /**
283
+ * gets all tracks matching identifiers, including unlisted.
284
+ *
285
+ * @param identifiers
286
+ * @returns {(Array)} track
287
+ */
288
+ async getTracksIncludingUnlisted(identifiers: string[], withUsers = false) {
289
+ const req = Requests.getTracksIncludingUnlisted(identifiers, withUsers)
290
+ return await this._makeRequest(req)
291
+ }
292
+
293
+ /**
294
+ * Gets random tracks from trending tracks for a given genre.
295
+ * If genre not given, will return trending tracks across all genres.
296
+ * Excludes specified track ids.
297
+ *
298
+ * @param genre
299
+ * @param limit
300
+ * @param exclusionList
301
+ * @param time
302
+ * @returns {(Array)} track
303
+ */
304
+ async getRandomTracks(
305
+ genre: string,
306
+ limit: number,
307
+ exclusionList: number[],
308
+ time: string
309
+ ) {
310
+ const req = Requests.getRandomTracks(genre, limit, exclusionList, time)
311
+ return await this._makeRequest(req)
312
+ }
313
+
314
+ /**
315
+ * Gets all stems for a given trackId as an array of tracks.
316
+ * @param trackId
317
+ * @returns {(Array)} track
318
+ */
319
+ async getStemsForTrack(trackId: number) {
320
+ const req = Requests.getStemsForTrack(trackId)
321
+ return await this._makeRequest(req)
322
+ }
323
+
324
+ /**
325
+ * Gets all the remixes of a given trackId as an array of tracks.
326
+ * @param trackId
327
+ * @param limit
328
+ * @param offset
329
+ * @returns {(Array)} track
330
+ */
331
+ async getRemixesOfTrack(trackId: number, limit?: number, offset?: number) {
332
+ const req = Requests.getRemixesOfTrack(trackId, limit, offset)
333
+ return await this._makeRequest(req)
334
+ }
335
+
336
+ /**
337
+ * Gets the remix parents of a given trackId as an array of tracks.
338
+ * @param limit
339
+ * @param offset
340
+ * @returns {(Array)} track
341
+ */
342
+ async getRemixTrackParents(trackId: number, limit?: number, offset?: number) {
343
+ const req = Requests.getRemixTrackParents(trackId, limit, offset)
344
+ return await this._makeRequest(req)
345
+ }
346
+
347
+ /**
348
+ * Gets tracks trending on Audius.
349
+ * @param genre
350
+ * @param timeFrame one of day, week, month, or year
351
+ * @param idsArray track ids
352
+ * @param limit
353
+ * @param offset
354
+ */
355
+ async getTrendingTracks(
356
+ genre?: string,
357
+ timeFrame?: string,
358
+ idsArray?: number[],
359
+ limit?: number,
360
+ offset?: number,
361
+ withUsers = false
362
+ ) {
363
+ const req = Requests.getTrendingTracks(
364
+ genre,
365
+ timeFrame,
366
+ idsArray,
367
+ limit,
368
+ offset,
369
+ withUsers
370
+ )
371
+ return await this._makeRequest<{
372
+ listenCounts: Array<{ trackId: number; listens: number }>
373
+ }>(req)
374
+ }
375
+
376
+ /**
377
+ * get full playlist objects, including tracks, for passed in array of playlistId
378
+ * @returns {Array} array of playlist objects
379
+ * additional metadata fields on playlist objects:
380
+ * {Integer} repost_count - repost count for given playlist
381
+ * {Integer} save_count - save count for given playlist
382
+ * {Boolean} has_current_user_reposted - has current user reposted given playlist
383
+ * {Array} followee_reposts - followees of current user that have reposted given playlist
384
+ * {Boolean} has_current_user_reposted - has current user reposted given playlist
385
+ * {Boolean} has_current_user_saved - has current user saved given playlist
386
+ */
387
+ async getPlaylists(
388
+ limit = 100,
389
+ offset = 0,
390
+ idsArray = null,
391
+ targetUserId = null,
392
+ withUsers = false
393
+ ) {
394
+ const req = Requests.getPlaylists(
395
+ limit,
396
+ offset,
397
+ idsArray,
398
+ targetUserId,
399
+ withUsers
400
+ )
401
+ return await this._makeRequest(req)
402
+ }
403
+
404
+ /**
405
+ * Return social feed for current user
406
+ * @param filter - filter by "all", "original", or "repost"
407
+ * @param limit - max # of items to return
408
+ * @param offset - offset into list to return from (for pagination)
409
+ * @returns {Object} {Array of track and playlist metadata objects}
410
+ * additional metadata fields on track and playlist objects:
411
+ * {String} activity_timestamp - timestamp of requested user's repost for given track or playlist,
412
+ * used for sorting feed
413
+ * {Integer} repost_count - repost count of given track/playlist
414
+ * {Integer} save_count - save count of given track/playlist
415
+ * {Boolean} has_current_user_reposted - has current user reposted given track/playlist
416
+ * {Array} followee_reposts - followees of current user that have reposted given track/playlist
417
+ */
418
+ async getSocialFeed(
419
+ filter: string,
420
+ limit = 100,
421
+ offset = 0,
422
+ withUsers = false,
423
+ tracksOnly = false
424
+ ) {
425
+ const req = Requests.getSocialFeed(
426
+ filter,
427
+ limit,
428
+ offset,
429
+ withUsers,
430
+ tracksOnly
431
+ )
432
+ return await this._makeRequest(req)
433
+ }
434
+
435
+ /**
436
+ * Return repost feed for requested user
437
+ * @param userId - requested user id
438
+ * @param limit - max # of items to return (for pagination)
439
+ * @param offset - offset into list to return from (for pagination)
440
+ * @returns {Object} {Array of track and playlist metadata objects}
441
+ * additional metadata fields on track and playlist objects:
442
+ * {String} activity_timestamp - timestamp of requested user's repost for given track or playlist,
443
+ * used for sorting feed
444
+ * {Integer} repost_count - repost count of given track/playlist
445
+ * {Integer} save_count - save count of given track/playlist
446
+ * {Boolean} has_current_user_reposted - has current user reposted given track/playlist
447
+ * {Array} followee_reposts - followees of current user that have reposted given track/playlist
448
+ */
449
+ async getUserRepostFeed(
450
+ userId: number,
451
+ limit = 100,
452
+ offset = 0,
453
+ withUsers = false
454
+ ) {
455
+ const req = Requests.getUserRepostFeed(userId, limit, offset, withUsers)
456
+ return await this._makeRequest(req)
457
+ }
458
+
459
+ /**
460
+ * get intersection of users that follow followeeUserId and users that are followed by followerUserId
461
+ * @param followeeUserId user that is followed
462
+ * @param followerUserId user that follows
463
+ * @example
464
+ * getFollowIntersectionUsers(100, 0, 1, 1) - IDs must be valid
465
+ */
466
+ async getFollowIntersectionUsers(
467
+ limit = 100,
468
+ offset = 0,
469
+ followeeUserId: number,
470
+ followerUserId: number
471
+ ) {
472
+ const req = Requests.getFollowIntersectionUsers(
473
+ limit,
474
+ offset,
475
+ followeeUserId,
476
+ followerUserId
477
+ )
478
+ return await this._makeRequest(req)
479
+ }
480
+
481
+ /**
482
+ * get intersection of users that have reposted repostTrackId and users that are followed by followerUserId
483
+ * followee = user that is followed; follower = user that follows
484
+ * @param repostTrackId track that is reposted
485
+ * @param followerUserId user that reposted track
486
+ * @example
487
+ * getTrackRepostIntersectionUsers(100, 0, 1, 1) - IDs must be valid
488
+ */
489
+ async getTrackRepostIntersectionUsers(
490
+ limit = 100,
491
+ offset = 0,
492
+ repostTrackId: number,
493
+ followerUserId: number
494
+ ) {
495
+ const req = Requests.getTrackRepostIntersectionUsers(
496
+ limit,
497
+ offset,
498
+ repostTrackId,
499
+ followerUserId
500
+ )
501
+ return await this._makeRequest(req)
502
+ }
503
+
504
+ /**
505
+ * get intersection of users that have reposted repostPlaylistId and users that are followed by followerUserId
506
+ * followee = user that is followed; follower = user that follows
507
+ * @param repostPlaylistId playlist that is reposted
508
+ * @param followerUserId user that reposted track
509
+ * @example
510
+ * getPlaylistRepostIntersectionUsers(100, 0, 1, 1) - IDs must be valid
511
+ */
512
+ async getPlaylistRepostIntersectionUsers(
513
+ limit = 100,
514
+ offset = 0,
515
+ repostPlaylistId: number,
516
+ followerUserId: number
517
+ ) {
518
+ const req = Requests.getPlaylistRepostIntersectionUsers(
519
+ limit,
520
+ offset,
521
+ repostPlaylistId,
522
+ followerUserId
523
+ )
524
+ return await this._makeRequest(req)
525
+ }
526
+
527
+ /**
528
+ * get users that follow followeeUserId, sorted by follower count descending
529
+ * @param followeeUserId user that is followed
530
+ * @return {Array} array of user objects with standard user metadata
531
+ */
532
+ async getFollowersForUser(limit = 100, offset = 0, followeeUserId: number) {
533
+ const req = Requests.getFollowersForUser(limit, offset, followeeUserId)
534
+ return await this._makeRequest(req)
535
+ }
536
+
537
+ /**
538
+ * get users that are followed by followerUserId, sorted by follower count descending
539
+ * @param followerUserId user - i am the one who follows
540
+ * @return {Array} array of user objects with standard user metadata
541
+ */
542
+ async getFolloweesForUser(limit = 100, offset = 0, followerUserId: number) {
543
+ const req = Requests.getFolloweesForUser(limit, offset, followerUserId)
544
+ return await this._makeRequest(req)
545
+ }
546
+
547
+ /**
548
+ * get users that reposted repostTrackId, sorted by follower count descending
549
+ * @param repostTrackId
550
+ * @return {Array} array of user objects
551
+ * additional metadata fields on user objects:
552
+ * {Integer} follower_count - follower count of given user
553
+ * @example
554
+ * getRepostersForTrack(100, 0, 1) - ID must be valid
555
+ */
556
+ async getRepostersForTrack(limit = 100, offset = 0, repostTrackId: number) {
557
+ const req = Requests.getRepostersForTrack(limit, offset, repostTrackId)
558
+ return await this._makeRequest(req)
559
+ }
560
+
561
+ /**
562
+ * get users that reposted repostPlaylistId, sorted by follower count descending
563
+ * @param repostPlaylistId
564
+ * @return {Array} array of user objects
565
+ * additional metadata fields on user objects:
566
+ * {Integer} follower_count - follower count of given user
567
+ * @example
568
+ * getRepostersForPlaylist(100, 0, 1) - ID must be valid
569
+ */
570
+ async getRepostersForPlaylist(
571
+ limit = 100,
572
+ offset = 0,
573
+ repostPlaylistId: number
574
+ ) {
575
+ const req = Requests.getRepostersForPlaylist(
576
+ limit,
577
+ offset,
578
+ repostPlaylistId
579
+ )
580
+ return await this._makeRequest(req)
581
+ }
582
+
583
+ /**
584
+ * get users that saved saveTrackId, sorted by follower count descending
585
+ * @param saveTrackId
586
+ * @return {Array} array of user objects
587
+ * additional metadata fields on user objects:
588
+ * {Integer} follower_count - follower count of given user
589
+ * @example
590
+ * getSaversForTrack(100, 0, 1) - ID must be valid
591
+ */
592
+ async getSaversForTrack(limit = 100, offset = 0, saveTrackId: number) {
593
+ const req = Requests.getSaversForTrack(limit, offset, saveTrackId)
594
+ return await this._makeRequest(req)
595
+ }
596
+
597
+ /**
598
+ * get users that saved savePlaylistId, sorted by follower count descending
599
+ * @param savePlaylistId
600
+ * @return {Array} array of user objects
601
+ * additional metadata fields on user objects:
602
+ * {Integer} follower_count - follower count of given user
603
+ * @example
604
+ * getSaversForPlaylist(100, 0, 1) - ID must be valid
605
+ */
606
+ async getSaversForPlaylist(limit = 100, offset = 0, savePlaylistId: number) {
607
+ const req = Requests.getSaversForPlaylist(limit, offset, savePlaylistId)
608
+ return await this._makeRequest(req)
609
+ }
610
+
611
+ /**
612
+ * get whether a JWT given by Audius Oauth popup is valid
613
+ * @param token - JWT
614
+ * @return {UserProfile | false} profile info of user attached to JWT payload if the JWT is valid, else false
615
+ */
616
+ async verifyToken(token: string): Promise<UserProfile | false> {
617
+ const req = Requests.verifyToken(token)
618
+ const res = await this._makeRequest<UserProfile[]>(req)
619
+ if (res == null || res[0] == null) {
620
+ return false
621
+ } else {
622
+ return res[0]
623
+ }
624
+ }
625
+
626
+ /**
627
+ * Perform a full-text search. Returns tracks, users, playlists, albums
628
+ * with optional user-specific results for each
629
+ * - user, track, and playlist objects have all same data as returned from standalone endpoints
630
+ * @param text search query
631
+ * @param kind 'tracks', 'users', 'playlists', 'albums', 'all'
632
+ * @param limit max # of items to return per list (for pagination)
633
+ * @param offset offset into list to return from (for pagination)
634
+ */
635
+ async searchFull(text: string, kind: string, limit = 100, offset = 0) {
636
+ const req = Requests.searchFull(text, kind, limit, offset)
637
+ return await this._makeRequest(req)
638
+ }
639
+
640
+ /**
641
+ * Perform a lighter-weight full-text search. Returns tracks, users, playlists, albums
642
+ * with optional user-specific results for each
643
+ * - user, track, and playlist objects have core data, and track & playlist objects
644
+ * also return user object
645
+ * @param text search query
646
+ * @param limit max # of items to return per list (for pagination)
647
+ * @param offset offset into list to return from (for pagination)
648
+ */
649
+ async searchAutocomplete(text: string, limit = 100, offset = 0) {
650
+ const req = Requests.searchAutocomplete(text, limit, offset)
651
+ return await this._makeRequest(req)
652
+ }
653
+
654
+ /**
655
+ * Perform a tags-only search. Returns tracks with required tag and users
656
+ * that have used a tag greater than a specified number of times
657
+ * @param text search query
658
+ * @param userTagCount min # of times a user must have used a tag to be returned
659
+ * @param kind 'tracks', 'users', 'playlists', 'albums', 'all'
660
+ * @param limit max # of items to return per list (for pagination)
661
+ * @param offset offset into list to return from (for pagination)
662
+ */
663
+ async searchTags(
664
+ text: string,
665
+ userTagCount = 2,
666
+ kind = 'all',
667
+ limit = 100,
668
+ offset = 0
669
+ ) {
670
+ const req = Requests.searchTags(text, userTagCount, kind, limit, offset)
671
+ return await this._makeRequest(req)
672
+ }
673
+
674
+ /**
675
+ * Return saved playlists for current user
676
+ * NOTE in returned JSON, SaveType string one of track, playlist, album
677
+ * @param limit - max # of items to return
678
+ * @param offset - offset into list to return from (for pagination)
679
+ */
680
+ async getSavedPlaylists(limit = 100, offset = 0, withUsers = false) {
681
+ const req = Requests.getSavedPlaylists(limit, offset, withUsers)
682
+ return await this._makeRequest(req)
683
+ }
684
+
685
+ /**
686
+ * Return saved albums for current user
687
+ * NOTE in returned JSON, SaveType string one of track, playlist, album
688
+ * @param limit - max # of items to return
689
+ * @param offset - offset into list to return from (for pagination)
690
+ */
691
+ async getSavedAlbums(limit = 100, offset = 0, withUsers = false) {
692
+ const req = Requests.getSavedAlbums(limit, offset, withUsers)
693
+ return await this._makeRequest(req)
694
+ }
695
+
696
+ /**
697
+ * Return saved tracks for current user
698
+ * NOTE in returned JSON, SaveType string one of track, playlist, album
699
+ * @param limit - max # of items to return
700
+ * @param offset - offset into list to return from (for pagination)
701
+ */
702
+ async getSavedTracks(limit = 100, offset = 0, withUsers = false) {
703
+ const req = Requests.getSavedTracks(limit, offset, withUsers)
704
+ return await this._makeRequest(req)
705
+ }
706
+
707
+ /**
708
+ * Return user collections (saved & uploaded) along w/ users for those collections
709
+ */
710
+ async getUserAccount(wallet: string) {
711
+ const req = Requests.getUserAccount(wallet)
712
+ return await this._makeRequest<CurrentUser>(req)
713
+ }
714
+
715
+ async getTopPlaylists(
716
+ type: string,
717
+ limit: number,
718
+ mood: string,
719
+ filter: string,
720
+ withUsers = false
721
+ ) {
722
+ const req = Requests.getTopPlaylists(type, limit, mood, filter, withUsers)
723
+ return await this._makeRequest(req)
724
+ }
725
+
726
+ async getTopFolloweeWindowed(
727
+ type: string,
728
+ window: string,
729
+ limit: string,
730
+ withUsers = false
731
+ ) {
732
+ const req = Requests.getTopFolloweeWindowed(type, window, limit, withUsers)
733
+ return await this._makeRequest(req)
734
+ }
735
+
736
+ async getTopFolloweeSaves(type: string, limit: string, withUsers = false) {
737
+ const req = Requests.getTopFolloweeSaves(type, limit, withUsers)
738
+ return await this._makeRequest(req)
739
+ }
740
+
741
+ async getLatest(type: string) {
742
+ const req = Requests.getLatest(type)
743
+ return await this._makeRequest(req)
744
+ }
745
+
746
+ async getTopCreatorsByGenres(
747
+ genres: string[],
748
+ limit = 30,
749
+ offset = 0,
750
+ withUsers = false
751
+ ) {
752
+ const req = Requests.getTopCreatorsByGenres(
753
+ genres,
754
+ limit,
755
+ offset,
756
+ withUsers
757
+ )
758
+ return await this._makeRequest(req)
759
+ }
760
+
761
+ async getURSMContentNodes(ownerWallet: string | null = null) {
762
+ const req = Requests.getURSMContentNodes(ownerWallet)
763
+ return await this._makeRequest(req)
764
+ }
765
+
766
+ async getNotifications(
767
+ minBlockNumber: string,
768
+ trackIds: string[],
769
+ timeout: number
770
+ ) {
771
+ const req = Requests.getNotifications(minBlockNumber, trackIds, timeout)
772
+ return await this._makeRequest(req)
773
+ }
774
+
775
+ async getSolanaNotifications(minSlotNumber: number, timeout: number) {
776
+ const req = Requests.getSolanaNotifications(minSlotNumber, timeout)
777
+ return await this._makeRequest(req)
778
+ }
779
+
780
+ async getTrackListenMilestones(timeout: number) {
781
+ const req = Requests.getTrackListenMilestones(timeout)
782
+ return await this._makeRequest(req)
783
+ }
784
+
785
+ async getChallengeAttestation(
786
+ challengeId: string,
787
+ encodedUserId: string,
788
+ specifier: string,
789
+ oracleAddress: string,
790
+ discoveryProviderEndpoint: string
791
+ ) {
792
+ const req = Requests.getChallengeAttestation(
793
+ challengeId,
794
+ encodedUserId,
795
+ specifier,
796
+ oracleAddress
797
+ )
798
+ const { data } = await this._performRequestWithMonitoring(
799
+ req,
800
+ discoveryProviderEndpoint
801
+ )
802
+ return data
803
+ }
804
+
805
+ async getCreateSenderAttestation(
806
+ senderEthAddress: string,
807
+ discoveryProviderEndpoint: string
808
+ ) {
809
+ const req = Requests.getCreateSenderAttestation(senderEthAddress)
810
+ const { data } = await this._performRequestWithMonitoring(
811
+ req,
812
+ discoveryProviderEndpoint
813
+ )
814
+ return data
815
+ }
816
+
817
+ async getUndisbursedChallenges(
818
+ limit: number | null = null,
819
+ offset: number | null = null,
820
+ completedBlockNumber: string | null = null,
821
+ encodedUserId: number | null = null
822
+ ) {
823
+ const req = Requests.getUndisbursedChallenges(
824
+ limit,
825
+ offset,
826
+ completedBlockNumber,
827
+ encodedUserId
828
+ )
829
+ const res = await this._makeRequest<Array<{ amount: string }>>(req)
830
+ if (!res) return []
831
+ return res.map((r) => ({ ...r, amount: parseInt(r.amount) }))
832
+ }
833
+
834
+ /* ------- INTERNAL FUNCTIONS ------- */
835
+
836
+ /**
837
+ * Performs a single request, defined in the request, via axios, calling any
838
+ * monitoring callbacks as needed.
839
+ *
840
+ * @param {{
841
+ endpoint: string,
842
+ urlParams: string,
843
+ queryParams: object,
844
+ method: string,
845
+ headers: object,
846
+ }} requestObj
847
+ * @param {string} discoveryProviderEndpoint
848
+ * @returns
849
+ * @memberof DiscoveryProvider
850
+ */
851
+ async _performRequestWithMonitoring(
852
+ requestObj: RequestParams,
853
+ discoveryProviderEndpoint: string
854
+ ) {
855
+ const axiosRequest = this._createDiscProvRequest(
856
+ requestObj,
857
+ discoveryProviderEndpoint
858
+ )
859
+ let response
860
+ let parsedResponse
861
+
862
+ const url = new URL(axiosRequest.url ?? '')
863
+ const start = Date.now()
864
+ try {
865
+ response = await axios(axiosRequest)
866
+ const duration = Date.now() - start
867
+ parsedResponse = Utils.parseDataFromResponse(response)
868
+
869
+ // Fire monitoring callbacks for request success case
870
+ if (this.monitoringCallbacks && 'request' in this.monitoringCallbacks) {
871
+ try {
872
+ this.monitoringCallbacks.request({
873
+ endpoint: url.origin,
874
+ pathname: url.pathname,
875
+ queryString: url.search,
876
+ signer: response.data.signer,
877
+ signature: response.data.signature,
878
+ requestMethod: axiosRequest.method,
879
+ status: response.status,
880
+ responseTimeMillis: duration
881
+ })
882
+ } catch (e) {
883
+ // Swallow errors -- this method should not throw generally
884
+ console.error(e)
885
+ }
886
+ }
887
+ } catch (e) {
888
+ const error = e as AxiosError
889
+ const resp = error.response
890
+ const duration = Date.now() - start
891
+ const errMsg = error.response?.data ?? error
892
+
893
+ // Fire monitoring callbaks for request failure case
894
+ if (this.monitoringCallbacks && 'request' in this.monitoringCallbacks) {
895
+ try {
896
+ this.monitoringCallbacks.request({
897
+ endpoint: url.origin,
898
+ pathname: url.pathname,
899
+ queryString: url.search,
900
+ requestMethod: axiosRequest.method,
901
+ status: resp?.status,
902
+ responseTimeMillis: duration
903
+ })
904
+ } catch (e) {
905
+ // Swallow errors -- this method should not throw generally
906
+ console.error(e)
907
+ }
908
+ }
909
+ if (resp && resp.status === 404) {
910
+ // We have 404'd. Throw that error message back out
911
+ throw new Error('404')
912
+ }
913
+
914
+ throw errMsg
915
+ }
916
+ return parsedResponse
917
+ }
918
+
919
+ /**
920
+ * Gets how many blocks behind a discovery node is.
921
+ * If this method throws (missing data in health check response),
922
+ * return an unhealthy number of blocks
923
+ * @param parsedResponse health check response object
924
+ * @returns a number of blocks if behind or null if not behind
925
+ */
926
+ async _getBlocksBehind(parsedResponse: {
927
+ latest_indexed_block: number
928
+ latest_chain_block: number
929
+ }) {
930
+ try {
931
+ const {
932
+ latest_indexed_block: indexedBlock,
933
+ latest_chain_block: chainBlock
934
+ } = parsedResponse
935
+
936
+ const blockDiff = chainBlock - indexedBlock
937
+ if (blockDiff > this.unhealthyBlockDiff) {
938
+ return blockDiff
939
+ }
940
+ return null
941
+ } catch (e) {
942
+ console.error(e)
943
+ return this.unhealthyBlockDiff
944
+ }
945
+ }
946
+
947
+ /**
948
+ * Gets how many plays slots behind a discovery node is.
949
+ * If this method throws (missing data in health check response),
950
+ * return an unhealthy number of slots
951
+ * @param parsedResponse health check response object
952
+ * @returns a number of slots if behind or null if not behind
953
+ */
954
+ async _getPlaysSlotsBehind(parsedResponse: {
955
+ latest_indexed_slot_plays: number
956
+ latest_chain_slot_plays: number
957
+ }) {
958
+ if (!this.unhealthySlotDiffPlays) return null
959
+
960
+ try {
961
+ const {
962
+ latest_indexed_slot_plays: indexedSlotPlays,
963
+ latest_chain_slot_plays: chainSlotPlays
964
+ } = parsedResponse
965
+
966
+ const slotDiff = chainSlotPlays - indexedSlotPlays
967
+ if (slotDiff > this.unhealthySlotDiffPlays) {
968
+ return slotDiff
969
+ }
970
+ return null
971
+ } catch (e) {
972
+ console.error(e)
973
+ return this.unhealthySlotDiffPlays
974
+ }
975
+ }
976
+
977
+ /**
978
+ * Makes a request to a discovery node, reselecting if necessary
979
+ * @param {{
980
+ * endpoint: string
981
+ * urlParams: object
982
+ * queryParams: object
983
+ * method: string
984
+ * headers: object
985
+ * }} {
986
+ * endpoint: the base route
987
+ * urlParams: string of URL params to be concatenated after base route
988
+ * queryParams: URL query (search) params
989
+ * method: string HTTP method
990
+ * }
991
+ * @param retry whether to retry on failure
992
+ * @param attemptedRetries number of attempted retries (stops retrying at max)
993
+ */
994
+ async _makeRequest<Response>(
995
+ requestObj: Record<string, unknown>,
996
+ retry = true,
997
+ attemptedRetries = 0
998
+ ): Promise<Response | undefined | null> {
999
+ try {
1000
+ const newDiscProvEndpoint =
1001
+ await this.getHealthyDiscoveryProviderEndpoint(attemptedRetries)
1002
+
1003
+ // If new DP endpoint is selected, update disc prov endpoint and reset attemptedRetries count
1004
+ if (this.discoveryProviderEndpoint !== newDiscProvEndpoint) {
1005
+ let updateDiscProvEndpointMsg = `Current Discovery Provider endpoint ${this.discoveryProviderEndpoint} is unhealthy. `
1006
+ updateDiscProvEndpointMsg += `Switching over to the new Discovery Provider endpoint ${newDiscProvEndpoint}!`
1007
+ console.info(updateDiscProvEndpointMsg)
1008
+ this.discoveryProviderEndpoint = newDiscProvEndpoint
1009
+ attemptedRetries = 0
1010
+ }
1011
+ } catch (e) {
1012
+ console.error(e)
1013
+ return
1014
+ }
1015
+ let parsedResponse
1016
+ try {
1017
+ parsedResponse = await this._performRequestWithMonitoring(
1018
+ requestObj as RequestParams,
1019
+ this.discoveryProviderEndpoint
1020
+ )
1021
+ } catch (e) {
1022
+ const error = e as Error
1023
+ const failureStr = 'Failed to make Discovery Provider request, '
1024
+ const attemptStr = `attempt #${attemptedRetries}, `
1025
+ const errorStr = `error ${JSON.stringify(error.message)}, `
1026
+ const requestStr = `request: ${JSON.stringify(requestObj)}`
1027
+ const fullErrString = `${failureStr}${attemptStr}${errorStr}${requestStr}`
1028
+
1029
+ console.warn(fullErrString)
1030
+
1031
+ if (retry) {
1032
+ if (error.message === '404') {
1033
+ this.request404Count += 1
1034
+ if (this.request404Count < this.maxRequestsForTrue404) {
1035
+ // In the case of a 404, retry with a different discovery node entirely
1036
+ // using selectionRequestRetries + 1 to force reselection
1037
+ return await this._makeRequest(
1038
+ requestObj,
1039
+ retry,
1040
+ this.selectionRequestRetries + 1
1041
+ )
1042
+ } else {
1043
+ this.request404Count = 0
1044
+ return null
1045
+ }
1046
+ }
1047
+
1048
+ // In the case of an unknown error, retry with attempts += 1
1049
+ return await this._makeRequest(requestObj, retry, attemptedRetries + 1)
1050
+ }
1051
+
1052
+ return null
1053
+ }
1054
+
1055
+ // Validate health check response
1056
+
1057
+ // Regressed mode signals we couldn't find a node that wasn't behind by some measure
1058
+ // so we should should pick something
1059
+ const notInRegressedMode =
1060
+ this.ethContracts && !this.ethContracts.isInRegressedMode()
1061
+
1062
+ const blockDiff = await this._getBlocksBehind(parsedResponse)
1063
+ if (notInRegressedMode && blockDiff) {
1064
+ if (retry) {
1065
+ console.info(
1066
+ `${this.discoveryProviderEndpoint} is too far behind [block diff: ${blockDiff}]. Retrying request at attempt #${attemptedRetries}...`
1067
+ )
1068
+ return await this._makeRequest(requestObj, retry, attemptedRetries + 1)
1069
+ }
1070
+ return null
1071
+ }
1072
+
1073
+ const playsSlotDiff = await this._getPlaysSlotsBehind(parsedResponse)
1074
+ if (notInRegressedMode && playsSlotDiff) {
1075
+ if (retry) {
1076
+ console.info(
1077
+ `${this.discoveryProviderEndpoint} is too far behind [slot diff: ${playsSlotDiff}]. Retrying request at attempt #${attemptedRetries}...`
1078
+ )
1079
+ return await this._makeRequest(requestObj, retry, attemptedRetries + 1)
1080
+ }
1081
+ return null
1082
+ }
1083
+
1084
+ // Reset 404 counts
1085
+ this.request404Count = 0
1086
+
1087
+ // Everything looks good, return the data!
1088
+ return parsedResponse.data
1089
+ }
1090
+
1091
+ /**
1092
+ * Gets the healthy discovery provider endpoint used in creating the axios request later.
1093
+ * If the number of retries is over the max count for retires, clear the cache and reselect
1094
+ * another healthy discovery provider. Else, return the current discovery provider endpoint
1095
+ * @param attemptedRetries the number of attempted requests made to the current disc prov endpoint
1096
+ */
1097
+ async getHealthyDiscoveryProviderEndpoint(attemptedRetries: number) {
1098
+ let endpoint = this.discoveryProviderEndpoint as string
1099
+ if (attemptedRetries > this.selectionRequestRetries) {
1100
+ // Add to unhealthy list if current disc prov endpoint has reached max retry count
1101
+ console.info(`Attempted max retries with endpoint ${endpoint}`)
1102
+ this.serviceSelector.addUnhealthy(endpoint)
1103
+
1104
+ // Clear the cached endpoint and select new endpoint from backups
1105
+ this.serviceSelector.clearCached()
1106
+ endpoint = await this.serviceSelector.select()
1107
+ }
1108
+
1109
+ // If there are no more available backups, throw error
1110
+ if (!endpoint) {
1111
+ throw new Error('All Discovery Providers are unhealthy and unavailable.')
1112
+ }
1113
+
1114
+ return endpoint
1115
+ }
1116
+
1117
+ /**
1118
+ * Creates the discovery provider axios request object with necessary configs
1119
+ * @param requestObj
1120
+ * @param discoveryProviderEndpoint
1121
+ */
1122
+ _createDiscProvRequest(
1123
+ requestObj: RequestParams,
1124
+ discoveryProviderEndpoint: string
1125
+ ) {
1126
+ // Sanitize URL params if needed
1127
+ if (requestObj.queryParams) {
1128
+ Object.entries(requestObj.queryParams).forEach(([k, v]) => {
1129
+ if (v === undefined || v === null) {
1130
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1131
+ delete requestObj.queryParams[k]
1132
+ }
1133
+ })
1134
+ }
1135
+
1136
+ const requestUrl = urlJoin(
1137
+ discoveryProviderEndpoint,
1138
+ requestObj.endpoint,
1139
+ requestObj.urlParams,
1140
+ { query: requestObj.queryParams }
1141
+ )
1142
+
1143
+ let headers: Record<string, string> = {}
1144
+ if (requestObj.headers) {
1145
+ headers = requestObj.headers
1146
+ }
1147
+ const currentUserId = this.userStateManager.getCurrentUserId()
1148
+ if (currentUserId) {
1149
+ headers['X-User-ID'] = currentUserId
1150
+ }
1151
+
1152
+ const timeout = requestObj.timeout ?? this.selectionRequestTimeout
1153
+ let axiosRequest: AxiosRequestConfig = {
1154
+ url: requestUrl,
1155
+ headers: headers,
1156
+ method: requestObj.method ?? 'get',
1157
+ timeout
1158
+ }
1159
+
1160
+ if (requestObj.method === 'post' && requestObj.data) {
1161
+ axiosRequest = {
1162
+ ...axiosRequest,
1163
+ data: requestObj.data
1164
+ }
1165
+ }
1166
+ return axiosRequest
1167
+ }
1168
+ }