@@ -51,6 +51,16 @@ interface ObfButton {
5151 image_id ?: string ; // Reference to image in the images array
5252}
5353
54+ interface ObfManifest {
55+ format ?: string ;
56+ root ?: string ;
57+ paths ?: {
58+ boards ?: { [ key : string ] : string } ;
59+ images ?: { [ key : string ] : string } ;
60+ sounds ?: { [ key : string ] : string } ;
61+ } ;
62+ }
63+
5464/**
5565 * Map OBF hidden value to AAC standard visibility
5666 * OBF: true = hidden, false/undefined = visible
@@ -201,15 +211,14 @@ class ObfProcessor extends BaseProcessor {
201211 }
202212 }
203213
204- private async processBoard ( boardData : ObfBoard , _boardPath : string ) : Promise < AACPage > {
214+ private async processBoard (
215+ boardData : ObfBoard ,
216+ _boardPath : string ,
217+ isZipEntry : boolean
218+ ) : Promise < AACPage > {
205219 const sourceButtons = boardData . buttons || [ ] ;
206220
207221 // Calculate page ID first (used to make button IDs unique)
208- const isZipEntry =
209- _boardPath &&
210- _boardPath . endsWith ( '.obf' ) &&
211- ! _boardPath . includes ( '/' ) &&
212- ! _boardPath . includes ( '\\' ) ;
213222 const pageId = isZipEntry
214223 ? _boardPath // Zip entry - use filename to match navigation paths
215224 : boardData ?. id
@@ -437,7 +446,7 @@ class ObfProcessor extends BaseProcessor {
437446 const boardData = tryParseObfJson ( content ) ;
438447 if ( boardData ) {
439448 console . log ( '[OBF] Detected .obf file, parsed as JSON' ) ;
440- const page = await this . processBoard ( boardData , filePathOrBuffer ) ;
449+ const page = await this . processBoard ( boardData , filePathOrBuffer , false ) ;
441450 tree . addPage ( page ) ;
442451
443452 // Set metadata from root board
@@ -475,7 +484,7 @@ class ObfProcessor extends BaseProcessor {
475484 const asJson = tryParseObfJson ( filePathOrBuffer ) ;
476485 if ( ! asJson ) throw new Error ( 'Invalid OBF content: not JSON and not ZIP' ) ;
477486 console . log ( '[OBF] Detected buffer/string as OBF JSON' ) ;
478- const page = await this . processBoard ( asJson , '[bufferOrString]' ) ;
487+ const page = await this . processBoard ( asJson , '[bufferOrString]' , false ) ;
479488 tree . addPage ( page ) ;
480489
481490 // Set metadata from root board
@@ -508,18 +517,43 @@ class ObfProcessor extends BaseProcessor {
508517
509518 console . log ( '[OBF] Detected zip archive, extracting .obf files' ) ;
510519
511- // Collect all .obf entries
512- const obfEntries = this . zipFile
513- . listFiles ( )
514- . filter ( ( name ) => name . toLowerCase ( ) . endsWith ( '.obf' ) ) ;
520+ // List manifest and OBF files
521+ const filesInZip = this . zipFile . listFiles ( ) ;
522+ const manifestFile = filesInZip . filter ( ( name ) => name . toLowerCase ( ) === 'manifest.json' ) ;
523+ let obfEntries = filesInZip . filter ( ( name ) => name . toLowerCase ( ) . endsWith ( '.obf' ) ) ;
524+
525+ // Attempt to read manifest
526+ if ( manifestFile && manifestFile . length === 1 ) {
527+ try {
528+ const content = await this . zipFile . readFile ( manifestFile [ 0 ] ) ;
529+ const data = decodeText ( content ) ;
530+ const str = typeof data === 'string' ? data : readTextFromInput ( data ) ;
531+ if ( ! str . trim ( ) ) throw new Error ( 'Manifest object missing' ) ;
532+ const manifestObject = JSON . parse ( str ) as ObfManifest ;
533+ if ( ! manifestObject ) throw new Error ( 'Manifest object is empty' ) ;
534+
535+ // Replace OBF file list
536+ if ( manifestObject . paths && manifestObject . paths . boards ) {
537+ obfEntries = Object . values ( manifestObject . paths . boards ) ;
538+ }
539+
540+ // Move root board to top of list
541+ if ( manifestObject . root ) {
542+ obfEntries = obfEntries . filter ( ( item ) => item !== manifestObject . root ) ;
543+ obfEntries . unshift ( manifestObject . root ) ;
544+ }
545+ } catch ( err ) {
546+ console . warn ( '[OBF] Error processing mainfest' , err ) ;
547+ }
548+ }
515549
516550 // Process each .obf entry
517551 for ( const entryName of obfEntries ) {
518552 try {
519553 const content = await this . zipFile . readFile ( entryName ) ;
520554 const boardData = tryParseObfJson ( decodeText ( content ) ) ;
521555 if ( boardData ) {
522- const page = await this . processBoard ( boardData , entryName ) ;
556+ const page = await this . processBoard ( boardData , entryName , true ) ;
523557 tree . addPage ( page ) ;
524558
525559 // Set metadata if not already set (use first board as reference)
0 commit comments