@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,122 @@
1
+ const Services = Object.freeze({
2
+ IDENTITY_SERVICE: 'Identity Service',
3
+ HEDGEHOG: 'Hedgehog',
4
+ DISCOVERY_PROVIDER: 'Discovery Provider',
5
+ CREATOR_NODE: 'Creator Node',
6
+ COMSTOCK: 'Comstock',
7
+ SOLANA_WEB3_MANAGER: 'Solana Web3 Manager'
8
+ })
9
+
10
+ class Base {
11
+ constructor (
12
+ userStateManager,
13
+ identityService,
14
+ hedgehog,
15
+ discoveryProvider,
16
+ web3Manager,
17
+ contracts,
18
+ ethWeb3Manager,
19
+ ethContracts,
20
+ solanaWeb3Manager,
21
+ anchorAudiusData,
22
+ wormholeClient,
23
+ creatorNode,
24
+ comstock,
25
+ captcha,
26
+ isServer,
27
+ logger = console
28
+ ) {
29
+ this.userStateManager = userStateManager
30
+ this.identityService = identityService
31
+ this.hedgehog = hedgehog
32
+ this.discoveryProvider = discoveryProvider
33
+ this.web3Manager = web3Manager
34
+ this.contracts = contracts
35
+ this.ethWeb3Manager = ethWeb3Manager
36
+ this.ethContracts = ethContracts
37
+ this.solanaWeb3Manager = solanaWeb3Manager
38
+ this.anchorAudiusData = anchorAudiusData
39
+ this.wormholeClient = wormholeClient
40
+ this.creatorNode = creatorNode
41
+ this.comstock = comstock
42
+ this.captcha = captcha
43
+ this.isServer = isServer
44
+ this.logger = logger
45
+
46
+ this._serviceMapping = {
47
+ [Services.IDENTITY_SERVICE]: this.identityService,
48
+ [Services.HEDGEHOG]: this.hedgehog,
49
+ [Services.DISCOVERY_PROVIDER]: this.discoveryProvider,
50
+ [Services.CREATOR_NODE]: this.creatorNode,
51
+ [Services.COMSTOCK]: this.comstock,
52
+ [Services.SOLANA_WEB3_MANAGER]: this.solanaWeb3Manager
53
+ }
54
+ }
55
+
56
+ REQUIRES (...services) {
57
+ services.forEach(s => {
58
+ if (!this._serviceMapping[s]) return Base._missingService(services)
59
+ })
60
+ }
61
+
62
+ IS_OBJECT (o) {
63
+ if (typeof (o) !== 'object') return Base._invalidType('object')
64
+ }
65
+
66
+ OBJECT_HAS_PROPS (o, props, requiredProps) {
67
+ const missingProps = []
68
+ props.forEach(prop => {
69
+ if (!Object.prototype.hasOwnProperty.call(o, prop)) missingProps.push(prop)
70
+ })
71
+ if (missingProps.length > 0) return Base._missingProps(missingProps)
72
+
73
+ const missingRequiredProps = []
74
+ requiredProps.forEach(prop => {
75
+ if (!Object.prototype.hasOwnProperty.call(o, prop) || o[prop] === '') missingRequiredProps.push(prop)
76
+ })
77
+ if (missingRequiredProps.length > 0) return Base._missingPropValues(missingRequiredProps)
78
+ }
79
+
80
+ FILE_IS_VALID (file) {
81
+ if (this.isServer) {
82
+ if (!file ||
83
+ typeof file !== 'object' ||
84
+ typeof file.pipe !== 'function' ||
85
+ !file.readable) { return Base._invalidFile() }
86
+ } else {
87
+ if (!file ||
88
+ typeof file !== 'object') { return Base._missingFile() }
89
+ }
90
+ }
91
+
92
+ /* ------- PRIVATE ------- */
93
+
94
+ static _missingService (...serviceNames) {
95
+ throw new Error(`Requires the following services: ${serviceNames.join(', ')}`)
96
+ }
97
+
98
+ static _invalidType (type) {
99
+ throw new Error(`Argument must be of type ${type}`)
100
+ }
101
+
102
+ static _missingProps (props) {
103
+ throw new Error(`Missing props ${props.join(', ')}`)
104
+ }
105
+
106
+ static _missingPropValues (props) {
107
+ throw new Error(`Missing field values ${props.join(', ')}`)
108
+ }
109
+
110
+ static _invalidFile () {
111
+ throw new Error('Expected file as readable stream')
112
+ }
113
+
114
+ static _missingFile () {
115
+ throw new Error('Missing or malformed file')
116
+ }
117
+ }
118
+
119
+ module.exports = {
120
+ Base,
121
+ Services
122
+ }
@@ -0,0 +1,168 @@
1
+ let urlJoin = require('proper-url-join')
2
+ if (urlJoin && urlJoin.default) urlJoin = urlJoin.default
3
+
4
+ const axios = require('axios')
5
+ const { Base, Services } = require('./base')
6
+ const { raceRequests } = require('../utils/network')
7
+ const retry = require('async-retry')
8
+
9
+ /**
10
+ * Downloads a file using an element in the DOM
11
+ * @param {*} url
12
+ * @param {*} filename
13
+ */
14
+ const downloadURL = (url, filename) => {
15
+ if (document) {
16
+ const link = document.createElement('a')
17
+ link.href = url
18
+ link.target = '_blank'
19
+ link.download = filename
20
+ link.click()
21
+ return
22
+ }
23
+ throw new Error('No body document found')
24
+ }
25
+
26
+ class File extends Base {
27
+ constructor (user, ...args) {
28
+ super(...args)
29
+
30
+ this.User = user
31
+ }
32
+
33
+ /**
34
+ * Fetches a file from Content Node with a given CID.
35
+ * @param {string} cid IPFS content identifier
36
+ * @param {Array<string>} creatorNodeGateways Content Node gateways to fetch content from
37
+ * @param {?function} callback callback called on each successful/failed fetch with
38
+ * [String, Bool](gateway, succeeded)
39
+ * Can be used for tracking metrics on which gateways were used.
40
+ */
41
+ async fetchCID (
42
+ cid,
43
+ creatorNodeGateways,
44
+ callback = null,
45
+ responseType = 'blob',
46
+ trackId = null
47
+ ) {
48
+ const urls = []
49
+
50
+ creatorNodeGateways.forEach(gateway => {
51
+ let gatewayWithCid = urlJoin(gateway, cid)
52
+ if (trackId) gatewayWithCid = urlJoin(gatewayWithCid, { query: { trackId } })
53
+ urls.push(gatewayWithCid)
54
+ })
55
+
56
+ return retry(async (bail) => {
57
+ try {
58
+ const { response, errored } = await raceRequests(urls, callback, {
59
+ method: 'get',
60
+ responseType
61
+ }, /* timeout */ null)
62
+
63
+ if (!response) {
64
+ const allUnauthorized = errored.every(error => error.response.status === 403)
65
+ if (allUnauthorized) {
66
+ // In the case for a 403, do not retry fetching
67
+ bail(new Error('Unauthorized'))
68
+ return
69
+ }
70
+ throw new Error(`Could not fetch ${cid}`)
71
+ }
72
+ return response
73
+ } catch (e) {
74
+ // TODO: Remove this fallback logic when no more users/tracks/playlists
75
+ // contain "legacy" image formats (no dir cid)
76
+ if (cid.includes('/')) { // dirCID -- an image
77
+ console.debug(`Attempted to fetch image ${cid} via legacy method`)
78
+ // Try legacy image format
79
+ // Lop off anything like /480x480.jpg in the CID
80
+ const legacyUrls = creatorNodeGateways.map(gateway => urlJoin(gateway, cid.split('/')[0]))
81
+ try {
82
+ const { response } = await raceRequests(legacyUrls, callback, {
83
+ method: 'get',
84
+ responseType
85
+ }, /* timeout */ null)
86
+ if (!response) throw new Error(`Could not fetch ${cid} via legacy method`)
87
+ return response
88
+ } catch (e) {
89
+ throw new Error(`Failed to retrieve ${cid} by legacy method`)
90
+ }
91
+ }
92
+
93
+ // Throw so we can retry
94
+ throw new Error(`Failed to retrieve ${cid}`)
95
+ }
96
+ }, {
97
+ minTimeout: 500,
98
+ maxTimeout: 4000,
99
+ factor: 3,
100
+ retries: 5,
101
+ onRetry: (err, i) => {
102
+ // eslint-disable-next-line no-console
103
+ console.log(`FetchCID attempt ${i} error: ${err}`)
104
+ }
105
+ })
106
+ }
107
+
108
+ /**
109
+ * Fetches a file from Content Node with a given CID. Follows the same pattern
110
+ * as fetchCID, but resolves with a download of the file rather than
111
+ * returning the response content.
112
+ * @param {string} cid IPFS content identifier
113
+ * @param {Array<string>} creatorNodeGateways Content Node gateways to fetch content from
114
+ * @param {string?} filename optional filename for the download
115
+ */
116
+ async downloadCID (cid, creatorNodeGateways, filename) {
117
+ const urls = creatorNodeGateways.map(gateway => urlJoin(gateway, cid, { query: { filename } }))
118
+
119
+ try {
120
+ // Races requests and fires the download callback for the first endpoint to
121
+ // respond with a valid response to a `head` request.
122
+ const { response } = await raceRequests(urls, (url) => downloadURL(url, filename), {
123
+ method: 'head'
124
+ }, /* timeout */ 10000)
125
+ return response
126
+ } catch (e) {
127
+ throw new Error(`Failed to retrieve ${cid}`)
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Checks if a CID exists on a Content Node.
133
+ * @param {string} cid IPFS content identifier
134
+ * @param {Array<string>} creatorNodeGateways Content Node gateways to fetch content from
135
+ * Eg. creatorNodeGateways = ["https://creatornode.audius.co/ipfs/", "https://creatornode2.audius.co/ipfs/"]
136
+ */
137
+ async checkIfCidAvailable (cid, creatorNodeGateways) {
138
+ const exists = {}
139
+
140
+ await Promise.all(creatorNodeGateways.map(async (gateway) => {
141
+ try {
142
+ const { status } = await axios({ url: urlJoin(gateway, cid), method: 'head' })
143
+ exists[gateway] = status === 200
144
+ } catch (err) {
145
+ exists[gateway] = false
146
+ }
147
+ }))
148
+
149
+ return exists
150
+ }
151
+
152
+ /**
153
+ * Uploads an image to the connected Content Node.
154
+ * @param {File} file
155
+ */
156
+ async uploadImage (file, square, timeoutMs = null) {
157
+ this.REQUIRES(Services.CREATOR_NODE)
158
+ this.FILE_IS_VALID(file)
159
+
160
+ // Assign a creator_node_endpoint to the user if necessary
161
+ await this.User.assignReplicaSetIfNecessary()
162
+
163
+ const resp = await this.creatorNode.uploadImage(file, square, /* onProgress */ undefined, timeoutMs)
164
+ return resp
165
+ }
166
+ }
167
+
168
+ module.exports = File
@@ -0,0 +1,328 @@
1
+ const { Base, Services } = require('./base')
2
+ const { Utils } = require('../utils')
3
+
4
+ const MAX_PLAYLIST_LENGTH = 200
5
+
6
+ class Playlists extends Base {
7
+ constructor (...args) {
8
+ super(...args)
9
+ this.getPlaylists = this.getPlaylists.bind(this)
10
+ this.getSavedPlaylists = this.getSavedPlaylists.bind(this)
11
+ this.getSavedAlbums = this.getSavedAlbums.bind(this)
12
+ this.createPlaylist = this.createPlaylist.bind(this)
13
+ this.addPlaylistTrack = this.addPlaylistTrack.bind(this)
14
+ this.orderPlaylistTracks = this.orderPlaylistTracks.bind(this)
15
+ this.validateTracksInPlaylist = this.validateTracksInPlaylist.bind(this)
16
+ this.uploadPlaylistCoverPhoto = this.uploadPlaylistCoverPhoto.bind(this)
17
+ this.updatePlaylistCoverPhoto = this.updatePlaylistCoverPhoto.bind(this)
18
+ this.updatePlaylistName = this.updatePlaylistName.bind(this)
19
+ this.updatePlaylistDescription = this.updatePlaylistDescription.bind(this)
20
+ this.updatePlaylistPrivacy = this.updatePlaylistPrivacy.bind(this)
21
+ this.addPlaylistRepost = this.addPlaylistRepost.bind(this)
22
+ this.deletePlaylistRepost = this.deletePlaylistRepost.bind(this)
23
+ this.deletePlaylistTrack = this.deletePlaylistTrack.bind(this)
24
+ this.addPlaylistSave = this.addPlaylistSave.bind(this)
25
+ this.deletePlaylistSave = this.deletePlaylistSave.bind(this)
26
+ this.deletePlaylist = this.deletePlaylist.bind(this)
27
+ }
28
+
29
+ /* ------- GETTERS ------- */
30
+
31
+ /**
32
+ * get full playlist objects, including tracks, for passed in array of playlistId
33
+ * @param {number} limit max # of items to return
34
+ * @param {number} offset offset into list to return from (for pagination)
35
+ * @param {Array} idsArray list of playlist ids
36
+ * @param {number} targetUserId the user whose playlists we're trying to get
37
+ * @param {boolean} withUsers whether to return users nested within the collection objects
38
+ * @returns {Array} array of playlist objects
39
+ * additional metadata fields on playlist objects:
40
+ * {Integer} repost_count - repost count for given playlist
41
+ * {Integer} save_count - save count for given playlist
42
+ * {Boolean} has_current_user_reposted - has current user reposted given playlist
43
+ * {Array} followee_reposts - followees of current user that have reposted given playlist
44
+ * {Boolean} has_current_user_reposted - has current user reposted given playlist
45
+ * {Boolean} has_current_user_saved - has current user saved given playlist
46
+ */
47
+ async getPlaylists (limit = 100, offset = 0, idsArray = null, targetUserId = null, withUsers = false) {
48
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
49
+ return this.discoveryProvider.getPlaylists(limit, offset, idsArray, targetUserId, withUsers)
50
+ }
51
+
52
+ /**
53
+ * Return saved playlists for current user
54
+ * NOTE in returned JSON, SaveType string one of track, playlist, album
55
+ * @param {number} limit - max # of items to return
56
+ * @param {number} offset - offset into list to return from (for pagination)
57
+ */
58
+ async getSavedPlaylists (limit = 100, offset = 0, withUsers = false) {
59
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
60
+ return this.discoveryProvider.getSavedPlaylists(limit, offset, withUsers)
61
+ }
62
+
63
+ /**
64
+ * Return saved albums for current user
65
+ * NOTE in returned JSON, SaveType string one of track, playlist, album
66
+ * @param {number} limit - max # of items to return
67
+ * @param {number} offset - offset into list to return from (for pagination)
68
+ */
69
+ async getSavedAlbums (limit = 100, offset = 0, withUsers = false) {
70
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
71
+ return this.discoveryProvider.getSavedAlbums(limit, offset, withUsers)
72
+ }
73
+
74
+ /* ------- SETTERS ------- */
75
+
76
+ /**
77
+ * Creates a new playlist
78
+ * @param {number} userId
79
+ * @param {string} playlistName
80
+ * @param {boolean} isPrivate
81
+ * @param {boolean} isAlbum
82
+ * @param {Array<number>} trackIds
83
+ */
84
+ async createPlaylist (userId, playlistName, isPrivate, isAlbum, trackIds) {
85
+ const maxInitialTracks = 50
86
+ const createInitialIdsArray = trackIds.slice(0, maxInitialTracks)
87
+ const postInitialIdsArray = trackIds.slice(maxInitialTracks)
88
+ let playlistId
89
+ let receipt = {}
90
+ try {
91
+ const response = await this.contracts.PlaylistFactoryClient.createPlaylist(
92
+ userId, playlistName, isPrivate, isAlbum, createInitialIdsArray
93
+ )
94
+ playlistId = response.playlistId
95
+ receipt = response.txReceipt
96
+
97
+ // Add remaining tracks
98
+ await Promise.all(postInitialIdsArray.map(trackId => {
99
+ return this.contracts.PlaylistFactoryClient.addPlaylistTrack(playlistId, trackId)
100
+ }))
101
+
102
+ // Order tracks
103
+ if (postInitialIdsArray.length > 0) {
104
+ receipt = await this.contracts.PlaylistFactoryClient.orderPlaylistTracks(playlistId, trackIds)
105
+ }
106
+ } catch (e) {
107
+ console.debug(`Reached libs createPlaylist catch block with playlist id ${playlistId}`)
108
+ console.error(e)
109
+ return { playlistId, error: true }
110
+ }
111
+ return { blockHash: receipt.blockHash, blockNumber: receipt.blockNumber, playlistId, error: false }
112
+ }
113
+
114
+ /**
115
+ * Adds a track to a given playlist
116
+ * @param {number} playlistId
117
+ * @param {number} trackId
118
+ */
119
+ async addPlaylistTrack (playlistId, trackId) {
120
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
121
+
122
+ const userId = this.userStateManager.getCurrentUserId()
123
+ const playlist = await this.discoveryProvider.getPlaylists(100, 0, [playlistId], userId)
124
+
125
+ // error if playlist does not exist or hasn't been indexed by discovery provider
126
+ if (!Array.isArray(playlist) || !playlist.length) {
127
+ throw new Error('Cannot add track - Playlist does not exist or has not yet been indexed by discovery provider')
128
+ }
129
+ // error if playlist already at max length
130
+ if (playlist[0].playlist_contents.track_ids.length >= MAX_PLAYLIST_LENGTH) {
131
+ throw new Error(`Cannot add track - playlist is already at max length of ${MAX_PLAYLIST_LENGTH}`)
132
+ }
133
+ return this.contracts.PlaylistFactoryClient.addPlaylistTrack(playlistId, trackId)
134
+ }
135
+
136
+ /**
137
+ * Reorders the tracks in a playlist
138
+ * @param {number} playlistId
139
+ * @param {Array<number>} trackIds
140
+ * @param {number?} retriesOverride [Optional, defaults to web3Manager.sendTransaction retries default]
141
+ */
142
+ async orderPlaylistTracks (playlistId, trackIds, retriesOverride) {
143
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
144
+ if (!Array.isArray(trackIds)) {
145
+ throw new Error('Cannot order playlist - trackIds must be array')
146
+ }
147
+
148
+ const userId = this.userStateManager.getCurrentUserId()
149
+ const playlist = await this.discoveryProvider.getPlaylists(100, 0, [playlistId], userId)
150
+
151
+ // error if playlist does not exist or hasn't been indexed by discovery provider
152
+ if (!Array.isArray(playlist) || !playlist.length) {
153
+ throw new Error('Cannot order playlist - Playlist does not exist or has not yet been indexed by discovery provider')
154
+ }
155
+
156
+ const playlistTrackIds = playlist[0].playlist_contents.track_ids.map(a => a.track)
157
+ // error if trackIds arg array length does not match playlist length
158
+ if (trackIds.length !== playlistTrackIds.length) {
159
+ throw new Error('Cannot order playlist - trackIds length must match playlist length')
160
+ }
161
+
162
+ // ensure existing playlist tracks and trackIds have same content, regardless of order
163
+ const trackIdsSorted = [...trackIds].sort()
164
+ const playlistTrackIdsSorted = playlistTrackIds.sort()
165
+ for (let i = 0; i < trackIdsSorted.length; i++) {
166
+ if (trackIdsSorted[i] !== playlistTrackIdsSorted[i]) {
167
+ throw new Error('Cannot order playlist - trackIds must have same content as playlist tracks')
168
+ }
169
+ }
170
+
171
+ return this.contracts.PlaylistFactoryClient.orderPlaylistTracks(playlistId, trackIds, retriesOverride)
172
+ }
173
+
174
+ /**
175
+ * Checks if a playlist has entered a corrupted state
176
+ * Check that each of the tracks within a playlist retrieved from discprov are in the onchain playlist
177
+ * Note: the onchain playlists stores the tracks as a mapping of track ID to track count and the
178
+ * track order is an event that is indexed by discprov. The track order event does not validate that the
179
+ * updated order of tracks has the correct track count, so a track order event w/ duplicate tracks can
180
+ * lead the playlist entering a corrupted state.
181
+ * @param {number} playlistId
182
+ */
183
+ async validateTracksInPlaylist (playlistId) {
184
+ this.REQUIRES(Services.DISCOVERY_PROVIDER, Services.CREATOR_NODE)
185
+
186
+ const userId = this.userStateManager.getCurrentUserId()
187
+ const playlistsReponse = await this.discoveryProvider.getPlaylists(1, 0, [playlistId], userId)
188
+
189
+ // error if playlist does not exist or hasn't been indexed by discovery provider
190
+ if (!Array.isArray(playlistsReponse) || !playlistsReponse.length) {
191
+ throw new Error('Cannot validate playlist - Playlist does not exist, is private and not owned by current user or has not yet been indexed by discovery provider')
192
+ }
193
+
194
+ const playlist = playlistsReponse[0]
195
+ const playlistTrackIds = playlist.playlist_contents.track_ids.map(a => a.track)
196
+
197
+ // Check if each track is in the playlist
198
+ const invalidTrackIds = []
199
+ for (const trackId of playlistTrackIds) {
200
+ const trackInPlaylist = await this.contracts.PlaylistFactoryClient.isTrackInPlaylist(playlistId, trackId)
201
+ if (!trackInPlaylist) invalidTrackIds.push(trackId)
202
+ }
203
+
204
+ return {
205
+ isValid: invalidTrackIds.length === 0,
206
+ invalidTrackIds
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Uploads a cover photo for a playlist without updating the actual playlist
212
+ * @param {File} coverPhotoFile the file to upload as the cover photo
213
+ * @return {string} CID of the uploaded cover photo
214
+ */
215
+ async uploadPlaylistCoverPhoto (coverPhotoFile) {
216
+ this.REQUIRES(Services.CREATOR_NODE)
217
+
218
+ const updatedPlaylistImage = await this.creatorNode.uploadImage(
219
+ coverPhotoFile,
220
+ true // square
221
+ )
222
+ return updatedPlaylistImage.dirCID
223
+ }
224
+
225
+ /**
226
+ * Updates the cover photo for a playlist
227
+ * @param {number} playlistId
228
+ * @param {File} coverPhoto
229
+ */
230
+ async updatePlaylistCoverPhoto (playlistId, coverPhoto) {
231
+ this.REQUIRES(Services.CREATOR_NODE)
232
+
233
+ const updatedPlaylistImageDirCid = await this.uploadPlaylistCoverPhoto(coverPhoto, true)
234
+ return this.contracts.PlaylistFactoryClient.updatePlaylistCoverPhoto(
235
+ playlistId,
236
+ Utils.formatOptionalMultihash(updatedPlaylistImageDirCid)
237
+ )
238
+ }
239
+
240
+ /**
241
+ * Updates a playlist name
242
+ * @param {number} playlistId
243
+ * @param {string} playlistName
244
+ */
245
+ async updatePlaylistName (playlistId, playlistName) {
246
+ return this.contracts.PlaylistFactoryClient.updatePlaylistName(playlistId, playlistName)
247
+ }
248
+
249
+ /**
250
+ * Updates a playlist description
251
+ * @param {number} playlistId
252
+ * @param {string} updatedPlaylistDescription
253
+ */
254
+ async updatePlaylistDescription (playlistId, updatedPlaylistDescription) {
255
+ return this.contracts.PlaylistFactoryClient.updatePlaylistDescription(playlistId, updatedPlaylistDescription)
256
+ }
257
+
258
+ /**
259
+ * Updates whether a playlist is public or private
260
+ * @param {number} playlistId
261
+ * @param {boolean} updatedPlaylistPrivacy
262
+ */
263
+ async updatePlaylistPrivacy (playlistId, updatedPlaylistPrivacy) {
264
+ return this.contracts.PlaylistFactoryClient.updatePlaylistPrivacy(playlistId, updatedPlaylistPrivacy)
265
+ }
266
+
267
+ /**
268
+ * Reposts a playlist for a user
269
+ * @param {number} userId
270
+ * @param {number} playlistId
271
+ */
272
+ async addPlaylistRepost (playlistId) {
273
+ const userId = this.userStateManager.getCurrentUserId()
274
+ return this.contracts.SocialFeatureFactoryClient.addPlaylistRepost(userId, playlistId)
275
+ }
276
+
277
+ /**
278
+ * Undoes a repost on a playlist for a user
279
+ * @param {number} userId
280
+ * @param {number} playlistId
281
+ */
282
+ async deletePlaylistRepost (playlistId) {
283
+ const userId = this.userStateManager.getCurrentUserId()
284
+ return this.contracts.SocialFeatureFactoryClient.deletePlaylistRepost(userId, playlistId)
285
+ }
286
+
287
+ /**
288
+ * Marks a track to be deleted from a playlist. The playlist entry matching
289
+ * the provided timestamp is deleted in the case of duplicates.
290
+ * @param {number} playlistId
291
+ * @param {number} deletedTrackId
292
+ * @param {string} deletedPlaylistTimestamp parseable timestamp (to be copied from playlist metadata)
293
+ * @param {number?} retriesOverride [Optional, defaults to web3Manager.sendTransaction retries default]
294
+ */
295
+ async deletePlaylistTrack (playlistId, deletedTrackId, deletedPlaylistTimestamp, retriesOverride) {
296
+ return this.contracts.PlaylistFactoryClient.deletePlaylistTrack(playlistId, deletedTrackId, deletedPlaylistTimestamp, retriesOverride)
297
+ }
298
+
299
+ /**
300
+ * Saves a playlist on behalf of a user
301
+ * @param {number} userId
302
+ * @param {number} playlistId
303
+ */
304
+ async addPlaylistSave (playlistId) {
305
+ const userId = this.userStateManager.getCurrentUserId()
306
+ return this.contracts.UserLibraryFactoryClient.addPlaylistSave(userId, playlistId)
307
+ }
308
+
309
+ /**
310
+ * Unsaves a playlist on behalf of a user
311
+ * @param {number} userId
312
+ * @param {number} playlistId
313
+ */
314
+ async deletePlaylistSave (playlistId) {
315
+ const userId = this.userStateManager.getCurrentUserId()
316
+ return this.contracts.UserLibraryFactoryClient.deletePlaylistSave(userId, playlistId)
317
+ }
318
+
319
+ /**
320
+ * Marks a playlist as deleted
321
+ * @param {number} playlistId
322
+ */
323
+ async deletePlaylist (playlistId) {
324
+ return this.contracts.PlaylistFactoryClient.deletePlaylist(playlistId)
325
+ }
326
+ }
327
+
328
+ module.exports = Playlists
@@ -0,0 +1,4 @@
1
+ // Placeholder type for rewardsAttester.ts
2
+ declare const SubmitAndEvaluateError: any
3
+
4
+ export { SubmitAndEvaluateError }