@@ -503,10 +503,10 @@ class BookRenderer {
503503 const title = volumeInfo . title || "Untitled" ;
504504 const authors = volumeInfo . authors ? volumeInfo . authors . join ( ", " ) : "Unknown Author" ;
505505 const thumb = volumeInfo . imageLinks ? volumeInfo . imageLinks . thumbnail : 'https://via.placeholder.com/128x196?text=No+Cover' ;
506- const description = volumeInfo . description ? volumeInfo . description . substring ( 0 , 100 ) + "..." : "A mysterious tome waiting to be opened." ;
506+ const originalDescription = volumeInfo . description ? volumeInfo . description . substring ( 0 , 100 ) + "..." : "A mysterious tome waiting to be opened." ;
507507 const categories = volumeInfo . categories || [ ] ;
508508
509- const vibe = this . generateVibe ( description , categories ) ;
509+ const vibe = this . generateVibe ( originalDescription , categories ) ;
510510 const spineColors = [ '#5D4037' , '#4E342E' , '#3E2723' , '#2C2420' , '#8D6E63' ] ;
511511 const randomSpine = spineColors [ Math . floor ( Math . random ( ) * spineColors . length ) ] ;
512512
@@ -517,24 +517,6 @@ class BookRenderer {
517517 const flipSound = new Audio ( '../assets/sounds/page-flip.mp3' ) ;
518518 flipSound . volume = 0.5 ;
519519
520- /**
521- * ============================================================================
522- * SECURE HTML ESCAPING HELPER & XSS PREVENTION
523- * ============================================================================
524- * Problem:
525- * Previously, book title and author data were injected directly into the
526- * scene.innerHTML without sanitization. If data from the Google Books API
527- * contained special HTML characters (e.g., <script> tags, <, >), this could
528- * lead to rendering bugs or Cross-Site Scripting (XSS) vulnerabilities.
529- *
530- * Fix:
531- * We introduce this `escapeHTML` helper function. It replaces sensitive
532- * HTML characters with their harmless entity equivalents before injection.
533- * This strictly forces the browser to treat the dynamic content as text
534- * rather than executable code or structural markup. This is a crucial
535- * security measure when constructing HTML strings manually.
536- * ============================================================================
537- */
538520 const escapeHTML = ( str ) => {
539521 if ( ! str ) return "" ;
540522 return String ( str )
@@ -547,17 +529,13 @@ class BookRenderer {
547529
548530 const safeTitle = escapeHTML ( title ) ;
549531 const safeAuthors = escapeHTML ( authors ) ;
550- const safeDescription = escapeHTML ( description ) ;
532+ const safeOriginalDescription = escapeHTML ( originalDescription ) ;
551533 const safeVibe = escapeHTML ( vibe ) ;
552534 const safeThumb = escapeHTML ( thumb . replace ( 'http:' , 'https:' ) ) ;
553535
554536 scene . innerHTML = `
555537 <div class="book" data-id="${ escapeHTML ( id ) } ">
556538 <div class="book__face book__face--front">
557- <!--
558- Using the sanitized title for the 'alt' attribute and
559- sanitized URL for 'src' ensures no attribute escape attacks.
560- -->
561539 <img src="${ safeThumb } " alt="${ safeTitle } ">
562540 </div>
563541 <div class="book__face book__face--spine" style="background: ${ randomSpine } "></div>
@@ -566,15 +544,14 @@ class BookRenderer {
566544 <div class="book__face book__face--bottom"></div>
567545 <div class="book__face book__face--back">
568546 <div style="overflow-y: auto; height: 100%; padding-right: 5px; scrollbar-width: thin;">
569- <!-- Safe data injection using escaped values -->
570547 <div style="font-weight: bold; font-size: 0.9rem; margin-bottom: 0.5rem; color: var(--text-main);">${ safeTitle } </div>
571548 <div class="handwritten-note" style="margin-bottom: 0.8rem; font-style: italic; color: var(--wood-dark);">${ safeVibe } </div>
572549 ${ bookData . moods && bookData . moods . length > 0 ? `
573550 <div class="book-mood-tags" style="margin-bottom: 0.8rem; display: flex; flex-wrap: wrap; gap: 4px;">
574551 ${ bookData . moods . map ( m => `<span style="font-size: 0.6rem; background: rgba(0,0,0,0.1); padding: 2px 6px; border-radius: 10px;"><i class="fa-solid ${ this . getMoodIcon ( m ) } "></i> ${ m } </span>` ) . join ( '' ) }
575552 </div>
576553 ` : '' }
577- <div class="book-blurb" style="font-size: 0.8rem; line-height: 1.4; color: var(--text-muted); text-align: justify;">${ safeDescription } </div>
554+ <div class="book-blurb" data-book-id=" ${ escapeHTML ( id ) } " style="font-size: 0.8rem; line-height: 1.4; color: var(--text-muted); text-align: justify; min-height: 60px; ">${ safeOriginalDescription } </div>
578555 </div>
579556 ${ shelf === 'current' ? `
580557 <div class="reading-progress">
@@ -590,11 +567,24 @@ class BookRenderer {
590567 </div>
591568 </div>
592569 <div class="glass-overlay">
593- <!-- Safe author and title data injection in the overlay -->
594570 <strong>${ safeTitle } </strong><br><small>${ safeAuthors } </small>
595571 </div>
596572 ` ;
597573
574+ // Fetch AI-generated blurb asynchronously
575+ const blurbElement = scene . querySelector ( '.book-blurb' ) ;
576+ if ( blurbElement ) {
577+ this . fetchAIBlurb ( id , title , authors , volumeInfo . description || "" , categories )
578+ . then ( aiBlurb => {
579+ if ( aiBlurb && blurbElement ) {
580+ blurbElement . textContent = aiBlurb ;
581+ }
582+ } )
583+ . catch ( err => {
584+ // Silently keep fallback description
585+ } ) ;
586+ }
587+
598588 // Interaction: Progress Slider
599589 const slider = scene . querySelector ( '.progress-slider' ) ;
600590 if ( slider ) {
@@ -696,10 +686,28 @@ class BookRenderer {
696686 } ) ;
697687 if ( res . ok ) {
698688 const data = await res . json ( ) ;
699- return data . vibe ;
689+ return data . data ?. blurb || null ;
690+ }
691+ } catch ( e ) {
692+ // Silently fail to use fallback
693+ }
694+ return null ;
695+ }
696+
697+ async fetchAIBlurb ( bookId , title , author , description , categories = [ ] ) {
698+ try {
699+ const res = await fetch ( `${ MOOD_API_BASE } /generate-note` , {
700+ method : 'POST' ,
701+ headers : { 'Content-Type' : 'application/json' } ,
702+ credentials : 'include' ,
703+ body : JSON . stringify ( { title, author, description } )
704+ } ) ;
705+ if ( res . ok ) {
706+ const data = await res . json ( ) ;
707+ return data . data ?. blurb || null ;
700708 }
701709 } catch ( e ) {
702- // Silently fail to mock vibe
710+ // Silently fail to use fallback
703711 }
704712 return null ;
705713 }
0 commit comments