@@ -20,51 +20,89 @@ export interface EntryCreationResult {
2020 } > ;
2121}
2222
23- async function createAssetFromUrlFast (
23+ interface AssetFileInput {
24+ fileName : string ;
25+ contentType : string ;
26+ upload ?: string ;
27+ uploadFrom ?: Record < string , unknown > ;
28+ }
29+
30+ async function createAssetFromUrl (
2431 cma : PageAppSDK [ 'cma' ] | ConfigAppSDK [ 'cma' ] ,
2532 spaceId : string ,
2633 environmentId : string ,
2734 url : string ,
2835 defaultLocale : string ,
29- metadata ?: { title ?: string ; altText ?: string ; fileName ?: string ; contentType ?: string }
36+ metadata ?: { title ?: string ; altText ?: string ; fileName ?: string ; contentType ?: string } ,
37+ oauthToken ?: string
3038) {
3139 const fileName = metadata ?. fileName || 'image.jpg' ;
3240 const contentType = metadata ?. contentType || 'image/jpeg' ;
3341 const title = metadata ?. title || metadata ?. altText || 'Image' ;
3442
43+ let fileField : AssetFileInput ;
44+
45+ if ( oauthToken ) {
46+ // Google content URLs are signed (the ?key= param is the credential) so no
47+ // Authorization header is needed or permitted cross-origin. Fetch the binary
48+ // directly here in the browser so Contentful's servers don't have to reach an
49+ // expiring signed URL, then upload the bytes via the CMA upload endpoint.
50+ const imageResponse = await fetch ( url ) ;
51+ if ( ! imageResponse . ok ) {
52+ throw new Error ( `Failed to fetch image (HTTP ${ imageResponse . status } )` ) ;
53+ }
54+ const arrayBuffer = await imageResponse . arrayBuffer ( ) ;
55+ const upload = await cma . upload . create ( { spaceId, environmentId } , { file : arrayBuffer } ) ;
56+ fileField = {
57+ contentType,
58+ fileName,
59+ uploadFrom : { sys : { type : 'Link' , linkType : 'Upload' , id : upload . sys . id } } ,
60+ } ;
61+ } else {
62+ fileField = { contentType, fileName, upload : url } ;
63+ }
64+
3565 const asset = await cma . asset . create (
3666 { spaceId, environmentId } ,
3767 {
3868 fields : {
3969 title : { [ defaultLocale ] : title } ,
40- file : {
41- [ defaultLocale ] : {
42- contentType,
43- fileName,
44- upload : url ,
45- } ,
46- } ,
70+ file : { [ defaultLocale ] : fileField } ,
4771 } ,
4872 }
4973 ) ;
5074
51- try {
52- await cma . asset . processForAllLocales ( { spaceId, environmentId } , asset ) ;
53- } catch ( error ) {
54- console . error ( `Failed to process asset for URL: ${ url } ` , error ) ;
55- }
75+ await cma . asset . processForAllLocales ( { spaceId, environmentId } , asset ) ;
5676
5777 return asset ;
5878}
5979
60- async function transformFieldsForContentType (
80+ function resolveAssetPlaceholder ( value : unknown , urlToAssetId : Record < string , string > ) : unknown {
81+ if ( value === null || typeof value !== 'object' || Array . isArray ( value ) ) return value ;
82+ const v = value as Record < string , unknown > ;
83+ const sys = v . sys ;
84+ if ( sys === null || typeof sys !== 'object' || Array . isArray ( sys ) ) return value ;
85+ const s = sys as Record < string , unknown > ;
86+ if ( s . type !== 'Link' || s . linkType !== 'Asset' || typeof s . id !== 'string' ) return value ;
87+ const resolvedId = urlToAssetId [ s . id ] ;
88+ if ( ! resolvedId ) {
89+ // Placeholder not found — asset creation likely failed. Return undefined so
90+ // the caller can omit this field rather than writing a dangling reference.
91+ console . warn ( '[asset] unresolved asset placeholder:' , s . id ) ;
92+ return undefined ;
93+ }
94+ return { sys : { type : 'Link' , linkType : 'Asset' , id : resolvedId } } ;
95+ }
96+
97+ function transformFieldsForContentType (
6198 fields : Record < string , Record < string , unknown > > ,
6299 contentType : ContentTypeProps | undefined ,
63100 urlToAssetId ?: Record < string , string >
64101) {
65102 if ( ! contentType ) return fields ;
66103
67104 const fieldDefs = new Map ( contentType . fields . map ( ( f ) => [ f . id , f ] ) ) ;
105+ const assetMap = urlToAssetId && Object . keys ( urlToAssetId ) . length > 0 ? urlToAssetId : undefined ;
68106 const transformed : Record < string , Record < string , unknown > > = { } ;
69107
70108 for ( const [ fieldId , localizedValue ] of Object . entries ( fields ) ) {
@@ -77,9 +115,24 @@ async function transformFieldsForContentType(
77115 const perLocale : Record < string , unknown > = { } ;
78116 for ( const [ locale , value ] of Object . entries ( localizedValue ) ) {
79117 if ( def . type === 'RichText' ) {
80- const assetMap =
81- urlToAssetId && Object . keys ( urlToAssetId ) . length > 0 ? urlToAssetId : undefined ;
82118 perLocale [ locale ] = normalizeAgentRichTextJson ( value , assetMap ) ;
119+ } else if ( assetMap && def . type === 'Link' && def . linkType === 'Asset' ) {
120+ const resolved = resolveAssetPlaceholder ( value , assetMap ) ;
121+ if ( resolved !== undefined ) {
122+ perLocale [ locale ] = resolved ;
123+ }
124+ } else if (
125+ assetMap &&
126+ def . type === 'Array' &&
127+ def . items ?. linkType === 'Asset' &&
128+ Array . isArray ( value )
129+ ) {
130+ const resolved = value
131+ . map ( ( item ) => resolveAssetPlaceholder ( item , assetMap ) )
132+ . filter ( ( item ) : item is NonNullable < typeof item > => item !== undefined ) ;
133+ if ( resolved . length > 0 ) {
134+ perLocale [ locale ] = resolved ;
135+ }
83136 } else {
84137 perLocale [ locale ] = value ;
85138 }
@@ -96,7 +149,8 @@ async function createAssetsFromAgentOutput(
96149 spaceId : string ,
97150 environmentId : string ,
98151 defaultLocale : string ,
99- assets : AssetToCreate [ ]
152+ assets : AssetToCreate [ ] ,
153+ oauthToken ?: string
100154) : Promise < Record < string , string > > {
101155 const urlToAssetId : Record < string , string > = { } ;
102156
@@ -106,7 +160,7 @@ async function createAssetsFromAgentOutput(
106160
107161 const assetCreationPromises = assets . map ( async ( asset ) => {
108162 try {
109- const createdAsset = await createAssetFromUrlFast (
163+ const createdAsset = await createAssetFromUrl (
110164 cma ,
111165 spaceId ,
112166 environmentId ,
@@ -117,7 +171,8 @@ async function createAssetsFromAgentOutput(
117171 altText : asset . altText ,
118172 fileName : asset . fileName ,
119173 contentType : asset . contentType ,
120- }
174+ } ,
175+ oauthToken
121176 ) ;
122177
123178 const normalizedUrl = asset . url . replace ( / \s + / g, '' ) ;
@@ -141,12 +196,19 @@ async function createAssetsFromAgentOutput(
141196 if ( ! result ) continue ;
142197
143198 const { normalizedUrl, altText, assetId } = result ;
199+ const rawUrl = assets [ i ] ?. url ;
144200 const placeholderId = assets [ i ] ?. placeholderId ;
145201
146202 if ( placeholderId ) {
147203 urlToAssetId [ placeholderId ] = assetId ;
148204 }
149205
206+ // Store both the raw URL and the whitespace-normalised form so the lookup
207+ // in resolveAssetPlaceholder succeeds regardless of how the agent encoded
208+ // the URL in the entry field.
209+ if ( rawUrl && rawUrl !== normalizedUrl ) {
210+ urlToAssetId [ rawUrl ] = assetId ;
211+ }
150212 urlToAssetId [ normalizedUrl ] = assetId ;
151213
152214 const compositeKey = `${ normalizedUrl } ::${ altText || 'image' } ` ;
@@ -163,7 +225,8 @@ async function createAssetsFromAgentOutput(
163225export async function createEntriesFromPreviewPayload (
164226 sdk : PageAppSDK | ConfigAppSDK ,
165227 payload : PreviewPayload ,
166- selectedEntryTempIds ?: Set < string >
228+ selectedEntryTempIds ?: Set < string > ,
229+ oauthToken ?: string
167230) : Promise < EntryCreationResult > {
168231 const effectivePayload =
169232 selectedEntryTempIds !== undefined
@@ -184,20 +247,23 @@ export async function createEntriesFromPreviewPayload(
184247 sdk ,
185248 entriesForSpaceLocale ,
186249 contentTypeIds ,
187- effectivePayload . assets
250+ effectivePayload . assets ,
251+ oauthToken
188252 ) ;
189253}
190254
191255/**
192256 * Creates entries in two passes: without reference fields, then patches references
193- * (including Rich Text entry links). Assets from `assets` are created first; Rich Text
194- * asset placeholders use the resulting id map in both passes.
257+ * (including Rich Text entry links). Assets are created first; the resulting
258+ * placeholder→id map is used to resolve asset references in RichText fields,
259+ * standalone Media fields, and arrays of Media fields across both passes.
195260 */
196261export async function createEntriesFromPreview (
197262 sdk : PageAppSDK | ConfigAppSDK ,
198263 entries : EntryToCreate [ ] ,
199264 contentTypeIds : string [ ] ,
200- assets : AssetToCreate [ ] = [ ]
265+ assets : AssetToCreate [ ] = [ ] ,
266+ oauthToken ?: string
201267) : Promise < EntryCreationResult > {
202268 const spaceId = sdk . ids . space ;
203269 const environmentId = sdk . ids . environment ;
@@ -236,7 +302,8 @@ export async function createEntriesFromPreview(
236302 spaceId ,
237303 environmentId ,
238304 defaultLocale ,
239- assets
305+ assets ,
306+ oauthToken
240307 ) ;
241308
242309 // PASS 1: Create all entries WITHOUT reference fields
0 commit comments