@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,888 @@
1
+ const { pick, isEqual } = require('lodash')
2
+ const { Base, Services } = require('./base')
3
+ const { Utils } = require('../utils')
4
+ const { CreatorNode, getSpIDForEndpoint, setSpIDForEndpoint } = require('../services/creatorNode')
5
+
6
+ // User metadata fields that are required on the metadata object and can have
7
+ // null or non-null values
8
+ const USER_PROPS = [
9
+ 'is_creator',
10
+ 'is_verified',
11
+ 'is_deactivated',
12
+ 'name',
13
+ 'handle',
14
+ 'profile_picture',
15
+ 'profile_picture_sizes',
16
+ 'cover_photo',
17
+ 'cover_photo_sizes',
18
+ 'bio',
19
+ 'location',
20
+ 'creator_node_endpoint',
21
+ 'associated_wallets',
22
+ 'associated_sol_wallets',
23
+ 'collectibles',
24
+ 'playlist_library',
25
+ 'events'
26
+ ]
27
+ // User metadata fields that are required on the metadata object and only can have
28
+ // non-null values
29
+ const USER_REQUIRED_PROPS = [
30
+ 'name',
31
+ 'handle'
32
+ ]
33
+ // Constants for user metadata fields
34
+ const USER_PROP_NAME_CONSTANTS = Object.freeze({
35
+ NAME: 'name',
36
+ IS_CREATOR: 'is_creator',
37
+ BIO: 'bio',
38
+ LOCATION: 'location',
39
+ PROFILE_PICTURE_SIZES: 'profile_picture_sizes',
40
+ COVER_PHOTO_SIZES: 'cover_photo_sizes',
41
+ CREATOR_NODE_ENDPOINT: 'creator_node_endpoint'
42
+ })
43
+
44
+ class Users extends Base {
45
+ constructor (serviceProvider, preferHigherPatchForPrimary, preferHigherPatchForSecondaries, ...args) {
46
+ super(...args)
47
+
48
+ this.ServiceProvider = serviceProvider
49
+ this.preferHigherPatchForPrimary = preferHigherPatchForPrimary
50
+ this.preferHigherPatchForSecondaries = preferHigherPatchForSecondaries
51
+
52
+ this.getUsers = this.getUsers.bind(this)
53
+ this.getMutualFollowers = this.getMutualFollowers.bind(this)
54
+ this.getFollowersForUser = this.getFollowersForUser.bind(this)
55
+ this.getFolloweesForUser = this.getFolloweesForUser.bind(this)
56
+ this.getUserRepostFeed = this.getUserRepostFeed.bind(this)
57
+ this.getSocialFeed = this.getSocialFeed.bind(this)
58
+ this.getTopCreatorsByGenres = this.getTopCreatorsByGenres.bind(this)
59
+ this.uploadProfileImages = this.uploadProfileImages.bind(this)
60
+ this.addUser = this.addUser.bind(this)
61
+ this.updateUser = this.updateUser.bind(this)
62
+ this.updateCreator = this.updateCreator.bind(this)
63
+ this.upgradeToCreator = this.upgradeToCreator.bind(this)
64
+ this.updateIsVerified = this.updateIsVerified.bind(this)
65
+ this.addUserFollow = this.addUserFollow.bind(this)
66
+ this.deleteUserFollow = this.deleteUserFollow.bind(this)
67
+
68
+ // For adding replica set to users on sign up
69
+ this.assignReplicaSet = this.assignReplicaSet.bind(this)
70
+
71
+ this.getClockValuesFromReplicaSet = this.getClockValuesFromReplicaSet.bind(this)
72
+ this._waitForCreatorNodeEndpointIndexing = this._waitForCreatorNodeEndpointIndexing.bind(this)
73
+ this._addUserOperations = this._addUserOperations.bind(this)
74
+ this._updateUserOperations = this._updateUserOperations.bind(this)
75
+ this._validateUserMetadata = this._validateUserMetadata.bind(this)
76
+ this.cleanUserMetadata = this.cleanUserMetadata.bind(this)
77
+
78
+ // For adding a creator_node_endpoint for a user if null
79
+ this.assignReplicaSetIfNecessary = this.assignReplicaSetIfNecessary.bind(this)
80
+ }
81
+
82
+ /* ----------- GETTERS ---------- */
83
+
84
+ /**
85
+ * get users with all relevant user data
86
+ * can be filtered by providing an integer array of ids
87
+ * @param {number} limit
88
+ * @param {number} offset
89
+ * @param {Object} idsArray
90
+ * @param {String} walletAddress
91
+ * @param {String} handle
92
+ * @param {Boolean} isCreator null returns all users, true returns creators only, false returns users only
93
+ * @param {number} currentUserId the currently logged in user
94
+ * @returns {Object} {Array of User metadata Objects}
95
+ * additional metadata fields on user objects:
96
+ * {Integer} track_count - track count for given user
97
+ * {Integer} playlist_count - playlist count for given user
98
+ * {Integer} album_count - album count for given user
99
+ * {Integer} follower_count - follower count for given user
100
+ * {Integer} followee_count - followee count for given user
101
+ * {Integer} repost_count - repost count for given user
102
+ * {Integer} track_blocknumber - blocknumber of latest track for user
103
+ * {Boolean} does_current_user_follow - does current user follow given user
104
+ * {Array} followee_follows - followees of current user that follow given user
105
+ * @example
106
+ * await getUsers()
107
+ * await getUsers(100, 0, [3,2,6]) - Invalid user ids will not be accepted
108
+ */
109
+ async getUsers (limit = 100, offset = 0, idsArray = null, walletAddress = null, handle = null, isCreator = null, minBlockNumber = null) {
110
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
111
+ return this.discoveryProvider.getUsers(limit, offset, idsArray, walletAddress, handle, isCreator, minBlockNumber)
112
+ }
113
+
114
+ /**
115
+ * get intersection of users that follow followeeUserId and users that are followed by followerUserId
116
+ * @param {number} followeeUserId user that is followed
117
+ * @example
118
+ * getMutualFollowers(100, 0, 1, 1) - IDs must be valid
119
+ */
120
+ async getMutualFollowers (limit = 100, offset = 0, followeeUserId) {
121
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
122
+ const followerUserId = this.userStateManager.getCurrentUserId()
123
+ if (followerUserId) {
124
+ return this.discoveryProvider.getFollowIntersectionUsers(limit, offset, followeeUserId, followerUserId)
125
+ }
126
+ return []
127
+ }
128
+
129
+ /**
130
+ * get users that follow followeeUserId, sorted by follower count descending
131
+ * @param {number} currentUserId the currently logged in user
132
+ * @param {number} followeeUserId user that is followed
133
+ * @return {Array} array of user objects with standard user metadata
134
+ */
135
+ async getFollowersForUser (limit = 100, offset = 0, followeeUserId) {
136
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
137
+ return this.discoveryProvider.getFollowersForUser(limit, offset, followeeUserId)
138
+ }
139
+
140
+ /**
141
+ * get users that are followed by followerUserId, sorted by follower count descending
142
+ * @param {number} currentUserId the currently logged in user
143
+ * @param {number} followerUserId user - i am the one who follows
144
+ * @return {Array} array of user objects with standard user metadata
145
+ */
146
+ async getFolloweesForUser (limit = 100, offset = 0, followerUserId) {
147
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
148
+ return this.discoveryProvider.getFolloweesForUser(limit, offset, followerUserId)
149
+ }
150
+
151
+ /**
152
+ * Return repost feed for requested user
153
+ * @param {number} userId - requested user id
154
+ * @param {filter} string - filter by "all", "original", or "repost"
155
+ * @param {number} limit - max # of items to return (for pagination)
156
+ * @param {number} offset - offset into list to return from (for pagination)
157
+ * @returns {Object} {Array of track and playlist metadata objects}
158
+ * additional metadata fields on track and playlist objects:
159
+ * {String} activity_timestamp - timestamp of requested user's repost for given track or playlist,
160
+ * used for sorting feed
161
+ * {Integer} repost_count - repost count of given track/playlist
162
+ * {Integer} save_count - save count of given track/playlist
163
+ * {Boolean} has_current_user_reposted - has current user reposted given track/playlist
164
+ * {Array} followee_reposts - followees of current user that have reposted given track/playlist
165
+ */
166
+ async getUserRepostFeed (userId, filter, limit = 100, offset = 0, withUsers = false) {
167
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
168
+ return this.discoveryProvider.getUserRepostFeed(userId, filter, limit, offset, withUsers)
169
+ }
170
+
171
+ /**
172
+ * Return social feed for current user
173
+ * @param {number} limit - max # of items to return
174
+ * @param {filter} string - filter by "all", "original", or "repost"
175
+ * @param {number} offset - offset into list to return from (for pagination)
176
+ * @returns {Object} {Array of track and playlist metadata objects}
177
+ * additional metadata fields on track and playlist objects:
178
+ * {String} activity_timestamp - timestamp of requested user's repost for given track or playlist,
179
+ * used for sorting feed
180
+ * {Integer} repost_count - repost count of given track/playlist
181
+ * {Integer} save_count - save count of given track/playlist
182
+ * {Boolean} has_current_user_reposted - has current user reposted given track/playlist
183
+ * {Array} followee_reposts - followees of current user that have reposted given track/playlist
184
+ */
185
+ async getSocialFeed (filter, limit = 100, offset = 0, withUsers = false, tracksOnly = false) {
186
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
187
+ const owner = this.userStateManager.getCurrentUser()
188
+ if (owner) {
189
+ return this.discoveryProvider.getSocialFeed(filter, limit, offset, withUsers, tracksOnly)
190
+ }
191
+
192
+ return []
193
+ }
194
+
195
+ /**
196
+ * Returns the top users for the specified genres
197
+ * @param {number} limit - max # of items to return
198
+ * @param {number} offset - offset into list to return from (for pagination)
199
+ * @param {Object} {Array of genres} - filter by genres ie. "Rock", "Alternative"
200
+ * @param {Boolean} with_users - If the userIds should be returned or the full user metadata
201
+ * @returns {Object} {Array of user objects if with_users set, else array of userIds}
202
+ */
203
+ async getTopCreatorsByGenres (genres, limit = 30, offset = 0, withUsers = false) {
204
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
205
+ return this.discoveryProvider.getTopCreatorsByGenres(genres, limit, offset, withUsers)
206
+ }
207
+
208
+ /* ------- SETTERS ------- */
209
+
210
+ /**
211
+ * Assigns a replica set to the user's metadata and adds new metadata to chain.
212
+ * This creates a record for that user on the connected creator node.
213
+ * @param {Object} param
214
+ * @param {number} param.userId
215
+ */
216
+ async assignReplicaSet ({
217
+ userId
218
+ }) {
219
+ this.REQUIRES(Services.CREATOR_NODE)
220
+ const phases = {
221
+ CLEAN_AND_VALIDATE_METADATA: 'CLEAN_AND_VALIDATE_METADATA',
222
+ AUTOSELECT_CONTENT_NODES: 'AUTOSELECT_CONTENT_NODES',
223
+ SYNC_ACROSS_CONTENT_NODES: 'SYNC_ACROSS_CONTENT_NODES',
224
+ SET_PRIMARY: 'SET_PRIMARY',
225
+ UPLOAD_METADATA_AND_UPDATE_ON_CHAIN: 'UPLOAD_METADATA_AND_UPDATE_ON_CHAIN'
226
+ }
227
+ let phase = ''
228
+
229
+ const logPrefix = `[User:assignReplicaSet()] [userId: ${userId}]`
230
+ const fnStartMs = Date.now()
231
+ let startMs = fnStartMs
232
+
233
+ const user = this.userStateManager.getCurrentUser()
234
+ // Failed the addUser() step
235
+ if (!user) { throw new Error('No current user') }
236
+ // No-op if the user already has a replica set assigned under creator_node_endpoint
237
+ if (user.creator_node_endpoint && user.creator_node_endpoint.length > 0) return
238
+
239
+ // The new metadata object that will contain the replica set
240
+ const newMetadata = { ...user }
241
+ try {
242
+ // Create starter metadata and validate
243
+ phase = phases.CLEAN_AND_VALIDATE_METADATA
244
+
245
+ // Autoselect a new replica set and update the metadata object with new content node endpoints
246
+ phase = phases.AUTOSELECT_CONTENT_NODES
247
+ const response = await this.ServiceProvider.autoSelectCreatorNodes({
248
+ performSyncCheck: false,
249
+ preferHigherPatchForPrimary: this.preferHigherPatchForPrimary,
250
+ preferHigherPatchForSecondaries: this.preferHigherPatchForSecondaries
251
+ })
252
+ console.log(`${logPrefix} [phase: ${phase}] ServiceProvider.autoSelectCreatorNodes() completed in ${Date.now() - startMs}ms`)
253
+ startMs = Date.now()
254
+
255
+ // Ideally, 1 primary and n-1 secondaries are chosen. The best-worst case scenario is that at least 1 primary
256
+ // is chosen. If a primary was not selected (which also implies that secondaries were not chosen), throw
257
+ // an error.
258
+ const { primary, secondaries } = response
259
+ if (!primary) {
260
+ throw new Error('Could not select a primary.')
261
+ }
262
+
263
+ const newContentNodeEndpoints = CreatorNode.buildEndpoint(primary, secondaries)
264
+ newMetadata.creator_node_endpoint = newContentNodeEndpoints
265
+
266
+ // Update the new primary to the auto-selected primary
267
+ phase = phases.SET_PRIMARY
268
+ await this.creatorNode.setEndpoint(primary)
269
+
270
+ // Update metadata in CN and on chain of newly assigned replica set
271
+ phase = phases.UPLOAD_METADATA_AND_UPDATE_ON_CHAIN
272
+ await this.updateAndUploadMetadata({
273
+ newMetadata,
274
+ userId
275
+ })
276
+ console.log(`${logPrefix} [phase: ${phase}] updateAndUploadMetadata() completed in ${Date.now() - startMs}ms`)
277
+
278
+ console.log(`${logPrefix} completed in ${Date.now() - fnStartMs}ms`)
279
+ } catch (e) {
280
+ const errorMsg = `assignReplicaSet() Error -- Phase ${phase} in ${Date.now() - fnStartMs}ms: ${e}`
281
+ console.log(errorMsg)
282
+ throw new Error(errorMsg)
283
+ }
284
+
285
+ return newMetadata
286
+ }
287
+
288
+ /**
289
+ * Util to upload profile picture and cover photo images and update
290
+ * a metadata object. This method inherently calls triggerSecondarySyncs().
291
+ * @param {?File} profilePictureFile an optional file to upload as the profile picture
292
+ * @param {?File} coverPhotoFile an optional file to upload as the cover photo
293
+ * @param {Object} metadata to update
294
+ * @returns {Object} the passed in metadata object with profile_picture_sizes and cover_photo_sizes fields added
295
+ */
296
+ async uploadProfileImages (profilePictureFile, coverPhotoFile, metadata) {
297
+ let didMetadataUpdate = false
298
+ if (profilePictureFile) {
299
+ const resp = await this.creatorNode.uploadImage(profilePictureFile, true)
300
+ metadata.profile_picture_sizes = resp.dirCID
301
+ didMetadataUpdate = true
302
+ }
303
+ if (coverPhotoFile) {
304
+ const resp = await this.creatorNode.uploadImage(coverPhotoFile, false)
305
+ metadata.cover_photo_sizes = resp.dirCID
306
+ didMetadataUpdate = true
307
+ }
308
+
309
+ if (didMetadataUpdate) {
310
+ await this.updateAndUploadMetadata({
311
+ newMetadata: metadata,
312
+ userId: metadata.user_id
313
+ })
314
+ }
315
+
316
+ return metadata
317
+ }
318
+
319
+ /**
320
+ * Create an on-chain non-creator user. Some fields are restricted (ex.
321
+ * creator_node_endpoint); this should error if the metadata given attempts to set them.
322
+ * @param {Object} metadata metadata to associate with the user
323
+ */
324
+ async addUser (metadata) {
325
+ this.IS_OBJECT(metadata)
326
+ const newMetadata = this.cleanUserMetadata(metadata)
327
+ this._validateUserMetadata(newMetadata)
328
+
329
+ let userId
330
+ const currentUser = this.userStateManager.getCurrentUser()
331
+ if (currentUser && currentUser.handle) {
332
+ userId = currentUser.user_id
333
+ } else {
334
+ userId = (await this.contracts.UserFactoryClient.addUser(newMetadata.handle)).userId
335
+ }
336
+ const { latestBlockHash: blockHash, latestBlockNumber: blockNumber } = await this._addUserOperations(
337
+ userId, newMetadata
338
+ )
339
+
340
+ newMetadata.wallet = this.web3Manager.getWalletAddress()
341
+ newMetadata.user_id = userId
342
+
343
+ this.userStateManager.setCurrentUser({
344
+ ...newMetadata,
345
+ // Initialize counts to be 0. We don't want to write this data to backends ever really
346
+ // (hence the cleanUserMetadata above), but we do want to make sure clients
347
+ // can properly "do math" on these numbers.
348
+ followee_count: 0,
349
+ follower_count: 0,
350
+ repost_count: 0
351
+ })
352
+ return { blockHash, blockNumber, userId }
353
+ }
354
+
355
+ /**
356
+ * Updates a user
357
+ * @param {number} userId
358
+ * @param {Object} metadata
359
+ */
360
+ async updateUser (userId, metadata) {
361
+ this.REQUIRES(Services.DISCOVERY_PROVIDER)
362
+ this.IS_OBJECT(metadata)
363
+ const newMetadata = this.cleanUserMetadata(metadata)
364
+ this._validateUserMetadata(newMetadata)
365
+
366
+ // Retrieve the current user metadata
367
+ const users = await this.discoveryProvider.getUsers(1, 0, [userId], null, null, false, null)
368
+ if (!users || !users[0]) throw new Error(`Cannot update user because no current record exists for user id ${userId}`)
369
+
370
+ const oldMetadata = users[0]
371
+ const { latestBlockHash: blockHash, latestBlockNumber: blockNumber } = await this._updateUserOperations(
372
+ newMetadata, oldMetadata, userId
373
+ )
374
+ this.userStateManager.setCurrentUser({ ...oldMetadata, ...newMetadata })
375
+ return { blockHash, blockNumber }
376
+ }
377
+
378
+ /**
379
+ * Updates a creator (updates their data on the creator node)
380
+ * @param {number} userId
381
+ * @param {Object} metadata
382
+ */
383
+ async updateCreator (userId, metadata) {
384
+ this.REQUIRES(Services.CREATOR_NODE, Services.DISCOVERY_PROVIDER)
385
+ this.IS_OBJECT(metadata)
386
+ const newMetadata = this.cleanUserMetadata(metadata)
387
+ this._validateUserMetadata(newMetadata)
388
+
389
+ const logPrefix = `[User:updateCreator()] [userId: ${userId}]`
390
+ const fnStartMs = Date.now()
391
+ let startMs = fnStartMs
392
+
393
+ // Error if libs instance does not already have existing user state
394
+ const user = this.userStateManager.getCurrentUser()
395
+ if (!user) {
396
+ throw new Error('No current user')
397
+ }
398
+
399
+ // Ensure libs is connected to correct CN
400
+ if (this.creatorNode.getEndpoint() !== CreatorNode.getPrimary(newMetadata.creator_node_endpoint)) {
401
+ throw new Error(`Not connected to correct content node. Expected ${CreatorNode.getPrimary(newMetadata.creator_node_endpoint)}, got ${this.creatorNode.getEndpoint()}`)
402
+ }
403
+
404
+ // Preserve old metadata object
405
+ const oldMetadata = { ...user }
406
+
407
+ // Update user creator_node_endpoint on chain if applicable
408
+ let updateEndpointTxBlockNumber = null
409
+ if (newMetadata.creator_node_endpoint !== oldMetadata.creator_node_endpoint) {
410
+ // Perform update to new contract
411
+ startMs = Date.now()
412
+ const {
413
+ txReceipt: updateEndpointTxReceipt, replicaSetSPIDs
414
+ } = await this._updateReplicaSetOnChain(userId, newMetadata.creator_node_endpoint)
415
+ updateEndpointTxBlockNumber = updateEndpointTxReceipt.blockNumber
416
+ console.log(`${logPrefix} _updateReplicaSetOnChain() completed in ${Date.now() - startMs}ms`)
417
+ startMs = Date.now()
418
+
419
+ await this._waitForURSMCreatorNodeEndpointIndexing(userId, replicaSetSPIDs)
420
+ console.log(`${logPrefix} _waitForURSMCreatorNodeEndpointIndexing() completed in ${Date.now() - startMs}ms`)
421
+ }
422
+
423
+ // Upload new metadata object to CN
424
+ const { metadataMultihash, metadataFileUUID } = await this.creatorNode.uploadCreatorContent(newMetadata, updateEndpointTxBlockNumber)
425
+
426
+ // Write metadata multihash to chain
427
+ const updatedMultihashDecoded = Utils.decodeMultihash(metadataMultihash)
428
+ const { txReceipt } = await this.contracts.UserFactoryClient.updateMultihash(userId, updatedMultihashDecoded.digest)
429
+
430
+ // Write remaining metadata fields to chain
431
+ let { latestBlockHash, latestBlockNumber } = await this._updateUserOperations(
432
+ newMetadata, oldMetadata, userId
433
+ )
434
+
435
+ // Write to CN to associate blockchain user id with updated metadata and block number
436
+ await this.creatorNode.associateCreator(userId, metadataFileUUID, Math.max(txReceipt.blockNumber, latestBlockNumber))
437
+
438
+ // Update libs instance with new user metadata object
439
+ this.userStateManager.setCurrentUser({ ...oldMetadata, ...newMetadata })
440
+
441
+ if (!latestBlockHash || !latestBlockNumber) {
442
+ latestBlockHash = txReceipt.blockHash
443
+ latestBlockNumber = txReceipt.blockNumber
444
+ }
445
+
446
+ return { blockHash: latestBlockHash, blockNumber: latestBlockNumber, userId }
447
+ }
448
+
449
+ /**
450
+ * Upgrades a user to a creator using their metadata object.
451
+ * This creates a record for that user on the connected creator node.
452
+ * @param {string} existingEndpoint
453
+ * @param {string} newCreatorNodeEndpoint comma delineated
454
+ */
455
+ async upgradeToCreator (existingEndpoint, newCreatorNodeEndpoint) {
456
+ this.REQUIRES(Services.CREATOR_NODE)
457
+
458
+ // Error if libs instance does not already have existing user state
459
+ const user = this.userStateManager.getCurrentUser()
460
+ if (!user) {
461
+ throw new Error('No current user')
462
+ }
463
+
464
+ // No-op if the user is already a creator.
465
+ // Consider them a creator iff they have is_creator=true AND a creator node endpoint
466
+ if (user.is_creator && user.creator_node_endpoint) return
467
+
468
+ const userId = user.user_id
469
+ const oldMetadata = { ...user }
470
+
471
+ const logPrefix = `[User:upgradeToCreator()] [userId: ${userId}]`
472
+ const fnStartMs = Date.now()
473
+ let startMs = fnStartMs
474
+
475
+ // Clean and validate metadata
476
+ const newMetadata = this.cleanUserMetadata({ ...user })
477
+ this._validateUserMetadata(newMetadata)
478
+
479
+ // Populate metadata with required fields - wallet, is_creator, creator_node_endpoint
480
+ newMetadata.wallet = this.web3Manager.getWalletAddress()
481
+ newMetadata.is_creator = true
482
+
483
+ let updateEndpointTxBlockNumber = null
484
+
485
+ /**
486
+ * If there is no creator_node_endpoint field or if newCreatorNodeEndpoint is not the same as the existing
487
+ * metadata creator_node_endpoint field value, update the field with newCreatorNodeEndpoint.
488
+ * This is because new users on signup will now be assigned a replica set, and do not need to
489
+ * be assigned a new one via newCreatorNodeEndpoint.
490
+ */
491
+ if (
492
+ !oldMetadata.creator_node_endpoint ||
493
+ oldMetadata.creator_node_endpoint !== newCreatorNodeEndpoint
494
+ ) {
495
+ newMetadata.creator_node_endpoint = newCreatorNodeEndpoint
496
+ const newPrimary = CreatorNode.getPrimary(newCreatorNodeEndpoint)
497
+
498
+ // Sync user data from old primary to new endpoint
499
+ if (existingEndpoint) {
500
+ // Don't validate what we're syncing from because the user isn't
501
+ // a creator yet.
502
+ await this.creatorNode.syncSecondary(
503
+ newPrimary,
504
+ existingEndpoint,
505
+ /* immediate= */ true,
506
+ /* validate= */ false
507
+ )
508
+ }
509
+
510
+ // Update local libs state with new CN endpoint
511
+ await this.creatorNode.setEndpoint(newPrimary)
512
+
513
+ // Update user creator_node_endpoint on chain if applicable
514
+ startMs = Date.now()
515
+ const {
516
+ txReceipt: updateEndpointTxReceipt, replicaSetSPIDs
517
+ } = await this._updateReplicaSetOnChain(userId, newMetadata.creator_node_endpoint)
518
+ updateEndpointTxBlockNumber = updateEndpointTxReceipt.blockNumber
519
+ console.log(`${logPrefix} _updateReplicaSetOnChain() completed in ${Date.now() - startMs}ms`)
520
+ startMs = Date.now()
521
+
522
+ await this._waitForURSMCreatorNodeEndpointIndexing(userId, replicaSetSPIDs)
523
+ console.log(`${logPrefix} _waitForURSMCreatorNodeEndpointIndexing() completed in ${Date.now() - startMs}ms`)
524
+ }
525
+
526
+ // Upload new metadata object to CN
527
+ const { metadataMultihash, metadataFileUUID } = await this.creatorNode.uploadCreatorContent(newMetadata, updateEndpointTxBlockNumber)
528
+
529
+ // Write metadata multihash to chain
530
+ const updatedMultihashDecoded = Utils.decodeMultihash(metadataMultihash)
531
+ const { txReceipt } = await this.contracts.UserFactoryClient.updateMultihash(userId, updatedMultihashDecoded.digest)
532
+
533
+ // Write remaining metadata fields to chain
534
+ const { latestBlockNumber } = await this._updateUserOperations(newMetadata, oldMetadata, userId)
535
+
536
+ // Write to CN to associate blockchain user id with updated metadata and block number
537
+ await this.creatorNode.associateCreator(userId, metadataFileUUID, Math.max(txReceipt.blockNumber, latestBlockNumber))
538
+
539
+ // Update libs instance with new user metadata object
540
+ this.userStateManager.setCurrentUser({ ...oldMetadata, ...newMetadata })
541
+
542
+ return userId
543
+ }
544
+
545
+ /**
546
+ * Updates a user on whether they are verified on Audius
547
+ * @param {number} userId
548
+ * @param {boolean} isVerified
549
+ */
550
+ async updateIsVerified (userId, isVerified, privateKey) {
551
+ return this.contracts.UserFactoryClient.updateIsVerified(userId, isVerified, privateKey)
552
+ }
553
+
554
+ /**
555
+ * Adds a user follow for a given follower and followee
556
+ * @param {number} followerUserId who is following
557
+ * @param {number} followeeUserId who is being followed...
558
+ */
559
+ async addUserFollow (followeeUserId) {
560
+ const followerUserId = this.userStateManager.getCurrentUserId()
561
+ return this.contracts.SocialFeatureFactoryClient.addUserFollow(followerUserId, followeeUserId)
562
+ }
563
+
564
+ /**
565
+ * Deletes a user follow for a given follower and followee
566
+ * @param {number} followerUserId who is no longer following
567
+ * @param {number} followeeUserId who is no longer being followed...
568
+ */
569
+ async deleteUserFollow (followeeUserId) {
570
+ const followerUserId = this.userStateManager.getCurrentUserId()
571
+ return this.contracts.SocialFeatureFactoryClient.deleteUserFollow(followerUserId, followeeUserId)
572
+ }
573
+
574
+ /**
575
+ * Gets the clock status for user in userStateManager across replica set.
576
+ */
577
+ async getClockValuesFromReplicaSet () {
578
+ return this.creatorNode.getClockValuesFromReplicaSet()
579
+ }
580
+
581
+ /* ------- PRIVATE ------- */
582
+
583
+ /**
584
+ * 1. Uploads metadata to primary Content Node (which inherently calls a sync accross secondaries)
585
+ * 2. Updates metadata on chain
586
+ * @param {Object} param
587
+ * @param {Object} param.newMetadata new metadata object
588
+ * @param {number} param.userId
589
+ */
590
+ async updateAndUploadMetadata ({ newMetadata, userId }) {
591
+ this.REQUIRES(Services.CREATOR_NODE, Services.DISCOVERY_PROVIDER)
592
+ this.IS_OBJECT(newMetadata)
593
+ const phases = {
594
+ UPDATE_CONTENT_NODE_ENDPOINT_ON_CHAIN: 'UPDATE_CONTENT_NODE_ENDPOINT_ON_CHAIN',
595
+ UPLOAD_METADATA: 'UPLOAD_METADATA',
596
+ UPDATE_METADATA_ON_CHAIN: 'UPDATE_METADATA_ON_CHAIN',
597
+ UPDATE_USER_ON_CHAIN_OPS: 'UPDATE_USER_ON_CHAIN_OPS',
598
+ ASSOCIATE_USER: 'ASSOCIATE_USER'
599
+ }
600
+ let phase = ''
601
+
602
+ const oldMetadata = this.userStateManager.getCurrentUser()
603
+ if (!oldMetadata) { throw new Error('No current user.') }
604
+
605
+ newMetadata = this.cleanUserMetadata(newMetadata)
606
+ this._validateUserMetadata(newMetadata)
607
+
608
+ const logPrefix = `[User:updateAndUploadMetadata()] [userId: ${userId}]`
609
+ const fnStartMs = Date.now()
610
+ let startMs = fnStartMs
611
+
612
+ try {
613
+ // Update user creator_node_endpoint on chain if applicable
614
+ if (newMetadata.creator_node_endpoint !== oldMetadata.creator_node_endpoint) {
615
+ phase = phases.UPDATE_CONTENT_NODE_ENDPOINT_ON_CHAIN
616
+ const { replicaSetSPIDs } = await this._updateReplicaSetOnChain(userId, newMetadata.creator_node_endpoint)
617
+ console.log(`${logPrefix} [phase: ${phase}] _updateReplicaSetOnChain() completed in ${Date.now() - startMs}ms`)
618
+ startMs = Date.now()
619
+
620
+ await this._waitForURSMCreatorNodeEndpointIndexing(userId, replicaSetSPIDs)
621
+ console.log(`${logPrefix} [phase: ${phase}] _waitForURSMCreatorNodeEndpointIndexing() completed in ${Date.now() - startMs}ms`)
622
+ }
623
+
624
+ // Upload new metadata object to CN
625
+ phase = phases.UPLOAD_METADATA
626
+ const { metadataMultihash, metadataFileUUID } = await this.creatorNode.uploadCreatorContent(newMetadata)
627
+ console.log(`${logPrefix} [phase: ${phase}] creatorNode.uploadCreatorContent() completed in ${Date.now() - startMs}ms`)
628
+ startMs = Date.now()
629
+
630
+ // Write metadata multihash to chain
631
+ phase = phases.UPDATE_METADATA_ON_CHAIN
632
+ const updatedMultihashDecoded = Utils.decodeMultihash(metadataMultihash)
633
+ const { txReceipt } = await this.contracts.UserFactoryClient.updateMultihash(userId, updatedMultihashDecoded.digest)
634
+ console.log(`${logPrefix} [phase: ${phase}] UserFactoryClient.updateMultihash() completed in ${Date.now() - startMs}ms`)
635
+ startMs = Date.now()
636
+
637
+ // Write remaining metadata fields to chain
638
+ phase = phases.UPDATE_USER_ON_CHAIN_OPS
639
+ const { latestBlockNumber } = await this._updateUserOperations(newMetadata, oldMetadata, userId, ['creator_node_endpoint'])
640
+ console.log(`${logPrefix} [phase: ${phase}] _updateUserOperations() completed in ${Date.now() - startMs}ms`)
641
+ startMs = Date.now()
642
+
643
+ // Write to CN to associate blockchain user id with updated metadata and block number
644
+ phase = phases.ASSOCIATE_USER
645
+ await this.creatorNode.associateCreator(userId, metadataFileUUID, Math.max(txReceipt.blockNumber, latestBlockNumber))
646
+ console.log(`${logPrefix} [phase: ${phase}] creatorNode.associateCreator() completed in ${Date.now() - startMs}ms`)
647
+ startMs = Date.now()
648
+
649
+ // Update libs instance with new user metadata object
650
+ this.userStateManager.setCurrentUser({ ...oldMetadata, ...newMetadata })
651
+
652
+ console.log(`${logPrefix} completed in ${Date.now() - fnStartMs}ms`)
653
+ } catch (e) {
654
+ // TODO: think about handling the update metadata on chain and associating..
655
+ const errorMsg = `updateAndUploadMetadata() Error -- Phase ${phase} in ${Date.now() - fnStartMs}ms: ${e}`
656
+ console.log(errorMsg)
657
+ throw new Error(errorMsg)
658
+ }
659
+ }
660
+
661
+ /**
662
+ * If a user's creator_node_endpoint is null, assign a replica set.
663
+ * Used during the sanity check and in uploadImage() in files.js
664
+ */
665
+ async assignReplicaSetIfNecessary () {
666
+ const user = this.userStateManager.getCurrentUser()
667
+
668
+ // If no user is logged in, or a creator node endpoint is already assigned,
669
+ // skip this call
670
+ if (!user || user.creator_node_endpoint) return
671
+
672
+ // Generate a replica set and assign to user
673
+ try {
674
+ await this.assignReplicaSet({ userId: user.user_id })
675
+ } catch (e) {
676
+ throw new Error(`assignReplicaSetIfNecessary error - ${e.toString()}`)
677
+ }
678
+ }
679
+
680
+ /** Waits for a discovery provider to confirm that a creator node endpoint is updated. */
681
+ async _waitForCreatorNodeEndpointIndexing (userId, creatorNodeEndpoint) {
682
+ while (true) {
683
+ const userList = await this.discoveryProvider.getUsers(1, 0, [userId])
684
+ if (userList) {
685
+ const user = userList[0]
686
+ if (user && user.creator_node_endpoint === creatorNodeEndpoint) {
687
+ break
688
+ }
689
+ }
690
+
691
+ await Utils.wait(500)
692
+ }
693
+ }
694
+
695
+ async _waitForURSMCreatorNodeEndpointIndexing (userId, replicaSetSPIDs, timeoutMs = 60000) {
696
+ const asyncFn = async () => {
697
+ while (true) {
698
+ const replicaSet = await this.contracts.UserReplicaSetManagerClient.getUserReplicaSet(userId)
699
+ if (
700
+ replicaSet &&
701
+ Object.prototype.hasOwnProperty.call(replicaSet, 'primaryId') &&
702
+ Object.prototype.hasOwnProperty.call(replicaSet, 'secondaryIds') &&
703
+ replicaSet.primaryId === replicaSetSPIDs[0] &&
704
+ isEqual(replicaSet.secondaryIds, replicaSetSPIDs.slice(1, 3))
705
+ ) {
706
+ break
707
+ }
708
+ }
709
+ await Utils.wait(500)
710
+ }
711
+ await Utils.racePromiseWithTimeout(
712
+ asyncFn(),
713
+ timeoutMs,
714
+ `[User:_waitForURSMCreatorNodeEndpointIndexing()] Timeout error after ${timeoutMs}ms`
715
+ )
716
+ }
717
+
718
+ async _addUserOperations (userId, newMetadata, exclude = []) {
719
+ const addOps = []
720
+
721
+ // Remove excluded keys from metadata object
722
+ const metadata = { ...newMetadata }
723
+ exclude.map(excludedKey => delete metadata[excludedKey])
724
+
725
+ if (metadata[USER_PROP_NAME_CONSTANTS.NAME]) {
726
+ addOps.push(this.contracts.UserFactoryClient.updateName(userId, metadata[USER_PROP_NAME_CONSTANTS.NAME]))
727
+ }
728
+ if (metadata[USER_PROP_NAME_CONSTANTS.LOCATION]) {
729
+ addOps.push(this.contracts.UserFactoryClient.updateLocation(userId, metadata[USER_PROP_NAME_CONSTANTS.LOCATION]))
730
+ }
731
+ if (metadata[USER_PROP_NAME_CONSTANTS.BIO]) {
732
+ addOps.push(this.contracts.UserFactoryClient.updateBio(userId, metadata[USER_PROP_NAME_CONSTANTS.BIO]))
733
+ }
734
+ if (metadata[USER_PROP_NAME_CONSTANTS.PROFILE_PICTURE_SIZES]) {
735
+ addOps.push(this.contracts.UserFactoryClient.updateProfilePhoto(
736
+ userId,
737
+ Utils.decodeMultihash(metadata[USER_PROP_NAME_CONSTANTS.PROFILE_PICTURE_SIZES]).digest
738
+ ))
739
+ }
740
+ if (metadata[USER_PROP_NAME_CONSTANTS.COVER_PHOTO_SIZES]) {
741
+ addOps.push(this.contracts.UserFactoryClient.updateCoverPhoto(
742
+ userId,
743
+ Utils.decodeMultihash(metadata[USER_PROP_NAME_CONSTANTS.COVER_PHOTO_SIZES]).digest
744
+ ))
745
+ }
746
+ if (metadata[USER_PROP_NAME_CONSTANTS.IS_CREATOR]) {
747
+ addOps.push(this.contracts.UserFactoryClient.updateIsCreator(userId, metadata[USER_PROP_NAME_CONSTANTS.IS_CREATOR]))
748
+ }
749
+
750
+ let ops; let latestBlockNumber = -Infinity; let latestBlockHash
751
+ if (addOps.length > 0) {
752
+ // Execute update promises concurrently
753
+ // TODO - what if one or more of these fails?
754
+ // sort transactions by blocknumber and return most recent transaction
755
+ ops = await Promise.all(addOps)
756
+ const sortedOpsDesc = ops.sort((op1, op2) => op2.txReceipt.blockNumber - op1.txReceipt.blockNumber)
757
+ const latestTx = sortedOpsDesc[0].txReceipt
758
+ latestBlockNumber = latestTx.blockNumber
759
+ latestBlockHash = latestTx.blockHash
760
+ }
761
+
762
+ return { ops, latestBlockNumber, latestBlockHash }
763
+ }
764
+
765
+ async _updateUserOperations (newMetadata, currentMetadata, userId, exclude = []) {
766
+ const updateOps = []
767
+
768
+ // Remove excluded keys from metadata object
769
+ const metadata = { ...newMetadata }
770
+ exclude.map(excludedKey => delete metadata[excludedKey])
771
+ // Compare the existing metadata with the new values and conditionally
772
+ // perform update operations
773
+ for (const key in metadata) {
774
+ if (Object.prototype.hasOwnProperty.call(metadata, key) && Object.prototype.hasOwnProperty.call(currentMetadata, key) && metadata[key] !== currentMetadata[key]) {
775
+ if (key === USER_PROP_NAME_CONSTANTS.NAME) {
776
+ updateOps.push(this.contracts.UserFactoryClient.updateName(userId, metadata[USER_PROP_NAME_CONSTANTS.NAME]))
777
+ }
778
+ if (key === USER_PROP_NAME_CONSTANTS.IS_CREATOR) {
779
+ updateOps.push(this.contracts.UserFactoryClient.updateIsCreator(userId, metadata[USER_PROP_NAME_CONSTANTS.IS_CREATOR]))
780
+ }
781
+ if (key === USER_PROP_NAME_CONSTANTS.BIO) {
782
+ updateOps.push(this.contracts.UserFactoryClient.updateBio(userId, metadata[USER_PROP_NAME_CONSTANTS.BIO]))
783
+ }
784
+ if (key === USER_PROP_NAME_CONSTANTS.LOCATION) {
785
+ updateOps.push(this.contracts.UserFactoryClient.updateLocation(userId, metadata[USER_PROP_NAME_CONSTANTS.LOCATION]))
786
+ }
787
+ if (key === USER_PROP_NAME_CONSTANTS.PROFILE_PICTURE_SIZES) {
788
+ updateOps.push(this.contracts.UserFactoryClient.updateProfilePhoto(
789
+ userId,
790
+ Utils.decodeMultihash(metadata[USER_PROP_NAME_CONSTANTS.PROFILE_PICTURE_SIZES]).digest
791
+ ))
792
+ }
793
+ if (key === USER_PROP_NAME_CONSTANTS.COVER_PHOTO_SIZES) {
794
+ updateOps.push(this.contracts.UserFactoryClient.updateCoverPhoto(
795
+ userId,
796
+ Utils.decodeMultihash(metadata[USER_PROP_NAME_CONSTANTS.COVER_PHOTO_SIZES]).digest
797
+ ))
798
+ }
799
+ }
800
+ }
801
+
802
+ let ops; let latestBlockNumber = -Infinity; let latestBlockHash
803
+ if (updateOps.length > 0) {
804
+ // sort transactions by blocknumber and return most recent transaction
805
+ ops = await Promise.all(updateOps)
806
+ const sortedOpsDesc = ops.sort((op1, op2) => op2.txReceipt.blockNumber - op1.txReceipt.blockNumber)
807
+ const latestTx = sortedOpsDesc[0].txReceipt
808
+ latestBlockNumber = latestTx.blockNumber
809
+ latestBlockHash = latestTx.blockHash
810
+ }
811
+
812
+ return { ops, latestBlockNumber, latestBlockHash }
813
+ }
814
+
815
+ _validateUserMetadata (metadata) {
816
+ this.OBJECT_HAS_PROPS(metadata, USER_PROPS, USER_REQUIRED_PROPS)
817
+ }
818
+
819
+ /**
820
+ * Metadata object may have extra fields.
821
+ * - Add what user props might be missing to normalize
822
+ * - Only keep core fields in USER_PROPS and 'user_id'.
823
+ */
824
+ cleanUserMetadata (metadata) {
825
+ USER_PROPS.forEach(prop => {
826
+ if (!(prop in metadata)) { metadata[prop] = null }
827
+ })
828
+ return pick(metadata, USER_PROPS.concat('user_id'))
829
+ }
830
+
831
+ // Perform replica set update
832
+ // Conditionally write to UserFactory contract, else write to UserReplicaSetManager
833
+ // This behavior is to ensure backwards compatibility prior to contract deploy
834
+ async _updateReplicaSetOnChain (userId, creatorNodeEndpoint) {
835
+ // Attempt to update through UserReplicaSetManagerClient if present
836
+ if (!this.contracts.UserReplicaSetManagerClient) {
837
+ await this.contracts.initUserReplicaSetManagerClient()
838
+ }
839
+
840
+ const primaryEndpoint = CreatorNode.getPrimary(creatorNodeEndpoint)
841
+ const secondaries = CreatorNode.getSecondaries(creatorNodeEndpoint)
842
+
843
+ if (secondaries.length < 2) {
844
+ throw new Error(`Invalid number of secondaries found - received ${secondaries}`)
845
+ }
846
+
847
+ const [primarySpID, secondary1SpID, secondary2SpID] = await Promise.all([
848
+ this._retrieveSpIDFromEndpoint(primaryEndpoint),
849
+ this._retrieveSpIDFromEndpoint(secondaries[0]),
850
+ this._retrieveSpIDFromEndpoint(secondaries[1])
851
+ ])
852
+
853
+ // Update in new contract
854
+ const txReceipt = await this.contracts.UserReplicaSetManagerClient.updateReplicaSet(
855
+ userId,
856
+ primarySpID,
857
+ [secondary1SpID, secondary2SpID]
858
+ )
859
+ const replicaSetSPIDs = [primarySpID, secondary1SpID, secondary2SpID]
860
+ return {
861
+ txReceipt,
862
+ replicaSetSPIDs
863
+ }
864
+ }
865
+
866
+ // Retrieve cached value for spID from endpoint if present, otherwise fetch from eth web3
867
+ // Any error in the web3 fetch will short circuit the entire operation as expected
868
+ async _retrieveSpIDFromEndpoint (endpoint) {
869
+ const cachedSpID = getSpIDForEndpoint(endpoint)
870
+ let spID = cachedSpID
871
+ if (!spID) {
872
+ const spEndpointInfo = await this.ethContracts.ServiceProviderFactoryClient.getServiceProviderInfoFromEndpoint(
873
+ endpoint
874
+ )
875
+ // Throw if this spID is 0, indicating invalid
876
+ spID = spEndpointInfo.spID
877
+ if (spID === 0) {
878
+ throw new Error(`Failed to find spID for ${endpoint}`)
879
+ }
880
+ // Cache value if it is valid
881
+ setSpIDForEndpoint(endpoint, spID)
882
+ }
883
+ return spID
884
+ }
885
+ }
886
+
887
+ module.exports = Users
888
+ module.exports.USER_PROP_NAME_CONSTANTS = USER_PROP_NAME_CONSTANTS