11import { pryv } from '../patchedPryv.ts' ;
2+ import { getAttachmentUrl } from '../toolkit/getAttachmentUrl.ts' ;
23
34type Connection = InstanceType < typeof pryv . Connection > ;
45
@@ -46,6 +47,8 @@ let _cache: Partial<Record<ProfileKey, any>> = {};
4647let _values : ProfileValues = { ...DEFAULTS } ;
4748/** @internal */
4849let _hooked = false ;
50+ /** @internal — tracks which streams have been verified/created */
51+ let _ensuredStreams : Set < string > = new Set ( ) ;
4952
5053/**
5154 * Check if event type matches a field's eventType (supports `picture/*` wildcard).
@@ -75,13 +78,9 @@ function matchField (event: any): ProfileKey | null {
7578 * Handles: attachment (construct API URL), data URL in content, plain URL in content.
7679 */
7780function resolveAvatarUrl ( event : any , conn : Connection | null = _connection ) : string | null {
78- // Attachment — construct URL from connection endpoint
81+ // Attachment — use shared getAttachmentUrl utility
7982 if ( event . attachments ?. length > 0 && conn ) {
80- const att = event . attachments [ 0 ] ;
81- const endpoint = ( conn as any ) . apiEndpoint || '' ;
82- const token = ( conn as any ) . token || '' ;
83- const fileName = att . fileName || att . id ;
84- return `${ endpoint } events/${ event . id } /${ fileName } ?auth=${ token } ` ;
83+ return getAttachmentUrl ( conn , event ) ;
8584 }
8685 // Content is a string (data URL or plain URL)
8786 if ( typeof event . content === 'string' && event . content . length > 0 ) {
@@ -135,6 +134,30 @@ async function trashExistingAvatar (): Promise<void> {
135134 }
136135}
137136
137+ /**
138+ * Ensure a profile child stream exists, creating the parent `profile` stream and the child if needed.
139+ */
140+ async function ensureStream ( streamId : string ) : Promise < void > {
141+ if ( _ensuredStreams . has ( streamId ) || ! _connection ) return ;
142+ try {
143+ // Create parent 'profile' stream (idempotent — ignores "already exists" error)
144+ await _connection . apiOne (
145+ 'streams.create' ,
146+ { id : 'profile' , name : 'Profile' } ,
147+ 'stream'
148+ ) . catch ( ( ) => { /* already exists — ok */ } ) ;
149+ // Create child stream
150+ await _connection . apiOne (
151+ 'streams.create' ,
152+ { id : streamId , name : streamId , parentId : 'profile' } ,
153+ 'stream'
154+ ) . catch ( ( ) => { /* already exists — ok */ } ) ;
155+ _ensuredStreams . add ( streamId ) ;
156+ } catch {
157+ // best effort — the subsequent events.create will fail with a clear error if stream is truly missing
158+ }
159+ }
160+
138161async function load ( ) : Promise < void > {
139162 if ( ! _connection ) return ;
140163
@@ -231,6 +254,7 @@ const HDSProfile = {
231254 ) ;
232255 _cache [ key ] = updated ;
233256 } else {
257+ await ensureStream ( field . streamId ) ;
234258 const created = await _connection . apiOne (
235259 'events.create' ,
236260 { streamIds : [ field . streamId ] , type : field . eventType , content : value } ,
@@ -264,6 +288,7 @@ const HDSProfile = {
264288 throw new Error ( 'HDSProfile: call hookToConnection() first' ) ;
265289 }
266290 await trashExistingAvatar ( ) ;
291+ await ensureStream ( PROFILE_FIELDS . avatar . streamId ) ;
267292 const result = await ( _connection as any ) . createEventWithFileFromBuffer (
268293 { type : 'picture/attached' , streamIds : [ PROFILE_FIELDS . avatar . streamId ] } ,
269294 fileData ,
@@ -284,6 +309,7 @@ const HDSProfile = {
284309 throw new Error ( 'HDSProfile: call hookToConnection() first' ) ;
285310 }
286311 await trashExistingAvatar ( ) ;
312+ await ensureStream ( PROFILE_FIELDS . avatar . streamId ) ;
287313 const created = await _connection . apiOne (
288314 'events.create' ,
289315 { streamIds : [ PROFILE_FIELDS . avatar . streamId ] , type : 'picture/base64' , content : dataUrl } ,
@@ -315,6 +341,7 @@ const HDSProfile = {
315341 _cache = { } ;
316342 _values = { ...DEFAULTS } ;
317343 _hooked = false ;
344+ _ensuredStreams = new Set ( ) ;
318345 } ,
319346} ;
320347
0 commit comments