@@ -28,34 +28,11 @@ import {
2828 readTextFromInput ,
2929 writeTextToPath ,
3030 encodeBase64 ,
31+ decodeText ,
32+ getNodeRequire ,
33+ isNodeRuntime ,
3134} from '../utils/io' ;
32- import type JSZip from 'jszip' ;
33-
34- // Use dynamic import for JSZip to support both browser and Node environments
35- type JSZipStatic = typeof JSZip ;
36- let JSZipModuleObf : JSZipStatic | undefined ;
37- async function getJSZipObf ( ) : Promise < JSZipStatic > {
38- if ( ! JSZipModuleObf ) {
39- try {
40- // Try ES module import first (browser/Vite)
41- const module = await import ( 'jszip' ) ;
42- JSZipModuleObf = module . default || module ;
43- } catch ( error ) {
44- // Fall back to CommonJS require (Node.js)
45- try {
46- // eslint-disable-next-line @typescript-eslint/no-var-requires
47- const module = require ( 'jszip' ) ;
48- JSZipModuleObf = module . default || module ;
49- } catch ( err2 ) {
50- throw new Error ( 'Zip handling requires JSZip in this environment.' ) ;
51- }
52- }
53- }
54- if ( ! JSZipModuleObf ) {
55- throw new Error ( 'Zip handling requires JSZip in this environment.' ) ;
56- }
57- return JSZipModuleObf ;
58- }
35+ import { openZipFromInput , type ZipAdapter } from '../utils/zip' ;
5936
6037const OBF_FORMAT_VERSION = 'open-board-0.1' ;
6138
@@ -106,7 +83,7 @@ interface ObfBoard {
10683}
10784
10885class ObfProcessor extends BaseProcessor {
109- private zipFile ?: JSZip ; // JSZip instance
86+ private zipFile ?: ZipAdapter ;
11087 private imageCache : Map < string , string > = new Map ( ) ; // Cache for data URLs
11188
11289 constructor ( options ?: ProcessorOptions ) {
@@ -136,10 +113,12 @@ class ObfProcessor extends BaseProcessor {
136113
137114 for ( const imagePath of possiblePaths ) {
138115 try {
139- const file = this . zipFile . file ( imagePath as string ) ;
140- if ( file ) {
141- const buffer = await file . async ( 'nodebuffer' ) ;
142- return buffer ;
116+ const buffer = await this . zipFile . readFile ( imagePath as string ) ;
117+ if ( buffer ) {
118+ if ( typeof Buffer !== 'undefined' ) {
119+ return Buffer . from ( buffer ) ;
120+ }
121+ return null ;
143122 }
144123 } catch ( err ) {
145124 continue ;
@@ -178,9 +157,8 @@ class ObfProcessor extends BaseProcessor {
178157
179158 for ( const imagePath of possiblePaths ) {
180159 try {
181- const file = this . zipFile . file ( imagePath as string ) ;
182- if ( file ) {
183- const buffer = await file . async ( 'uint8array' ) ;
160+ const buffer = await this . zipFile . readFile ( imagePath as string ) ;
161+ if ( buffer ) {
184162 const contentType =
185163 ( imageData as { content_type ?: string } ) . content_type ||
186164 this . getMimeTypeFromFilename ( imagePath as string ) ;
@@ -518,38 +496,29 @@ class ObfProcessor extends BaseProcessor {
518496 throw new Error ( 'Invalid OBF content: not JSON and not ZIP' ) ;
519497 }
520498
521- const JSZip = await getJSZipObf ( ) ;
522- let zip : JSZip ;
523499 try {
524- const zipInput = readBinaryFromInput ( filePathOrBuffer ) ;
525- zip = await JSZip . loadAsync ( zipInput ) ;
500+ const zipResult = await openZipFromInput ( filePathOrBuffer ) ;
501+ this . zipFile = zipResult . zip ;
526502 } catch ( err ) {
527- console . error ( '[OBF] Error loading ZIP with JSZip :' , err ) ;
503+ console . error ( '[OBF] Error loading ZIP:' , err ) ;
528504 throw err ;
529505 }
530506
531507 // Store the ZIP file reference for image extraction
532- this . zipFile = zip ;
533508 this . imageCache . clear ( ) ; // Clear cache for new file
534509
535510 console . log ( '[OBF] Detected zip archive, extracting .obf files' ) ;
536511
537512 // Collect all .obf entries
538- const obfEntries : Array < { name : string ; file : JSZip . JSZipObject } > = [ ] ;
539- zip . forEach ( ( relativePath : string , file : JSZip . JSZipObject ) => {
540- if ( file . dir ) return ;
541- if ( relativePath . toLowerCase ( ) . endsWith ( '.obf' ) ) {
542- obfEntries . push ( { name : relativePath , file } ) ;
543- }
544- } ) ;
513+ const obfEntries = this . zipFile . listFiles ( ) . filter ( ( name ) => name . toLowerCase ( ) . endsWith ( '.obf' ) ) ;
545514
546515 // Process each .obf entry
547- for ( const entry of obfEntries ) {
516+ for ( const entryName of obfEntries ) {
548517 try {
549- const content = await entry . file . async ( 'string' ) ;
550- const boardData = tryParseObfJson ( content ) ;
518+ const content = await this . zipFile . readFile ( entryName ) ;
519+ const boardData = tryParseObfJson ( decodeText ( content ) ) ;
551520 if ( boardData ) {
552- const page = await this . processBoard ( boardData , entry . name ) ;
521+ const page = await this . processBoard ( boardData , entryName ) ;
553522 tree . addPage ( page ) ;
554523
555524 // Set metadata if not already set (use first board as reference)
@@ -564,10 +533,10 @@ class ObfProcessor extends BaseProcessor {
564533 tree . rootId = page . id ;
565534 }
566535 } else {
567- console . warn ( '[OBF] Skipped entry (not valid OBF JSON):' , entry . name ) ;
536+ console . warn ( '[OBF] Skipped entry (not valid OBF JSON):' , entryName ) ;
568537 }
569538 } catch ( err ) {
570- console . warn ( '[OBF] Error processing entry:' , entry . name , err ) ;
539+ console . warn ( '[OBF] Error processing entry:' , entryName , err ) ;
571540 }
572541 }
573542
@@ -722,18 +691,34 @@ class ObfProcessor extends BaseProcessor {
722691 writeTextToPath ( outputPath , JSON . stringify ( obfBoard , null , 2 ) ) ;
723692 } else {
724693 // Save as OBZ (zip with multiple OBF files)
725- const JSZip = await getJSZipObf ( ) ;
726- const zip = new JSZip ( ) ;
694+ if ( isNodeRuntime ( ) ) {
695+ const AdmZip = getNodeRequire ( ) ( 'adm-zip' ) as typeof import ( 'adm-zip' ) ;
696+ const zip = new AdmZip ( ) ;
697+
698+ Object . values ( tree . pages ) . forEach ( ( page ) => {
699+ const obfBoard = this . createObfBoardFromPage ( page , 'Board' , tree . metadata ) ;
700+ const obfContent = JSON . stringify ( obfBoard , null , 2 ) ;
701+ zip . addFile ( `${ page . id } .obf` , Buffer . from ( obfContent , 'utf8' ) ) ;
702+ } ) ;
727703
728- Object . values ( tree . pages ) . forEach ( ( page ) => {
729- const obfBoard = this . createObfBoardFromPage ( page , 'Board' , tree . metadata ) ;
730- const obfContent = JSON . stringify ( obfBoard , null , 2 ) ;
731- zip . file ( `${ page . id } .obf` , obfContent ) ;
732- } ) ;
704+ const zipBuffer = zip . toBuffer ( ) ;
705+ const { writeBinaryToPath } = await import ( '../utils/io' ) ;
706+ writeBinaryToPath ( outputPath , zipBuffer ) ;
707+ } else {
708+ const module = await import ( 'jszip' ) ;
709+ const JSZip = module . default || module ;
710+ const zip = new JSZip ( ) ;
711+
712+ Object . values ( tree . pages ) . forEach ( ( page ) => {
713+ const obfBoard = this . createObfBoardFromPage ( page , 'Board' , tree . metadata ) ;
714+ const obfContent = JSON . stringify ( obfBoard , null , 2 ) ;
715+ zip . file ( `${ page . id } .obf` , obfContent ) ;
716+ } ) ;
733717
734- const zipBuffer = await zip . generateAsync ( { type : 'uint8array' } ) ;
735- const { writeBinaryToPath } = await import ( '../utils/io' ) ;
736- writeBinaryToPath ( outputPath , zipBuffer ) ;
718+ const zipBuffer = await zip . generateAsync ( { type : 'uint8array' } ) ;
719+ const { writeBinaryToPath } = await import ( '../utils/io' ) ;
720+ writeBinaryToPath ( outputPath , zipBuffer ) ;
721+ }
737722 }
738723 }
739724
0 commit comments