diff --git a/packages/common/package-lock.json b/packages/common/package-lock.json index 2083bdc134..92c2613a6a 100644 --- a/packages/common/package-lock.json +++ b/packages/common/package-lock.json @@ -33,9 +33,9 @@ } }, "@audius/sdk": { - "version": "3.0.3-beta.63", - "resolved": "https://registry.npmjs.org/@audius/sdk/-/sdk-3.0.3-beta.63.tgz", - "integrity": "sha512-yiUjzV/xOZe9N1a+P0ApD3mpzeZcaVQ+vG55qu7Ic045MX87IrKCatHGU1tZMK71fe3P4ZpktTo6SeGs6UCKlQ==", + "version": "3.0.3-beta.76", + "resolved": "https://registry.npmjs.org/@audius/sdk/-/sdk-3.0.3-beta.76.tgz", + "integrity": "sha512-zhH7ZMcVsMVhDTRNxwWtgXOR69E7GVq09bUnDd7m/Xh/dsL4vg0eZaULTlPCNi1b3VGDZs0KFyVzR16Pyt9ufA==", "requires": { "@audius/hedgehog": "2.1.0", "@babel/runtime": "7.18.3", @@ -143,9 +143,9 @@ } }, "@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", "requires": { "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", @@ -285,9 +285,9 @@ "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" }, "@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "requires": { "@babel/types": "^7.22.5" }, @@ -394,9 +394,9 @@ } }, "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==" }, "@babel/plugin-syntax-jsx": { "version": "7.22.5", @@ -514,17 +514,17 @@ } }, "@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", "requires": { "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", + "@babel/generator": "^7.22.7", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" @@ -8344,9 +8344,9 @@ } }, "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.10.tgz", + "integrity": "sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA==", "requires": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", diff --git a/packages/common/package.json b/packages/common/package.json index 5a8b6fc1b9..e2c5721c65 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -28,7 +28,7 @@ "url": "https://github.com/AudiusProject/audius-client/issues" }, "dependencies": { - "@audius/sdk": "3.0.3-beta.63", + "@audius/sdk": "3.0.3-beta.76", "@fingerprintjs/fingerprintjs-pro": "3.5.6", "@metaplex-foundation/mpl-token-metadata": "2.5.2", "@optimizely/optimizely-sdk": "4.0.0", diff --git a/packages/common/src/api/index.ts b/packages/common/src/api/index.ts index 7df2f3ed16..9c1e600847 100644 --- a/packages/common/src/api/index.ts +++ b/packages/common/src/api/index.ts @@ -4,3 +4,4 @@ export * from './collection' export * from './user' export * from './developerApps' export * from './suggestedTracks' +export * from './trending' diff --git a/packages/common/src/api/reducer.ts b/packages/common/src/api/reducer.ts index 0bab353108..eb71b40acd 100644 --- a/packages/common/src/api/reducer.ts +++ b/packages/common/src/api/reducer.ts @@ -5,6 +5,7 @@ import { developerAppsApiReducer } from './developerApps' import { favoritesApiReducer } from './favorites' import { relatedArtistsApiReducer } from './relatedArtists' import { trackApiReducer } from './track' +import { trendingApiReducer } from './trending' import { userApiReducer } from './user' export default combineReducers({ @@ -13,5 +14,6 @@ export default combineReducers({ trackApi: trackApiReducer, userApi: userApiReducer, developerAppsApi: developerAppsApiReducer, - favoritesApi: favoritesApiReducer + favoritesApi: favoritesApiReducer, + trendingApi: trendingApiReducer }) diff --git a/packages/common/src/api/suggestedTracks.ts b/packages/common/src/api/suggestedTracks.ts index 869406bc1c..6edaacf5aa 100644 --- a/packages/common/src/api/suggestedTracks.ts +++ b/packages/common/src/api/suggestedTracks.ts @@ -1,31 +1,132 @@ -import { useMemo } from 'react' +import { useCallback, useEffect, useState } from 'react' -import { sampleSize } from 'lodash' -import { useSelector } from 'react-redux' +import { difference, shuffle } from 'lodash' +import { useSelector, useDispatch } from 'react-redux' +import { usePaginatedQuery } from 'audius-query' +import { ID } from 'models/Identifiers' +import { Status } from 'models/Status' +import { TimeRange } from 'models/TimeRange' import { getUserId } from 'store/account/selectors' +import { addTrackToPlaylist } from 'store/cache/collections/actions' +import { getCollection } from 'store/cache/collections/selectors' +import { getTrack } from 'store/cache/tracks/selectors' +import { CommonState } from 'store/index' import { useGetFavoritedTrackList } from './favorites' import { useGetTracksByIds } from './track' +import { useGetTrending } from './trending' -export const useGetSuggestedTracks = () => { +const suggestedTrackCount = 5 + +const selectSuggestedTracks = (state: CommonState, ids: ID[]) => { + return ids.map((id) => { + const track = getTrack(state, { id }) + if (!track) return { id, isLoading: true as const } + return { id, track, isLoading: false as const } + }) +} + +const selectCollectionTrackIds = (state: CommonState, collectionId: ID) => { + const collection = getCollection(state, { id: collectionId }) + if (!collection) return [] + return collection?.playlist_contents.track_ids.map((trackId) => trackId.track) +} + +export const useGetSuggestedTracks = (collectionId: ID) => { const currentUserId = useSelector(getUserId) - const { data: favoritedTracks } = useGetFavoritedTrackList( - { currentUserId }, - { disabled: !currentUserId } + const dispatch = useDispatch() + const [suggestedTrackIds, setSuggestedTrackIds] = useState([]) + + const collectionTrackIds = useSelector((state: CommonState) => + selectCollectionTrackIds(state, collectionId) ) - const favoritedTracksSample = useMemo(() => { - return sampleSize(favoritedTracks, 5) + const { data: favoritedTracks, status: favoritedStatus } = + useGetFavoritedTrackList({ currentUserId }, { disabled: !currentUserId }) + + useEffect(() => { + if (favoritedTracks) { + const suggestedTrackIds = difference( + shuffle(favoritedTracks).map((track) => track.save_item_id), + collectionTrackIds + ) + setSuggestedTrackIds(suggestedTrackIds) + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [favoritedTracks]) - return useGetTracksByIds( + const { + data: trendingTracks, + status: trendingStatus, + loadMore + } = usePaginatedQuery( + useGetTrending, + { + timeRange: TimeRange.WEEK, + currentUserId, + genre: null + }, + { + pageSize: 10, + disabled: favoritedStatus !== Status.SUCCESS + } + ) + + useEffect(() => { + if (trendingStatus === Status.SUCCESS) { + const trendingTrackIds = difference( + trendingTracks.map((track) => track.track_id), + collectionTrackIds + ) + setSuggestedTrackIds([...suggestedTrackIds, ...trendingTrackIds]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [trendingStatus]) + + useEffect(() => { + if (suggestedTrackIds.length < 5) { + loadMore() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [suggestedTrackIds.length]) + + const suggestedTracks = useSelector((state: CommonState) => + selectSuggestedTracks( + state, + suggestedTrackIds.slice(0, suggestedTrackCount) + ) + ) + + useGetTracksByIds( { currentUserId, - ids: favoritedTracksSample?.map((favorite) => favorite.save_item_id) + ids: suggestedTracks + .filter((suggestedTrack) => suggestedTrack.isLoading) + .map((suggestedTrack) => suggestedTrack.id) }, { - disabled: !currentUserId || favoritedTracksSample.length === 0 + disabled: !currentUserId || suggestedTrackIds.length === 0 } ) + + const handleAddTrack = useCallback( + (trackId: ID, collectionId: ID) => { + dispatch(addTrackToPlaylist(trackId, collectionId)) + const trackIndexToRemove = suggestedTrackIds.indexOf(trackId) + suggestedTrackIds.splice(trackIndexToRemove, 1) + setSuggestedTrackIds(suggestedTrackIds) + }, + [dispatch, suggestedTrackIds] + ) + + const handleRefresh = useCallback(() => { + setSuggestedTrackIds(suggestedTrackIds.slice(suggestedTrackCount)) + }, [suggestedTrackIds]) + + return { + suggestedTracks, + onRefresh: handleRefresh, + onAddTrack: handleAddTrack + } } diff --git a/packages/common/src/api/trending.ts b/packages/common/src/api/trending.ts new file mode 100644 index 0000000000..4756795309 --- /dev/null +++ b/packages/common/src/api/trending.ts @@ -0,0 +1,32 @@ +import { createApi } from 'audius-query' +import { ID } from 'models/Identifiers' +import { Kind } from 'models/Kind' +import { TimeRange } from 'models/TimeRange' +import { Genre } from 'utils/genres' +import { Nullable } from 'utils/typeUtils' + +type GetTrendingArgs = { + timeRange: TimeRange + genre: Nullable + offset: number + limit: number + currentUserId: Nullable +} + +const trendingApi = createApi({ + reducerPath: 'trendingApi', + endpoints: { + getTrending: { + fetch: async (args: GetTrendingArgs, { apiClient }) => { + return await apiClient.getTrending(args) + }, + options: { + kind: Kind.TRACKS, + schemaKey: 'tracks' + } + } + } +}) + +export const { useGetTrending } = trendingApi.hooks +export const trendingApiReducer = trendingApi.reducer diff --git a/packages/common/src/audius-query/hooks/usePaginatedQuery.ts b/packages/common/src/audius-query/hooks/usePaginatedQuery.ts index eff73ba235..a4ec838121 100644 --- a/packages/common/src/audius-query/hooks/usePaginatedQuery.ts +++ b/packages/common/src/audius-query/hooks/usePaginatedQuery.ts @@ -1,27 +1,42 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { isEqual } from 'lodash' import { useCustomCompareEffect } from 'react-use' import { Status } from 'models/Status' -import { QueryHookResults } from '../types' +import { QueryHookOptions, QueryHookResults } from '../types' export const usePaginatedQuery = < - Data extends [], - ArgsType extends { limit: number; offset: number }, - HookType extends (args: ArgsType) => QueryHookResults + Data, + ArgsType extends { limit: number; offset: number } >( - useQueryHook: HookType, - baseArgs: Exclude, - pageSize: number + useQueryHook: ( + args: ArgsType, + options?: QueryHookOptions + ) => QueryHookResults, + baseArgs: Omit, + options: { pageSize: number } & QueryHookOptions ) => { + const { pageSize, ...queryHookOptions } = options + const { disabled } = queryHookOptions const [page, setPage] = useState(0) - const args = { ...baseArgs, limit: pageSize, offset: page * pageSize } - const result = useQueryHook(args) + const args = { + ...baseArgs, + limit: pageSize, + offset: page * pageSize + } as ArgsType + const result = useQueryHook(args, queryHookOptions) + + const loadMore = useCallback(() => { + if (!disabled) { + setPage(page + 1) + } + }, [disabled, page]) + return { ...result, - loadMore: () => setPage(page + 1), + loadMore, hasMore: result.status === Status.IDLE || (!result.data && result.status === Status.LOADING) || @@ -31,13 +46,16 @@ export const usePaginatedQuery = < export const useAllPaginatedQuery = < Data, - ArgsType extends { limit: number; offset: number }, - HookType extends (args: ArgsType) => QueryHookResults + ArgsType extends { limit: number; offset: number } >( - useQueryHook: HookType, + useQueryHook: ( + args: ArgsType, + options?: QueryHookOptions + ) => QueryHookResults, baseArgs: Omit, - pageSize: number + options: { pageSize: number } & QueryHookOptions ) => { + const { pageSize, ...queryHookOptions } = options const [page, setPage] = useState(0) const [allData, setAllData] = useState([]) const args = { @@ -45,7 +63,7 @@ export const useAllPaginatedQuery = < limit: pageSize, offset: page * pageSize } as ArgsType - const result = useQueryHook(args) + const result = useQueryHook(args, queryHookOptions) useEffect(() => { if (result.status !== Status.SUCCESS) return setAllData((allData) => [...allData, ...result.data]) diff --git a/packages/common/src/models/Lineup.ts b/packages/common/src/models/Lineup.ts index 7479ec4278..ea12e4c987 100644 --- a/packages/common/src/models/Lineup.ts +++ b/packages/common/src/models/Lineup.ts @@ -38,7 +38,6 @@ export type LineupState = { page: number isMetadataLoading: boolean dedupe?: boolean - containsDeleted: boolean maxEntries: Nullable entryIds?: Nullable> } diff --git a/packages/common/src/services/audius-backend/AudiusBackend.ts b/packages/common/src/services/audius-backend/AudiusBackend.ts index b17a08e085..f5300c4a94 100644 --- a/packages/common/src/services/audius-backend/AudiusBackend.ts +++ b/packages/common/src/services/audius-backend/AudiusBackend.ts @@ -40,11 +40,13 @@ import { Name, PlaylistTrackId, ProfilePictureSizes, + SquareSizes, StringWei, Track, TrackMetadata, User, - UserMetadata + UserMetadata, + WidthSizes } from '../../models' import { AnalyticsEvent } from '../../models/Analytics' import { ReportToSentryArgs } from '../../models/ErrorReporting' @@ -249,7 +251,6 @@ type AudiusBackendParams = { generalAdmissionUrl: Maybe isElectron: Maybe isMobile: Maybe - legacyUserNodeUrl: Maybe localStorage?: LocalStorage monitoringCallbacks: MonitoringCallbacks nativeMobile: Maybe @@ -295,7 +296,6 @@ export const audiusBackend = ({ generalAdmissionUrl, isElectron, isMobile, - legacyUserNodeUrl, localStorage, monitoringCallbacks, nativeMobile, @@ -363,20 +363,6 @@ export const audiusBackend = ({ } } - function getCreatorNodeIPFSGateways(endpoint: Nullable) { - if (endpoint) { - return endpoint - .split(',') - .filter(Boolean) - .map((endpoint) => `${endpoint}/ipfs/`) - } - const gateways = [`${userNodeUrl}/ipfs/`] - if (legacyUserNodeUrl) { - gateways.push(`${legacyUserNodeUrl}/ipfs/`) - } - return gateways - } - async function preloadImage(url: string) { if (!preloadImageTimer) { const batchSize = @@ -459,75 +445,46 @@ export const audiusBackend = ({ } } - async function fetchImageCID( - cid: CID, - creatorNodeGateways: string[] = [], - cache = true - ) { - if (CIDCache.has(cid)) { - return CIDCache.get(cid) + async function fetchImageCID(cid: CID, size?: SquareSizes | WidthSizes) { + const cidFileName = size ? `${cid}/${size}.jpg` : `${cid}.jpg` + if (CIDCache.has(cidFileName)) { + return CIDCache.get(cidFileName) as string } - creatorNodeGateways.push(`${userNodeUrl}/ipfs`) - const primary = creatorNodeGateways[0] - const firstImageUrl = `${primary}${cid}` + const storageNodeSelector = await getStorageNodeSelector() + const storageNode = storageNodeSelector.getNodes(cid)[0] + const imageUrl = `${storageNode}/content/${cidFileName}` - if (primary) { - if (imagePreloader) { - try { - const preloaded = await imagePreloader(firstImageUrl) - if (preloaded) { - return firstImageUrl - } - } catch (e) { - // swallow error and continue - } - } else { - // Attempt to fetch/load the image using the first creator node gateway - const preloadedImageUrl = await preloadImage(firstImageUrl) - - // If the image is loaded, add to cache and return - if (preloadedImageUrl && cache) { - CIDCache.add(cid, preloadedImageUrl) - } - if (preloadedImageUrl) { - return preloadedImageUrl + if (imagePreloader) { + try { + const preloaded = await imagePreloader(imageUrl) + console.log('we doing this actually?') + if (preloaded) { + return imageUrl } + } catch (e) { + // swallow error and continue } - } - - await waitForLibsInit() - // Else, race fetching of the image from all gateways & return the image url blob - try { - const image = await audiusLibs.File.fetchCID( - cid, - creatorNodeGateways, - () => {} - ) - - const url = nativeMobile - ? image.config.url - : URL.createObjectURL(image.data) - - if (cache) CIDCache.add(cid, url) + } else { + // Attempt to fetch/load the image using the first creator node gateway + const preloadedImageUrl = await preloadImage(imageUrl) - return url - } catch (e) { - console.error(e) - return '' + // If the image is loaded, add to cache and return + if (preloadedImageUrl) { + CIDCache.add(cidFileName, preloadedImageUrl) + return preloadedImageUrl + } } + return '' } async function getImageUrl( cid: Nullable, - size: Nullable, - gateways: string[] + size?: SquareSizes | WidthSizes ) { if (!cid) return '' try { - return size - ? fetchImageCID(`${cid}/${size}.jpg`, gateways) - : fetchImageCID(cid, gateways) + return await fetchImageCID(cid, size) } catch (e) { console.error(e) return '' @@ -642,14 +599,8 @@ export const audiusBackend = ({ async function sanityChecks(audiusLibs: any) { try { - const sanityCheckOptions = { - skipRollover: getRemoteVar(BooleanKeys.SKIP_ROLLOVER_NODES_SANITY_CHECK) - } - const sanityChecks = new SanityChecks(audiusLibs, sanityCheckOptions) - await sanityChecks.run( - null, - getBlockList(StringKeys.CONTENT_NODE_BLOCK_LIST) - ) + const sanityChecks = new SanityChecks(audiusLibs) + await sanityChecks.run() } catch (e) { console.error(`Sanity checks failed: ${e}`) } @@ -697,6 +648,14 @@ export const audiusBackend = ({ if (useSdkDiscoveryNodeSelector) { discoveryNodeSelector = await discoveryNodeSelectorService.getInstance() + const initialSelectedNode: string | undefined = + // TODO: Need a synchronous method to check if a discovery node is already selected? + // Alternatively, remove all this AudiusBackend/Libs init/APIClient init stuff in favor of SDK + // @ts-ignore config is private + discoveryNodeSelector.config.initialSelectedNode + if (initialSelectedNode) { + discoveryProviderSelectionCallback(initialSelectedNode, []) + } discoveryNodeSelector.addEventListener('change', (endpoint) => { console.debug('[AudiusBackend] DiscoveryNodeSelector changed', endpoint) discoveryProviderSelectionCallback(endpoint, []) @@ -705,13 +664,9 @@ export const audiusBackend = ({ const baseCreatorNodeConfig = AudiusLibs.configCreatorNode( userNodeUrl, - /* lazyConnect */ true, /* passList */ null, contentNodeBlockList, - monitoringCallbacks.contentNode, - /* writeQuorumEnabled */ await getFeatureEnabled( - FeatureFlags.WRITE_QUORUM_ENABLED - ) + monitoringCallbacks.contentNode ) try { @@ -1692,31 +1647,7 @@ export const audiusBackend = ({ setLocalStorageItem('is-mobile-user', 'true') } - const storageV2SignupEnabled = await getFeatureEnabled( - FeatureFlags.STORAGE_V2_SIGNUP - ) - if (storageV2SignupEnabled) { - return await audiusLibs.Account.signUpV2( - email, - password, - metadata, - formFields.profilePicture, - formFields.coverPhoto, - hasWallet, - getHostUrl(), - (eventName: string, properties: Record) => - recordAnalytics({ eventName, properties }), - { - Request: Name.CREATE_USER_BANK_REQUEST, - Success: Name.CREATE_USER_BANK_SUCCESS, - Failure: Name.CREATE_USER_BANK_FAILURE - }, - feePayerOverride, - true - ) - } - // Returns { userId, error, phase } - return await audiusLibs.Account.signUp( + return await audiusLibs.Account.signUpV2( email, password, metadata, @@ -1761,17 +1692,6 @@ export const audiusBackend = ({ return audiusLibs.Account.generateRecoveryLink({ host }) } - async function associateAudiusUserForAuth(email: string, handle: string) { - await waitForLibsInit() - try { - await audiusLibs.Account.associateAudiusUserForAuth(email, handle) - return { success: true } - } catch (error) { - console.error(getErrorMessage(error)) - return { success: false, error } - } - } - async function emailInUse(email: string) { await waitForLibsInit() try { @@ -3282,7 +3202,6 @@ export const audiusBackend = ({ addDiscoveryProviderSelectionListener, addPlaylistTrack, audiusLibs: audiusLibs as AudiusLibsType, - associateAudiusUserForAuth, associateInstagramAccount, associateTwitterAccount, associateTikTokAccount, @@ -3321,7 +3240,6 @@ export const audiusBackend = ({ getBrowserPushSubscription, getClaimDistributionAmount, getCollectionImages, - getCreatorNodeIPFSGateways, getCreators, getSocialHandles, getEmailNotificationSettings, @@ -3346,7 +3264,6 @@ export const audiusBackend = ({ getWeb3, handleInUse, identityServiceUrl, - legacyUserNodeUrl, listCreatorNodes, markAllNotificationAsViewed, orderPlaylist, diff --git a/packages/common/src/services/local-storage/LocalStorage.ts b/packages/common/src/services/local-storage/LocalStorage.ts index 88b8853b57..a59dae02fd 100644 --- a/packages/common/src/services/local-storage/LocalStorage.ts +++ b/packages/common/src/services/local-storage/LocalStorage.ts @@ -7,8 +7,6 @@ import { Nullable } from '../../utils' // TODO: the following should come from @audius/libs/dist/core when // discoveryProvider/constants is migrated to typescript. -const DISCOVERY_PROVIDER_TIMESTAMP = '@audius/libs:discovery-node-timestamp' - const AUDIUS_ACCOUNT_KEY = '@audius/account' const AUDIUS_ACCOUNT_USER_KEY = '@audius/audius-user' @@ -116,10 +114,4 @@ export class LocalStorage { getCurrentUserExists = async () => this.getValue(CURRENT_USER_EXISTS_LOCAL_STORAGE_KEY) - - async getCachedDiscoveryProvider() { - return await this.getJSONValue( - DISCOVERY_PROVIDER_TIMESTAMP - ) - } } diff --git a/packages/common/src/services/remote-config/feature-flags.ts b/packages/common/src/services/remote-config/feature-flags.ts index ca4da32c2c..4d9fe18284 100644 --- a/packages/common/src/services/remote-config/feature-flags.ts +++ b/packages/common/src/services/remote-config/feature-flags.ts @@ -11,7 +11,6 @@ export enum FeatureFlags { ENABLE_SPL_AUDIO = 'enable_spl_audio', DISABLE_SIGN_UP_CONFIRMATION = 'disable_sign_up_confirmation', TIPPING_ENABLED = 'tipping_enabled', - WRITE_QUORUM_ENABLED = 'write_quorum_enabled', EARLY_ACCESS = 'early_access', SUPPORTER_DETHRONED_ENABLED = 'supporter_dethroned_enabled', NEW_ARTIST_DASHBOARD_TABLE = 'new_artist_dashboard_table', @@ -41,7 +40,6 @@ export enum FeatureFlags { SDK_DISCOVERY_NODE_SELECTOR = 'sdk_discovery_node_selector_2', RELATED_ARTISTS_ON_PROFILE_ENABLED = 'related_artists_on_profile_enabled', PROXY_WORMHOLE = 'proxy_wormhole', - STORAGE_V2_SIGNUP = 'storage_v2_signup', PLAYLIST_UPDATES_PRE_QA = 'playlist_updates_pre_qa', PLAYLIST_UPDATES_POST_QA = 'playlist_updates_post_qa', AI_ATTRIBUTION = 'ai_attribution', @@ -74,7 +72,6 @@ export const flagDefaults: FlagDefaults = { [FeatureFlags.ENABLE_SPL_AUDIO]: false, [FeatureFlags.DISABLE_SIGN_UP_CONFIRMATION]: false, [FeatureFlags.TIPPING_ENABLED]: false, - [FeatureFlags.WRITE_QUORUM_ENABLED]: false, [FeatureFlags.EARLY_ACCESS]: false, [FeatureFlags.SUPPORTER_DETHRONED_ENABLED]: false, [FeatureFlags.NEW_ARTIST_DASHBOARD_TABLE]: false, @@ -104,7 +101,6 @@ export const flagDefaults: FlagDefaults = { [FeatureFlags.SDK_DISCOVERY_NODE_SELECTOR]: false, [FeatureFlags.RELATED_ARTISTS_ON_PROFILE_ENABLED]: false, [FeatureFlags.PROXY_WORMHOLE]: false, - [FeatureFlags.STORAGE_V2_SIGNUP]: false, [FeatureFlags.PLAYLIST_UPDATES_PRE_QA]: false, [FeatureFlags.PLAYLIST_UPDATES_POST_QA]: false, [FeatureFlags.AI_ATTRIBUTION]: false, diff --git a/packages/common/src/services/sdk/bootstrapNodes.ts b/packages/common/src/services/sdk/bootstrapNodes.ts index f244ff8a52..3f6e7f1c03 100644 --- a/packages/common/src/services/sdk/bootstrapNodes.ts +++ b/packages/common/src/services/sdk/bootstrapNodes.ts @@ -1,5 +1,20 @@ import { Env } from '../env' +const devBootstrapNodes = [ + { + delegateOwnerWallet: '0x0D38e653eC28bdea5A2296fD5940aaB2D0B8875c', + endpoint: 'http://audius-protocol-creator-node-1' + }, + { + delegateOwnerWallet: '0x1B569e8f1246907518Ff3386D523dcF373e769B6', + endpoint: 'http://audius-protocol-creator-node-2' + }, + { + delegateOwnerWallet: '0xCBB025e7933FADfc7C830AE520Fb2FD6D28c1065', + endpoint: 'http://audius-protocol-creator-node-3' + } +] + const stagingBootstrapNodes = [ { endpoint: 'https://usermetadata.staging.audius.co', @@ -311,4 +326,4 @@ export const getBootstrapNodes = (env: Env) => ? stagingBootstrapNodes : env.ENVIRONMENT === 'production' ? productionBootstrapNodes - : undefined + : devBootstrapNodes diff --git a/packages/common/src/services/sdk/discovery-node-selector/DiscoveryNodeSelectorService.ts b/packages/common/src/services/sdk/discovery-node-selector/DiscoveryNodeSelectorService.ts index d021d4c7b5..e0306d0799 100644 --- a/packages/common/src/services/sdk/discovery-node-selector/DiscoveryNodeSelectorService.ts +++ b/packages/common/src/services/sdk/discovery-node-selector/DiscoveryNodeSelectorService.ts @@ -16,18 +16,24 @@ import { type DiscoveryNodeSelectorConfig = { env: Env remoteConfigInstance: RemoteConfigInstance + initialSelectedNode?: string + onChange?: (endpoint: string) => void } export class DiscoveryNodeSelectorService { private env: Env private remoteConfigInstance: RemoteConfigInstance private discoveryNodeSelectorPromise: Promise | null + private initialSelectedNode: string | undefined + private onChange: ((endpoint: string) => void) | undefined constructor(config: DiscoveryNodeSelectorConfig) { - const { env, remoteConfigInstance } = config + const { env, remoteConfigInstance, initialSelectedNode, onChange } = config this.env = env this.remoteConfigInstance = remoteConfigInstance this.discoveryNodeSelectorPromise = null + this.initialSelectedNode = initialSelectedNode + this.onChange = onChange } private async makeDiscoveryNodeSelector() { @@ -59,12 +65,17 @@ export class DiscoveryNodeSelectorService { const requestTimeout = getRemoteVar(IntKeys.DISCOVERY_PROVIDER_SELECTION_TIMEOUT_MS) ?? undefined - return new DiscoveryNodeSelector({ + const dnSelector = new DiscoveryNodeSelector({ healthCheckThresholds, blocklist, requestTimeout, - bootstrapServices: discoveryNodes + bootstrapServices: discoveryNodes, + initialSelectedNode: this.initialSelectedNode }) + if (this.onChange) { + dnSelector.addEventListener('change', this.onChange) + } + return dnSelector } private getBlockList(remoteVarKey: StringKeys) { diff --git a/packages/common/src/store/cache/reducer.ts b/packages/common/src/store/cache/reducer.ts index 5fb35d5da6..072a641127 100644 --- a/packages/common/src/store/cache/reducer.ts +++ b/packages/common/src/store/cache/reducer.ts @@ -152,6 +152,25 @@ export const mergeCustomizer = (objValue: any, srcValue: any, key: string) => { } } +const updateImageCache = (existing: Metadata, next: Metadata, merged: any) => { + if ( + 'profile_picture_sizes' in existing && + 'profile_picture_sizes' in next && + existing.profile_picture_sizes !== next.profile_picture_sizes + ) { + merged._profile_picture_sizes = {} + } + if ( + 'cover_photo_sizes' in existing && + 'cover_photo_sizes' in next && + existing.cover_photo_sizes !== next.cover_photo_sizes + ) { + merged._cover_photo_sizes = {} + } + + return merged +} + const addEntries = (state: CacheState, entries: any[], replace?: boolean) => { const { cacheType } = state const newEntries = { ...state.entries } @@ -181,12 +200,13 @@ const addEntries = (state: CacheState, entries: any[], replace?: boolean) => { ) { // do nothing } else if (existing) { - const newMetadata = mergeWith( + let newMetadata = mergeWith( {}, existing, entity.metadata, mergeCustomizer ) + newMetadata = updateImageCache(existing, entity.metadata, newMetadata) if (cacheType === 'safe-fast' && isEqual(existing, newMetadata)) { // do nothing } else { @@ -248,14 +268,10 @@ const actionsMap = { const newSubscriptions = { ...state.subscriptions } action.entries.forEach((e: { id: string | number; metadata: any }) => { - newEntries[e.id] = wrapEntry( - mergeWith( - {}, - { ...unwrapEntry(state.entries[e.id]) }, - e.metadata, - mergeCustomizer - ) - ) + const existing = { ...unwrapEntry(state.entries[e.id]) } + let newEntry = mergeWith({}, existing, e.metadata, mergeCustomizer) + newEntry = updateImageCache(existing, e.metadata, newEntry) + newEntries[e.id] = wrapEntry(newEntry) }) action.subscriptions.forEach((s: { id: any; kind: any; uids: any }) => { diff --git a/packages/common/src/store/lineup/reducer.ts b/packages/common/src/store/lineup/reducer.ts index f8ecf460d1..61d3c7900d 100644 --- a/packages/common/src/store/lineup/reducer.ts +++ b/packages/common/src/store/lineup/reducer.ts @@ -39,8 +39,6 @@ export const initialLineupState = { // Boolean if the lineup fetch call pagination includes deleted tracks/collections // e.g. This should be true if we request 10 tracks but only get 9 back because // one is deleted - // - Used to know if the lineup should stop fetching more content - containsDeleted: true, // Whether the lineup is limited to a certain length maxEntries: null } @@ -131,18 +129,7 @@ export const actionsMap = { const newState = { ...state } newState.isMetadataLoading = false newState.status = Status.SUCCESS - newState.hasMore = - action.entries.length + action.deleted >= action.limit - action.offset - - // If the lineup does not fetch deleted tracks and there are missing tracks - // in the response (indicated by 'deleted' count), then there is no more content - if ( - !newState.containsDeleted && - !isNaN(action.deleted) && - action.deleted > 0 - ) { - newState.hasMore = false - } + newState.hasMore = action.entries.length + action.deleted >= action.limit // Hack alert: // For lineups with max entries (such as trending playlists) and deleted content, diff --git a/packages/common/src/store/pages/ai/lineup/reducer.ts b/packages/common/src/store/pages/ai/lineup/reducer.ts index 3e542b86f4..54af172778 100644 --- a/packages/common/src/store/pages/ai/lineup/reducer.ts +++ b/packages/common/src/store/pages/ai/lineup/reducer.ts @@ -5,7 +5,6 @@ import { PREFIX } from './actions' export const initialState = { ...initialLineupState, - containsDeleted: false, prefix: PREFIX } diff --git a/packages/common/src/store/pages/collection/actions.ts b/packages/common/src/store/pages/collection/actions.ts index db5627ee13..14e7de85b8 100644 --- a/packages/common/src/store/pages/collection/actions.ts +++ b/packages/common/src/store/pages/collection/actions.ts @@ -15,10 +15,15 @@ export const setCollectionPermalink = (permalink: string) => ({ permalink }) -export const fetchCollection = (id: Nullable, permalink?: string) => ({ +export const fetchCollection = ( + id: Nullable, + permalink?: string, + fetchLineup?: boolean +) => ({ type: FETCH_COLLECTION, id, - permalink + permalink, + fetchLineup }) export const fetchCollectionSucceeded = ( diff --git a/packages/common/src/store/pages/feed/lineup/reducer.ts b/packages/common/src/store/pages/feed/lineup/reducer.ts index 2b9565dd30..9b75da90a0 100644 --- a/packages/common/src/store/pages/feed/lineup/reducer.ts +++ b/packages/common/src/store/pages/feed/lineup/reducer.ts @@ -8,7 +8,6 @@ export const initialState = { ...initialLineupState, prefix: PREFIX, dedupe: true, - containsDeleted: false, entryIds: new Set([]) } diff --git a/packages/common/src/store/pages/profile/lineups/feed/reducer.ts b/packages/common/src/store/pages/profile/lineups/feed/reducer.ts index 891b18c76f..a3e85c9179 100644 --- a/packages/common/src/store/pages/profile/lineups/feed/reducer.ts +++ b/packages/common/src/store/pages/profile/lineups/feed/reducer.ts @@ -6,8 +6,7 @@ import { LineupState } from '../../../../../models' export const initialState: LineupState<{ id: number }> = { ...initialLineupState, - prefix: PREFIX, - containsDeleted: false + prefix: PREFIX } type ResetSucceededAction = { diff --git a/packages/common/src/store/pages/profile/lineups/tracks/reducer.ts b/packages/common/src/store/pages/profile/lineups/tracks/reducer.ts index 138544005a..ab0492ffdc 100644 --- a/packages/common/src/store/pages/profile/lineups/tracks/reducer.ts +++ b/packages/common/src/store/pages/profile/lineups/tracks/reducer.ts @@ -6,8 +6,7 @@ import { PREFIX } from 'store/pages/profile/lineups/tracks/actions' export const initialState = { ...initialLineupState, - prefix: PREFIX, - containsDeleted: false + prefix: PREFIX } const actionsMap = { diff --git a/packages/common/src/store/pages/remixes/lineup/reducer.ts b/packages/common/src/store/pages/remixes/lineup/reducer.ts index 14be7ac1ac..67f1cc37d7 100644 --- a/packages/common/src/store/pages/remixes/lineup/reducer.ts +++ b/packages/common/src/store/pages/remixes/lineup/reducer.ts @@ -4,7 +4,6 @@ import { PREFIX } from 'store/pages/remixes/lineup/actions' export const initialState = { ...initialLineupState, - containsDeleted: false, prefix: PREFIX } diff --git a/packages/common/src/store/pages/search-results/lineup/tracks/reducer.ts b/packages/common/src/store/pages/search-results/lineup/tracks/reducer.ts index dc65141056..2c96f4efda 100644 --- a/packages/common/src/store/pages/search-results/lineup/tracks/reducer.ts +++ b/packages/common/src/store/pages/search-results/lineup/tracks/reducer.ts @@ -6,8 +6,7 @@ import { LineupState, Track } from '../../../../../models' export const initialState: LineupState = { ...initialLineupState, - prefix: PREFIX, - containsDeleted: false + prefix: PREFIX } type ResetSucceededAction = { diff --git a/packages/common/src/utils/chatUtils.ts b/packages/common/src/utils/chatUtils.ts index 9e0d0f7eaa..eaa5fd476c 100644 --- a/packages/common/src/utils/chatUtils.ts +++ b/packages/common/src/utils/chatUtils.ts @@ -5,6 +5,9 @@ import { Status } from 'models/Status' import { MESSAGE_GROUP_THRESHOLD_MINUTES } from './constants' +export const CHAT_BLOG_POST_URL = + 'http://support.audius.co/help/How-to-Send-Messages-on-Audius' + /** * Checks to see if the message was sent within the time threshold for grouping it with the next message */ diff --git a/packages/embed/.env.dev b/packages/embed/.env.dev index 758a0ac66c..a39eac9e6f 100644 --- a/packages/embed/.env.dev +++ b/packages/embed/.env.dev @@ -4,6 +4,5 @@ PREACT_APP_AMPLITUDE_KEY=72a58ce4ad1f9bafcba0b92bedb6c33d PREACT_APP_AMPLITUDE_API_PROXY=gain.audius.co PREACT_APP_ENVIRONMENT=development PREACT_APP_IDENTITY_ENDPOINT=https://identityservice.staging.audius.co -PREACT_APP_CREATOR_NODE_WHITELIST=https://usermetadata.staging.audius.co,https://creatornode.staging.audius.co,https://creatornode2.staging.audius.co,https://creatornode3.staging.audius.co PREACT_APP_HOSTNAME_REDIRECT=redirect.staging.audius.co PREACT_APP_STREAM_MP3_ON=true \ No newline at end of file diff --git a/packages/embed/.env.prod b/packages/embed/.env.prod index 7dc8d1f27d..b1df9fc506 100644 --- a/packages/embed/.env.prod +++ b/packages/embed/.env.prod @@ -1,6 +1,5 @@ PREACT_APP_AUDIUS_SCHEME=https PREACT_APP_IDENTITY_ENDPOINT=https://identityservice.audius.co -PREACT_APP_CREATOR_NODE_WHITELIST=https://creatornode.audius.co,https://creatornode2.audius.co,https://creatornode3.audius.co,https://content-node.audius.co,https://audius-content-1.figment.io,https://creatornode.audius.prod-us-west-2.staked.cloud,https://audius-content-2.figment.io,https://audius-content-3.figment.io,https://audius-content-4.figment.io,https://audius-content-5.figment.io,https://creatornode.audius1.prod-us-west-2.staked.cloud,https://creatornode.audius2.prod-us-west-2.staked.cloud,https://creatornode.audius3.prod-us-west-2.staked.cloud,https://creatornode.audius4.prod-us-west-2.staked.cloud,https://creatornode.audius5.prod-us-west-2.staked.cloud,https://creatornode.audius6.prod-us-west-2.staked.cloud,https://audius-content-6.figment.io,https://audius-content-7.figment.io,https://audius-content-8.figment.io,https://usermetadata.audius.co,https://creatornode.audius7.prod-us-west-2.staked.cloud,https://audius-content-9.figment.io,https://audius-content-10.figment.io,https://audius-content-11.figment.io,https://audius.prod.capturealpha.io,https://content.grassfed.network,https://blockdaemon-audius-content-01.bdnodes.net,https://audius-content-1.cultur3stake.com,https://audius-content-2.cultur3stake.com,https://audius-content-3.cultur3stake.com,https://audius-content-4.cultur3stake.com,https://audius-content-5.cultur3stake.com,https://audius-content-6.cultur3stake.com,https://audius-content-7.cultur3stake.com,https://blockdaemon-audius-content-02.bdnodes.net,https://blockdaemon-audius-content-03.bdnodes.net,https://blockdaemon-audius-content-04.bdnodes.net,https://blockdaemon-audius-content-05.bdnodes.net,https://blockdaemon-audius-content-06.bdnodes.net,https://blockdaemon-audius-content-07.bdnodes.net,https://blockdaemon-audius-content-08.bdnodes.net,https://blockdaemon-audius-content-09.bdnodes.net,https://audius-content-8.cultur3stake.com,https://blockchange-audius-content-01.bdnodes.net,https://blockchange-audius-content-02.bdnodes.net,https://blockchange-audius-content-03.bdnodes.net,https://audius-content-9.cultur3stake.com,https://audius-content-10.cultur3stake.com,https://audius-content-11.cultur3stake.com,https://audius-content-12.cultur3stake.com,https://audius-content-13.cultur3stake.com,https://audius-content-14.cultur3stake.com,https://audius-content-15.cultur3stake.com,https://audius-content-16.cultur3stake.com,https://audius-content-17.cultur3stake.com,https://audius-content-18.cultur3stake.com,https://audius-content-12.figment.io,https://cn0.mainnet.audiusindex.org,https://cn1.mainnet.audiusindex.org,https://cn2.mainnet.audiusindex.org,https://cn3.mainnet.audiusindex.org,https://audius-content-13.figment.io,https://audius-content-14.figment.io,https://cn4.mainnet.audiusindex.org,https://audius-content-1.jollyworld.xyz,https://audius-content-1.decentralizeaudio.xyz PREACT_APP_HOST_PREFIX=/embed PREACT_APP_AMPLITUDE_KEY=86760558b8bb1b3aae61656efd4ddacb PREACT_APP_AMPLITUDE_API_PROXY=gain.audius.co diff --git a/packages/embed/.env.stage b/packages/embed/.env.stage index 42b1b52406..aab7e84323 100644 --- a/packages/embed/.env.stage +++ b/packages/embed/.env.stage @@ -1,6 +1,5 @@ PREACT_APP_AUDIUS_SCHEME=https PREACT_APP_IDENTITY_ENDPOINT=https://identityservice.staging.audius.co -PREACT_APP_CREATOR_NODE_WHITELIST=https://usermetadata.staging.audius.co,https://creatornode.staging.audius.co,https://creatornode2.staging.audius.co,https://creatornode3.staging.audius.co PREACT_APP_HOST_PREFIX=/embed PREACT_APP_AMPLITUDE_KEY=72a58ce4ad1f9bafcba0b92bedb6c33d PREACT_APP_AMPLITUDE_API_PROXY=gain.audius.co diff --git a/packages/embed/package-lock.json b/packages/embed/package-lock.json index b89a6d3e86..4f9f8782ed 100644 --- a/packages/embed/package-lock.json +++ b/packages/embed/package-lock.json @@ -77,9 +77,9 @@ } }, "@audius/sdk": { - "version": "3.0.3-beta.63", - "resolved": "https://registry.npmjs.org/@audius/sdk/-/sdk-3.0.3-beta.63.tgz", - "integrity": "sha512-yiUjzV/xOZe9N1a+P0ApD3mpzeZcaVQ+vG55qu7Ic045MX87IrKCatHGU1tZMK71fe3P4ZpktTo6SeGs6UCKlQ==", + "version": "3.0.3-beta.76", + "resolved": "https://registry.npmjs.org/@audius/sdk/-/sdk-3.0.3-beta.76.tgz", + "integrity": "sha512-zhH7ZMcVsMVhDTRNxwWtgXOR69E7GVq09bUnDd7m/Xh/dsL4vg0eZaULTlPCNi1b3VGDZs0KFyVzR16Pyt9ufA==", "requires": { "@audius/hedgehog": "2.1.0", "@babel/runtime": "7.18.3", @@ -3758,9 +3758,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -3818,9 +3818,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -20466,9 +20466,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" } diff --git a/packages/embed/package.json b/packages/embed/package.json index 6716e2dd2b..c20d1ba389 100644 --- a/packages/embed/package.json +++ b/packages/embed/package.json @@ -34,7 +34,7 @@ "webpack-cli": "4.5.0" }, "dependencies": { - "@audius/sdk": "3.0.3-beta.63", + "@audius/sdk": "3.0.3-beta.76", "@audius/stems": "1.5.31", "amplitude-js": "8.11.1", "axios": "0.19.2", diff --git a/packages/embed/src/components/collectibles/CollectibleGallery.jsx b/packages/embed/src/components/collectibles/CollectibleGallery.jsx index 113238b9ef..d5d8f875b4 100644 --- a/packages/embed/src/components/collectibles/CollectibleGallery.jsx +++ b/packages/embed/src/components/collectibles/CollectibleGallery.jsx @@ -2,13 +2,13 @@ import cn from 'classnames' import { h } from 'preact' import { useState, useEffect } from 'preact/hooks' -import { fetchJsonFromCID } from '../../util/fetchCID' import Card from '../card/Card' import CollectibleDetailsView from './CollectibleDetailsView' import styles from './CollectibleGallery.module.css' import CollectibleTile from './CollectibleTile' import CollectiblesHeader from './CollectiblesHeader' +import { getCollectiblesJson } from '../../util/BedtimeClient' const CollectibleGallery = ({ collectibles, @@ -22,7 +22,7 @@ const CollectibleGallery = ({ const [hasFetched, setHasFetched] = useState(false) const fetchCollectiblesOrder = async () => { - const result = await fetchJsonFromCID(user.metadata_multihash) + const result = await getCollectiblesJson(user.metadata_multihash) setHasFetched(true) if (result && result.collectibles) { diff --git a/packages/embed/src/util/BedtimeClient.js b/packages/embed/src/util/BedtimeClient.js index fc18dd2a8a..362241a9f8 100644 --- a/packages/embed/src/util/BedtimeClient.js +++ b/packages/embed/src/util/BedtimeClient.js @@ -49,6 +49,11 @@ const audiusSdk = sdk({ export const getTrackStreamEndpoint = (trackId) => `${discoveryEndpoint}/v1/tracks/${trackId}/stream` +export const getCollectiblesJson = async (cid) => { + const url = `${discoveryEndpoint}/v1/full/cid_data/${cid}` + return (await (await fetch(url)).json())?.data?.data +} + export const uuid = () => { // https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript/873856#873856 const s = [] diff --git a/packages/embed/src/util/fetchCID.js b/packages/embed/src/util/fetchCID.js deleted file mode 100644 index c81d9b5fed..0000000000 --- a/packages/embed/src/util/fetchCID.js +++ /dev/null @@ -1,124 +0,0 @@ -import { getCreatorNodeWhitelist } from './getEnv' -import { logError } from './logError' - -const axios = require('axios') - -const CancelToken = axios.CancelToken - -const creatorNodes = getCreatorNodeWhitelist() -const creatorNodeWhitelist = new Set( - creatorNodes.split(',').map((c) => `${c}/ipfs/`) -) - -// Stolen from libs :) - -const wait = async (milliseconds) => { - return new Promise((resolve) => setTimeout(resolve, milliseconds)) -} - -/** - * Given an array of promises, it returns the first resolved promise as soon as it finishes - * @param {Array} promises - * @return {Promise} A promise that resolves with the first promise that resolves - */ -const promiseFight = async (promises) => { - return Promise.all( - promises.map((p) => { - return p.then( - (val) => Promise.reject(val), - (err) => Promise.resolve(err) - ) - }) - ).then( - (errors) => Promise.reject(errors), - (val) => Promise.resolve(val) - ) -} - -// Races requests for file content -async function raceRequests(urls, callback) { - const sources = [] - const requests = urls.map(async (url, i) => { - const source = CancelToken.source() - sources.push(source) - - // Slightly offset requests by their order, so: - // 1. We try public gateways first - // 2. We give requests the opportunity to get canceled if other's are very fast - await wait(100 * i) - - return new Promise((resolve, reject) => { - axios({ - method: 'get', - url, - responseType: 'blob', - cancelToken: source.token - }) - .then((response) => { - resolve({ - blob: response, - url - }) - }) - .catch((thrown) => { - reject(thrown) - // no-op. - // If debugging `axios.isCancel(thrown)` - // can be used to check if the throw was from a cancel. - }) - }) - }) - const response = await promiseFight(requests) - sources.forEach((source) => { - source.cancel('Fetch already succeeded') - }) - callback(response.url) - return response.blob -} - -export const fetchCID = async (cid) => { - const allGateways = creatorNodeWhitelist - - try { - const image = await _fetchCID(cid, allGateways) - const url = URL.createObjectURL(image.data) - return url - } catch (e) { - logError(e) - return '' - } -} - -export const fetchJsonFromCID = async (cid) => { - const allGateways = creatorNodeWhitelist - - try { - const image = await _fetchCID(cid, allGateways) - return JSON.parse(await image.data.text()) - } catch (e) { - logError(e) - return null - } -} - -/** - * Fetches a file from IPFS with a given CID. Public gateways are tried first, then - * fallback to a specified gateway and then to the default gateway. - * @param {string} cid IPFS content identifier - * @param {Set} creatorNodeGateways fallback ipfs gateways from creator nodes - * @param {?function} callback callback called on each successful/failed fetch with - * [String, Bool](gateway, succeeded) - * Can be used for tracking metrics on which gateways were used. - */ -const _fetchCID = async (cid, creatorNodeGateways, callback = () => {}) => { - const gateways = [...creatorNodeGateways] - const urls = gateways.map((gateway) => `${gateway}${cid}`) - - try { - return raceRequests(urls, callback) - } catch (e) { - throw new Error(`Failed to retrieve ${cid}`) - } -} - -export default fetchCID diff --git a/packages/embed/src/util/getEnv.js b/packages/embed/src/util/getEnv.js index d54883cc3e..eea24e55ca 100644 --- a/packages/embed/src/util/getEnv.js +++ b/packages/embed/src/util/getEnv.js @@ -10,10 +10,6 @@ export const getIdentityEndpoint = () => { return process.env.PREACT_APP_IDENTITY_ENDPOINT } -export const getCreatorNodeWhitelist = () => { - return process.env.PREACT_APP_CREATOR_NODE_WHITELIST -} - // Need some way to run against GA locally export const getAPIHostname = () => { const localGAPort = process.env.PREACT_APP_LOCAL_GA_PORT diff --git a/packages/mobile/.env.dev b/packages/mobile/.env.dev index db1b21bcd0..6cbf918b9a 100644 --- a/packages/mobile/.env.dev +++ b/packages/mobile/.env.dev @@ -3,7 +3,6 @@ IS_PRODUCTION=true USER_METADATA_NODE=http://audius-protocol-creator-node-1 AUDIUS_URL=https://staging.audius.co USER_NODE=http://audius-protocol-creator-node-1 -LEGACY_USER_NODE=http://audius-protocol-creator-node-1 IDENTITY_SERVICE=http://audius-protocol-identity-service-1 HCAPTCHA_SITE_KEY=2abe61f1-af6e-4707-be19-a9a4146a9bea HCAPTCHA_BASE_URL=https://staging.audius.co @@ -11,7 +10,7 @@ AMPLITUDE_WRITE_KEY=abc OPTIMIZELY_KEY=MX4fYBgANQetvmBXGpuxzF REGISTRY_ADDRESS=0xCfEB869F69431e42cdB54A4F4f105C19C080A601 -ENTITY_MANAGER_ADDRESS=0x5b9b42d6e4B2e4Bf8d42Eba32D46918e10899B66 +ENTITY_MANAGER_ADDRESS=0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B WEB3_PROVIDER_URL=http://audius-protocol-poa-ganache-1 ETH_REGISTRY_ADDRESS=0xABbfF712977dB51f9f212B85e8A4904c818C2b63 diff --git a/packages/mobile/.env.prod b/packages/mobile/.env.prod index 9d9b0efd5f..84754e6e8f 100644 --- a/packages/mobile/.env.prod +++ b/packages/mobile/.env.prod @@ -3,7 +3,6 @@ IS_PRODUCTION=true USER_METADATA_NODE=https://usermetadata.audius.co AUDIUS_URL=https://audius.co USER_NODE=https://usermetadata.audius.co -LEGACY_USER_NODE=https://creatornode.audius.co IDENTITY_SERVICE=https://identityservice.audius.co HCAPTCHA_SITE_KEY=b250803e-dcba-428c-bc87-8acf559aacb9 HCAPTCHA_BASE_URL=https://audius.co diff --git a/packages/mobile/dapp-store/.asset-manifest.json b/packages/mobile/dapp-store/.asset-manifest.json index 7b226ca550..0a6c38c435 100644 --- a/packages/mobile/dapp-store/.asset-manifest.json +++ b/packages/mobile/dapp-store/.asset-manifest.json @@ -42,8 +42,8 @@ }, "../android/app/build/outputs/apk/prod/release/app-prod-release.apk": { "path": "../android/app/build/outputs/apk/prod/release/app-prod-release.apk", - "sha256": "43dsu7XSpWBo12EGRp5XKXRw8Na1fYqxCuDgu2JNJeE=", - "uri": "https://arweave.net/ZLPIepLLjdfc6GhBsFNJwoQhhcyYxLBPdL0P3BZZLYU" + "sha256": "AOsnAd78g8VdQ28vN5NMJlzevad3xjUFG/gxQolj+Us=", + "uri": "https://arweave.net/oE8e3clWxPwXQyCtmCbTT_J8MRIL4LSVpSza1D6Pnxk" } } } \ No newline at end of file diff --git a/packages/mobile/dapp-store/config.yaml b/packages/mobile/dapp-store/config.yaml index b34e75a4a2..63ea667dd8 100644 --- a/packages/mobile/dapp-store/config.yaml +++ b/packages/mobile/dapp-store/config.yaml @@ -19,7 +19,7 @@ app: - purpose: icon uri: ./media/app_icon.png release: - address: APkBBQBUrFn9xL812E5bEgBTbZ3rBTLGHgrpE657rMko + address: 9sDh8omCxc5VcjSMtdEJwT9oxbWauzLB2g7EWoAPLpdi media: - purpose: icon uri: ./media/publisher_icon.png diff --git a/packages/mobile/dapp-store/package.json b/packages/mobile/dapp-store/package.json index c2a899ddbb..13a260c2ce 100644 --- a/packages/mobile/dapp-store/package.json +++ b/packages/mobile/dapp-store/package.json @@ -9,6 +9,6 @@ "author": "", "license": "ISC", "devDependencies": { - "@solana-mobile/dapp-store-cli": "0.4.2" + "@solana-mobile/dapp-store-cli": "0.5.0" } } diff --git a/packages/mobile/dapp-store/pnpm-lock.yaml b/packages/mobile/dapp-store/pnpm-lock.yaml index 54441e7cd5..356277610a 100644 --- a/packages/mobile/dapp-store/pnpm-lock.yaml +++ b/packages/mobile/dapp-store/pnpm-lock.yaml @@ -1,13 +1,650 @@ lockfileVersion: 5.4 specifiers: - '@solana-mobile/dapp-store-cli': 0.4.2 + '@solana-mobile/dapp-store-cli': 0.5.0 devDependencies: - '@solana-mobile/dapp-store-cli': 0.4.2 + '@solana-mobile/dapp-store-cli': 0.5.0 packages: + /@aws-crypto/crc32/3.0.0: + resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.369.0 + tslib: 1.14.1 + dev: true + + /@aws-crypto/crc32c/3.0.0: + resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.369.0 + tslib: 1.14.1 + dev: true + + /@aws-crypto/ie11-detection/3.0.0: + resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + dependencies: + tslib: 1.14.1 + dev: true + + /@aws-crypto/sha1-browser/3.0.0: + resolution: {integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: true + + /@aws-crypto/sha256-browser/3.0.0: + resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: true + + /@aws-crypto/sha256-js/3.0.0: + resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.369.0 + tslib: 1.14.1 + dev: true + + /@aws-crypto/supports-web-crypto/3.0.0: + resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + dependencies: + tslib: 1.14.1 + dev: true + + /@aws-crypto/util/3.0.0: + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + dependencies: + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: true + + /@aws-sdk/chunked-blob-reader-native/3.310.0: + resolution: {integrity: sha512-RuhyUY9hCd6KWA2DMF/U6rilYLLRYrDY6e0lq3Of1yzSRFxi4bk9ZMCF0mxf/9ppsB5eudUjrOypYgm6Axt3zw==} + dependencies: + '@aws-sdk/util-base64': 3.310.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/chunked-blob-reader/3.310.0: + resolution: {integrity: sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==} + dependencies: + tslib: 2.6.0 + dev: true + + /@aws-sdk/client-s3/3.369.0: + resolution: {integrity: sha512-nhLjpeCFt5KSypNP0B0VXJrhd5WCE4un4t6zHcb0rAIbmmRvILAby3e/3/3nmUTDp4MNriz5YW6dWI0sYtbJIA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha1-browser': 3.0.0 + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.369.0 + '@aws-sdk/credential-provider-node': 3.369.0 + '@aws-sdk/hash-blob-browser': 3.369.0 + '@aws-sdk/hash-stream-node': 3.369.0 + '@aws-sdk/md5-js': 3.369.0 + '@aws-sdk/middleware-bucket-endpoint': 3.369.0 + '@aws-sdk/middleware-expect-continue': 3.369.0 + '@aws-sdk/middleware-flexible-checksums': 3.369.0 + '@aws-sdk/middleware-host-header': 3.369.0 + '@aws-sdk/middleware-location-constraint': 3.369.0 + '@aws-sdk/middleware-logger': 3.369.0 + '@aws-sdk/middleware-recursion-detection': 3.369.0 + '@aws-sdk/middleware-sdk-s3': 3.369.0 + '@aws-sdk/middleware-signing': 3.369.0 + '@aws-sdk/middleware-ssec': 3.369.0 + '@aws-sdk/middleware-user-agent': 3.369.0 + '@aws-sdk/signature-v4-multi-region': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-endpoints': 3.369.0 + '@aws-sdk/util-user-agent-browser': 3.369.0 + '@aws-sdk/util-user-agent-node': 3.369.0 + '@aws-sdk/xml-builder': 3.310.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/eventstream-serde-browser': 1.0.2 + '@smithy/eventstream-serde-config-resolver': 1.0.2 + '@smithy/eventstream-serde-node': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-stream': 1.0.2 + '@smithy/util-utf8': 1.0.2 + '@smithy/util-waiter': 1.0.2 + fast-xml-parser: 4.2.5 + tslib: 2.6.0 + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - aws-crt + dev: true + + /@aws-sdk/client-sso-oidc/3.369.0: + resolution: {integrity: sha512-NOnsRrkHMss9pE68uTPMEt1KoW6eWt4ZCesJayCOiIgmIA/AhXHz06IBCYJ9eu9Xbu/55FDr4X3VCtUf7Rfh6g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.369.0 + '@aws-sdk/middleware-logger': 3.369.0 + '@aws-sdk/middleware-recursion-detection': 3.369.0 + '@aws-sdk/middleware-user-agent': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-endpoints': 3.369.0 + '@aws-sdk/util-user-agent-browser': 3.369.0 + '@aws-sdk/util-user-agent-node': 3.369.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/client-sso/3.369.0: + resolution: {integrity: sha512-SjJd9QGT9ccHOY64qnMfvVjrneBORIx/k8OdtL0nV2wemPqCM9uAm+TYZ01E91D/+lfXS+lLMGSidSA39PMIOA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.369.0 + '@aws-sdk/middleware-logger': 3.369.0 + '@aws-sdk/middleware-recursion-detection': 3.369.0 + '@aws-sdk/middleware-user-agent': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-endpoints': 3.369.0 + '@aws-sdk/util-user-agent-browser': 3.369.0 + '@aws-sdk/util-user-agent-node': 3.369.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/client-sts/3.369.0: + resolution: {integrity: sha512-kyZl654U27gsQX9UjiiO4CX5M6kHwzDouwbhjc5HshQld/lUbJQ4uPpAwhlbZiqnzGeB639MdAGaSwrOOw2ixw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/credential-provider-node': 3.369.0 + '@aws-sdk/middleware-host-header': 3.369.0 + '@aws-sdk/middleware-logger': 3.369.0 + '@aws-sdk/middleware-recursion-detection': 3.369.0 + '@aws-sdk/middleware-sdk-sts': 3.369.0 + '@aws-sdk/middleware-signing': 3.369.0 + '@aws-sdk/middleware-user-agent': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-endpoints': 3.369.0 + '@aws-sdk/util-user-agent-browser': 3.369.0 + '@aws-sdk/util-user-agent-node': 3.369.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + fast-xml-parser: 4.2.5 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/credential-provider-env/3.369.0: + resolution: {integrity: sha512-EZUXGLjnun5t5/dVYJ9yyOwPAJktOdLEQSwtw7Q9XOxaNqVFFz9EU+TwYraV4WZ3CFRNn7GEIctVlXAHVFLm/w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/credential-provider-ini/3.369.0: + resolution: {integrity: sha512-12XXd4gnrn05adio/xPF8Nxl99L2FFzksbFILDIfSni7nLDX0m2XprnkswQiCKSbfDIQQsgnnh2F+HhorLuqfQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.369.0 + '@aws-sdk/credential-provider-process': 3.369.0 + '@aws-sdk/credential-provider-sso': 3.369.0 + '@aws-sdk/credential-provider-web-identity': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/credential-provider-node/3.369.0: + resolution: {integrity: sha512-vxX4s33EpRDh7OhKBDVAPxdBxVHPOOj1r7nN6f0hZLw5WPeeffSjLqw+MnFj33gSO7Htnt+Q0cAJQzeY5G8q3A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.369.0 + '@aws-sdk/credential-provider-ini': 3.369.0 + '@aws-sdk/credential-provider-process': 3.369.0 + '@aws-sdk/credential-provider-sso': 3.369.0 + '@aws-sdk/credential-provider-web-identity': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/credential-provider-process/3.369.0: + resolution: {integrity: sha512-OyasKV3mZz6TRSxczRnyZoifrtYwqGBxtr75YP37cm/JkecDshHXRcE8Jt9LyBg/93oWfKou03WVQiY9UIDJGQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/credential-provider-sso/3.369.0: + resolution: {integrity: sha512-qXbEsmgFpGPbRVnwBYPxL53wQuue0+Z8tVu877itbrzpHm61AuQ04Hn8T1boKrr40excDuxiSrCX5oCKRG4srQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.369.0 + '@aws-sdk/token-providers': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/credential-provider-web-identity/3.369.0: + resolution: {integrity: sha512-oFGxC839pQTJ6djFEBuokSi3/jNjNMVgZSpg26Z23V/r3vKRSgXfVmeus1FLYIWg0jO7KFsMPo9eVJW6auzw6w==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/hash-blob-browser/3.369.0: + resolution: {integrity: sha512-fx+6Qavc5dSuVm6vAXrA7oyPSu/gGW2W8YnSCmhDUCQw7UFB8b9Uc97sM43K8RNi0pj3cPevvgbab1m+E8Vs8A==} + dependencies: + '@aws-sdk/chunked-blob-reader': 3.310.0 + '@aws-sdk/chunked-blob-reader-native': 3.310.0 + '@aws-sdk/types': 3.369.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/hash-stream-node/3.369.0: + resolution: {integrity: sha512-v4xGoCHw8VLEa2HcvnNa5TMrmNS6iNVHKWpjWnq/zu7ZwtoJcRFsjEEQaW0EkfpoBtT0Ll7jHmSFS+q28xa/Fw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-utf8': 3.310.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/is-array-buffer/3.310.0: + resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@aws-sdk/md5-js/3.369.0: + resolution: {integrity: sha512-gnwXE/9h1UufrafvCKdONuNEzqeiBfFJM68Ww3b2c9Eby7+BVv/O3jghxr9XAEM60A0CaEoLCqH+5Auh58NJag==} + dependencies: + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-utf8': 3.310.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-bucket-endpoint/3.369.0: + resolution: {integrity: sha512-wcb8e40pOktygAeHwR9JmkZPZsc/UIHU7qdaKuKjE4MgLS3EUUp71iE4GMfFOpVrRlLlTAaGylaXVjFIcZuhnw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + '@smithy/util-config-provider': 1.0.2 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-expect-continue/3.369.0: + resolution: {integrity: sha512-uHUOjPDFHSaO6QTO0KGAl6sWbz3Kp21/AlO/qEexvP/F+12cSimR/f/mFLfAHvBCyftiD/6TFxf6p5WzkEkGBQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-flexible-checksums/3.369.0: + resolution: {integrity: sha512-7oLXQbB6G2KrssFXH6iIdIbmI8Ex1VUQ+xnF1QBJcHasFY/Wn/WMAEZHtlk/J+eqHafR2UhlyncR80J1tZh9KA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@aws-crypto/crc32c': 3.0.0 + '@aws-sdk/types': 3.369.0 + '@smithy/is-array-buffer': 1.0.2 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-host-header/3.369.0: + resolution: {integrity: sha512-ysbur68WHY7RYpGfth1Iu0+S03nSCLtIHJ+CDVYcVcyvYxaAv6y3gvfrkH9oL220uX75UVLj3tCKgAaLUBy5uA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-location-constraint/3.369.0: + resolution: {integrity: sha512-zv9n9KjThMdcyDNxeR5PI+14HZCuOteUQYrAahBUsSwlZUF5PfscVWJVoZJHqWXduhPb5SIOZC0NJndfc3Jtfw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-logger/3.369.0: + resolution: {integrity: sha512-mp4gVRaFRRX+LEDEIlPxHOI/+k1jPPp0tuKyoyNZQS8IPOL+6bqFdPan03hkTjujeyaZOyRjpaXXat6k1HkHhw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-recursion-detection/3.369.0: + resolution: {integrity: sha512-V7TNhHRTwiKlVXiaW2CYGcm3vObWdG5zU0SN7ZxHDT27eTRYL8ncVpDnQZ65HfekXL8T9llVibBTYYvZrxLJ1g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-sdk-s3/3.369.0: + resolution: {integrity: sha512-hiZmGmsGiZXk2oKbgAUdnslPokpJWua/y6VD0XHv/yB1EOg2xhBLSzLRp/BpgoUjj+nEpk4wf4mxJyM35nvFeQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-sdk-sts/3.369.0: + resolution: {integrity: sha512-Igizyt7TWy8kTitvE6o7R1Cfa4qLqijS/WxqT1cnHscQyZFFiIJVNypWeV4V19DZ9Msb/feAQdc8EWgHvZvYGA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-signing/3.369.0: + resolution: {integrity: sha512-55qihn+9/zjsHUNvEgc4OUWQBxVlKW9C+whVhdy8H8olwAnfOH1ui9xXQ+SAyBCD9ck3vAY89VmBeQQQGZVVQw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/property-provider': 1.0.2 + '@smithy/protocol-http': 1.1.1 + '@smithy/signature-v4': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-middleware': 1.0.2 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-ssec/3.369.0: + resolution: {integrity: sha512-neQeE7Z7gBvTRaK6PG6TZysW3ZiE/mMipNHLcHat2Dap2YO7Dcdzyge2MLwNQNL0d/34dpmV8ohMUw5SqnDoLw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/middleware-user-agent/3.369.0: + resolution: {integrity: sha512-a7Wb3s0y+blGF654GZv3nI3ZMRARAGH7iQrF2gWGtb2Qq0f3TQGHmpoHddWObYxiFWYzdXdTC3kbsAW1zRwEAA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + '@aws-sdk/util-endpoints': 3.369.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/signature-v4-multi-region/3.369.0: + resolution: {integrity: sha512-OodVH5mFcwpZxv0RC4fx7a0G6Pi6R73fA4bDgjmZHq+UOQs9ZaodAydZRKupvDpZhjAk/a4+CgSNIRsWfC6V1Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/signature-v4-crt': ^3.118.0 + peerDependenciesMeta: + '@aws-sdk/signature-v4-crt': + optional: true + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/signature-v4': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/token-providers/3.369.0: + resolution: {integrity: sha512-xIz8KbF4RMlMq0aAJbVocLB03OiqJIU5RLy+2t+bKMQ60fV4bnVINH5GxAMiFXiBIQVqfehFJlxJACtEphqQwA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso-oidc': 3.369.0 + '@aws-sdk/types': 3.369.0 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: true + + /@aws-sdk/types/3.369.0: + resolution: {integrity: sha512-0LgII+RatF2OEFaFQcNyX72py4ZgWz+/JAv++PXv0gkIaTRnsJbSveQArNynEK+aAc/rZKWJgBvwT4FvLM2vgA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-arn-parser/3.310.0: + resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-base64/3.310.0: + resolution: {integrity: sha512-v3+HBKQvqgdzcbL+pFswlx5HQsd9L6ZTlyPVL2LS9nNXnCcR3XgGz9jRskikRUuUvUXtkSG1J88GAOnJ/apTPg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.310.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-buffer-from/3.310.0: + resolution: {integrity: sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/is-array-buffer': 3.310.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-endpoints/3.369.0: + resolution: {integrity: sha512-dkzhhMIvQRsgdomHi8fmgQ3df2cS1jeWAUIPjxV4lBikcvcF2U0CtvH9QYyMpluSNP1IYcEuONe8wfZGSrNjdg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.369.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-locate-window/3.310.0: + resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-user-agent-browser/3.369.0: + resolution: {integrity: sha512-wrF0CqnfFac4sYr8jLZXz7B5NPxdW4GettH07Sl3ihO2aXsTvZ0RoyqzwF7Eve8ihbK0vCKt1S3/vZTOLw8sCg==} + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/types': 1.1.1 + bowser: 2.11.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-user-agent-node/3.369.0: + resolution: {integrity: sha512-RkiGyWp+YUlK4njsvqD7S08aihEW8aMNrT5OXmLGdukEUGWMAyvIcq4XS8MxA02GRPUxTUNInLltXwc1AaDpCw==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.369.0 + '@smithy/node-config-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-utf8-browser/3.259.0: + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + dependencies: + tslib: 2.6.0 + dev: true + + /@aws-sdk/util-utf8/3.310.0: + resolution: {integrity: sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/util-buffer-from': 3.310.0 + tslib: 2.6.0 + dev: true + + /@aws-sdk/xml-builder/3.310.0: + resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + /@babel/runtime/7.20.6: resolution: {integrity: sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==} engines: {node: '>=6.9.0'} @@ -422,8 +1059,22 @@ packages: resolution: {integrity: sha512-S9RulC2fFCFOQraz61bij+5YCHhSO9llJegK8c8Y6731fSi6snUSQJdCUqYS8AIgR0TKbQvdvgSyIIdbDFZbBA==} dev: true - /@metaplex-foundation/js/0.17.11: - resolution: {integrity: sha512-33H49nM6IpgXXHg6Akl38OYHE1bfTtC3P3kvF6orA1F96/MJe11ThAkT8ekfk4n3P+Zj4NuN2tBlqO5yHy3FZQ==} + /@metaplex-foundation/js-plugin-aws/0.18.3: + resolution: {integrity: sha512-z1vIMNW8YVGqfjfVP3nqlzxbIlNXrnRrZP67Xpb9nvzoZ+AWtq9akusX/eCnqpQkqQcXFoVBlrzIe5G4M5velA==} + dependencies: + '@aws-sdk/client-s3': 3.369.0 + '@metaplex-foundation/js': 0.18.3 + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - aws-crt + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: true + + /@metaplex-foundation/js/0.18.3: + resolution: {integrity: sha512-rqI8vI+V5Bt3pgrv8E7leqR8gxxdw6Q/pbWg4EznbuYSmpNGRQkjMaZE0C+rQrmtQbMqUD9rUsUuYOoppSlI4A==} dependencies: '@bundlr-network/client': 0.8.9_debug@4.3.4 '@metaplex-foundation/beet': 0.7.1 @@ -431,7 +1082,7 @@ packages: '@metaplex-foundation/mpl-candy-guard': 0.3.0 '@metaplex-foundation/mpl-candy-machine': 5.0.0 '@metaplex-foundation/mpl-candy-machine-core': 0.1.2 - '@metaplex-foundation/mpl-token-metadata': 2.5.2 + '@metaplex-foundation/mpl-token-metadata': 2.12.0 '@noble/ed25519': 1.7.1 '@noble/hashes': 1.1.4 '@solana/spl-token': 0.3.6_@solana+web3.js@1.68.0 @@ -515,8 +1166,8 @@ packages: - utf-8-validate dev: true - /@metaplex-foundation/mpl-token-metadata/2.5.2: - resolution: {integrity: sha512-lAjQjj2gGtyLq8MOkp4tWZSC5DK9NWgPd3EoH0KQ9gMs3sKIJRik0CBaZg+JA0uLwzkiErY2Izus4vbWtRADJQ==} + /@metaplex-foundation/mpl-token-metadata/2.12.0: + resolution: {integrity: sha512-DetC2F5MwMRt4TmLXwj8PJ8nClRYGMecSQ4pr9iKKa+rWertHgKoJHl2XhheRa084GtL7i0ssOKbX2gfYFosuQ==} dependencies: '@metaplex-foundation/beet': 0.7.1 '@metaplex-foundation/beet-solana': 0.4.0 @@ -580,16 +1231,405 @@ packages: engines: {node: '>=14.16'} dev: true - /@solana-mobile/dapp-store-cli/0.4.2: - resolution: {integrity: sha512-Ym176ncRoLH7DOZtnNDDKYYk/mbRy+eRiSlohgJT7ihasD+3mHg+5u8RoTt0WR/E5eHqYoUCnHGrRcgbvjMW/Q==} + /@smithy/abort-controller/1.0.2: + resolution: {integrity: sha512-tb2h0b+JvMee+eAxTmhnyqyNk51UXIK949HnE14lFeezKsVJTB30maan+CO2IMwnig2wVYQH84B5qk6ylmKCuA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/config-resolver/1.0.2: + resolution: {integrity: sha512-8Bk7CgnVKg1dn5TgnjwPz2ebhxeR7CjGs5yhVYH3S8x0q8yPZZVWwpRIglwXaf5AZBzJlNO1lh+lUhMf2e73zQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-config-provider': 1.0.2 + '@smithy/util-middleware': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/credential-provider-imds/1.0.2: + resolution: {integrity: sha512-fLjCya+JOu2gPJpCiwSUyoLvT8JdNJmOaTOkKYBZoGf7CzqR6lluSyI+eboZnl/V0xqcfcqBG4tgqCISmWS3/w==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/eventstream-codec/1.0.2: + resolution: {integrity: sha512-eW/XPiLauR1VAgHKxhVvgvHzLROUgTtqat2lgljztbH8uIYWugv7Nz+SgCavB+hWRazv2iYgqrSy74GvxXq/rg==} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@smithy/types': 1.1.1 + '@smithy/util-hex-encoding': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/eventstream-serde-browser/1.0.2: + resolution: {integrity: sha512-8bDImzBewLQrIF6hqxMz3eoYwEus2E5JrEwKnhpkSFkkoj8fDSKiLeP/26xfcaoVJgZXB8M1c6jSEZiY3cUMsw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-serde-universal': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/eventstream-serde-config-resolver/1.0.2: + resolution: {integrity: sha512-SeiJ5pfrXzkGP4WCt9V3Pimfr3OM85Nyh9u/V4J6E0O2dLOYuqvSuKdVnktV0Tcmuu1ZYbt78Th0vfetnSEcdQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/eventstream-serde-node/1.0.2: + resolution: {integrity: sha512-jqSfi7bpOBHqgd5OgUtCX0wAVhPqxlVdqcj2c4gHaRRXcbpCmK0DRDg7P+Df0h4JJVvTqI6dy2c0YhHk5ehPCw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-serde-universal': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/eventstream-serde-universal/1.0.2: + resolution: {integrity: sha512-cQ9bT0j0x49cp8TQ1yZSnn4+9qU0WQSTkoucl3jKRoTZMzNYHg62LQao6HTQ3Jgd77nAXo00c7hqUEjHXwNA+A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/fetch-http-handler/1.0.2: + resolution: {integrity: sha512-kynyofLf62LvR8yYphPPdyHb8fWG3LepFinM/vWUTG2Q1pVpmPCM530ppagp3+q2p+7Ox0UvSqldbKqV/d1BpA==} + dependencies: + '@smithy/protocol-http': 1.1.1 + '@smithy/querystring-builder': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-base64': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/hash-node/1.0.2: + resolution: {integrity: sha512-K6PKhcUNrJXtcesyzhIvNlU7drfIU7u+EMQuGmPw6RQDAg/ufUcfKHz4EcUhFAodUmN+rrejhRG9U6wxjeBOQA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-buffer-from': 1.0.2 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/invalid-dependency/1.0.2: + resolution: {integrity: sha512-B1Y3Tsa6dfC+Vvb+BJMhTHOfFieeYzY9jWQSTR1vMwKkxsymD0OIAnEw8rD/RiDj/4E4RPGFdx9Mdgnyd6Bv5Q==} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/is-array-buffer/1.0.2: + resolution: {integrity: sha512-pkyBnsBRpe+c/6ASavqIMRBdRtZNJEVJOEzhpxZ9JoAXiZYbkfaSMRA/O1dUxGdJ653GHONunnZ4xMo/LJ7utQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/middleware-content-length/1.0.2: + resolution: {integrity: sha512-pa1/SgGIrSmnEr2c9Apw7CdU4l/HW0fK3+LKFCPDYJrzM0JdYpqjQzgxi31P00eAkL0EFBccpus/p1n2GF9urw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/middleware-endpoint/1.0.3: + resolution: {integrity: sha512-GsWvTXMFjSgl617PCE2km//kIjjtvMRrR2GAuRDIS9sHiLwmkS46VWaVYy+XE7ubEsEtzZ5yK2e8TKDR6Qr5Lw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-serde': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-middleware': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/middleware-retry/1.0.4: + resolution: {integrity: sha512-G7uRXGFL8c3F7APnoIMTtNAHH8vT4F2qVnAWGAZaervjupaUQuRRHYBLYubK0dWzOZz86BtAXKieJ5p+Ni2Xpg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/protocol-http': 1.1.1 + '@smithy/service-error-classification': 1.0.3 + '@smithy/types': 1.1.1 + '@smithy/util-middleware': 1.0.2 + '@smithy/util-retry': 1.0.4 + tslib: 2.6.0 + uuid: 8.3.2 + dev: true + + /@smithy/middleware-serde/1.0.2: + resolution: {integrity: sha512-T4PcdMZF4xme6koUNfjmSZ1MLi7eoFeYCtodQNQpBNsS77TuJt1A6kt5kP/qxrTvfZHyFlj0AubACoaUqgzPeg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/middleware-stack/1.0.2: + resolution: {integrity: sha512-H7/uAQEcmO+eDqweEFMJ5YrIpsBwmrXSP6HIIbtxKJSQpAcMGY7KrR2FZgZBi1FMnSUOh+rQrbOyj5HQmSeUBA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/node-config-provider/1.0.2: + resolution: {integrity: sha512-HU7afWpTToU0wL6KseGDR2zojeyjECQfr8LpjAIeHCYIW7r360ABFf4EaplaJRMVoC3hD9FeltgI3/NtShOqCg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/node-http-handler/1.0.3: + resolution: {integrity: sha512-PcPUSzTbIb60VCJCiH0PU0E6bwIekttsIEf5Aoo/M0oTfiqsxHTn0Rcij6QoH6qJy6piGKXzLSegspXg5+Kq6g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 1.0.2 + '@smithy/protocol-http': 1.1.1 + '@smithy/querystring-builder': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/property-provider/1.0.2: + resolution: {integrity: sha512-pXDPyzKX8opzt38B205kDgaxda6LHcTfPvTYQZnwP6BAPp1o9puiCPjeUtkKck7Z6IbpXCPUmUQnzkUzWTA42Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/protocol-http/1.1.1: + resolution: {integrity: sha512-mFLFa2sSvlUxm55U7B4YCIsJJIMkA6lHxwwqOaBkral1qxFz97rGffP/mmd4JDuin1EnygiO5eNJGgudiUgmDQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/querystring-builder/1.0.2: + resolution: {integrity: sha512-6P/xANWrtJhMzTPUR87AbXwSBuz1SDHIfL44TFd/GT3hj6rA+IEv7rftEpPjayUiWRocaNnrCPLvmP31mobOyA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-uri-escape': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/querystring-parser/1.0.2: + resolution: {integrity: sha512-IWxwxjn+KHWRRRB+K2Ngl+plTwo2WSgc2w+DvLy0DQZJh9UGOpw40d6q97/63GBlXIt4TEt5NbcFrO30CKlrsA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/service-error-classification/1.0.3: + resolution: {integrity: sha512-2eglIYqrtcUnuI71yweu7rSfCgt6kVvRVf0C72VUqrd0LrV1M0BM0eYN+nitp2CHPSdmMI96pi+dU9U/UqAMSA==} + engines: {node: '>=14.0.0'} + dev: true + + /@smithy/shared-ini-file-loader/1.0.2: + resolution: {integrity: sha512-bdQj95VN+lCXki+P3EsDyrkpeLn8xDYiOISBGnUG/AGPYJXN8dmp4EhRRR7XOoLoSs8anZHR4UcGEOzFv2jwGw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/signature-v4/1.0.2: + resolution: {integrity: sha512-rpKUhmCuPmpV5dloUkOb9w1oBnJatvKQEjIHGmkjRGZnC3437MTdzWej9TxkagcZ8NRRJavYnEUixzxM1amFig==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 1.0.2 + '@smithy/is-array-buffer': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-hex-encoding': 1.0.2 + '@smithy/util-middleware': 1.0.2 + '@smithy/util-uri-escape': 1.0.2 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/smithy-client/1.0.4: + resolution: {integrity: sha512-gpo0Xl5Nyp9sgymEfpt7oa9P2q/GlM3VmQIdm+FeH0QEdYOQx3OtvwVmBYAMv2FIPWxkMZlsPYRTnEiBTK5TYg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-stack': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-stream': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/types/1.1.0: + resolution: {integrity: sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/types/1.1.1: + resolution: {integrity: sha512-tMpkreknl2gRrniHeBtdgQwaOlo39df8RxSrwsHVNIGXULy5XP6KqgScUw2m12D15wnJCKWxVhCX+wbrBW/y7g==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/url-parser/1.0.2: + resolution: {integrity: sha512-0JRsDMQe53F6EHRWksdcavKDRjyqp8vrjakg8EcCUOa7PaFRRB1SO/xGZdzSlW1RSTWQDEksFMTCEcVEKmAoqA==} + dependencies: + '@smithy/querystring-parser': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/util-base64/1.0.2: + resolution: {integrity: sha512-BCm15WILJ3SL93nusoxvJGMVfAMWHZhdeDZPtpAaskozuexd0eF6szdz4kbXaKp38bFCSenA6bkUHqaE3KK0dA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/util-body-length-browser/1.0.2: + resolution: {integrity: sha512-Xh8L06H2anF5BHjSYTg8hx+Itcbf4SQZnVMl4PIkCOsKtneMJoGjPRLy17lEzfoh/GOaa0QxgCP6lRMQWzNl4w==} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/util-body-length-node/1.0.2: + resolution: {integrity: sha512-nXHbZsUtvZeyfL4Ceds9nmy2Uh2AhWXohG4vWHyjSdmT8cXZlJdmJgnH6SJKDjyUecbu+BpKeVvSrA4cWPSOPA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/util-buffer-from/1.0.2: + resolution: {integrity: sha512-lHAYIyrBO9RANrPvccnPjU03MJnWZ66wWuC5GjWWQVfsmPwU6m00aakZkzHdUT6tGCkGacXSgArP5wgTgA+oCw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/is-array-buffer': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/util-config-provider/1.0.2: + resolution: {integrity: sha512-HOdmDm+3HUbuYPBABLLHtn8ittuRyy+BSjKOA169H+EMc+IozipvXDydf+gKBRAxUa4dtKQkLraypwppzi+PRw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/util-defaults-mode-browser/1.0.2: + resolution: {integrity: sha512-J1u2PO235zxY7dg0+ZqaG96tFg4ehJZ7isGK1pCBEA072qxNPwIpDzUVGnLJkHZvjWEGA8rxIauDtXfB0qxeAg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + bowser: 2.11.0 + tslib: 2.6.0 + dev: true + + /@smithy/util-defaults-mode-node/1.0.2: + resolution: {integrity: sha512-9/BN63rlIsFStvI+AvljMh873Xw6bbI6b19b+PVYXyycQ2DDQImWcjnzRlHW7eP65CCUNGQ6otDLNdBQCgMXqg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/config-resolver': 1.0.2 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@smithy/util-hex-encoding/1.0.2: + resolution: {integrity: sha512-Bxydb5rMJorMV6AuDDMOxro3BMDdIwtbQKHpwvQFASkmr52BnpDsWlxgpJi8Iq7nk1Bt4E40oE1Isy/7ubHGzg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/util-middleware/1.0.2: + resolution: {integrity: sha512-vtXK7GOR2BoseCX8NCGe9SaiZrm9M2lm/RVexFGyPuafTtry9Vyv7hq/vw8ifd/G/pSJ+msByfJVb1642oQHKw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/util-retry/1.0.4: + resolution: {integrity: sha512-RnZPVFvRoqdj2EbroDo3OsnnQU8eQ4AlnZTOGusbYKybH3269CFdrZfZJloe60AQjX7di3J6t/79PjwCLO5Khw==} + engines: {node: '>= 14.0.0'} + dependencies: + '@smithy/service-error-classification': 1.0.3 + tslib: 2.6.0 + dev: true + + /@smithy/util-stream/1.0.2: + resolution: {integrity: sha512-qyN2M9QFMTz4UCHi6GnBfLOGYKxQZD01Ga6nzaXFFC51HP/QmArU72e4kY50Z/EtW8binPxspP2TAsGbwy9l3A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/types': 1.1.1 + '@smithy/util-base64': 1.0.2 + '@smithy/util-buffer-from': 1.0.2 + '@smithy/util-hex-encoding': 1.0.2 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/util-uri-escape/1.0.2: + resolution: {integrity: sha512-k8C0BFNS9HpBMHSgUDnWb1JlCQcFG+PPlVBq9keP4Nfwv6a9Q0yAfASWqUCtzjuMj1hXeLhn/5ADP6JxnID1Pg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: true + + /@smithy/util-utf8/1.0.2: + resolution: {integrity: sha512-V4cyjKfJlARui0dMBfWJMQAmJzoW77i4N3EjkH/bwnE2Ngbl4tqD2Y0C/xzpzY/J1BdxeCKxAebVFk8aFCaSCw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 1.0.2 + tslib: 2.6.0 + dev: true + + /@smithy/util-waiter/1.0.2: + resolution: {integrity: sha512-+jq4/Vd9ejPzR45qwYSePyjQbqYP9QqtyZYsFVyfzRnbGGC0AjswOh7txcxroafuEBExK4qE+L/QZA8wWXsJYw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: true + + /@solana-mobile/dapp-store-cli/0.5.0: + resolution: {integrity: sha512-mgttIWG1BNBFNUvOx/i1fsVZvk55uKeWsh+3NLkPap4I+OdPv0TFbeJ2gGNS4rLjEbs0u6gn3p05v5RDklNI/A==} engines: {node: '>=18'} hasBin: true dependencies: - '@solana-mobile/dapp-store-publishing-tools': 0.4.2 + '@aws-sdk/client-s3': 3.369.0 + '@metaplex-foundation/js-plugin-aws': 0.18.3 + '@solana-mobile/dapp-store-publishing-tools': 0.5.0 '@solana/web3.js': 1.68.0 '@types/semver': 7.3.13 ajv: 8.11.2 boxen: 7.0.2 + chokidar: 3.5.3 commander: 9.4.1 debug: 4.3.4 dotenv: 16.0.3 @@ -602,20 +1642,23 @@ packages: tweetnacl: 1.0.3 update-notifier: 6.0.2 transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - aws-crt - bufferutil - encoding - supports-color - utf-8-validate dev: true - /@solana-mobile/dapp-store-publishing-tools/0.4.2: - resolution: {integrity: sha512-E+gifg82mbwj/JimijTxspjlRAx0ezeno0Hv72WOT5a/gq7E+YTFg/iEulioWtdSDD3ZOjnug0l9mEouE5sIHw==} + /@solana-mobile/dapp-store-publishing-tools/0.5.0: + resolution: {integrity: sha512-YhZs9MSi0jFjsRLSPr6Z5PLrr/XqDOTbHFiG6PPKafaZ6Vyhj0YNDGr4RTZPFTfpD55XLS7RVSeAcGP9Pixbgg==} engines: {node: '>=18'} dependencies: - '@metaplex-foundation/js': 0.17.11 + '@metaplex-foundation/js': 0.18.3 '@solana/web3.js': 1.68.0 ajv: 8.11.2 axios: 1.1.3_debug@4.3.4 + chokidar: 3.5.3 debug: 4.3.4 image-size: 1.0.2 mime: 3.0.0 @@ -850,6 +1893,14 @@ packages: resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} dev: true + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + /arbundles/0.6.22_czmx2g7h6asgihnegvx7crnrsm: resolution: {integrity: sha512-QlSavBHk59mNqgQ6ScxlqaBJlDbSmSrK/uTcF3HojLAZ/4aufTkVTBjl1hSfZ/ZN45oIPgJC05R8SmVARF+8VA==} dependencies: @@ -1010,6 +2061,11 @@ packages: resolution: {integrity: sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==} dev: true + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + /bindings/1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} dependencies: @@ -1076,6 +2132,10 @@ packages: text-encoding-utf-8: 1.0.2 dev: true + /bowser/2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: true + /boxen/7.0.2: resolution: {integrity: sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==} engines: {node: '>=14.16'} @@ -1097,6 +2157,13 @@ packages: concat-map: 0.0.1 dev: true + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + /brorand/1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} dev: true @@ -1220,6 +2287,21 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /ci-info/3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -1619,6 +2701,13 @@ packages: resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} dev: true + /fast-xml-parser/4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: true + /figures/3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -1630,6 +2719,13 @@ packages: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: true + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + /follow-redirects/1.15.2_debug@4.3.4: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -1666,6 +2762,14 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /function-bind/1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true @@ -1691,6 +2795,13 @@ packages: engines: {node: '>=10'} dev: true + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob/7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -1894,6 +3005,13 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + /is-callable/1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -1906,6 +3024,11 @@ packages: ci-info: 3.8.0 dev: true + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1918,6 +3041,13 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + /is-hex-prefixed/1.0.0: resolution: {integrity: sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==} engines: {node: '>=6.5.0', npm: '>=3'} @@ -1941,6 +3071,11 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + /is-obj/2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -2250,6 +3385,11 @@ packages: hasBin: true dev: true + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + /normalize-url/8.0.0: resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} engines: {node: '>=14.16'} @@ -2333,6 +3473,11 @@ packages: sha.js: 2.4.11 dev: true + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + /process/0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -2394,6 +3539,13 @@ packages: util-deprecate: 1.0.2 dev: true + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + /regenerator-runtime/0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: true @@ -2482,7 +3634,7 @@ packages: /rxjs/7.6.0: resolution: {integrity: sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ==} dependencies: - tslib: 2.4.1 + tslib: 2.6.0 dev: true /safe-buffer/5.2.1: @@ -2601,6 +3753,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /strnum/1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: true + /superstruct/0.14.2: resolution: {integrity: sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==} dev: true @@ -2656,6 +3812,13 @@ packages: rimraf: 3.0.2 dev: true + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + /toidentifier/1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -2670,8 +3833,12 @@ packages: engines: {node: '>=0.6'} dev: true - /tslib/2.4.1: - resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib/2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} dev: true /tweetnacl/1.0.3: diff --git a/packages/mobile/package-lock.json b/packages/mobile/package-lock.json index fd3da089ae..5466f5be1c 100644 --- a/packages/mobile/package-lock.json +++ b/packages/mobile/package-lock.json @@ -73,9 +73,9 @@ } }, "@audius/sdk": { - "version": "3.0.3-beta.63", - "resolved": "https://registry.npmjs.org/@audius/sdk/-/sdk-3.0.3-beta.63.tgz", - "integrity": "sha512-yiUjzV/xOZe9N1a+P0ApD3mpzeZcaVQ+vG55qu7Ic045MX87IrKCatHGU1tZMK71fe3P4ZpktTo6SeGs6UCKlQ==", + "version": "3.0.3-beta.76", + "resolved": "https://registry.npmjs.org/@audius/sdk/-/sdk-3.0.3-beta.76.tgz", + "integrity": "sha512-zhH7ZMcVsMVhDTRNxwWtgXOR69E7GVq09bUnDd7m/Xh/dsL4vg0eZaULTlPCNi1b3VGDZs0KFyVzR16Pyt9ufA==", "requires": { "@audius/hedgehog": "2.1.0", "@babel/runtime": "7.18.3", @@ -9740,70 +9740,6 @@ "use-deep-compare-effect": "1.6.1" }, "dependencies": { - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } - }, "react-native-qrcode-svg": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/react-native-qrcode-svg/-/react-native-qrcode-svg-6.0.6.tgz", @@ -9814,18 +9750,9 @@ } }, "react-native-svg": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.3.0.tgz", - "integrity": "sha512-ESG1g1j7/WLD7X3XRFTQHVv0r6DpbHNNcdusngAODIxG88wpTWUZkhcM3A2HJTb+BbXTFDamHv7FwtRKWQ/ALg==", - "requires": { - "css-select": "^4.2.1", - "css-tree": "^1.0.0-alpha.39" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "version": "9.6.4", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-9.6.4.tgz", + "integrity": "sha512-6SlbGx0vlXHyDPQXSpX+8o6bNjxKFNJsISoboAkR7YWW6hdnkMg/HJXCgT6oJC0/ClKtSO7ZPrQcK4HR65kDNg==" } } }, @@ -9951,7 +9878,7 @@ "absolute-path": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==" + "integrity": "sha1-p4di+9rftSl76ZsV01p4Wy8JW/c=" }, "accepts": { "version": "1.3.8", @@ -12713,7 +12640,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, "coa": { @@ -12810,7 +12737,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, "compare-versions": { "version": "3.6.0", @@ -12847,7 +12774,7 @@ "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "debug": { "version": "2.6.9", @@ -12860,7 +12787,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -12926,7 +12853,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -12938,7 +12865,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "constants-browserify": { "version": "1.0.0", @@ -13445,7 +13372,7 @@ "dedent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.6.0.tgz", - "integrity": "sha512-cSfRWjXJtZQeRuZGVvDrJroCR5V2UvBNUMHsPCdNYzuAG8b9V8aAy3KUcdQrGQPXs17Y+ojbPh1aOCplg9YR9g==" + "integrity": "sha1-Dm2o8M5Sg471zsXI+TlrDBtko8s=" }, "deep-extend": { "version": "0.6.0", @@ -13559,12 +13486,12 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denodeify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==" + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=" }, "depd": { "version": "2.0.0", @@ -15362,7 +15289,7 @@ "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, "expand-brackets": { @@ -16300,7 +16227,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "on-finished": { "version": "2.3.0", @@ -16814,7 +16741,7 @@ "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -16829,12 +16756,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } @@ -17464,7 +17391,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "has-value": { "version": "1.0.0", @@ -17729,7 +17656,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "immer": { "version": "9.0.7", @@ -19892,7 +19819,7 @@ "keymirror": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz", - "integrity": "sha512-vIkZAFWoDijgQT/Nvl2AHCMmnegN2ehgTPYuyy2hWQkQSntI0S7ESYqdLkoSe1HyEBFHHkCgSIvVdSEiWwKvCg==" + "integrity": "sha1-kYiJ6hP40KQufFVyUO7nE63JXDU=" }, "keyv": { "version": "3.1.0", @@ -19986,7 +19913,7 @@ "lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", "requires": { "immediate": "~3.0.5" } @@ -20103,7 +20030,7 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, "lodash.isequal": { "version": "4.5.0", @@ -20161,7 +20088,7 @@ "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, "lodash.tostring": { "version": "4.1.4", @@ -22412,7 +22339,7 @@ "murmurhash": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-0.0.2.tgz", - "integrity": "sha512-LKlwdZKWzvCQpMszb2HO5leJ7P9T4m5XuDKku8bM0uElrzqK9cn0+iozwQS8jO4SNjrp4w7olalgd8WgsIjhWA==" + "integrity": "sha1-bwe9ihEF5wnCb8iUIMtZMMJFhf4=" }, "murmurhash3js-revisited": { "version": "3.0.0", @@ -22587,7 +22514,7 @@ "node-dir": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=", "requires": { "minimatch": "^3.0.2" } @@ -24415,9 +24342,9 @@ }, "dependencies": { "@types/node": { - "version": "20.3.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz", - "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==" + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==" } } }, @@ -26614,7 +26541,7 @@ "readline": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" + "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, "realpath-native": { "version": "1.1.0", @@ -27412,7 +27339,7 @@ "serialize-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==" + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" }, "serve-static": { "version": "1.15.0", @@ -28760,7 +28687,7 @@ "temp": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", "requires": { "os-tmpdir": "^1.0.0", "rimraf": "~2.2.6" @@ -28769,7 +28696,7 @@ "rimraf": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==" + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" } } }, @@ -29707,7 +29634,7 @@ "wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "requires": { "defaults": "^1.0.3" } diff --git a/packages/mobile/package.json b/packages/mobile/package.json index a1d80a3783..af5f592084 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -45,7 +45,7 @@ "dependencies": { "@amplitude/react-native": "2.6.0", "@audius/common": "1.5.31", - "@audius/sdk": "3.0.3-beta.63", + "@audius/sdk": "3.0.3-beta.76", "@fingerprintjs/fingerprintjs-pro-react-native": "2.0.0-test.2", "@gorhom/portal": "1.0.9", "@hcaptcha/react-native-hcaptcha": "1.3.4", diff --git a/packages/mobile/src/components/audio/Audio.tsx b/packages/mobile/src/components/audio/Audio.tsx index 0db2259308..dc8c5e42ec 100644 --- a/packages/mobile/src/components/audio/Audio.tsx +++ b/packages/mobile/src/components/audio/Audio.tsx @@ -614,7 +614,6 @@ export const Audio = () => { ? getImageSourceOptimistic({ cid, endpoints: storageNodeSelector.getNodes(cid), - user: trackOwner, size: SquareSizes.SIZE_1000_BY_1000, localSource: localTrackImageSource })?.uri ?? DEFAULT_IMAGE_URL diff --git a/packages/mobile/src/components/audio/GoogleCast.tsx b/packages/mobile/src/components/audio/GoogleCast.tsx index db58563177..02309b9e0e 100644 --- a/packages/mobile/src/components/audio/GoogleCast.tsx +++ b/packages/mobile/src/components/audio/GoogleCast.tsx @@ -54,14 +54,9 @@ export const useChromecast = () => { const loadCast = useCallback( async (track, startTime) => { if (client && track && owner && streamingUri) { - const gateways = audiusBackendInstance.getCreatorNodeIPFSGateways( - owner.creator_node_endpoint - ) - const imageUrl = await audiusBackendInstance.getImageUrl( track.cover_art_sizes, - SquareSizes.SIZE_1000_BY_1000, - gateways + SquareSizes.SIZE_1000_BY_1000 ) client.loadMedia({ diff --git a/packages/mobile/src/components/image/CollectionImage.tsx b/packages/mobile/src/components/image/CollectionImage.tsx index 2881ac03c0..d26f8dad22 100644 --- a/packages/mobile/src/components/image/CollectionImage.tsx +++ b/packages/mobile/src/components/image/CollectionImage.tsx @@ -3,10 +3,9 @@ import type { ID, Maybe, Nullable, - SquareSizes, - User + SquareSizes } from '@audius/common' -import { reachabilitySelectors, cacheUsersSelectors } from '@audius/common' +import { reachabilitySelectors } from '@audius/common' import { useSelector } from 'react-redux' import imageEmpty from 'app/assets/images/imageBlank2x.png' @@ -20,7 +19,6 @@ import type { FastImageProps } from './FastImage' import { FastImage } from './FastImage' const { getIsReachable } = reachabilitySelectors -const { getUser } = cacheUsersSelectors type UseCollectionImageOptions = { collection: Nullable< @@ -30,7 +28,6 @@ type UseCollectionImageOptions = { > > size: SquareSizes - user?: Pick } const useLocalCollectionImageUri = (collectionId: Maybe) => { @@ -56,15 +53,11 @@ const useLocalCollectionImageUri = (collectionId: Maybe) => { } export const useCollectionImage = (options: UseCollectionImageOptions) => { - const { collection, size, user } = options + const { collection, size } = options const cid = collection ? collection.cover_art_sizes || collection.cover_art : null - const selectedUser = useSelector((state) => - getUser(state, { id: collection?.playlist_owner_id }) - ) - const localCollectionImageUri = useLocalCollectionImageUri( collection?.playlist_id ) @@ -72,7 +65,6 @@ export const useCollectionImage = (options: UseCollectionImageOptions) => { const contentNodeSource = useContentNodeImage({ cid, size, - user: selectedUser ?? user ?? null, fallbackImageSource: imageEmpty, localSource: localCollectionImageUri ? { uri: localCollectionImageUri } @@ -85,9 +77,9 @@ export const useCollectionImage = (options: UseCollectionImageOptions) => { type CollectionImageProps = UseCollectionImageOptions & Partial export const CollectionImage = (props: CollectionImageProps) => { - const { collection, size, user, style, ...other } = props + const { collection, size, style, ...other } = props - const collectionImageSource = useCollectionImage({ collection, size, user }) + const collectionImageSource = useCollectionImage({ collection, size }) const { neutralLight6 } = useThemeColors() if (!collectionImageSource) return null diff --git a/packages/mobile/src/components/image/TrackImage.tsx b/packages/mobile/src/components/image/TrackImage.tsx index ee71430a79..0b3a8af444 100644 --- a/packages/mobile/src/components/image/TrackImage.tsx +++ b/packages/mobile/src/components/image/TrackImage.tsx @@ -1,12 +1,5 @@ -import type { - User, - Track, - Nullable, - SquareSizes, - ID, - Maybe -} from '@audius/common' -import { reachabilitySelectors, cacheUsersSelectors } from '@audius/common' +import type { Track, Nullable, SquareSizes, ID, Maybe } from '@audius/common' +import { reachabilitySelectors } from '@audius/common' import { useSelector } from 'react-redux' import imageEmpty from 'app/assets/images/imageBlank2x.png' @@ -23,14 +16,10 @@ export const DEFAULT_IMAGE_URL = 'https://download.audius.co/static-resources/preview-image.jpg' const { getIsReachable } = reachabilitySelectors -const { getUser } = cacheUsersSelectors type UseTrackImageOptions = { - track: Nullable< - Pick - > + track: Nullable> size: SquareSizes - user?: Pick } const useLocalTrackImageUri = (trackId: Maybe) => { @@ -50,19 +39,14 @@ const useLocalTrackImageUri = (trackId: Maybe) => { return trackImageUri } -export const useTrackImage = ({ track, size, user }: UseTrackImageOptions) => { +export const useTrackImage = ({ track, size }: UseTrackImageOptions) => { const cid = track ? track.cover_art_sizes || track.cover_art : null - const selectedUser = useSelector((state) => - getUser(state, { id: track?.owner_id }) - ) - const localTrackImageUri = useLocalTrackImageUri(track?.track_id) const contentNodeSource = useContentNodeImage({ cid, size, - user: user ?? selectedUser, fallbackImageSource: imageEmpty, localSource: localTrackImageUri ? { uri: localTrackImageUri } : null }) @@ -73,9 +57,9 @@ export const useTrackImage = ({ track, size, user }: UseTrackImageOptions) => { type TrackImageProps = UseTrackImageOptions & Partial export const TrackImage = (props: TrackImageProps) => { - const { track, size, user, style, ...other } = props + const { track, size, style, ...other } = props - const trackImageSource = useTrackImage({ track, size, user }) + const trackImageSource = useTrackImage({ track, size }) const { neutralLight8 } = useThemeColors() if (!trackImageSource) return null diff --git a/packages/mobile/src/components/image/UserCoverImage.tsx b/packages/mobile/src/components/image/UserCoverImage.tsx index 60aec66902..68b4911fdb 100644 --- a/packages/mobile/src/components/image/UserCoverImage.tsx +++ b/packages/mobile/src/components/image/UserCoverImage.tsx @@ -25,13 +25,7 @@ const interpolateImageTranslate = (animatedValue: Animated.Value) => }) type CoverImageUser = Nullable< - Pick< - User, - | 'cover_photo_sizes' - | 'cover_photo' - | 'creator_node_endpoint' - | 'updatedCoverPhoto' - > + Pick > export const useUserCoverImage = (user: CoverImageUser) => { @@ -39,7 +33,6 @@ export const useUserCoverImage = (user: CoverImageUser) => { const contentNodeImage = useContentNodeImage({ cid, - user, size: WidthSizes.SIZE_640, fallbackImageSource: imageCoverPhotoBlank }) diff --git a/packages/mobile/src/components/image/UserImage.tsx b/packages/mobile/src/components/image/UserImage.tsx index d072a83683..81c2cac11f 100644 --- a/packages/mobile/src/components/image/UserImage.tsx +++ b/packages/mobile/src/components/image/UserImage.tsx @@ -10,10 +10,7 @@ type UseUserImageOptions = { user: Nullable< Pick< User, - | 'profile_picture_sizes' - | 'profile_picture' - | 'creator_node_endpoint' - | 'updatedProfilePicture' + 'profile_picture_sizes' | 'profile_picture' | 'updatedProfilePicture' > > size: SquareSizes @@ -25,7 +22,6 @@ export const useUserImage = ({ user, size }: UseUserImageOptions) => { const contentNodeImage = useContentNodeImage({ cid, size, - user, fallbackImageSource: profilePicEmpty }) diff --git a/packages/mobile/src/components/image/utils.ts b/packages/mobile/src/components/image/utils.ts deleted file mode 100644 index bcea888a21..0000000000 --- a/packages/mobile/src/components/image/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Config from 'react-native-config' - -export const gateways = [`${Config.USER_NODE}`, `${Config.LEGACY_USER_NODE}`] diff --git a/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx b/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx index 4dfa8d2e51..7c4f5973db 100644 --- a/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx +++ b/packages/mobile/src/components/inbox-unavailable-drawer/InboxUnavailableDrawer.tsx @@ -6,14 +6,15 @@ import { chatActions, tippingActions, cacheUsersSelectors, - ChatPermissionAction + ChatPermissionAction, + CHAT_BLOG_POST_URL } from '@audius/common' import { View } from 'react-native' import { useDispatch, useSelector } from 'react-redux' import IconMessageLocked from 'app/assets/images/iconMessageLocked.svg' import IconTip from 'app/assets/images/iconTip.svg' -import { Text, Button } from 'app/components/core' +import { Text, Button, useLink } from 'app/components/core' import { NativeDrawer } from 'app/components/drawer' import { useDrawer } from 'app/hooks/useDrawer' import { useNavigation } from 'app/hooks/useNavigation' @@ -132,10 +133,11 @@ const DrawerContent = ({ data }: DrawerContentProps) => { closeDrawer() }, [dispatch, userId, shouldOpenChat, canCreateChat, closeDrawer]) + const { onPress: onPressLearnMore } = useLink(CHAT_BLOG_POST_URL) const handleLearnMorePress = useCallback(() => { - // TODO: Link to blog + onPressLearnMore() closeDrawer() - }, [closeDrawer]) + }, [closeDrawer, onPressLearnMore]) const handleTipPress = useCallback(() => { dispatch(beginTip({ user, source: 'inboxUnavailableModal' })) diff --git a/packages/mobile/src/components/suggested-tracks/SuggestedTracks.tsx b/packages/mobile/src/components/suggested-tracks/SuggestedTracks.tsx index 703e555847..9e471a6f29 100644 --- a/packages/mobile/src/components/suggested-tracks/SuggestedTracks.tsx +++ b/packages/mobile/src/components/suggested-tracks/SuggestedTracks.tsx @@ -1,20 +1,35 @@ -import { Fragment } from 'react' +import { Fragment, useCallback, useEffect, useRef } from 'react' -import type { UserTrackMetadata } from '@audius/common' -import { SquareSizes, useGetSuggestedTracks } from '@audius/common' -import { View } from 'react-native' +import type { ID, Track } from '@audius/common' +import { + SquareSizes, + cacheUsersSelectors, + useGetSuggestedTracks +} from '@audius/common' +import { Animated, LayoutAnimation, View } from 'react-native' +import { useSelector } from 'react-redux' +import { useToggle } from 'react-use' +import IconCaretDown from 'app/assets/images/iconCaretDown.svg' import IconRefresh from 'app/assets/images/iconRefresh.svg' -import { Button, Divider, Text, TextButton, Tile } from 'app/components/core' +import { + Button, + Divider, + IconButton, + Text, + TextButton, + Tile +} from 'app/components/core' import { makeStyles } from 'app/styles' import { TrackImage } from '../image/TrackImage' +import { Skeleton } from '../skeleton' import { UserBadges } from '../user-badges' +const { getUser } = cacheUsersSelectors + const messages = { title: 'Add some tracks', - description: - 'Placeholder copy: dependent on backend logic and what we decide to do with this new feature.', addTrack: 'Add', refresh: 'Refresh' } @@ -22,8 +37,14 @@ const messages = { const useStyles = makeStyles(({ spacing, typography, palette }) => ({ root: { marginBottom: spacing(12) }, heading: { + flexDirection: 'row', + gap: spacing(3), + padding: spacing(4), + alignItems: 'center' + }, + headingText: { gap: spacing(2), - padding: spacing(4) + flex: 1 }, suggestedTrack: { flexDirection: 'row', @@ -59,12 +80,16 @@ const useStyles = makeStyles(({ spacing, typography, palette }) => ({ })) type SuggestedTrackProps = { - track: UserTrackMetadata + collectionId: ID + track: Track + onAddTrack: (trackId: ID, collectionId: ID) => void } const SuggestedTrack = (props: SuggestedTrackProps) => { - const { track } = props - const { title, user } = track + const { collectionId, track, onAddTrack } = props + const { track_id, title, owner_id } = track + + const user = useSelector((state) => getUser(state, { id: owner_id })) const styles = useStyles() return ( @@ -84,7 +109,9 @@ const SuggestedTrack = (props: SuggestedTrackProps) => { > {title} - + {user ? ( + + ) : null} @@ -93,43 +120,106 @@ const SuggestedTrack = (props: SuggestedTrackProps) => { title={messages.addTrack} size='small' styles={{ text: styles.buttonText }} + onPress={() => onAddTrack(track_id, collectionId)} /> ) } -export const SuggestedTracks = () => { +const SuggestedTrackSkeleton = () => { + const styles = useStyles() + return ( + + + + + + + + + + ) +} + +type SuggestedTracksProps = { + collectionId: ID +} + +export const SuggestedTracks = (props: SuggestedTracksProps) => { + const { collectionId } = props const styles = useStyles() - const { data: suggestedTracks } = useGetSuggestedTracks() + const { suggestedTracks, onRefresh, onAddTrack } = + useGetSuggestedTracks(collectionId) + + const [isExpanded, toggleIsExpanded] = useToggle(false) + + const handleExpanded = useCallback(() => { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) + toggleIsExpanded() + }, [toggleIsExpanded]) + + const expandAnimation = useRef(new Animated.Value(0)) + const expandIconStyle = { + transform: [ + { + rotate: expandAnimation.current.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '180deg'] + }) + } + ] + } + + useEffect(() => { + Animated.spring(expandAnimation.current, { + toValue: isExpanded ? 1 : 0, + useNativeDriver: true + }).start() + }, [isExpanded]) return ( - - {messages.title} - - - {messages.description} - + + + {messages.title} + + + + + - - - {suggestedTracks?.map((suggestedTrack) => ( - - + {isExpanded ? ( + <> + - - ))} - - + {suggestedTracks?.map((suggestedTrack) => ( + + {suggestedTrack.track ? ( + + ) : ( + + )} + + + ))} + + + + ) : null} ) } diff --git a/packages/mobile/src/hooks/useContentNodeImage.ts b/packages/mobile/src/hooks/useContentNodeImage.ts index 7178c15a52..450995988c 100644 --- a/packages/mobile/src/hooks/useContentNodeImage.ts +++ b/packages/mobile/src/hooks/useContentNodeImage.ts @@ -2,7 +2,6 @@ import { useState, useMemo, useCallback } from 'react' import type { Nullable, CID, WidthSizes, SquareSizes } from '@audius/common' import { interleave, useAppContext } from '@audius/common' -import type { User } from '@sentry/react-native' import type { ImageSourcePropType, ImageURISource } from 'react-native' export type ContentNodeImageSource = { @@ -45,18 +44,16 @@ const createImageSourcesForEndpoints = ({ */ export const createAllImageSources = ({ cid, - user, endpoints, size, localSource }: { cid: Nullable - user?: Nullable<{ creator_node_endpoint: Nullable }> endpoints: string[] size: SquareSizes | WidthSizes localSource?: ImageURISource | null }) => { - if (!cid || (!user && !endpoints)) { + if (!cid || !endpoints) { return [] } @@ -86,7 +83,7 @@ export const createAllImageSources = ({ } /** - * Return the first image source, usually the user's primary + * Return the first image source, usually the best content node * or a local source. This is useful for cases where there is no error * callback if the image fails to load - like the MusicControls on the lockscreen */ @@ -99,7 +96,6 @@ export const getImageSourceOptimistic = ( type UseContentNodeImageOptions = { cid: Nullable - user: Nullable> // The size of the image to fetch size: SquareSizes | WidthSizes fallbackImageSource: ImageSourcePropType @@ -107,9 +103,9 @@ type UseContentNodeImageOptions = { } /** - * Load an image from a user's replica set + * Load an image from best content node * - * If the image fails to load, try the next node in the replica set + * If the image fails to load, try the next best node * * Returns props for the DynamicImage component * @returns { @@ -118,13 +114,10 @@ type UseContentNodeImageOptions = { * isFallbackImage: boolean * } */ -export const useContentNodeImage = ({ - cid, - user, - size, - fallbackImageSource, - localSource -}: UseContentNodeImageOptions): ContentNodeImageSource => { +export const useContentNodeImage = ( + options: UseContentNodeImageOptions +): ContentNodeImageSource => { + const { cid, size, fallbackImageSource, localSource } = options const [imageSourceIndex, setImageSourceIndex] = useState(0) const [failedToLoad, setFailedToLoad] = useState(false) const { storageNodeSelector } = useAppContext() @@ -156,8 +149,8 @@ export const useContentNodeImage = ({ }, [imageSourceIndex, imageSources]) const showFallbackImage = useMemo(() => { - return !user || !cid || failedToLoad - }, [failedToLoad, user, cid]) + return !cid || failedToLoad + }, [failedToLoad, cid]) const source = useMemo(() => { if (showFallbackImage) { diff --git a/packages/mobile/src/screens/chat-screen/ChatUnavailable.tsx b/packages/mobile/src/screens/chat-screen/ChatUnavailable.tsx index 32b2f08d30..a08a80cc22 100644 --- a/packages/mobile/src/screens/chat-screen/ChatUnavailable.tsx +++ b/packages/mobile/src/screens/chat-screen/ChatUnavailable.tsx @@ -1,9 +1,14 @@ import { useCallback, useMemo } from 'react' -import { ChatPermissionAction, useCanSendMessage } from '@audius/common' +import { + ChatPermissionAction, + useCanSendMessage, + CHAT_BLOG_POST_URL +} from '@audius/common' import { View, Text } from 'react-native' import { useDispatch } from 'react-redux' +import { useLink } from 'app/components/core' import { useNavigation } from 'app/hooks/useNavigation' import { setVisibility } from 'app/store/drawers/slice' import { makeStyles } from 'app/styles' @@ -49,8 +54,7 @@ export const ChatUnavailable = ({ chatId }: ChatUnavailableProps) => { const { firstOtherUser: otherUser, callToAction } = useCanSendMessage(chatId) - // TODO: link to blog - const handleLearnMorePress = useCallback(() => {}, []) + const { onPress: handleLearnMorePress } = useLink(CHAT_BLOG_POST_URL) const handleUnblockPress = useCallback(() => { if (otherUser) { diff --git a/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx b/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx index 311379a799..e33d4bf6a1 100644 --- a/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx +++ b/packages/mobile/src/screens/chat-screen/ChatUserListItem.tsx @@ -147,10 +147,13 @@ const useStyles = makeStyles(({ spacing, palette, typography }) => ({ } })) -const ctaToTextMap = { +const ctaToTextMap: Record = { [ChatPermissionAction.TIP]: messages.ctaTip, [ChatPermissionAction.UNBLOCK]: messages.ctaBlock, - [ChatPermissionAction.NONE]: messages.ctaNone + [ChatPermissionAction.NONE]: messages.ctaNone, + [ChatPermissionAction.WAIT]: messages.ctaNone, + [ChatPermissionAction.NOT_APPLICABLE]: messages.ctaNone, + [ChatPermissionAction.SIGN_UP]: messages.ctaNone } type ChatUserListItemProps = { diff --git a/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx b/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx index 996102d453..7c084b01c1 100644 --- a/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx +++ b/packages/mobile/src/screens/collection-screen/CollectionScreen.tsx @@ -111,7 +111,7 @@ export const CollectionScreen = () => { }, [collectionName, idParam]) const handleFetchCollection = useCallback(() => { - dispatch(fetchCollection(id)) + dispatch(fetchCollection(id, undefined, true)) }, [dispatch, id]) useFocusEffect(handleFetchCollection) @@ -311,11 +311,12 @@ const CollectionScreenComponent = (props: CollectionScreenComponentProps) => { trackCount={track_ids.length} title={playlist_name} user={user} + isOwner={isOwner} /> {isOwner && !is_album && arePlaylistUpdatesEnabled ? ( <> - + ) : null} diff --git a/packages/mobile/src/screens/collection-screen/CollectionScreenDetailsTile.tsx b/packages/mobile/src/screens/collection-screen/CollectionScreenDetailsTile.tsx index 7c58ad5248..fbd393c09a 100644 --- a/packages/mobile/src/screens/collection-screen/CollectionScreenDetailsTile.tsx +++ b/packages/mobile/src/screens/collection-screen/CollectionScreenDetailsTile.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import type { ID, Maybe, SmartCollectionVariant, UID } from '@audius/common' import { @@ -11,9 +11,11 @@ import { PlaybackSource, formatSecondsAsText, collectionPageLineupActions as tracksActions, - reachabilitySelectors + reachabilitySelectors, + cacheCollectionsSelectors } from '@audius/common' import { useDispatch, useSelector } from 'react-redux' +import { usePrevious } from 'react-use' import { createSelector } from 'reselect' import { Text } from 'app/components/core' @@ -34,6 +36,7 @@ const { resetAndFetchCollectionTracks } = collectionPageActions const { getPlaying, getUid, getCurrentTrack } = playerSelectors const { getIsReachable } = reachabilitySelectors const { getCollectionTracksLineup } = collectionPageSelectors +const { getCollection } = cacheCollectionsSelectors const selectTrackUids = createSelector( (state: AppState) => getCollectionTracksLineup(state).entries, @@ -72,9 +75,28 @@ const selectIsQueued = createSelector( } ) +const useRefetchLineupOnTrackAdd = ( + collectionId: ID | SmartCollectionVariant +) => { + const trackCount = useSelector((state) => + typeof collectionId !== 'number' + ? 0 + : getCollection(state, { id: collectionId })?.track_count + ) + const previousTrackCount = usePrevious(trackCount) + const dispatch = useDispatch() + + useEffect(() => { + if (previousTrackCount && previousTrackCount !== trackCount) { + dispatch(tracksActions.fetchLineupMetadatas(0, 200, false)) + } + }, [previousTrackCount, trackCount, dispatch]) +} + const messages = { empty: 'This playlist is empty. Start adding tracks to share it or make it public.', + emptyPublic: 'This playlist is empty', detailsPlaceholder: '---' } @@ -96,6 +118,7 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ type CollectionScreenDetailsTileProps = { isAlbum?: boolean isPrivate?: boolean + isOwner?: boolean isPublishing?: boolean extraDetails?: DetailsTileDetail[] collectionId: number | SmartCollectionVariant @@ -127,6 +150,7 @@ export const CollectionScreenDetailsTile = ({ isPublishing, renderImage, trackCount: trackCountProp, + isOwner, ...detailsTileProps }: CollectionScreenDetailsTileProps) => { const styles = useStyles() @@ -152,6 +176,8 @@ export const CollectionScreenDetailsTile = ({ const playingTrackId = playingTrack?.track_id const firstTrack = useSelector(selectFirstTrack) + useRefetchLineupOnTrackAdd(collectionId) + const details = useMemo(() => { if (!isLineupLoading && trackCount === 0) return [] return [ @@ -218,9 +244,11 @@ export const CollectionScreenDetailsTile = ({ togglePlay={handlePressTrackListItemPlay} uids={isLineupLoading ? Array(Math.min(5, trackCount ?? 0)) : trackUids} ListEmptyComponent={ - - {messages.empty} - + isLineupLoading ? null : ( + + {isOwner ? messages.empty : messages.emptyPublic} + + ) } /> ) @@ -231,7 +259,8 @@ export const CollectionScreenDetailsTile = ({ isLineupLoading, styles, trackUids, - trackCount + trackCount, + isOwner ]) const isPlayable = isQueued || (trackCount > 0 && !!firstTrack) diff --git a/packages/mobile/src/screens/edit-profile-screen/EditProfileScreen.tsx b/packages/mobile/src/screens/edit-profile-screen/EditProfileScreen.tsx index 5a6a9c5e42..ee1d7ce55f 100644 --- a/packages/mobile/src/screens/edit-profile-screen/EditProfileScreen.tsx +++ b/packages/mobile/src/screens/edit-profile-screen/EditProfileScreen.tsx @@ -33,7 +33,8 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ coverPhoto: { height: 96, width: '100%', - borderRadius: 0 + borderRadius: 0, + aspectRatio: undefined }, profilePicture: { position: 'absolute', diff --git a/packages/mobile/src/screens/search-screen/SearchResults/SearchItem.tsx b/packages/mobile/src/screens/search-screen/SearchResults/SearchItem.tsx index 07267b043c..cd4872aeb5 100644 --- a/packages/mobile/src/screens/search-screen/SearchResults/SearchItem.tsx +++ b/packages/mobile/src/screens/search-screen/SearchResults/SearchItem.tsx @@ -95,7 +95,6 @@ const TrackSearchResult = (props: TrackSearchResultProps) => { @@ -134,7 +133,6 @@ const PlaylistSearchResult = (props: PlaylistSearchResultProps) => { @@ -173,7 +171,6 @@ const AlbumSearchResult = (props: AlbumSearchResultProps) => { diff --git a/packages/mobile/src/screens/update-required-screen/NewVersionPrompt.tsx b/packages/mobile/src/screens/update-required-screen/NewVersionPrompt.tsx index 898970ab9d..3bba658c84 100644 --- a/packages/mobile/src/screens/update-required-screen/NewVersionPrompt.tsx +++ b/packages/mobile/src/screens/update-required-screen/NewVersionPrompt.tsx @@ -52,14 +52,16 @@ export const NewVersionPrompt = ({ {contentText} - + + + ) +} diff --git a/packages/web/src/components/suggested-tracks/index.ts b/packages/web/src/components/suggested-tracks/index.ts new file mode 100644 index 0000000000..bc82061073 --- /dev/null +++ b/packages/web/src/components/suggested-tracks/index.ts @@ -0,0 +1 @@ +export * from './SuggestedTracks' diff --git a/packages/web/src/components/tracks-table/TracksTable.tsx b/packages/web/src/components/tracks-table/TracksTable.tsx index 4dda70bb59..a528e3194f 100644 --- a/packages/web/src/components/tracks-table/TracksTable.tsx +++ b/packages/web/src/components/tracks-table/TracksTable.tsx @@ -188,7 +188,8 @@ export const TracksTable = ({ ] ?? { isUserAccessTBD: false, doesUserHaveAccess: true } const isLocked = !isUserAccessTBD && !doesUserHaveAccess const index = cellInfo.row.index - const deleted = track.is_delete || track.user?.is_deactivated + const deleted = + track.is_delete || track._marked_deleted || !!track.user?.is_deactivated const renderLocked = () => { return ( @@ -311,7 +312,8 @@ export const TracksTable = ({ track.track_id ] ?? { isUserAccessTBD: false, doesUserHaveAccess: true } const isLocked = !isUserAccessTBD && !doesUserHaveAccess - const deleted = track.is_delete || !!track.user?.is_deactivated + const deleted = + track.is_delete || track._marked_deleted || !!track.user?.is_deactivated const isOwner = track.owner_id === userId if (isLocked || deleted || isOwner) { return
@@ -345,7 +347,8 @@ export const TracksTable = ({ track.track_id ] ?? { isUserAccessTBD: false, doesUserHaveAccess: true } const isLocked = !isUserAccessTBD && !doesUserHaveAccess - const deleted = track.is_delete || track.user?.is_deactivated + const deleted = + track.is_delete || track._marked_deleted || !!track.user?.is_deactivated const isOwner = track.owner_id === userId if (isLocked || deleted || isOwner) { return
@@ -382,7 +385,8 @@ export const TracksTable = ({ track.track_id ] ?? { isUserAccessTBD: false, doesUserHaveAccess: true } const isLocked = !isUserAccessTBD && !doesUserHaveAccess - const deleted = track.is_delete || !!track.user?.is_deactivated + const deleted = + track.is_delete || track._marked_deleted || !!track.user?.is_deactivated return (
{ + const { children } = props + + const { value: storageNodeSelector } = useAsync(getStorageNodeSelector) + + const value = useMemo( + () => ({ + analytics, + storageNodeSelector + }), + [storageNodeSelector] + ) + + return {children} +} diff --git a/packages/web/src/pages/AppProviders.tsx b/packages/web/src/pages/AppProviders.tsx index 023b2cc1c9..b3284e09c3 100644 --- a/packages/web/src/pages/AppProviders.tsx +++ b/packages/web/src/pages/AppProviders.tsx @@ -1,12 +1,11 @@ -import { AppContext } from '@audius/common' - import { RouterContextProvider } from 'components/animated-switch/RouterContextProvider' import { HeaderContextProvider } from 'components/header/mobile/HeaderContextProvider' import { NavProvider } from 'components/nav/store/context' import { ScrollProvider } from 'components/scroll-provider/ScrollProvider' import { ToastContextProvider } from 'components/toast/ToastContext' import { MainContentContextProvider } from 'pages/MainContentContext' -import * as analytics from 'services/analytics' + +import { AppContextProvider } from './AppContextProvider' type AppContextProps = { children: JSX.Element @@ -14,7 +13,7 @@ type AppContextProps = { const AppProviders = ({ children }: AppContextProps) => { return ( - + @@ -26,7 +25,7 @@ const AppProviders = ({ children }: AppContextProps) => { - + ) } diff --git a/packages/web/src/pages/PublicSite.tsx b/packages/web/src/pages/PublicSite.tsx index fc41318e9c..d1d3b3cc4a 100644 --- a/packages/web/src/pages/PublicSite.tsx +++ b/packages/web/src/pages/PublicSite.tsx @@ -13,6 +13,8 @@ import { AUDIUS_PRESS_LINK } from 'utils/route' +import { AppContextProvider } from './AppContextProvider' + const BASENAME = process.env.PUBLIC_URL const PrivacyPolicyPage = lazy( @@ -96,76 +98,78 @@ const PublicSite = ({ isMobile, setRenderPublicSite }: PublicSiteProps) => { /> }> - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - { - window.location.href = AUDIUS_PRESS_LINK - return null - }} - /> - ( - - )} - /> - ( - - )} - /> - } - /> - + + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + { + window.location.href = AUDIUS_PRESS_LINK + return null + }} + /> + ( + + )} + /> + ( + + )} + /> + } + /> + + ) diff --git a/packages/web/src/pages/audio-rewards-page/components/modals/TopAPI.tsx b/packages/web/src/pages/audio-rewards-page/components/modals/TopAPI.tsx index 701bddb087..4bb5c08d9d 100644 --- a/packages/web/src/pages/audio-rewards-page/components/modals/TopAPI.tsx +++ b/packages/web/src/pages/audio-rewards-page/components/modals/TopAPI.tsx @@ -21,7 +21,7 @@ const TopAPIBody = () => { const wm = useWithMobileStyle(styles.mobile) const onClickAudiusAPI = useCallback(() => { - window.open(AUDIUS_API_LINK, '__blank') + window.open(AUDIUS_API_LINK, '_blank') }, []) return ( diff --git a/packages/web/src/pages/chat-page/components/ChatMessageListItem.tsx b/packages/web/src/pages/chat-page/components/ChatMessageListItem.tsx index dbf1aba741..f6976e281f 100644 --- a/packages/web/src/pages/chat-page/components/ChatMessageListItem.tsx +++ b/packages/web/src/pages/chat-page/components/ChatMessageListItem.tsx @@ -231,6 +231,7 @@ export const ChatMessageListItem = (props: ChatMessageListItemProps) => { } } }, + rel: 'noreferrer noopener', target: (href) => { return isAudiusUrl(href) ? '' : '_blank' } diff --git a/packages/web/src/pages/chat-page/components/InboxUnavailableMessage.tsx b/packages/web/src/pages/chat-page/components/InboxUnavailableMessage.tsx index feebe7e9b8..63e98184e1 100644 --- a/packages/web/src/pages/chat-page/components/InboxUnavailableMessage.tsx +++ b/packages/web/src/pages/chat-page/components/InboxUnavailableMessage.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, MouseEventHandler } from 'react' -import { User, ChatPermissionAction } from '@audius/common' +import { User, ChatPermissionAction, CHAT_BLOG_POST_URL } from '@audius/common' import { UserNameAndBadges } from 'components/user-name-and-badges/UserNameAndBadges' @@ -68,8 +68,8 @@ export const InboxUnavailableMessage = ({ default: return ( diff --git a/packages/web/src/pages/collection-page/CollectionPageProvider.tsx b/packages/web/src/pages/collection-page/CollectionPageProvider.tsx index e123b9edea..01fa06acd7 100644 --- a/packages/web/src/pages/collection-page/CollectionPageProvider.tsx +++ b/packages/web/src/pages/collection-page/CollectionPageProvider.tsx @@ -301,6 +301,15 @@ class CollectionPage extends Component< } } + // check if a track has been added to collection + if ( + metadata && + prevMetadata && + metadata.track_count > prevMetadata.track_count + ) { + this.props.fetchTracks() + } + // check that the collection content hasn't changed if ( metadata && diff --git a/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx b/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx index 9105c8c12d..4087269bc4 100644 --- a/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx +++ b/packages/web/src/pages/collection-page/components/desktop/CollectionPage.tsx @@ -21,13 +21,13 @@ import { import { CollectionHeader } from 'components/collection/desktop/CollectionHeader' import { Divider } from 'components/divider' import Page from 'components/page/Page' +import { SuggestedTracks } from 'components/suggested-tracks' import { Tile } from 'components/tile' import { TracksTable, TracksTableColumn } from 'components/tracks-table' import { useFlag } from 'hooks/useRemoteConfig' import { computeCollectionMetadataProps } from 'pages/collection-page/store/utils' import styles from './CollectionPage.module.css' -import { SuggestedTracks } from './SuggestedTracks' const messages = { emptyPage: { @@ -295,7 +295,7 @@ const CollectionPage = ({ {isOwner && !isAlbum && !isNftPlaylist && arePlaylistUpdatesEnabled ? ( <> - + ) : null} diff --git a/packages/web/src/pages/collection-page/components/desktop/SuggestedTracks.tsx b/packages/web/src/pages/collection-page/components/desktop/SuggestedTracks.tsx deleted file mode 100644 index 531e0473f3..0000000000 --- a/packages/web/src/pages/collection-page/components/desktop/SuggestedTracks.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Fragment } from 'react' - -import { - SquareSizes, - Status, - UserTrackMetadata, - useGetSuggestedTracks -} from '@audius/common' -import { Button, ButtonSize, ButtonType, IconRefresh } from '@audius/stems' - -import { Divider } from 'components/divider' -import LoadingSpinner from 'components/loading-spinner/LoadingSpinner' -import { Tile } from 'components/tile' -import { UserNameAndBadges } from 'components/user-name-and-badges/UserNameAndBadges' -import { useTrackCoverArt2 } from 'hooks/useTrackCoverArt' - -import styles from './SuggestedTracks.module.css' - -const messages = { - title: 'Add some tracks', - description: - 'Placeholder copy: dependent on backend logic and what we decide to do with this new feature.', - addTrack: 'Add', - refresh: 'Refresh' -} - -type SuggestedTrackProps = { - track: UserTrackMetadata -} - -const SuggestedTrack = (props: SuggestedTrackProps) => { - const { track } = props - const { track_id, title, user } = track - - const image = useTrackCoverArt2(track_id, SquareSizes.SIZE_150_BY_150) - - return ( -
-
- -
-

{title}

- -
-
-
- ) -} - -export const SuggestedTracks = () => { - const { data: suggestedTracks, status } = useGetSuggestedTracks() - - const divider = - - return ( - -
-

{messages.title}

-

{messages.description}

-
-
    - {divider} - {!suggestedTracks && status === Status.LOADING ? ( - - ) : null} - {suggestedTracks?.map((suggestedTrack) => ( - -
  • - -
  • - {divider} -
    - ))} - {divider} -
- -
- ) -} diff --git a/packages/web/src/pages/landing-page/components/FeaturedContent.tsx b/packages/web/src/pages/landing-page/components/FeaturedContent.tsx index a7bac635fc..e19ab8554a 100644 --- a/packages/web/src/pages/landing-page/components/FeaturedContent.tsx +++ b/packages/web/src/pages/landing-page/components/FeaturedContent.tsx @@ -1,6 +1,13 @@ import { useEffect, useState } from 'react' -import { UserCollectionMetadata } from '@audius/common' +import { + Maybe, + Nullable, + SquareSizes, + UserCollectionMetadata, + useAppContext +} from '@audius/common' +import { StorageNodeSelectorService } from '@audius/sdk' // eslint-disable-next-line no-restricted-imports -- TODO: migrate to @react-spring/web import { useSpring, animated } from 'react-spring' import { useAsyncFn } from 'react-use' @@ -128,25 +135,17 @@ type FeaturedContentProps = { } const getImageUrl = ( - size: 'small' | 'large', - { cover_art, cover_art_sizes }: UserCollectionMetadata, - creatorNodeEndpoint: string | null + cid: Nullable, + size: SquareSizes, + storageNodeSelector: Maybe ) => { - const gateways = - audiusBackendInstance.getCreatorNodeIPFSGateways(creatorNodeEndpoint) - const cNode = gateways[0] - if (cover_art_sizes) { - return `${cNode}${cover_art_sizes}/${ - size === 'small' ? '150x150' : '1000x1000' - }.jpg` - } else if (cover_art_sizes) { - return `${cNode}${cover_art}` - } else { - return null - } + if (!storageNodeSelector || !cid) return null + const node = storageNodeSelector.getNodes(cid)[0] + return `${node}/content/${cid}/${size}.jpg` } const FeaturedContent = (props: FeaturedContentProps) => { + const { storageNodeSelector } = useAppContext() const [trendingPlaylistsResponse, fetchTrendingPlaylists] = useAsyncFn(async () => { const featuredContent = await fetchExploreContent() @@ -193,9 +192,9 @@ const FeaturedContent = (props: FeaturedContentProps) => { title={p.playlist_name} artist={p.user.name} imageUrl={getImageUrl( - 'small', - p, - p.user.creator_node_endpoint + p.cover_art_sizes, + SquareSizes.SIZE_150_BY_150, + storageNodeSelector )} onClick={handleClickRoute( playlistPage( @@ -247,9 +246,9 @@ const FeaturedContent = (props: FeaturedContentProps) => { title={p.playlist_name} artist={p.user.name} imageUrl={getImageUrl( - 'large', - p, - p.user.creator_node_endpoint + p.cover_art_sizes, + SquareSizes.SIZE_1000_BY_1000, + storageNodeSelector )} onClick={handleClickRoute( playlistPage( diff --git a/packages/web/src/pages/oauth-login-page/utils.ts b/packages/web/src/pages/oauth-login-page/utils.ts index fbae4570e9..95170f77ae 100644 --- a/packages/web/src/pages/oauth-login-page/utils.ts +++ b/packages/web/src/pages/oauth-login-page/utils.ts @@ -4,6 +4,7 @@ import base64url from 'base64url' import { audiusBackendInstance } from 'services/audius-backend/audius-backend-instance' import { audiusSdk } from 'services/audius-sdk' +import { getStorageNodeSelector } from 'services/audius-sdk/storageNodeSelector' export const getIsRedirectValid = ({ parsedRedirectUri, @@ -106,22 +107,23 @@ export const formOAuthResponse = async ({ email = userEmail } - const gateways = audiusBackendInstance.getCreatorNodeIPFSGateways( - account.creator_node_endpoint - ) - const cNode = gateways[0] + const storageNodeSelector = await getStorageNodeSelector() let profilePicture: | { '150x150': string; '480x480': string; '1000x1000': string } | undefined if (account.profile_picture_sizes) { - const base = `${cNode}${account.profile_picture_sizes}/` + const storageNode = storageNodeSelector.getNodes( + account.profile_picture_sizes + ) + const base = `${storageNode}${account.profile_picture_sizes}/` profilePicture = { '150x150': `${base}150x150.jpg`, '480x480': `${base}480x480.jpg`, '1000x1000': `${base}1000x1000.jpg` } } else if (account.profile_picture) { - const url = `${cNode}${account.profile_picture}` + const storageNode = storageNodeSelector.getNodes(account.profile_picture) + const url = `${storageNode}${account.profile_picture}` profilePicture = { '150x150': url, '480x480': url, diff --git a/packages/web/src/pages/upload-page/components/TrackModalArray.tsx b/packages/web/src/pages/upload-page/components/TrackModalArray.tsx index a4c1e3fa99..480c9dbf68 100644 --- a/packages/web/src/pages/upload-page/components/TrackModalArray.tsx +++ b/packages/web/src/pages/upload-page/components/TrackModalArray.tsx @@ -1,6 +1,7 @@ -import { ReleaseDateModalForm } from '../fields/ReleaseDateModalForm' -import { RemixModalForm } from '../fields/RemixModalForm' -import { SourceFilesModalForm } from '../fields/SourceFilesModalForm' +import { ReleaseDateModalForm } from '../forms/ReleaseDateModalForm' +import { RemixModalForm } from '../forms/RemixModalForm' +import { SourceFilesModalForm } from '../forms/SourceFilesModalForm' +import { TrackAvailabilityModalForm } from '../forms/TrackAvailabilityModalForm' import styles from './TrackModalArray.module.css' @@ -10,6 +11,7 @@ export const TrackModalArray = () => { +
) } diff --git a/packages/web/src/pages/upload-page/fields/ToggleRowField.module.css b/packages/web/src/pages/upload-page/fields/SwitchRowField.module.css similarity index 100% rename from packages/web/src/pages/upload-page/fields/ToggleRowField.module.css rename to packages/web/src/pages/upload-page/fields/SwitchRowField.module.css diff --git a/packages/web/src/pages/upload-page/fields/SwitchRowField.tsx b/packages/web/src/pages/upload-page/fields/SwitchRowField.tsx new file mode 100644 index 0000000000..b93e8aa1f0 --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/SwitchRowField.tsx @@ -0,0 +1,49 @@ +import { ChangeEvent, ComponentProps, PropsWithChildren } from 'react' + +import { Switch } from '@audius/stems' +import cn from 'classnames' +import { useField } from 'formik' + +import styles from './SwitchRowField.module.css' + +type ToggleFieldProps = PropsWithChildren & { + name: string + header: string + description: string + inverted?: boolean +} & Partial> + +export const SwitchRowField = (props: ToggleFieldProps) => { + const { name, header, description, inverted, children, ...inputOverrides } = + props + const [field] = useField({ + name, + type: 'checkbox' + }) + + const onChange = inverted + ? (e: ChangeEvent) => { + const modifiedEvent = { ...e } + modifiedEvent.target.checked = !e.target.checked + modifiedEvent.target.value = + e.target.value === 'true' ? 'false' : 'true' + field.onChange(modifiedEvent) + } + : field.onChange + + return ( +
+
+

{header}

+

{description}

+ {(inverted ? !field.checked : field.checked) ? children : null} +
+ +
+ ) +} diff --git a/packages/web/src/pages/upload-page/fields/ToggleRowField.tsx b/packages/web/src/pages/upload-page/fields/ToggleRowField.tsx deleted file mode 100644 index 21674e2ff7..0000000000 --- a/packages/web/src/pages/upload-page/fields/ToggleRowField.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { ComponentProps, PropsWithChildren } from 'react' - -import { Switch } from '@audius/stems' -import cn from 'classnames' -import { useField } from 'formik' - -import styles from './ToggleRowField.module.css' - -type ToggleFieldProps = PropsWithChildren & { - name: string - header: string - description: string -} & Partial> - -export const ToggleRowField = (props: ToggleFieldProps) => { - const { name, header, description, children, ...inputOverrides } = props - const [field] = useField({ - name, - type: 'checkbox' - }) - - return ( -
-
-

{header}

-

{description}

- {field.checked ? children : null} -
- -
- ) -} diff --git a/packages/web/src/pages/upload-page/fields/availability/HiddenAvailabilityFields.module.css b/packages/web/src/pages/upload-page/fields/availability/HiddenAvailabilityFields.module.css new file mode 100644 index 0000000000..e6f8d72883 --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/HiddenAvailabilityFields.module.css @@ -0,0 +1,31 @@ +.root { + display: flex; + flex-wrap: wrap; + gap: var(--unit-2) var(--unit-6); + padding: var(--unit-4); + background: var(--neutral-light-10); + border: 1px solid var(--neutral-light-7); + border-radius: var(--unit-2); + font-size: var(--font-l); + font-weight: var(--font-demi-bold); +} + +.switchRow { + flex: 1 1 calc(50% - var(--unit-3)); + min-width: 200px; + display: flex; + align-items: center; + justify-content: space-between; + line-height: 22px; +} + +.switchLabel { + white-space: nowrap; +} + +.column { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--unit-2); +} diff --git a/packages/web/src/pages/upload-page/fields/availability/HiddenAvailabilityFields.tsx b/packages/web/src/pages/upload-page/fields/availability/HiddenAvailabilityFields.tsx new file mode 100644 index 0000000000..1a3ccacf3d --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/HiddenAvailabilityFields.tsx @@ -0,0 +1,82 @@ +import { Switch } from '@audius/stems' +import { useField } from 'formik' + +import { FIELD_VISIBILITY } from '../../forms/TrackAvailabilityModalForm' + +import styles from './HiddenAvailabilityFields.module.css' + +const messages = { + showGenre: 'Show Genre', + showMood: 'Show Mood', + showTags: 'Show Tags', + showShareButton: 'Show Share Button', + showPlayCount: 'Show Play Count' +} + +export enum UnlistedTrackMetadataField { + UNLISTED = 'unlisted', + GENRE = 'genre', + MOOD = 'mood', + TAGS = 'tags', + SHARE = 'share', + PLAYS = 'plays' +} + +export const defaultHiddenFields = { + genre: true, + mood: true, + tags: true, + share: false, + play_count: false + // REMIXES handled by a separate field +} + +// The order of toggles in the modal +const unlistedTrackMetadataOrder = [ + UnlistedTrackMetadataField.GENRE, + UnlistedTrackMetadataField.SHARE, + UnlistedTrackMetadataField.MOOD, + UnlistedTrackMetadataField.PLAYS, + UnlistedTrackMetadataField.TAGS +] + +export const HiddenAvailabilityFields = () => { + return ( +
+ {unlistedTrackMetadataOrder.map((fieldName) => { + return + })} + {/* Dummy row for spacing consistency */} +
+
+ ) +} + +const messageByFieldName = { + [UnlistedTrackMetadataField.UNLISTED]: '', + [UnlistedTrackMetadataField.GENRE]: messages.showGenre, + [UnlistedTrackMetadataField.MOOD]: messages.showMood, + [UnlistedTrackMetadataField.TAGS]: messages.showTags, + [UnlistedTrackMetadataField.SHARE]: messages.showShareButton, + [UnlistedTrackMetadataField.PLAYS]: messages.showPlayCount +} + +type AvailabilityToggleFieldProps = { + fieldName: UnlistedTrackMetadataField +} + +const AvailabilityToggleField = (props: AvailabilityToggleFieldProps) => { + const { fieldName } = props + const [field] = useField({ + name: `${FIELD_VISIBILITY}.${fieldName}`, + type: 'checkbox' + }) + return ( +
+ + {messageByFieldName[fieldName]} + + +
+ ) +} diff --git a/packages/web/src/pages/upload-page/fields/availability/SpecialAccessFields.module.css b/packages/web/src/pages/upload-page/fields/availability/SpecialAccessFields.module.css new file mode 100644 index 0000000000..18b525fdda --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/SpecialAccessFields.module.css @@ -0,0 +1,42 @@ +.root { + display: flex; + flex-direction: column; + gap: var(--unit-2); + padding: var(--unit-4); + background: var(--neutral-light-10); + border: 1px solid var(--neutral-light-7); + border-radius: var(--unit-2); + font-weight: var(--font-demi-bold); + font-size: var(--font-l); +} + +.radio { + margin-right: var(--unit-1); +} + +.row { + display: flex; + align-items: center; + gap: var(--unit-2); +} + +.row:not(.disabled) { + cursor: pointer; +} + +.icon path { + fill: var(--neutral-light-4); +} + +.tooltip :global(.ant-tooltip-inner) { + width: 272px; + text-align: center; +} + +.disabled .radio input { + cursor: default; +} + +.disabled span { + color: var(--neutral-light-4); +} diff --git a/packages/web/src/pages/upload-page/fields/availability/SpecialAccessFields.tsx b/packages/web/src/pages/upload-page/fields/availability/SpecialAccessFields.tsx new file mode 100644 index 0000000000..7e46cb4fcf --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/SpecialAccessFields.tsx @@ -0,0 +1,98 @@ +import { ChangeEvent, useCallback } from 'react' + +import { accountSelectors } from '@audius/common' +import { IconInfo, RadioButton, RadioButtonGroup } from '@audius/stems' +import cn from 'classnames' +import { useField } from 'formik' +import { useSelector } from 'react-redux' + +import Tooltip from 'components/tooltip/Tooltip' + +import { + PREMIUM_CONDITIONS, + TrackAvailabilityFormValues +} from '../../forms/TrackAvailabilityModalForm' + +import styles from './SpecialAccessFields.module.css' + +const { getUserId } = accountSelectors + +const messages = { + followersOnly: 'Available to Followers Only', + supportersOnly: 'Available to Supporters Only', + supportersInfo: 'Supporters are users who have sent you a tip.' +} + +export enum SpecialAccessType { + TIP = 'tip', + FOLLOW = 'follow' +} + +type TrackAvailabilityFieldsProps = { + disabled?: boolean +} + +const SPECIAL_ACCESS_TYPE = 'special_access_type' + +export const SpecialAccessFields = (props: TrackAvailabilityFieldsProps) => { + const { disabled } = props + const accountUserId = useSelector(getUserId) + const [specialAccessTypeField] = useField({ + name: SPECIAL_ACCESS_TYPE + }) + + const [, , { setValue: setPremiumConditionsValue }] = + useField( + PREMIUM_CONDITIONS + ) + + const handleChange = useCallback( + (e: ChangeEvent) => { + const type = e.target.value as SpecialAccessType + if (accountUserId) { + if (type === SpecialAccessType.FOLLOW) { + setPremiumConditionsValue({ follow_user_id: accountUserId }) + } else if (type === SpecialAccessType.TIP) { + setPremiumConditionsValue({ tip_user_id: accountUserId }) + } + } + specialAccessTypeField.onChange(e) + }, + [accountUserId, setPremiumConditionsValue, specialAccessTypeField] + ) + + return ( + + + + + ) +} diff --git a/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedDescription.module.css b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedDescription.module.css new file mode 100644 index 0000000000..708e14ca42 --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedDescription.module.css @@ -0,0 +1,16 @@ +.learnMoreButton { + align-self: flex-start; + padding: 0; + color: var(--neutral-light-2); +} + +.learnMoreArrow { + width: var(--unit-4); + height: var(--unit-4); +} + +.innerDescription { + display: flex; + flex-direction: column; + gap: var(--unit-4); +} diff --git a/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedDescription.tsx b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedDescription.tsx new file mode 100644 index 0000000000..dd75249dc1 --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedDescription.tsx @@ -0,0 +1,58 @@ +import { collectiblesSelectors } from '@audius/common' +import { Button, ButtonType } from '@audius/stems' +import { useSelector } from 'react-redux' + +import { ReactComponent as IconExternalLink } from 'assets/img/iconExternalLink.svg' +import { HelpCallout } from 'components/help-callout/HelpCallout' +import { AUDIUS_GATED_CONTENT_BLOG_LINK } from 'utils/route' + +import styles from './CollectibleGatedDescription.module.css' + +const { getHasUnsupportedCollection } = collectiblesSelectors +const messages = { + collectibleGated: 'Collectible Gated', + collectibleGatedSubtitle: + 'Users who own a digital collectible matching your selection will have access to your track. Collectible gated content does not appear on trending or in user feeds.', + noCollectibles: + 'No Collectibles found. To enable this option, link a wallet containing a collectible.', + compatibilityTitle: "Not seeing what you're looking for?", + compatibilitySubtitle: + 'Unverified Solana NFT Collections are not compatible at this time.', + learnMore: 'Learn More' +} + +export const CollectibleGatedDescription = ({ + hasCollectibles, + isUpload +}: { + hasCollectibles: boolean + isUpload: boolean +}) => { + const hasUnsupportedCollection = useSelector(getHasUnsupportedCollection) + + const helpContent = hasUnsupportedCollection ? ( +
+
{messages.compatibilityTitle}
+
{messages.compatibilitySubtitle}
+
+ ) : ( + messages.noCollectibles + ) + + return ( +
+ {messages.collectibleGatedSubtitle} + {!hasCollectibles && isUpload ? ( + + ) : null} +
+ ) +} diff --git a/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedFields.module.css b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedFields.module.css new file mode 100644 index 0000000000..9c1ed4502e --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedFields.module.css @@ -0,0 +1,56 @@ +.root { + padding-top: var(--unit-2); +} + +.dropdown { + z-index: 10000; +} + +.root .dropdownInput:has(:global(.ant-select)) { + height: 72px; +} + +.dropdownInput :global(.ant-select-selection-item), +.dropdownInput :global(.ant-select-selection-placeholder) > div { + display: flex; + align-items: center; +} + +.root .dropdown :global(.ant-select-item) { + padding: 16px; + height: 80px; +} +.dropdownRow { + display: flex; + align-items: center; + z-index: 3000; +} +.dropdownRow > span { + margin-left: 8px; + font-weight: var(--font-demi-bold); + font-size: var(--font-l); +} + +.dropdownInput :global(.ant-select-selection-item) { + pointer-events: none; +} + +:global(.ant-select-selection-item) .dropdownRow > img { + width: 32px; + height: 32px; + border-radius: 8px; +} + +:global(.ant-select-item) .dropdownRow > img { + width: 48px; + height: 48px; + border-radius: 8px; +} + +.helpCallout { + border-right: none; + border-left: none; + border-bottom: none; + border-radius: initial; + height: 100%; +} diff --git a/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedFields.tsx b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedFields.tsx new file mode 100644 index 0000000000..a0741cd26e --- /dev/null +++ b/packages/web/src/pages/upload-page/fields/availability/collectible-gated/CollectibleGatedFields.tsx @@ -0,0 +1,178 @@ +import { useCallback, useMemo } from 'react' + +import { + Chain, + collectiblesSelectors, + TrackAvailabilityType +} from '@audius/common' +import { useField } from 'formik' +import { useSelector } from 'react-redux' + +import DropdownInput from 'components/data-entry/DropdownInput' +import { HelpCallout } from 'components/help-callout/HelpCallout' + +import { + AVAILABILITY_TYPE, + PREMIUM_CONDITIONS, + TrackAvailabilityFormValues +} from '../../../forms/TrackAvailabilityModalForm' + +import styles from './CollectibleGatedFields.module.css' + +const { getSupportedUserCollections, getHasUnsupportedCollection } = + collectiblesSelectors + +const messages = { + pickACollection: 'Pick a Collection', + compatibilityTitle: "Not seeing what you're looking for?", + compatibilitySubtitle: + 'Unverified Solana NFT Collections are not compatible at this time.' +} + +type CollectibleGatedFieldsProps = { + disabled: boolean +} + +export const CollectibleGatedFields = ({ + disabled +}: CollectibleGatedFieldsProps) => { + const [, , { setValue: setAvailabilityValue }] = useField({ + name: AVAILABILITY_TYPE + }) + const [ + { value: premiumConditionsValue }, + , + { setValue: setPremiumConditionsValue } + ] = + useField( + PREMIUM_CONDITIONS + ) + + const { ethCollectionMap, solCollectionMap } = useSelector( + getSupportedUserCollections + ) + const hasUnsupportedCollection = useSelector(getHasUnsupportedCollection) + + const ethCollectibleItems = useMemo(() => { + return Object.keys(ethCollectionMap) + .sort((s1, s2) => + ethCollectionMap[s1].name.localeCompare(ethCollectionMap[s2].name) + ) + .map((slug) => ({ + text: ethCollectionMap[slug].name, + el: ( +
+ {ethCollectionMap[slug].img ? ( + {ethCollectionMap[slug].name} + ) : null} + {ethCollectionMap[slug].name} +
+ ), + value: slug + })) + }, [ethCollectionMap]) + + const solCollectibleItems = useMemo(() => { + return Object.keys(solCollectionMap) + .sort((m1, m2) => + solCollectionMap[m1].name.localeCompare(solCollectionMap[m2].name) + ) + .map((mint) => ({ + text: solCollectionMap[mint].name, + el: ( +
+ {solCollectionMap[mint].img ? ( + {solCollectionMap[mint].name} + ) : null} + {solCollectionMap[mint].name} +
+ ), + value: mint + })) + }, [solCollectionMap]) + + const menuItems = useMemo( + () => [...ethCollectibleItems, ...solCollectibleItems], + [ethCollectibleItems, solCollectibleItems] + ) + + // If no nft collection was previously selected, then the default value is an empty string, + // which makes the dropdown show the placeholder. + // Otherwise, the default value is the nft collection which was previously selected, + // which also includes the collection image. + const defaultCollectionName = + premiumConditionsValue?.nft_collection?.name ?? '' + const defaultCollection = menuItems.find( + (item) => item.text === defaultCollectionName + ) + const defaultValue = defaultCollection || defaultCollectionName + + const renderFooter = useCallback(() => { + return hasUnsupportedCollection ? ( + +
{messages.compatibilityTitle}
+
{messages.compatibilitySubtitle}
+
+ } + /> + ) : null + }, [hasUnsupportedCollection]) + + return ( +
+ + // hack to escape the collapsible container which has overflow: hidden + // maintains scrollability, unlike `mount={'page'} + triggerNode.parentNode?.parentNode?.parentNode + } + menu={{ items: menuItems }} + defaultValue={defaultValue} + onSelect={(value: string) => { + if (ethCollectionMap[value]) { + setPremiumConditionsValue({ + nft_collection: { + chain: Chain.Eth, + standard: ethCollectionMap[value].standard, + address: ethCollectionMap[value].address, + name: ethCollectionMap[value].name, + imageUrl: ethCollectionMap[value].img, + externalLink: ethCollectionMap[value].externalLink, + slug: value + } + }) + setAvailabilityValue(TrackAvailabilityType.COLLECTIBLE_GATED) + } else if (solCollectionMap[value]) { + setPremiumConditionsValue({ + nft_collection: { + chain: Chain.Sol, + address: value, + name: solCollectionMap[value].name, + imageUrl: solCollectionMap[value].img, + externalLink: solCollectionMap[value].externalLink + } + }) + setAvailabilityValue(TrackAvailabilityType.COLLECTIBLE_GATED) + } + }} + size='large' + dropdownStyle={styles.dropdown} + dropdownInputStyle={styles.dropdownInput} + footer={renderFooter()} + disabled={disabled} + /> +
+ ) +} diff --git a/packages/web/src/pages/upload-page/fields/ReleaseDateModalForm.module.css b/packages/web/src/pages/upload-page/forms/ReleaseDateModalForm.module.css similarity index 100% rename from packages/web/src/pages/upload-page/fields/ReleaseDateModalForm.module.css rename to packages/web/src/pages/upload-page/forms/ReleaseDateModalForm.module.css diff --git a/packages/web/src/pages/upload-page/fields/ReleaseDateModalForm.tsx b/packages/web/src/pages/upload-page/forms/ReleaseDateModalForm.tsx similarity index 95% rename from packages/web/src/pages/upload-page/fields/ReleaseDateModalForm.tsx rename to packages/web/src/pages/upload-page/forms/ReleaseDateModalForm.tsx index 87b6d24b30..ec1b46e703 100644 --- a/packages/web/src/pages/upload-page/fields/ReleaseDateModalForm.tsx +++ b/packages/web/src/pages/upload-page/forms/ReleaseDateModalForm.tsx @@ -7,9 +7,9 @@ import { get, set } from 'lodash' import moment from 'moment' import { EditFormValues } from '../components/EditPageNew' +import { DatePickerField } from '../fields/DatePickerField' +import { ModalField } from '../fields/ModalField' -import { DatePickerField } from './DatePickerField' -import { ModalField } from './ModalField' import styles from './ReleaseDateModalForm.module.css' const messages = { title: 'Release Date', diff --git a/packages/web/src/pages/upload-page/fields/RemixModalForm.module.css b/packages/web/src/pages/upload-page/forms/RemixModalForm.module.css similarity index 100% rename from packages/web/src/pages/upload-page/fields/RemixModalForm.module.css rename to packages/web/src/pages/upload-page/forms/RemixModalForm.module.css diff --git a/packages/web/src/pages/upload-page/fields/RemixModalForm.tsx b/packages/web/src/pages/upload-page/forms/RemixModalForm.tsx similarity index 90% rename from packages/web/src/pages/upload-page/fields/RemixModalForm.tsx rename to packages/web/src/pages/upload-page/forms/RemixModalForm.tsx index 2c6eac0e80..f15e63b32d 100644 --- a/packages/web/src/pages/upload-page/fields/RemixModalForm.tsx +++ b/packages/web/src/pages/upload-page/forms/RemixModalForm.tsx @@ -28,10 +28,10 @@ import { useTrackCoverArt } from 'hooks/useTrackCoverArt' import { fullTrackPage, stripBaseUrl } from 'utils/route' import { EditFormValues } from '../components/EditPageNew' +import { ModalField } from '../fields/ModalField' +import { SwitchRowField } from '../fields/SwitchRowField' -import { ModalField } from './ModalField' import styles from './RemixModalForm.module.css' -import { ToggleRowField } from './ToggleRowField' const { getUserId } = accountSelectors @@ -56,13 +56,13 @@ const messages = { export type RemixOfField = Nullable<{ tracks: { parent_track_id: ID }[] }> export const REMIX_OF = 'remix_of' -export const HIDE_REMIXES = 'fieldVisibility.remixes' +export const SHOW_REMIXES = `field_visibility.remixes` const IS_REMIX = 'is_remix' const REMIX_LINK = 'remix_of_link' export type RemixFormValues = { - [HIDE_REMIXES]: boolean + [SHOW_REMIXES]: boolean [IS_REMIX]: boolean [REMIX_LINK]: string | null } @@ -73,8 +73,8 @@ export type RemixFormValues = { */ export const RemixModalForm = () => { // These refer to the field in the outer EditForm - const [{ value: hideRemixesValue }, , { setValue: setHideRemixesValue }] = - useField(HIDE_REMIXES) + const [{ value: showRemixesValue }, , { setValue: setShowRemixesValue }] = + useField(SHOW_REMIXES) const [{ value: remixOfValue }, , { setValue: setRemixOfValue }] = useField(REMIX_OF) @@ -90,7 +90,7 @@ export const RemixModalForm = () => { const initialValues = useMemo(() => { const initialValues = {} - set(initialValues, HIDE_REMIXES, hideRemixesValue) + set(initialValues, SHOW_REMIXES, showRemixesValue) set( initialValues, IS_REMIX, @@ -98,7 +98,7 @@ export const RemixModalForm = () => { ) set(initialValues, REMIX_LINK, remixLink) return initialValues as RemixFormValues - }, [hideRemixesValue, remixLink, remixOfValue?.tracks]) + }, [showRemixesValue, remixLink, remixOfValue?.tracks]) const [url, setUrl] = useState() @@ -110,7 +110,7 @@ export const RemixModalForm = () => { const onSubmit = useCallback( (values: RemixFormValues) => { - setHideRemixesValue(get(values, HIDE_REMIXES)) + setShowRemixesValue(get(values, SHOW_REMIXES)) if (get(values, IS_REMIX) && get(values, REMIX_LINK)) { // TODO: handle undefined linkedTrack with form validation setRemixOfValue({ @@ -123,7 +123,7 @@ export const RemixModalForm = () => { }) } }, - [linkedTrack?.track_id, setHideRemixesValue, setRemixOfValue] + [linkedTrack?.track_id, setShowRemixesValue, setRemixOfValue] ) const preview = ( @@ -171,13 +171,14 @@ const RemixModalFields = (props: RemixModalFieldsProps) => { return (
- - { /> {/* @ts-ignore TDOO: need to populate track with cover art sizes */} {track ? : null} - +
) } diff --git a/packages/web/src/pages/upload-page/fields/SourceFilesModalForm.module.css b/packages/web/src/pages/upload-page/forms/SourceFilesModalForm.module.css similarity index 100% rename from packages/web/src/pages/upload-page/fields/SourceFilesModalForm.module.css rename to packages/web/src/pages/upload-page/forms/SourceFilesModalForm.module.css diff --git a/packages/web/src/pages/upload-page/fields/SourceFilesModalForm.tsx b/packages/web/src/pages/upload-page/forms/SourceFilesModalForm.tsx similarity index 96% rename from packages/web/src/pages/upload-page/fields/SourceFilesModalForm.tsx rename to packages/web/src/pages/upload-page/forms/SourceFilesModalForm.tsx index 85f93eb048..d3e8ee775b 100644 --- a/packages/web/src/pages/upload-page/fields/SourceFilesModalForm.tsx +++ b/packages/web/src/pages/upload-page/forms/SourceFilesModalForm.tsx @@ -7,15 +7,15 @@ import { get, set } from 'lodash' import { ReactComponent as IconSourceFiles } from 'assets/img/iconSourceFiles.svg' import { Divider } from 'components/divider' -import { processFiles } from '../store/utils/processFiles' - -import { ModalField } from './ModalField' -import styles from './SourceFilesModalForm.module.css' +import { ModalField } from '../fields/ModalField' import { SourceFilesView, dropdownRows as stemCategories -} from './SourceFilesView' -import { ToggleRowField } from './ToggleRowField' +} from '../fields/SourceFilesView' +import { SwitchRowField } from '../fields/SwitchRowField' +import { processFiles } from '../store/utils/processFiles' + +import styles from './SourceFilesModalForm.module.css' const ALLOW_DOWNLOAD = 'download.is_downloadable' const FOLLOWER_GATED = 'download.requires_follow' @@ -140,7 +140,7 @@ const SourceFilesModalFiels = () => {
{messages.description}
- { }} /> - + [SPECIAL_ACCESS_TYPE]: Nullable + [FIELD_VISIBILITY]: Nullable +} + +/** + * A modal that allows you to set a track as collectible-gated, special access, or unlisted, + * as well as toggle individual unlisted metadata field visibility. + */ +export const TrackAvailabilityModalForm = () => { + // Fields from the outer form + const [{ value: isUnlistedValue }, , { setValue: setIsUnlistedValue }] = + useField(IS_UNLISTED) + const [{ value: isPremiumValue }, , { setValue: setIsPremiumValue }] = + useField(IS_PREMIUM) + const [ + { value: premiumConditionsValue }, + , + { setValue: setPremiumConditionsValue } + ] = useField(PREMIUM_CONDITIONS) + const [ + { value: fieldVisibilityValue }, + , + { setValue: setFieldVisibilityValue } + ] = useField(FIELD_VISIBILITY) + const [{ value: remixOfValue }] = + useField(REMIX_OF) + const isRemix = !isEmpty(remixOfValue?.tracks) + + const initialValues = useMemo(() => { + const initialValues = {} + set(initialValues, IS_UNLISTED, isUnlistedValue) + set(initialValues, IS_PREMIUM, isPremiumValue) + set(initialValues, PREMIUM_CONDITIONS, premiumConditionsValue) + + let availabilityType = TrackAvailabilityType.PUBLIC + if ( + premiumConditionsValue?.follow_user_id || + premiumConditionsValue?.tip_user_id + ) { + availabilityType = TrackAvailabilityType.SPECIAL_ACCESS + } + if (premiumConditionsValue?.nft_collection) { + availabilityType = TrackAvailabilityType.COLLECTIBLE_GATED + } + if ( + // Remixes has its own toggle field so should not affect the selected availability type + !isEqual(omit(fieldVisibilityValue, 'remixes'), defaultHiddenFields) + ) { + availabilityType = TrackAvailabilityType.HIDDEN + } + // TODO: USDC gated type + set(initialValues, AVAILABILITY_TYPE, availabilityType) + + set(initialValues, FIELD_VISIBILITY, fieldVisibilityValue) + set( + initialValues, + SPECIAL_ACCESS_TYPE, + premiumConditionsValue?.tip_user_id + ? SpecialAccessType.TIP + : SpecialAccessType.FOLLOW + ) + return initialValues as TrackAvailabilityFormValues + }, [ + fieldVisibilityValue, + isPremiumValue, + isUnlistedValue, + premiumConditionsValue + ]) + + const onSubmit = useCallback( + (values: TrackAvailabilityFormValues) => { + setPremiumConditionsValue(get(values, PREMIUM_CONDITIONS)) + if (values[PREMIUM_CONDITIONS]) { + setIsPremiumValue(true) + } + setFieldVisibilityValue(get(values, FIELD_VISIBILITY) ?? undefined) + if (values[AVAILABILITY_TYPE] === TrackAvailabilityType.HIDDEN) { + setIsUnlistedValue(true) + } else { + setFieldVisibilityValue(defaultFieldVisibility) + } + }, + [ + setFieldVisibilityValue, + setIsPremiumValue, + setIsUnlistedValue, + setPremiumConditionsValue + ] + ) + + const preview = ( +
+
+ +
+
{messages.description}
+ {/* TODO: Rich preview display */} +
+ ) + + return ( + + initialValues={initialValues} + onSubmit={onSubmit} + enableReinitialize + > + } + preview={preview} + > + + + + ) +} + +type TrackAvailabilityFieldsProps = { + premiumConditions: EditFormValues[typeof PREMIUM_CONDITIONS] + isRemix: boolean +} + +const TrackAvailabilityFields = (props: TrackAvailabilityFieldsProps) => { + const { isRemix } = props + const accountUserId = useSelector(getUserId) + const { isEnabled: isCollectibleGatedEnabled } = useFlag( + FeatureFlags.COLLECTIBLE_GATED_ENABLED + ) + const { isEnabled: isSpecialAccessEnabled } = useFlag( + FeatureFlags.SPECIAL_ACCESS_ENABLED + ) + const [ + { value: premiumConditionsValue }, + , + { setValue: setPremiumConditionsValue } + ] = + useField( + PREMIUM_CONDITIONS + ) + const [ + { value: fieldVisibilityValue }, + , + { setValue: setfieldVisibilityValue } + ] = + useField( + FIELD_VISIBILITY + ) + + const [availabilityField, , { setValue: setAvailabilityValue }] = useField({ + name: AVAILABILITY_TYPE + }) + const { ethCollectionMap, solCollectionMap } = useSelector( + getSupportedUserCollections + ) + const numEthCollectibles = Object.keys(ethCollectionMap).length + const numSolCollectibles = Object.keys(solCollectionMap).length + const hasCollectibles = numEthCollectibles + numSolCollectibles > 0 + + const noCollectibleGate = !hasCollectibles + const noSpecialAccess = isRemix + + const handleChange = useCallback( + (e: ChangeEvent) => { + const type = e.target.value as TrackAvailabilityType + switch (type) { + case TrackAvailabilityType.PUBLIC: { + setPremiumConditionsValue(null) + break + } + case TrackAvailabilityType.SPECIAL_ACCESS: { + if (!accountUserId || premiumConditionsValue?.tip_user_id) break + setPremiumConditionsValue({ follow_user_id: accountUserId }) + break + } + case TrackAvailabilityType.COLLECTIBLE_GATED: + if (!accountUserId || premiumConditionsValue?.nft_collection) break + setPremiumConditionsValue(null) + break + case TrackAvailabilityType.HIDDEN: + if (!fieldVisibilityValue) break + setfieldVisibilityValue({ + ...fieldVisibilityValue, + ...defaultHiddenFields + }) + break + } + setAvailabilityValue(type) + }, + [ + accountUserId, + fieldVisibilityValue, + premiumConditionsValue, + setAvailabilityValue, + setPremiumConditionsValue, + setfieldVisibilityValue + ] + ) + + return ( + <> + {isRemix ? ( + + ) : null} + + } + label={messages.public} + description={messages.publicSubtitle} + value={TrackAvailabilityType.PUBLIC} + /> + {isSpecialAccessEnabled ? ( + } + label={messages.specialAccess} + description={messages.specialAccessSubtitle} + value={TrackAvailabilityType.SPECIAL_ACCESS} + disabled={noSpecialAccess} + checkedContent={} + /> + ) : null} + {isCollectibleGatedEnabled ? ( + } + label={messages.collectibleGated} + value={TrackAvailabilityType.COLLECTIBLE_GATED} + disabled={noCollectibleGate} + description={ + + } + checkedContent={ + + } + /> + ) : null} + } + label={messages.hidden} + value={TrackAvailabilityType.HIDDEN} + description={messages.hiddenSubtitle} + checkedContent={} + /> + + + ) +} diff --git a/packages/web/src/services/audius-backend/audius-backend-instance.ts b/packages/web/src/services/audius-backend/audius-backend-instance.ts index 43c3270880..6f9d8b16b7 100644 --- a/packages/web/src/services/audius-backend/audius-backend-instance.ts +++ b/packages/web/src/services/audius-backend/audius-backend-instance.ts @@ -91,7 +91,6 @@ export const audiusBackendInstance = audiusBackend({ generalAdmissionUrl: process.env.REACT_APP_GENERAL_ADMISSION, isElectron: isElectron(), isMobile: isMobile(), - legacyUserNodeUrl: process.env.REACT_APP_LEGACY_USER_NODE, monitoringCallbacks, nativeMobile: false, onLibsInit: (libs: AudiusLibs) => { diff --git a/packages/web/src/services/audius-sdk/discoveryNodeSelector.ts b/packages/web/src/services/audius-sdk/discoveryNodeSelector.ts index f657cacf40..a16addc98b 100644 --- a/packages/web/src/services/audius-sdk/discoveryNodeSelector.ts +++ b/packages/web/src/services/audius-sdk/discoveryNodeSelector.ts @@ -3,7 +3,69 @@ import { DiscoveryNodeSelectorService } from '@audius/common' import { env } from '../env' import { remoteConfigInstance } from '../remote-config/remote-config-instance' +const DISCOVERY_PROVIDER_TIMESTAMP = '@audius/libs:discovery-node-timestamp' +const CACHE_TTL = 24 * 60 * 1000 // 24 hours + +type CachedDiscoveryNodeTimestamp = + | { + endpoint?: string + timestamp?: number + } + | null + | undefined + +const getCachedDiscoveryNode = () => { + const cached = localStorage.getItem(DISCOVERY_PROVIDER_TIMESTAMP) + if (cached) { + try { + const cachedDiscoveryNodeTimestamp = JSON.parse( + cached + ) as CachedDiscoveryNodeTimestamp + if ( + cachedDiscoveryNodeTimestamp?.timestamp && + cachedDiscoveryNodeTimestamp.timestamp > + new Date().getTime() - CACHE_TTL + ) { + const cachedDiscoveryNode = + cachedDiscoveryNodeTimestamp.endpoint ?? undefined + console.debug( + '[discovery-node-selector-service] Using cached discovery node', + cachedDiscoveryNode + ) + return cachedDiscoveryNode + } else { + console.debug( + '[discovery-node-selector-service] Cached discovery node expired', + cachedDiscoveryNodeTimestamp + ) + } + } catch (e) { + console.error( + "[discovery-node-selector-service] Couldn't parse cached discovery node", + e + ) + } + } +} + +const updateCachedDiscoveryNode = (endpoint: string) => { + const newTimestamp: CachedDiscoveryNodeTimestamp = { + endpoint, + timestamp: new Date().getTime() + } + console.debug( + '[discovery-node-selector-service] Updating cached discovery provider', + newTimestamp + ) + localStorage.setItem( + DISCOVERY_PROVIDER_TIMESTAMP, + JSON.stringify(newTimestamp) + ) +} + export const discoveryNodeSelectorService = new DiscoveryNodeSelectorService({ env, - remoteConfigInstance + remoteConfigInstance, + initialSelectedNode: getCachedDiscoveryNode(), + onChange: updateCachedDiscoveryNode }) diff --git a/packages/web/src/utils/route.ts b/packages/web/src/utils/route.ts index a59bb8e723..43d9f3def4 100644 --- a/packages/web/src/utils/route.ts +++ b/packages/web/src/utils/route.ts @@ -124,6 +124,8 @@ export const AUDIUS_REMIX_CONTESTS_LINK = 'https://remix.audius.co/' export const AUDIUS_BLOG_LINK = 'https://blog.audius.co/' export const AUDIUS_AI_BLOG_LINK = 'https://help.audius.co/help/What-should-I-know-about-AI-generated-music-on-Audius' +export const AUDIUS_GATED_CONTENT_BLOG_LINK = + 'https://blog.audius.co/article/introducing-nft-collectible-gated-content' // Org Links export const AUDIUS_ORG = 'https://audius.org'