2323const path = require ( "path" ) ;
2424const fs = require ( "fs" ) ;
2525const os = require ( "os" ) ;
26+ const crypto = require ( "crypto" ) ;
27+ const { pathToFileURL } = require ( "url" ) ;
28+
29+ function sriSha384 ( source ) {
30+ return `sha384-${ crypto . createHash ( "sha384" ) . update ( source ) . digest ( "base64" ) } ` ;
31+ }
32+
33+ function withPreviewGsapSri ( html , gsapSource ) {
34+ const integrity = sriSha384 ( gsapSource ) ;
35+ return html . replace ( / < s c r i p t \b (? = [ ^ > ] * \b s r c = [ " ' ] [ ^ " ' ] * g s a p [ ^ " ' ] * [ " ' ] ) [ ^ > ] * > / gi, ( tag ) =>
36+ tag
37+ . replace ( / \s + i n t e g r i t y \s * = \s * (?: " [ ^ " ] * " | ' [ ^ ' ] * ' ) / gi, "" )
38+ . replace ( / > $ / , ` integrity="${ integrity } ">` ) ,
39+ ) ;
40+ }
2641
2742const HF_ROOTS = [
2843 process . env . HYPERFRAMES_ROOT ,
@@ -63,10 +78,10 @@ for (const r of HF_ROOTS) {
6378 }
6479 if ( ! gsapSource ) {
6580 const g = findInBun ( r , "gsap" , path . join ( "dist" , "gsap.min.js" ) ) ;
66- if ( g ) gsapSource = fs . readFileSync ( g , "utf8" ) ;
81+ if ( g ) gsapSource = fs . readFileSync ( g ) ;
6782 }
6883}
69- if ( ! puppeteer || ! sharp ) {
84+ if ( require . main === module && ( ! puppeteer || ! sharp ) ) {
7085 console . error ( "[preview] need puppeteer+sharp — set HYPERFRAMES_ROOT" ) ;
7186 process . exit ( 0 ) ;
7287}
@@ -80,17 +95,23 @@ async function shotAt(browser, file, W, H, t) {
8095 // evaluation runs while document.head is still null, and gsap's init then
8196 // throws "appendChild of null" — which killed previews for theme projects.
8297 await page . setRequestInterception ( true ) ;
98+ const documentUrl = pathToFileURL ( file ) . href ;
99+ const documentHtml = gsapSource
100+ ? withPreviewGsapSri ( fs . readFileSync ( file , "utf8" ) , gsapSource )
101+ : fs . readFileSync ( file , "utf8" ) ;
83102 page . on ( "request" , ( req ) => {
84103 const u = req . url ( ) ;
85- if ( req . resourceType ( ) === "script" && / g s a p / i. test ( u ) && / ^ h t t p s ? : / i. test ( u ) ) {
104+ if ( req . isNavigationRequest ( ) && u === documentUrl ) {
105+ req . respond ( { status : 200 , contentType : "text/html" , body : documentHtml } ) ;
106+ } else if ( req . resourceType ( ) === "script" && / g s a p / i. test ( u ) && / ^ h t t p s ? : / i. test ( u ) ) {
86107 if ( gsapSource )
87108 req . respond ( { status : 200 , contentType : "application/javascript" , body : gsapSource } ) ;
88109 else req . continue ( ) ; // no local bundle — let the CDN load (online machines)
89110 } else if ( req . resourceType ( ) === "media" )
90111 req . abort ( ) ; // a-roll pixels come from frames_bg
91112 else req . continue ( ) ;
92113 } ) ;
93- await page . goto ( `file:// ${ file } ` , { waitUntil : "load" , timeout : 15000 } ) ;
114+ await page . goto ( documentUrl , { waitUntil : "load" , timeout : 15000 } ) ;
94115 const t0 = Date . now ( ) ;
95116 let tlReady = false ;
96117 while ( Date . now ( ) - t0 < 15000 ) {
@@ -260,9 +281,13 @@ async function main() {
260281 await Promise . race ( [ browser . close ( ) . catch ( ( ) => { } ) , new Promise ( ( r ) => setTimeout ( r , 8000 ) ) ] ) ;
261282 }
262283}
263- main ( )
264- . then ( ( ) => process . exit ( 0 ) )
265- . catch ( ( e ) => {
266- console . error ( "[preview]" , e . message ) ;
267- process . exit ( 1 ) ;
268- } ) ;
284+ if ( require . main === module ) {
285+ main ( )
286+ . then ( ( ) => process . exit ( 0 ) )
287+ . catch ( ( e ) => {
288+ console . error ( "[preview]" , e . message ) ;
289+ process . exit ( 1 ) ;
290+ } ) ;
291+ }
292+
293+ module . exports = { sriSha384, withPreviewGsapSri } ;
0 commit comments