1515 */
1616
1717import * as superstruct from 'superstruct' ;
18+ import express from 'express' ;
1819
1920import * as utils from '../utils' ;
2021import { Operation , OperationAction , OperationBuilder } from '../types' ;
@@ -48,6 +49,15 @@ const structFromUrl = superstruct.object({
4849 url : superstruct . string ( ) ,
4950} ) ;
5051
52+ /**
53+ * A path url of an image. The request hostname will be prepended to this path.
54+ */
55+ const structFromPath = superstruct . object ( {
56+ operation : superstruct . literal ( name ) ,
57+ type : superstruct . literal ( 'path' ) ,
58+ path : superstruct . string ( ) ,
59+ } ) ;
60+
5161/**
5262 * Create a new image.
5363 */
@@ -110,18 +120,21 @@ const structCreateNewImage = superstruct.object({
110120const struct = superstruct . union ( [
111121 structFromGcs ,
112122 structFromUrl ,
123+ structFromPath ,
113124 structCreateNewImage ,
114125] ) ;
115126
116127export type OperationInputGcs = superstruct . Infer < typeof structFromGcs > ;
117128export type OperationInputUrl = superstruct . Infer < typeof structFromUrl > ;
129+ export type OperationInputPath = superstruct . Infer < typeof structFromPath > ;
118130export type OperationInputCreateNew = superstruct . Infer <
119131 typeof structCreateNewImage
120132> ;
121133
122134export type OperationInput =
123135 | OperationInputGcs
124136 | OperationInputUrl
137+ | OperationInputPath
125138 | OperationInputCreateNew ;
126139
127140export const operationInput : OperationBuilder = {
@@ -135,12 +148,14 @@ export const operationInput: OperationBuilder = {
135148 return structFromGcs . create ( rawOptions ) ;
136149 case 'url' :
137150 return structFromUrl . create ( rawOptions ) ;
151+ case 'path' :
152+ return structFromPath . create ( rawOptions ) ;
138153 }
139154 throw new AssertionError ( {
140155 message : `'${ rawOptions . type } ' is not a valid input 'type'.` ,
141156 } ) ;
142157 } ,
143- async build ( operation ) {
158+ async build ( operation , _fileMetadata , req ) {
144159 const options = operation . options as OperationInput ;
145160
146161 switch ( options . type ) {
@@ -150,6 +165,8 @@ export const operationInput: OperationBuilder = {
150165 return await fetchGcsFile ( options ) ;
151166 case 'url' :
152167 return await fetchUrl ( options ) ;
168+ case 'path' :
169+ return await fetchPathUrl ( options , req ) ;
153170 }
154171 } ,
155172} ;
@@ -169,6 +186,57 @@ async function fetchUrl(options: OperationInput): Promise<OperationAction[]> {
169186 ] ;
170187}
171188
189+ async function fetchPathUrl (
190+ options : OperationInput ,
191+ req ?: express . Request ,
192+ ) : Promise < OperationAction [ ] > {
193+ if ( options . type !== 'path' ) return [ ] ;
194+
195+ if ( ! req ) {
196+ throw new AssertionError ( {
197+ message : 'Request object is required for path type inputs.' ,
198+ } ) ;
199+ }
200+
201+ // Extract hostname from request
202+ const origin = ( req . headers . origin as string ) || '' ;
203+ const referer = ( req . headers . referer as string ) || '' ;
204+ const host = req . headers . host || '' ;
205+
206+ let baseUrl = '' ;
207+
208+ // Try to get the base URL from various sources
209+ if ( origin ) {
210+ baseUrl = origin ;
211+ } else if ( referer ) {
212+ try {
213+ const url = new URL ( referer ) ;
214+ baseUrl = `${ url . protocol } //${ url . host } ` ;
215+ } catch ( e ) {
216+ // Invalid URL format in referer
217+ }
218+ } else if ( host ) {
219+ const protocol = req . secure ? 'https:' : 'http:' ;
220+ baseUrl = `${ protocol } //${ host } ` ;
221+ }
222+
223+ if ( ! baseUrl ) {
224+ throw new AssertionError ( {
225+ message : `Could not determine request hostname for path URL.` ,
226+ } ) ;
227+ }
228+
229+ // Construct the full URL
230+ const fullUrl = `${ baseUrl } ${ options . path } ` ;
231+
232+ return [
233+ {
234+ method : 'constructor' ,
235+ arguments : [ await fetchImageBufferFromUrl ( fullUrl ) ] ,
236+ } ,
237+ ] ;
238+ }
239+
172240async function fetchGcsFile (
173241 options : OperationInput ,
174242) : Promise < OperationAction [ ] > {
0 commit comments