@@ -12,6 +12,8 @@ interface ExtraOptions {
1212 // Seed (string) of our cache hash
1313 // changing the seed will invalidate all previous entries
1414 seed ?: string ;
15+ // Custom endpoint to fetch public files from instead of Firebase hosting
16+ publicEndpoint ?: URL ;
1517}
1618interface HeaderOptions {
1719 [ key : string ] : string | null ;
@@ -29,9 +31,10 @@ export default function firebase(
2931 return fbase . serve . bind ( fbase ) ;
3032}
3133
32- class Firebase {
34+ export class Firebase {
3335 public matcher : Matcher ;
3436 public projectID : string ;
37+ public publicEndpoint : URL ;
3538 public hostingEndpoint : URL ;
3639 public globalHeaders : HeaderOptions ;
3740 public proxy : ServeFunction ;
@@ -44,12 +47,22 @@ class Firebase {
4447 this . matcher = new Matcher ( config . rewrites ) ;
4548 // Static Hosting endpoint
4649 this . hostingEndpoint = fbhostingEndpoint ( projectID ) ;
50+ // Endpoint for public files in hosting, can be overriden in extra options
51+ this . publicEndpoint =
52+ extra && extra . publicEndpoint
53+ ? extra . publicEndpoint
54+ : this . hostingEndpoint ;
4755 // Custom headers
4856 this . globalHeaders = extra && extra . headers ? extra . headers : { } ;
4957 // Cache seed
5058 this . seed = extra && extra . seed ? extra . seed : '42' ;
5159 // Proxy
52- this . proxy = cache ( customProxy ( req => this . getEndpoint ( req ! ) ) , this . seed ) ;
60+ this . proxy = cache (
61+ customProxy ( req => this . getEndpoint ( req ! ) , {
62+ rewriteURL : url => this . rewriteURL ( url )
63+ } ) ,
64+ this . seed
65+ ) ;
5366 }
5467
5568 public async serve ( event : FetchEvent ) : Promise < Response > {
@@ -71,18 +84,44 @@ class Firebase {
7184 const url = new URL ( request . url ) ;
7285 const pathname = url . pathname ;
7386
87+ // If reserved, pass through to the original FirebaseHosting application endpoint
88+ if ( isReserved ( pathname ) ) {
89+ return this . hostingEndpoint ;
90+ }
91+
7492 // Get cloud func for path
75- const funcname = this . matcher . match ( pathname ) ;
93+ const match = this . matcher . match ( pathname ) ;
94+ // If no func matched, we're looking for a public file in Firebase hosting, pass through
95+ if ( ! match || ! ( 'function' in match ) ) {
96+ return this . publicEndpoint ;
97+ }
7698
77- // Is this URL part of Firebase's reserved /__/* namespace
78- const isReserved = pathname . startsWith ( '/__/' ) ;
99+ // Route to specific cloud function
100+ return cloudfuncEndpoint ( this . projectID , match . function ) ;
101+ }
79102
80- // If no func matched or reserved, pass through to FirebaseHosting
81- if ( isReserved || ! funcname ) {
82- return this . hostingEndpoint ;
103+ /**
104+ * Rewrite URL's pathname to match configured destination
105+ */
106+ public rewriteURL ( url : URL ) : URL {
107+ // If reserved, we never modify the request
108+ if ( isReserved ( url . pathname ) ) {
109+ return url ;
83110 }
84111
85- // Route to specific cloud function
86- return cloudfuncEndpoint ( this . projectID , funcname ) ;
112+ const match = this . matcher . match ( url . pathname ) ;
113+ if ( ! match || ! ( 'destination' in match ) ) {
114+ return url ;
115+ }
116+
117+ url . pathname = match . destination ;
118+ return url ;
87119 }
88120}
121+
122+ /**
123+ * Is this URL part of Firebase's reserved /__/* namespace
124+ */
125+ function isReserved ( pathname : string ) : boolean {
126+ return pathname . startsWith ( '/__/' ) ;
127+ }
0 commit comments