···11-/**
22- * Utility functions for normalizing embeds to ensure CID objects are converted to $link format
33- * This ensures consistent storage and retrieval of embed data across the application.
44- */
55-66-interface CidRef {
77- $link?: string;
88- code?: number;
99- version?: number;
1010- multihash?: Uint8Array;
1111- bytes?: string;
1212- toString?: () => string;
1313-}
1414-1515-interface NormalizedCidRef {
1616- $link: string;
1717-}
1818-1919-interface VideoEmbed {
2020- $type: "so.sprk.embed.video";
2121- video?: {
2222- $type: "blob";
2323- ref: CidRef;
2424- mimeType?: string;
2525- size?: number;
2626- };
2727-}
2828-2929-interface ImageEmbed {
3030- $type: "so.sprk.embed.images";
3131- images?: Array<{
3232- image: {
3333- $type: "blob";
3434- ref: CidRef;
3535- mimeType?: string;
3636- size?: number;
3737- };
3838- alt?: string;
3939- aspectRatio?: number;
4040- }>;
4141-}
4242-4343-interface Profile {
4444- avatar?: {
4545- $type?: "blob";
4646- ref?: NormalizedCidRef | null;
4747- } | CidRef;
4848- banner?: {
4949- $type?: "blob";
5050- ref?: NormalizedCidRef | null;
5151- } | CidRef;
5252- [key: string]: unknown;
5353-}
5454-5555-// Normalize embed to ensure CID objects are converted to $link format
5656-export function normalizeEmbed(embed: unknown): unknown {
5757- if (!embed || typeof embed !== "object") return embed;
5858-5959- const embedObj = embed as Record<string, unknown>;
6060-6161- if (
6262- embedObj.$type === "so.sprk.media.video" && embedObj.video &&
6363- typeof embedObj.video === "object"
6464- ) {
6565- const video = embedObj.video as Record<string, unknown>;
6666- if (video.ref) {
6767- const ref = video.ref;
6868- // If ref is a CID object (has code/version/multihash), convert to $link
6969- if (
7070- typeof ref === "object" && ref && !(ref as CidRef).$link &&
7171- ((ref as CidRef).code || (ref as CidRef).version ||
7272- (ref as CidRef).multihash)
7373- ) {
7474- const toStringFn = (ref as CidRef).toString;
7575-7676- if (toStringFn && typeof toStringFn === "function") {
7777- const cidString = toStringFn.call(ref);
7878- // Return cleaned up structure without 'original' field
7979- return {
8080- $type: "so.sprk.media.video",
8181- video: {
8282- $type: "blob",
8383- ref: { $link: cidString },
8484- mimeType: video.mimeType,
8585- size: video.size,
8686- },
8787- };
8888- } else {
8989- console.error("DEBUG: Could not convert CID object to string:", ref);
9090- return embed; // Return original if we can't convert
9191- }
9292- } else if ((ref as CidRef).$link) {
9393- // Already normalized, return cleaned up structure
9494- return {
9595- $type: "so.sprk.media.video",
9696- video: {
9797- $type: "blob",
9898- ref: { $link: (ref as CidRef).$link },
9999- mimeType: video.mimeType,
100100- size: video.size,
101101- },
102102- };
103103- }
104104- }
105105- }
106106-107107- if (
108108- embedObj.$type === "so.sprk.media.images" && Array.isArray(embedObj.images)
109109- ) {
110110- const normalizedImages = embedObj.images.map((img: unknown) => {
111111- if (
112112- typeof img === "object" && img && (img as Record<string, unknown>).image
113113- ) {
114114- const image = (img as Record<string, unknown>).image as Record<
115115- string,
116116- unknown
117117- >;
118118- if (image.ref) {
119119- const ref = image.ref;
120120- if (
121121- typeof ref === "object" && ref && !(ref as CidRef).$link &&
122122- ((ref as CidRef).code || (ref as CidRef).version ||
123123- (ref as CidRef).multihash)
124124- ) {
125125- const toStringFn = (ref as CidRef).toString;
126126-127127- if (toStringFn && typeof toStringFn === "function") {
128128- const cidString = toStringFn.call(ref);
129129- return {
130130- image: {
131131- $type: "blob",
132132- ref: { $link: cidString },
133133- mimeType: image.mimeType,
134134- size: image.size,
135135- },
136136- alt: (img as Record<string, unknown>).alt,
137137- aspectRatio: (img as Record<string, unknown>).aspectRatio,
138138- };
139139- } else {
140140- console.error(
141141- "DEBUG: Could not convert CID object to string:",
142142- ref,
143143- );
144144- return img;
145145- }
146146- } else if ((ref as CidRef).$link) {
147147- // Already normalized
148148- return {
149149- image: {
150150- $type: "blob",
151151- ref: { $link: (ref as CidRef).$link },
152152- mimeType: image.mimeType,
153153- size: image.size,
154154- },
155155- alt: (img as Record<string, unknown>).alt,
156156- aspectRatio: (img as Record<string, unknown>).aspectRatio,
157157- };
158158- }
159159- }
160160- }
161161- return img;
162162- });
163163-164164- return {
165165- $type: "so.sprk.media.images",
166166- images: normalizedImages,
167167- };
168168- }
169169-170170- return embed;
171171-}
172172-173173-// Normalize a single CID reference to $link format
174174-export function normalizeCidRef(
175175- ref: CidRef | null | undefined,
176176-): NormalizedCidRef | null | undefined {
177177- if (!ref) return ref;
178178-179179- // If it's already in $link format, return as-is
180180- if (typeof ref === "object" && ref.$link) {
181181- return ref as NormalizedCidRef;
182182- }
183183-184184- // If it's a CID object (has code/version/multihash), convert to $link
185185- if (
186186- typeof ref === "object" && !ref.$link &&
187187- (ref.code || ref.version || ref.multihash)
188188- ) {
189189- let cidString: string;
190190- const toStringFn = ref.toString;
191191-192192- if (toStringFn && typeof toStringFn === "function") {
193193- cidString = toStringFn.call(ref);
194194- } else {
195195- console.error("DEBUG: Could not convert CID object to string:", ref);
196196- return ref as NormalizedCidRef; // Return original if we can't convert
197197- }
198198-199199- return { $link: cidString };
200200- }
201201-202202- // If it's a string, wrap it in $link format
203203- if (typeof ref === "string") {
204204- return { $link: ref };
205205- }
206206-207207- return ref as NormalizedCidRef;
208208-}
209209-210210-// Normalize profile data to ensure any CID references are converted to $link format
211211-export function normalizeProfile(profile: unknown): unknown {
212212- if (!profile || typeof profile !== "object") return profile;
213213-214214- const normalized: Record<string, unknown> = {
215215- ...profile as Record<string, unknown>,
216216- };
217217-218218- // Normalize avatar if present
219219- if (normalized.avatar) {
220220- // If avatar is a BlobRef (has ref property), normalize the ref
221221- if (
222222- typeof normalized.avatar === "object" && normalized.avatar &&
223223- "ref" in normalized.avatar
224224- ) {
225225- const normalizedRef = normalizeCidRef(
226226- (normalized.avatar as Record<string, unknown>).ref as CidRef,
227227- );
228228- if (normalizedRef) {
229229- // Return only the MediaRef format: { $type: "blob", ref: { $link: string } }
230230- normalized.avatar = {
231231- $type: "blob",
232232- ref: normalizedRef,
233233- };
234234- }
235235- } else {
236236- // If avatar is just a CID object, wrap it in the proper structure
237237- const normalizedRef = normalizeCidRef(normalized.avatar as CidRef);
238238- if (normalizedRef) {
239239- normalized.avatar = {
240240- $type: "blob",
241241- ref: normalizedRef,
242242- };
243243- }
244244- }
245245- }
246246-247247- // Normalize banner if present
248248- if (normalized.banner) {
249249- // If banner is a BlobRef (has ref property), normalize the ref
250250- if (
251251- typeof normalized.banner === "object" && normalized.banner &&
252252- "ref" in normalized.banner
253253- ) {
254254- const normalizedRef = normalizeCidRef(
255255- (normalized.banner as Record<string, unknown>).ref as CidRef,
256256- );
257257- if (normalizedRef) {
258258- // Return only the MediaRef format: { $type: "blob", ref: { $link: string } }
259259- normalized.banner = {
260260- $type: "blob",
261261- ref: normalizedRef,
262262- };
263263- }
264264- } else {
265265- // If banner is just a CID object, wrap it in the proper structure
266266- const normalizedRef = normalizeCidRef(normalized.banner as CidRef);
267267- if (normalizedRef) {
268268- normalized.banner = {
269269- $type: "blob",
270270- ref: normalizedRef,
271271- };
272272- }
273273- }
274274- }
275275-276276- return normalized;
277277-}
278278-279279-// Normalize any object that might contain CID references
280280-export function normalizeObject(obj: unknown): unknown {
281281- if (!obj || typeof obj !== "object") return obj;
282282-283283- if (Array.isArray(obj)) {
284284- return obj.map((item) => normalizeObject(item));
285285- }
286286-287287- const normalized: Record<string, unknown> = {};
288288-289289- for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
290290- if (
291291- key === "ref" && typeof value === "object" && value &&
292292- !(value as CidRef).$link &&
293293- ((value as CidRef).code || (value as CidRef).version ||
294294- (value as CidRef).multihash)
295295- ) {
296296- // This looks like a CID object, normalize it
297297- normalized[key] = normalizeCidRef(value as CidRef);
298298- } else if (typeof value === "object" && value !== null) {
299299- // Recursively normalize nested objects
300300- normalized[key] = normalizeObject(value);
301301- } else {
302302- // Keep primitive values as-is
303303- normalized[key] = value;
304304- }
305305- }
306306-307307- return normalized;
308308-}
+22-3
utils/media-transformer.ts
···22import {
33 ImageMedia,
44 PostMedia,
55+ StoryMedia,
56 VideoMappingDocument,
67} from "../data-plane/db/models.ts";
78import { ServerConfig } from "../config.ts";
···8081}
81828283export function transformMedia(
8383- media: PostMedia | undefined,
8484+ media: PostMedia | StoryMedia | undefined,
8485 authorDid: string,
8586 cfg: ServerConfig,
8687 videoMapping?: VideoMappingDocument | null,
···9293 }
93949495 if (media.$type === "so.sprk.media.images") {
9595- return transformImagesMedia(media, authorDid, options);
9696+ return transformImagesMedia(media as PostMedia, authorDid, options);
9797+ }
9898+9999+ if (media.$type === "so.sprk.media.image") {
100100+ // Handle single image (used in stories and replies)
101101+ const singleImageMedia = media as StoryMedia;
102102+ if (!singleImageMedia.image) {
103103+ return undefined;
104104+ }
105105+106106+ return {
107107+ $type: "so.sprk.media.image#view",
108108+ thumb:
109109+ `https://media.sprk.so/img/medium/${authorDid}/${singleImageMedia.image.ref.$link}/webp`,
110110+ fullsize:
111111+ `https://media.sprk.so/img/full/${authorDid}/${singleImageMedia.image.ref.$link}/webp`,
112112+ alt: singleImageMedia.image.alt ?? "",
113113+ aspectRatio: singleImageMedia.image.aspectRatio || undefined,
114114+ } as const;
96115 }
9711698117 if (media.$type === "so.sprk.media.video") {
99118 return transformVideoMedia(
100100- media,
119119+ media as PostMedia,
101120 authorDid,
102121 cfg,
103122 videoMapping,
+1-14
utils/uris.ts
···3232}
33333434export function uriToDid(uri: string) {
3535- try {
3636- return new AtUri(uri).hostname;
3737- } catch (error) {
3838- console.log(`AtUri parser failed for URI: ${uri}, error:`, error);
3939- // Handle custom collection namespaces that AtUri might not recognize
4040- // Extract DID from URI manually as fallback
4141- const match = uri.match(/^at:\/\/(did:[^\/]+)/);
4242- if (match) {
4343- console.log(`Successfully extracted DID using fallback: ${match[1]}`);
4444- return match[1];
4545- }
4646- console.error(`Failed to extract DID from URI: ${uri}`);
4747- throw new Error(`Invalid AT URI format: ${uri}`);
4848- }
3535+ return new AtUri(uri).hostname;
4936}
50375138// @TODO temp fix for proliferation of invalid pinned post values