@prose-reader/streamer 1.333.0 → 1.335.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,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=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return encodeURI(`${n}${t}`)},l=t=>n=>({title:n.title,path:n.path,href:n.containerHref?(0,e.urlJoin)(t,n.containerHref):``,contents:n.contents.map(l(t))}),u=e=>{let t=e?.trim();if(t===`cover`||t===`title-page`||t===`copyright-page`||t===`text`)return t},d=e=>/^https?:\/\//.test(e),f=e=>e.records.find(({dir:e})=>e)?.basename.replace(/\/$/,``),p=(t,n,r,i)=>{let{metadata:a,readingOrder:o}=r,s=i.basePath,c=[];for(let e of i.opf.guide){let t=u(e.type);t!==void 0&&c.push({href:e.href,title:e.title,type:t})}let p=(0,e.createXmlSafeIdFactory)();return{filename:t.filename??``,title:a.title||f(t)||``,renditionLayout:a.renditionLayout,renditionFlow:a.renditionFlow??`auto`,renditionSpread:a.renditionSpread,readingDirection:a.readingDirection,spineItems:o.map((e,t)=>({id:e.id??p(e.uri),index:t,href:d(e.uri)?e.uri:`${n||`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:i.opf.manifestItems.map(e=>({id:e.id,href:d(e.href)?e.href:`${n||`file://`}${s?`${s}/`:``}${e.href}`,mediaType:e.mediaType})),guide:c.length>0?c:void 0,nav:{toc:(r.toc??[]).map(l(n))}}},m=(t,n,r)=>{let{metadata:i,readingOrder:a}=r,o=t.records.filter(e=>!e.dir),u=(0,e.createXmlSafeIdFactory)(),d=new Map(o.map(e=>[e.uri,u(e.uri)])),p={filename:t.filename??``,title:i.title||f(t)||t.filename||``,renditionLayout:i.renditionLayout,renditionSpread:`auto`,readingDirection:i.readingDirection,spineItems:a.map((e,t)=>({id:d.get(e.uri)??u(e.uri),index:t,href:c({baseUrl:n,resourcePath:e.uri}),renditionLayout:e.renditionLayout,progressionWeight:e.progressionWeight,pageSpreadLeft:void 0,pageSpreadRight:void 0,mediaType:e.mediaType})),items:o.map(e=>({id:d.get(e.uri)??u(e.uri),href:encodeURI(`${n}${e.uri}`)}))},m=s(p,t)??r.toc?.map(l(n));return m===void 0?p:{...p,nav:{toc:m}}},h=({archive:e,baseUrl:t,resolved:n})=>{let r=n.sources.opf;return r?p(e,t,n,r):m(e,t,n)},g=e=>e?e.endsWith(`/`)?e:`${e}/`:``,_=async(e,{baseUrl:n=``,hooks:r=[]}={})=>{i.log(`Generating manifest from archive`,e);let a=g(n);try{let n=h({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}},v=(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},y=e=>{let t=e.descendantWithPath(`head`)?.childrenNamed(`meta`).find(e=>e.attr.name===`calibre:cover`);return!!(t&&t.attr.name===`calibre:cover`)},b=e=>e.descendantWithPath(`body`)?.descendantWithPath(`div`)?.childrenNamed(`svg`)?.find(e=>e.attr.width===`100%`&&e.attr.preserveAspectRatio===`none`),x=v(`.xhtml`,e=>{let t=new n.XmlDocument(e);if(!y(t))return;let r=b(t);return r&&delete r.attr.preserveAspectRatio,t.toString()}),S=v(`.css`,e=>e.replaceAll(`-webkit-writing-mode`,`writing-mode`)),C=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:w(n)}},w=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`},T=({archive:e,resourcePath:n})=>async r=>{let i=(0,t.getArchiveFileRecordByUri)(e,n);if(!i)return r;let a=await C(e,n);return{...r,params:{...r.params,...i?.encodingFormat&&{contentType:i.encodingFormat},...a.mediaType&&{contentType:a.mediaType}}}},E=`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(`.`),D=v(`.xhtml`,e=>{if(!RegExp(`<(${E.join(`|`)})[\\s/>]`,`i`).test(e))return;let t=RegExp(`<(${E.join(`|`)})(\\s[^>]*)?\\s*/>`,`gi`);return e.replace(t,(e,t,n=``)=>`<${t} ${n.trim()}></${t}>`)}),O=async(e,n,{hooks:r=[]}={})=>{let a={params:{}},o=[...r.map(t=>t({archive:e,resourcePath:n})),T({archive:e,resourcePath:n}),D({archive:e,resourcePath:n}),S({archive:e,resourcePath:n}),x({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}},k=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))}},A=({getArchive:e,cleanArchiveAfter:t=300*1e3})=>{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)(()=>(0,r.merge)(d.pipe((0,r.switchMap)(()=>o),(0,r.switchMap)(()=>u),(0,r.filter)(e=>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 k(t);s[e]=a,a.update({locks:a.state.locks+1});let o=()=>{i||(i=!0,a.update({locks:a.state.locks-1}))};return n.next(e),(0,r.merge)(a.state$.pipe((0,r.map)(({archive:e})=>e),(0,r.filter)(e=>!!e)),a.state$.pipe((0,r.tap)(({error:e})=>{if(e)throw e}),(0,r.ignoreElements)())).pipe((0,r.first)(),(0,r.map)(e=>({archive:e,release:o})),(0,r.catchError)(e=>{throw o(),e}))},purge:()=>{o.next()},_archives:s}},j=e=>e?/^\d+$/.test(e)?{valid:!0,value:Number.parseInt(e,10)}:{valid:!1,value:void 0}:{valid:!0,value:void 0},M=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=j(r.trim()),o=j(i.trim());return!a.valid||!o.valid?{kind:`invalid`}:{kind:`single`,start:a.value,end:o.value}},N=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}}}},P=({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=M(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=N(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})},F=`file://`,I=/^https?:\/\//,L=e=>{try{return decodeURIComponent(e)}catch{return e}},R=e=>e.startsWith(F)?e.slice(F.length):e,z=e=>{let t=R(e);return I.test(t)?t:R(L(t))},B=class{constructor({hooks:e,onError:t,onManifestSuccess:n,...r}){this.onError=e=>(console.error(e),new Response(String(e),{status:500})),this.archiveLoader=A(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}){return(0,r.lastValueFrom)(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)))))}fetchManifest({key:e,baseUrl:t}){return this.withArchiveResponse({key:e,getResponse:e=>(0,r.from)(_(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=>(0,r.from)(O(e,z(t),{hooks:this.hooks.resource})).pipe((0,r.map)(e=>P({body:e.body??``,contentType:e.params.contentType,rangeHeader:n?.headers.get(`range`)})))})}},V=class extends B{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=V,exports.Streamer=B,exports.configure=a,exports.createManifestResourceHref=c,exports.generateManifestFromArchive=_,exports.generateResourceFromArchive=O;
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=(t,n,r,i)=>{let{metadata:a,readingOrder:o}=r,s=i.basePath,c=[];for(let e of i.opf.guide){let t=d(e.type);t!==void 0&&c.push({href:e.href,title:e.title,type:t})}let l=(0,e.createXmlSafeIdFactory)();return{filename:t.filename??``,title:a.title||p(t)||``,renditionLayout:a.renditionLayout,renditionFlow:a.renditionFlow??`auto`,renditionSpread:a.renditionSpread,readingDirection:a.readingDirection,spineItems:o.map((e,t)=>({id:e.id??l(e.uri),index:t,href:f(e.uri)?e.uri:`${n||`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:i.opf.manifestItems.map(e=>({id:e.id,href:f(e.href)?e.href:`${n||`file://`}${s?`${s}/`:``}${e.href}`,mediaType:e.mediaType})),guide:c.length>0?c:void 0,nav:{toc:(r.toc??[]).map(u(n))}}},h=(t,n,r)=>{let{metadata:i,readingOrder:a}=r,o=t.records.filter(e=>!e.dir),c=(0,e.createXmlSafeIdFactory)(),d=new Map(o.map(e=>[e.uri,c(e.uri)])),f={filename:t.filename??``,title:i.title||p(t)||t.filename||``,renditionLayout:i.renditionLayout,renditionSpread:`auto`,readingDirection:i.readingDirection,spineItems:a.map((e,t)=>({id:d.get(e.uri)??c(e.uri),index:t,href:l({baseUrl:n,resourcePath:e.uri}),renditionLayout:e.renditionLayout,progressionWeight:e.progressionWeight,pageSpreadLeft:void 0,pageSpreadRight:void 0,mediaType:e.mediaType})),items:o.map(e=>({id:d.get(e.uri)??c(e.uri),href:encodeURI(`${n}${e.uri}`)}))},m=s(f,t)??r.toc?.map(u(n));return m===void 0?f:{...f,nav:{toc:m}}},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=300*1e3})=>{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)(()=>(0,r.merge)(d.pipe((0,r.switchMap)(()=>o),(0,r.switchMap)(()=>u),(0,r.filter)(e=>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}))};return n.next(e),(0,r.merge)(a.state$.pipe((0,r.map)(({archive:e})=>e),(0,r.filter)(e=>!!e)),a.state$.pipe((0,r.tap)(({error:e})=>{if(e)throw e}),(0,r.ignoreElements)())).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}){return(0,r.lastValueFrom)(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)))))}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=>(0,r.from)(k(e,B(t),{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":[],"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\": \"1.332.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\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\": \"^1.332.0\",\n \"@prose-reader/shared\": \"^1.332.0\",\n \"xmldoc\": \"^2.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"*\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\"\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","export 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}${resourcePath}`)\n}\n","import type {\n Archive,\n ArchiveTocItem,\n 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 \"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 title: metadata.title || 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 title:\n metadata.title || firstDirectoryTitle(archive) || archive.filename || ``,\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":[],"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\": \"1.334.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\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\": \"^1.334.0\",\n \"@prose-reader/shared\": \"^1.334.0\",\n \"xmldoc\": \"^2.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"*\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\"\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 type {\n Archive,\n ArchiveTocItem,\n 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 \"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 title: metadata.title || 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 title:\n metadata.title || firstDirectoryTitle(archive) || archive.filename || ``,\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":"yLCOA,IAAa,EAAS,EAAA,OAAa,UAAU,yBAAM,IAAA,GAAW,CAC5D,MAAO,SACT,CAAC,ECPY,GAAa,CACxB,gBAGE,CAAC,IAAM,CACT,EAAO,OAAO,CAAC,CAAC,CAAY,CAC9B,ECCa,EAA4B,GACvC,EACG,QAAQ,WAAY,EAAE,CAAC,CACvB,QAAQ,QAAS,GAAG,CAAC,CACrB,QAAQ,OAAQ,GAAG,CAAC,CACpB,KAAK,EAEG,GACX,EACA,IACoB,CAChB,KAAS,WAAW,SAAW,GAElB,EAAS,WAAW,MAAO,KAK1C,EAAA,EAAA,iBAAA,CAHmB,EAAK,WAAa,EAAE,IAAA,EAAA,EAAA,uBAAA,CACd,EAAK,IAAI,EAAA,EAEjB,WAAW,QAAQ,CAGjC,EAEL,OAAO,EAAS,WAAW,IAAK,GAAS,CACvC,IAAM,EAAS,EAAQ,QAAQ,KAC5B,GAAM,CAAC,EAAE,KAAO,UAAU,EAAK,IAAI,CAAC,CAAC,SAAS,EAAE,GAAG,CACtD,EAEA,MAAO,CACL,MAAO,EAAyB,GAAQ,UAAY,EAAK,IAAI,EAC7D,KAAM,EAAK,KACX,KAAM,GAAQ,KAAO,EAAK,KAC1B,SAAU,CAAC,CACb,CACF,CAAC,CACH,ECxCM,EAAsB,GAC1B,UAAU,CAAY,CAAC,CAAC,QAAQ,KAAM,KAAK,CAAC,CAAC,QAAQ,MAAO,KAAK,EAEtD,GAA8B,CACzC,UAAU,GACV,kBAII,CACJ,GAAI,CAAC,GAAW,eAAe,KAAK,CAAY,EAC9C,OAAO,UAAU,CAAY,EAG/B,IAAM,EAAc,EAChB,GAAG,IAAU,EAAQ,SAAS,GAAG,EAAI,GAAK,MAC1C,UAEJ,MAAO,GAAG,UAAU,CAAW,IAAI,EAAmB,CAAY,GACpE,ECNM,EACH,GACA,IAAmC,CAClC,MAAO,EAAK,MACZ,KAAM,EAAK,KAGX,KAAM,EAAK,eAAA,EAAA,EAAA,QAAA,CAAwB,EAAS,EAAK,aAAa,EAAI,GAClE,SAAU,EAAK,SAAS,IAAI,EAAkB,CAAO,CAAC,CACxD,GAEI,EACJ,GAC2C,CAC3C,IAAM,EAAI,GAAK,KAAK,EACpB,GACE,IAAM,SACN,IAAM,cACN,IAAM,kBACN,IAAM,OAEN,OAAO,CAGX,EAEM,EAAa,GAAkB,eAAe,KAAK,CAAK,EAExD,EAAuB,GAC3B,EAAQ,QAAQ,MAAM,CAAE,SAAU,CAAG,CAAC,EAAE,SAAS,QAAQ,MAAO,EAAE,EAY9D,GACJ,EACA,EACA,EACA,IACa,CACb,GAAM,CAAE,WAAU,gBAAiB,EAC7B,EAAc,EAAI,SAElB,EAA8B,CAAC,EACrC,IAAK,IAAM,KAAa,EAAI,IAAI,MAAO,CACrC,IAAM,EAAO,EAAkB,EAAU,IAAI,EACzC,IAAS,IAAA,IAGb,EAAM,KAAK,CAAE,KAAM,EAAU,KAAM,MAAO,EAAU,MAAO,MAAK,CAAC,CACnE,CAEA,IAAM,GAAA,EAAA,EAAA,uBAAA,CAAsC,EAE5C,MAAO,CACL,SAAU,EAAQ,UAAY,GAC9B,MAAO,EAAS,OAAS,EAAoB,CAAO,GAAK,GACzD,gBAAiB,EAAS,gBAC1B,cAAe,EAAS,eAAiB,OACzC,gBAAiB,EAAS,gBAC1B,iBAAkB,EAAS,iBAC3B,WAAY,EAAa,KAAK,EAAM,KAAW,CAC7C,GAAI,EAAK,IAAM,EAAa,EAAK,GAAG,EACpC,QAGA,KAAM,EAAU,EAAK,GAAG,EACpB,EAAK,IACL,GAAG,GAAW,YAAY,EAAK,MACnC,gBAAiB,EAAK,gBACtB,GAAI,EAAK,gBAAkB,IAAA,GAEvB,CAAC,EADD,CAAE,cAAe,EAAK,aAAc,EAExC,kBAAmB,EAAK,kBACxB,eAAgB,EAAK,eACrB,gBAAiB,EAAK,gBACtB,UAAW,EAAK,SAClB,EAAE,EACF,MAAO,EAAI,IAAI,cAAc,IAAK,IAAU,CAC1C,GAAI,EAAK,GACT,KAAM,EAAU,EAAK,IAAI,EACrB,EAAK,KACL,GAAG,GAAW,YAAY,EAAc,GAAG,EAAY,GAAK,KAAK,EAAK,OAC1E,UAAW,EAAK,SAClB,EAAE,EACF,MAAO,EAAM,OAAS,EAAI,EAAQ,IAAA,GAClC,IAAK,CAEH,KAAM,EAAS,KAAO,CAAC,EAAA,CAAG,IAAI,EAAkB,CAAO,CAAC,CAC1D,CACF,CACF,EAEM,GACJ,EACA,EACA,IACa,CACb,GAAM,CAAE,WAAU,gBAAiB,EAE7B,EAAQ,EAAQ,QAAQ,OAAQ,GAAS,CAAC,EAAK,GAAG,EAClD,GAAA,EAAA,EAAA,uBAAA,CAAsC,EACtC,EAAU,IAAI,IAClB,EAAM,IAAK,GAAS,CAAC,EAAK,IAAK,EAAa,EAAK,GAAG,CAAC,CAAC,CACxD,EAEM,EAAqB,CACzB,SAAU,EAAQ,UAAY,GAC9B,MACE,EAAS,OAAS,EAAoB,CAAO,GAAK,EAAQ,UAAY,GACxE,gBAAiB,EAAS,gBAC1B,gBAAiB,OACjB,iBAAkB,EAAS,iBAC3B,WAAY,EAAa,KAAK,EAAM,KAAW,CAC7C,GAAI,EAAQ,IAAI,EAAK,GAAG,GAAK,EAAa,EAAK,GAAG,EAClD,QACA,KAAM,EAA2B,CAAE,UAAS,aAAc,EAAK,GAAI,CAAC,EACpE,gBAAiB,EAAK,gBACtB,kBAAmB,EAAK,kBACxB,eAAgB,IAAA,GAChB,gBAAiB,IAAA,GACjB,UAAW,EAAK,SAClB,EAAE,EAGF,MAAO,EAAM,IAAK,IAAU,CAC1B,GAAI,EAAQ,IAAI,EAAK,GAAG,GAAK,EAAa,EAAK,GAAG,EAClD,KAAM,UAAU,GAAG,IAAU,EAAK,KAAK,CACzC,EAAE,CACJ,EAGM,EADe,EAAkB,EAAU,CACrC,GAAgB,EAAS,KAAK,IAAI,EAAkB,CAAO,CAAC,EAExE,OAAO,IAAQ,IAAA,GAA4C,EAAhC,CAAE,GAAG,EAAU,IAAK,CAAE,KAAI,CAAE,CACzD,EASa,GAA+B,CAC1C,UACA,UACA,cAKc,CACd,IAAM,EAAM,EAAS,QAAQ,IAI7B,OAFI,EAAY,EAAa,EAAS,EAAS,EAAU,CAAG,EAErD,EAAgB,EAAS,EAAS,CAAQ,CACnD,EChLM,EAAoB,GACnB,EAEE,EAAQ,SAAS,GAAG,EAAI,EAAU,GAAG,EAAQ,GAF/B,GAaV,EAA8B,MACzC,EACA,CACE,UAAU,GACV,QAAQ,CAAC,GACsD,CAAC,IAC/D,CACH,EAAO,IAAI,mCAAoC,CAAO,EAEtD,IAAM,EAAoB,EAAiB,CAAO,EAElD,GAAI,CAMF,IAAI,EAAW,EAA4B,CACzC,UACA,QAAS,EACT,SAAA,MAAA,EAAA,EAAA,eAAA,CARoC,EAAS,CAC7C,QAAS,CAAC,WAAY,eAAgB,MAAO,SAAS,EACtD,WAAY,EACd,CAAC,CAMD,CAAC,EAED,IAAK,IAAM,KAAe,EACxB,EAAW,MAAM,EAAY,CAC3B,UACA,QAAS,CACX,CAAC,CAAC,CAAC,CAAQ,EAUb,GAPA,EAAW,CACT,GAAG,EACH,iBAAkB,EAAS,kBAAoB,KACjD,EAEA,EAAO,IAAI,qBAAsB,CAAQ,EAEzC,QAAA,IAAA,WAA6B,eACvB,EAAO,UAAU,EAAG,CACtB,IAAM,EAAc,KAAK,UAAU,EAAU,KAAM,CAAC,EACpD,EAAO,eAAe,GAAG,EAAO,aAAa,oBAAoB,CAAC,EAClE,EAAO,IAAI,KAAK,GAAa,EAC7B,EAAO,SAAS,CAClB,CAGF,OAAO,CACT,OAAS,EAAG,CAGV,MAFA,EAAO,MAAM,CAAC,EAER,CACR,CACF,ECnDa,GAET,EACA,KAID,CACC,UACA,kBAEF,KAAO,IAAa,CAClB,IAAM,GAAA,EAAA,EAAA,0BAAA,CAAiC,EAAS,CAAY,EAE5D,GAAI,GAAM,SAAS,SAAS,CAAS,EAAG,CAMtC,IAAM,EAAU,MAAM,EAJpB,OAAO,EAAS,MAAS,SACrB,EAAS,KACT,MAAA,EAAA,EAAA,iBAAA,CAAuB,CAAI,CAEU,EAE3C,GAAI,IAAY,IAAA,GACd,MAAO,CACL,GAAG,EACH,KAAM,CACR,CAEJ,CAEA,OAAO,CACT,ECjDI,EAAuB,GAAqB,CAChD,IAAM,EAAU,EACb,mBAAmB,MAAM,CAAC,EACzB,cAAc,MAAM,CAAC,CACtB,KAAM,GAAS,EAAK,KAAK,OAAS,eAAe,EAEpD,MAAO,CAAC,EAAE,GAAW,EAAQ,KAAK,OAAS,gBAC7C,EAEM,EAAoB,GACjB,EACJ,mBAAmB,MAAM,CAAC,EACzB,mBAAmB,KAAK,CAAC,EACzB,cAAc,KAAK,CAAC,EACpB,KACC,GACC,EAAK,KAAK,QAAU,QAAU,EAAK,KAAK,sBAAwB,MACpE,EAGS,EAAiB,EAC5B,SACC,GAAgB,CACf,IAAM,EAAY,IAAI,EAAA,YAAY,CAAW,EAE7C,GAAI,CAAC,EAAoB,CAAS,EAChC,OAGF,IAAM,EAAW,EAAiB,CAAS,EAM3C,OAJI,GACF,OAAO,EAAS,KAAK,oBAGhB,EAAU,SAAS,CAC5B,CACF,ECtCa,EAAa,EAAuB,OAAS,GAKxD,EAAK,WAAW,uBAAwB,cAAc,CACxD,ECIM,EAAc,MAAO,EAAkB,IAAyB,CACpE,IAAM,EAAS,MAAA,EAAA,EAAA,eAAA,CAAqB,CAAO,EAE3C,GAAI,EAAQ,CACV,GAAM,CAAE,MAAK,YAAa,EAKpB,EAAiB,EAAI,cAAc,KAAM,GAAS,CACtD,IAAM,EAAgB,EAAW,GAAG,EAAS,GAAG,EAAK,OAAS,EAAK,KAEnE,OAAO,EAAa,SAAS,CAAa,CAC5C,CAAC,CAAC,EAAE,UAEJ,GAAI,EACF,MAAO,CACL,UAAW,CACb,CAEJ,CAEA,MAAO,CACL,UAAW,EAA4B,CAAY,CACrD,CACF,EAEM,EAA+B,GAAgB,CACnD,GAAI,EAAI,SAAS,MAAM,EACrB,MAAO,0BAET,GAAI,EAAI,SAAS,MAAM,EACrB,MAAO,YAET,GAAI,EAAI,SAAS,QAAQ,EACvB,MAAO,wBAET,GAAI,EAAI,SAAS,MAAM,EACrB,MAAO,YAET,GAAI,EAAI,SAAS,MAAM,EACrB,MAAO,eAEX,EAEa,GACV,CAAE,UAAS,kBACZ,KAAO,IAAkD,CACvD,IAAM,GAAA,EAAA,EAAA,0BAAA,CAAiC,EAAS,CAAY,EAE5D,GAAI,CAAC,EAAM,OAAO,EAElB,IAAM,EAAW,MAAM,EAAY,EAAS,CAAY,EAExD,MAAO,CACL,GAAG,EACH,OAAQ,CACN,GAAG,EAAS,OACZ,GAAI,GAAM,gBAAkB,CAC1B,YAAa,EAAK,cACpB,EACA,GAAI,EAAS,WAAa,CACxB,YAAa,EAAS,SACxB,CACF,CACF,CACF,EC5EI,EAAyB,wRAmD/B,EASa,EAAyB,EACpC,SACC,GAAgB,CAKf,GAAI,CAJwB,OAC1B,KAAK,EAAuB,KAAK,GAAG,EAAE,UACtC,GAEG,CAAA,CAAgB,KAAK,CAAW,EACnC,OAGF,IAAM,EAAiB,OACrB,KAAK,EAAuB,KAAK,GAAG,EAAE,oBACtC,IACF,EAEA,OAAO,EAAY,QAAQ,GAAa,EAAG,EAAS,EAAa,KAExD,IAAI,EAAQ,GAAG,EAAW,KAAK,EAAE,KAAK,EAAQ,EACtD,CACH,CACF,EC7Da,EAA8B,MACzC,EACA,EACA,CAAE,QAAQ,CAAC,GAAiD,CAAC,IAC1D,CACH,IAAM,EAAgC,CACpC,OAAQ,CAAC,CACX,EAOM,EAAgB,CACpB,GAPoB,EAAM,IAAK,GAAS,EAAK,CAAE,UAAS,cAAa,CAAC,CAOnE,EACH,EAAY,CAAE,UAAS,cAAa,CAAC,EACrC,EAAuB,CAAE,UAAS,cAAa,CAAC,EAChD,EAAW,CAAE,UAAS,cAAa,CAAC,EACpC,EAAe,CAAE,UAAS,cAAa,CAAC,CAC1C,EAEA,GAAI,CACF,IAAM,EAAW,MAAM,EAAc,OAAO,MAAO,EAAU,IACpD,MAAM,EAAI,MAAM,CAAQ,EAC9B,QAAQ,QAAQ,CAAe,CAAC,EAInC,GAFA,EAAO,IAAI,qBAAsB,EAAc,CAAQ,EAEnD,EAAS,OAAS,IAAA,GACpB,OAAO,EAGT,IAAM,GAAA,EAAA,EAAA,0BAAA,CAAiC,EAAS,CAAY,EAE5D,GAAI,CAAC,EACH,MAAU,MAAM,kCAAkC,GAAc,EAGlE,MAAO,CACL,GAAG,EACH,KAAM,MAAM,EAAK,KAAK,CACxB,CACF,OAAS,EAAG,CAGV,MAFA,EAAO,MAAM,CAAC,EAER,CACR,CACF,EC5CM,EAAN,KAAmB,CAWjB,YAAY,EAAmC,CAA3B,KAAA,kBAAA,cAVX,IAAI,EAAA,gBAKV,CACD,OAAQ,OACR,MAAO,CACT,CAAC,CAE+C,CAEhD,OAAO,EAAsD,CAC3D,KAAK,OAAO,KAAK,CAAE,GAAG,KAAK,OAAO,SAAS,EAAG,GAAG,CAAO,CAAC,CAC3D,CAEA,IAAI,QAAS,CACX,OAAO,KAAK,OAAO,MAAA,EAAA,EAAA,IAAA,EAAU,CAAE,WAAY,CAAK,CAAC,CACnD,CAEA,IAAI,OAAQ,CACV,OAAO,KAAK,OAAO,SAAS,CAC9B,CAEA,IAAI,aAAc,CAChB,OAAO,KAAK,OAAO,MAAA,EAAA,EAAA,IAAA,CACZ,GAAU,GAAS,CAAC,GAAA,EAAA,EAAA,qBAAA,CACJ,GAAA,EAAA,EAAA,YAAA,CACT,CACd,CACF,CAEA,IAAI,UAAW,CACb,OAAO,KAAK,YAAY,MAAA,EAAA,EAAA,UAAA,CACX,GACR,EAEG,KAAK,oBAAsB,IACzB,EAAA,OAAA,EAAA,EAAA,MAAA,CACM,KAAK,iBAAiB,EAH9B,EAAA,KAIN,CACF,CACF,CACF,EAEa,GAAuB,CAClC,aACA,oBAAoB,IAAS,OAIzB,CACJ,IAAM,EAAc,IAAI,EAAA,QAClB,EAAiB,IAAI,EAAA,QACrB,EAAe,IAAI,EAAA,QACnB,EAAyC,CAAC,EAqIhD,OAnIqB,EAAY,MAAA,EAAA,EAAA,SAAA,CACrB,GAAQ,CAChB,IAAM,EAAe,EAAS,GAE9B,GAAI,GAAc,MAAM,SAAW,OAAQ,OAAO,EAAA,MAElD,IAAI,EAAW,GAET,EAAkB,GAAgB,CACtC,EAAO,MAAM,iCAAiC,GAAK,EAEnD,IAAM,EAAQ,EAAS,GAEvB,OAAO,EAAS,GAEhB,AAEE,KADA,GAAO,MAAM,SAAS,MAAM,EACjB,GAEf,EAEA,EAAa,OAAO,CAClB,OAAQ,SACV,CAAC,EAED,IAAM,EAAS,EAAa,OACtB,EAAc,EAAa,YAE3B,EAAa,EAAO,MAAA,EAAA,EAAA,SAAA,CACf,GAAA,EAAA,EAAA,OAAA,EACD,CAAC,EAAM,KAAY,EAAS,CAAI,GAAA,EAAA,EAAA,UAAA,CAC9B,EAAI,CAChB,EAIA,OAAA,EAAA,EAAA,KAAA,CAFsB,EAAW,CAAG,CAE7B,CAAA,CAAS,MAAA,EAAA,EAAA,IAAA,CACT,GAAY,CACf,EAAa,OAAO,CAClB,UACA,OAAQ,SACV,CAAC,CACH,CAAC,GAAA,EAAA,EAAA,WAAA,CACW,IACV,EAAe,CAAG,EAElB,EAAa,OAAO,CAClB,OAAQ,QACR,OACF,CAAC,EAEM,EAAA,MACR,GAAA,EAAA,EAAA,UAAA,MAUC,EAAA,EAAA,MAAA,CARuB,EAAW,MAAA,EAAA,EAAA,UAAA,KAChB,CAAY,GAAA,EAAA,EAAA,UAAA,KACZ,CAAW,GAAA,EAAA,EAAA,OAAA,CACnB,GAAe,CAAU,CAGV,EAAgB,EAAa,QAE/C,CAAA,CAAW,MAAA,EAAA,EAAA,MAAA,CACV,GAAA,EAAA,EAAA,IAAA,KACI,CACR,EAAe,CAAG,CACpB,CAAC,CACH,CACD,CACH,CACF,CAAC,GAAA,EAAA,EAAA,UAAA,CACS,CAAc,CA0D1B,CAAA,CAAa,UAAU,EAEhB,CACL,OA1Dc,GAAgB,CAC9B,IAAI,EAAgB,GAEd,EAAe,EAAS,IAAQ,IAAI,EAAa,CAAiB,EAExE,EAAS,GAAO,EAEhB,EAAa,OAAO,CAClB,MAAO,EAAa,MAAM,MAAQ,CACpC,CAAC,EAED,IAAM,MAAgB,CAChB,IAEJ,EAAgB,GAEhB,EAAa,OAAO,CAClB,MAAO,EAAa,MAAM,MAAQ,CACpC,CAAC,EACH,EAkBA,OAhBA,EAAY,KAAK,CAAG,GAgBpB,EAAA,EAAA,MAAA,CAdiB,EAAa,OAAO,MAAA,EAAA,EAAA,IAAA,EAC9B,CAAE,aAAc,CAAO,GAAA,EAAA,EAAA,OAAA,CACpB,GAAY,CAAC,CAAC,CAAO,CAYlB,EATE,EAAa,OAAO,MAAA,EAAA,EAAA,IAAA,EAC5B,CAAE,WAAY,CACjB,GAAI,EACF,MAAM,CAEV,CAAC,GAAA,EAAA,EAAA,eAAA,CACc,CAGM,CAAM,CAAC,CAAC,MAAA,EAAA,EAAA,MAAA,CACvB,GAAA,EAAA,EAAA,IAAA,CACD,IAAa,CAAE,UAAS,SAAQ,EAAE,GAAA,EAAA,EAAA,WAAA,CAC3B,GAAU,CAGpB,MAFA,EAAQ,EAEF,CACR,CAAC,CACH,CACF,EAaE,UARkB,CAClB,EAAa,KAAK,CACpB,EAOE,UAAW,CACb,CACF,EC5NM,EAAsB,GACrB,EAOA,QAAQ,KAAK,CAAK,EAOhB,CACL,MAAO,GACP,MAAO,OAAO,SAAS,EAAO,EAAE,CAClC,EATS,CACL,MAAO,GACP,MAAO,IAAA,EACT,EAVO,CACL,MAAO,GACP,MAAO,IAAA,EACT,EAgBE,EAAwB,GAAwB,CACpD,GAAI,CAAC,EAAY,YAAY,CAAC,CAAC,WAAW,QAAQ,EAChD,MAAO,CACL,KAAM,SACR,EAGF,IAAM,EAAa,EAAY,MAAM,CAAe,CAAC,CAAC,KAAK,EAE3D,GAAI,CAAC,EACH,MAAO,CACL,KAAM,SACR,EAGF,GAAI,EAAW,SAAS,GAAG,EACzB,MAAO,CACL,KAAM,OACR,EAGF,IAAM,EAAU,gBAAgB,KAAK,CAAU,EAE/C,GAAI,CAAC,EACH,MAAO,CACL,KAAM,SACR,EAGF,GAAM,EAAG,EAAW,GAAI,EAAS,IAAM,EACjC,EAAc,EAAmB,EAAS,KAAK,CAAC,EAChD,EAAY,EAAmB,EAAO,KAAK,CAAC,EAQlD,MANI,CAAC,EAAY,OAAS,CAAC,EAAU,MAC5B,CACL,KAAM,SACR,EAGK,CACL,KAAM,SACN,MAAO,EAAY,MACnB,IAAK,EAAU,KACjB,CACF,EAQM,EAAyB,GAAwB,CACrD,GAAI,aAAgB,KAClB,MAAO,CACL,KAAM,EAAK,KACX,OAAQ,EAAe,IAAyB,CAC9C,IAAM,EAAO,EAAK,MAAM,EAAO,CAAY,EAC3C,MAAO,CAAE,QAAS,EAAM,OAAQ,EAAK,IAAK,CAC5C,CACF,EAGF,IAAM,EAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,CAAI,EAE3C,MAAO,CACL,KAAM,EAAM,WACZ,OAAQ,EAAe,IAAyB,CAC9C,IAAM,EAAO,EAAM,MAAM,EAAO,CAAY,EAC5C,MAAO,CAAE,QAAS,EAAM,OAAQ,EAAK,UAAW,CAClD,CACF,CACF,EAEa,GAAuB,CAClC,OACA,cACA,iBAKI,CACJ,IAAM,EAAU,IAAI,QAQpB,GANI,GACF,EAAQ,IAAI,eAAgB,CAAW,EAGzC,EAAQ,IAAI,gBAAiB,OAAO,EAEhC,CAAC,EAKH,OAJI,aAAgB,MAClB,EAAQ,IAAI,iBAAkB,OAAO,EAAK,IAAI,CAAC,EAG1C,IAAI,SAAS,EAAM,CACxB,OAAQ,IACR,SACF,CAAC,EAGH,IAAM,EAAc,EAAqB,CAAW,EAEpD,GAAI,EAAY,OAAS,WAAa,EAAY,OAAS,QAKzD,OAJI,aAAgB,MAClB,EAAQ,IAAI,iBAAkB,OAAO,EAAK,IAAI,CAAC,EAG1C,IAAI,SAAS,EAAM,CACxB,OAAQ,IACR,SACF,CAAC,EAGH,IAAM,EAAY,EAAsB,CAAI,EACtC,EAAO,EAAU,KAEvB,GAAI,EAAY,OAAS,UACvB,OAAO,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,QAAS,CACP,gBAAiB,WAAW,GAC9B,CACF,CAAC,EAGH,IAAI,EAAQ,EAAY,MACpB,EAAM,EAAY,IAmBtB,GAjBI,IAAU,IAAA,IAAa,IAAQ,IAAA,KAS/B,IAAU,IAAA,IAEZ,EAAQ,KAAK,IAAI,EAAG,EADC,KAAK,IAAI,GAAO,EAAG,CACb,CAAY,EACvC,EAAM,EAAO,IACJ,IAAQ,IAAA,IAAa,GAAO,KACrC,EAAM,EAAO,GAGX,EAAQ,GAAK,EAAM,GAAK,GAAS,GAAQ,GAAO,GAAQ,EAAQ,GAClE,OAAO,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,QAAS,CACP,gBAAiB,WAAW,GAC9B,CACF,CAAC,EAGH,IAAM,EAAU,EAAU,MAAM,EAAO,EAAM,CAAC,EAK9C,OAHA,EAAQ,IAAI,iBAAkB,OAAO,EAAQ,MAAM,CAAC,EACpD,EAAQ,IAAI,gBAAiB,SAAS,EAAM,GAAG,EAAI,GAAG,GAAM,EAErD,IAAI,SAAS,EAAQ,QAAS,CACnC,OAAQ,IACR,SACF,CAAC,CACH,EC9JM,EAAe,UACf,EAAsB,eAEtB,EAAsB,GAAyB,CACnD,GAAI,CACF,OAAO,mBAAmB,CAAY,CACxC,MAAQ,CACN,OAAO,CACT,CACF,EAEM,EAAqB,GACzB,EAAa,WAAW,CAAY,EAChC,EAAa,MAAM,EAAa,MAAM,EACtC,EAEA,EAAyB,GAAyB,CACtD,IAAM,EAAkC,EAAkB,CAAY,EAMtE,OAJI,EAAoB,KAAK,CAA+B,EACnD,EAGF,EAAkB,EAAmB,CAA+B,CAAC,CAC9E,EAEa,EAAb,KAAsB,CAWpB,YAAY,CACV,QACA,UACA,oBACA,GAAG,GAKF,cAlB2B,IAC5B,QAAQ,MAAM,CAAK,EAEZ,IAAI,SAAS,OAAO,CAAK,EAAG,CAAE,OAAQ,GAAI,CAAC,GAgBlD,KAAK,cAAgB,EAAoB,CAAI,EAC7C,KAAK,MAAQ,GAAS,CAAC,EAEvB,KAAK,kBACH,KAAuB,CAAE,cAAe,QAAQ,QAAQ,CAAQ,GAClE,KAAK,QAAU,GAAW,KAAK,OACjC,CAEA,OAAe,CACb,KAAK,cAAc,MAAM,CAC3B,CAEA,cAAqB,EAAa,CAOhC,OANI,KAAK,kBAAoB,IAAA,IAAa,KAAK,kBAAoB,GACjE,KAAK,cAAc,MAAM,EAG3B,KAAK,gBAAkB,EAEhB,KAAK,cAAc,OAAO,CAAG,CACtC,CAEA,yBAAgC,EAAa,CAC3C,OAAO,KAAK,cAAc,CAAG,CAAC,CAAC,MAAA,EAAA,EAAA,IAAA,EACxB,CAAE,UAAS,cACd,EAAQ,EAED,EACR,CACH,CACF,CAEA,oBAA8B,CAC5B,MACA,eAIC,CAcD,OAAA,EAAA,EAAA,cAAA,CAbkB,KAAK,cAAc,CAAG,CAAC,CAAC,MAAA,EAAA,EAAA,SAAA,EAC9B,CAAE,UAAS,cAAA,EAAA,EAAA,KAAA,CACd,EAAY,CAAO,CAAC,CAAC,CAAC,MAAA,EAAA,EAAA,SAAA,KACV,CACb,EAAQ,CACV,CAAC,CACH,CACF,GAAA,EAAA,EAAA,WAAA,CACY,IACV,EAAA,EAAA,GAAA,CAAU,KAAK,QAAQ,CAAK,CAAC,CAC9B,CAGkB,CAAS,CAChC,CAEA,cAAqB,CAAE,MAAK,WAA8C,CACxE,OAAO,KAAK,oBAAoB,CAC9B,MACA,YAAc,IAQZ,EAAA,EAAA,KAAA,CANE,EAA4B,EAAS,CACnC,UACA,MAAO,KAAK,MAAM,QACpB,CAAC,CAGI,CAAA,CAAU,MAAA,EAAA,EAAA,UAAA,CACJ,IAAA,EAAA,EAAA,KAAA,CACJ,KAAK,kBAAkB,CAAE,WAAU,SAAQ,CAAC,CAAC,CACpD,GAAA,EAAA,EAAA,IAAA,CAEG,GACC,IAAI,SAAS,KAAK,UAAU,CAA2B,EAAG,CACxD,OAAQ,GACV,CAAC,CACL,CACF,CAEJ,CAAC,CACH,CAEA,cAAqB,CACnB,MACA,eACA,WAKC,CACD,OAAO,KAAK,oBAAoB,CAC9B,MACA,YAAc,IAoBZ,EAAA,EAAA,KAAA,CALE,EAA4B,EARF,EAAsB,CAQX,EAAqB,CACxD,MAAO,KAAK,MAAM,QACpB,CAAC,CAGI,CAAA,CAAU,MAAA,EAAA,EAAA,IAAA,CACV,GACH,EAAoB,CAClB,KAAM,EAAS,MAAQ,GACvB,YAAa,EAAS,OAAO,YAC7B,YAAa,GAAS,QAAQ,IAAI,OAAO,CAC3C,CAAC,CACH,CACF,CAEJ,CAAC,CACH,CACF,EC9La,EAAb,cAA2C,CAAS,CAOlD,YAAY,CACV,aACA,GAAG,GAOF,CACD,MAAM,CAAI,EAEV,KAAK,WAAa,EAClB,KAAK,mBAAqB,KAAK,mBAAmB,KAAK,IAAI,CAC7D,CAEA,mBAAmB,EAAwC,CACzD,GAAI,CACF,IAAM,EAAU,KAAK,WAAW,CAAK,EAErC,GAAI,CAAC,EAAS,OAEd,IAAM,GAAA,EAAA,EAAA,oBAAA,CAA8B,EAAQ,OAAO,EAC7C,EAAe,EAAM,QAAQ,IAAI,UACrC,EAAQ,OAAS,CACnB,EACM,CAAC,EAAM,IAAM,EAAa,MAAM,GAAG,EACnC,GAAA,EAAA,EAAA,oBAAA,CACJ,EAAa,UAAU,EAAI,OAAS,CAAU,CAChD,EAEI,EAAa,SAAS,WAAW,EACnC,EAAM,YACJ,KAAK,cAAc,CAAE,MAAK,QAAS,GAAG,EAAQ,GAAG,EAAI,EAAG,CAAC,CAC3D,EAEA,EAAM,YACJ,KAAK,cAAc,CACjB,MACA,eACA,QAAS,EAAM,OACjB,CAAC,CACH,CAEJ,OAAS,EAAG,CACV,EAAM,YAAY,IAAI,SAAS,OAAO,CAAC,EAAG,CAAE,OAAQ,GAAI,CAAC,CAAC,CAC5D,CACF,CACF"}
@@ -1,37 +1,37 @@
1
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
2
  import { getArchiveFileRecordByUri as o, readArchiveOpf as s, readRecordAsText as c, resolveArchive as l } from "@prose-reader/archive-reader";
3
3
  import { XmlDocument as u } from "xmldoc";
4
- import { BehaviorSubject as d, EMPTY as f, NEVER as p, Subject as m, catchError as h, distinctUntilChanged as g, filter as _, finalize as ee, first as v, from as y, ignoreElements as te, lastValueFrom as ne, map as b, merge as x, mergeMap as S, of as C, pairwise as w, shareReplay as T, startWith as E, switchMap as D, takeUntil as re, tap as O, timer as ie } from "rxjs";
4
+ import { BehaviorSubject as d, EMPTY as f, NEVER as p, Subject as m, catchError as h, distinctUntilChanged as g, filter as _, finalize as ee, first as v, from as y, ignoreElements as b, lastValueFrom as x, map as S, merge as C, mergeMap as w, of as te, pairwise as T, shareReplay as E, startWith as D, switchMap as O, takeUntil as ne, tap as k, timer as re } from "rxjs";
5
5
  //#endregion
6
6
  //#region src/report.ts
7
- var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae = ({ enableReport: e } = {}) => {
8
- k.enable(!!e);
9
- }, oe = (e) => e.replace(/\.[^.]+$/, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim(), A = (e, t) => {
7
+ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ie = ({ enableReport: e } = {}) => {
8
+ A.enable(!!e);
9
+ }, ae = (e) => e.replace(/\.[^.]+$/, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim(), j = (e, t) => {
10
10
  if (e.spineItems.length !== 0 && e.spineItems.every((e) => (r(e.mediaType ?? "") || n(e.href))?.startsWith("audio/"))) return e.spineItems.map((e) => {
11
11
  let n = t.records.find((t) => !t.dir && decodeURI(e.href).endsWith(t.uri));
12
12
  return {
13
- title: oe(n?.basename ?? e.href),
13
+ title: ae(n?.basename ?? e.href),
14
14
  href: e.href,
15
15
  path: n?.uri ?? e.href,
16
16
  contents: []
17
17
  };
18
18
  });
19
- }, j = ({ baseUrl: e = "", resourcePath: t }) => {
19
+ }, M = (e) => encodeURI(e).replace(/#/g, "%23").replace(/\?/g, "%3F"), N = ({ baseUrl: e = "", resourcePath: t }) => {
20
20
  if (!e && /^https?:\/\//.test(t)) return encodeURI(t);
21
21
  let n = e ? `${e}${e.endsWith("/") ? "" : "/"}` : "file://";
22
- return encodeURI(`${n}${t}`);
23
- }, M = (e) => (t) => ({
22
+ return `${encodeURI(n)}${M(t)}`;
23
+ }, P = (e) => (t) => ({
24
24
  title: t.title,
25
25
  path: t.path,
26
26
  href: t.containerHref ? a(e, t.containerHref) : "",
27
- contents: t.contents.map(M(e))
28
- }), N = (e) => {
27
+ contents: t.contents.map(P(e))
28
+ }), F = (e) => {
29
29
  let t = e?.trim();
30
30
  if (t === "cover" || t === "title-page" || t === "copyright-page" || t === "text") return t;
31
- }, P = (e) => /^https?:\/\//.test(e), F = (e) => e.records.find(({ dir: e }) => e)?.basename.replace(/\/$/, ""), I = (e, n, r, i) => {
31
+ }, I = (e) => /^https?:\/\//.test(e), L = (e) => e.records.find(({ dir: e }) => e)?.basename.replace(/\/$/, ""), R = (e, n, r, i) => {
32
32
  let { metadata: a, readingOrder: o } = r, s = i.basePath, c = [];
33
33
  for (let e of i.opf.guide) {
34
- let t = N(e.type);
34
+ let t = F(e.type);
35
35
  t !== void 0 && c.push({
36
36
  href: e.href,
37
37
  title: e.title,
@@ -41,7 +41,7 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
41
41
  let l = t();
42
42
  return {
43
43
  filename: e.filename ?? "",
44
- title: a.title || F(e) || "",
44
+ title: a.title || L(e) || "",
45
45
  renditionLayout: a.renditionLayout,
46
46
  renditionFlow: a.renditionFlow ?? "auto",
47
47
  renditionSpread: a.renditionSpread,
@@ -49,7 +49,7 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
49
49
  spineItems: o.map((e, t) => ({
50
50
  id: e.id ?? l(e.uri),
51
51
  index: t,
52
- href: P(e.uri) ? e.uri : `${n || "file://"}${e.uri}`,
52
+ href: I(e.uri) ? e.uri : `${n || "file://"}${e.uri}`,
53
53
  renditionLayout: e.renditionLayout,
54
54
  ...e.renditionFlow === void 0 ? {} : { renditionFlow: e.renditionFlow },
55
55
  progressionWeight: e.progressionWeight,
@@ -59,23 +59,23 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
59
59
  })),
60
60
  items: i.opf.manifestItems.map((e) => ({
61
61
  id: e.id,
62
- href: P(e.href) ? e.href : `${n || "file://"}${s ? `${s}/` : ""}${e.href}`,
62
+ href: I(e.href) ? e.href : `${n || "file://"}${s ? `${s}/` : ""}${e.href}`,
63
63
  mediaType: e.mediaType
64
64
  })),
65
65
  guide: c.length > 0 ? c : void 0,
66
- nav: { toc: (r.toc ?? []).map(M(n)) }
66
+ nav: { toc: (r.toc ?? []).map(P(n)) }
67
67
  };
68
- }, L = (e, n, r) => {
68
+ }, z = (e, n, r) => {
69
69
  let { metadata: i, readingOrder: a } = r, o = e.records.filter((e) => !e.dir), s = t(), c = new Map(o.map((e) => [e.uri, s(e.uri)])), l = {
70
70
  filename: e.filename ?? "",
71
- title: i.title || F(e) || e.filename || "",
71
+ title: i.title || L(e) || e.filename || "",
72
72
  renditionLayout: i.renditionLayout,
73
73
  renditionSpread: "auto",
74
74
  readingDirection: i.readingDirection,
75
75
  spineItems: a.map((e, t) => ({
76
76
  id: c.get(e.uri) ?? s(e.uri),
77
77
  index: t,
78
- href: j({
78
+ href: N({
79
79
  baseUrl: n,
80
80
  resourcePath: e.uri
81
81
  }),
@@ -89,19 +89,19 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
89
89
  id: c.get(e.uri) ?? s(e.uri),
90
90
  href: encodeURI(`${n}${e.uri}`)
91
91
  }))
92
- }, u = A(l, e) ?? r.toc?.map(M(n));
92
+ }, u = j(l, e) ?? r.toc?.map(P(n));
93
93
  return u === void 0 ? l : {
94
94
  ...l,
95
95
  nav: { toc: u }
96
96
  };
97
- }, R = ({ archive: e, baseUrl: t, resolved: n }) => {
97
+ }, B = ({ archive: e, baseUrl: t, resolved: n }) => {
98
98
  let r = n.sources.opf;
99
- return r ? I(e, t, n, r) : L(e, t, n);
100
- }, z = (e) => e ? e.endsWith("/") ? e : `${e}/` : "", B = async (e, { baseUrl: t = "", hooks: n = [] } = {}) => {
101
- k.log("Generating manifest from archive", e);
102
- let r = z(t);
99
+ return r ? R(e, t, n, r) : z(e, t, n);
100
+ }, V = (e) => e ? e.endsWith("/") ? e : `${e}/` : "", H = async (e, { baseUrl: t = "", hooks: n = [] } = {}) => {
101
+ A.log("Generating manifest from archive", e);
102
+ let r = V(t);
103
103
  try {
104
- let t = R({
104
+ let t = B({
105
105
  archive: e,
106
106
  baseUrl: r,
107
107
  resolved: await l(e, {
@@ -121,15 +121,15 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
121
121
  if (t = {
122
122
  ...t,
123
123
  readingDirection: t.readingDirection ?? "ltr"
124
- }, k.log("Generated manifest", t), process.env.NODE_ENV === "development" && k.isEnabled()) {
124
+ }, A.log("Generated manifest", t), process.env.NODE_ENV === "development" && A.isEnabled()) {
125
125
  let e = JSON.stringify(t, null, 2);
126
- k.groupCollapsed(...k.getGroupArgs("Generated manifest")), k.log(`\n${e}`), k.groupEnd();
126
+ A.groupCollapsed(...A.getGroupArgs("Generated manifest")), A.log(`\n${e}`), A.groupEnd();
127
127
  }
128
128
  return t;
129
129
  } catch (e) {
130
- throw k.error(e), e;
130
+ throw A.error(e), e;
131
131
  }
132
- }, V = (e, t) => ({ archive: n, resourcePath: r }) => async (i) => {
132
+ }, U = (e, t) => ({ archive: n, resourcePath: r }) => async (i) => {
133
133
  let a = o(n, r);
134
134
  if (a?.basename.endsWith(e)) {
135
135
  let e = await t(typeof i.body == "string" ? i.body : await c(a));
@@ -139,15 +139,15 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
139
139
  };
140
140
  }
141
141
  return i;
142
- }, H = (e) => {
142
+ }, W = (e) => {
143
143
  let t = e.descendantWithPath("head")?.childrenNamed("meta").find((e) => e.attr.name === "calibre:cover");
144
144
  return !!(t && t.attr.name === "calibre:cover");
145
- }, U = (e) => e.descendantWithPath("body")?.descendantWithPath("div")?.childrenNamed("svg")?.find((e) => e.attr.width === "100%" && e.attr.preserveAspectRatio === "none"), W = V(".xhtml", (e) => {
145
+ }, G = (e) => e.descendantWithPath("body")?.descendantWithPath("div")?.childrenNamed("svg")?.find((e) => e.attr.width === "100%" && e.attr.preserveAspectRatio === "none"), K = U(".xhtml", (e) => {
146
146
  let t = new u(e);
147
- if (!H(t)) return;
148
- let n = U(t);
147
+ if (!W(t)) return;
148
+ let n = G(t);
149
149
  return n && delete n.attr.preserveAspectRatio, t.toString();
150
- }), G = V(".css", (e) => e.replaceAll("-webkit-writing-mode", "writing-mode")), se = async (e, t) => {
150
+ }), oe = U(".css", (e) => e.replaceAll("-webkit-writing-mode", "writing-mode")), q = async (e, t) => {
151
151
  let n = await s(e);
152
152
  if (n) {
153
153
  let { opf: e, basePath: r } = n, i = e.manifestItems.find((e) => {
@@ -156,8 +156,8 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
156
156
  })?.mediaType;
157
157
  if (i) return { mediaType: i };
158
158
  }
159
- return { mediaType: K(t) };
160
- }, K = (e) => {
159
+ return { mediaType: se(t) };
160
+ }, se = (e) => {
161
161
  if (e.endsWith(".css")) return "text/css; charset=UTF-8";
162
162
  if (e.endsWith(".jpg")) return "image/jpg";
163
163
  if (e.endsWith(".xhtml")) return "application/xhtml+xml";
@@ -166,7 +166,7 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
166
166
  }, ce = ({ archive: e, resourcePath: t }) => async (n) => {
167
167
  let r = o(e, t);
168
168
  if (!r) return n;
169
- let i = await se(e, t);
169
+ let i = await q(e, t);
170
170
  return {
171
171
  ...n,
172
172
  params: {
@@ -175,11 +175,11 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
175
175
  ...i.mediaType && { contentType: i.mediaType }
176
176
  }
177
177
  };
178
- }, 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("."), le = V(".xhtml", (e) => {
179
- if (!RegExp(`<(${q.join("|")})[\\s/>]`, "i").test(e)) return;
180
- let t = RegExp(`<(${q.join("|")})(\\s[^>]*)?\\s*/>`, "gi");
178
+ }, 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("."), le = U(".xhtml", (e) => {
179
+ if (!RegExp(`<(${J.join("|")})[\\s/>]`, "i").test(e)) return;
180
+ let t = RegExp(`<(${J.join("|")})(\\s[^>]*)?\\s*/>`, "gi");
181
181
  return e.replace(t, (e, t, n = "") => `<${t} ${n.trim()}></${t}>`);
182
- }), J = async (e, t, { hooks: n = [] } = {}) => {
182
+ }), Y = async (e, t, { hooks: n = [] } = {}) => {
183
183
  let r = { params: {} }, i = [
184
184
  ...n.map((n) => n({
185
185
  archive: e,
@@ -193,18 +193,18 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
193
193
  archive: e,
194
194
  resourcePath: t
195
195
  }),
196
- G({
196
+ oe({
197
197
  archive: e,
198
198
  resourcePath: t
199
199
  }),
200
- W({
200
+ K({
201
201
  archive: e,
202
202
  resourcePath: t
203
203
  })
204
204
  ];
205
205
  try {
206
206
  let n = await i.reduce(async (e, t) => await t(await e), Promise.resolve(r));
207
- if (k.log("Generated resource", t, n), n.body !== void 0) return n;
207
+ if (A.log("Generated resource", t, n), n.body !== void 0) return n;
208
208
  let a = o(e, t);
209
209
  if (!a) throw Error(`no file found for resourcePath:${t}`);
210
210
  return {
@@ -212,7 +212,7 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
212
212
  body: await a.blob()
213
213
  };
214
214
  } catch (e) {
215
- throw k.error(e), e;
215
+ throw A.error(e), e;
216
216
  }
217
217
  }, ue = class {
218
218
  constructor(e) {
@@ -228,30 +228,30 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
228
228
  });
229
229
  }
230
230
  get locks$() {
231
- return this.state$.pipe(b(({ locks: e }) => e));
231
+ return this.state$.pipe(S(({ locks: e }) => e));
232
232
  }
233
233
  get state() {
234
234
  return this.state$.getValue();
235
235
  }
236
236
  get isUnlocked$() {
237
- return this.locks$.pipe(b((e) => e <= 0), g(), T());
237
+ return this.locks$.pipe(S((e) => e <= 0), g(), E());
238
238
  }
239
239
  get overTTL$() {
240
- return this.isUnlocked$.pipe(D((e) => e ? this.cleanArchiveAfter === Infinity ? p : ie(this.cleanArchiveAfter) : p));
240
+ return this.isUnlocked$.pipe(O((e) => e ? this.cleanArchiveAfter === Infinity ? p : re(this.cleanArchiveAfter) : p));
241
241
  }
242
242
  }, de = ({ getArchive: e, cleanArchiveAfter: t = 300 * 1e3 }) => {
243
243
  let n = new m(), r = new m(), i = new m(), a = {};
244
- return n.pipe(S((t) => {
244
+ return n.pipe(w((t) => {
245
245
  let n = a[t];
246
246
  if (n?.state.status !== "idle") return f;
247
247
  let r = !1, o = (e) => {
248
- k.debug(`Cleaning up archive with key: ${e}`);
248
+ A.debug(`Cleaning up archive with key: ${e}`);
249
249
  let t = a[e];
250
250
  delete a[e], r ||= (t?.state.archive?.close(), !0);
251
251
  };
252
252
  n.update({ status: "loading" });
253
- let s = n.locks$, c = n.isUnlocked$, l = s.pipe(w(), _(([e, t]) => t > e), E(!0));
254
- return y(e(t)).pipe(O((e) => {
253
+ let s = n.locks$, c = n.isUnlocked$, l = s.pipe(T(), _(([e, t]) => t > e), D(!0));
254
+ return y(e(t)).pipe(k((e) => {
255
255
  n.update({
256
256
  archive: e,
257
257
  status: "success"
@@ -259,19 +259,19 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
259
259
  }), h((e) => (o(t), n.update({
260
260
  status: "error",
261
261
  error: e
262
- }), f)), D(() => x(l.pipe(D(() => i), D(() => c), _((e) => e)), n.overTTL$).pipe(v(), O(() => {
262
+ }), f)), O(() => C(l.pipe(O(() => i), O(() => c), _((e) => e)), n.overTTL$).pipe(v(), k(() => {
263
263
  o(t);
264
264
  }))));
265
- }), re(r)).subscribe(), {
265
+ }), ne(r)).subscribe(), {
266
266
  access: (e) => {
267
267
  let r = !1, i = a[e] ?? new ue(t);
268
268
  a[e] = i, i.update({ locks: i.state.locks + 1 });
269
269
  let o = () => {
270
270
  r || (r = !0, i.update({ locks: i.state.locks - 1 }));
271
271
  };
272
- return n.next(e), x(i.state$.pipe(b(({ archive: e }) => e), _((e) => !!e)), i.state$.pipe(O(({ error: e }) => {
272
+ return n.next(e), C(i.state$.pipe(S(({ archive: e }) => e), _((e) => !!e)), i.state$.pipe(k(({ error: e }) => {
273
273
  if (e) throw e;
274
- }), te())).pipe(v(), b((e) => ({
274
+ }), b())).pipe(v(), S((e) => ({
275
275
  archive: e,
276
276
  release: o
277
277
  })), h((e) => {
@@ -283,7 +283,7 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
283
283
  },
284
284
  _archives: a
285
285
  };
286
- }, Y = (e) => e ? /^\d+$/.test(e) ? {
286
+ }, X = (e) => e ? /^\d+$/.test(e) ? {
287
287
  valid: !0,
288
288
  value: Number.parseInt(e, 10)
289
289
  } : {
@@ -299,7 +299,7 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
299
299
  if (t.includes(",")) return { kind: "multi" };
300
300
  let n = /^(\d*)-(\d*)$/.exec(t);
301
301
  if (!n) return { kind: "invalid" };
302
- let [, r = "", i = ""] = n, a = Y(r.trim()), o = Y(i.trim());
302
+ let [, r = "", i = ""] = n, a = X(r.trim()), o = X(i.trim());
303
303
  return !a.valid || !o.valid ? { kind: "invalid" } : {
304
304
  kind: "single",
305
305
  start: a.value,
@@ -353,15 +353,15 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
353
353
  status: 206,
354
354
  headers: r
355
355
  });
356
- }, X = "file://", he = /^https?:\/\//, Z = (e) => {
356
+ }, Z = "file://", he = /^https?:\/\//, ge = (e) => {
357
357
  try {
358
358
  return decodeURIComponent(e);
359
359
  } catch {
360
360
  return e;
361
361
  }
362
- }, Q = (e) => e.startsWith(X) ? e.slice(X.length) : e, ge = (e) => {
362
+ }, Q = (e) => e.startsWith(Z) ? e.slice(Z.length) : e, _e = (e) => {
363
363
  let t = Q(e);
364
- return he.test(t) ? t : Q(Z(t));
364
+ return he.test(t) ? t : Q(ge(t));
365
365
  }, $ = class {
366
366
  constructor({ hooks: e, onError: t, onManifestSuccess: n, ...r }) {
367
367
  this.onError = (e) => (console.error(e), new Response(String(e), { status: 500 })), this.archiveLoader = de(r), this.hooks = e ?? {}, this.onManifestSuccess = n ?? (({ manifest: e }) => Promise.resolve(e)), this.onError = t ?? this.onError;
@@ -373,36 +373,36 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
373
373
  return this.lastAccessedKey !== void 0 && this.lastAccessedKey !== e && this.archiveLoader.purge(), this.lastAccessedKey = e, this.archiveLoader.access(e);
374
374
  }
375
375
  accessArchiveWithoutLock(e) {
376
- return this.accessArchive(e).pipe(b(({ archive: e, release: t }) => (t(), e)));
376
+ return this.accessArchive(e).pipe(S(({ archive: e, release: t }) => (t(), e)));
377
377
  }
378
378
  withArchiveResponse({ key: e, getResponse: t }) {
379
- return ne(this.accessArchive(e).pipe(S(({ archive: e, release: n }) => y(t(e)).pipe(ee(() => {
379
+ return x(this.accessArchive(e).pipe(w(({ archive: e, release: n }) => y(t(e)).pipe(ee(() => {
380
380
  n();
381
- }))), h((e) => C(this.onError(e)))));
381
+ }))), h((e) => te(this.onError(e)))));
382
382
  }
383
383
  fetchManifest({ key: e, baseUrl: t }) {
384
384
  return this.withArchiveResponse({
385
385
  key: e,
386
- getResponse: (e) => y(B(e, {
386
+ getResponse: (e) => y(H(e, {
387
387
  baseUrl: t,
388
388
  hooks: this.hooks.manifest
389
- })).pipe(D((t) => y(this.onManifestSuccess({
389
+ })).pipe(O((t) => y(this.onManifestSuccess({
390
390
  manifest: t,
391
391
  archive: e
392
- }))), b((e) => new Response(JSON.stringify(e), { status: 200 })))
392
+ }))), S((e) => new Response(JSON.stringify(e), { status: 200 })))
393
393
  });
394
394
  }
395
395
  fetchResource({ key: e, resourcePath: t, request: n }) {
396
396
  return this.withArchiveResponse({
397
397
  key: e,
398
- getResponse: (e) => y(J(e, ge(t), { hooks: this.hooks.resource })).pipe(b((e) => me({
398
+ getResponse: (e) => y(Y(e, _e(t), { hooks: this.hooks.resource })).pipe(S((e) => me({
399
399
  body: e.body ?? "",
400
400
  contentType: e.params.contentType,
401
401
  rangeHeader: n?.headers.get("range")
402
402
  })))
403
403
  });
404
404
  }
405
- }, _e = class extends $ {
405
+ }, ve = class extends $ {
406
406
  constructor({ getUriInfo: e, ...t }) {
407
407
  super(t), this.getUriInfo = e, this.fetchEventListener = this.fetchEventListener.bind(this);
408
408
  }
@@ -425,6 +425,6 @@ var k = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), ae
425
425
  }
426
426
  };
427
427
  //#endregion
428
- export { _e as ServiceWorkerStreamer, $ as Streamer, ae as configure, j as createManifestResourceHref, B as generateManifestFromArchive, J as generateResourceFromArchive };
428
+ export { ve as ServiceWorkerStreamer, $ as Streamer, ie as configure, N as createManifestResourceHref, H as generateManifestFromArchive, Y as generateResourceFromArchive };
429
429
 
430
430
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"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\": \"1.332.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\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\": \"^1.332.0\",\n \"@prose-reader/shared\": \"^1.332.0\",\n \"xmldoc\": \"^2.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"*\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\"\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","export 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}${resourcePath}`)\n}\n","import type {\n Archive,\n ArchiveTocItem,\n 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 \"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 title: metadata.title || 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 title:\n metadata.title || firstDirectoryTitle(archive) || archive.filename || ``,\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":[],"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\": \"1.334.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\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\": \"^1.334.0\",\n \"@prose-reader/shared\": \"^1.334.0\",\n \"xmldoc\": \"^2.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"*\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\"\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 type {\n Archive,\n ArchiveTocItem,\n 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 \"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 title: metadata.title || 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 title:\n metadata.title || firstDirectoryTitle(archive) || archive.filename || ``,\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":";;;;;;ACOA,IAAa,IAAS,EAAa,UAAU,0BAAM,KAAA,GAAW,EAC5D,OAAO,UACT,CAAC,GCPY,MAAa,EACxB,oBAGE,CAAC,MAAM;CACT,EAAO,OAAO,CAAC,CAAC,CAAY;AAC9B,GCCa,MAA4B,MACvC,EACG,QAAQ,YAAY,EAAE,CAAC,CACvB,QAAQ,SAAS,GAAG,CAAC,CACrB,QAAQ,QAAQ,GAAG,CAAC,CACpB,KAAK,GAEG,KACX,GACA,MACoB;CAChB,MAAS,WAAW,WAAW,KAElB,EAAS,WAAW,OAAO,OAExC,EAAiB,EAAK,aAAa,EAAE,KACrC,EAAuB,EAAK,IAAI,EAAA,EAEjB,WAAW,QAAQ,CAGjC,GAEL,OAAO,EAAS,WAAW,KAAK,MAAS;EACvC,IAAM,IAAS,EAAQ,QAAQ,MAC5B,MAAM,CAAC,EAAE,OAAO,UAAU,EAAK,IAAI,CAAC,CAAC,SAAS,EAAE,GAAG,CACtD;EAEA,OAAO;GACL,OAAO,GAAyB,GAAQ,YAAY,EAAK,IAAI;GAC7D,MAAM,EAAK;GACX,MAAM,GAAQ,OAAO,EAAK;GAC1B,UAAU,CAAC;EACb;CACF,CAAC;AACH,GCxCM,KAAsB,MAC1B,UAAU,CAAY,CAAC,CAAC,QAAQ,MAAM,KAAK,CAAC,CAAC,QAAQ,OAAO,KAAK,GAEtD,KAA8B,EACzC,aAAU,IACV,sBAII;CACJ,IAAI,CAAC,KAAW,eAAe,KAAK,CAAY,GAC9C,OAAO,UAAU,CAAY;CAG/B,IAAM,IAAc,IAChB,GAAG,IAAU,EAAQ,SAAS,GAAG,IAAI,KAAK,QAC1C;CAEJ,OAAO,GAAG,UAAU,CAAW,IAAI,EAAmB,CAAY;AACpE,GCNM,KACH,OACA,OAAmC;CAClC,OAAO,EAAK;CACZ,MAAM,EAAK;CAGX,MAAM,EAAK,gBAAgB,EAAQ,GAAS,EAAK,aAAa,IAAI;CAClE,UAAU,EAAK,SAAS,IAAI,EAAkB,CAAO,CAAC;AACxD,IAEI,KACJ,MAC2C;CAC3C,IAAM,IAAI,GAAK,KAAK;CACpB,IACE,MAAM,WACN,MAAM,gBACN,MAAM,oBACN,MAAM,QAEN,OAAO;AAGX,GAEM,KAAa,MAAkB,eAAe,KAAK,CAAK,GAExD,KAAuB,MAC3B,EAAQ,QAAQ,MAAM,EAAE,aAAU,CAAG,CAAC,EAAE,SAAS,QAAQ,OAAO,EAAE,GAY9D,KACJ,GACA,GACA,GACA,MACa;CACb,IAAM,EAAE,aAAU,oBAAiB,GAC7B,IAAc,EAAI,UAElB,IAA8B,CAAC;CACrC,KAAK,IAAM,KAAa,EAAI,IAAI,OAAO;EACrC,IAAM,IAAO,EAAkB,EAAU,IAAI;EACzC,MAAS,KAAA,KAGb,EAAM,KAAK;GAAE,MAAM,EAAU;GAAM,OAAO,EAAU;GAAO;EAAK,CAAC;CACnE;CAEA,IAAM,IAAe,EAAuB;CAE5C,OAAO;EACL,UAAU,EAAQ,YAAY;EAC9B,OAAO,EAAS,SAAS,EAAoB,CAAO,KAAK;EACzD,iBAAiB,EAAS;EAC1B,eAAe,EAAS,iBAAiB;EACzC,iBAAiB,EAAS;EAC1B,kBAAkB,EAAS;EAC3B,YAAY,EAAa,KAAK,GAAM,OAAW;GAC7C,IAAI,EAAK,MAAM,EAAa,EAAK,GAAG;GACpC;GAGA,MAAM,EAAU,EAAK,GAAG,IACpB,EAAK,MACL,GAAG,KAAW,YAAY,EAAK;GACnC,iBAAiB,EAAK;GACtB,GAAI,EAAK,kBAAkB,KAAA,IAEvB,CAAC,IADD,EAAE,eAAe,EAAK,cAAc;GAExC,mBAAmB,EAAK;GACxB,gBAAgB,EAAK;GACrB,iBAAiB,EAAK;GACtB,WAAW,EAAK;EAClB,EAAE;EACF,OAAO,EAAI,IAAI,cAAc,KAAK,OAAU;GAC1C,IAAI,EAAK;GACT,MAAM,EAAU,EAAK,IAAI,IACrB,EAAK,OACL,GAAG,KAAW,YAAY,IAAc,GAAG,EAAY,KAAK,KAAK,EAAK;GAC1E,WAAW,EAAK;EAClB,EAAE;EACF,OAAO,EAAM,SAAS,IAAI,IAAQ,KAAA;EAClC,KAAK,EAEH,MAAM,EAAS,OAAO,CAAC,EAAA,CAAG,IAAI,EAAkB,CAAO,CAAC,EAC1D;CACF;AACF,GAEM,KACJ,GACA,GACA,MACa;CACb,IAAM,EAAE,aAAU,oBAAiB,GAE7B,IAAQ,EAAQ,QAAQ,QAAQ,MAAS,CAAC,EAAK,GAAG,GAClD,IAAe,EAAuB,GACtC,IAAU,IAAI,IAClB,EAAM,KAAK,MAAS,CAAC,EAAK,KAAK,EAAa,EAAK,GAAG,CAAC,CAAC,CACxD,GAEM,IAAqB;EACzB,UAAU,EAAQ,YAAY;EAC9B,OACE,EAAS,SAAS,EAAoB,CAAO,KAAK,EAAQ,YAAY;EACxE,iBAAiB,EAAS;EAC1B,iBAAiB;EACjB,kBAAkB,EAAS;EAC3B,YAAY,EAAa,KAAK,GAAM,OAAW;GAC7C,IAAI,EAAQ,IAAI,EAAK,GAAG,KAAK,EAAa,EAAK,GAAG;GAClD;GACA,MAAM,EAA2B;IAAE;IAAS,cAAc,EAAK;GAAI,CAAC;GACpE,iBAAiB,EAAK;GACtB,mBAAmB,EAAK;GACxB,gBAAgB,KAAA;GAChB,iBAAiB,KAAA;GACjB,WAAW,EAAK;EAClB,EAAE;EAGF,OAAO,EAAM,KAAK,OAAU;GAC1B,IAAI,EAAQ,IAAI,EAAK,GAAG,KAAK,EAAa,EAAK,GAAG;GAClD,MAAM,UAAU,GAAG,IAAU,EAAK,KAAK;EACzC,EAAE;CACJ,GAGM,IADe,EAAkB,GAAU,CACrC,KAAgB,EAAS,KAAK,IAAI,EAAkB,CAAO,CAAC;CAExE,OAAO,MAAQ,KAAA,IAA4C,IAAhC;EAAE,GAAG;EAAU,KAAK,EAAE,OAAI;CAAE;AACzD,GASa,KAA+B,EAC1C,YACA,YACA,kBAKc;CACd,IAAM,IAAM,EAAS,QAAQ;CAI7B,OAFI,IAAY,EAAa,GAAS,GAAS,GAAU,CAAG,IAErD,EAAgB,GAAS,GAAS,CAAQ;AACnD,GChLM,KAAoB,MACnB,IAEE,EAAQ,SAAS,GAAG,IAAI,IAAU,GAAG,EAAQ,KAF/B,IAaV,IAA8B,OACzC,GACA,EACE,aAAU,IACV,WAAQ,CAAC,MACsD,CAAC,MAC/D;CACH,EAAO,IAAI,oCAAoC,CAAO;CAEtD,IAAM,IAAoB,EAAiB,CAAO;CAElD,IAAI;EAMF,IAAI,IAAW,EAA4B;GACzC;GACA,SAAS;GACT,UAAA,MARqB,EAAe,GAAS;IAC7C,SAAS;KAAC;KAAY;KAAgB;KAAO;IAAS;IACtD,YAAY;GACd,CAAC;EAMD,CAAC;EAED,KAAK,IAAM,KAAe,GACxB,IAAW,MAAM,EAAY;GAC3B;GACA,SAAS;EACX,CAAC,CAAC,CAAC,CAAQ;EAUb,IAPA,IAAW;GACT,GAAG;GACH,kBAAkB,EAAS,oBAAoB;EACjD,GAEA,EAAO,IAAI,sBAAsB,CAAQ,GAEzC,QAAA,IAAA,aAA6B,iBACvB,EAAO,UAAU,GAAG;GACtB,IAAM,IAAc,KAAK,UAAU,GAAU,MAAM,CAAC;GAGpD,AAFA,EAAO,eAAe,GAAG,EAAO,aAAa,oBAAoB,CAAC,GAClE,EAAO,IAAI,KAAK,GAAa,GAC7B,EAAO,SAAS;EAClB;EAGF,OAAO;CACT,SAAS,GAAG;EAGV,MAFA,EAAO,MAAM,CAAC,GAER;CACR;AACF,GCnDa,KAET,GACA,OAID,EACC,YACA,sBAEF,OAAO,MAAa;CAClB,IAAM,IAAO,EAA0B,GAAS,CAAY;CAE5D,IAAI,GAAM,SAAS,SAAS,CAAS,GAAG;EAMtC,IAAM,IAAU,MAAM,EAJpB,OAAO,EAAS,QAAS,WACrB,EAAS,OACT,MAAM,EAAiB,CAAI,CAEU;EAE3C,IAAI,MAAY,KAAA,GACd,OAAO;GACL,GAAG;GACH,MAAM;EACR;CAEJ;CAEA,OAAO;AACT,GCjDI,KAAuB,MAAqB;CAChD,IAAM,IAAU,EACb,mBAAmB,MAAM,CAAC,EACzB,cAAc,MAAM,CAAC,CACtB,MAAM,MAAS,EAAK,KAAK,SAAS,eAAe;CAEpD,OAAO,CAAC,EAAE,KAAW,EAAQ,KAAK,SAAS;AAC7C,GAEM,KAAoB,MACjB,EACJ,mBAAmB,MAAM,CAAC,EACzB,mBAAmB,KAAK,CAAC,EACzB,cAAc,KAAK,CAAC,EACpB,MACC,MACC,EAAK,KAAK,UAAU,UAAU,EAAK,KAAK,wBAAwB,MACpE,GAGS,IAAiB,EAC5B,WACC,MAAgB;CACf,IAAM,IAAY,IAAI,EAAY,CAAW;CAE7C,IAAI,CAAC,EAAoB,CAAS,GAChC;CAGF,IAAM,IAAW,EAAiB,CAAS;CAM3C,OAJI,KACF,OAAO,EAAS,KAAK,qBAGhB,EAAU,SAAS;AAC5B,CACF,GCtCa,KAAa,EAAuB,SAAS,MAKxD,EAAK,WAAW,wBAAwB,cAAc,CACxD,GCIM,IAAc,OAAO,GAAkB,MAAyB;CACpE,IAAM,IAAS,MAAM,EAAe,CAAO;CAE3C,IAAI,GAAQ;EACV,IAAM,EAAE,QAAK,gBAAa,GAKpB,IAAiB,EAAI,cAAc,MAAM,MAAS;GACtD,IAAM,IAAgB,IAAW,GAAG,EAAS,GAAG,EAAK,SAAS,EAAK;GAEnE,OAAO,EAAa,SAAS,CAAa;EAC5C,CAAC,CAAC,EAAE;EAEJ,IAAI,GACF,OAAO,EACL,WAAW,EACb;CAEJ;CAEA,OAAO,EACL,WAAW,GAA4B,CAAY,EACrD;AACF,GAEM,MAA+B,MAAgB;CACnD,IAAI,EAAI,SAAS,MAAM,GACrB,OAAO;CAET,IAAI,EAAI,SAAS,MAAM,GACrB,OAAO;CAET,IAAI,EAAI,SAAS,QAAQ,GACvB,OAAO;CAET,IAAI,EAAI,SAAS,MAAM,GACrB,OAAO;CAET,IAAI,EAAI,SAAS,MAAM,GACrB,OAAO;AAEX,GAEa,MACV,EAAE,YAAS,sBACZ,OAAO,MAAkD;CACvD,IAAM,IAAO,EAA0B,GAAS,CAAY;CAE5D,IAAI,CAAC,GAAM,OAAO;CAElB,IAAM,IAAW,MAAM,EAAY,GAAS,CAAY;CAExD,OAAO;EACL,GAAG;EACH,QAAQ;GACN,GAAG,EAAS;GACZ,GAAI,GAAM,kBAAkB,EAC1B,aAAa,EAAK,eACpB;GACA,GAAI,EAAS,aAAa,EACxB,aAAa,EAAS,UACxB;EACF;CACF;AACF,GC5EI,IAAyB,wSAmD/B,GASa,KAAyB,EACpC,WACC,MAAgB;CAKf,IAAI,CAJwB,OAC1B,KAAK,EAAuB,KAAK,GAAG,EAAE,WACtC,GAEG,CAAA,CAAgB,KAAK,CAAW,GACnC;CAGF,IAAM,IAAiB,OACrB,KAAK,EAAuB,KAAK,GAAG,EAAE,qBACtC,IACF;CAEA,OAAO,EAAY,QAAQ,IAAa,GAAG,GAAS,IAAa,OAExD,IAAI,EAAQ,GAAG,EAAW,KAAK,EAAE,KAAK,EAAQ,EACtD;AACH,CACF,GC7Da,IAA8B,OACzC,GACA,GACA,EAAE,WAAQ,CAAC,MAAiD,CAAC,MAC1D;CACH,IAAM,IAAgC,EACpC,QAAQ,CAAC,EACX,GAOM,IAAgB;EACpB,GAPoB,EAAM,KAAK,MAAS,EAAK;GAAE;GAAS;EAAa,CAAC,CAOnE;EACH,GAAY;GAAE;GAAS;EAAa,CAAC;EACrC,GAAuB;GAAE;GAAS;EAAa,CAAC;EAChD,GAAW;GAAE;GAAS;EAAa,CAAC;EACpC,EAAe;GAAE;GAAS;EAAa,CAAC;CAC1C;CAEA,IAAI;EACF,IAAM,IAAW,MAAM,EAAc,OAAO,OAAO,GAAU,MACpD,MAAM,EAAI,MAAM,CAAQ,GAC9B,QAAQ,QAAQ,CAAe,CAAC;EAInC,IAFA,EAAO,IAAI,sBAAsB,GAAc,CAAQ,GAEnD,EAAS,SAAS,KAAA,GACpB,OAAO;EAGT,IAAM,IAAO,EAA0B,GAAS,CAAY;EAE5D,IAAI,CAAC,GACH,MAAU,MAAM,kCAAkC,GAAc;EAGlE,OAAO;GACL,GAAG;GACH,MAAM,MAAM,EAAK,KAAK;EACxB;CACF,SAAS,GAAG;EAGV,MAFA,EAAO,MAAM,CAAC,GAER;CACR;AACF,GC5CM,KAAN,MAAmB;CAWjB,YAAY,GAAmC;EAA3B,KAAA,oBAAA,iBAVX,IAAI,EAKV;GACD,QAAQ;GACR,OAAO;EACT,CAAC;CAE+C;CAEhD,OAAO,GAAsD;EAC3D,KAAK,OAAO,KAAK;GAAE,GAAG,KAAK,OAAO,SAAS;GAAG,GAAG;EAAO,CAAC;CAC3D;CAEA,IAAI,SAAS;EACX,OAAO,KAAK,OAAO,KAAK,GAAK,EAAE,eAAY,CAAK,CAAC;CACnD;CAEA,IAAI,QAAQ;EACV,OAAO,KAAK,OAAO,SAAS;CAC9B;CAEA,IAAI,cAAc;EAChB,OAAO,KAAK,OAAO,KACjB,GAAK,MAAU,KAAS,CAAC,GACzB,EAAqB,GACrB,EAAY,CACd;CACF;CAEA,IAAI,WAAW;EACb,OAAO,KAAK,YAAY,KACtB,GAAW,MACR,IAEG,KAAK,sBAAsB,WACzB,IACA,GAAM,KAAK,iBAAiB,IAH9B,CAIN,CACF;CACF;AACF,GAEa,MAAuB,EAClC,eACA,uBAAoB,MAAS,UAIzB;CACJ,IAAM,IAAc,IAAI,EAAgB,GAClC,IAAiB,IAAI,EAAc,GACnC,IAAe,IAAI,EAAc,GACjC,IAAyC,CAAC;CAqIhD,OAnIqB,EAAY,KAC/B,GAAU,MAAQ;EAChB,IAAM,IAAe,EAAS;EAE9B,IAAI,GAAc,MAAM,WAAW,QAAQ,OAAO;EAElD,IAAI,IAAW,IAET,KAAkB,MAAgB;GACtC,EAAO,MAAM,iCAAiC,GAAK;GAEnD,IAAM,IAAQ,EAAS;GAIvB,AAFA,OAAO,EAAS,IAEhB,AAEE,OADA,GAAO,MAAM,SAAS,MAAM,GACjB;EAEf;EAEA,EAAa,OAAO,EAClB,QAAQ,UACV,CAAC;EAED,IAAM,IAAS,EAAa,QACtB,IAAc,EAAa,aAE3B,IAAa,EAAO,KACxB,EAAS,GACT,GAAQ,CAAC,GAAM,OAAY,IAAS,CAAI,GACxC,EAAU,EAAI,CAChB;EAIA,OAFiB,EAAK,EAAW,CAAG,CAE7B,CAAA,CAAS,KACd,GAAK,MAAY;GACf,EAAa,OAAO;IAClB;IACA,QAAQ;GACV,CAAC;EACH,CAAC,GACD,GAAY,OACV,EAAe,CAAG,GAElB,EAAa,OAAO;GAClB,QAAQ;GACR;EACF,CAAC,GAEM,EACR,GACD,QAOqB,EANI,EAAW,KAChC,QAAgB,CAAY,GAC5B,QAAgB,CAAW,GAC3B,GAAQ,MAAe,CAAU,CAGV,GAAgB,EAAa,QAE/C,CAAA,CAAW,KAChB,EAAM,GACN,QAAU;GACR,EAAe,CAAG;EACpB,CAAC,CACH,CACD,CACH;CACF,CAAC,GACD,GAAU,CAAc,CA0D1B,CAAA,CAAa,UAAU,GAEhB;EACL,SA1Dc,MAAgB;GAC9B,IAAI,IAAgB,IAEd,IAAe,EAAS,MAAQ,IAAI,GAAa,CAAiB;GAIxE,AAFA,EAAS,KAAO,GAEhB,EAAa,OAAO,EAClB,OAAO,EAAa,MAAM,QAAQ,EACpC,CAAC;GAED,IAAM,UAAgB;IAChB,MAEJ,IAAgB,IAEhB,EAAa,OAAO,EAClB,OAAO,EAAa,MAAM,QAAQ,EACpC,CAAC;GACH;GAkBA,OAhBA,EAAY,KAAK,CAAG,GAgBb,EAdU,EAAa,OAAO,KACnC,GAAK,EAAE,iBAAc,CAAO,GAC5B,GAAQ,MAAY,CAAC,CAAC,CAAO,CAYlB,GATE,EAAa,OAAO,KACjC,GAAK,EAAE,eAAY;IACjB,IAAI,GACF,MAAM;GAEV,CAAC,GACD,EAAe,CAGM,CAAM,CAAC,CAAC,KAC7B,EAAM,GACN,GAAK,OAAa;IAAE;IAAS;GAAQ,EAAE,GACvC,GAAY,MAAU;IAGpB,MAFA,EAAQ,GAEF;GACR,CAAC,CACH;EACF;EAaE,aARkB;GAClB,EAAa,KAAK;EACpB;EAOE,WAAW;CACb;AACF,GC5NM,KAAsB,MACrB,IAOA,QAAQ,KAAK,CAAK,IAOhB;CACL,OAAO;CACP,OAAO,OAAO,SAAS,GAAO,EAAE;AAClC,IATS;CACL,OAAO;CACP,OAAO,KAAA;AACT,IAVO;CACL,OAAO;CACP,OAAO,KAAA;AACT,GAgBE,MAAwB,MAAwB;CACpD,IAAI,CAAC,EAAY,YAAY,CAAC,CAAC,WAAW,QAAQ,GAChD,OAAO,EACL,MAAM,UACR;CAGF,IAAM,IAAa,EAAY,MAAM,CAAe,CAAC,CAAC,KAAK;CAE3D,IAAI,CAAC,GACH,OAAO,EACL,MAAM,UACR;CAGF,IAAI,EAAW,SAAS,GAAG,GACzB,OAAO,EACL,MAAM,QACR;CAGF,IAAM,IAAU,gBAAgB,KAAK,CAAU;CAE/C,IAAI,CAAC,GACH,OAAO,EACL,MAAM,UACR;CAGF,IAAM,GAAG,IAAW,IAAI,IAAS,MAAM,GACjC,IAAc,EAAmB,EAAS,KAAK,CAAC,GAChD,IAAY,EAAmB,EAAO,KAAK,CAAC;CAQlD,OANI,CAAC,EAAY,SAAS,CAAC,EAAU,QAC5B,EACL,MAAM,UACR,IAGK;EACL,MAAM;EACN,OAAO,EAAY;EACnB,KAAK,EAAU;CACjB;AACF,GAQM,MAAyB,MAAwB;CACrD,IAAI,aAAgB,MAClB,OAAO;EACL,MAAM,EAAK;EACX,QAAQ,GAAe,MAAyB;GAC9C,IAAM,IAAO,EAAK,MAAM,GAAO,CAAY;GAC3C,OAAO;IAAE,SAAS;IAAM,QAAQ,EAAK;GAAK;EAC5C;CACF;CAGF,IAAM,IAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,CAAI;CAE3C,OAAO;EACL,MAAM,EAAM;EACZ,QAAQ,GAAe,MAAyB;GAC9C,IAAM,IAAO,EAAM,MAAM,GAAO,CAAY;GAC5C,OAAO;IAAE,SAAS;IAAM,QAAQ,EAAK;GAAW;EAClD;CACF;AACF,GAEa,MAAuB,EAClC,SACA,gBACA,qBAKI;CACJ,IAAM,IAAU,IAAI,QAAQ;CAQ5B,IANI,KACF,EAAQ,IAAI,gBAAgB,CAAW,GAGzC,EAAQ,IAAI,iBAAiB,OAAO,GAEhC,CAAC,GAKH,OAJI,aAAgB,QAClB,EAAQ,IAAI,kBAAkB,OAAO,EAAK,IAAI,CAAC,GAG1C,IAAI,SAAS,GAAM;EACxB,QAAQ;EACR;CACF,CAAC;CAGH,IAAM,IAAc,GAAqB,CAAW;CAEpD,IAAI,EAAY,SAAS,aAAa,EAAY,SAAS,SAKzD,OAJI,aAAgB,QAClB,EAAQ,IAAI,kBAAkB,OAAO,EAAK,IAAI,CAAC,GAG1C,IAAI,SAAS,GAAM;EACxB,QAAQ;EACR;CACF,CAAC;CAGH,IAAM,IAAY,GAAsB,CAAI,GACtC,IAAO,EAAU;CAEvB,IAAI,EAAY,SAAS,WACvB,OAAO,IAAI,SAAS,MAAM;EACxB,QAAQ;EACR,SAAS,EACP,iBAAiB,WAAW,IAC9B;CACF,CAAC;CAGH,IAAI,IAAQ,EAAY,OACpB,IAAM,EAAY;CAmBtB,IAjBI,MAAU,KAAA,KAAa,MAAQ,KAAA,MAS/B,MAAU,KAAA,KAEZ,IAAQ,KAAK,IAAI,GAAG,IADC,KAAK,IAAI,KAAO,GAAG,CACb,CAAY,GACvC,IAAM,IAAO,MACJ,MAAQ,KAAA,KAAa,KAAO,OACrC,IAAM,IAAO,IAGX,IAAQ,KAAK,IAAM,KAAK,KAAS,KAAQ,KAAO,KAAQ,IAAQ,IAClE,OAAO,IAAI,SAAS,MAAM;EACxB,QAAQ;EACR,SAAS,EACP,iBAAiB,WAAW,IAC9B;CACF,CAAC;CAGH,IAAM,IAAU,EAAU,MAAM,GAAO,IAAM,CAAC;CAK9C,OAHA,EAAQ,IAAI,kBAAkB,OAAO,EAAQ,MAAM,CAAC,GACpD,EAAQ,IAAI,iBAAiB,SAAS,EAAM,GAAG,EAAI,GAAG,GAAM,GAErD,IAAI,SAAS,EAAQ,SAAS;EACnC,QAAQ;EACR;CACF,CAAC;AACH,GC9JM,IAAe,WACf,KAAsB,gBAEtB,MAAsB,MAAyB;CACnD,IAAI;EACF,OAAO,mBAAmB,CAAY;CACxC,QAAQ;EACN,OAAO;CACT;AACF,GAEM,KAAqB,MACzB,EAAa,WAAW,CAAY,IAChC,EAAa,MAAM,EAAa,MAAM,IACtC,GAEA,MAAyB,MAAyB;CACtD,IAAM,IAAkC,EAAkB,CAAY;CAMtE,OAJI,GAAoB,KAAK,CAA+B,IACnD,IAGF,EAAkB,GAAmB,CAA+B,CAAC;AAC9E,GAEa,IAAb,MAAsB;CAWpB,YAAY,EACV,UACA,YACA,sBACA,GAAG,KAKF;EAMD,gBAxB4B,OAC5B,QAAQ,MAAM,CAAK,GAEZ,IAAI,SAAS,OAAO,CAAK,GAAG,EAAE,QAAQ,IAAI,CAAC,IAgBlD,KAAK,gBAAgB,GAAoB,CAAI,GAC7C,KAAK,QAAQ,KAAS,CAAC,GAEvB,KAAK,oBACH,OAAuB,EAAE,kBAAe,QAAQ,QAAQ,CAAQ,IAClE,KAAK,UAAU,KAAW,KAAK;CACjC;CAEA,QAAe;EACb,KAAK,cAAc,MAAM;CAC3B;CAEA,cAAqB,GAAa;EAOhC,OANI,KAAK,oBAAoB,KAAA,KAAa,KAAK,oBAAoB,KACjE,KAAK,cAAc,MAAM,GAG3B,KAAK,kBAAkB,GAEhB,KAAK,cAAc,OAAO,CAAG;CACtC;CAEA,yBAAgC,GAAa;EAC3C,OAAO,KAAK,cAAc,CAAG,CAAC,CAAC,KAC7B,GAAK,EAAE,YAAS,kBACd,EAAQ,GAED,EACR,CACH;CACF;CAEA,oBAA8B,EAC5B,QACA,kBAIC;EAcD,OAAO,EAbW,KAAK,cAAc,CAAG,CAAC,CAAC,KACxC,GAAU,EAAE,YAAS,iBACnB,EAAK,EAAY,CAAO,CAAC,CAAC,CAAC,KACzB,SAAe;GACb,EAAQ;EACV,CAAC,CACH,CACF,GACA,GAAY,MACH,GAAG,KAAK,QAAQ,CAAK,CAAC,CAC9B,CAGkB,CAAS;CAChC;CAEA,cAAqB,EAAE,QAAK,cAA8C;EACxE,OAAO,KAAK,oBAAoB;GAC9B;GACA,cAAc,MACM,EAChB,EAA4B,GAAS;IACnC;IACA,OAAO,KAAK,MAAM;GACpB,CAAC,CAGI,CAAA,CAAU,KACf,GAAW,MACT,EAAK,KAAK,kBAAkB;IAAE;IAAU;GAAQ,CAAC,CAAC,CACpD,GACA,GACG,MACC,IAAI,SAAS,KAAK,UAAU,CAA2B,GAAG,EACxD,QAAQ,IACV,CAAC,CACL,CACF;EAEJ,CAAC;CACH;CAEA,cAAqB,EACnB,QACA,iBACA,cAKC;EACD,OAAO,KAAK,oBAAoB;GAC9B;GACA,cAAc,MAcM,EAChB,EAA4B,GARF,GAAsB,CAQX,GAAqB,EACxD,OAAO,KAAK,MAAM,SACpB,CAAC,CAGI,CAAA,CAAU,KACf,GAAK,MACH,GAAoB;IAClB,MAAM,EAAS,QAAQ;IACvB,aAAa,EAAS,OAAO;IAC7B,aAAa,GAAS,QAAQ,IAAI,OAAO;GAC3C,CAAC,CACH,CACF;EAEJ,CAAC;CACH;AACF,GC9La,KAAb,cAA2C,EAAS;CAOlD,YAAY,EACV,eACA,GAAG,KAOF;EAID,AAHA,MAAM,CAAI,GAEV,KAAK,aAAa,GAClB,KAAK,qBAAqB,KAAK,mBAAmB,KAAK,IAAI;CAC7D;CAEA,mBAAmB,GAAwC;EACzD,IAAI;GACF,IAAM,IAAU,KAAK,WAAW,CAAK;GAErC,IAAI,CAAC,GAAS;GAEd,IAAM,IAAU,EAAoB,EAAQ,OAAO,GAC7C,IAAe,EAAM,QAAQ,IAAI,UACrC,EAAQ,SAAS,CACnB,GACM,CAAC,IAAM,MAAM,EAAa,MAAM,GAAG,GACnC,IAAe,EACnB,EAAa,UAAU,EAAI,SAAS,CAAU,CAChD;GAEA,AAAI,EAAa,SAAS,WAAW,IACnC,EAAM,YACJ,KAAK,cAAc;IAAE;IAAK,SAAS,GAAG,EAAQ,GAAG,EAAI;GAAG,CAAC,CAC3D,IAEA,EAAM,YACJ,KAAK,cAAc;IACjB;IACA;IACA,SAAS,EAAM;GACjB,CAAC,CACH;EAEJ,SAAS,GAAG;GACV,EAAM,YAAY,IAAI,SAAS,OAAO,CAAC,GAAG,EAAE,QAAQ,IAAI,CAAC,CAAC;EAC5D;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prose-reader/streamer",
3
- "version": "1.333.0",
3
+ "version": "1.335.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index/index.cjs",
6
6
  "module": "./dist/index/index.js",
@@ -24,12 +24,12 @@
24
24
  "test:watch": "vitest watch"
25
25
  },
26
26
  "dependencies": {
27
- "@prose-reader/archive-reader": "^1.333.0",
28
- "@prose-reader/shared": "^1.333.0",
27
+ "@prose-reader/archive-reader": "^1.335.0",
28
+ "@prose-reader/shared": "^1.335.0",
29
29
  "xmldoc": "^2.0.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "rxjs": "*"
33
33
  },
34
- "gitHead": "c0b2b7f2732801c2ecbfdd8c85234e621025b116"
34
+ "gitHead": "46905ab66448182c3e49a370f09db82548fdcca3"
35
35
  }