diff --git a/e2e/profiles.spec.ts b/e2e/profiles.spec.ts index 41396d0..25ec7c6 100644 --- a/e2e/profiles.spec.ts +++ b/e2e/profiles.spec.ts @@ -29,7 +29,7 @@ test.describe("Given a Publication link posted on a social media website/app", a await v1Profile.open(); expect(await v1Profile.extractOpenGraphProperties()).toMatchObject({ - "og:title": `@${v1Profile.handle} profile`, + "og:title": `${v1Profile.handle} profile`, "og:description": "The Social Layer for Web3 🌿", "og:url": expect.stringContaining(`/u/${v1Profile.handle}`), "og:site_name": "Lens Share", @@ -43,7 +43,7 @@ test.describe("Given a Publication link posted on a social media website/app", a expect(await v1Profile.extractTwitterMetaTags()).toEqual({ "twitter:card": "summary_large_image", "twitter:site": "LensProtocol", - "twitter:title": `@${v1Profile.handle} profile`, + "twitter:title": `${v1Profile.handle} profile`, "twitter:description": "The Social Layer for Web3 🌿", "twitter:image": expect.any(String), "twitter:image:type": "image/png", diff --git a/e2e/publications.spec.ts b/e2e/publications.spec.ts index 70d0236..f5a59ed 100644 --- a/e2e/publications.spec.ts +++ b/e2e/publications.spec.ts @@ -26,7 +26,7 @@ test.describe("Given a Publication link posted on a social media website/app", a await textPost.open(); expect(await textPost.extractOpenGraphProperties()).toEqual({ - "og:title": "Post by @stani.lens", + "og:title": "Post by lens/stani", "og:description": "This post will age well.", "og:url": expect.stringContaining(`/p/${textPost.publicationId}`), "og:site_name": "Lens Share", @@ -42,7 +42,7 @@ test.describe("Given a Publication link posted on a social media website/app", a expect(await textPost.extractTwitterMetaTags()).toEqual({ "twitter:card": "summary", "twitter:site": "LensProtocol", - "twitter:title": "Post by @stani.lens", + "twitter:title": "Post by lens/stani", "twitter:description": "This post will age well.", }); }); @@ -53,8 +53,7 @@ test.describe("Given a Publication link posted on a social media website/app", a await imagePost.open(); expect(await imagePost.extractOpenGraphProperties()).toMatchObject({ - "og:image": - "https://ipfs-2.thirdwebcdn.com/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF", + "og:image": "https://gw.ipfs-lens.dev/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF", "og:image:type": "image/jpeg", }); }); @@ -67,7 +66,7 @@ test.describe("Given a Publication link posted on a social media website/app", a expect(await imagePost.extractTwitterMetaTags()).toMatchObject({ "twitter:card": "summary_large_image", "twitter:image": - "https://ipfs-2.thirdwebcdn.com/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF", + "https://gw.ipfs-lens.dev/ipfs/QmRxDD6oxyWxtTyJoq52C1nUfuWiA5HiseJwksAXPz24BF", }); }); }); @@ -78,7 +77,7 @@ test.describe("Given a Publication link posted on a social media website/app", a expect(await videoPost.extractOpenGraphProperties()).toMatchObject({ "og:image": - "https://ipfs-2.thirdwebcdn.com/ipfs/bafybeib7o45x6oesq4ziwdatncakvvwqkp6wvur5b45od4fm6gbcbjmce4", + "https://gw.ipfs-lens.dev/ipfs/bafybeib7o45x6oesq4ziwdatncakvvwqkp6wvur5b45od4fm6gbcbjmce4", }); }); }); @@ -118,13 +117,7 @@ test.describe("Given a Video Publication link", async () => { }) => { await videoPost.open(); - await expect(videoPost.options).toHaveText([ - "Buttrfly", - "Collectz", - "Hey", - "Soclly", - "Tape", - ]); + await expect(videoPost.options).toHaveText(["Buttrfly", "Collectz", "Hey", "Soclly", "Tape"]); }); }); }); @@ -134,13 +127,7 @@ test.describe("Given a Publication link with `by` attribution param", async () = test("Then it should show the specified app first", async ({ videoPost }) => { await videoPost.openAsSharedBy("tape"); - await expect(videoPost.options).toHaveText([ - "Tape", - "Buttrfly", - "Collectz", - "Hey", - "Soclly", - ]); + await expect(videoPost.options).toHaveText(["Tape", "Buttrfly", "Collectz", "Hey", "Soclly"]); }); }); diff --git a/package.json b/package.json index fa474b5..89feaa4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "BASE_URL=http://localhost:3000 next dev", "build": "next build", "start": "next start", "lint": "next lint && pnpm run tsc", @@ -13,7 +13,7 @@ }, "dependencies": { "@heroicons/react": "^2.0.18", - "@lens-protocol/client": "2.0.0-alpha.18", + "@lens-protocol/client": "2.0.0-alpha.19", "@lens-protocol/shared-kernel": "0.11.0-alpha.6", "@thirdweb-dev/storage": "^1.2.11", "autoprefixer": "10.4.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4b2c68..3046159 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ dependencies: specifier: ^2.0.18 version: 2.0.18(react@18.2.0) '@lens-protocol/client': - specifier: 2.0.0-alpha.18 - version: 2.0.0-alpha.18 + specifier: 2.0.0-alpha.19 + version: 2.0.0-alpha.19 '@lens-protocol/shared-kernel': specifier: 0.11.0-alpha.6 version: 0.11.0-alpha.6 @@ -566,8 +566,8 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: false - /@lens-protocol/client@2.0.0-alpha.18: - resolution: {integrity: sha512-C5yRGi37mfPdgc/mDvn+OulQIBrhYagzHCf3tZmb+UjjCfb8eHgR7HMeX/HOZDWucx5nWsf03YW04g/dtjQHRA==} + /@lens-protocol/client@2.0.0-alpha.19: + resolution: {integrity: sha512-VU4ypKY9nxnSc1/e6N6U2u/Ejwm518iULZzvzGNHpNF7GAegMFhOOJuhnllbYhUVgKZ0qMbQD+k3SkNdGWGLmA==} engines: {node: ^18.15.0} peerDependencies: '@lens-protocol/metadata': ^1.0.0 diff --git a/src/app/p/[id]/page.tsx b/src/app/p/[id]/page.tsx index 1b01023..bdeb0df 100644 --- a/src/app/p/[id]/page.tsx +++ b/src/app/p/[id]/page.tsx @@ -1,4 +1,4 @@ -import { AnyPublicationMetadataFragment, AnyPublicationFragment } from "@lens-protocol/client"; +import { PublicationMetadataFragment, AnyPublicationFragment } from "@lens-protocol/client"; import truncateMarkdown from "markdown-truncate"; import { Metadata, ResolvingMetadata } from "next"; import { notFound } from "next/navigation"; @@ -52,14 +52,14 @@ export default async function PublicationPage({ params, searchParams }: Publicat ); } -function resolveMetadata(publication: AnyPublicationFragment): AnyPublicationMetadataFragment { +function resolveMetadata(publication: AnyPublicationFragment): PublicationMetadataFragment { if (publication.__typename === "Mirror") { return publication.mirrorOn.metadata; } return publication.metadata; } -function formatPageDescription(metadata: AnyPublicationMetadataFragment) { +function formatPageDescription(metadata: PublicationMetadataFragment) { switch (metadata.__typename) { case "ArticleMetadataV3": case "AudioMetadataV3": @@ -94,24 +94,25 @@ function formatPageTitle(publication: AnyPublicationFragment, attribution: AppMa } async function extractImage( - metadata: AnyPublicationMetadataFragment + metadata: PublicationMetadataFragment ): Promise { switch (metadata.__typename) { - case "ArticleMetadataV3": case "AudioMetadataV3": + case "ImageMetadataV3": + case "StoryMetadataV3": + case "VideoMetadataV3": + return mediaToOpenGraphImage([metadata.asset]); // extract from asset + case "ArticleMetadataV3": case "CheckingInMetadataV3": case "EmbedMetadataV3": case "EventMetadataV3": - case "ImageMetadataV3": case "LinkMetadataV3": case "LiveStreamMetadataV3": case "MintMetadataV3": case "SpaceMetadataV3": case "ThreeDMetadataV3": case "TransactionMetadataV3": - case "VideoMetadataV3": - return mediaToOpenGraphImage(metadata.attachments); - case "StoryMetadataV3": + return mediaToOpenGraphImage(metadata.attachments); // extract from attachments case "TextOnlyMetadataV3": return null; } diff --git a/src/data/findPublicationApps.ts b/src/data/findPublicationApps.ts index 21e8e7d..a918788 100644 --- a/src/data/findPublicationApps.ts +++ b/src/data/findPublicationApps.ts @@ -1,4 +1,4 @@ -import { AnyPublicationFragment, AnyPublicationMetadataFragment } from "@lens-protocol/client"; +import { AnyPublicationFragment, PublicationMetadataFragment } from "@lens-protocol/client"; import { Overwrite } from "@lens-protocol/shared-kernel"; import { AppId, PlatformType, RouteKind } from "@/app/types"; @@ -27,7 +27,7 @@ function supportsPublicationRoute(app: AppManifest): app is WithPublicationRoute function resolvePublicationMainContentFocus( publication: AnyPublicationFragment -): AnyPublicationMetadataFragment { +): PublicationMetadataFragment { if (publication.__typename === "Mirror") { return publication.mirrorOn.metadata; } diff --git a/src/utils/handle.ts b/src/utils/handle.ts index a479953..17be995 100644 --- a/src/utils/handle.ts +++ b/src/utils/handle.ts @@ -1,8 +1,13 @@ const V1_SUFFIX = ".lens"; const V2_NAMESPACE = "lens"; +function startsWithNamespace(handle: string): boolean { + const regex = /^[^/]*\//; + return regex.test(handle); +} + function isV1Handle(handle: string): boolean { - return handle.endsWith(".lens"); + return handle.endsWith(".lens") || !startsWithNamespace(handle); } /** diff --git a/src/utils/metadata.ts b/src/utils/metadata.ts index 7d5715b..b7d436f 100644 --- a/src/utils/metadata.ts +++ b/src/utils/metadata.ts @@ -1,19 +1,12 @@ import { EncryptableImageSetFragment, PublicationMetadataMainFocusType, - PublicationMetadataMediaAudioFragment, - PublicationMetadataMediaImageFragment, - PublicationMetadataMediaVideoFragment, - AnyPublicationMetadataFragment, + PublicationMetadataFragment, + PublicationMetadataMediaFragment, } from "@lens-protocol/client"; import { isImageType, resolveMediaUrl } from "./media"; -export type PublicationMetadataMediaFragment = - | PublicationMetadataMediaAudioFragment - | PublicationMetadataMediaImageFragment - | PublicationMetadataMediaVideoFragment; - export type OGImageDescriptor = { url: string; alt?: string; @@ -27,24 +20,51 @@ function resolveImage(image: EncryptableImageSetFragment) { export function mediaToOpenGraphImage( mediaArray: PublicationMetadataMediaFragment[] | null ): OGImageDescriptor | null { - const imageMedia = - mediaArray?.find((media) => media.__typename === "PublicationMetadataMediaImage") ?? null; + if (!mediaArray || mediaArray.length === 0) return null; - if (!imageMedia || imageMedia.__typename !== "PublicationMetadataMediaImage") { - return null; - } + // filter for media with images + const imageMediaArray = mediaArray.filter((media) => { + switch (media.__typename) { + case "PublicationMetadataMediaImage": + return !!media.image; + case "PublicationMetadataMediaAudio": + return !!media.cover; + case "PublicationMetadataMediaVideo": + return !!media.cover; + } + }); - const image = resolveImage(imageMedia.image); + if (imageMediaArray.length === 0) return null; - return { - url: resolveMediaUrl(image.uri), - alt: imageMedia.altTag ?? undefined, - type: image.mimeType && isImageType(image.mimeType) ? image.mimeType : undefined, - }; + const firstImageMedia = imageMediaArray.pop()!; + + switch (firstImageMedia.__typename) { + case "PublicationMetadataMediaImage": + const i1 = resolveImage(firstImageMedia.image); + return { + url: resolveMediaUrl(i1.uri), + alt: firstImageMedia.altTag ?? undefined, + type: i1.mimeType && isImageType(i1.mimeType) ? i1.mimeType : undefined, + }; + case "PublicationMetadataMediaAudio": + const i2 = resolveImage(firstImageMedia.cover!); + return { + url: resolveMediaUrl(i2.uri), + alt: undefined, + type: i2.mimeType && isImageType(i2.mimeType) ? i2.mimeType : undefined, + }; + case "PublicationMetadataMediaVideo": + const i3 = resolveImage(firstImageMedia.cover!); + return { + url: resolveMediaUrl(i3.uri), + alt: firstImageMedia.altTag ?? undefined, + type: i3.mimeType && isImageType(i3.mimeType) ? i3.mimeType : undefined, + }; + } } export function publicationMetadataToMainFocusType( - metadata: AnyPublicationMetadataFragment + metadata: PublicationMetadataFragment ): PublicationMetadataMainFocusType { switch (metadata.__typename) { case "ArticleMetadataV3":