diff --git a/packages/sdk/src/streamStateView_Space.ts b/packages/sdk/src/streamStateView_Space.ts index 58a567351..e1cbb0528 100644 --- a/packages/sdk/src/streamStateView_Space.ts +++ b/packages/sdk/src/streamStateView_Space.ts @@ -32,7 +32,7 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent { readonly streamId: string readonly spaceChannelsMetadata = new Map() private spaceImage: ChunkedMedia | undefined - private encryptedSpaceImage: EncryptedData | undefined + public encryptedSpaceImage: { eventId: string; data: EncryptedData } | undefined private decryptionInProgress: | { encryptedData: EncryptedData; promise: Promise } | undefined @@ -55,7 +55,7 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent { } if (content.spaceImage?.data) { - this.encryptedSpaceImage = content.spaceImage.data + this.encryptedSpaceImage = { data: content.spaceImage.data, eventId: eventHash } } } @@ -125,7 +125,7 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent { ) break case 'spaceImage': - this.encryptedSpaceImage = payload.content.value + this.encryptedSpaceImage = { data: payload.content.value, eventId: event.hashStr } stateEmitter?.emit('spaceImageUpdated', this.streamId) break case undefined: @@ -138,7 +138,7 @@ export class StreamStateView_Space extends StreamStateView_AbstractContent { public async getSpaceImage(): Promise { // if we have an encrypted space image, decrypt it if (this.encryptedSpaceImage) { - const encryptedData = this.encryptedSpaceImage + const encryptedData = this.encryptedSpaceImage?.data this.encryptedSpaceImage = undefined this.decryptionInProgress = { promise: this.decryptSpaceImage(encryptedData), diff --git a/packages/stream-metadata/src/node.ts b/packages/stream-metadata/src/node.ts index 7b1724fd3..5bcfce8f2 100644 --- a/packages/stream-metadata/src/node.ts +++ b/packages/stream-metadata/src/node.ts @@ -109,6 +109,7 @@ export function setupRoutes(srv: Server) { srv.get('/media/:mediaStreamId', fetchMedia) srv.get('/user/:userId/image', fetchUserProfileImage) srv.get('/space/:spaceAddress/image', fetchSpaceImage) + srv.get('/space/:spaceAddress/image/:eventId', fetchSpaceImage) srv.get('/space/:spaceAddress', fetchSpaceMetadata) srv.get('/space/:spaceAddress/token/:tokenId', fetchSpaceMemberMetadata) srv.get('/user/:userId/bio', fetchUserBio) diff --git a/packages/stream-metadata/src/routes/spaceImage.ts b/packages/stream-metadata/src/routes/spaceImage.ts index f3874af77..e23c93d22 100644 --- a/packages/stream-metadata/src/routes/spaceImage.ts +++ b/packages/stream-metadata/src/routes/spaceImage.ts @@ -16,6 +16,7 @@ const paramsSchema = z.object({ .refine(isValidEthereumAddress, { message: 'Invalid spaceAddress format', }), + eventId: z.string().optional(), }) const CACHE_CONTROL = { @@ -40,8 +41,8 @@ export async function fetchSpaceImage(request: FastifyRequest, reply: FastifyRep .send({ error: 'Bad Request', message: errorMessage }) } - const { spaceAddress } = parseResult.data - logger.info({ spaceAddress }, 'Fetching space image') + const { spaceAddress, eventId } = parseResult.data + logger.info({ spaceAddress, eventId }, 'Fetching space image') let stream: StreamStateView try { diff --git a/packages/stream-metadata/src/routes/spaceMemberMetadata.ts b/packages/stream-metadata/src/routes/spaceMemberMetadata.ts index 4edd83b47..5e3775c60 100644 --- a/packages/stream-metadata/src/routes/spaceMemberMetadata.ts +++ b/packages/stream-metadata/src/routes/spaceMemberMetadata.ts @@ -1,9 +1,11 @@ -import { FastifyReply, FastifyRequest } from 'fastify' +import { FastifyReply, FastifyRequest, type FastifyBaseLogger } from 'fastify' import { z } from 'zod' +import { StreamPrefix, makeStreamId } from '@river-build/sdk' import { config } from '../environment' import { isValidEthereumAddress } from '../validators' import { spaceDapp } from '../contract-utils' +import { getStream } from '../riverStreamRpcClient' const paramsSchema = z.object({ spaceAddress: z @@ -46,7 +48,7 @@ export async function fetchSpaceMemberMetadata(request: FastifyRequest, reply: F const { spaceAddress, tokenId } = parseResult.data try { - const metadata = await getSpaceMemberMetadata(spaceAddress, tokenId) + const metadata = await getSpaceMemberMetadata(logger, spaceAddress, tokenId) return reply .header('Content-Type', 'application/json') .header('Cache-Control', CACHE_CONTROL[200]) @@ -61,6 +63,7 @@ export async function fetchSpaceMemberMetadata(request: FastifyRequest, reply: F } const getSpaceMemberMetadata = async ( + logger: FastifyBaseLogger, spaceAddress: string, tokenId: string, ): Promise => { @@ -69,6 +72,20 @@ const getSpaceMemberMetadata = async ( throw new Error('Space contract not found') } + let imageEventId: string = 'default' + try { + const streamId = makeStreamId(StreamPrefix.Space, spaceAddress) + const streamView = await getStream(logger, streamId) + if ( + streamView.contentKind === 'spaceContent' && + streamView.spaceContent.encryptedSpaceImage?.eventId + ) { + imageEventId = streamView.spaceContent.encryptedSpaceImage.eventId + } + } catch (error) { + // no-op + } + const [name, renewalPrice, membershipExpiration, isBanned] = await Promise.all([ space.SpaceOwner.read.getSpaceInfo(spaceAddress).then((spaceInfo) => spaceInfo.name), space.Membership.read.getMembershipRenewalPrice(tokenId), @@ -79,7 +96,7 @@ const getSpaceMemberMetadata = async ( return { name: `${name} - Member`, description: `Member of ${name}`, - image: `${config.streamMetadataBaseUrl}/space/${spaceAddress}/image`, + image: `${config.streamMetadataBaseUrl}/space/${spaceAddress}/image/${imageEventId}`, attributes: [ { trait_type: 'Renewal Price', diff --git a/packages/stream-metadata/src/routes/spaceMetadata.ts b/packages/stream-metadata/src/routes/spaceMetadata.ts index fc181efeb..138a53fe3 100644 --- a/packages/stream-metadata/src/routes/spaceMetadata.ts +++ b/packages/stream-metadata/src/routes/spaceMetadata.ts @@ -1,10 +1,12 @@ import { FastifyReply, FastifyRequest } from 'fastify' import { SpaceInfo } from '@river-build/web3' import { z } from 'zod' +import { makeStreamId, StreamPrefix } from '@river-build/sdk' import { config } from '../environment' import { isValidEthereumAddress } from '../validators' import { spaceDapp } from '../contract-utils' +import { getStream } from '../riverStreamRpcClient' export const spaceMetadataBaseUrl = `${config.streamMetadataBaseUrl}/space`.toLowerCase() @@ -67,10 +69,24 @@ export async function fetchSpaceMetadata(request: FastifyRequest, reply: Fastify .send({ error: 'Not Found', message: 'Space contract not found' }) } + let imageEventId: string = 'default' + try { + const streamId = makeStreamId(StreamPrefix.Space, spaceAddress) + const streamView = await getStream(logger, streamId) + if ( + streamView.contentKind === 'spaceContent' && + streamView.spaceContent.encryptedSpaceImage?.eventId + ) { + imageEventId = streamView.spaceContent.encryptedSpaceImage.eventId + } + } catch (error) { + // no-op + } + // Normalize the contractUri for case-insensitive comparison and handle empty string const defaultSpaceTokenUri = `${spaceMetadataBaseUrl}/${spaceAddress}` - const image = `${defaultSpaceTokenUri}/image` + const image = `${defaultSpaceTokenUri}/image/${imageEventId}` const spaceMetadata: SpaceMetadataResponse = { name: spaceInfo.name, description: getSpaceDecription(spaceInfo), diff --git a/packages/stream-metadata/src/routes/spaceRefresh.ts b/packages/stream-metadata/src/routes/spaceRefresh.ts index 139b5c393..0aef59604 100644 --- a/packages/stream-metadata/src/routes/spaceRefresh.ts +++ b/packages/stream-metadata/src/routes/spaceRefresh.ts @@ -43,7 +43,7 @@ export async function spaceRefreshOnResponse( try { await CloudfrontManager.createCloudfrontInvalidation({ - paths: [`/space/${spaceAddress}/image`], + paths: [`/space/${spaceAddress}/image*`], logger, waitUntilFinished: true, })