@prose-reader/streamer 26.0.0 → 28.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,9 @@
1
- import { Archive } from '@prose-reader/archive-reader';
2
1
  import { Manifest } from '@prose-reader/shared';
3
2
  type Toc = NonNullable<Manifest["nav"]>["toc"];
4
3
  export declare const normalizeFilenameAsTitle: (filename: string) => string;
5
- export declare const buildAudiobookToc: (manifest: Manifest, archive: Archive) => Toc | undefined;
4
+ /**
5
+ * One entry per track for a book made only of audio, each titled after the
6
+ * archive record it plays.
7
+ */
8
+ export declare const buildAudiobookToc: (manifest: Manifest) => Toc | undefined;
6
9
  export {};
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/shared"),t=require("@prose-reader/archive-reader"),n=require("xmldoc"),r=require("rxjs");var i=e.Report.namespace(`@prose-reader/streamer`,void 0,{color:`#ffae42`}),a=({enableReport:e}={})=>{i.enable(!!e)},o=e=>e.replace(/\.[^.]+$/,``).replace(/[_-]/g,` `).replace(/\s+/g,` `).trim(),s=(t,n)=>{if(t.spineItems.length!==0&&t.spineItems.every(t=>((0,e.parseContentType)(t.mediaType??``)||(0,e.detectMimeTypeFromName)(t.href))?.startsWith(`audio/`)))return t.spineItems.map(e=>{let t=n.records.find(t=>!t.dir&&decodeURI(e.href).endsWith(t.uri));return{title:o(t?.basename??e.href),href:e.href,path:t?.uri??e.href,contents:[]}})},c=e=>encodeURI(e).replace(/#/g,`%23`).replace(/\?/g,`%3F`),l=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return`${encodeURI(n)}${c(t)}`},u=t=>n=>({title:n.title,path:n.path,href:n.containerHref?(0,e.urlJoin)(t,n.containerHref):``,contents:n.contents.map(u(t))}),d=e=>{let t=e?.trim();if(t===`cover`||t===`title-page`||t===`copyright-page`||t===`text`)return t},f=e=>/^https?:\/\//.test(e),p=e=>e.records.find(({dir:e})=>e)?.basename.replace(/\/$/,``),m=(n,r,i,a)=>{let{metadata:o,readingOrder:s}=i,c=a.basePath,l=[];for(let e of a.opf.guide){let t=d(e.type);t!==void 0&&l.push({href:e.href,title:e.title,type:t})}let m=(0,e.createXmlSafeIdFactory)();return{filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(o)||p(n)||``,renditionLayout:o.renditionLayout,renditionFlow:o.renditionFlow??`auto`,renditionSpread:o.renditionSpread,readingDirection:o.readingDirection,spineItems:s.map((e,t)=>({id:e.id??m(e.uri),index:t,href:f(e.uri)?e.uri:`${r||`file://`}${e.uri}`,renditionLayout:e.renditionLayout,...e.renditionFlow===void 0?{}:{renditionFlow:e.renditionFlow},progressionWeight:e.progressionWeight,pageSpreadLeft:e.pageSpreadLeft,pageSpreadRight:e.pageSpreadRight,mediaType:e.mediaType})),items:a.opf.manifestItems.map(e=>({id:e.id,href:f(e.href)?e.href:`${r||`file://`}${c?`${c}/`:``}${e.href}`,mediaType:e.mediaType})),guide:l.length>0?l:void 0,nav:{toc:(i.toc??[]).map(u(r))}}},h=(n,r,i)=>{let{metadata:a,readingOrder:o}=i,c=n.records.filter(e=>!e.dir),d=(0,e.createXmlSafeIdFactory)(),f=new Map(c.map(e=>[e.uri,d(e.uri)])),m={filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(a)||p(n)||n.filename||``,renditionLayout:a.renditionLayout,renditionSpread:`auto`,readingDirection:a.readingDirection,spineItems:o.map((e,t)=>({id:f.get(e.uri)??d(e.uri),index:t,href:l({baseUrl:r,resourcePath:e.uri}),renditionLayout:e.renditionLayout,progressionWeight:e.progressionWeight,pageSpreadLeft:void 0,pageSpreadRight:void 0,mediaType:e.mediaType})),items:c.map(e=>({id:f.get(e.uri)??d(e.uri),href:encodeURI(`${r}${e.uri}`)}))},h=s(m,n)??i.toc?.map(u(r));return h===void 0?m:{...m,nav:{toc:h}}},g=({archive:e,baseUrl:t,resolved:n})=>{let r=n.sources.opf;return r?m(e,t,n,r):h(e,t,n)},_=e=>e?e.endsWith(`/`)?e:`${e}/`:``,v=async(e,{baseUrl:n=``,hooks:r=[]}={})=>{i.log(`Generating manifest from archive`,e);let a=_(n);try{let n=g({archive:e,baseUrl:a,resolved:await(0,t.resolveArchive)(e,{include:[`metadata`,`readingOrder`,`toc`,`sources`],layoutScan:!0})});for(let t of r)n=await t({archive:e,baseUrl:a})(n);if(n={...n,readingDirection:n.readingDirection??`ltr`},i.log(`Generated manifest`,n),process.env.NODE_ENV===`development`&&i.isEnabled()){let e=JSON.stringify(n,null,2);i.groupCollapsed(...i.getGroupArgs(`Generated manifest`)),i.log(`\n${e}`),i.groupEnd()}return n}catch(e){throw i.error(e),e}},y=(e,n)=>({archive:r,resourcePath:i})=>async a=>{let o=(0,t.getArchiveFileRecordByUri)(r,i);if(o?.basename.endsWith(e)){let e=await n(typeof a.body==`string`?a.body:await(0,t.readRecordAsText)(o));if(e!==void 0)return{...a,body:e}}return a},b=e=>{let t=e.descendantWithPath(`head`)?.childrenNamed(`meta`).find(e=>e.attr.name===`calibre:cover`);return!!(t&&t.attr.name===`calibre:cover`)},x=e=>e.descendantWithPath(`body`)?.descendantWithPath(`div`)?.childrenNamed(`svg`)?.find(e=>e.attr.width===`100%`&&e.attr.preserveAspectRatio===`none`),S=y(`.xhtml`,e=>{let t=new n.XmlDocument(e);if(!b(t))return;let r=x(t);return r&&delete r.attr.preserveAspectRatio,t.toString()}),C=y(`.css`,e=>e.replaceAll(`-webkit-writing-mode`,`writing-mode`)),w=async(e,n)=>{let r=await(0,t.readArchiveOpf)(e);if(r){let{opf:e,basePath:t}=r,i=e.manifestItems.find(e=>{let r=t?`${t}/${e.href}`:e.href;return n.endsWith(r)})?.mediaType;if(i)return{mediaType:i}}return{mediaType:T(n)}},T=e=>{if(e.endsWith(`.css`))return`text/css; charset=UTF-8`;if(e.endsWith(`.jpg`))return`image/jpg`;if(e.endsWith(`.xhtml`))return`application/xhtml+xml`;if(e.endsWith(`.mp4`))return`video/mp4`;if(e.endsWith(`.svg`))return`image/svg+xml`},E=({archive:e,resourcePath:n})=>async r=>{let i=(0,t.getArchiveFileRecordByUri)(e,n);if(!i)return r;let a=await w(e,n);return{...r,params:{...r.params,...i?.encodingFormat&&{contentType:i.encodingFormat},...a.mediaType&&{contentType:a.mediaType}}}},D=`div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style`.split(`.`),O=y(`.xhtml`,e=>{if(!RegExp(`<(${D.join(`|`)})[\\s/>]`,`i`).test(e))return;let t=RegExp(`<(${D.join(`|`)})(\\s[^>]*)?\\s*/>`,`gi`);return e.replace(t,(e,t,n=``)=>`<${t} ${n.trim()}></${t}>`)}),k=async(e,n,{hooks:r=[]}={})=>{let a={params:{}},o=[...r.map(t=>t({archive:e,resourcePath:n})),E({archive:e,resourcePath:n}),O({archive:e,resourcePath:n}),C({archive:e,resourcePath:n}),S({archive:e,resourcePath:n})];try{let r=await o.reduce(async(e,t)=>await t(await e),Promise.resolve(a));if(i.log(`Generated resource`,n,r),r.body!==void 0)return r;let s=(0,t.getArchiveFileRecordByUri)(e,n);if(!s)throw Error(`no file found for resourcePath:${n}`);return{...r,body:await s.blob()}}catch(e){throw i.error(e),e}},A=class{constructor(e){this.cleanArchiveAfter=e,this.state$=new r.BehaviorSubject({status:`idle`,locks:0})}update(e){this.state$.next({...this.state$.getValue(),...e})}get locks$(){return this.state$.pipe((0,r.map)(({locks:e})=>e))}get state(){return this.state$.getValue()}get isUnlocked$(){return this.locks$.pipe((0,r.map)(e=>e<=0),(0,r.distinctUntilChanged)(),(0,r.shareReplay)())}get overTTL$(){return this.isUnlocked$.pipe((0,r.switchMap)(e=>e?this.cleanArchiveAfter===1/0?r.NEVER:(0,r.timer)(this.cleanArchiveAfter):r.NEVER))}},j=({getArchive:e,cleanArchiveAfter:t=3e5})=>{let n=new r.Subject,a=new r.Subject,o=new r.Subject,s={};return n.pipe((0,r.mergeMap)(t=>{let n=s[t];if(n?.state.status!==`idle`)return r.EMPTY;let a=!1,c=e=>{i.debug(`Cleaning up archive with key: ${e}`);let t=s[e];delete s[e],a||=(t?.state.archive?.close(),!0)};n.update({status:`loading`});let l=n.locks$,u=n.isUnlocked$,d=l.pipe((0,r.pairwise)(),(0,r.filter)(([e,t])=>t>e),(0,r.startWith)(!0));return(0,r.from)(e(t)).pipe((0,r.tap)(e=>{n.update({archive:e,status:`success`})}),(0,r.catchError)(e=>(c(t),n.update({status:`error`,error:e}),r.EMPTY)),(0,r.switchMap)(()=>{let e=d.pipe((0,r.switchMap)(()=>o),(0,r.switchMap)(()=>u),(0,r.filter)(e=>e));return(0,r.merge)(e,n.overTTL$).pipe((0,r.first)(),(0,r.tap)(()=>{c(t)}))}))}),(0,r.takeUntil)(a)).subscribe(),{access:e=>{let i=!1,a=s[e]??new A(t);s[e]=a,a.update({locks:a.state.locks+1});let o=()=>{i||(i=!0,a.update({locks:a.state.locks-1}))};n.next(e);let c=a.state$.pipe((0,r.map)(({archive:e})=>e),(0,r.filter)(e=>!!e)),l=a.state$.pipe((0,r.tap)(({error:e})=>{if(e)throw e}),(0,r.ignoreElements)());return(0,r.merge)(c,l).pipe((0,r.first)(),(0,r.map)(e=>({archive:e,release:o})),(0,r.catchError)(e=>{throw o(),e}))},purge:()=>{o.next()},_archives:s}},M=e=>e?/^\d+$/.test(e)?{valid:!0,value:Number.parseInt(e,10)}:{valid:!1,value:void 0}:{valid:!0,value:void 0},N=e=>{if(!e.toLowerCase().startsWith(`bytes=`))return{kind:`missing`};let t=e.slice(6).trim();if(!t)return{kind:`invalid`};if(t.includes(`,`))return{kind:`multi`};let n=/^(\d*)-(\d*)$/.exec(t);if(!n)return{kind:`invalid`};let[,r=``,i=``]=n,a=M(r.trim()),o=M(i.trim());return!a.valid||!o.valid?{kind:`invalid`}:{kind:`single`,start:a.value,end:o.value}},P=e=>{if(e instanceof Blob)return{size:e.size,slice:(t,n)=>{let r=e.slice(t,n);return{content:r,length:r.size}}};let t=new TextEncoder().encode(e);return{size:t.byteLength,slice:(e,n)=>{let r=t.slice(e,n);return{content:r,length:r.byteLength}}}},F=({body:e,contentType:t,rangeHeader:n})=>{let r=new Headers;if(t&&r.set(`Content-Type`,t),r.set(`Accept-Ranges`,`bytes`),!n)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let i=N(n);if(i.kind===`missing`||i.kind===`multi`)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let a=P(e),o=a.size;if(i.kind===`invalid`)return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let s=i.start,c=i.end;if(s===void 0&&c===void 0||(s===void 0?(s=Math.max(0,o-Math.min(c??0,o)),c=o-1):(c===void 0||c>=o)&&(c=o-1),s<0||c<0||s>=o||c>=o||s>c))return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let l=a.slice(s,c+1);return r.set(`Content-Length`,String(l.length)),r.set(`Content-Range`,`bytes ${s}-${c}/${o}`),new Response(l.content,{status:206,headers:r})},I=`file://`,L=/^https?:\/\//,R=e=>{try{return decodeURIComponent(e)}catch{return e}},z=e=>e.startsWith(I)?e.slice(I.length):e,B=e=>{let t=z(e);return L.test(t)?t:z(R(t))},V=class{constructor({hooks:e,onError:t,onManifestSuccess:n,...r}){this.onError=e=>(console.error(e),new Response(String(e),{status:500})),this.archiveLoader=j(r),this.hooks=e??{},this.onManifestSuccess=n??(({manifest:e})=>Promise.resolve(e)),this.onError=t??this.onError}prune(){this.archiveLoader.purge()}accessArchive(e){return this.lastAccessedKey!==void 0&&this.lastAccessedKey!==e&&this.archiveLoader.purge(),this.lastAccessedKey=e,this.archiveLoader.access(e)}accessArchiveWithoutLock(e){return this.accessArchive(e).pipe((0,r.map)(({archive:e,release:t})=>(t(),e)))}withArchiveResponse({key:e,getResponse:t}){let n=this.accessArchive(e).pipe((0,r.mergeMap)(({archive:e,release:n})=>(0,r.from)(t(e)).pipe((0,r.finalize)(()=>{n()}))),(0,r.catchError)(e=>(0,r.of)(this.onError(e))));return(0,r.lastValueFrom)(n)}fetchManifest({key:e,baseUrl:t}){return this.withArchiveResponse({key:e,getResponse:e=>(0,r.from)(v(e,{baseUrl:t,hooks:this.hooks.manifest})).pipe((0,r.switchMap)(t=>(0,r.from)(this.onManifestSuccess({manifest:t,archive:e}))),(0,r.map)(e=>new Response(JSON.stringify(e),{status:200})))})}fetchResource({key:e,resourcePath:t,request:n}){return this.withArchiveResponse({key:e,getResponse:e=>{let i=B(t);return(0,r.from)(k(e,i,{hooks:this.hooks.resource})).pipe((0,r.map)(e=>F({body:e.body??``,contentType:e.params.contentType,rangeHeader:n?.headers.get(`range`)})))}})}},H=class extends V{constructor({getUriInfo:e,...t}){super(t),this.getUriInfo=e,this.fetchEventListener=this.fetchEventListener.bind(this)}fetchEventListener(t){try{let n=this.getUriInfo(t);if(!n)return;let r=(0,e.removeTrailingSlash)(n.baseUrl),i=t.request.url.substring(r.length+1),[a=``]=i.split(`/`),o=(0,e.removeTrailingSlash)(i.substring(a.length+1));i.endsWith(`/manifest`)?t.respondWith(this.fetchManifest({key:a,baseUrl:`${r}/${a}/`})):t.respondWith(this.fetchResource({key:a,resourcePath:o,request:t.request}))}catch(e){t.respondWith(new Response(String(e),{status:500}))}}};exports.ServiceWorkerStreamer=H,exports.Streamer=V,exports.configure=a,exports.createManifestResourceHref=l,exports.generateManifestFromArchive=v,exports.generateResourceFromArchive=k;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/shared"),t=require("@prose-reader/archive-reader"),n=require("xmldoc"),r=require("rxjs");var i=e.Report.namespace(`@prose-reader/streamer`,void 0,{color:`#ffae42`}),a=({enableReport:e}={})=>{i.enable(!!e)},o=e=>e.replace(/\.[^.]+$/,``).replace(/[_-]/g,` `).replace(/\s+/g,` `).trim(),s=t=>{if(t.spineItems.length!==0&&t.spineItems.every(t=>((0,e.parseContentType)(t.mediaType??``)||(0,e.detectMimeTypeFromName)(t.containerUri))?.startsWith(`audio/`)))return t.spineItems.map(t=>({title:o((0,e.getUriBasename)(t.containerUri)),href:t.href,path:t.containerUri,contents:[]}))},c=e=>encodeURI(e).replace(/#/g,`%23`).replace(/\?/g,`%3F`),l=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return`${encodeURI(n)}${c(t)}`},u=t=>n=>({title:n.title,path:n.path,href:n.containerHref?(0,e.urlJoin)(t,n.containerHref):``,contents:n.contents.map(u(t))}),d=e=>{let t=e?.trim();if(t===`cover`||t===`title-page`||t===`copyright-page`||t===`text`)return t},f=e=>/^https?:\/\//.test(e),p=e=>e.records.find(({dir:e})=>e)?.basename.replace(/\/$/,``),m=(n,r,i,a)=>{let{metadata:o,readingOrder:s}=i,c=a.basePath,l=[];for(let e of a.opf.guide){let t=d(e.type);t!==void 0&&l.push({href:e.href,title:e.title,type:t})}let m=(0,e.createXmlSafeIdFactory)();return{filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(o)||p(n)||``,renditionLayout:o.renditionLayout,renditionFlow:o.renditionFlow??`auto`,renditionSpread:o.renditionSpread,readingDirection:o.readingDirection,spineItems:s.map((e,t)=>({id:e.id??m(e.uri),index:t,href:f(e.uri)?e.uri:`${r||`file://`}${e.containerHref??e.uri}`,containerUri:e.uri,renditionLayout:e.renditionLayout,...e.renditionFlow===void 0?{}:{renditionFlow:e.renditionFlow},progressionWeight:e.progressionWeight,pageSpreadLeft:e.pageSpreadLeft,pageSpreadRight:e.pageSpreadRight,mediaType:e.mediaType})),items:a.opf.manifestItems.map(e=>({id:e.id,href:f(e.href)?e.href:`${r||`file://`}${c?`${c}/`:``}${e.href}`,mediaType:e.mediaType})),guide:l.length>0?l:void 0,nav:{toc:(i.toc??[]).map(u(r))}}},h=(n,r,i)=>{let{metadata:a,readingOrder:o}=i,c=n.records.filter(e=>!e.dir),d=(0,e.createXmlSafeIdFactory)(),f=new Map(c.map(e=>[e.uri,d(e.uri)])),m={filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(a)||p(n)||n.filename||``,renditionLayout:a.renditionLayout,renditionSpread:`auto`,readingDirection:a.readingDirection,spineItems:o.map((e,t)=>({id:f.get(e.uri)??d(e.uri),index:t,href:l({baseUrl:r,resourcePath:e.uri}),containerUri:e.uri,renditionLayout:e.renditionLayout,progressionWeight:e.progressionWeight,pageSpreadLeft:void 0,pageSpreadRight:void 0,mediaType:e.mediaType})),items:c.map(e=>({id:f.get(e.uri)??d(e.uri),href:encodeURI(`${r}${e.uri}`)}))},h=s(m)??i.toc?.map(u(r));return h===void 0?m:{...m,nav:{toc:h}}},g=({archive:e,baseUrl:t,resolved:n})=>{let r=n.sources.opf;return r?m(e,t,n,r):h(e,t,n)},_=e=>e?e.endsWith(`/`)?e:`${e}/`:``,v=async(e,{baseUrl:n=``,hooks:r=[]}={})=>{i.log(`Generating manifest from archive`,e);let a=_(n);try{let n=g({archive:e,baseUrl:a,resolved:await(0,t.resolveArchive)(e,{include:[`metadata`,`readingOrder`,`toc`,`sources`],layoutScan:!0})});for(let t of r)n=await t({archive:e,baseUrl:a})(n);if(n={...n,readingDirection:n.readingDirection??`ltr`},i.log(`Generated manifest`,n),process.env.NODE_ENV===`development`&&i.isEnabled()){let e=JSON.stringify(n,null,2);i.groupCollapsed(...i.getGroupArgs(`Generated manifest`)),i.log(`\n${e}`),i.groupEnd()}return n}catch(e){throw i.error(e),e}},y=(e,n)=>({archive:r,resourcePath:i})=>async a=>{let o=(0,t.getArchiveFileRecordByUri)(r,i);if(o?.basename.endsWith(e)){let e=await n(typeof a.body==`string`?a.body:await(0,t.readRecordAsText)(o));if(e!==void 0)return{...a,body:e}}return a},b=e=>{let t=e.descendantWithPath(`head`)?.childrenNamed(`meta`).find(e=>e.attr.name===`calibre:cover`);return!!(t&&t.attr.name===`calibre:cover`)},x=e=>e.descendantWithPath(`body`)?.descendantWithPath(`div`)?.childrenNamed(`svg`)?.find(e=>e.attr.width===`100%`&&e.attr.preserveAspectRatio===`none`),S=y(`.xhtml`,e=>{let t=new n.XmlDocument(e);if(!b(t))return;let r=x(t);return r&&delete r.attr.preserveAspectRatio,t.toString()}),C=y(`.css`,e=>e.replaceAll(`-webkit-writing-mode`,`writing-mode`)),w=async(e,n)=>{let r=await(0,t.readArchiveOpf)(e);if(r){let{opf:e,basePath:t}=r,i=e.manifestItems.find(e=>{let r=t?`${t}/${e.href}`:e.href;return n.endsWith(r)})?.mediaType;if(i)return{mediaType:i}}return{mediaType:T(n)}},T=e=>{if(e.endsWith(`.css`))return`text/css; charset=UTF-8`;if(e.endsWith(`.jpg`))return`image/jpg`;if(e.endsWith(`.xhtml`))return`application/xhtml+xml`;if(e.endsWith(`.mp4`))return`video/mp4`;if(e.endsWith(`.svg`))return`image/svg+xml`},E=({archive:e,resourcePath:n})=>async r=>{let i=(0,t.getArchiveFileRecordByUri)(e,n);if(!i)return r;let a=await w(e,n);return{...r,params:{...r.params,...i?.encodingFormat&&{contentType:i.encodingFormat},...a.mediaType&&{contentType:a.mediaType}}}},D=`div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style`.split(`.`),O=y(`.xhtml`,e=>{if(!RegExp(`<(${D.join(`|`)})[\\s/>]`,`i`).test(e))return;let t=RegExp(`<(${D.join(`|`)})(\\s[^>]*)?\\s*/>`,`gi`);return e.replace(t,(e,t,n=``)=>`<${t} ${n.trim()}></${t}>`)}),k=async(e,n,{hooks:r=[]}={})=>{let a={params:{}},o=[...r.map(t=>t({archive:e,resourcePath:n})),E({archive:e,resourcePath:n}),O({archive:e,resourcePath:n}),C({archive:e,resourcePath:n}),S({archive:e,resourcePath:n})];try{let r=await o.reduce(async(e,t)=>await t(await e),Promise.resolve(a));if(i.log(`Generated resource`,n,r),r.body!==void 0)return r;let s=(0,t.getArchiveFileRecordByUri)(e,n);if(!s)throw Error(`no file found for resourcePath:${n}`);return{...r,body:await s.blob()}}catch(e){throw i.error(e),e}},A=class{constructor(e){this.cleanArchiveAfter=e,this.state$=new r.BehaviorSubject({status:`idle`,locks:0})}update(e){this.state$.next({...this.state$.getValue(),...e})}get locks$(){return this.state$.pipe((0,r.map)(({locks:e})=>e))}get state(){return this.state$.getValue()}get isUnlocked$(){return this.locks$.pipe((0,r.map)(e=>e<=0),(0,r.distinctUntilChanged)(),(0,r.shareReplay)())}get overTTL$(){return this.isUnlocked$.pipe((0,r.switchMap)(e=>e?this.cleanArchiveAfter===1/0?r.NEVER:(0,r.timer)(this.cleanArchiveAfter):r.NEVER))}},j=({getArchive:e,cleanArchiveAfter:t=3e5})=>{let n=new r.Subject,a=new r.Subject,o=new r.Subject,s={};return n.pipe((0,r.mergeMap)(t=>{let n=s[t];if(n?.state.status!==`idle`)return r.EMPTY;let a=!1,c=e=>{i.debug(`Cleaning up archive with key: ${e}`);let t=s[e];delete s[e],a||=(t?.state.archive?.close(),!0)};n.update({status:`loading`});let l=n.locks$,u=n.isUnlocked$,d=l.pipe((0,r.pairwise)(),(0,r.filter)(([e,t])=>t>e),(0,r.startWith)(!0));return(0,r.from)(e(t)).pipe((0,r.tap)(e=>{n.update({archive:e,status:`success`})}),(0,r.catchError)(e=>(c(t),n.update({status:`error`,error:e}),r.EMPTY)),(0,r.switchMap)(()=>{let e=d.pipe((0,r.switchMap)(()=>o),(0,r.switchMap)(()=>u),(0,r.filter)(e=>e));return(0,r.merge)(e,n.overTTL$).pipe((0,r.first)(),(0,r.tap)(()=>{c(t)}))}))}),(0,r.takeUntil)(a)).subscribe(),{access:e=>{let i=!1,a=s[e]??new A(t);s[e]=a,a.update({locks:a.state.locks+1});let o=()=>{i||(i=!0,a.update({locks:a.state.locks-1}))};n.next(e);let c=a.state$.pipe((0,r.map)(({archive:e})=>e),(0,r.filter)(e=>!!e)),l=a.state$.pipe((0,r.tap)(({error:e})=>{if(e)throw e}),(0,r.ignoreElements)());return(0,r.merge)(c,l).pipe((0,r.first)(),(0,r.map)(e=>({archive:e,release:o})),(0,r.catchError)(e=>{throw o(),e}))},purge:()=>{o.next()},_archives:s}},M=e=>e?/^\d+$/.test(e)?{valid:!0,value:Number.parseInt(e,10)}:{valid:!1,value:void 0}:{valid:!0,value:void 0},N=e=>{if(!e.toLowerCase().startsWith(`bytes=`))return{kind:`missing`};let t=e.slice(6).trim();if(!t)return{kind:`invalid`};if(t.includes(`,`))return{kind:`multi`};let n=/^(\d*)-(\d*)$/.exec(t);if(!n)return{kind:`invalid`};let[,r=``,i=``]=n,a=M(r.trim()),o=M(i.trim());return!a.valid||!o.valid?{kind:`invalid`}:{kind:`single`,start:a.value,end:o.value}},P=e=>{if(e instanceof Blob)return{size:e.size,slice:(t,n)=>{let r=e.slice(t,n);return{content:r,length:r.size}}};let t=new TextEncoder().encode(e);return{size:t.byteLength,slice:(e,n)=>{let r=t.slice(e,n);return{content:r,length:r.byteLength}}}},F=({body:e,contentType:t,rangeHeader:n})=>{let r=new Headers;if(t&&r.set(`Content-Type`,t),r.set(`Accept-Ranges`,`bytes`),!n)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let i=N(n);if(i.kind===`missing`||i.kind===`multi`)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let a=P(e),o=a.size;if(i.kind===`invalid`)return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let s=i.start,c=i.end;if(s===void 0&&c===void 0||(s===void 0?(s=Math.max(0,o-Math.min(c??0,o)),c=o-1):(c===void 0||c>=o)&&(c=o-1),s<0||c<0||s>=o||c>=o||s>c))return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let l=a.slice(s,c+1);return r.set(`Content-Length`,String(l.length)),r.set(`Content-Range`,`bytes ${s}-${c}/${o}`),new Response(l.content,{status:206,headers:r})},I=`file://`,L=/^https?:\/\//,R=e=>{try{return decodeURIComponent(e)}catch{return e}},z=e=>e.startsWith(I)?e.slice(I.length):e,B=e=>{let t=z(e);return L.test(t)?t:z(R(t))},V=class{constructor({hooks:e,onError:t,onManifestSuccess:n,...r}){this.onError=e=>(console.error(e),new Response(String(e),{status:500})),this.archiveLoader=j(r),this.hooks=e??{},this.onManifestSuccess=n??(({manifest:e})=>Promise.resolve(e)),this.onError=t??this.onError}prune(){this.archiveLoader.purge()}accessArchive(e){return this.lastAccessedKey!==void 0&&this.lastAccessedKey!==e&&this.archiveLoader.purge(),this.lastAccessedKey=e,this.archiveLoader.access(e)}accessArchiveWithoutLock(e){return this.accessArchive(e).pipe((0,r.map)(({archive:e,release:t})=>(t(),e)))}withArchiveResponse({key:e,getResponse:t}){let n=this.accessArchive(e).pipe((0,r.mergeMap)(({archive:e,release:n})=>(0,r.from)(t(e)).pipe((0,r.finalize)(()=>{n()}))),(0,r.catchError)(e=>(0,r.of)(this.onError(e))));return(0,r.lastValueFrom)(n)}fetchManifest({key:e,baseUrl:t}){return this.withArchiveResponse({key:e,getResponse:e=>(0,r.from)(v(e,{baseUrl:t,hooks:this.hooks.manifest})).pipe((0,r.switchMap)(t=>(0,r.from)(this.onManifestSuccess({manifest:t,archive:e}))),(0,r.map)(e=>new Response(JSON.stringify(e),{status:200})))})}fetchResource({key:e,resourcePath:t,request:n}){return this.withArchiveResponse({key:e,getResponse:e=>{let i=B(t);return(0,r.from)(k(e,i,{hooks:this.hooks.resource})).pipe((0,r.map)(e=>F({body:e.body??``,contentType:e.params.contentType,rangeHeader:n?.headers.get(`range`)})))}})}},H=class extends V{constructor({getUriInfo:e,...t}){super(t),this.getUriInfo=e,this.fetchEventListener=this.fetchEventListener.bind(this)}fetchEventListener(t){try{let n=this.getUriInfo(t);if(!n)return;let r=(0,e.removeTrailingSlash)(n.baseUrl),i=t.request.url.substring(r.length+1),[a=``]=i.split(`/`),o=(0,e.removeTrailingSlash)(i.substring(a.length+1));i.endsWith(`/manifest`)?t.respondWith(this.fetchManifest({key:a,baseUrl:`${r}/${a}/`})):t.respondWith(this.fetchResource({key:a,resourcePath:o,request:t.request}))}catch(e){t.respondWith(new Response(String(e),{status:500}))}}};exports.ServiceWorkerStreamer=H,exports.Streamer=V,exports.configure=a,exports.createManifestResourceHref=l,exports.generateManifestFromArchive=v,exports.generateResourceFromArchive=k;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["SharedReport","parseContentType","detectMimeTypeFromName","urlJoin","createXmlSafeIdFactory","mainTitle","resolveArchive","getArchiveFileRecordByUri","readRecordAsText","XmlDocument","readArchiveOpf","getArchiveFileRecordByUri","getArchiveFileRecordByUri","BehaviorSubject","map","distinctUntilChanged","shareReplay","switchMap","NEVER","timer","Subject","mergeMap","EMPTY","pairwise","filter","startWith","from","tap","catchError","merge","first","takeUntil","ignoreElements","map","mergeMap","from","finalize","catchError","of","lastValueFrom","switchMap","removeTrailingSlash","_prose_reader_shared","_prose_reader_archive_reader","xmldoc","rxjs","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","item","record","archive","r","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","hookFactory","hooks","manifestStr","createTextResourceHook","extension","newBody","transform","resource","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","foundMediaType","containerHref","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","update","locks","isUnlocked","createArchiveLoader","loadSubject","destroySubject","purgeSubject","archives","archiveEntry","key","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","prev","curent","getArchive","readyForPurge$","releaseCalled","release","archive$","error$","error","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","streamerPath"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"26.0.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\nexport const buildAudiobookToc = (\n manifest: Manifest,\n archive: Archive,\n): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.href)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => {\n const record = archive.records.find(\n (r) => !r.dir && decodeURI(item.href).endsWith(r.uri),\n )\n\n return {\n title: normalizeFilenameAsTitle(record?.basename ?? item.href),\n href: item.href,\n path: record?.uri ?? item.href,\n contents: [],\n }\n })\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.uri}`,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${opfBasePath ? `${opfBasePath}/` : ``}${item.href}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest, archive)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n} from \"@prose-reader/archive-reader\"\nimport type { HookResource } from \"./types\"\n\n/**\n * Resolves `contentType` from the package OPF when possible, else from the URI.\n * Uses `readArchiveOpf` each time — see `generateResourceFromArchive` JSDoc for\n * why that can mean repeated OPF work on the resource path.\n */\nconst getMetadata = async (archive: Archive, resourcePath: string) => {\n const parsed = await readArchiveOpf(archive)\n\n if (parsed) {\n const { opf, basePath } = parsed\n\n // we are comparing opf items container-relative path in epub archive\n // against resourcePath (which is an absolute path in archive).\n // They should in theory match.\n const foundMediaType = opf.manifestItems.find((item) => {\n const containerHref = basePath ? `${basePath}/${item.href}` : item.href\n\n return resourcePath.endsWith(containerHref)\n })?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["SharedReport","parseContentType","detectMimeTypeFromName","getUriBasename","urlJoin","createXmlSafeIdFactory","mainTitle","resolveArchive","getArchiveFileRecordByUri","readRecordAsText","XmlDocument","readArchiveOpf","getArchiveFileRecordByUri","getArchiveFileRecordByUri","BehaviorSubject","map","distinctUntilChanged","shareReplay","switchMap","NEVER","timer","Subject","mergeMap","EMPTY","pairwise","filter","startWith","from","tap","catchError","merge","first","takeUntil","ignoreElements","map","mergeMap","from","finalize","catchError","of","lastValueFrom","switchMap","removeTrailingSlash","_prose_reader_shared","_prose_reader_archive_reader","xmldoc","rxjs","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","item","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","archive","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","hookFactory","hooks","manifestStr","createTextResourceHook","extension","newBody","transform","resource","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","foundMediaType","containerHref","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","update","locks","isUnlocked","createArchiveLoader","loadSubject","destroySubject","purgeSubject","archives","archiveEntry","key","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","prev","curent","getArchive","readyForPurge$","releaseCalled","release","archive$","error$","error","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","streamerPath"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"28.0.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import {\n detectMimeTypeFromName,\n getUriBasename,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\n/**\n * One entry per track for a book made only of audio, each titled after the\n * archive record it plays.\n */\nexport const buildAudiobookToc = (manifest: Manifest): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.containerUri)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => ({\n title: normalizeFilenameAsTitle(getUriBasename(item.containerUri)),\n href: item.href,\n path: item.containerUri,\n contents: [],\n }))\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere, and\n // the reference the book makes to them, which its navigation and links\n // match as written\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.containerHref ?? item.uri}`,\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${opfBasePath ? `${opfBasePath}/` : ``}${item.href}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n} from \"@prose-reader/archive-reader\"\nimport type { HookResource } from \"./types\"\n\n/**\n * Resolves `contentType` from the package OPF when possible, else from the URI.\n * Uses `readArchiveOpf` each time — see `generateResourceFromArchive` JSDoc for\n * why that can mean repeated OPF work on the resource path.\n */\nconst getMetadata = async (archive: Archive, resourcePath: string) => {\n const parsed = await readArchiveOpf(archive)\n\n if (parsed) {\n const { opf, basePath } = parsed\n\n // we are comparing opf items container-relative path in epub archive\n // against resourcePath (which is an absolute path in archive).\n // They should in theory match.\n const foundMediaType = opf.manifestItems.find((item) => {\n const containerHref = basePath ? `${basePath}/${item.href}` : item.href\n\n return resourcePath.endsWith(containerHref)\n })?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"A,yLCOA,IAAa+C,EAAS/C,EAAAA,OAAa,UAAU,yBAAM,IAAA,GAAW,CAC5D,MAAO,SACT,CAAC,ECPYgD,GAAa,CACxB,gBAGE,CAAC,IAAM,CACTD,EAAO,OAAO,CAAC,CAACE,CAAY,CAC9B,ECCaC,EAA4B,GACvCC,EACG,QAAQ,WAAY,EAAE,CAAC,CACvB,QAAQ,QAAS,GAAG,CAAC,CACrB,QAAQ,OAAQ,GAAG,CAAC,CACpB,KAAK,EAMGC,EAAqB,GAAwC,CACxE,GAAIC,EAAS,WAAW,SAAW,GAElBA,EAAS,WAAW,MAAO,KAK1C,EAHEpD,EAAAA,iBAAAA,CAAiBqD,EAAK,WAAa,EAAE,IAAA,EACrCpD,EAAAA,uBAAAA,CAAuBoD,EAAK,YAAY,EAAA,EAEzB,WAAW,QAAQ,CAGjC,EAEL,OAAOD,EAAS,WAAW,IAAK,IAAU,CACxC,MAAOH,GAAA,EAAyB/C,EAAAA,eAAAA,CAAemD,EAAK,YAAY,CAAC,EACjE,KAAMA,EAAK,KACX,KAAMA,EAAK,aACX,SAAU,CAAC,CACb,EAAE,CACJ,ECnCMC,EAAsB,GAC1B,UAAUC,CAAY,CAAC,CAAC,QAAQ,KAAM,KAAK,CAAC,CAAC,QAAQ,MAAO,KAAK,EAEtDC,GAA8B,CACzC,UAAU,GACV,kBAII,CACJ,GAAI,CAACC,GAAW,eAAe,KAAKF,CAAY,EAC9C,OAAO,UAAUA,CAAY,EAG/B,IAAMG,EAAcD,EAChB,GAAGA,IAAUA,EAAQ,SAAS,GAAG,EAAI,GAAK,MAC1C,UAEJ,MAAO,GAAG,UAAUC,CAAW,IAAIJ,EAAmBC,CAAY,GACpE,ECLMI,EACH,GACA,IAAmC,CAClC,MAAON,EAAK,MACZ,KAAMA,EAAK,KAGX,KAAMA,EAAK,eAAA,EAAgBlD,EAAAA,QAAAA,CAAQsD,EAASJ,EAAK,aAAa,EAAI,GAClE,SAAUA,EAAK,SAAS,IAAIM,EAAkBF,CAAO,CAAC,CACxD,GAEIG,EACJ,GAC2C,CAC3C,IAAMC,EAAIC,GAAK,KAAK,EACpB,GACED,IAAM,SACNA,IAAM,cACNA,IAAM,kBACNA,IAAM,OAEN,OAAOA,CAGX,EAEME,EAAa,GAAkB,eAAe,KAAKC,CAAK,EAExDC,EAAuB,GAC3BC,EAAQ,QAAQ,MAAM,CAAE,SAAUC,CAAG,CAAC,EAAE,SAAS,QAAQ,MAAO,EAAE,EAY9DC,GACJ,EACA,EACA,EACA,IACa,CACb,GAAM,CAAE,WAAU,gBAAiBC,EAC7BC,EAAcC,EAAI,SAElBC,EAA8B,CAAC,EACrC,IAAK,IAAMC,KAAaF,EAAI,IAAI,MAAO,CACrC,IAAMG,EAAOd,EAAkBa,EAAU,IAAI,EACzCC,IAAS,IAAA,IAGbF,EAAM,KAAK,CAAE,KAAMC,EAAU,KAAM,MAAOA,EAAU,MAAO,MAAK,CAAC,CACnE,CAEA,IAAME,GAAA,EAAevE,EAAAA,uBAAAA,CAAuB,EAE5C,MAAO,CACL,SAAU8D,EAAQ,UAAY,GAC9B,UAAWG,EAAS,UACpB,OAAA,EAAOhE,EAAAA,UAAAA,CAAUuE,CAAQ,GAAKX,EAAoBC,CAAO,GAAK,GAC9D,gBAAiBU,EAAS,gBAC1B,cAAeA,EAAS,eAAiB,OACzC,gBAAiBA,EAAS,gBAC1B,iBAAkBA,EAAS,iBAC3B,WAAYC,EAAa,KAAK,EAAM,KAAW,CAC7C,GAAIxB,EAAK,IAAMsB,EAAatB,EAAK,GAAG,EACpC,QAKA,KAAMU,EAAUV,EAAK,GAAG,EACpBA,EAAK,IACL,GAAGI,GAAW,YAAYJ,EAAK,eAAiBA,EAAK,MACzD,aAAcA,EAAK,IACnB,gBAAiBA,EAAK,gBACtB,GAAIA,EAAK,gBAAkB,IAAA,GAEvB,CAAC,EADD,CAAE,cAAeA,EAAK,aAAc,EAExC,kBAAmBA,EAAK,kBACxB,eAAgBA,EAAK,eACrB,gBAAiBA,EAAK,gBACtB,UAAWA,EAAK,SAClB,EAAE,EACF,MAAOkB,EAAI,IAAI,cAAc,IAAK,IAAU,CAC1C,GAAIlB,EAAK,GACT,KAAMU,EAAUV,EAAK,IAAI,EACrBA,EAAK,KACL,GAAGI,GAAW,YAAYa,EAAc,GAAGA,EAAY,GAAK,KAAKjB,EAAK,OAC1E,UAAWA,EAAK,SAClB,EAAE,EACF,MAAOmB,EAAM,OAAS,EAAIA,EAAQ,IAAA,GAClC,IAAK,CAEH,KAAMH,EAAS,KAAO,CAAC,EAAA,CAAG,IAAIV,EAAkBF,CAAO,CAAC,CAC1D,CACF,CACF,EAEMqB,GACJ,EACA,EACA,IACa,CACb,GAAM,CAAE,WAAU,gBAAiBT,EAE7BU,EAAQb,EAAQ,QAAQ,OAAQ,GAAS,CAACc,EAAK,GAAG,EAClDL,GAAA,EAAevE,EAAAA,uBAAAA,CAAuB,EACtC6E,EAAU,IAAI,IAClBF,EAAM,IAAK,GAAS,CAACC,EAAK,IAAKL,EAAaK,EAAK,GAAG,CAAC,CAAC,CACxD,EAEM5B,EAAqB,CACzB,SAAUc,EAAQ,UAAY,GAC9B,UAAWG,EAAS,UACpB,OAAA,EACEhE,EAAAA,UAAAA,CAAUuE,CAAQ,GAClBX,EAAoBC,CAAO,GAC3BA,EAAQ,UACR,GACF,gBAAiBU,EAAS,gBAC1B,gBAAiB,OACjB,iBAAkBA,EAAS,iBAC3B,WAAYC,EAAa,KAAK,EAAM,KAAW,CAC7C,GAAII,EAAQ,IAAI5B,EAAK,GAAG,GAAKsB,EAAatB,EAAK,GAAG,EAClD,QACA,KAAMG,EAA2B,CAAE,UAAS,aAAcH,EAAK,GAAI,CAAC,EACpE,aAAcA,EAAK,IACnB,gBAAiBA,EAAK,gBACtB,kBAAmBA,EAAK,kBACxB,eAAgB,IAAA,GAChB,gBAAiB,IAAA,GACjB,UAAWA,EAAK,SAClB,EAAE,EAGF,MAAO0B,EAAM,IAAK,IAAU,CAC1B,GAAIE,EAAQ,IAAID,EAAK,GAAG,GAAKL,EAAaK,EAAK,GAAG,EAClD,KAAM,UAAU,GAAGvB,IAAUuB,EAAK,KAAK,CACzC,EAAE,CACJ,EAGME,EADe/B,EAAkBC,CAC3B,GAAgBiB,EAAS,KAAK,IAAIV,EAAkBF,CAAO,CAAC,EAExE,OAAOyB,IAAQ,IAAA,GAA4C9B,EAAhC,CAAE,GAAGA,EAAU,IAAK,CAAE,KAAI,CAAE,CACzD,EASa+B,GAA+B,CAC1C,UACA,UACA,cAKc,CACd,IAAMZ,EAAMF,EAAS,QAAQ,IAI7B,OAFIE,EAAYH,EAAaF,EAAST,EAASY,EAAUE,CAAG,EAErDO,EAAgBZ,EAAST,EAASY,CAAQ,CACnD,EC1LMe,EAAoB,GACnB3B,EAEEA,EAAQ,SAAS,GAAG,EAAIA,EAAU,GAAGA,EAAQ,GAF/B,GAaV4B,EAA8B,MACzC,EACA,CACE,UAAU,GACV,QAAQ,CAAC,GACsD,CAAC,IAC/D,CACHvC,EAAO,IAAI,mCAAoCoB,CAAO,EAEtD,IAAMoB,EAAoBF,EAAiB3B,CAAO,EAElD,GAAI,CAMF,IAAIL,EAAW+B,EAA4B,CACzC,UACA,QAASG,EACT,eARe,EAAMhF,EAAAA,eAAAA,CAAe4D,EAAS,CAC7C,QAAS,CAAC,WAAY,eAAgB,MAAO,SAAS,EACtD,WAAY,EACd,CAAC,CAMD,CAAC,EAED,IAAK,IAAMqB,KAAeC,EACxB,EAAW,MAAMD,EAAY,CAC3B,UACA,QAASD,CACX,CAAC,CAAC,CAAClC,CAAQ,EAUb,GAPA,EAAW,CACT,GAAGA,EACH,iBAAkBA,EAAS,kBAAoB,KACjD,EAEAN,EAAO,IAAI,qBAAsBM,CAAQ,EAEzC,QAAA,IAAA,WAA6B,eACvBN,EAAO,UAAU,EAAG,CACtB,IAAM2C,EAAc,KAAK,UAAUrC,EAAU,KAAM,CAAC,EACpDN,EAAO,eAAe,GAAGA,EAAO,aAAa,oBAAoB,CAAC,EAClEA,EAAO,IAAI,KAAK2C,GAAa,EAC7B3C,EAAO,SAAS,CAClB,CAGF,OAAOM,CACT,OAAS,EAAG,CAGV,MAFAN,EAAO,MAAM,CAAC,EAER,CACR,CACF,ECnDa4C,GAET,EACA,KAID,CACC,UACA,kBAEF,KAAO,IAAa,CAClB,IAAMV,GAAA,EAAOzE,EAAAA,0BAAAA,CAA0B2D,EAASX,CAAY,EAE5D,GAAIyB,GAAM,SAAS,SAASW,CAAS,EAAG,CAMtC,IAAMC,EAAU,MAAMC,EAJpB,OAAOC,EAAS,MAAS,SACrBA,EAAS,KACT,MAAA,EAAMtF,EAAAA,iBAAAA,CAAiBwE,CAAI,CAEU,EAE3C,GAAIY,IAAY,IAAA,GACd,MAAO,CACL,GAAGE,EACH,KAAMF,CACR,CAEJ,CAEA,OAAOE,CACT,ECjDIC,EAAuB,GAAqB,CAChD,IAAMC,EAAUC,EACb,mBAAmB,MAAM,CAAC,EACzB,cAAc,MAAM,CAAC,CACtB,KAAM,GAASC,EAAK,KAAK,OAAS,eAAe,EAEpD,MAAO,CAAC,EAAEF,GAAWA,EAAQ,KAAK,OAAS,gBAC7C,EAEMG,EAAoB,GACjBF,EACJ,mBAAmB,MAAM,CAAC,EACzB,mBAAmB,KAAK,CAAC,EACzB,cAAc,KAAK,CAAC,EACpB,KACC,GACCC,EAAK,KAAK,QAAU,QAAUA,EAAK,KAAK,sBAAwB,MACpE,EAGSE,EAAiBV,EAC5B,SACC,GAAgB,CACf,IAAMW,EAAY,IAAI5F,EAAAA,YAAY6F,CAAW,EAE7C,GAAI,CAACP,EAAoBM,CAAS,EAChC,OAGF,IAAME,EAAWJ,EAAiBE,CAAS,EAM3C,OAJIE,GACF,OAAOA,EAAS,KAAK,oBAGhBF,EAAU,SAAS,CAC5B,CACF,ECtCaG,EAAad,EAAuB,OAAS,GAKxDe,EAAK,WAAW,uBAAwB,cAAc,CACxD,ECIMC,EAAc,MAAO,EAAkB,IAAyB,CACpE,IAAMC,EAAS,MAAA,EAAMjG,EAAAA,eAAAA,CAAewD,CAAO,EAE3C,GAAIyC,EAAQ,CACV,GAAM,CAAE,MAAK,YAAaA,EAKpBC,EAAiBrC,EAAI,cAAc,KAAM,GAAS,CACtD,IAAMsC,EAAgBC,EAAW,GAAGA,EAAS,GAAGzD,EAAK,OAASA,EAAK,KAEnE,OAAOE,EAAa,SAASsD,CAAa,CAC5C,CAAC,CAAC,EAAE,UAEJ,GAAID,EACF,MAAO,CACL,UAAWA,CACb,CAEJ,CAEA,MAAO,CACL,UAAWG,EAA4BxD,CAAY,CACrD,CACF,EAEMwD,EAA+B,GAAgB,CACnD,GAAIC,EAAI,SAAS,MAAM,EACrB,MAAO,0BAET,GAAIA,EAAI,SAAS,MAAM,EACrB,MAAO,YAET,GAAIA,EAAI,SAAS,QAAQ,EACvB,MAAO,wBAET,GAAIA,EAAI,SAAS,MAAM,EACrB,MAAO,YAET,GAAIA,EAAI,SAAS,MAAM,EACrB,MAAO,eAEX,EAEaC,GACV,CAAE,UAAS,kBACZ,KAAO,IAAkD,CACvD,IAAMjC,GAAA,EAAOrE,EAAAA,0BAAAA,CAA0BuD,EAASX,CAAY,EAE5D,GAAI,CAACyB,EAAM,OAAOc,EAElB,IAAMlB,EAAW,MAAM8B,EAAYxC,EAASX,CAAY,EAExD,MAAO,CACL,GAAGuC,EACH,OAAQ,CACN,GAAGA,EAAS,OACZ,GAAId,GAAM,gBAAkB,CAC1B,YAAaA,EAAK,cACpB,EACA,GAAIJ,EAAS,WAAa,CACxB,YAAaA,EAAS,SACxB,CACF,CACF,CACF,EC5EIsC,EAAyB,wRAmD/B,EASaC,EAAyBzB,EACpC,SACC,GAAgB,CAKf,GAAI,CAJwB,OAC1B,KAAKwB,EAAuB,KAAK,GAAG,EAAE,UACtC,GAEG,CAAA,CAAgB,KAAKZ,CAAW,EACnC,OAGF,IAAMc,EAAiB,OACrB,KAAKF,EAAuB,KAAK,GAAG,EAAE,oBACtC,IACF,EAEA,OAAOZ,EAAY,QAAQc,GAAa,EAAG,EAAS,EAAa,KAExD,IAAIC,EAAQ,GAAGC,EAAW,KAAK,EAAE,KAAKD,EAAQ,EACtD,CACH,CACF,EC7DaE,EAA8B,MACzC,EACA,EACA,CAAE,QAAQ,CAAC,GAAiD,CAAC,IAC1D,CACH,IAAMC,EAAgC,CACpC,OAAQ,CAAC,CACX,EAOMC,EAAgB,CACpB,GAPoBjC,EAAM,IAAK,GAASkC,EAAK,CAAE,UAAS,cAAa,CAAC,CAOnE,EACHT,EAAY,CAAE,UAAS,cAAa,CAAC,EACrCE,EAAuB,CAAE,UAAS,cAAa,CAAC,EAChDX,EAAW,CAAE,UAAS,cAAa,CAAC,EACpCJ,EAAe,CAAE,UAAS,cAAa,CAAC,CAC1C,EAEA,GAAI,CACF,IAAMN,EAAW,MAAM2B,EAAc,OAAO,MAAO,EAAU,IACpD,MAAME,EAAI,MAAMvE,CAAQ,EAC9B,QAAQ,QAAQoE,CAAe,CAAC,EAInC,GAFA1E,EAAO,IAAI,qBAAsBS,EAAcuC,CAAQ,EAEnDA,EAAS,OAAS,IAAA,GACpB,OAAOA,EAGT,IAAMd,GAAA,EAAOpE,EAAAA,0BAAAA,CAA0BsD,EAASX,CAAY,EAE5D,GAAI,CAACyB,EACH,MAAU,MAAM,kCAAkCzB,GAAc,EAGlE,MAAO,CACL,GAAGuC,EACH,KAAM,MAAMd,EAAK,KAAK,CACxB,CACF,OAAS,EAAG,CAGV,MAFAlC,EAAO,MAAM,CAAC,EAER,CACR,CACF,EC5CM8E,EAAN,KAAmB,CAWjB,YAAY,EAAmC,CAA3B,KAAA,kBAAAC,EAVX,KAAA,OAAA,IAAIhH,EAAAA,gBAKV,CACD,OAAQ,OACR,MAAO,CACT,CAAC,CAE+C,CAEhD,OAAO,EAAsD,CAC3D,KAAK,OAAO,KAAK,CAAE,GAAG,KAAK,OAAO,SAAS,EAAG,GAAGiH,CAAO,CAAC,CAC3D,CAEA,IAAI,QAAS,CACX,OAAO,KAAK,OAAO,MAAA,EAAKhH,EAAAA,IAAAA,EAAK,CAAE,WAAYiH,CAAK,CAAC,CACnD,CAEA,IAAI,OAAQ,CACV,OAAO,KAAK,OAAO,SAAS,CAC9B,CAEA,IAAI,aAAc,CAChB,OAAO,KAAK,OAAO,MAAA,EACjBjH,EAAAA,IAAAA,CAAK,GAAUiH,GAAS,CAAC,GAAA,EACzBhH,EAAAA,qBAAAA,CAAqB,GAAA,EACrBC,EAAAA,YAAAA,CAAY,CACd,CACF,CAEA,IAAI,UAAW,CACb,OAAO,KAAK,YAAY,MAAA,EACtBC,EAAAA,UAAAA,CAAW,GACR+G,EAEG,KAAK,oBAAsB,IACzB9G,EAAAA,OAAAA,EACAC,EAAAA,MAAAA,CAAM,KAAK,iBAAiB,EAH9BD,EAAAA,KAIN,CACF,CACF,CACF,EAEa+G,GAAuB,CAClC,aACA,oBAAoB,OAIhB,CACJ,IAAMC,EAAc,IAAI9G,EAAAA,QAClB+G,EAAiB,IAAI/G,EAAAA,QACrBgH,EAAe,IAAIhH,EAAAA,QACnBiH,EAAyC,CAAC,EAqIhD,OAnIqBH,EAAY,MAAA,EAC/B7G,EAAAA,SAAAA,CAAU,GAAQ,CAChB,IAAMiH,EAAeD,EAASE,GAE9B,GAAID,GAAc,MAAM,SAAW,OAAQ,OAAOhH,EAAAA,MAElD,IAAIkH,EAAW,GAETC,EAAkB,GAAgB,CACtC3F,EAAO,MAAM,iCAAiCyF,GAAK,EAEnD,IAAMG,EAAQL,EAASE,GAEvB,OAAOF,EAASE,GAEhB,AAEEC,KADAE,GAAO,MAAM,SAAS,MAAM,EACjB,GAEf,EAEAJ,EAAa,OAAO,CAClB,OAAQ,SACV,CAAC,EAED,IAAMK,EAASL,EAAa,OACtBM,EAAcN,EAAa,YAE3BO,EAAaF,EAAO,MAAA,EACxBpH,EAAAA,SAAAA,CAAS,GAAA,EACTC,EAAAA,OAAAA,EAAQ,CAACsH,EAAMC,KAAYA,EAASD,CAAI,GAAA,EACxCrH,EAAAA,UAAAA,CAAU,EAAI,CAChB,EAIA,OAAA,EAFiBC,EAAAA,KAAAA,CAAKsH,EAAWT,CAAG,CAE7B,CAAA,CAAS,MAAA,EACd5G,EAAAA,IAAAA,CAAK,GAAY,CACf2G,EAAa,OAAO,CAClB,UACA,OAAQ,SACV,CAAC,CACH,CAAC,GAAA,EACD1G,EAAAA,WAAAA,CAAY,IACV6G,EAAeF,CAAG,EAElBD,EAAa,OAAO,CAClB,OAAQ,QACR,OACF,CAAC,EAEMhH,EAAAA,MACR,GAAA,EACDL,EAAAA,UAAAA,KAAgB,CACd,IAAMgI,EAAiBJ,EAAW,MAAA,EAChC5H,EAAAA,UAAAA,KAAgBmH,CAAY,GAAA,EAC5BnH,EAAAA,UAAAA,KAAgB2H,CAAW,GAAA,EAC3BpH,EAAAA,OAAAA,CAAQ,GAAewG,CAAU,CACnC,EAIA,OAAA,EAFmBnG,EAAAA,MAAAA,CAAMoH,EAAgBX,EAAa,QAE/C,CAAA,CAAW,MAAA,EAChBxG,EAAAA,MAAAA,CAAM,GAAA,EACNH,EAAAA,IAAAA,KAAU,CACR8G,EAAeF,CAAG,CACpB,CAAC,CACH,CACF,CAAC,CACH,CACF,CAAC,GAAA,EACDxG,EAAAA,UAAAA,CAAUoG,CAAc,CA0D1B,CAAA,CAAa,UAAU,EAEhB,CACL,OA1Dc,GAAgB,CAC9B,IAAIe,EAAgB,GAEdZ,EAAeD,EAASE,IAAQ,IAAIX,EAAaC,CAAiB,EAExE,EAASU,GAAOD,EAEhBA,EAAa,OAAO,CAClB,MAAOA,EAAa,MAAM,MAAQ,CACpC,CAAC,EAED,IAAMa,MAAgB,CAChBD,IAEJ,EAAgB,GAEhBZ,EAAa,OAAO,CAClB,MAAOA,EAAa,MAAM,MAAQ,CACpC,CAAC,EACH,EAEAJ,EAAY,KAAKK,CAAG,EAEpB,IAAMa,EAAWd,EAAa,OAAO,MAAA,EACnCxH,EAAAA,IAAAA,EAAK,CAAE,aAAcoD,CAAO,GAAA,EAC5B1C,EAAAA,OAAAA,CAAQ,GAAY,CAAC,CAAC0C,CAAO,CAC/B,EAEMmF,EAASf,EAAa,OAAO,MAAA,EACjC3G,EAAAA,IAAAA,EAAK,CAAE,WAAY,CACjB,GAAI2H,EACF,MAAMA,CAEV,CAAC,GAAA,EACDtH,EAAAA,eAAAA,CAAe,CACjB,EAEA,OAAA,EAAOH,EAAAA,MAAAA,CAAMuH,EAAUC,CAAM,CAAC,CAAC,MAAA,EAC7BvH,EAAAA,MAAAA,CAAM,GAAA,EACNhB,EAAAA,IAAAA,CAAK,IAAa,CAAE,UAAS,SAAQ,EAAE,GAAA,EACvCc,EAAAA,WAAAA,CAAY,GAAU,CAGpB,MAFAuH,EAAQ,EAEFG,CACR,CAAC,CACH,CACF,EAaE,UARkB,CAClBlB,EAAa,KAAK,CACpB,EAOE,UAAWC,CACb,CACF,EC5NMkB,EAAsB,GACrBvF,EAOA,QAAQ,KAAKA,CAAK,EAOhB,CACL,MAAO,GACP,MAAO,OAAO,SAASA,EAAO,EAAE,CAClC,EATS,CACL,MAAO,GACP,MAAO,IAAA,EACT,EAVO,CACL,MAAO,GACP,MAAO,IAAA,EACT,EAgBEwF,EAAwB,GAAwB,CACpD,GAAI,CAACC,EAAY,YAAY,CAAC,CAAC,WAAW,QAAQ,EAChD,MAAO,CACL,KAAM,SACR,EAGF,IAAMC,EAAaD,EAAY,MAAM,CAAe,CAAC,CAAC,KAAK,EAE3D,GAAI,CAACC,EACH,MAAO,CACL,KAAM,SACR,EAGF,GAAIA,EAAW,SAAS,GAAG,EACzB,MAAO,CACL,KAAM,OACR,EAGF,IAAMC,EAAU,gBAAgB,KAAKD,CAAU,EAE/C,GAAI,CAACC,EACH,MAAO,CACL,KAAM,SACR,EAGF,GAAM,EAAGC,EAAW,GAAIC,EAAS,IAAMF,EACjCG,EAAcP,EAAmBK,EAAS,KAAK,CAAC,EAChDG,EAAYR,EAAmBM,EAAO,KAAK,CAAC,EAQlD,MANI,CAACC,EAAY,OAAS,CAACC,EAAU,MAC5B,CACL,KAAM,SACR,EAGK,CACL,KAAM,SACN,MAAOD,EAAY,MACnB,IAAKC,EAAU,KACjB,CACF,EAQMC,EAAyB,GAAwB,CACrD,GAAIvD,aAAgB,KAClB,MAAO,CACL,KAAMA,EAAK,KACX,OAAQ,EAAe,IAAyB,CAC9C,IAAMwD,EAAOxD,EAAK,MAAMyD,EAAOC,CAAY,EAC3C,MAAO,CAAE,QAASF,EAAM,OAAQA,EAAK,IAAK,CAC5C,CACF,EAGF,IAAMG,EAAQ,IAAI,YAAY,CAAC,CAAC,OAAO3D,CAAI,EAE3C,MAAO,CACL,KAAM2D,EAAM,WACZ,OAAQ,EAAe,IAAyB,CAC9C,IAAMH,EAAOG,EAAM,MAAMF,EAAOC,CAAY,EAC5C,MAAO,CAAE,QAASF,EAAM,OAAQA,EAAK,UAAW,CAClD,CACF,CACF,EAEaI,GAAuB,CAClC,OACA,cACA,iBAKI,CACJ,IAAMC,EAAU,IAAI,QAQpB,GANIC,GACFD,EAAQ,IAAI,eAAgBC,CAAW,EAGzCD,EAAQ,IAAI,gBAAiB,OAAO,EAEhC,CAACb,EAKH,OAJIhD,aAAgB,MAClB6D,EAAQ,IAAI,iBAAkB,OAAO7D,EAAK,IAAI,CAAC,EAG1C,IAAI,SAASA,EAAM,CACxB,OAAQ,IACR,SACF,CAAC,EAGH,IAAM+D,EAAchB,EAAqBC,CAAW,EAEpD,GAAIe,EAAY,OAAS,WAAaA,EAAY,OAAS,QAKzD,OAJI/D,aAAgB,MAClB6D,EAAQ,IAAI,iBAAkB,OAAO7D,EAAK,IAAI,CAAC,EAG1C,IAAI,SAASA,EAAM,CACxB,OAAQ,IACR,SACF,CAAC,EAGH,IAAMgE,EAAYT,EAAsBvD,CAAI,EACtCiE,EAAOD,EAAU,KAEvB,GAAID,EAAY,OAAS,UACvB,OAAO,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,QAAS,CACP,gBAAiB,WAAWE,GAC9B,CACF,CAAC,EAGH,IAAIR,EAAQM,EAAY,MACpBG,EAAMH,EAAY,IAmBtB,GAjBIN,IAAU,IAAA,IAAaS,IAAQ,IAAA,KAS/BT,IAAU,IAAA,IAEZ,EAAQ,KAAK,IAAI,EAAGQ,EADC,KAAK,IAAIC,GAAO,EAAGD,CACb,CAAY,EACvC,EAAMA,EAAO,IACJC,IAAQ,IAAA,IAAaA,GAAOD,KACrC,EAAMA,EAAO,GAGXR,EAAQ,GAAKS,EAAM,GAAKT,GAASQ,GAAQC,GAAOD,GAAQR,EAAQS,GAClE,OAAO,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,QAAS,CACP,gBAAiB,WAAWD,GAC9B,CACF,CAAC,EAGH,IAAME,EAAUH,EAAU,MAAMP,EAAOS,EAAM,CAAC,EAK9C,OAHAL,EAAQ,IAAI,iBAAkB,OAAOM,EAAQ,MAAM,CAAC,EACpDN,EAAQ,IAAI,gBAAiB,SAASJ,EAAM,GAAGS,EAAI,GAAGD,GAAM,EAErD,IAAI,SAASE,EAAQ,QAAS,CACnC,OAAQ,IACR,SACF,CAAC,CACH,EC9JMC,EAAe,UACfC,EAAsB,eAEtBC,EAAsB,GAAyB,CACnD,GAAI,CACF,OAAO,mBAAmBxH,CAAY,CACxC,MAAQ,CACN,OAAOA,CACT,CACF,EAEMyH,EAAqB,GACzBzH,EAAa,WAAWsH,CAAY,EAChCtH,EAAa,MAAMsH,EAAa,MAAM,EACtCtH,EAEA0H,EAAyB,GAAyB,CACtD,IAAMC,EAAkCF,EAAkBzH,CAAY,EAMtE,OAJIuH,EAAoB,KAAKI,CAA+B,EACnDA,EAGFF,EAAkBD,EAAmBG,CAA+B,CAAC,CAC9E,EAEaC,EAAb,KAAsB,CAWpB,YAAY,CACV,QACA,UACA,oBACA,GAAGC,GAKF,CAlB2B,KAAA,QAAA,IAC5B,QAAQ,MAAM9B,CAAK,EAEZ,IAAI,SAAS,OAAOA,CAAK,EAAG,CAAE,OAAQ,GAAI,CAAC,GAgBlD,KAAK,cAAgBrB,EAAoBmD,CAAI,EAC7C,KAAK,MAAQ5F,GAAS,CAAC,EAEvB,KAAK,kBACH6F,KAAuB,CAAE,cAAe,QAAQ,QAAQjI,CAAQ,GAClE,KAAK,QAAUkI,GAAW,KAAK,OACjC,CAEA,OAAe,CACb,KAAK,cAAc,MAAM,CAC3B,CAEA,cAAqB,EAAa,CAOhC,OANI,KAAK,kBAAoB,IAAA,IAAa,KAAK,kBAAoB/C,GACjE,KAAK,cAAc,MAAM,EAG3B,KAAK,gBAAkBA,EAEhB,KAAK,cAAc,OAAOA,CAAG,CACtC,CAEA,yBAAgC,EAAa,CAC3C,OAAO,KAAK,cAAcA,CAAG,CAAC,CAAC,MAAA,EAC7BtG,EAAAA,IAAAA,EAAK,CAAE,UAAS,cACdkH,EAAQ,EAEDjF,EACR,CACH,CACF,CAEA,oBAA8B,CAC5B,MACA,eAIC,CACD,IAAMqH,EAAY,KAAK,cAAchD,CAAG,CAAC,CAAC,MAAA,EACxCrG,EAAAA,SAAAA,EAAU,CAAE,UAAS,cAAA,EACnBC,EAAAA,KAAAA,CAAKqJ,EAAYtH,CAAO,CAAC,CAAC,CAAC,MAAA,EACzB9B,EAAAA,SAAAA,KAAe,CACb+G,EAAQ,CACV,CAAC,CACH,CACF,GAAA,EACA9G,EAAAA,WAAAA,CAAY,IACV,EAAOC,EAAAA,GAAAA,CAAG,KAAK,QAAQgH,CAAK,CAAC,CAC9B,CACH,EAEA,OAAA,EAAO/G,EAAAA,cAAAA,CAAcgJ,CAAS,CAChC,CAEA,cAAqB,CAAE,MAAK,WAA8C,CACxE,OAAO,KAAK,oBAAoB,CAC9B,MACA,YAAc,IAQZ,EAPkBpJ,EAAAA,KAAAA,CAChBkD,EAA4BnB,EAAS,CACnC,UACA,MAAO,KAAK,MAAM,QACpB,CAAC,CAGI,CAAA,CAAU,MAAA,EACf1B,EAAAA,UAAAA,CAAW,IAAA,EACTL,EAAAA,KAAAA,CAAK,KAAK,kBAAkB,CAAE,WAAU,SAAQ,CAAC,CAAC,CACpD,GAAA,EACAF,EAAAA,IAAAA,CACG,GACC,IAAI,SAAS,KAAK,UAAUmB,CAA2B,EAAG,CACxD,OAAQ,GACV,CAAC,CACL,CACF,CAEJ,CAAC,CACH,CAEA,cAAqB,CACnB,MACA,eACA,WAKC,CACD,OAAO,KAAK,oBAAoB,CAC9B,MACA,YAAc,GAAY,CAOxB,IAAMqI,EAAsBR,EAAsB1H,CAAY,EAa9D,OAAA,EANkBpB,EAAAA,KAAAA,CAChBoF,EAA4BrD,EAASuH,EAAqB,CACxD,MAAO,KAAK,MAAM,QACpB,CAAC,CAGI,CAAA,CAAU,MAAA,EACfxJ,EAAAA,IAAAA,CAAK,GACHoI,EAAoB,CAClB,KAAMvE,EAAS,MAAQ,GACvB,YAAaA,EAAS,OAAO,YAC7B,YAAa4F,GAAS,QAAQ,IAAI,OAAO,CAC3C,CAAC,CACH,CACF,CACF,CACF,CAAC,CACH,CACF,EC9LaC,EAAb,cAA2CR,CAAS,CAOlD,YAAY,CACV,aACA,GAAGC,GAOF,CACD,MAAMA,CAAI,EAEV,KAAK,WAAaQ,EAClB,KAAK,mBAAqB,KAAK,mBAAmB,KAAK,IAAI,CAC7D,CAEA,mBAAmB,EAAwC,CACzD,GAAI,CACF,IAAMC,EAAU,KAAK,WAAWC,CAAK,EAErC,GAAI,CAACD,EAAS,OAEd,IAAMpI,GAAA,EAAUhB,EAAAA,oBAAAA,CAAoBoJ,EAAQ,OAAO,EAC7CE,EAAeD,EAAM,QAAQ,IAAI,UACrCrI,EAAQ,OAAS,CACnB,EACM,CAAC8E,EAAM,IAAMwD,EAAa,MAAM,GAAG,EACnCxI,GAAA,EAAed,EAAAA,oBAAAA,CACnBsJ,EAAa,UAAUxD,EAAI,OAAS,CAAU,CAChD,EAEIwD,EAAa,SAAS,WAAW,EACnCD,EAAM,YACJ,KAAK,cAAc,CAAE,MAAK,QAAS,GAAGrI,EAAQ,GAAG8E,EAAI,EAAG,CAAC,CAC3D,EAEAuD,EAAM,YACJ,KAAK,cAAc,CACjB,MACA,eACA,QAASA,EAAM,OACjB,CAAC,CACH,CAEJ,OAAS,EAAG,CACVA,EAAM,YAAY,IAAI,SAAS,OAAO,CAAC,EAAG,CAAE,OAAQ,GAAI,CAAC,CAAC,CAC5D,CACF,CACF,E"}
@@ -1,21 +1,18 @@
1
- import { Report as e, createXmlSafeIdFactory as t, detectMimeTypeFromName as n, parseContentType as r, removeTrailingSlash as i, urlJoin as a } from "@prose-reader/shared";
2
- import { getArchiveFileRecordByUri as o, mainTitle as s, readArchiveOpf as c, readRecordAsText as l, resolveArchive as u } from "@prose-reader/archive-reader";
3
- import { XmlDocument as d } from "xmldoc";
4
- import { BehaviorSubject as f, EMPTY as p, NEVER as m, Subject as h, catchError as g, distinctUntilChanged as _, filter as v, finalize as ee, first as y, from as b, ignoreElements as te, lastValueFrom as x, map as S, merge as C, mergeMap as w, of as ne, pairwise as T, shareReplay as E, startWith as D, switchMap as O, takeUntil as k, tap as A, timer as re } from "rxjs";
1
+ import { Report as e, createXmlSafeIdFactory as t, detectMimeTypeFromName as n, getUriBasename as r, parseContentType as i, removeTrailingSlash as a, urlJoin as o } from "@prose-reader/shared";
2
+ import { getArchiveFileRecordByUri as s, mainTitle as c, readArchiveOpf as l, readRecordAsText as u, resolveArchive as d } from "@prose-reader/archive-reader";
3
+ import { XmlDocument as f } from "xmldoc";
4
+ import { BehaviorSubject as p, EMPTY as m, NEVER as h, Subject as g, catchError as _, distinctUntilChanged as ee, filter as v, finalize as y, first as b, from as x, ignoreElements as te, lastValueFrom as ne, map as S, merge as C, mergeMap as w, of as T, pairwise as E, shareReplay as D, startWith as re, switchMap as O, takeUntil as ie, tap as k, timer as ae } from "rxjs";
5
5
  //#endregion
6
6
  //#region src/report.ts
7
- var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie = ({ enableReport: e } = {}) => {
8
- j.enable(!!e);
9
- }, ae = (e) => e.replace(/\.[^.]+$/, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim(), M = (e, t) => {
10
- if (e.spineItems.length !== 0 && e.spineItems.every((e) => (r(e.mediaType ?? "") || n(e.href))?.startsWith("audio/"))) return e.spineItems.map((e) => {
11
- let n = t.records.find((t) => !t.dir && decodeURI(e.href).endsWith(t.uri));
12
- return {
13
- title: ae(n?.basename ?? e.href),
14
- href: e.href,
15
- path: n?.uri ?? e.href,
16
- contents: []
17
- };
18
- });
7
+ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe = ({ enableReport: e } = {}) => {
8
+ A.enable(!!e);
9
+ }, j = (e) => e.replace(/\.[^.]+$/, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim(), M = (e) => {
10
+ if (e.spineItems.length !== 0 && e.spineItems.every((e) => (i(e.mediaType ?? "") || n(e.containerUri))?.startsWith("audio/"))) return e.spineItems.map((e) => ({
11
+ title: j(r(e.containerUri)),
12
+ href: e.href,
13
+ path: e.containerUri,
14
+ contents: []
15
+ }));
19
16
  }, N = (e) => encodeURI(e).replace(/#/g, "%23").replace(/\?/g, "%3F"), P = ({ baseUrl: e = "", resourcePath: t }) => {
20
17
  if (!e && /^https?:\/\//.test(t)) return encodeURI(t);
21
18
  let n = e ? `${e}${e.endsWith("/") ? "" : "/"}` : "file://";
@@ -23,13 +20,13 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
23
20
  }, F = (e) => (t) => ({
24
21
  title: t.title,
25
22
  path: t.path,
26
- href: t.containerHref ? a(e, t.containerHref) : "",
23
+ href: t.containerHref ? o(e, t.containerHref) : "",
27
24
  contents: t.contents.map(F(e))
28
25
  }), I = (e) => {
29
26
  let t = e?.trim();
30
27
  if (t === "cover" || t === "title-page" || t === "copyright-page" || t === "text") return t;
31
28
  }, L = (e) => /^https?:\/\//.test(e), R = (e) => e.records.find(({ dir: e }) => e)?.basename.replace(/\/$/, ""), z = (e, n, r, i) => {
32
- let { metadata: a, readingOrder: o } = r, c = i.basePath, l = [];
29
+ let { metadata: a, readingOrder: o } = r, s = i.basePath, l = [];
33
30
  for (let e of i.opf.guide) {
34
31
  let t = I(e.type);
35
32
  t !== void 0 && l.push({
@@ -42,7 +39,7 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
42
39
  return {
43
40
  filename: e.filename ?? "",
44
41
  mediaType: r.mediaType,
45
- title: s(a) || R(e) || "",
42
+ title: c(a) || R(e) || "",
46
43
  renditionLayout: a.renditionLayout,
47
44
  renditionFlow: a.renditionFlow ?? "auto",
48
45
  renditionSpread: a.renditionSpread,
@@ -50,7 +47,8 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
50
47
  spineItems: o.map((e, t) => ({
51
48
  id: e.id ?? u(e.uri),
52
49
  index: t,
53
- href: L(e.uri) ? e.uri : `${n || "file://"}${e.uri}`,
50
+ href: L(e.uri) ? e.uri : `${n || "file://"}${e.containerHref ?? e.uri}`,
51
+ containerUri: e.uri,
54
52
  renditionLayout: e.renditionLayout,
55
53
  ...e.renditionFlow === void 0 ? {} : { renditionFlow: e.renditionFlow },
56
54
  progressionWeight: e.progressionWeight,
@@ -60,27 +58,28 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
60
58
  })),
61
59
  items: i.opf.manifestItems.map((e) => ({
62
60
  id: e.id,
63
- href: L(e.href) ? e.href : `${n || "file://"}${c ? `${c}/` : ""}${e.href}`,
61
+ href: L(e.href) ? e.href : `${n || "file://"}${s ? `${s}/` : ""}${e.href}`,
64
62
  mediaType: e.mediaType
65
63
  })),
66
64
  guide: l.length > 0 ? l : void 0,
67
65
  nav: { toc: (r.toc ?? []).map(F(n)) }
68
66
  };
69
67
  }, B = (e, n, r) => {
70
- let { metadata: i, readingOrder: a } = r, o = e.records.filter((e) => !e.dir), c = t(), l = new Map(o.map((e) => [e.uri, c(e.uri)])), u = {
68
+ let { metadata: i, readingOrder: a } = r, o = e.records.filter((e) => !e.dir), s = t(), l = new Map(o.map((e) => [e.uri, s(e.uri)])), u = {
71
69
  filename: e.filename ?? "",
72
70
  mediaType: r.mediaType,
73
- title: s(i) || R(e) || e.filename || "",
71
+ title: c(i) || R(e) || e.filename || "",
74
72
  renditionLayout: i.renditionLayout,
75
73
  renditionSpread: "auto",
76
74
  readingDirection: i.readingDirection,
77
75
  spineItems: a.map((e, t) => ({
78
- id: l.get(e.uri) ?? c(e.uri),
76
+ id: l.get(e.uri) ?? s(e.uri),
79
77
  index: t,
80
78
  href: P({
81
79
  baseUrl: n,
82
80
  resourcePath: e.uri
83
81
  }),
82
+ containerUri: e.uri,
84
83
  renditionLayout: e.renditionLayout,
85
84
  progressionWeight: e.progressionWeight,
86
85
  pageSpreadLeft: void 0,
@@ -88,10 +87,10 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
88
87
  mediaType: e.mediaType
89
88
  })),
90
89
  items: o.map((e) => ({
91
- id: l.get(e.uri) ?? c(e.uri),
90
+ id: l.get(e.uri) ?? s(e.uri),
92
91
  href: encodeURI(`${n}${e.uri}`)
93
92
  }))
94
- }, d = M(u, e) ?? r.toc?.map(F(n));
93
+ }, d = M(u) ?? r.toc?.map(F(n));
95
94
  return d === void 0 ? u : {
96
95
  ...u,
97
96
  nav: { toc: d }
@@ -100,13 +99,13 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
100
99
  let r = n.sources.opf;
101
100
  return r ? z(e, t, n, r) : B(e, t, n);
102
101
  }, H = (e) => e ? e.endsWith("/") ? e : `${e}/` : "", U = async (e, { baseUrl: t = "", hooks: n = [] } = {}) => {
103
- j.log("Generating manifest from archive", e);
102
+ A.log("Generating manifest from archive", e);
104
103
  let r = H(t);
105
104
  try {
106
105
  let t = V({
107
106
  archive: e,
108
107
  baseUrl: r,
109
- resolved: await u(e, {
108
+ resolved: await d(e, {
110
109
  include: [
111
110
  "metadata",
112
111
  "readingOrder",
@@ -123,18 +122,18 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
123
122
  if (t = {
124
123
  ...t,
125
124
  readingDirection: t.readingDirection ?? "ltr"
126
- }, j.log("Generated manifest", t), process.env.NODE_ENV === "development" && j.isEnabled()) {
125
+ }, A.log("Generated manifest", t), process.env.NODE_ENV === "development" && A.isEnabled()) {
127
126
  let e = JSON.stringify(t, null, 2);
128
- j.groupCollapsed(...j.getGroupArgs("Generated manifest")), j.log(`\n${e}`), j.groupEnd();
127
+ A.groupCollapsed(...A.getGroupArgs("Generated manifest")), A.log(`\n${e}`), A.groupEnd();
129
128
  }
130
129
  return t;
131
130
  } catch (e) {
132
- throw j.error(e), e;
131
+ throw A.error(e), e;
133
132
  }
134
133
  }, W = (e, t) => ({ archive: n, resourcePath: r }) => async (i) => {
135
- let a = o(n, r);
134
+ let a = s(n, r);
136
135
  if (a?.basename.endsWith(e)) {
137
- let e = await t(typeof i.body == "string" ? i.body : await l(a));
136
+ let e = await t(typeof i.body == "string" ? i.body : await u(a));
138
137
  if (e !== void 0) return {
139
138
  ...i,
140
139
  body: e
@@ -144,13 +143,13 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
144
143
  }, G = (e) => {
145
144
  let t = e.descendantWithPath("head")?.childrenNamed("meta").find((e) => e.attr.name === "calibre:cover");
146
145
  return !!(t && t.attr.name === "calibre:cover");
147
- }, K = (e) => e.descendantWithPath("body")?.descendantWithPath("div")?.childrenNamed("svg")?.find((e) => e.attr.width === "100%" && e.attr.preserveAspectRatio === "none"), q = W(".xhtml", (e) => {
148
- let t = new d(e);
146
+ }, K = (e) => e.descendantWithPath("body")?.descendantWithPath("div")?.childrenNamed("svg")?.find((e) => e.attr.width === "100%" && e.attr.preserveAspectRatio === "none"), se = W(".xhtml", (e) => {
147
+ let t = new f(e);
149
148
  if (!G(t)) return;
150
149
  let n = K(t);
151
150
  return n && delete n.attr.preserveAspectRatio, t.toString();
152
- }), oe = W(".css", (e) => e.replaceAll("-webkit-writing-mode", "writing-mode")), se = async (e, t) => {
153
- let n = await c(e);
151
+ }), ce = W(".css", (e) => e.replaceAll("-webkit-writing-mode", "writing-mode")), le = async (e, t) => {
152
+ let n = await l(e);
154
153
  if (n) {
155
154
  let { opf: e, basePath: r } = n, i = e.manifestItems.find((e) => {
156
155
  let n = r ? `${r}/${e.href}` : e.href;
@@ -158,17 +157,17 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
158
157
  })?.mediaType;
159
158
  if (i) return { mediaType: i };
160
159
  }
161
- return { mediaType: ce(t) };
162
- }, ce = (e) => {
160
+ return { mediaType: ue(t) };
161
+ }, ue = (e) => {
163
162
  if (e.endsWith(".css")) return "text/css; charset=UTF-8";
164
163
  if (e.endsWith(".jpg")) return "image/jpg";
165
164
  if (e.endsWith(".xhtml")) return "application/xhtml+xml";
166
165
  if (e.endsWith(".mp4")) return "video/mp4";
167
166
  if (e.endsWith(".svg")) return "image/svg+xml";
168
- }, le = ({ archive: e, resourcePath: t }) => async (n) => {
169
- let r = o(e, t);
167
+ }, de = ({ archive: e, resourcePath: t }) => async (n) => {
168
+ let r = s(e, t);
170
169
  if (!r) return n;
171
- let i = await se(e, t);
170
+ let i = await le(e, t);
172
171
  return {
173
172
  ...n,
174
173
  params: {
@@ -177,48 +176,48 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
177
176
  ...i.mediaType && { contentType: i.mediaType }
178
177
  }
179
178
  };
180
- }, J = /* @__PURE__ */ "div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style".split("."), ue = W(".xhtml", (e) => {
181
- if (!RegExp(`<(${J.join("|")})[\\s/>]`, "i").test(e)) return;
182
- let t = RegExp(`<(${J.join("|")})(\\s[^>]*)?\\s*/>`, "gi");
179
+ }, q = /* @__PURE__ */ "div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style".split("."), fe = W(".xhtml", (e) => {
180
+ if (!RegExp(`<(${q.join("|")})[\\s/>]`, "i").test(e)) return;
181
+ let t = RegExp(`<(${q.join("|")})(\\s[^>]*)?\\s*/>`, "gi");
183
182
  return e.replace(t, (e, t, n = "") => `<${t} ${n.trim()}></${t}>`);
184
- }), Y = async (e, t, { hooks: n = [] } = {}) => {
183
+ }), J = async (e, t, { hooks: n = [] } = {}) => {
185
184
  let r = { params: {} }, i = [
186
185
  ...n.map((n) => n({
187
186
  archive: e,
188
187
  resourcePath: t
189
188
  })),
190
- le({
189
+ de({
191
190
  archive: e,
192
191
  resourcePath: t
193
192
  }),
194
- ue({
193
+ fe({
195
194
  archive: e,
196
195
  resourcePath: t
197
196
  }),
198
- oe({
197
+ ce({
199
198
  archive: e,
200
199
  resourcePath: t
201
200
  }),
202
- q({
201
+ se({
203
202
  archive: e,
204
203
  resourcePath: t
205
204
  })
206
205
  ];
207
206
  try {
208
207
  let n = await i.reduce(async (e, t) => await t(await e), Promise.resolve(r));
209
- if (j.log("Generated resource", t, n), n.body !== void 0) return n;
210
- let a = o(e, t);
208
+ if (A.log("Generated resource", t, n), n.body !== void 0) return n;
209
+ let a = s(e, t);
211
210
  if (!a) throw Error(`no file found for resourcePath:${t}`);
212
211
  return {
213
212
  ...n,
214
213
  body: await a.blob()
215
214
  };
216
215
  } catch (e) {
217
- throw j.error(e), e;
216
+ throw A.error(e), e;
218
217
  }
219
- }, de = class {
218
+ }, pe = class {
220
219
  constructor(e) {
221
- this.cleanArchiveAfter = e, this.state$ = new f({
220
+ this.cleanArchiveAfter = e, this.state$ = new p({
222
221
  status: "idle",
223
222
  locks: 0
224
223
  });
@@ -236,52 +235,52 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
236
235
  return this.state$.getValue();
237
236
  }
238
237
  get isUnlocked$() {
239
- return this.locks$.pipe(S((e) => e <= 0), _(), E());
238
+ return this.locks$.pipe(S((e) => e <= 0), ee(), D());
240
239
  }
241
240
  get overTTL$() {
242
- return this.isUnlocked$.pipe(O((e) => e ? this.cleanArchiveAfter === Infinity ? m : re(this.cleanArchiveAfter) : m));
241
+ return this.isUnlocked$.pipe(O((e) => e ? this.cleanArchiveAfter === Infinity ? h : ae(this.cleanArchiveAfter) : h));
243
242
  }
244
- }, fe = ({ getArchive: e, cleanArchiveAfter: t = 3e5 }) => {
245
- let n = new h(), r = new h(), i = new h(), a = {};
243
+ }, me = ({ getArchive: e, cleanArchiveAfter: t = 3e5 }) => {
244
+ let n = new g(), r = new g(), i = new g(), a = {};
246
245
  return n.pipe(w((t) => {
247
246
  let n = a[t];
248
- if (n?.state.status !== "idle") return p;
247
+ if (n?.state.status !== "idle") return m;
249
248
  let r = !1, o = (e) => {
250
- j.debug(`Cleaning up archive with key: ${e}`);
249
+ A.debug(`Cleaning up archive with key: ${e}`);
251
250
  let t = a[e];
252
251
  delete a[e], r ||= (t?.state.archive?.close(), !0);
253
252
  };
254
253
  n.update({ status: "loading" });
255
- let s = n.locks$, c = n.isUnlocked$, l = s.pipe(T(), v(([e, t]) => t > e), D(!0));
256
- return b(e(t)).pipe(A((e) => {
254
+ let s = n.locks$, c = n.isUnlocked$, l = s.pipe(E(), v(([e, t]) => t > e), re(!0));
255
+ return x(e(t)).pipe(k((e) => {
257
256
  n.update({
258
257
  archive: e,
259
258
  status: "success"
260
259
  });
261
- }), g((e) => (o(t), n.update({
260
+ }), _((e) => (o(t), n.update({
262
261
  status: "error",
263
262
  error: e
264
- }), p)), O(() => {
263
+ }), m)), O(() => {
265
264
  let e = l.pipe(O(() => i), O(() => c), v((e) => e));
266
- return C(e, n.overTTL$).pipe(y(), A(() => {
265
+ return C(e, n.overTTL$).pipe(b(), k(() => {
267
266
  o(t);
268
267
  }));
269
268
  }));
270
- }), k(r)).subscribe(), {
269
+ }), ie(r)).subscribe(), {
271
270
  access: (e) => {
272
- let r = !1, i = a[e] ?? new de(t);
271
+ let r = !1, i = a[e] ?? new pe(t);
273
272
  a[e] = i, i.update({ locks: i.state.locks + 1 });
274
273
  let o = () => {
275
274
  r || (r = !0, i.update({ locks: i.state.locks - 1 }));
276
275
  };
277
276
  n.next(e);
278
- let s = i.state$.pipe(S(({ archive: e }) => e), v((e) => !!e)), c = i.state$.pipe(A(({ error: e }) => {
277
+ let s = i.state$.pipe(S(({ archive: e }) => e), v((e) => !!e)), c = i.state$.pipe(k(({ error: e }) => {
279
278
  if (e) throw e;
280
279
  }), te());
281
- return C(s, c).pipe(y(), S((e) => ({
280
+ return C(s, c).pipe(b(), S((e) => ({
282
281
  archive: e,
283
282
  release: o
284
- })), g((e) => {
283
+ })), _((e) => {
285
284
  throw o(), e;
286
285
  }));
287
286
  },
@@ -290,7 +289,7 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
290
289
  },
291
290
  _archives: a
292
291
  };
293
- }, X = (e) => e ? /^\d+$/.test(e) ? {
292
+ }, Y = (e) => e ? /^\d+$/.test(e) ? {
294
293
  valid: !0,
295
294
  value: Number.parseInt(e, 10)
296
295
  } : {
@@ -299,20 +298,20 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
299
298
  } : {
300
299
  valid: !0,
301
300
  value: void 0
302
- }, pe = (e) => {
301
+ }, he = (e) => {
303
302
  if (!e.toLowerCase().startsWith("bytes=")) return { kind: "missing" };
304
303
  let t = e.slice(6).trim();
305
304
  if (!t) return { kind: "invalid" };
306
305
  if (t.includes(",")) return { kind: "multi" };
307
306
  let n = /^(\d*)-(\d*)$/.exec(t);
308
307
  if (!n) return { kind: "invalid" };
309
- let [, r = "", i = ""] = n, a = X(r.trim()), o = X(i.trim());
308
+ let [, r = "", i = ""] = n, a = Y(r.trim()), o = Y(i.trim());
310
309
  return !a.valid || !o.valid ? { kind: "invalid" } : {
311
310
  kind: "single",
312
311
  start: a.value,
313
312
  end: o.value
314
313
  };
315
- }, me = (e) => {
314
+ }, ge = (e) => {
316
315
  if (e instanceof Blob) return {
317
316
  size: e.size,
318
317
  slice: (t, n) => {
@@ -334,18 +333,18 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
334
333
  };
335
334
  }
336
335
  };
337
- }, he = ({ body: e, contentType: t, rangeHeader: n }) => {
336
+ }, X = ({ body: e, contentType: t, rangeHeader: n }) => {
338
337
  let r = new Headers();
339
338
  if (t && r.set("Content-Type", t), r.set("Accept-Ranges", "bytes"), !n) return e instanceof Blob && r.set("Content-Length", String(e.size)), new Response(e, {
340
339
  status: 200,
341
340
  headers: r
342
341
  });
343
- let i = pe(n);
342
+ let i = he(n);
344
343
  if (i.kind === "missing" || i.kind === "multi") return e instanceof Blob && r.set("Content-Length", String(e.size)), new Response(e, {
345
344
  status: 200,
346
345
  headers: r
347
346
  });
348
- let a = me(e), o = a.size;
347
+ let a = ge(e), o = a.size;
349
348
  if (i.kind === "invalid") return new Response(null, {
350
349
  status: 416,
351
350
  headers: { "Content-Range": `bytes */${o}` }
@@ -360,18 +359,18 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
360
359
  status: 206,
361
360
  headers: r
362
361
  });
363
- }, Z = "file://", ge = /^https?:\/\//, _e = (e) => {
362
+ }, Z = "file://", _e = /^https?:\/\//, ve = (e) => {
364
363
  try {
365
364
  return decodeURIComponent(e);
366
365
  } catch {
367
366
  return e;
368
367
  }
369
- }, Q = (e) => e.startsWith(Z) ? e.slice(Z.length) : e, ve = (e) => {
368
+ }, Q = (e) => e.startsWith(Z) ? e.slice(Z.length) : e, ye = (e) => {
370
369
  let t = Q(e);
371
- return ge.test(t) ? t : Q(_e(t));
370
+ return _e.test(t) ? t : Q(ve(t));
372
371
  }, $ = class {
373
372
  constructor({ hooks: e, onError: t, onManifestSuccess: n, ...r }) {
374
- this.onError = (e) => (console.error(e), new Response(String(e), { status: 500 })), this.archiveLoader = fe(r), this.hooks = e ?? {}, this.onManifestSuccess = n ?? (({ manifest: e }) => Promise.resolve(e)), this.onError = t ?? this.onError;
373
+ this.onError = (e) => (console.error(e), new Response(String(e), { status: 500 })), this.archiveLoader = me(r), this.hooks = e ?? {}, this.onManifestSuccess = n ?? (({ manifest: e }) => Promise.resolve(e)), this.onError = t ?? this.onError;
375
374
  }
376
375
  prune() {
377
376
  this.archiveLoader.purge();
@@ -383,18 +382,18 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
383
382
  return this.accessArchive(e).pipe(S(({ archive: e, release: t }) => (t(), e)));
384
383
  }
385
384
  withArchiveResponse({ key: e, getResponse: t }) {
386
- let n = this.accessArchive(e).pipe(w(({ archive: e, release: n }) => b(t(e)).pipe(ee(() => {
385
+ let n = this.accessArchive(e).pipe(w(({ archive: e, release: n }) => x(t(e)).pipe(y(() => {
387
386
  n();
388
- }))), g((e) => ne(this.onError(e))));
389
- return x(n);
387
+ }))), _((e) => T(this.onError(e))));
388
+ return ne(n);
390
389
  }
391
390
  fetchManifest({ key: e, baseUrl: t }) {
392
391
  return this.withArchiveResponse({
393
392
  key: e,
394
- getResponse: (e) => b(U(e, {
393
+ getResponse: (e) => x(U(e, {
395
394
  baseUrl: t,
396
395
  hooks: this.hooks.manifest
397
- })).pipe(O((t) => b(this.onManifestSuccess({
396
+ })).pipe(O((t) => x(this.onManifestSuccess({
398
397
  manifest: t,
399
398
  archive: e
400
399
  }))), S((e) => new Response(JSON.stringify(e), { status: 200 })))
@@ -404,8 +403,8 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
404
403
  return this.withArchiveResponse({
405
404
  key: e,
406
405
  getResponse: (e) => {
407
- let r = ve(t);
408
- return b(Y(e, r, { hooks: this.hooks.resource })).pipe(S((e) => he({
406
+ let r = ye(t);
407
+ return x(J(e, r, { hooks: this.hooks.resource })).pipe(S((e) => X({
409
408
  body: e.body ?? "",
410
409
  contentType: e.params.contentType,
411
410
  rangeHeader: n?.headers.get("range")
@@ -413,7 +412,7 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
413
412
  }
414
413
  });
415
414
  }
416
- }, ye = class extends $ {
415
+ }, be = class extends $ {
417
416
  constructor({ getUriInfo: e, ...t }) {
418
417
  super(t), this.getUriInfo = e, this.fetchEventListener = this.fetchEventListener.bind(this);
419
418
  }
@@ -421,12 +420,12 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
421
420
  try {
422
421
  let t = this.getUriInfo(e);
423
422
  if (!t) return;
424
- let n = i(t.baseUrl), r = e.request.url.substring(n.length + 1), [a = ""] = r.split("/"), o = i(r.substring(a.length + 1));
423
+ let n = a(t.baseUrl), r = e.request.url.substring(n.length + 1), [i = ""] = r.split("/"), o = a(r.substring(i.length + 1));
425
424
  r.endsWith("/manifest") ? e.respondWith(this.fetchManifest({
426
- key: a,
427
- baseUrl: `${n}/${a}/`
425
+ key: i,
426
+ baseUrl: `${n}/${i}/`
428
427
  })) : e.respondWith(this.fetchResource({
429
- key: a,
428
+ key: i,
430
429
  resourcePath: o,
431
430
  request: e.request
432
431
  }));
@@ -436,6 +435,6 @@ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie
436
435
  }
437
436
  };
438
437
  //#endregion
439
- export { ye as ServiceWorkerStreamer, $ as Streamer, ie as configure, P as createManifestResourceHref, U as generateManifestFromArchive, Y as generateResourceFromArchive };
438
+ export { be as ServiceWorkerStreamer, $ as Streamer, oe as configure, P as createManifestResourceHref, U as generateManifestFromArchive, J as generateResourceFromArchive };
440
439
 
441
440
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Report","SharedReport","Report","Report$1","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","parseContentType","item","detectMimeTypeFromName","record","archive","r","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","urlJoin","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","createXmlSafeIdFactory","mainTitle","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","resolveArchive","hookFactory","hooks","manifestStr","createTextResourceHook","getArchiveFileRecordByUri","extension","newBody","transform","resource","readRecordAsText","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","XmlDocument","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","readArchiveOpf","foundMediaType","containerHref","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","BehaviorSubject","update","map","locks","distinctUntilChanged","shareReplay","switchMap","isUnlocked","NEVER","timer","createArchiveLoader","loadSubject","Subject","destroySubject","purgeSubject","archives","mergeMap","archiveEntry","key","EMPTY","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","pairwise","filter","prev","curent","startWith","from","getArchive","tap","catchError","readyForPurge$","merge","first","takeUntil","releaseCalled","release","archive$","error$","error","ignoreElements","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","finalize","of","lastValueFrom","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","removeTrailingSlash","streamerPath","e"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"26.0.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n detectMimeTypeFromName,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\nexport const buildAudiobookToc = (\n manifest: Manifest,\n archive: Archive,\n): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.href)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => {\n const record = archive.records.find(\n (r) => !r.dir && decodeURI(item.href).endsWith(r.uri),\n )\n\n return {\n title: normalizeFilenameAsTitle(record?.basename ?? item.href),\n href: item.href,\n path: record?.uri ?? item.href,\n contents: [],\n }\n })\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.uri}`,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${opfBasePath ? `${opfBasePath}/` : ``}${item.href}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest, archive)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n} from \"@prose-reader/archive-reader\"\nimport type { HookResource } from \"./types\"\n\n/**\n * Resolves `contentType` from the package OPF when possible, else from the URI.\n * Uses `readArchiveOpf` each time — see `generateResourceFromArchive` JSDoc for\n * why that can mean repeated OPF work on the resource path.\n */\nconst getMetadata = async (archive: Archive, resourcePath: string) => {\n const parsed = await readArchiveOpf(archive)\n\n if (parsed) {\n const { opf, basePath } = parsed\n\n // we are comparing opf items container-relative path in epub archive\n // against resourcePath (which is an absolute path in archive).\n // They should in theory match.\n const foundMediaType = opf.manifestItems.find((item) => {\n const containerHref = basePath ? `${basePath}/${item.href}` : item.href\n\n return resourcePath.endsWith(containerHref)\n })?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["Report","SharedReport","Report","Report$1","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","parseContentType","item","detectMimeTypeFromName","getUriBasename","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","urlJoin","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","archive","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","createXmlSafeIdFactory","mainTitle","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","resolveArchive","hookFactory","hooks","manifestStr","createTextResourceHook","getArchiveFileRecordByUri","extension","newBody","transform","resource","readRecordAsText","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","XmlDocument","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","readArchiveOpf","foundMediaType","containerHref","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","BehaviorSubject","update","map","locks","distinctUntilChanged","shareReplay","switchMap","isUnlocked","NEVER","timer","createArchiveLoader","loadSubject","Subject","destroySubject","purgeSubject","archives","mergeMap","archiveEntry","key","EMPTY","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","pairwise","filter","prev","curent","startWith","from","getArchive","tap","catchError","readyForPurge$","merge","first","takeUntil","releaseCalled","release","archive$","error$","error","ignoreElements","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","finalize","of","lastValueFrom","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","removeTrailingSlash","streamerPath","e"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"28.0.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import {\n detectMimeTypeFromName,\n getUriBasename,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\n/**\n * One entry per track for a book made only of audio, each titled after the\n * archive record it plays.\n */\nexport const buildAudiobookToc = (manifest: Manifest): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.containerUri)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => ({\n title: normalizeFilenameAsTitle(getUriBasename(item.containerUri)),\n href: item.href,\n path: item.containerUri,\n contents: [],\n }))\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere, and\n // the reference the book makes to them, which its navigation and links\n // match as written\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.containerHref ?? item.uri}`,\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${opfBasePath ? `${opfBasePath}/` : ``}${item.href}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n} from \"@prose-reader/archive-reader\"\nimport type { HookResource } from \"./types\"\n\n/**\n * Resolves `contentType` from the package OPF when possible, else from the URI.\n * Uses `readArchiveOpf` each time — see `generateResourceFromArchive` JSDoc for\n * why that can mean repeated OPF work on the resource path.\n */\nconst getMetadata = async (archive: Archive, resourcePath: string) => {\n const parsed = await readArchiveOpf(archive)\n\n if (parsed) {\n const { opf, basePath } = parsed\n\n // we are comparing opf items container-relative path in epub archive\n // against resourcePath (which is an absolute path in archive).\n // They should in theory match.\n const foundMediaType = opf.manifestItems.find((item) => {\n const containerHref = basePath ? `${basePath}/${item.href}` : item.href\n\n return resourcePath.endsWith(containerHref)\n })?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prose-reader/streamer",
3
- "version": "26.0.0",
3
+ "version": "28.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index/index.cjs",
6
6
  "module": "./dist/index/index.js",
@@ -28,14 +28,14 @@
28
28
  "test:watch": "vitest watch"
29
29
  },
30
30
  "dependencies": {
31
- "@prose-reader/archive-reader": "^26.0.0",
32
- "@prose-reader/shared": "^26.0.0",
31
+ "@prose-reader/archive-reader": "^28.0.0",
32
+ "@prose-reader/shared": "^28.0.0",
33
33
  "xmldoc": "^3.0.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "rxjs": "^7.5.5"
37
37
  },
38
- "gitHead": "38e60dff8a984e54ee4222268f8e5dfb94eec6ac",
38
+ "gitHead": "889074a8f68ffa7c3cde305514e9564af4b65a22",
39
39
  "engines": {
40
40
  "node": ">=22.12"
41
41
  }