Live video on the AT Protocol
at natb/command-errors 295 lines 8.1 kB view raw
1import { 2 getPDSServiceEndpoint, 3 resolveDIDDocument, 4} from "@streamplace/components"; 5import { AppStore } from "store"; 6import { StateCreator } from "zustand"; 7import { BlueskySlice } from "./blueskySlice"; 8 9export interface ContentMetadataSlice { 10 creating: boolean; 11 updating: boolean; 12 error: string | null; 13 lastCreatedRecord: any | null; 14 // actions 15 createContentMetadata: (params: { 16 contentWarnings?: string[]; 17 distributionPolicy?: { deleteAfter?: number }; 18 contentRights?: { 19 creator?: string; 20 copyrightNotice?: string; 21 copyrightYear?: number; 22 license?: string; 23 creditLine?: string; 24 }; 25 }) => Promise<void>; 26 updateContentMetadata: (params: { 27 rkey?: string; 28 livestreamRef?: { uri: string; cid: string }; 29 contentWarnings?: string[]; 30 distributionPolicy?: { deleteAfter?: number }; 31 contentRights?: { 32 creator?: string; 33 copyrightNotice?: string; 34 copyrightYear?: number; 35 license?: string; 36 creditLine?: string; 37 }; 38 }) => Promise<void>; 39 getContentMetadata: (params?: { 40 userDid?: string; 41 rkey?: string; 42 }) => Promise<void>; 43 clearError: () => void; 44} 45 46export const createContentMetadataSlice: StateCreator< 47 AppStore, 48 [], 49 [], 50 ContentMetadataSlice 51> = (set, get) => ({ 52 creating: false, 53 updating: false, 54 error: null, 55 lastCreatedRecord: null, 56 57 createContentMetadata: async ({ 58 contentWarnings = [], 59 distributionPolicy = { deleteAfter: undefined }, 60 contentRights = {}, 61 }) => { 62 set({ creating: true, error: null }); 63 try { 64 // need access to bluesky slice - will handle in combined store 65 const state = get() as any; 66 const bluesky: BlueskySlice = state; 67 68 if (!bluesky.pdsAgent) { 69 throw new Error("No agent"); 70 } 71 72 const did = bluesky.oauthSession?.did; 73 if (!did) { 74 throw new Error("No DID"); 75 } 76 77 const metadataRecord = { 78 $type: "place.stream.metadata.configuration", 79 createdAt: new Date().toISOString(), 80 ...(contentWarnings.length > 0 && { 81 contentWarnings: { warnings: contentWarnings }, 82 }), 83 ...(distributionPolicy.deleteAfter && { distributionPolicy }), 84 ...(contentRights && 85 Object.keys(contentRights).length > 0 && { 86 contentRights, 87 }), 88 }; 89 90 const result = await bluesky.pdsAgent.com.atproto.repo.createRecord({ 91 repo: did, 92 collection: "place.stream.metadata.configuration", 93 rkey: "self", 94 record: metadataRecord, 95 }); 96 97 const rkey = result.data.uri.split("/").pop(); 98 99 set({ 100 creating: false, 101 error: null, 102 lastCreatedRecord: { 103 record: metadataRecord, 104 uri: result.data.uri, 105 cid: result.data.cid, 106 rkey, 107 }, 108 }); 109 } catch (error) { 110 set({ 111 creating: false, 112 error: error?.message ?? "Failed to create content metadata", 113 }); 114 } 115 }, 116 117 updateContentMetadata: async ({ 118 rkey, 119 livestreamRef, 120 contentWarnings = [], 121 distributionPolicy = { deleteAfter: undefined }, 122 contentRights = {}, 123 }) => { 124 set({ updating: true, error: null }); 125 try { 126 const state = get() as any; 127 const bluesky: BlueskySlice = state; 128 129 if (!bluesky.pdsAgent) { 130 throw new Error("No agent"); 131 } 132 133 const did = bluesky.oauthSession?.did; 134 if (!did) { 135 throw new Error("No DID"); 136 } 137 138 const metadataRecord = { 139 $type: "place.stream.metadata.configuration", 140 ...(livestreamRef && { livestreamRef }), 141 createdAt: new Date().toISOString(), 142 ...(contentWarnings.length > 0 && { 143 contentWarnings: { warnings: contentWarnings }, 144 }), 145 ...(distributionPolicy.deleteAfter && { distributionPolicy }), 146 ...(contentRights && 147 Object.keys(contentRights).length > 0 && { 148 contentRights, 149 }), 150 }; 151 152 const result = await bluesky.pdsAgent.com.atproto.repo.putRecord({ 153 repo: did, 154 collection: "place.stream.metadata.configuration", 155 rkey: "self", 156 record: metadataRecord, 157 }); 158 159 set({ 160 updating: false, 161 error: null, 162 lastCreatedRecord: { 163 record: metadataRecord, 164 uri: `at://${did}/place.stream.metadata.configuration/self`, 165 cid: result.data.cid, 166 }, 167 }); 168 } catch (error) { 169 set({ 170 updating: false, 171 error: error?.message ?? "Failed to update content metadata", 172 }); 173 } 174 }, 175 176 getContentMetadata: async ({ userDid, rkey = "self" } = {}) => { 177 set({ error: null }); 178 try { 179 const state = get() as any; 180 const bluesky: BlueskySlice = state; 181 182 if (!bluesky.pdsAgent) { 183 throw new Error("No agent"); 184 } 185 186 const targetDid = userDid || bluesky.oauthSession?.did; 187 if (!targetDid) { 188 throw new Error("No DID provided or user not authenticated"); 189 } 190 191 console.log(`[getContentMetadata] Debug info:`, { 192 targetDid, 193 rkey, 194 pdsAgentType: bluesky.pdsAgent.constructor.name, 195 hasOAuthSession: !!bluesky.oauthSession, 196 currentUserDid: bluesky.oauthSession?.did, 197 pdsAgentHost: 198 (bluesky.pdsAgent as any)?.host || 199 (bluesky.pdsAgent as any)?.service?.host || 200 "unknown", 201 pdsAgentUrl: 202 (bluesky.pdsAgent as any)?.url || 203 (bluesky.pdsAgent as any)?.service?.url || 204 "unknown", 205 }); 206 207 try { 208 let targetPDS: string | null = null; 209 try { 210 const didDoc = await resolveDIDDocument(targetDid); 211 targetPDS = getPDSServiceEndpoint(didDoc); 212 console.log( 213 `[getContentMetadata] Resolved PDS for ${targetDid}:`, 214 targetPDS, 215 ); 216 } catch (pdsResolveError) { 217 console.log( 218 `[getContentMetadata] Failed to resolve PDS for ${targetDid}:`, 219 pdsResolveError, 220 ); 221 } 222 223 let agent = bluesky.pdsAgent; 224 if (targetPDS && targetPDS !== (bluesky.pdsAgent as any)?.host) { 225 const { StreamplaceAgent } = await import("streamplace"); 226 agent = new StreamplaceAgent(targetPDS) as any; 227 console.log( 228 `[getContentMetadata] Created new agent for PDS:`, 229 targetPDS, 230 ); 231 } 232 233 console.log(`[getContentMetadata] Attempting to fetch record from:`, { 234 repo: targetDid, 235 collection: "place.stream.metadata.configuration", 236 rkey, 237 usingPDS: targetPDS || "default", 238 }); 239 240 const result = await agent.com.atproto.repo.getRecord({ 241 repo: targetDid, 242 collection: "place.stream.metadata.configuration", 243 rkey, 244 }); 245 246 console.log(`[getContentMetadata] API response:`, result); 247 248 if (!result.success) { 249 throw new Error("Failed to get content metadata record"); 250 } 251 252 set({ 253 error: null, 254 lastCreatedRecord: { 255 userDid: targetDid, 256 record: result.data.value, 257 uri: result.data.uri, 258 cid: result.data.cid, 259 }, 260 }); 261 } catch (error) { 262 console.log(`[getContentMetadata] Error details:`, { 263 error: error.message, 264 errorType: error.constructor.name, 265 errorStack: error.stack, 266 }); 267 268 if ( 269 error.message?.includes("not found") || 270 error.message?.includes("RecordNotFound") 271 ) { 272 set({ 273 error: null, 274 lastCreatedRecord: { 275 userDid: targetDid, 276 record: null, 277 uri: null, 278 cid: null, 279 }, 280 }); 281 return; 282 } 283 throw error; 284 } 285 } catch (error) { 286 set({ 287 error: error?.message ?? "Failed to get content metadata", 288 }); 289 } 290 }, 291 292 clearError: () => { 293 set({ error: null }); 294 }, 295});